forgeos 0.1.0-alpha.11 → 0.1.0-alpha.13
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/AGENTS.md +1 -1
- package/CHANGELOG.md +22 -1
- package/README.md +7 -0
- package/docs/changelog.md +1 -1
- package/package.json +1 -1
- package/src/forge/_generated/actionSubscriptions.json +1 -1
- package/src/forge/_generated/actionSubscriptions.ts +3 -3
- package/src/forge/_generated/agentAdapterManifest.json +1 -1
- package/src/forge/_generated/agentAdapterManifest.ts +3 -3
- package/src/forge/_generated/agentContract.json +1 -1
- package/src/forge/_generated/agentContract.ts +2 -2
- package/src/forge/_generated/agentQuickstart.md +1 -1
- package/src/forge/_generated/agentTools.json +1 -1
- package/src/forge/_generated/agentTools.md +1 -1
- package/src/forge/_generated/agentTools.ts +2 -2
- package/src/forge/_generated/aiContext.ts +1 -1
- package/src/forge/_generated/aiModels.ts +1 -1
- package/src/forge/_generated/aiProviders.ts +1 -1
- package/src/forge/_generated/aiRegistry.json +1 -1
- package/src/forge/_generated/aiRegistry.ts +3 -3
- package/src/forge/_generated/api.json +1 -1
- package/src/forge/_generated/api.ts +1 -1
- package/src/forge/_generated/appGraph.json +1 -1
- package/src/forge/_generated/appGraph.ts +626 -71
- package/src/forge/_generated/appMap.md +1 -1
- package/src/forge/_generated/artifactManifest.json +1 -1
- package/src/forge/_generated/artifactManifest.ts +2 -2
- package/src/forge/_generated/authClaims.ts +1 -1
- package/src/forge/_generated/authConfig.ts +1 -1
- package/src/forge/_generated/authContext.ts +1 -1
- package/src/forge/_generated/authRegistry.ts +1 -1
- package/src/forge/_generated/buildInfo.json +1 -1
- package/src/forge/_generated/buildInfo.ts +4 -4
- package/src/forge/_generated/capabilityMap.json +1 -1
- package/src/forge/_generated/capabilityMap.md +1 -1
- package/src/forge/_generated/capabilityMap.ts +2 -2
- package/src/forge/_generated/client.ts +1 -1
- package/src/forge/_generated/clientApi.ts +1 -1
- package/src/forge/_generated/clientManifest.json +1 -1
- package/src/forge/_generated/clientManifest.ts +3 -3
- package/src/forge/_generated/clientTypes.ts +1 -1
- package/src/forge/_generated/configRegistry.ts +1 -1
- package/src/forge/_generated/dataGraph.json +1 -1
- package/src/forge/_generated/dataGraph.ts +3 -3
- package/src/forge/_generated/db.ts +1 -1
- package/src/forge/_generated/dbSecurityManifest.ts +1 -1
- package/src/forge/_generated/dbSessionContext.ts +1 -1
- package/src/forge/_generated/deployManifest.json +1 -1
- package/src/forge/_generated/deployManifest.ts +7 -7
- package/src/forge/_generated/devManifest.json +1 -1
- package/src/forge/_generated/devManifest.ts +3 -3
- package/src/forge/_generated/envSchema.ts +1 -1
- package/src/forge/_generated/externalServices.ts +1 -1
- package/src/forge/_generated/frontendGraph.ts +1 -1
- package/src/forge/_generated/importGuards.ts +1 -1
- package/src/forge/_generated/index.ts +1 -1
- package/src/forge/_generated/liveProductionManifest.ts +1 -1
- package/src/forge/_generated/liveProtocol.ts +1 -1
- package/src/forge/_generated/liveQueryRegistry.json +1 -1
- package/src/forge/_generated/liveQueryRegistry.ts +3 -3
- package/src/forge/_generated/liveTransportConfig.ts +1 -1
- package/src/forge/_generated/makeRegistry.json +1 -1
- package/src/forge/_generated/makeRegistry.ts +2 -2
- package/src/forge/_generated/makeTemplates.ts +1 -1
- package/src/forge/_generated/mockMap.ts +1 -1
- package/src/forge/_generated/operationPlaybooks.md +1 -1
- package/src/forge/_generated/packageGraph.json +1 -1
- package/src/forge/_generated/packageGraph.ts +2 -2
- package/src/forge/_generated/packageUpgradeRegistry.json +1 -1
- package/src/forge/_generated/packageUpgradeRegistry.ts +2 -2
- package/src/forge/_generated/permissionMatrix.json +1 -1
- package/src/forge/_generated/permissionMatrix.ts +3 -3
- package/src/forge/_generated/policyRegistry.json +1 -1
- package/src/forge/_generated/policyRegistry.ts +3 -3
- package/src/forge/_generated/queryRegistry.json +1 -1
- package/src/forge/_generated/queryRegistry.ts +3 -3
- package/src/forge/_generated/react.d.ts +1 -1
- package/src/forge/_generated/react.ts +1 -1
- package/src/forge/_generated/reactManifest.json +1 -1
- package/src/forge/_generated/reactManifest.ts +3 -3
- package/src/forge/_generated/releaseManifest.json +1 -1
- package/src/forge/_generated/releaseManifest.ts +3 -3
- package/src/forge/_generated/rlsPolicies.sql +1 -1
- package/src/forge/_generated/rlsPolicies.ts +1 -1
- package/src/forge/_generated/runtimeGraph.json +1 -1
- package/src/forge/_generated/runtimeGraph.ts +3 -3
- package/src/forge/_generated/runtimeMatrix.ts +1 -1
- package/src/forge/_generated/runtimeRegistry.ts +1 -1
- package/src/forge/_generated/runtimeRules.md +1 -1
- package/src/forge/_generated/secretRegistry.ts +1 -1
- package/src/forge/_generated/secretsContext.ts +1 -1
- package/src/forge/_generated/serverApi.ts +1 -1
- package/src/forge/_generated/sourceMapManifest.json +1 -1
- package/src/forge/_generated/sourceMapManifest.ts +2 -2
- package/src/forge/_generated/sqlPlan.ts +1 -1
- package/src/forge/_generated/subscriptionManifest.json +1 -1
- package/src/forge/_generated/subscriptionManifest.ts +3 -3
- package/src/forge/_generated/symbolicationManifest.json +1 -1
- package/src/forge/_generated/symbolicationManifest.ts +2 -2
- package/src/forge/_generated/telemetryRegistry.json +1 -1
- package/src/forge/_generated/telemetryRegistry.ts +3 -3
- package/src/forge/_generated/telemetrySinks.json +1 -1
- package/src/forge/_generated/telemetrySinks.ts +2 -2
- package/src/forge/_generated/tenantScope.json +1 -1
- package/src/forge/_generated/tenantScope.ts +3 -3
- package/src/forge/_generated/testGraph.json +1 -1
- package/src/forge/_generated/testGraph.ts +75 -3
- package/src/forge/_generated/testPlanRegistry.json +1 -1
- package/src/forge/_generated/testPlanRegistry.ts +2 -2
- package/src/forge/_generated/uiRoutes.ts +1 -1
- package/src/forge/_generated/uiScenarios.ts +1 -1
- package/src/forge/_generated/uiTestManifest.json +1 -1
- package/src/forge/_generated/uiTestManifest.ts +2 -2
- package/src/forge/_generated/workflowRegistry.json +1 -1
- package/src/forge/_generated/workflowRegistry.ts +3 -3
- package/src/forge/_generated/workflowSubscriptions.json +1 -1
- package/src/forge/_generated/workflowSubscriptions.ts +3 -3
- package/src/forge/agent-adapters/index.ts +36 -2
- package/src/forge/agent-adapters/types.ts +10 -1
- package/src/forge/agent-memory/bridge.ts +228 -0
- package/src/forge/agent-memory/context-pack.ts +104 -0
- package/src/forge/agent-memory/mcp.ts +224 -0
- package/src/forge/agent-memory/normalize.ts +249 -0
- package/src/forge/agent-memory/redaction.ts +94 -0
- package/src/forge/agent-memory/sources/claude-code.ts +51 -0
- package/src/forge/agent-memory/sources/codex.ts +58 -0
- package/src/forge/agent-memory/sources/cursor.ts +35 -0
- package/src/forge/agent-memory/types.ts +112 -0
- package/src/forge/cli/build.ts +19 -3
- package/src/forge/cli/commands.ts +56 -0
- package/src/forge/cli/dev.ts +17 -8
- package/src/forge/cli/main.ts +12 -1
- package/src/forge/cli/new.ts +1 -0
- package/src/forge/cli/parse.ts +159 -2
- package/src/forge/delta/classifier.ts +52 -0
- package/src/forge/delta/explain.ts +126 -0
- package/src/forge/delta/git-observer.ts +43 -0
- package/src/forge/delta/ids.ts +44 -0
- package/src/forge/delta/index.ts +6 -0
- package/src/forge/delta/recorder.ts +350 -0
- package/src/forge/delta/redaction.ts +50 -0
- package/src/forge/delta/schema.ts +238 -0
- package/src/forge/delta/session.ts +141 -0
- package/src/forge/delta/status.ts +68 -0
- package/src/forge/delta/store.ts +2573 -0
- package/src/forge/delta/timeline.ts +104 -0
- package/src/forge/dev/server.ts +39 -0
- package/src/forge/dev/types.ts +2 -0
- package/src/forge/dev/watch.ts +17 -7
- package/src/forge/version.ts +1 -1
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
// @forge-generated generator=0.1.0-alpha.
|
|
1
|
+
// @forge-generated generator=0.1.0-alpha.13 input=3f5e1915da753f2190d1baa076ce591a66915ab697552bf8768f4c1c6850b945 content=3075b08b0bccea60cfe02bc8deb3f71af9aefa8e465b29999a279112bcec5353
|
|
2
2
|
export const workflowSubscriptions = {
|
|
3
3
|
"analyzerVersion": "0.1.0",
|
|
4
4
|
"byEvent": {},
|
|
5
5
|
"diagnostics": [],
|
|
6
|
-
"generatorVersion": "0.1.0-alpha.
|
|
7
|
-
"inputHash": "
|
|
6
|
+
"generatorVersion": "0.1.0-alpha.13",
|
|
7
|
+
"inputHash": "06bfbcbe27d8a631e77cfc206b83997252aa45b78fdcc90a8b9136f1bd8e6e47",
|
|
8
8
|
"schemaVersion": "0.1.0",
|
|
9
9
|
"subscriptions": []
|
|
10
10
|
} as const;
|
|
@@ -32,6 +32,12 @@ import type {
|
|
|
32
32
|
CustomAdapterConfig,
|
|
33
33
|
AgentCheckResult,
|
|
34
34
|
} from "./types.ts";
|
|
35
|
+
import {
|
|
36
|
+
formatAgentMemoryHuman,
|
|
37
|
+
formatAgentMemoryJson,
|
|
38
|
+
runAgentMemoryCommand,
|
|
39
|
+
type AgentMemoryCommandResult,
|
|
40
|
+
} from "../agent-memory/bridge.ts";
|
|
35
41
|
|
|
36
42
|
export const AGENT_ADAPTER_VERSION = "agent-adapter-0.1.0";
|
|
37
43
|
export const AGENT_FORMAT_VERSION = "2026-06";
|
|
@@ -945,7 +951,7 @@ export function runAgentDoctor(options: AgentCommandOptions): AgentDoctorResult
|
|
|
945
951
|
}
|
|
946
952
|
|
|
947
953
|
export async function runAgentCommand(options: AgentCommandOptions): Promise<
|
|
948
|
-
AgentExportResult | AgentCheckResult | AgentTargetsResult | AgentPrintContextResult | AgentDoctorResult
|
|
954
|
+
AgentExportResult | AgentCheckResult | AgentTargetsResult | AgentPrintContextResult | AgentDoctorResult | AgentMemoryCommandResult
|
|
949
955
|
> {
|
|
950
956
|
if (options.subcommand === "list-targets") {
|
|
951
957
|
return runAgentListTargets(options.workspaceRoot);
|
|
@@ -965,6 +971,27 @@ export async function runAgentCommand(options: AgentCommandOptions): Promise<
|
|
|
965
971
|
if (options.subcommand === "clean") {
|
|
966
972
|
return runAgentClean(options);
|
|
967
973
|
}
|
|
974
|
+
if (
|
|
975
|
+
options.subcommand === "install" ||
|
|
976
|
+
options.subcommand === "ingest" ||
|
|
977
|
+
options.subcommand === "context" ||
|
|
978
|
+
options.subcommand === "memory"
|
|
979
|
+
) {
|
|
980
|
+
return runAgentMemoryCommand({
|
|
981
|
+
subcommand: options.subcommand,
|
|
982
|
+
workspaceRoot: options.workspaceRoot,
|
|
983
|
+
json: options.json,
|
|
984
|
+
target: options.target,
|
|
985
|
+
source: options.target,
|
|
986
|
+
eventName: options.eventName,
|
|
987
|
+
input: options.input,
|
|
988
|
+
entry: options.entry,
|
|
989
|
+
current: options.current,
|
|
990
|
+
dryRun: options.dryRun,
|
|
991
|
+
force: options.force,
|
|
992
|
+
limit: options.limit,
|
|
993
|
+
});
|
|
994
|
+
}
|
|
968
995
|
return {
|
|
969
996
|
ok: false,
|
|
970
997
|
target: options.target,
|
|
@@ -979,10 +1006,16 @@ export async function runAgentCommand(options: AgentCommandOptions): Promise<
|
|
|
979
1006
|
}
|
|
980
1007
|
|
|
981
1008
|
export function formatAgentJson(result: Awaited<ReturnType<typeof runAgentCommand>>): string {
|
|
1009
|
+
if ("privacy" in result || "event" in result || "agentMemory" in result || "events" in result) {
|
|
1010
|
+
return formatAgentMemoryJson(result as AgentMemoryCommandResult);
|
|
1011
|
+
}
|
|
982
1012
|
return `${JSON.stringify(result, null, 2)}\n`;
|
|
983
1013
|
}
|
|
984
1014
|
|
|
985
1015
|
export function formatAgentHuman(result: Awaited<ReturnType<typeof runAgentCommand>>): string {
|
|
1016
|
+
if ("privacy" in result || "event" in result || "agentMemory" in result || "events" in result) {
|
|
1017
|
+
return formatAgentMemoryHuman(result as AgentMemoryCommandResult);
|
|
1018
|
+
}
|
|
986
1019
|
if ("targets" in result) {
|
|
987
1020
|
return `${result.targets.map((target) => `${target.name}${target.default ? " (default)" : ""}${target.optional ? " (optional)" : ""}${target.custom ? " (custom)" : ""}`).join("\n")}\n`;
|
|
988
1021
|
}
|
|
@@ -998,5 +1031,6 @@ export function formatAgentHuman(result: Awaited<ReturnType<typeof runAgentComma
|
|
|
998
1031
|
}
|
|
999
1032
|
return `agent adapter exports are stale\nmissing: ${result.missing.join(", ") || "none"}\nstale: ${result.stale.join(", ") || "none"}\n`;
|
|
1000
1033
|
}
|
|
1001
|
-
|
|
1034
|
+
const exportResult = result as AgentExportResult;
|
|
1035
|
+
return `agent export ${exportResult.ok ? "ok" : "failed"} for ${exportResult.target}\nfiles written:\n${exportResult.filesWritten.map((file: string) => `- ${file}`).join("\n") || "- none"}\n`;
|
|
1002
1036
|
}
|
|
@@ -10,7 +10,11 @@ export type AgentSubcommand =
|
|
|
10
10
|
| "check"
|
|
11
11
|
| "doctor"
|
|
12
12
|
| "print-context"
|
|
13
|
-
| "clean"
|
|
13
|
+
| "clean"
|
|
14
|
+
| "install"
|
|
15
|
+
| "ingest"
|
|
16
|
+
| "context"
|
|
17
|
+
| "memory";
|
|
14
18
|
|
|
15
19
|
export interface AgentCommandOptions {
|
|
16
20
|
subcommand: AgentSubcommand;
|
|
@@ -22,6 +26,11 @@ export interface AgentCommandOptions {
|
|
|
22
26
|
preserveUserSections: boolean;
|
|
23
27
|
skills: boolean;
|
|
24
28
|
rules: boolean;
|
|
29
|
+
eventName?: string;
|
|
30
|
+
input?: unknown;
|
|
31
|
+
entry?: string;
|
|
32
|
+
current?: boolean;
|
|
33
|
+
limit?: number;
|
|
25
34
|
}
|
|
26
35
|
|
|
27
36
|
export interface AgentExportFile {
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { DeltaStore } from "../delta/store.ts";
|
|
4
|
+
import { extractAgentEventBindings, normalizeAgentEvent, summarizeAgentEvent } from "./normalize.ts";
|
|
5
|
+
import { buildAgentMemoryContext } from "./context-pack.ts";
|
|
6
|
+
import { claudeCodeInstallFiles, claudeCodeInstallResult } from "./sources/claude-code.ts";
|
|
7
|
+
import { codexInstallFiles, codexInstallResult, privacyDefaults } from "./sources/codex.ts";
|
|
8
|
+
import { cursorInstallFiles, cursorInstallResult } from "./sources/cursor.ts";
|
|
9
|
+
import type {
|
|
10
|
+
AgentEventEnvelope,
|
|
11
|
+
AgentIngestResult,
|
|
12
|
+
AgentInstallResult,
|
|
13
|
+
AgentMemoryContextPack,
|
|
14
|
+
AgentMemoryEventRecord,
|
|
15
|
+
AgentMemorySourceName,
|
|
16
|
+
} from "./types.ts";
|
|
17
|
+
|
|
18
|
+
export interface AgentMemoryCommandOptions {
|
|
19
|
+
subcommand: "install" | "ingest" | "context" | "memory";
|
|
20
|
+
workspaceRoot: string;
|
|
21
|
+
json: boolean;
|
|
22
|
+
target?: string;
|
|
23
|
+
source?: string;
|
|
24
|
+
eventName?: string;
|
|
25
|
+
input?: unknown;
|
|
26
|
+
entry?: string;
|
|
27
|
+
current?: boolean;
|
|
28
|
+
dryRun?: boolean;
|
|
29
|
+
force?: boolean;
|
|
30
|
+
limit?: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type AgentMemoryCommandResult =
|
|
34
|
+
| AgentInstallResult
|
|
35
|
+
| AgentIngestResult
|
|
36
|
+
| AgentMemoryContextPack
|
|
37
|
+
| { ok: true; events: AgentMemoryEventRecord[]; exitCode: 0 };
|
|
38
|
+
|
|
39
|
+
export async function runAgentMemoryCommand(options: AgentMemoryCommandOptions): Promise<AgentMemoryCommandResult> {
|
|
40
|
+
if (options.subcommand === "install") {
|
|
41
|
+
return installAgentMemory(options);
|
|
42
|
+
}
|
|
43
|
+
if (options.subcommand === "ingest") {
|
|
44
|
+
return ingestAgentMemory(options);
|
|
45
|
+
}
|
|
46
|
+
if (options.subcommand === "context") {
|
|
47
|
+
return buildAgentMemoryContext({
|
|
48
|
+
workspaceRoot: options.workspaceRoot,
|
|
49
|
+
entry: options.entry,
|
|
50
|
+
limit: options.limit,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
const store = await DeltaStore.open(options.workspaceRoot);
|
|
54
|
+
try {
|
|
55
|
+
return {
|
|
56
|
+
ok: true,
|
|
57
|
+
events: await store.listAgentMemoryEvents({ target: options.entry, limit: options.limit }),
|
|
58
|
+
exitCode: 0,
|
|
59
|
+
};
|
|
60
|
+
} finally {
|
|
61
|
+
await store.close();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export async function ingestEnvelope(workspaceRoot: string, envelope: AgentEventEnvelope): Promise<AgentIngestResult> {
|
|
66
|
+
const store = await DeltaStore.open(workspaceRoot);
|
|
67
|
+
try {
|
|
68
|
+
const bindings = extractAgentEventBindings(envelope);
|
|
69
|
+
const summary = summarizeAgentEvent(envelope);
|
|
70
|
+
const event = await store.recordAgentMemoryEvent({ envelope, summary, bindings });
|
|
71
|
+
return { ok: true, event, envelope, exitCode: 0 };
|
|
72
|
+
} finally {
|
|
73
|
+
await store.close();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function ingestAgentMemory(options: AgentMemoryCommandOptions): Promise<AgentIngestResult> {
|
|
78
|
+
const source = options.source ?? options.target ?? "generic";
|
|
79
|
+
const raw = normalizeRawInput(options.input ?? await readStdinJson());
|
|
80
|
+
if (!raw) {
|
|
81
|
+
return { ok: false, exitCode: 1, error: "agent ingest requires JSON input on stdin or --input" };
|
|
82
|
+
}
|
|
83
|
+
const envelope = normalizeAgentEvent({
|
|
84
|
+
workspaceRoot: options.workspaceRoot,
|
|
85
|
+
source,
|
|
86
|
+
eventName: options.eventName,
|
|
87
|
+
raw,
|
|
88
|
+
integration: source === "cursor" ? "mcp" : "native-hook",
|
|
89
|
+
});
|
|
90
|
+
return ingestEnvelope(options.workspaceRoot, envelope);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function installAgentMemory(options: AgentMemoryCommandOptions): AgentInstallResult {
|
|
94
|
+
const target = normalizeInstallTarget(options.target ?? options.source ?? "generic");
|
|
95
|
+
const files =
|
|
96
|
+
target === "codex"
|
|
97
|
+
? codexInstallFiles()
|
|
98
|
+
: target === "claude-code"
|
|
99
|
+
? claudeCodeInstallFiles()
|
|
100
|
+
: target === "cursor"
|
|
101
|
+
? cursorInstallFiles()
|
|
102
|
+
: [];
|
|
103
|
+
if (files.length === 0) {
|
|
104
|
+
return {
|
|
105
|
+
ok: false,
|
|
106
|
+
target,
|
|
107
|
+
filesWritten: [],
|
|
108
|
+
filesPlanned: [],
|
|
109
|
+
privacy: privacyDefaults(),
|
|
110
|
+
warnings: [`unknown agent memory install target: ${target}`],
|
|
111
|
+
exitCode: 1,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
const filesWritten: string[] = [];
|
|
115
|
+
for (const file of files) {
|
|
116
|
+
const absolute = join(options.workspaceRoot, file.path);
|
|
117
|
+
const content = maybeMergeJson(absolute, file.content);
|
|
118
|
+
if (options.dryRun) {
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (!options.force && existsSync(absolute) && readFileSync(absolute, "utf8") === content) {
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
mkdirSync(dirname(absolute), { recursive: true });
|
|
125
|
+
writeFileSync(absolute, content);
|
|
126
|
+
filesWritten.push(file.path);
|
|
127
|
+
}
|
|
128
|
+
const planned = files.map((file) => file.path);
|
|
129
|
+
if (target === "codex") {
|
|
130
|
+
return codexInstallResult(filesWritten, planned);
|
|
131
|
+
}
|
|
132
|
+
if (target === "claude-code") {
|
|
133
|
+
return claudeCodeInstallResult(filesWritten, planned);
|
|
134
|
+
}
|
|
135
|
+
return cursorInstallResult(filesWritten, planned);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function formatAgentMemoryJson(result: AgentMemoryCommandResult): string {
|
|
139
|
+
return `${JSON.stringify(result, null, 2)}\n`;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function formatAgentMemoryHuman(result: AgentMemoryCommandResult): string {
|
|
143
|
+
if ("event" in result || "envelope" in result) {
|
|
144
|
+
return result.ok
|
|
145
|
+
? `agent memory ingested: ${result.event?.normalizedKind ?? "event"}\n`
|
|
146
|
+
: `agent memory ingest failed: ${result.error ?? "unknown error"}\n`;
|
|
147
|
+
}
|
|
148
|
+
if ("filesPlanned" in result) {
|
|
149
|
+
return [
|
|
150
|
+
`Forge Agent Memory Bridge ${result.ok ? "installed" : "failed"} for ${result.target}.`,
|
|
151
|
+
"files written:",
|
|
152
|
+
...(result.filesWritten.length > 0 ? result.filesWritten.map((file) => `- ${file}`) : ["- none"]),
|
|
153
|
+
"privacy:",
|
|
154
|
+
"- raw prompts: off",
|
|
155
|
+
"- raw completions: off",
|
|
156
|
+
"- raw tool args: off",
|
|
157
|
+
"- transcript import: off",
|
|
158
|
+
].join("\n") + "\n";
|
|
159
|
+
}
|
|
160
|
+
if ("agentMemory" in result) {
|
|
161
|
+
return `${JSON.stringify(result, null, 2)}\n`;
|
|
162
|
+
}
|
|
163
|
+
const events = "events" in result ? result.events : [];
|
|
164
|
+
return `${JSON.stringify(events, null, 2)}\n`;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function normalizeInstallTarget(target: string): AgentMemorySourceName | string {
|
|
168
|
+
if (target === "claude") {
|
|
169
|
+
return "claude-code";
|
|
170
|
+
}
|
|
171
|
+
return target;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function normalizeRawInput(input: unknown): Record<string, unknown> | null {
|
|
175
|
+
if (input && typeof input === "object" && !Array.isArray(input)) {
|
|
176
|
+
return input as Record<string, unknown>;
|
|
177
|
+
}
|
|
178
|
+
if (typeof input === "string" && input.trim()) {
|
|
179
|
+
try {
|
|
180
|
+
const parsed = JSON.parse(input) as unknown;
|
|
181
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed as Record<string, unknown> : null;
|
|
182
|
+
} catch {
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async function readStdinJson(): Promise<unknown> {
|
|
190
|
+
if (process.stdin.isTTY) {
|
|
191
|
+
return undefined;
|
|
192
|
+
}
|
|
193
|
+
const chunks: Buffer[] = [];
|
|
194
|
+
for await (const chunk of process.stdin) {
|
|
195
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)));
|
|
196
|
+
}
|
|
197
|
+
const raw = Buffer.concat(chunks).toString("utf8").trim();
|
|
198
|
+
return raw ? raw : undefined;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function maybeMergeJson(path: string, generated: string): string {
|
|
202
|
+
if (!existsSync(path) || !path.endsWith(".json")) {
|
|
203
|
+
return generated;
|
|
204
|
+
}
|
|
205
|
+
try {
|
|
206
|
+
const current = JSON.parse(readFileSync(path, "utf8")) as unknown;
|
|
207
|
+
const next = JSON.parse(generated) as unknown;
|
|
208
|
+
if (!current || typeof current !== "object" || Array.isArray(current) || !next || typeof next !== "object" || Array.isArray(next)) {
|
|
209
|
+
return generated;
|
|
210
|
+
}
|
|
211
|
+
return `${JSON.stringify(deepMerge(current as Record<string, unknown>, next as Record<string, unknown>), null, 2)}\n`;
|
|
212
|
+
} catch {
|
|
213
|
+
return generated;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function deepMerge(left: Record<string, unknown>, right: Record<string, unknown>): Record<string, unknown> {
|
|
218
|
+
const output: Record<string, unknown> = { ...left };
|
|
219
|
+
for (const [key, value] of Object.entries(right)) {
|
|
220
|
+
const existing = output[key];
|
|
221
|
+
output[key] =
|
|
222
|
+
existing && typeof existing === "object" && !Array.isArray(existing) &&
|
|
223
|
+
value && typeof value === "object" && !Array.isArray(value)
|
|
224
|
+
? deepMerge(existing as Record<string, unknown>, value as Record<string, unknown>)
|
|
225
|
+
: value;
|
|
226
|
+
}
|
|
227
|
+
return output;
|
|
228
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { DeltaStore } from "../delta/store.ts";
|
|
2
|
+
import type { AgentMemoryContextPack, AgentMemoryEventRecord } from "./types.ts";
|
|
3
|
+
|
|
4
|
+
export async function buildAgentMemoryContext(input: {
|
|
5
|
+
workspaceRoot: string;
|
|
6
|
+
entry?: string;
|
|
7
|
+
limit?: number;
|
|
8
|
+
}): Promise<AgentMemoryContextPack> {
|
|
9
|
+
const store = await DeltaStore.open(input.workspaceRoot);
|
|
10
|
+
try {
|
|
11
|
+
const target = input.entry;
|
|
12
|
+
const events = await store.listAgentMemoryEvents({ target, limit: input.limit ?? 50 });
|
|
13
|
+
const timeline = target ? await store.semanticTimeline({ target, limit: input.limit ?? 50 }) : undefined;
|
|
14
|
+
const current = target ? timeline?.currentState ?? {} : await currentSessionState(store);
|
|
15
|
+
return {
|
|
16
|
+
ok: true,
|
|
17
|
+
scope: target ? "entry" : "current",
|
|
18
|
+
entry: target,
|
|
19
|
+
currentState: current,
|
|
20
|
+
agentMemory: {
|
|
21
|
+
goals: events
|
|
22
|
+
.filter((event) => event.normalizedKind === "agent.prompt.submitted")
|
|
23
|
+
.map((event) => ({
|
|
24
|
+
source: event.sourceName,
|
|
25
|
+
summary: event.summary ?? "Prompt submitted",
|
|
26
|
+
confidence: event.confidence,
|
|
27
|
+
})),
|
|
28
|
+
toolCalls: toolCalls(events),
|
|
29
|
+
files: uniqueStrings(events.flatMap((event) => bindings(event).files)),
|
|
30
|
+
entries: uniqueStrings(events.flatMap((event) => bindings(event).entries)),
|
|
31
|
+
approvals: events
|
|
32
|
+
.filter((event) => event.normalizedKind.startsWith("approval."))
|
|
33
|
+
.map((event) => ({
|
|
34
|
+
source: event.sourceName,
|
|
35
|
+
status: event.normalizedKind.replace(/^approval\./, ""),
|
|
36
|
+
summary: event.summary,
|
|
37
|
+
})),
|
|
38
|
+
proofs: uniqueStrings(events.flatMap((event) => bindings(event).proofs))
|
|
39
|
+
.map((kind) => ({ kind })),
|
|
40
|
+
events,
|
|
41
|
+
openQuestions: timeline?.openQuestions ?? [],
|
|
42
|
+
},
|
|
43
|
+
exitCode: 0,
|
|
44
|
+
};
|
|
45
|
+
} finally {
|
|
46
|
+
await store.close();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function currentSessionState(store: DeltaStore): Promise<Record<string, unknown>> {
|
|
51
|
+
const session = await store.currentWorkSession();
|
|
52
|
+
return session
|
|
53
|
+
? {
|
|
54
|
+
sessionId: session.id,
|
|
55
|
+
title: session.title,
|
|
56
|
+
inferredIntent: session.inferredIntent,
|
|
57
|
+
confidence: session.confidence,
|
|
58
|
+
status: session.status,
|
|
59
|
+
}
|
|
60
|
+
: {};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function toolCalls(events: AgentMemoryEventRecord[]): AgentMemoryContextPack["agentMemory"]["toolCalls"] {
|
|
64
|
+
return events
|
|
65
|
+
.filter((event) => event.normalizedKind.startsWith("agent.tool"))
|
|
66
|
+
.map((event) => {
|
|
67
|
+
const eventBindings = bindings(event);
|
|
68
|
+
return {
|
|
69
|
+
source: event.sourceName,
|
|
70
|
+
tool: eventBindings.toolName ?? "unknown",
|
|
71
|
+
status: eventBindings.status,
|
|
72
|
+
summary: event.summary,
|
|
73
|
+
};
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function bindings(event: AgentMemoryEventRecord): {
|
|
78
|
+
toolName?: string;
|
|
79
|
+
status?: string;
|
|
80
|
+
files: string[];
|
|
81
|
+
entries: string[];
|
|
82
|
+
proofs: string[];
|
|
83
|
+
} {
|
|
84
|
+
const raw = event.data.bindings;
|
|
85
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
86
|
+
return { files: [], entries: [], proofs: [] };
|
|
87
|
+
}
|
|
88
|
+
const record = raw as Record<string, unknown>;
|
|
89
|
+
return {
|
|
90
|
+
toolName: typeof record.toolName === "string" ? record.toolName : undefined,
|
|
91
|
+
status: typeof record.status === "string" ? record.status : undefined,
|
|
92
|
+
files: arrayOfStrings(record.files),
|
|
93
|
+
entries: arrayOfStrings(record.entries),
|
|
94
|
+
proofs: arrayOfStrings(record.proofs),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function arrayOfStrings(value: unknown): string[] {
|
|
99
|
+
return Array.isArray(value) ? value.filter((item): item is string => typeof item === "string" && item.length > 0) : [];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function uniqueStrings(values: string[]): string[] {
|
|
103
|
+
return [...new Set(values)].sort();
|
|
104
|
+
}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { DeltaStore } from "../delta/store.ts";
|
|
4
|
+
import { buildAgentMemoryContext } from "./context-pack.ts";
|
|
5
|
+
import { ingestEnvelope } from "./bridge.ts";
|
|
6
|
+
import { normalizeAgentEvent } from "./normalize.ts";
|
|
7
|
+
|
|
8
|
+
interface JsonRpcRequest {
|
|
9
|
+
jsonrpc?: "2.0";
|
|
10
|
+
id?: string | number | null;
|
|
11
|
+
method: string;
|
|
12
|
+
params?: Record<string, unknown>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function handleMcpRequest(workspaceRoot: string, request: JsonRpcRequest): Promise<Record<string, unknown> | null> {
|
|
16
|
+
if (request.method.startsWith("notifications/")) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
if (request.method === "initialize") {
|
|
21
|
+
return response(request.id, {
|
|
22
|
+
protocolVersion: "2024-11-05",
|
|
23
|
+
capabilities: { tools: {} },
|
|
24
|
+
serverInfo: { name: "forgeos-agent-memory", version: "0.1.0" },
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
if (request.method === "tools/list") {
|
|
28
|
+
return response(request.id, {
|
|
29
|
+
tools: [
|
|
30
|
+
{
|
|
31
|
+
name: "agent_context",
|
|
32
|
+
description: "Read the ForgeOS Agent Memory context pack for the current work or a runtime entry.",
|
|
33
|
+
inputSchema: {
|
|
34
|
+
type: "object",
|
|
35
|
+
properties: { entry: { type: "string" } },
|
|
36
|
+
additionalProperties: false,
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: "agent_memory",
|
|
41
|
+
description: "List recent redacted agent memory events.",
|
|
42
|
+
inputSchema: {
|
|
43
|
+
type: "object",
|
|
44
|
+
properties: { target: { type: "string" }, limit: { type: "number" } },
|
|
45
|
+
additionalProperties: false,
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: "timeline",
|
|
50
|
+
description: "Read the semantic timeline for an entry, file, policy, service, tool, or agent.",
|
|
51
|
+
inputSchema: {
|
|
52
|
+
type: "object",
|
|
53
|
+
properties: { target: { type: "string" }, limit: { type: "number" } },
|
|
54
|
+
required: ["target"],
|
|
55
|
+
additionalProperties: false,
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
name: "inspect_all",
|
|
60
|
+
description: "Read the generated ForgeOS machine contract artifacts that are safe for agents.",
|
|
61
|
+
inputSchema: {
|
|
62
|
+
type: "object",
|
|
63
|
+
properties: {},
|
|
64
|
+
additionalProperties: false,
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
if (request.method === "tools/call") {
|
|
71
|
+
const params = request.params ?? {};
|
|
72
|
+
const name = typeof params.name === "string" ? params.name : "";
|
|
73
|
+
const args = params.arguments && typeof params.arguments === "object" && !Array.isArray(params.arguments)
|
|
74
|
+
? params.arguments as Record<string, unknown>
|
|
75
|
+
: {};
|
|
76
|
+
const result = await runTool(workspaceRoot, name, args);
|
|
77
|
+
await logMcpToolCall(workspaceRoot, name, args, "completed");
|
|
78
|
+
return response(request.id, {
|
|
79
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
return response(request.id, null, { code: -32601, message: `unknown MCP method: ${request.method}` });
|
|
83
|
+
} catch (error) {
|
|
84
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
85
|
+
return response(request.id, null, { code: -32000, message });
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export async function runMcpServe(workspaceRoot: string): Promise<number> {
|
|
90
|
+
let buffer = "";
|
|
91
|
+
let sawFramedMessage = false;
|
|
92
|
+
for await (const chunk of process.stdin) {
|
|
93
|
+
buffer += Buffer.isBuffer(chunk) ? chunk.toString("utf8") : String(chunk);
|
|
94
|
+
const parsed = parseMcpFrames(buffer);
|
|
95
|
+
buffer = parsed.remainder;
|
|
96
|
+
if (parsed.requests.length > 0) {
|
|
97
|
+
sawFramedMessage = true;
|
|
98
|
+
}
|
|
99
|
+
for (const request of parsed.requests) {
|
|
100
|
+
const result = await handleMcpRequest(workspaceRoot, request);
|
|
101
|
+
if (result) {
|
|
102
|
+
writeMcpMessage(result);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
const leftover = buffer.trim();
|
|
107
|
+
if (!sawFramedMessage && leftover.startsWith("{")) {
|
|
108
|
+
const result = await handleMcpRequest(workspaceRoot, JSON.parse(leftover) as JsonRpcRequest);
|
|
109
|
+
if (result) {
|
|
110
|
+
writeMcpMessage(result);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return 0;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function runTool(workspaceRoot: string, name: string, args: Record<string, unknown>): Promise<unknown> {
|
|
117
|
+
if (name === "agent_context") {
|
|
118
|
+
return buildAgentMemoryContext({
|
|
119
|
+
workspaceRoot,
|
|
120
|
+
entry: typeof args.entry === "string" ? args.entry : undefined,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
if (name === "agent_memory") {
|
|
124
|
+
const store = await DeltaStore.open(workspaceRoot);
|
|
125
|
+
try {
|
|
126
|
+
return {
|
|
127
|
+
ok: true,
|
|
128
|
+
events: await store.listAgentMemoryEvents({
|
|
129
|
+
target: typeof args.target === "string" ? args.target : undefined,
|
|
130
|
+
limit: typeof args.limit === "number" ? args.limit : undefined,
|
|
131
|
+
}),
|
|
132
|
+
};
|
|
133
|
+
} finally {
|
|
134
|
+
await store.close();
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (name === "timeline") {
|
|
138
|
+
const target = typeof args.target === "string" ? args.target : undefined;
|
|
139
|
+
if (!target) {
|
|
140
|
+
throw new Error("timeline requires target");
|
|
141
|
+
}
|
|
142
|
+
const store = await DeltaStore.open(workspaceRoot);
|
|
143
|
+
try {
|
|
144
|
+
return {
|
|
145
|
+
ok: true,
|
|
146
|
+
timeline: await store.semanticTimeline({
|
|
147
|
+
target,
|
|
148
|
+
limit: typeof args.limit === "number" ? args.limit : undefined,
|
|
149
|
+
}),
|
|
150
|
+
};
|
|
151
|
+
} finally {
|
|
152
|
+
await store.close();
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (name === "inspect_all") {
|
|
156
|
+
return readInspectAll(workspaceRoot);
|
|
157
|
+
}
|
|
158
|
+
throw new Error(`unknown ForgeOS MCP tool: ${name}`);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async function logMcpToolCall(workspaceRoot: string, toolName: string, args: Record<string, unknown>, status: string): Promise<void> {
|
|
162
|
+
const envelope = normalizeAgentEvent({
|
|
163
|
+
workspaceRoot,
|
|
164
|
+
source: "generic",
|
|
165
|
+
integration: "mcp",
|
|
166
|
+
eventName: "tool.call",
|
|
167
|
+
raw: {
|
|
168
|
+
toolName,
|
|
169
|
+
args,
|
|
170
|
+
status,
|
|
171
|
+
timestamp: new Date().toISOString(),
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
await ingestEnvelope(workspaceRoot, envelope);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function readInspectAll(workspaceRoot: string): Record<string, unknown> {
|
|
178
|
+
const generated = join(workspaceRoot, "src", "forge", "_generated");
|
|
179
|
+
const read = (name: string) => {
|
|
180
|
+
try {
|
|
181
|
+
return JSON.parse(readFileSync(join(generated, name), "utf8")) as unknown;
|
|
182
|
+
} catch {
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
return {
|
|
187
|
+
ok: true,
|
|
188
|
+
agentContract: read("agentContract.json"),
|
|
189
|
+
agentTools: read("agentTools.json"),
|
|
190
|
+
runtimeGraph: read("runtimeGraph.json"),
|
|
191
|
+
policyRegistry: read("policyRegistry.json"),
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function response(id: JsonRpcRequest["id"], result: unknown, error?: Record<string, unknown>): Record<string, unknown> {
|
|
196
|
+
return error ? { jsonrpc: "2.0", id: id ?? null, error } : { jsonrpc: "2.0", id: id ?? null, result };
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function parseMcpFrames(raw: string): { requests: JsonRpcRequest[]; remainder: string } {
|
|
200
|
+
const messages: JsonRpcRequest[] = [];
|
|
201
|
+
let cursor = 0;
|
|
202
|
+
while (cursor < raw.length) {
|
|
203
|
+
const headerEnd = raw.indexOf("\r\n\r\n", cursor);
|
|
204
|
+
if (headerEnd === -1) {
|
|
205
|
+
break;
|
|
206
|
+
}
|
|
207
|
+
const header = raw.slice(cursor, headerEnd);
|
|
208
|
+
const match = /Content-Length:\s*(\d+)/i.exec(header);
|
|
209
|
+
if (!match) {
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
const length = Number(match[1]);
|
|
213
|
+
const bodyStart = headerEnd + 4;
|
|
214
|
+
const body = raw.slice(bodyStart, bodyStart + length);
|
|
215
|
+
messages.push(JSON.parse(body) as JsonRpcRequest);
|
|
216
|
+
cursor = bodyStart + length;
|
|
217
|
+
}
|
|
218
|
+
return { requests: messages, remainder: raw.slice(cursor) };
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function writeMcpMessage(message: Record<string, unknown>): void {
|
|
222
|
+
const body = JSON.stringify(message);
|
|
223
|
+
process.stdout.write(`Content-Length: ${Buffer.byteLength(body, "utf8")}\r\n\r\n${body}`);
|
|
224
|
+
}
|