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
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { normalizePath } from "../compiler/primitives/paths.ts";
|
|
2
|
+
import { readDeltaGitSnapshot } from "../delta/git-observer.ts";
|
|
3
|
+
import { redactAgentPayload } from "./redaction.ts";
|
|
4
|
+
import {
|
|
5
|
+
AGENT_EVENT_SCHEMA,
|
|
6
|
+
type AgentEventEnvelope,
|
|
7
|
+
type AgentMemoryIntegrationKind,
|
|
8
|
+
type AgentMemorySourceName,
|
|
9
|
+
type AgentMemoryTrustLevel,
|
|
10
|
+
} from "./types.ts";
|
|
11
|
+
|
|
12
|
+
export interface NormalizeAgentEventInput {
|
|
13
|
+
workspaceRoot: string;
|
|
14
|
+
source: AgentMemorySourceName | string;
|
|
15
|
+
eventName?: string;
|
|
16
|
+
integration?: AgentMemoryIntegrationKind | string;
|
|
17
|
+
raw: Record<string, unknown>;
|
|
18
|
+
now?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function normalizeAgentEvent(input: NormalizeAgentEventInput): AgentEventEnvelope {
|
|
22
|
+
const source = normalizeSource(input.source);
|
|
23
|
+
const rawEventName = input.eventName ?? stringField(input.raw, "hook_event_name") ?? stringField(input.raw, "event") ?? "unknown";
|
|
24
|
+
const normalizedKind = normalizeEventKind(source, rawEventName, input.raw);
|
|
25
|
+
const redacted = redactAgentPayload(input.raw);
|
|
26
|
+
const git = readDeltaGitSnapshot(input.workspaceRoot);
|
|
27
|
+
const actorName = source === "claude-code"
|
|
28
|
+
? "Claude Code"
|
|
29
|
+
: source === "codex"
|
|
30
|
+
? "Codex"
|
|
31
|
+
: source === "cursor"
|
|
32
|
+
? "Cursor"
|
|
33
|
+
: "External Agent";
|
|
34
|
+
return {
|
|
35
|
+
schema: AGENT_EVENT_SCHEMA,
|
|
36
|
+
source: {
|
|
37
|
+
agent: source,
|
|
38
|
+
integration: input.integration ?? defaultIntegrationForSource(source),
|
|
39
|
+
version: stringField(input.raw, "version") ?? "unknown",
|
|
40
|
+
},
|
|
41
|
+
workspace: {
|
|
42
|
+
root: normalizePath(input.workspaceRoot),
|
|
43
|
+
gitBranch: git.branch,
|
|
44
|
+
gitHead: git.head,
|
|
45
|
+
},
|
|
46
|
+
session: {
|
|
47
|
+
externalSessionId: stringField(input.raw, "session_id") ?? stringField(input.raw, "sessionId") ?? stringField(input.raw, "conversation_id"),
|
|
48
|
+
forgeSessionId: stringField(input.raw, "forgeSessionId"),
|
|
49
|
+
turnId: stringField(input.raw, "turn_id") ?? stringField(input.raw, "turnId"),
|
|
50
|
+
},
|
|
51
|
+
event: {
|
|
52
|
+
kind: normalizedKind,
|
|
53
|
+
timestamp: stringField(input.raw, "timestamp") ?? input.now ?? new Date().toISOString(),
|
|
54
|
+
},
|
|
55
|
+
actor: {
|
|
56
|
+
kind: "agent",
|
|
57
|
+
name: stringField(input.raw, "actor") ?? actorName,
|
|
58
|
+
model: stringField(input.raw, "model"),
|
|
59
|
+
},
|
|
60
|
+
payload: redacted.value,
|
|
61
|
+
privacy: {
|
|
62
|
+
rawPromptStored: false,
|
|
63
|
+
rawCompletionStored: false,
|
|
64
|
+
rawToolArgsStored: false,
|
|
65
|
+
transcriptImported: false,
|
|
66
|
+
redacted: true,
|
|
67
|
+
sensitiveFieldsRemoved: redacted.sensitiveFieldsRemoved,
|
|
68
|
+
},
|
|
69
|
+
capture: {
|
|
70
|
+
trustLevel: trustLevelForIntegration(input.integration ?? defaultIntegrationForSource(source)),
|
|
71
|
+
confidence: confidenceForSource(source, input.integration ?? defaultIntegrationForSource(source)),
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function summarizeAgentEvent(envelope: AgentEventEnvelope): string {
|
|
77
|
+
const tool = stringField(envelope.payload, "toolName") ??
|
|
78
|
+
stringField(envelope.payload, "tool_name") ??
|
|
79
|
+
stringField(envelope.payload, "tool");
|
|
80
|
+
const command = stringField(envelope.payload, "command");
|
|
81
|
+
const promptSummary = stringField(envelope.payload, "promptSummary") ?? stringField(envelope.payload, "userPromptSummary");
|
|
82
|
+
if (envelope.event.kind === "agent.prompt.submitted" && promptSummary) {
|
|
83
|
+
return promptSummary;
|
|
84
|
+
}
|
|
85
|
+
if (tool) {
|
|
86
|
+
return `${envelope.source.agent} ${tool} ${statusFromKind(envelope.event.kind)}`;
|
|
87
|
+
}
|
|
88
|
+
if (command) {
|
|
89
|
+
return `${envelope.source.agent} ran ${command}`;
|
|
90
|
+
}
|
|
91
|
+
return `${envelope.source.agent} ${envelope.event.kind}`;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function extractAgentEventBindings(envelope: AgentEventEnvelope): {
|
|
95
|
+
toolName?: string;
|
|
96
|
+
command?: string;
|
|
97
|
+
exitCode?: number;
|
|
98
|
+
files: string[];
|
|
99
|
+
entries: string[];
|
|
100
|
+
proofs: string[];
|
|
101
|
+
status?: string;
|
|
102
|
+
} {
|
|
103
|
+
const payload = envelope.payload;
|
|
104
|
+
return {
|
|
105
|
+
toolName: stringField(payload, "toolName") ?? stringField(payload, "tool_name") ?? stringField(payload, "tool"),
|
|
106
|
+
command: stringField(payload, "command"),
|
|
107
|
+
exitCode: numberField(payload, "exitCode") ?? numberField(payload, "exit_code"),
|
|
108
|
+
files: uniqueStrings([
|
|
109
|
+
...arrayOfStrings(payload.files),
|
|
110
|
+
...arrayOfStrings(payload.paths),
|
|
111
|
+
stringField(payload, "file_path"),
|
|
112
|
+
stringField(payload, "filePath"),
|
|
113
|
+
stringField(payload, "path"),
|
|
114
|
+
].map((value) => value ? normalizePath(value) : value)),
|
|
115
|
+
entries: uniqueStrings([
|
|
116
|
+
...arrayOfStrings(payload.entries),
|
|
117
|
+
stringField(payload, "entryName"),
|
|
118
|
+
stringField(payload, "entry_name"),
|
|
119
|
+
stringField(payload, "runtimeEntry"),
|
|
120
|
+
]),
|
|
121
|
+
proofs: uniqueStrings([
|
|
122
|
+
...arrayOfStrings(payload.proofs),
|
|
123
|
+
stringField(payload, "proofKind"),
|
|
124
|
+
stringField(payload, "proof_kind"),
|
|
125
|
+
]),
|
|
126
|
+
status: statusFromKind(envelope.event.kind),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function normalizeSource(source: string): AgentMemorySourceName | string {
|
|
131
|
+
if (source === "claude" || source === "claude-code") {
|
|
132
|
+
return "claude-code";
|
|
133
|
+
}
|
|
134
|
+
if (source === "codex" || source === "cursor") {
|
|
135
|
+
return source;
|
|
136
|
+
}
|
|
137
|
+
return source || "generic";
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function defaultIntegrationForSource(source: string): AgentMemoryIntegrationKind {
|
|
141
|
+
return source === "cursor" ? "mcp" : "native-hook";
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function trustLevelForIntegration(integration: string): AgentMemoryTrustLevel {
|
|
145
|
+
if (integration === "native-hook") {
|
|
146
|
+
return "direct-hook";
|
|
147
|
+
}
|
|
148
|
+
if (integration === "mcp") {
|
|
149
|
+
return "mcp-tool";
|
|
150
|
+
}
|
|
151
|
+
if (integration === "cli-wrapper") {
|
|
152
|
+
return "forge-command";
|
|
153
|
+
}
|
|
154
|
+
if (integration === "file-watcher") {
|
|
155
|
+
return "file-watcher";
|
|
156
|
+
}
|
|
157
|
+
if (integration === "git-hook") {
|
|
158
|
+
return "git-observer";
|
|
159
|
+
}
|
|
160
|
+
return "manual-import";
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function confidenceForSource(source: string, integration: string): number {
|
|
164
|
+
if (integration === "native-hook") {
|
|
165
|
+
return source === "generic" ? 0.78 : 0.94;
|
|
166
|
+
}
|
|
167
|
+
if (integration === "mcp") {
|
|
168
|
+
return 0.9;
|
|
169
|
+
}
|
|
170
|
+
if (integration === "cli-wrapper") {
|
|
171
|
+
return 0.84;
|
|
172
|
+
}
|
|
173
|
+
return 0.68;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function normalizeEventKind(source: string, eventName: string, raw: Record<string, unknown>): string {
|
|
177
|
+
const canonical = eventName.replace(/\s+/g, "");
|
|
178
|
+
switch (canonical) {
|
|
179
|
+
case "SessionStart":
|
|
180
|
+
return "agent.session.started";
|
|
181
|
+
case "SessionEnd":
|
|
182
|
+
return "agent.session.ended";
|
|
183
|
+
case "UserPromptSubmit":
|
|
184
|
+
return "agent.prompt.submitted";
|
|
185
|
+
case "PreToolUse":
|
|
186
|
+
return "agent.tool.requested";
|
|
187
|
+
case "PermissionRequest":
|
|
188
|
+
return "approval.requested";
|
|
189
|
+
case "PermissionDenied":
|
|
190
|
+
return "approval.denied";
|
|
191
|
+
case "PostToolUseFailure":
|
|
192
|
+
return "agent.tool.failed";
|
|
193
|
+
case "PostToolUse":
|
|
194
|
+
return "agent.tool.completed";
|
|
195
|
+
case "SubagentStart":
|
|
196
|
+
return "agent.subagent.started";
|
|
197
|
+
case "SubagentStop":
|
|
198
|
+
return "agent.subagent.ended";
|
|
199
|
+
case "PreCompact":
|
|
200
|
+
return "agent.memory.compaction.requested";
|
|
201
|
+
case "PostCompact":
|
|
202
|
+
return "agent.memory.compaction.completed";
|
|
203
|
+
case "FileChanged":
|
|
204
|
+
return "agent.file.changed";
|
|
205
|
+
case "Stop":
|
|
206
|
+
return source === "codex" ? "agent.turn.stopped" : "agent.turn.completed";
|
|
207
|
+
default: {
|
|
208
|
+
const tool = stringField(raw, "toolName") ?? stringField(raw, "tool_name");
|
|
209
|
+
if (tool && (canonical === "tool.call" || canonical === "tool_call" || canonical === "unknown")) {
|
|
210
|
+
return "agent.tool.called";
|
|
211
|
+
}
|
|
212
|
+
return canonical.includes(".") ? canonical : `agent.${canonical || "event"}`;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function statusFromKind(kind: string): string | undefined {
|
|
218
|
+
if (kind.endsWith(".completed") || kind === "agent.tool.called") {
|
|
219
|
+
return "completed";
|
|
220
|
+
}
|
|
221
|
+
if (kind.endsWith(".failed")) {
|
|
222
|
+
return "failed";
|
|
223
|
+
}
|
|
224
|
+
if (kind.endsWith(".requested")) {
|
|
225
|
+
return "requested";
|
|
226
|
+
}
|
|
227
|
+
if (kind.endsWith(".denied")) {
|
|
228
|
+
return "denied";
|
|
229
|
+
}
|
|
230
|
+
return undefined;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function stringField(value: Record<string, unknown>, key: string): string | undefined {
|
|
234
|
+
const field = value[key];
|
|
235
|
+
return typeof field === "string" && field.length > 0 ? field : undefined;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function numberField(value: Record<string, unknown>, key: string): number | undefined {
|
|
239
|
+
const field = value[key];
|
|
240
|
+
return typeof field === "number" && Number.isFinite(field) ? field : undefined;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function arrayOfStrings(value: unknown): string[] {
|
|
244
|
+
return Array.isArray(value) ? value.filter((item): item is string => typeof item === "string" && item.length > 0) : [];
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function uniqueStrings(values: Array<string | undefined>): string[] {
|
|
248
|
+
return [...new Set(values.filter((value): value is string => typeof value === "string" && value.length > 0))];
|
|
249
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { hashStable } from "../compiler/primitives/hash.ts";
|
|
2
|
+
import { redactDeltaPayload } from "../delta/redaction.ts";
|
|
3
|
+
|
|
4
|
+
const RAW_TEXT_KEYS = new Set([
|
|
5
|
+
"prompt",
|
|
6
|
+
"userPrompt",
|
|
7
|
+
"last_assistant_message",
|
|
8
|
+
"lastAssistantMessage",
|
|
9
|
+
"completion",
|
|
10
|
+
"message",
|
|
11
|
+
"transcript",
|
|
12
|
+
"transcript_path",
|
|
13
|
+
"transcriptPath",
|
|
14
|
+
"output",
|
|
15
|
+
"stdout",
|
|
16
|
+
"stderr",
|
|
17
|
+
"result",
|
|
18
|
+
]);
|
|
19
|
+
|
|
20
|
+
const RAW_ARGS_KEYS = new Set(["args", "arguments", "tool_input", "toolInput", "input"]);
|
|
21
|
+
|
|
22
|
+
export interface AgentPayloadRedaction {
|
|
23
|
+
value: Record<string, unknown>;
|
|
24
|
+
sensitiveFieldsRemoved: string[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function redactAgentPayload(payload: Record<string, unknown>): AgentPayloadRedaction {
|
|
28
|
+
const removed: string[] = [];
|
|
29
|
+
const coarse = stripRawPayload(payload, [], removed) as Record<string, unknown>;
|
|
30
|
+
const redacted = redactDeltaPayload(coarse);
|
|
31
|
+
return {
|
|
32
|
+
value: redacted.value,
|
|
33
|
+
sensitiveFieldsRemoved: [...new Set([...removed, ...redacted.redaction.diagnostics])],
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function stripRawPayload(value: unknown, path: string[], removed: string[]): unknown {
|
|
38
|
+
if (Array.isArray(value)) {
|
|
39
|
+
return value.slice(0, 50).map((item, index) => stripRawPayload(item, [...path, String(index)], removed));
|
|
40
|
+
}
|
|
41
|
+
if (!value || typeof value !== "object") {
|
|
42
|
+
return value;
|
|
43
|
+
}
|
|
44
|
+
const output: Record<string, unknown> = {};
|
|
45
|
+
for (const [key, child] of Object.entries(value as Record<string, unknown>)) {
|
|
46
|
+
const nextPath = [...path, key];
|
|
47
|
+
if (RAW_TEXT_KEYS.has(key)) {
|
|
48
|
+
removed.push(nextPath.join("."));
|
|
49
|
+
output[`${key}Hash`] = typeof child === "string" ? hashStable(child) : hashStable(JSON.stringify(child ?? null));
|
|
50
|
+
output[`${key}Stored`] = false;
|
|
51
|
+
if (typeof child === "string" && !isPromptLikeKey(key)) {
|
|
52
|
+
const summary = summarizeText(child);
|
|
53
|
+
if (summary) {
|
|
54
|
+
output[`${key}Summary`] = redactDeltaPayload({ summary }).value.summary;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (RAW_ARGS_KEYS.has(key)) {
|
|
60
|
+
removed.push(nextPath.join("."));
|
|
61
|
+
output[`${key}Hash`] = hashStable(JSON.stringify(child ?? null));
|
|
62
|
+
output[`${key}Stored`] = false;
|
|
63
|
+
output[`${key}Shape`] = describeShape(child);
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
output[key] = stripRawPayload(child, nextPath, removed);
|
|
67
|
+
}
|
|
68
|
+
return output;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function isPromptLikeKey(key: string): boolean {
|
|
72
|
+
return key.toLowerCase().includes("prompt") || key.toLowerCase().includes("completion") || key.toLowerCase().includes("message");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function summarizeText(value: string): string | undefined {
|
|
76
|
+
const normalized = value.replace(/\s+/g, " ").trim();
|
|
77
|
+
if (!normalized) {
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
return normalized.length > 160 ? `${normalized.slice(0, 157)}...` : normalized;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function describeShape(value: unknown): unknown {
|
|
84
|
+
if (Array.isArray(value)) {
|
|
85
|
+
return { kind: "array", length: value.length };
|
|
86
|
+
}
|
|
87
|
+
if (value && typeof value === "object") {
|
|
88
|
+
return {
|
|
89
|
+
kind: "object",
|
|
90
|
+
keys: Object.keys(value as Record<string, unknown>).slice(0, 20).sort(),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
return { kind: typeof value };
|
|
94
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { AgentInstallResult } from "../types.ts";
|
|
2
|
+
import { privacyDefaults } from "./codex.ts";
|
|
3
|
+
|
|
4
|
+
const CLAUDE_EVENTS = [
|
|
5
|
+
"SessionStart",
|
|
6
|
+
"UserPromptSubmit",
|
|
7
|
+
"PreToolUse",
|
|
8
|
+
"PermissionRequest",
|
|
9
|
+
"PostToolUse",
|
|
10
|
+
"PostToolUseFailure",
|
|
11
|
+
"PermissionDenied",
|
|
12
|
+
"FileChanged",
|
|
13
|
+
"SubagentStart",
|
|
14
|
+
"SubagentStop",
|
|
15
|
+
"Stop",
|
|
16
|
+
"SessionEnd",
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
export function claudeCodeInstallFiles(): Array<{ path: string; content: string }> {
|
|
20
|
+
const settings = {
|
|
21
|
+
hooks: Object.fromEntries(CLAUDE_EVENTS.map((event) => [
|
|
22
|
+
event,
|
|
23
|
+
[
|
|
24
|
+
{
|
|
25
|
+
matcher: "*",
|
|
26
|
+
hooks: [
|
|
27
|
+
{
|
|
28
|
+
type: "command",
|
|
29
|
+
command: `forge agent ingest claude-code --event ${event}`,
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
])),
|
|
35
|
+
};
|
|
36
|
+
return [
|
|
37
|
+
{ path: ".claude/settings.json", content: `${JSON.stringify(settings, null, 2)}\n` },
|
|
38
|
+
];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function claudeCodeInstallResult(filesWritten: string[], filesPlanned: string[]): AgentInstallResult {
|
|
42
|
+
return {
|
|
43
|
+
ok: true,
|
|
44
|
+
target: "claude-code",
|
|
45
|
+
filesWritten,
|
|
46
|
+
filesPlanned,
|
|
47
|
+
privacy: privacyDefaults(),
|
|
48
|
+
warnings: ["Claude transcript imports are opt-in only; hooks store redacted project memory."],
|
|
49
|
+
exitCode: 0,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { AgentInstallResult } from "../types.ts";
|
|
2
|
+
|
|
3
|
+
const CODEX_EVENTS = [
|
|
4
|
+
"SessionStart",
|
|
5
|
+
"UserPromptSubmit",
|
|
6
|
+
"PreToolUse",
|
|
7
|
+
"PermissionRequest",
|
|
8
|
+
"PostToolUse",
|
|
9
|
+
"SubagentStart",
|
|
10
|
+
"SubagentStop",
|
|
11
|
+
"PreCompact",
|
|
12
|
+
"PostCompact",
|
|
13
|
+
"Stop",
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
export function codexInstallFiles(): Array<{ path: string; content: string }> {
|
|
17
|
+
const hook = {
|
|
18
|
+
hooks: Object.fromEntries(CODEX_EVENTS.map((event) => [
|
|
19
|
+
event,
|
|
20
|
+
[
|
|
21
|
+
{
|
|
22
|
+
matcher: "*",
|
|
23
|
+
hooks: [
|
|
24
|
+
{
|
|
25
|
+
type: "command",
|
|
26
|
+
command: `forge agent ingest codex --event ${event}`,
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
])),
|
|
32
|
+
};
|
|
33
|
+
return [
|
|
34
|
+
{ path: ".codex/hooks.json", content: `${JSON.stringify(hook, null, 2)}\n` },
|
|
35
|
+
];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function codexInstallResult(filesWritten: string[], filesPlanned: string[]): AgentInstallResult {
|
|
39
|
+
return {
|
|
40
|
+
ok: true,
|
|
41
|
+
target: "codex",
|
|
42
|
+
filesWritten,
|
|
43
|
+
filesPlanned,
|
|
44
|
+
privacy: privacyDefaults(),
|
|
45
|
+
warnings: ["Codex memories and transcripts are not imported automatically."],
|
|
46
|
+
exitCode: 0,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function privacyDefaults(): AgentInstallResult["privacy"] {
|
|
51
|
+
return {
|
|
52
|
+
rawPrompts: "off",
|
|
53
|
+
rawCompletions: "off",
|
|
54
|
+
rawToolArgs: "off",
|
|
55
|
+
transcriptImport: "off",
|
|
56
|
+
cloudSync: "off",
|
|
57
|
+
};
|
|
58
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { AgentInstallResult } from "../types.ts";
|
|
2
|
+
import { privacyDefaults } from "./codex.ts";
|
|
3
|
+
|
|
4
|
+
export function cursorInstallFiles(): Array<{ path: string; content: string }> {
|
|
5
|
+
return [
|
|
6
|
+
{
|
|
7
|
+
path: ".cursor/mcp.json",
|
|
8
|
+
content: `${JSON.stringify({
|
|
9
|
+
mcpServers: {
|
|
10
|
+
forgeos: {
|
|
11
|
+
type: "stdio",
|
|
12
|
+
command: "forge",
|
|
13
|
+
args: ["mcp", "serve"],
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
}, null, 2)}\n`,
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
path: ".cursor/rules/forgeos-agent-memory.mdc",
|
|
20
|
+
content: `---\ndescription: ForgeOS Agent Memory Bridge context and MCP workflow.\nalwaysApply: true\n---\n\n# ForgeOS Agent Memory\n\n- Before modifying runtime entries, call the ForgeOS MCP tool \`agent_context\` or run \`forge agent context --current --json\`.\n- Prefer ForgeOS MCP tools for inspect, timeline, check, verify, and agent context.\n- Do not edit \`src/forge/_generated/**\` directly.\n- Do not expose destructive or write tools without approval metadata.\n- After changes, run \`forge check --json\`; use \`forge verify --strict\` before handoff.\n- Do not ask ForgeOS to read Cursor internal chats, checkpoints, or editor databases.\n`,
|
|
21
|
+
},
|
|
22
|
+
];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function cursorInstallResult(filesWritten: string[], filesPlanned: string[]): AgentInstallResult {
|
|
26
|
+
return {
|
|
27
|
+
ok: true,
|
|
28
|
+
target: "cursor",
|
|
29
|
+
filesWritten,
|
|
30
|
+
filesPlanned,
|
|
31
|
+
privacy: privacyDefaults(),
|
|
32
|
+
warnings: ["Cursor internal chats, checkpoints, and databases are not read by ForgeOS."],
|
|
33
|
+
exitCode: 0,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
export const AGENT_EVENT_SCHEMA = "forge.agent-event.v1" as const;
|
|
2
|
+
|
|
3
|
+
export type AgentMemorySourceName = "claude-code" | "codex" | "cursor" | "generic";
|
|
4
|
+
export type AgentMemoryIntegrationKind = "native-hook" | "mcp" | "cli-wrapper" | "file-watcher" | "git-hook" | "manual-import";
|
|
5
|
+
export type AgentMemoryTrustLevel =
|
|
6
|
+
| "direct-hook"
|
|
7
|
+
| "mcp-tool"
|
|
8
|
+
| "forge-command"
|
|
9
|
+
| "file-watcher"
|
|
10
|
+
| "git-observer"
|
|
11
|
+
| "manual-import"
|
|
12
|
+
| "inferred";
|
|
13
|
+
|
|
14
|
+
export interface AgentEventEnvelope {
|
|
15
|
+
schema: typeof AGENT_EVENT_SCHEMA;
|
|
16
|
+
source: {
|
|
17
|
+
agent: AgentMemorySourceName | string;
|
|
18
|
+
integration: AgentMemoryIntegrationKind | string;
|
|
19
|
+
version: string;
|
|
20
|
+
};
|
|
21
|
+
workspace: {
|
|
22
|
+
root: string;
|
|
23
|
+
gitBranch?: string;
|
|
24
|
+
gitHead?: string;
|
|
25
|
+
};
|
|
26
|
+
session: {
|
|
27
|
+
externalSessionId?: string;
|
|
28
|
+
forgeSessionId?: string;
|
|
29
|
+
turnId?: string;
|
|
30
|
+
};
|
|
31
|
+
event: {
|
|
32
|
+
kind: string;
|
|
33
|
+
timestamp: string;
|
|
34
|
+
};
|
|
35
|
+
actor: {
|
|
36
|
+
kind: "agent" | "human" | "forge" | "unknown";
|
|
37
|
+
name: string;
|
|
38
|
+
model?: string;
|
|
39
|
+
};
|
|
40
|
+
payload: Record<string, unknown>;
|
|
41
|
+
privacy: {
|
|
42
|
+
rawPromptStored: false;
|
|
43
|
+
rawCompletionStored: false;
|
|
44
|
+
rawToolArgsStored: false;
|
|
45
|
+
transcriptImported: false;
|
|
46
|
+
redacted: true;
|
|
47
|
+
sensitiveFieldsRemoved: string[];
|
|
48
|
+
};
|
|
49
|
+
capture: {
|
|
50
|
+
trustLevel: AgentMemoryTrustLevel;
|
|
51
|
+
confidence: number;
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface AgentMemoryEventRecord {
|
|
56
|
+
id: string;
|
|
57
|
+
externalEventId: string;
|
|
58
|
+
sourceName: string;
|
|
59
|
+
integrationKind: string;
|
|
60
|
+
trustLevel: string;
|
|
61
|
+
externalSessionId?: string;
|
|
62
|
+
externalTurnId?: string;
|
|
63
|
+
eventKind: string;
|
|
64
|
+
normalizedKind: string;
|
|
65
|
+
summary?: string;
|
|
66
|
+
confidence: number;
|
|
67
|
+
capturedAt: string;
|
|
68
|
+
operationId?: string;
|
|
69
|
+
data: Record<string, unknown>;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface AgentMemoryContextPack {
|
|
73
|
+
ok: true;
|
|
74
|
+
scope: "current" | "entry";
|
|
75
|
+
entry?: string;
|
|
76
|
+
currentState: Record<string, unknown>;
|
|
77
|
+
agentMemory: {
|
|
78
|
+
goals: Array<{ source: string; summary: string; confidence: number }>;
|
|
79
|
+
toolCalls: Array<{ source: string; tool: string; status?: string; summary?: string }>;
|
|
80
|
+
files: string[];
|
|
81
|
+
entries: string[];
|
|
82
|
+
approvals: Array<{ source: string; status: string; summary?: string }>;
|
|
83
|
+
proofs: Array<{ kind: string; result?: string }>;
|
|
84
|
+
events: AgentMemoryEventRecord[];
|
|
85
|
+
openQuestions: string[];
|
|
86
|
+
};
|
|
87
|
+
exitCode: 0;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface AgentInstallResult {
|
|
91
|
+
ok: boolean;
|
|
92
|
+
target: string;
|
|
93
|
+
filesWritten: string[];
|
|
94
|
+
filesPlanned: string[];
|
|
95
|
+
privacy: {
|
|
96
|
+
rawPrompts: "off";
|
|
97
|
+
rawCompletions: "off";
|
|
98
|
+
rawToolArgs: "off";
|
|
99
|
+
transcriptImport: "off";
|
|
100
|
+
cloudSync: "off";
|
|
101
|
+
};
|
|
102
|
+
warnings: string[];
|
|
103
|
+
exitCode: 0 | 1;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export interface AgentIngestResult {
|
|
107
|
+
ok: boolean;
|
|
108
|
+
event?: AgentMemoryEventRecord;
|
|
109
|
+
envelope?: AgentEventEnvelope;
|
|
110
|
+
exitCode: 0 | 1;
|
|
111
|
+
error?: string;
|
|
112
|
+
}
|
package/src/forge/cli/build.ts
CHANGED
|
@@ -46,6 +46,22 @@ async function runOptionalScript(
|
|
|
46
46
|
return { skipped: false, exitCode };
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
function readPackageScripts(workspaceRoot: string): Record<string, string> {
|
|
50
|
+
const packageJsonPath = join(workspaceRoot, "package.json");
|
|
51
|
+
if (!nodeFileSystem.exists(packageJsonPath)) {
|
|
52
|
+
return {};
|
|
53
|
+
}
|
|
54
|
+
const pkg = JSON.parse(nodeFileSystem.readText(packageJsonPath) ?? "{}") as {
|
|
55
|
+
scripts?: Record<string, string>;
|
|
56
|
+
};
|
|
57
|
+
return pkg.scripts ?? {};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function shouldTypecheckBuild(workspaceRoot: string): boolean {
|
|
61
|
+
const scripts = readPackageScripts(workspaceRoot);
|
|
62
|
+
return Boolean(scripts.typecheck || nodeFileSystem.exists(join(workspaceRoot, "tsconfig.json")));
|
|
63
|
+
}
|
|
64
|
+
|
|
49
65
|
export async function runBuildCommand(options: BuildCommandOptions): Promise<BuildCommandResult> {
|
|
50
66
|
const steps: BuildCommandResult["steps"] = [];
|
|
51
67
|
const generate = await runGenerateCommand({
|
|
@@ -63,9 +79,9 @@ export async function runBuildCommand(options: BuildCommandOptions): Promise<Bui
|
|
|
63
79
|
const verify = await runVerifyCommand({
|
|
64
80
|
workspaceRoot: options.workspaceRoot,
|
|
65
81
|
json: false,
|
|
66
|
-
skipTests:
|
|
67
|
-
skipTypecheck:
|
|
68
|
-
skipEslint:
|
|
82
|
+
skipTests: true,
|
|
83
|
+
skipTypecheck: !shouldTypecheckBuild(options.workspaceRoot),
|
|
84
|
+
skipEslint: true,
|
|
69
85
|
strict: false,
|
|
70
86
|
});
|
|
71
87
|
steps.push({ name: "verify", ok: verify.exitCode === 0, exitCode: verify.exitCode });
|