@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/CHANGELOG.md +29 -0
- package/dist/activity/audit-store.js +18 -0
- package/dist/activity/bash-output-store.js +127 -0
- package/dist/activity/host-turn-store.js +46 -0
- package/dist/activity/lifecycle.js +62 -31
- package/dist/activity/mcp-query-tools.js +129 -0
- package/dist/activity/process-output-audit.js +1 -0
- package/dist/activity/query-service.js +247 -0
- package/dist/db/migrations.js +68 -0
- package/dist/db/schema.js +44 -1
- package/dist/lsp/test-support/server-fixture.js +13 -3
- package/dist/process-sessions.js +50 -8
- package/dist/server.js +209 -26
- package/package.json +2 -2
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 {
|
|
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
|
-
|
|
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
|
|
489
|
-
return
|
|
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(
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
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",
|