feat(mcp): enrich process notifications with runtime context
Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
ad6ccd09bb
commit
e09f3518e0
4 changed files with 74 additions and 11 deletions
|
|
@ -100,6 +100,9 @@ func (s *Service) emitTestResult(ctx context.Context, processID string, exitCode
|
|||
"status": status,
|
||||
"passed": status == "passed",
|
||||
}
|
||||
if meta.Dir != "" {
|
||||
payload["dir"] = meta.Dir
|
||||
}
|
||||
if !meta.StartedAt.IsZero() {
|
||||
payload["startedAt"] = meta.StartedAt
|
||||
}
|
||||
|
|
|
|||
|
|
@ -109,18 +109,20 @@ func (s *Service) HandleIPCEvents(c *core.Core, msg core.Message) core.Result {
|
|||
case ChannelPush:
|
||||
s.ChannelSend(ctx, ev.Channel, ev.Data)
|
||||
case process.ActionProcessStarted:
|
||||
startedAt := time.Now()
|
||||
s.recordProcessRuntime(ev.ID, processRuntime{
|
||||
Command: ev.Command,
|
||||
Args: ev.Args,
|
||||
Dir: ev.Dir,
|
||||
StartedAt: time.Now(),
|
||||
StartedAt: startedAt,
|
||||
})
|
||||
s.ChannelSend(ctx, ChannelProcessStart, map[string]any{
|
||||
"id": ev.ID,
|
||||
"command": ev.Command,
|
||||
"args": ev.Args,
|
||||
"dir": ev.Dir,
|
||||
"pid": ev.PID,
|
||||
"id": ev.ID,
|
||||
"command": ev.Command,
|
||||
"args": ev.Args,
|
||||
"dir": ev.Dir,
|
||||
"pid": ev.PID,
|
||||
"startedAt": startedAt,
|
||||
})
|
||||
case process.ActionProcessOutput:
|
||||
s.ChannelSend(ctx, ChannelProcessOutput, map[string]any{
|
||||
|
|
@ -129,11 +131,20 @@ func (s *Service) HandleIPCEvents(c *core.Core, msg core.Message) core.Result {
|
|||
"stream": ev.Stream,
|
||||
})
|
||||
case process.ActionProcessExited:
|
||||
meta, ok := s.processRuntimeFor(ev.ID)
|
||||
payload := map[string]any{
|
||||
"id": ev.ID,
|
||||
"exitCode": ev.ExitCode,
|
||||
"duration": ev.Duration,
|
||||
}
|
||||
if ok {
|
||||
payload["command"] = meta.Command
|
||||
payload["args"] = meta.Args
|
||||
payload["dir"] = meta.Dir
|
||||
if !meta.StartedAt.IsZero() {
|
||||
payload["startedAt"] = meta.StartedAt
|
||||
}
|
||||
}
|
||||
if ev.Error != nil {
|
||||
payload["error"] = ev.Error.Error()
|
||||
}
|
||||
|
|
@ -144,10 +155,20 @@ func (s *Service) HandleIPCEvents(c *core.Core, msg core.Message) core.Result {
|
|||
}
|
||||
s.emitTestResult(ctx, ev.ID, ev.ExitCode, ev.Duration, "", errText)
|
||||
case process.ActionProcessKilled:
|
||||
s.ChannelSend(ctx, ChannelProcessExit, map[string]any{
|
||||
meta, ok := s.processRuntimeFor(ev.ID)
|
||||
payload := map[string]any{
|
||||
"id": ev.ID,
|
||||
"signal": ev.Signal,
|
||||
})
|
||||
}
|
||||
if ok {
|
||||
payload["command"] = meta.Command
|
||||
payload["args"] = meta.Args
|
||||
payload["dir"] = meta.Dir
|
||||
if !meta.StartedAt.IsZero() {
|
||||
payload["startedAt"] = meta.StartedAt
|
||||
}
|
||||
}
|
||||
s.ChannelSend(ctx, ChannelProcessExit, payload)
|
||||
s.emitTestResult(ctx, ev.ID, 0, 0, ev.Signal, "")
|
||||
}
|
||||
return core.Result{OK: true}
|
||||
|
|
|
|||
|
|
@ -135,6 +135,15 @@ func TestHandleIPCEvents_Good_ForwardsProcessActions(t *testing.T) {
|
|||
if payload["id"] != "proc-1" || payload["command"] != "go" {
|
||||
t.Fatalf("unexpected payload: %#v", payload)
|
||||
}
|
||||
if payload["dir"] != "/workspace" {
|
||||
t.Fatalf("expected dir /workspace, got %#v", payload["dir"])
|
||||
}
|
||||
if payload["pid"] != float64(1234) {
|
||||
t.Fatalf("expected pid 1234, got %#v", payload["pid"])
|
||||
}
|
||||
if payload["args"] == nil {
|
||||
t.Fatalf("expected args in payload, got %#v", payload)
|
||||
}
|
||||
return
|
||||
case <-deadline.C:
|
||||
t.Fatal("timed out waiting for process start notification")
|
||||
|
|
@ -311,6 +320,9 @@ func TestHandleIPCEvents_Good_ForwardsTestResult(t *testing.T) {
|
|||
if payload["id"] != "proc-test" || payload["command"] != "go" {
|
||||
t.Fatalf("unexpected payload: %#v", payload)
|
||||
}
|
||||
if payload["dir"] != nil {
|
||||
t.Fatalf("expected dir to be absent when not recorded, got %#v", payload["dir"])
|
||||
}
|
||||
if payload["status"] != "passed" || payload["passed"] != true {
|
||||
t.Fatalf("expected passed test result, got %#v", payload)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -208,7 +208,12 @@ func (s *Service) processStart(ctx context.Context, req *mcp.CallToolRequest, in
|
|||
StartedAt: output.StartedAt,
|
||||
})
|
||||
s.ChannelSend(ctx, ChannelProcessStart, map[string]any{
|
||||
"id": output.ID, "pid": output.PID, "command": output.Command,
|
||||
"id": output.ID,
|
||||
"pid": output.PID,
|
||||
"command": output.Command,
|
||||
"args": output.Args,
|
||||
"dir": info.Dir,
|
||||
"startedAt": output.StartedAt,
|
||||
})
|
||||
return nil, output, nil
|
||||
}
|
||||
|
|
@ -234,7 +239,15 @@ func (s *Service) processStop(ctx context.Context, req *mcp.CallToolRequest, inp
|
|||
return nil, ProcessStopOutput{}, log.E("processStop", "failed to stop process", err)
|
||||
}
|
||||
|
||||
s.ChannelSend(ctx, ChannelProcessExit, map[string]any{"id": input.ID, "signal": "stop"})
|
||||
info := proc.Info()
|
||||
s.ChannelSend(ctx, ChannelProcessExit, map[string]any{
|
||||
"id": input.ID,
|
||||
"signal": "stop",
|
||||
"command": info.Command,
|
||||
"args": info.Args,
|
||||
"dir": info.Dir,
|
||||
"startedAt": info.StartedAt,
|
||||
})
|
||||
s.emitTestResult(ctx, input.ID, 0, 0, "stop", "")
|
||||
return nil, ProcessStopOutput{
|
||||
ID: input.ID,
|
||||
|
|
@ -251,12 +264,26 @@ func (s *Service) processKill(ctx context.Context, req *mcp.CallToolRequest, inp
|
|||
return nil, ProcessKillOutput{}, errIDEmpty
|
||||
}
|
||||
|
||||
proc, err := s.processService.Get(input.ID)
|
||||
if err != nil {
|
||||
log.Error("mcp: process kill failed", "id", input.ID, "err", err)
|
||||
return nil, ProcessKillOutput{}, log.E("processKill", "process not found", err)
|
||||
}
|
||||
|
||||
if err := s.processService.Kill(input.ID); err != nil {
|
||||
log.Error("mcp: process kill failed", "id", input.ID, "err", err)
|
||||
return nil, ProcessKillOutput{}, log.E("processKill", "failed to kill process", err)
|
||||
}
|
||||
|
||||
s.ChannelSend(ctx, ChannelProcessExit, map[string]any{"id": input.ID, "signal": "kill"})
|
||||
info := proc.Info()
|
||||
s.ChannelSend(ctx, ChannelProcessExit, map[string]any{
|
||||
"id": input.ID,
|
||||
"signal": "kill",
|
||||
"command": info.Command,
|
||||
"args": info.Args,
|
||||
"dir": info.Dir,
|
||||
"startedAt": info.StartedAt,
|
||||
})
|
||||
s.emitTestResult(ctx, input.ID, 0, 0, "kill", "")
|
||||
return nil, ProcessKillOutput{
|
||||
ID: input.ID,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue