@akira-tl/forgerelay 0.5.1 → 0.5.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/server.js CHANGED
@@ -16,7 +16,11 @@ import express from "express";
16
16
  import * as z from "zod/v4";
17
17
  import { applyPatch } from "./apply-patch.js";
18
18
  import { ActivityAuditStore } from "./activity/audit-store.js";
19
- import { ActivityLifecycle } from "./activity/lifecycle.js";
19
+ import { BashOutputStore } from "./activity/bash-output-store.js";
20
+ import { HostTurnStore } from "./activity/host-turn-store.js";
21
+ import { registerActivityQueryTools } from "./activity/mcp-query-tools.js";
22
+ import { ActivityLifecycle, } from "./activity/lifecycle.js";
23
+ import { ActivityQueryService } from "./activity/query-service.js";
20
24
  import { buildCapabilityFingerprint } from "./capabilities.js";
21
25
  import { CapabilityError, createCapabilityRegistry, } from "./capability-registry.js";
22
26
  import { deletePath, renamePath } from "./file-mutations.js";
@@ -468,6 +472,24 @@ async function readWorkspaceAppResource(config, requestedUri, transportSessionId
468
472
  throw error;
469
473
  }
470
474
  }
475
+ const PROCESS_RESPONSE_OUTPUT_LINES = 10;
476
+ function compactProcessOutput(output) {
477
+ if (!output)
478
+ return { output: "", truncated: false };
479
+ const trailingNewline = output.endsWith("\n");
480
+ const body = trailingNewline ? output.slice(0, -1) : output;
481
+ const lines = body.split("\n");
482
+ if (lines.length <= PROCESS_RESPONSE_OUTPUT_LINES)
483
+ return { output, truncated: false };
484
+ const compact = lines.slice(-PROCESS_RESPONSE_OUTPUT_LINES).join("\n");
485
+ return {
486
+ output: trailingNewline ? `${compact}\n` : compact,
487
+ truncated: true,
488
+ };
489
+ }
490
+ function outputIdNotice(outputId) {
491
+ return outputId ? `Full output ID: ${outputId}.` : "";
492
+ }
471
493
  function processResult(snapshot) {
472
494
  const status = snapshot.running
473
495
  ? `Process running with process ID ${snapshot.processId}.`
@@ -476,7 +498,8 @@ function processResult(snapshot) {
476
498
  : snapshot.signal
477
499
  ? `Process exited after signal ${snapshot.signal}.`
478
500
  : `Process exited with code ${snapshot.exitCode ?? "unknown"}.`;
479
- return snapshot.output ? `${snapshot.output.replace(/\n$/, "")}\n${status}` : status;
501
+ const compact = compactProcessOutput(snapshot.output).output.replace(/\n$/, "");
502
+ return [compact, status, outputIdNotice(snapshot.outputId)].filter(Boolean).join("\n");
480
503
  }
481
504
  function completedProcessResult(snapshot) {
482
505
  const status = snapshot.timedOut
@@ -485,12 +508,14 @@ function completedProcessResult(snapshot) {
485
508
  ? `Background process ${snapshot.processId} exited after signal ${snapshot.signal}.`
486
509
  : `Background process ${snapshot.processId} exited with code ${snapshot.exitCode ?? "unknown"}.`;
487
510
  const command = `Command: ${snapshot.command}`;
488
- const output = snapshot.output ? `\n${snapshot.output.replace(/\n$/, "")}` : "";
489
- return `${status}\n${command}${output}`;
511
+ const output = compactProcessOutput(snapshot.output).output.replace(/\n$/, "");
512
+ return [status, command, output, outputIdNotice(snapshot.outputId)].filter(Boolean).join("\n");
490
513
  }
491
- function attachCompletedProcessNotices(processSessions, workspaceId, result) {
514
+ function attachCompletedProcessNotices(processSessions, workspaceId, result, onCompleted) {
492
515
  if (result instanceof Error) {
493
516
  const completed = processSessions.takeCompleted(workspaceId);
517
+ for (const snapshot of completed)
518
+ onCompleted?.(snapshot);
494
519
  if (completed.length > 0) {
495
520
  result.message = [
496
521
  result.message,
@@ -513,6 +538,8 @@ function attachCompletedProcessNotices(processSessions, workspaceId, result) {
513
538
  : undefined
514
539
  : undefined;
515
540
  const completed = processSessions.takeCompleted(workspaceId, undefined, currentProcessId);
541
+ for (const snapshot of completed)
542
+ onCompleted?.(snapshot);
516
543
  if (completed.length === 0)
517
544
  return result;
518
545
  return {
@@ -527,6 +554,7 @@ function processOutputSchema() {
527
554
  return resultOutputSchema({
528
555
  processId: z.number().int().positive().optional().describe("Canonical process handle for bash(action=\"process\") or the active command adapter."),
529
556
  sessionId: z.number().int().positive().optional().describe("Deprecated alias of processId for compatibility."),
557
+ outputId: z.string().optional().describe("Stable local audit identifier for retrieving the complete original process output."),
530
558
  running: z.boolean(),
531
559
  exitCode: z.number().int().optional(),
532
560
  signal: z.string().optional(),
@@ -543,9 +571,10 @@ function readForgeRelayVersion() {
543
571
  return packageJson.version;
544
572
  }
545
573
  function processToolResponse(tool, workspaceId, snapshot, summary) {
574
+ const compact = compactProcessOutput(snapshot.output);
546
575
  const result = processResult(snapshot);
547
576
  const content = [textBlock(result)];
548
- const outputSummary = textSummary(snapshot.output ? [textBlock(snapshot.output)] : []);
577
+ const outputSummary = textSummary(compact.output ? [textBlock(compact.output)] : []);
549
578
  return {
550
579
  content,
551
580
  _meta: {
@@ -560,15 +589,111 @@ function processToolResponse(tool, workspaceId, snapshot, summary) {
560
589
  result,
561
590
  processId: snapshot.processId,
562
591
  sessionId: snapshot.sessionId,
592
+ outputId: snapshot.outputId,
563
593
  running: snapshot.running,
564
594
  exitCode: snapshot.exitCode,
565
595
  signal: snapshot.signal,
566
596
  timedOut: snapshot.timedOut,
567
597
  wallTimeMs: snapshot.wallTimeMs,
568
- outputTruncated: snapshot.outputTruncated,
598
+ outputTruncated: snapshot.outputTruncated || compact.truncated,
569
599
  },
570
600
  };
571
601
  }
602
+ function durableOutputResult(record) {
603
+ const status = record.status === "running"
604
+ ? `Process ${record.processId} is still running.`
605
+ : record.timedOut
606
+ ? `Process ${record.processId} timed out and was terminated.`
607
+ : record.signal
608
+ ? `Process ${record.processId} exited after signal ${record.signal}.`
609
+ : `Process ${record.processId} exited with code ${record.exitCode ?? "unknown"}.`;
610
+ return [record.output.replace(/\n$/, ""), status, `Full output ID: ${record.outputId}.`]
611
+ .filter(Boolean)
612
+ .join("\n");
613
+ }
614
+ function durableOutputResponse(tool, workspaceId, record) {
615
+ const result = durableOutputResult(record);
616
+ const content = [textBlock(result)];
617
+ const finishedAt = record.finishedAt ? Date.parse(record.finishedAt) : Date.now();
618
+ const startedAt = Date.parse(record.startedAt);
619
+ return {
620
+ content,
621
+ _meta: {
622
+ tool,
623
+ card: {
624
+ workspaceId,
625
+ summary: textSummary(record.output ? [textBlock(record.output)] : []),
626
+ payload: { content },
627
+ },
628
+ },
629
+ structuredContent: {
630
+ result,
631
+ processId: record.processId,
632
+ sessionId: record.processId,
633
+ outputId: record.outputId,
634
+ running: record.status === "running",
635
+ exitCode: record.exitCode,
636
+ signal: record.signal,
637
+ timedOut: record.timedOut,
638
+ wallTimeMs: Math.max(0, Number.isFinite(finishedAt - startedAt) ? finishedAt - startedAt : 0),
639
+ outputTruncated: false,
640
+ },
641
+ };
642
+ }
643
+ function markReturnedOutput(store, result) {
644
+ if (typeof result !== "object" || result === null)
645
+ return;
646
+ const structured = result.structuredContent;
647
+ if (typeof structured !== "object" || structured === null)
648
+ return;
649
+ const record = structured;
650
+ if (record.running === true && typeof record.outputId === "string") {
651
+ store.markReturned(record.outputId);
652
+ }
653
+ }
654
+ function readWorkspaceBashOutput(store, workspaceId, outputId) {
655
+ const record = store.read(outputId);
656
+ if (!record)
657
+ throw new Error(`Unknown Bash output: ${outputId}`);
658
+ if (record.workspaceId !== workspaceId) {
659
+ throw new Error(`Bash output ${outputId} does not belong to workspace ${workspaceId}.`);
660
+ }
661
+ return record;
662
+ }
663
+ function bashCompletionError(record) {
664
+ if (record.error)
665
+ return record.error;
666
+ if (record.timedOut)
667
+ return `Background process ${record.processId} timed out.`;
668
+ if (record.signal)
669
+ return `Background process ${record.processId} exited after signal ${record.signal}.`;
670
+ return `Background process ${record.processId} exited with code ${record.exitCode ?? "unknown"}.`;
671
+ }
672
+ function recordBashCompletion(lifecycle, store, outputId) {
673
+ if (!outputId)
674
+ return;
675
+ const completion = store.claimCompletion(outputId);
676
+ if (!completion)
677
+ return;
678
+ lifecycle.recordLinked({
679
+ sourceActivityId: completion.activityId,
680
+ tool: "bash_result",
681
+ request: {
682
+ processId: completion.processId,
683
+ outputId: completion.outputId,
684
+ },
685
+ result: {
686
+ processId: completion.processId,
687
+ outputId: completion.outputId,
688
+ exitCode: completion.exitCode,
689
+ signal: completion.signal,
690
+ timedOut: completion.timedOut,
691
+ },
692
+ outcome: completion.status === "failed"
693
+ ? { type: "failed", error: bashCompletionError(completion) }
694
+ : { type: "succeeded" },
695
+ });
696
+ }
572
697
  function workspaceHookInvocation(workspace) {
573
698
  return {
574
699
  workspaceId: workspace.id,
@@ -669,7 +794,7 @@ function runActivityTool(lifecycle, workspace, requestMeta, tool, request, opera
669
794
  function runActivityToolWithHooks(lifecycle, hooks, workspace, requestMeta, request, hookOptions) {
670
795
  return runActivityTool(lifecycle, workspace, requestMeta, hookOptions.tool, request, () => runToolWithHooks(hooks, hookOptions));
671
796
  }
672
- function registerProcessTools(server, config, workspaces, processSessions, hooks, activityLifecycle) {
797
+ function registerProcessTools(server, config, workspaces, processSessions, hooks, activityLifecycle, bashOutputStore) {
673
798
  if (config.toolMode === "codex") {
674
799
  registerAppTool(server, "exec_command", {
675
800
  title: "Execute command",
@@ -715,7 +840,7 @@ function registerProcessTools(server, config, workspaces, processSessions, hooks
715
840
  }, async ({ workspaceId, cmd, tty, columns, rows, workingDirectory, yieldTimeMs, timeoutMs, maxOutputTokens }, extra) => {
716
841
  const workspace = workspaces.getWorkspace(workspaceId);
717
842
  let undeliveredProcessId;
718
- return runActivityTool(activityLifecycle, workspace, extra._meta, "exec_command", { workspaceId, cmd, tty, columns, rows, workingDirectory, yieldTimeMs, timeoutMs, maxOutputTokens }, async () => {
843
+ const activityResult = await runActivityTool(activityLifecycle, workspace, extra._meta, "exec_command", { workspaceId, cmd, tty, columns, rows, workingDirectory, yieldTimeMs, timeoutMs, maxOutputTokens }, async (activityContext) => {
719
844
  try {
720
845
  const result = await runToolWithHooks(hooks, {
721
846
  signal: extra.signal,
@@ -739,6 +864,7 @@ function registerProcessTools(server, config, workspaces, processSessions, hooks
739
864
  maxOutputTokens,
740
865
  codexCi: true,
741
866
  signal: extra.signal,
867
+ audit: activityContext,
742
868
  });
743
869
  undeliveredProcessId = snapshot.running ? snapshot.processId : undefined;
744
870
  logToolCall(config, {
@@ -772,17 +898,20 @@ function registerProcessTools(server, config, workspaces, processSessions, hooks
772
898
  throw error;
773
899
  }
774
900
  }, processActivityOutcome);
901
+ markReturnedOutput(bashOutputStore, activityResult);
902
+ return activityResult;
775
903
  });
776
904
  }
777
905
  if (config.toolMode !== "codex")
778
906
  return;
779
907
  registerAppTool(server, "write_stdin", {
780
908
  title: "Write to process",
781
- description: "Poll or write characters to a running process returned by bash or exec_command. Omit chars or pass an empty string to poll. Waiting never kills the process; pass \\u0003 to explicitly send Ctrl-C.",
909
+ description: "Poll or write characters to a running process returned by exec_command, or retrieve complete durable process output by outputId. Omit chars or pass an empty string to poll. Waiting never kills the process; pass \\u0003 to explicitly send Ctrl-C.",
782
910
  inputSchema: {
783
911
  workspaceId: z.string().describe("Workspace identifier used to start the process."),
784
912
  processId: z.number().int().positive().optional().describe("Canonical process identifier returned by bash or exec_command."),
785
913
  sessionId: z.number().int().positive().optional().describe("Deprecated alias for processId. Retained for compatibility."),
914
+ outputId: z.string().optional().describe("Stable output identifier returned by exec_command. When supplied, retrieve the complete durable output instead of controlling a process."),
786
915
  chars: z.string().optional().describe("Characters to write. Omit or pass an empty string to poll."),
787
916
  columns: z.number().int().min(1).max(1_000).optional().describe("Resize a PTY to this width."),
788
917
  rows: z.number().int().min(1).max(1_000).optional().describe("Resize a PTY to this height."),
@@ -804,8 +933,21 @@ function registerProcessTools(server, config, workspaces, processSessions, hooks
804
933
  outputSchema: processOutputSchema(),
805
934
  ...toolWidgetDescriptorMeta(config, "shell"),
806
935
  annotations: SHELL_TOOL_ANNOTATIONS,
807
- }, async ({ workspaceId, processId, sessionId, chars, columns, rows, yieldTimeMs, maxOutputTokens }, extra) => {
936
+ }, async ({ workspaceId, processId, sessionId, outputId, chars, columns, rows, yieldTimeMs, maxOutputTokens }, extra) => {
808
937
  const workspace = workspaces.getWorkspace(workspaceId);
938
+ if (outputId !== undefined) {
939
+ if (processId !== undefined || sessionId !== undefined || chars !== undefined || columns !== undefined ||
940
+ rows !== undefined || yieldTimeMs !== undefined || maxOutputTokens !== undefined) {
941
+ throw new Error("write_stdin outputId lookup cannot be combined with process control fields.");
942
+ }
943
+ return runToolWithHooks(hooks, {
944
+ signal: extra.signal,
945
+ tool: "write_stdin",
946
+ invocation: workspaceHookInvocation(workspace),
947
+ payload: { outputId },
948
+ operation: async () => durableOutputResponse("write_stdin", workspaceId, readWorkspaceBashOutput(bashOutputStore, workspaceId, outputId)),
949
+ });
950
+ }
809
951
  const resolvedProcessId = resolveProcessId(processId, sessionId);
810
952
  return runToolWithHooks(hooks, {
811
953
  signal: extra.signal,
@@ -838,20 +980,24 @@ function registerProcessTools(server, config, workspaces, processSessions, hooks
838
980
  success: snapshot.running || snapshot.exitCode === 0,
839
981
  durationMs: Math.round(performance.now() - startedAt),
840
982
  });
841
- return processToolResponse("write_stdin", workspaceId, snapshot, {
983
+ const response = processToolResponse("write_stdin", workspaceId, snapshot, {
842
984
  processId: resolvedProcessId,
843
985
  charactersWritten: chars?.length ?? 0,
844
986
  running: snapshot.running,
845
987
  exitCode: snapshot.exitCode,
846
988
  wallTimeMs: snapshot.wallTimeMs,
847
989
  });
990
+ if (!snapshot.running) {
991
+ recordBashCompletion(activityLifecycle, bashOutputStore, snapshot.outputId);
992
+ }
993
+ return response;
848
994
  },
849
995
  });
850
996
  });
851
997
  }
852
- export function createMcpServer(config, workspaces, reviewCheckpoints, processSessions, localAgentProviders, incomingArtifactAdapters, codeIntelligence, activityLifecycle) {
998
+ export function createMcpServer(config, workspaces, reviewCheckpoints, processSessions, localAgentProviders, incomingArtifactAdapters, codeIntelligence, activityLifecycle, bashOutputStore, activityQueries) {
853
999
  const toolDescriptions = buildToolDescriptions(config);
854
- const hooks = new HookRunner(config.hooks, config.logging, process.env, (workspaceId, result) => attachCompletedProcessNotices(processSessions, workspaceId, result));
1000
+ const hooks = new HookRunner(config.hooks, config.logging, process.env, (workspaceId, result) => attachCompletedProcessNotices(processSessions, workspaceId, result, (snapshot) => recordBashCompletion(activityLifecycle, bashOutputStore, snapshot.outputId)));
855
1001
  const incomingArtifactRegistry = new IncomingArtifactAdapterRegistry(incomingArtifactAdapters);
856
1002
  const artifactDownloadAvailable = config.artifactsEnabled && isArtifactDownloadSupportedPlatform();
857
1003
  const reviewChangesAvailable = config.widgets === "changes";
@@ -1322,6 +1468,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
1322
1468
  },
1323
1469
  }, hookReports));
1324
1470
  });
1471
+ registerActivityQueryTools(server, activityQueries);
1325
1472
  registerAppTool(server, toolNames.capability, {
1326
1473
  title: "Use optional capability",
1327
1474
  description: "Describe or run one optional ForgeRelay capability advertised by open_workspace. Use describe when the capability contract is unfamiliar, then read its advertised guide if needed. Run dispatches only explicitly registered capabilities; it cannot invoke arbitrary shell commands, URLs, or methods.",
@@ -2045,9 +2192,9 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
2045
2192
  .string()
2046
2193
  .describe("Workspace identifier returned by open_workspace."),
2047
2194
  action: z
2048
- .enum(["run", "process"])
2195
+ .enum(["run", "process", "output"])
2049
2196
  .optional()
2050
- .describe("Defaults to run. Use process with a returned processId to poll, interact, resize, or interrupt a running command."),
2197
+ .describe("Defaults to run. Use process with a returned processId to poll/interact, or output with outputId to retrieve complete durable output."),
2051
2198
  command: z
2052
2199
  .string()
2053
2200
  .optional()
@@ -2058,6 +2205,10 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
2058
2205
  .positive()
2059
2206
  .optional()
2060
2207
  .describe("Process identifier returned by a previous bash action=run call. Required for action=process."),
2208
+ outputId: z
2209
+ .string()
2210
+ .optional()
2211
+ .describe("Stable output identifier returned by a Bash run. Required for action=output."),
2061
2212
  input: z
2062
2213
  .string()
2063
2214
  .optional()
@@ -2113,16 +2264,16 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
2113
2264
  outputSchema: processOutputSchema(),
2114
2265
  ...toolWidgetDescriptorMeta(config, "shell"),
2115
2266
  annotations: SHELL_TOOL_ANNOTATIONS,
2116
- }, async ({ workspaceId, action = "run", command, processId, input, interrupt, tty, columns, rows, workingDirectory, yieldTimeMs, timeoutMs, maxOutputTokens, }, extra) => {
2267
+ }, async ({ workspaceId, action = "run", command, processId, outputId, input, interrupt, tty, columns, rows, workingDirectory, yieldTimeMs, timeoutMs, maxOutputTokens, }, extra) => {
2117
2268
  const workspace = workspaces.getWorkspace(workspaceId);
2118
2269
  if (action === "run") {
2119
2270
  if (!command)
2120
2271
  throw new Error("bash action=run requires command.");
2121
- if (processId !== undefined || input !== undefined || interrupt !== undefined) {
2122
- throw new Error("bash action=run does not accept processId, input, or interrupt.");
2272
+ if (processId !== undefined || outputId !== undefined || input !== undefined || interrupt !== undefined) {
2273
+ throw new Error("bash action=run does not accept processId, outputId, input, or interrupt.");
2123
2274
  }
2124
2275
  let undeliveredProcessId;
2125
- return runActivityTool(activityLifecycle, workspace, extra._meta, toolNames.shell, {
2276
+ const activityResult = await runActivityTool(activityLifecycle, workspace, extra._meta, toolNames.shell, {
2126
2277
  workspaceId,
2127
2278
  action,
2128
2279
  command,
@@ -2133,7 +2284,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
2133
2284
  yieldTimeMs,
2134
2285
  timeoutMs,
2135
2286
  maxOutputTokens,
2136
- }, async () => {
2287
+ }, async (activityContext) => {
2137
2288
  try {
2138
2289
  const result = await runToolWithHooks(hooks, {
2139
2290
  signal: extra.signal,
@@ -2161,6 +2312,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
2161
2312
  timeoutMs,
2162
2313
  maxOutputTokens,
2163
2314
  signal: extra.signal,
2315
+ audit: activityContext,
2164
2316
  });
2165
2317
  undeliveredProcessId = snapshot.running ? snapshot.processId : undefined;
2166
2318
  logToolCall(config, {
@@ -2198,7 +2350,27 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
2198
2350
  throw error;
2199
2351
  }
2200
2352
  }, processActivityOutcome);
2353
+ markReturnedOutput(bashOutputStore, activityResult);
2354
+ return activityResult;
2201
2355
  }
2356
+ if (action === "output") {
2357
+ if (!outputId)
2358
+ throw new Error("bash action=output requires outputId.");
2359
+ if (command !== undefined || processId !== undefined || input !== undefined || interrupt !== undefined ||
2360
+ tty !== undefined || columns !== undefined || rows !== undefined || workingDirectory !== undefined ||
2361
+ yieldTimeMs !== undefined || timeoutMs !== undefined || maxOutputTokens !== undefined) {
2362
+ throw new Error("bash action=output accepts only workspaceId and outputId.");
2363
+ }
2364
+ return runToolWithHooks(hooks, {
2365
+ signal: extra.signal,
2366
+ tool: toolNames.shell,
2367
+ invocation: workspaceHookInvocation(workspace),
2368
+ payload: { action, outputId },
2369
+ operation: async () => durableOutputResponse(toolNames.shell, workspaceId, readWorkspaceBashOutput(bashOutputStore, workspaceId, outputId)),
2370
+ });
2371
+ }
2372
+ if (outputId !== undefined)
2373
+ throw new Error("bash action=process does not accept outputId.");
2202
2374
  if (command !== undefined || workingDirectory !== undefined || tty !== undefined || timeoutMs !== undefined) {
2203
2375
  throw new Error("bash action=process does not accept command, workingDirectory, tty, or timeoutMs.");
2204
2376
  }
@@ -2241,7 +2413,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
2241
2413
  success: snapshot.running || snapshot.exitCode === 0,
2242
2414
  durationMs: Math.round(performance.now() - startedAt),
2243
2415
  });
2244
- return processToolResponse(toolNames.shell, workspaceId, snapshot, {
2416
+ const response = processToolResponse(toolNames.shell, workspaceId, snapshot, {
2245
2417
  action,
2246
2418
  processId,
2247
2419
  inputLength: input?.length ?? 0,
@@ -2250,11 +2422,15 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
2250
2422
  exitCode: snapshot.exitCode,
2251
2423
  wallTimeMs: snapshot.wallTimeMs,
2252
2424
  });
2425
+ if (!snapshot.running) {
2426
+ recordBashCompletion(activityLifecycle, bashOutputStore, snapshot.outputId);
2427
+ }
2428
+ return response;
2253
2429
  },
2254
2430
  });
2255
2431
  });
2256
2432
  }
2257
- registerProcessTools(server, config, workspaces, processSessions, hooks, activityLifecycle);
2433
+ registerProcessTools(server, config, workspaces, processSessions, hooks, activityLifecycle, bashOutputStore);
2258
2434
  return server;
2259
2435
  }
2260
2436
  export function createServer(config = loadConfig(), options = {}) {
@@ -2281,9 +2457,14 @@ export function createServer(config = loadConfig(), options = {}) {
2281
2457
  const workspaceStore = createWorkspaceStore(config.stateDir);
2282
2458
  const workspaces = new WorkspaceRegistry(config, workspaceStore);
2283
2459
  const activityAuditStore = new ActivityAuditStore(config.stateDir);
2284
- const activityLifecycle = new ActivityLifecycle(activityAuditStore);
2460
+ const bashOutputStore = new BashOutputStore(config.stateDir);
2461
+ const hostTurnStore = new HostTurnStore(config.stateDir);
2462
+ const activityQueries = new ActivityQueryService(hostTurnStore, activityAuditStore, bashOutputStore);
2463
+ const activityLifecycle = new ActivityLifecycle(activityAuditStore, {
2464
+ turnIdForConversation: (conversationScopeId) => activityQueries.currentTurnId(conversationScopeId),
2465
+ });
2285
2466
  const reviewCheckpoints = createReviewCheckpointManager();
2286
- const processSessions = new ProcessManager();
2467
+ const processSessions = new ProcessManager({ outputAudit: bashOutputStore });
2287
2468
  const codeIntelligence = new CodeIntelligenceManager(config);
2288
2469
  const localAgentProviders = config.subagents
2289
2470
  ? getLocalAgentProviderAvailabilitySnapshot()
@@ -2468,7 +2649,7 @@ export function createServer(config = loadConfig(), options = {}) {
2468
2649
  });
2469
2650
  }
2470
2651
  };
2471
- const server = createMcpServer(config, workspaces, reviewCheckpoints, processSessions, localAgentProviders, incomingArtifactAdapters, codeIntelligence, activityLifecycle);
2652
+ const server = createMcpServer(config, workspaces, reviewCheckpoints, processSessions, localAgentProviders, incomingArtifactAdapters, codeIntelligence, activityLifecycle, bashOutputStore, activityQueries);
2472
2653
  await server.connect(transport);
2473
2654
  }
2474
2655
  else {
@@ -2500,6 +2681,8 @@ export function createServer(config = loadConfig(), options = {}) {
2500
2681
  processSessions.shutdown();
2501
2682
  await codeIntelligence.shutdown();
2502
2683
  oauthProvider.close();
2684
+ hostTurnStore.close();
2685
+ bashOutputStore.close();
2503
2686
  activityAuditStore.close();
2504
2687
  workspaceStore.close?.();
2505
2688
  })();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akira-tl/forgerelay",
3
- "version": "0.5.1",
3
+ "version": "0.5.4",
4
4
  "description": "Local development control plane for MCP coding agents.",
5
5
  "type": "module",
6
6
  "homepage": "https://github.com/Akira-TL/forgerelay#readme",
@@ -44,7 +44,7 @@
44
44
  "release:parity": "node scripts/release-parity.mjs",
45
45
  "postinstall": "node scripts/fix-node-pty-permissions.mjs",
46
46
  "start": "node dist/cli.js serve",
47
- "test": "node --test scripts/release-proof.test.mjs && tsx src/config.test.ts && tsx src/lsp/language-server-config.test.ts && tsx src/lsp/normalization/hover.test.ts && tsx src/lsp/references.server.test.ts && tsx src/lsp/operations/document-symbols.server.test.ts && tsx src/lsp/operations/workspace-symbols.server.test.ts && tsx src/lsp/operations/diagnostics-push.server.test.ts && tsx src/lsp/operations/diagnostics-pull.server.test.ts && tsx src/lsp/operations/request-hardening.server.test.ts && tsx src/lsp/operations/recovery.server.test.ts && tsx src/lsp/operations/lifecycle.server.test.ts && tsx src/lsp/runtime/semantic-requests.test.ts && tsx src/logger.test.ts && tsx src/proxy-trust.test.ts && tsx src/mcp-app-template.test.ts && tsx src/hooks.test.ts && tsx src/capability-registry.test.ts && tsx src/mcp/server-instructions.test.ts && tsx src/request-meta.test.ts && tsx src/incoming-artifacts.test.ts && tsx src/artifact-download.test.ts && tsx src/ui/card-types.test.ts && tsx src/ui/patch-display.test.ts && tsx src/ui/tool-display.test.ts && tsx src/apply-patch.test.ts && tsx src/process-platform.test.ts && tsx src/process-sessions.test.ts && tsx src/mcp-sessions.test.ts && tsx src/server-shutdown.test.ts && tsx src/local-agent-runtime.test.ts && tsx src/local-agent-adapters.test.ts && tsx src/local-agent-availability.test.ts && tsx src/local-agent-profiles.test.ts && tsx src/local-agent-targets.test.ts && tsx src/local-agent-store.test.ts && tsx src/roots.test.ts && tsx src/file-mutations.test.ts && tsx src/skills.test.ts && tsx src/workspace-store.test.ts && tsx src/activity/audit-store.test.ts && tsx src/activity/lifecycle.test.ts && tsx src/workspaces.test.ts && tsx src/workspace-conversation.test.ts && tsx src/review-checkpoints.test.ts && tsx src/lsp/code-intelligence.server.test.ts && tsx src/server.test.ts && tsx src/oauth-store.test.ts && tsx src/cli.test.ts",
47
+ "test": "node --test scripts/release-proof.test.mjs && tsx src/config.test.ts && tsx src/lsp/language-server-config.test.ts && tsx src/lsp/normalization/hover.test.ts && tsx src/lsp/references.server.test.ts && tsx src/lsp/operations/document-symbols.server.test.ts && tsx src/lsp/operations/workspace-symbols.server.test.ts && tsx src/lsp/operations/diagnostics-push.server.test.ts && tsx src/lsp/operations/diagnostics-pull.server.test.ts && tsx src/lsp/operations/request-hardening.server.test.ts && tsx src/lsp/operations/recovery.server.test.ts && tsx src/lsp/operations/lifecycle.server.test.ts && tsx src/lsp/runtime/semantic-requests.test.ts && tsx src/logger.test.ts && tsx src/proxy-trust.test.ts && tsx src/mcp-app-template.test.ts && tsx src/hooks.test.ts && tsx src/capability-registry.test.ts && tsx src/mcp/server-instructions.test.ts && tsx src/request-meta.test.ts && tsx src/incoming-artifacts.test.ts && tsx src/artifact-download.test.ts && tsx src/ui/card-types.test.ts && tsx src/ui/patch-display.test.ts && tsx src/ui/tool-display.test.ts && tsx src/apply-patch.test.ts && tsx src/process-platform.test.ts && tsx src/process-sessions.test.ts && tsx src/mcp-sessions.test.ts && tsx src/server-shutdown.test.ts && tsx src/local-agent-runtime.test.ts && tsx src/local-agent-adapters.test.ts && tsx src/local-agent-availability.test.ts && tsx src/local-agent-profiles.test.ts && tsx src/local-agent-targets.test.ts && tsx src/local-agent-store.test.ts && tsx src/roots.test.ts && tsx src/file-mutations.test.ts && tsx src/skills.test.ts && tsx src/workspace-store.test.ts && tsx src/activity/audit-store.test.ts && tsx src/activity/bash-output-store.test.ts && tsx src/activity/lifecycle.test.ts && tsx src/activity/query-service.test.ts && tsx src/workspaces.test.ts && tsx src/workspace-conversation.test.ts && tsx src/review-checkpoints.test.ts && tsx src/lsp/code-intelligence.server.test.ts && tsx src/server.test.ts && tsx src/oauth-store.test.ts && tsx src/cli.test.ts",
48
48
  "typecheck": "tsc -p tsconfig.json --noEmit",
49
49
  "release:check": "node scripts/release-version.mjs check",
50
50
  "release:tag-check": "node scripts/release-version.mjs tag",