forgeos 0.1.0-alpha.24 → 0.1.0-alpha.25
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 +17 -0
- package/adapters/java/target/forge-java-adapter-0.1.0-alpha.11.jar +0 -0
- package/adapters/java-spring-boot-starter/target/forge-java-spring-boot-starter-0.1.0-alpha.11.jar +0 -0
- package/docs/changelog.md +13 -0
- package/examples/java-billing/target/java-billing-0.1.0-alpha.11-all.jar +0 -0
- package/examples/java-billing/target/java-billing-0.1.0-alpha.11.jar +0 -0
- package/package.json +1 -1
- package/src/forge/_generated/releaseManifest.json +1 -1
- package/src/forge/_generated/releaseManifest.ts +3 -3
- package/src/forge/agent-memory/bridge.ts +64 -7
- package/src/forge/agent-memory/types.ts +4 -0
- package/src/forge/compiler/agent-contract/build.ts +41 -10
- package/src/forge/delta/recorder.ts +136 -58
- package/src/forge/version.ts +1 -1
package/AGENTS.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// @forge-generated generator=0.1.0-alpha.
|
|
1
|
+
// @forge-generated generator=0.1.0-alpha.25 input=f4bc0a51dfadbdbabe4e7083c29844716f74c2beebcb86ce30e7a70c4f9487b4 content=0d493cf0e41b71cb652d5e0e1b0c1f83d2a1281b748321f0b00f0773ba93074e
|
|
2
2
|
# AGENTS.md
|
|
3
3
|
|
|
4
4
|
<!-- forge-generated:start -->
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.1.0-alpha.25
|
|
6
|
+
|
|
7
|
+
### Patch Changes
|
|
8
|
+
|
|
9
|
+
- Harden DeltaDB and Agent Memory under real `forge dev` concurrency.
|
|
10
|
+
|
|
11
|
+
- Stop long-running dev recorders from holding the DeltaDB writer lock between events.
|
|
12
|
+
- Retry short transient DeltaDB writer conflicts before reporting `FORGE_DELTA_BUSY`.
|
|
13
|
+
- Keep Codex hook queue checkpoints unchanged when Agent Memory ingest is blocked by a busy DeltaDB writer, then retry safely instead of losing queued events.
|
|
14
|
+
- Add watcher backoff metadata for lock recovery and document the safe queue/DeltaDB behavior.
|
|
15
|
+
|
|
16
|
+
- Fix tenant-scope reporting in the generated agent contract and capability map.
|
|
17
|
+
|
|
18
|
+
- Match tenant-scoped tables by both authored/camelCase table names and generated SQL snake_case table names.
|
|
19
|
+
- Report camelCase liveQuery dependencies such as `onboardingTasks` as `tenant` scoped when `tenantScope.json` confirms `tenant_id`.
|
|
20
|
+
- Add regression coverage for the Team Onboarding style liveQuery/capability-map path.
|
|
21
|
+
|
|
5
22
|
## 0.1.0-alpha.24
|
|
6
23
|
|
|
7
24
|
### Patch Changes
|
|
Binary file
|
package/adapters/java-spring-boot-starter/target/forge-java-spring-boot-starter-0.1.0-alpha.11.jar
CHANGED
|
Binary file
|
package/docs/changelog.md
CHANGED
|
@@ -6,6 +6,19 @@ The canonical source file in the repository is `CHANGELOG.md`.
|
|
|
6
6
|
|
|
7
7
|
## Unreleased
|
|
8
8
|
|
|
9
|
+
## 0.1.0-alpha.25
|
|
10
|
+
|
|
11
|
+
- Hardened DeltaDB and Agent Memory for concurrent `forge dev` usage:
|
|
12
|
+
dev recorders now release the writer lock between events, Agent Memory ingest
|
|
13
|
+
retries short transient writer conflicts, and queued Codex hook events keep
|
|
14
|
+
their checkpoint unchanged when DeltaDB is temporarily busy.
|
|
15
|
+
- Fixed tenant-scope reporting in the generated agent contract and capability
|
|
16
|
+
map for camelCase authored tables such as `onboardingTasks`, so liveQuery
|
|
17
|
+
dependencies now report `tenant` scope when `tenantScope.json` confirms the
|
|
18
|
+
table is tenant-scoped.
|
|
19
|
+
- Added regression tests and docs for the DeltaDB lock recovery path and the
|
|
20
|
+
Team Onboarding style capability-map tenant-scope path.
|
|
21
|
+
|
|
9
22
|
## 0.1.0-alpha.24
|
|
10
23
|
|
|
11
24
|
- Consolidated the public alpha adoption surface: MIT license, package license
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"defaultProvider":"local","diagnostics":[],"env":{"deployEnv":"FORGE_DEPLOY_ENV","deployId":"FORGE_DEPLOY_ID","publicReleaseId":"NEXT_PUBLIC_FORGE_RELEASE_ID","releaseId":"FORGE_RELEASE_ID"},"gitSha":"unknown","optionalProviders":["local","sentry-compatible","sentry","glitchtip","bugsink","otel","custom"],"packageName":"forgeos","packageVersion":"0.1.0-alpha.
|
|
1
|
+
{"defaultProvider":"local","diagnostics":[],"env":{"deployEnv":"FORGE_DEPLOY_ENV","deployId":"FORGE_DEPLOY_ID","publicReleaseId":"NEXT_PUBLIC_FORGE_RELEASE_ID","releaseId":"FORGE_RELEASE_ID"},"gitSha":"unknown","optionalProviders":["local","sentry-compatible","sentry","glitchtip","bugsink","otel","custom"],"packageName":"forgeos","packageVersion":"0.1.0-alpha.25","releaseId":"forgeos@0.1.0-alpha.25+unknown","schemaVersion":"0.1.0"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// @forge-generated generator=0.1.0-alpha.
|
|
1
|
+
// @forge-generated generator=0.1.0-alpha.25 input=f4bc0a51dfadbdbabe4e7083c29844716f74c2beebcb86ce30e7a70c4f9487b4 content=a7c5e9f36b973e9b5a51716753739f57a63fa77c68b0fad18f059b5407aa58fe
|
|
2
2
|
export const releaseManifest = {
|
|
3
3
|
"defaultProvider": "local",
|
|
4
4
|
"diagnostics": [],
|
|
@@ -19,7 +19,7 @@ export const releaseManifest = {
|
|
|
19
19
|
"custom"
|
|
20
20
|
],
|
|
21
21
|
"packageName": "forgeos",
|
|
22
|
-
"packageVersion": "0.1.0-alpha.
|
|
23
|
-
"releaseId": "forgeos@0.1.0-alpha.
|
|
22
|
+
"packageVersion": "0.1.0-alpha.25",
|
|
23
|
+
"releaseId": "forgeos@0.1.0-alpha.25+unknown",
|
|
24
24
|
"schemaVersion": "0.1.0"
|
|
25
25
|
} as const;
|
|
@@ -113,10 +113,16 @@ async function openMemoryStore(
|
|
|
113
113
|
workspaceRoot: string,
|
|
114
114
|
access: "read" | "write" = "write",
|
|
115
115
|
): Promise<DeltaStore | AgentMemoryUnavailableResult> {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
116
|
+
const retryDelays = access === "write" ? [25, 75, 150] : [];
|
|
117
|
+
for (let attempt = 0; ; attempt += 1) {
|
|
118
|
+
try {
|
|
119
|
+
return await DeltaStore.open(workspaceRoot, { access });
|
|
120
|
+
} catch (error) {
|
|
121
|
+
if (!(error instanceof DeltaStoreBusyError) || attempt >= retryDelays.length) {
|
|
122
|
+
return memoryUnavailable(error, workspaceRoot);
|
|
123
|
+
}
|
|
124
|
+
await sleep(retryDelays[attempt] ?? 0);
|
|
125
|
+
}
|
|
120
126
|
}
|
|
121
127
|
}
|
|
122
128
|
|
|
@@ -131,6 +137,10 @@ function isExternalPgliteRead(result: AgentMemoryUnavailableResult): boolean {
|
|
|
131
137
|
);
|
|
132
138
|
}
|
|
133
139
|
|
|
140
|
+
function isDeltaBusyIngestResult(result: AgentIngestResult): boolean {
|
|
141
|
+
return result.ok === false && result.busy?.code === "FORGE_DELTA_BUSY";
|
|
142
|
+
}
|
|
143
|
+
|
|
134
144
|
function fallbackMemoryPath(workspaceRoot: string): string {
|
|
135
145
|
return join(workspaceRoot, ".forge", "agent", "events.ndjson");
|
|
136
146
|
}
|
|
@@ -413,23 +423,29 @@ async function ingestAgentMemoryQueueFile(options: AgentMemoryCommandOptions): P
|
|
|
413
423
|
source,
|
|
414
424
|
eventName: options.eventName,
|
|
415
425
|
});
|
|
426
|
+
const errors = [
|
|
427
|
+
...drained.errors,
|
|
428
|
+
...(drained.busy ? ["DeltaDB is busy; queue checkpoint was not advanced"] : []),
|
|
429
|
+
];
|
|
416
430
|
return {
|
|
417
|
-
ok:
|
|
431
|
+
ok: errors.length === 0,
|
|
418
432
|
watch: false,
|
|
419
433
|
source,
|
|
420
434
|
file: options.file,
|
|
421
435
|
eventsIngested: drained.eventsIngested,
|
|
422
|
-
errors
|
|
436
|
+
errors,
|
|
423
437
|
bytesRead: drained.bytesRead,
|
|
424
438
|
pendingBytes: drained.pendingBytes,
|
|
425
439
|
checkpointFile: drained.checkpointFile,
|
|
426
440
|
compacted: drained.compacted,
|
|
427
441
|
historyFile: drained.historyFile,
|
|
442
|
+
...(drained.busy ? { busy: drained.busy, pendingDueToBusy: true } : {}),
|
|
428
443
|
nextActions: [
|
|
444
|
+
...(drained.busy ? ["forge delta status --json"] : []),
|
|
429
445
|
`forge agent memory --entry ${source} --json`,
|
|
430
446
|
`forge agent hooks status --target ${source} --json`,
|
|
431
447
|
],
|
|
432
|
-
exitCode:
|
|
448
|
+
exitCode: errors.length === 0 ? 0 : 1,
|
|
433
449
|
};
|
|
434
450
|
}
|
|
435
451
|
|
|
@@ -644,6 +660,7 @@ export async function drainAgentMemoryQueueFile(options: {
|
|
|
644
660
|
checkpointFile: string;
|
|
645
661
|
compacted: boolean;
|
|
646
662
|
historyFile: string;
|
|
663
|
+
busy?: AgentMemoryUnavailableResult["busy"];
|
|
647
664
|
}> {
|
|
648
665
|
const historyFile = queueHistoryPath(options.watchFile);
|
|
649
666
|
if (!existsSync(options.watchFile)) {
|
|
@@ -699,6 +716,17 @@ export async function drainAgentMemoryQueueFile(options: {
|
|
|
699
716
|
eventsIngested += 1;
|
|
700
717
|
consumedOffset = bytesRead + line.endOffset;
|
|
701
718
|
writeQueueCheckpoint(options.watchFile, consumedOffset);
|
|
719
|
+
} else if (isDeltaBusyIngestResult(result)) {
|
|
720
|
+
return {
|
|
721
|
+
eventsIngested,
|
|
722
|
+
errors,
|
|
723
|
+
bytesRead,
|
|
724
|
+
pendingBytes,
|
|
725
|
+
checkpointFile: queueCheckpointPath(options.watchFile),
|
|
726
|
+
compacted: false,
|
|
727
|
+
historyFile,
|
|
728
|
+
busy: result.busy,
|
|
729
|
+
};
|
|
702
730
|
} else {
|
|
703
731
|
errors.push(result.error ?? "agent memory ingest failed");
|
|
704
732
|
break;
|
|
@@ -886,7 +914,19 @@ async function watchAgentMemoryIngest(options: AgentMemoryCommandOptions): Promi
|
|
|
886
914
|
|
|
887
915
|
let eventsIngested = 0;
|
|
888
916
|
const errors: string[] = [];
|
|
917
|
+
let busyRetries = 0;
|
|
918
|
+
let lastBusy: AgentMemoryUnavailableResult["busy"] | undefined;
|
|
889
919
|
let pendingIngest = Promise.resolve();
|
|
920
|
+
let retryTimer: ReturnType<typeof setTimeout> | undefined;
|
|
921
|
+
const scheduleBusyRetry = () => {
|
|
922
|
+
if (retryTimer) {
|
|
923
|
+
return;
|
|
924
|
+
}
|
|
925
|
+
retryTimer = setTimeout(() => {
|
|
926
|
+
retryTimer = undefined;
|
|
927
|
+
pendingIngest = pendingIngest.then(ingestNewContent, ingestNewContent);
|
|
928
|
+
}, 500);
|
|
929
|
+
};
|
|
890
930
|
const ingestNewContent = async () => {
|
|
891
931
|
const result = await drainAgentMemoryQueueFile({
|
|
892
932
|
workspaceRoot: options.workspaceRoot,
|
|
@@ -895,6 +935,13 @@ async function watchAgentMemoryIngest(options: AgentMemoryCommandOptions): Promi
|
|
|
895
935
|
eventName: options.eventName,
|
|
896
936
|
});
|
|
897
937
|
eventsIngested += result.eventsIngested;
|
|
938
|
+
if (result.busy) {
|
|
939
|
+
busyRetries += 1;
|
|
940
|
+
lastBusy = result.busy;
|
|
941
|
+
scheduleBusyRetry();
|
|
942
|
+
return;
|
|
943
|
+
}
|
|
944
|
+
lastBusy = undefined;
|
|
898
945
|
errors.push(...result.errors);
|
|
899
946
|
};
|
|
900
947
|
|
|
@@ -904,6 +951,10 @@ async function watchAgentMemoryIngest(options: AgentMemoryCommandOptions): Promi
|
|
|
904
951
|
pendingIngest = pendingIngest.then(ingestNewContent, ingestNewContent);
|
|
905
952
|
});
|
|
906
953
|
const shutdown = () => {
|
|
954
|
+
if (retryTimer) {
|
|
955
|
+
clearTimeout(retryTimer);
|
|
956
|
+
retryTimer = undefined;
|
|
957
|
+
}
|
|
907
958
|
watcher.close();
|
|
908
959
|
void pendingIngest.finally(() => {
|
|
909
960
|
resolve({
|
|
@@ -913,7 +964,9 @@ async function watchAgentMemoryIngest(options: AgentMemoryCommandOptions): Promi
|
|
|
913
964
|
file,
|
|
914
965
|
eventsIngested,
|
|
915
966
|
errors,
|
|
967
|
+
...(lastBusy ? { busy: lastBusy, pendingDueToBusy: true, busyRetries } : {}),
|
|
916
968
|
nextActions: [
|
|
969
|
+
...(lastBusy ? ["forge delta status --json"] : []),
|
|
917
970
|
`forge agent memory --entry ${source} --json`,
|
|
918
971
|
`forge agent hooks status --target ${source} --json`,
|
|
919
972
|
],
|
|
@@ -971,6 +1024,10 @@ function installAgentMemory(options: AgentMemoryCommandOptions): AgentInstallRes
|
|
|
971
1024
|
return cursorInstallResult(filesWritten, planned);
|
|
972
1025
|
}
|
|
973
1026
|
|
|
1027
|
+
function sleep(ms: number): Promise<void> {
|
|
1028
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1029
|
+
}
|
|
1030
|
+
|
|
974
1031
|
export function formatAgentMemoryJson(result: AgentMemoryCommandResult): string {
|
|
975
1032
|
return `${JSON.stringify(result, null, 2)}\n`;
|
|
976
1033
|
}
|
|
@@ -155,6 +155,7 @@ export interface AgentIngestResult {
|
|
|
155
155
|
path: string;
|
|
156
156
|
reason: "pglite-active";
|
|
157
157
|
};
|
|
158
|
+
busy?: AgentMemoryUnavailableResult["busy"];
|
|
158
159
|
exitCode: 0 | 1;
|
|
159
160
|
error?: string;
|
|
160
161
|
diagnostics?: Diagnostic[];
|
|
@@ -174,6 +175,9 @@ export interface AgentIngestWatchResult {
|
|
|
174
175
|
checkpointFile?: string;
|
|
175
176
|
compacted?: boolean;
|
|
176
177
|
historyFile?: string;
|
|
178
|
+
busy?: AgentMemoryUnavailableResult["busy"];
|
|
179
|
+
pendingDueToBusy?: boolean;
|
|
180
|
+
busyRetries?: number;
|
|
177
181
|
nextActions: string[];
|
|
178
182
|
exitCode: 0 | 1;
|
|
179
183
|
}
|
|
@@ -7,6 +7,7 @@ import { detectSecrets } from "../classifier/secrets.ts";
|
|
|
7
7
|
import { GENERATOR_VERSION } from "../emitter/constants.ts";
|
|
8
8
|
import { stripDeterministicHeader } from "../primitives/header.ts";
|
|
9
9
|
import { canonicalJson, normalizeNewlines, serializeCanonical } from "../primitives/serialize.ts";
|
|
10
|
+
import { toSnakeCase } from "../data-graph/sql/naming.ts";
|
|
10
11
|
import { resolveByPackageName } from "../recipes/registry.ts";
|
|
11
12
|
import {
|
|
12
13
|
defaultRuntimeCompatibility,
|
|
@@ -288,6 +289,35 @@ function runtimeRules(): AgentRuntimeRule[] {
|
|
|
288
289
|
];
|
|
289
290
|
}
|
|
290
291
|
|
|
292
|
+
type TenantTableInfo = {
|
|
293
|
+
tenantIdColumn: string;
|
|
294
|
+
tenantField: string;
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
function buildTenantTableLookup(tenantScope: TenantScope, dataGraph: DataGraph): Map<string, TenantTableInfo> {
|
|
298
|
+
const lookup = new Map<string, TenantTableInfo>();
|
|
299
|
+
for (const scoped of tenantScope.tables) {
|
|
300
|
+
const table = dataGraph.tables.find((candidate) =>
|
|
301
|
+
candidate.name === scoped.table ||
|
|
302
|
+
candidate.exportName === scoped.exportName ||
|
|
303
|
+
toSnakeCase(candidate.name) === scoped.table ||
|
|
304
|
+
toSnakeCase(candidate.exportName) === scoped.table
|
|
305
|
+
);
|
|
306
|
+
const tenantField = table?.fields.find((field) => toSnakeCase(field.name) === scoped.tenantIdColumn)?.name ??
|
|
307
|
+
scoped.tenantIdColumn;
|
|
308
|
+
const info = { tenantIdColumn: scoped.tenantIdColumn, tenantField };
|
|
309
|
+
for (const key of uniqueSorted([
|
|
310
|
+
scoped.table,
|
|
311
|
+
scoped.exportName,
|
|
312
|
+
table?.name,
|
|
313
|
+
table?.exportName,
|
|
314
|
+
].filter((key): key is string => typeof key === "string" && key.length > 0))) {
|
|
315
|
+
lookup.set(key, info);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
return lookup;
|
|
319
|
+
}
|
|
320
|
+
|
|
291
321
|
function agentProtocols(workspaceRoot: string): AgentProtocolInfo[] {
|
|
292
322
|
return [
|
|
293
323
|
{
|
|
@@ -1037,9 +1067,7 @@ export function buildAgentContractArtifacts(
|
|
|
1037
1067
|
input: AgentContractInput,
|
|
1038
1068
|
): AgentContractArtifacts {
|
|
1039
1069
|
const project = readPackageInfo(input.workspaceRoot);
|
|
1040
|
-
const tenantTables =
|
|
1041
|
-
input.tenantScope.tables.map((table) => [table.table, table.tenantIdColumn]),
|
|
1042
|
-
);
|
|
1070
|
+
const tenantTables = buildTenantTableLookup(input.tenantScope, input.dataGraph);
|
|
1043
1071
|
const commandAuth = new Map(
|
|
1044
1072
|
input.policyRegistry.commandAuth.map((binding) => [binding.commandName, binding.auth]),
|
|
1045
1073
|
);
|
|
@@ -1183,13 +1211,16 @@ export function buildAgentContractArtifacts(
|
|
|
1183
1211
|
}),
|
|
1184
1212
|
),
|
|
1185
1213
|
data: {
|
|
1186
|
-
tables: sorted(input.dataGraph.tables, (table) => table.name).map((table) =>
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1214
|
+
tables: sorted(input.dataGraph.tables, (table) => table.name).map((table) => {
|
|
1215
|
+
const tenantInfo = tenantTables.get(table.name);
|
|
1216
|
+
return {
|
|
1217
|
+
name: table.name,
|
|
1218
|
+
file: table.file,
|
|
1219
|
+
tenantScoped: Boolean(tenantInfo),
|
|
1220
|
+
...(tenantInfo ? { tenantField: tenantInfo.tenantField } : {}),
|
|
1221
|
+
fields: uniqueSorted(table.fields.map((field) => field.name)),
|
|
1222
|
+
};
|
|
1223
|
+
}),
|
|
1193
1224
|
},
|
|
1194
1225
|
policies: sorted(input.policyRegistry.policies, (policy) => policy.name).map((policy) => ({
|
|
1195
1226
|
name: policy.name,
|
|
@@ -4,7 +4,7 @@ import type { ForgeCommand } from "../cli/parse.ts";
|
|
|
4
4
|
import { GENERATED_DIR } from "../compiler/emitter/constants.ts";
|
|
5
5
|
import { normalizePath } from "../compiler/primitives/paths.ts";
|
|
6
6
|
import { hashUtf8Bytes } from "../compiler/primitives/hash.ts";
|
|
7
|
-
import { DeltaStore, type DeltaRuntimeCallInput } from "./store.ts";
|
|
7
|
+
import { DeltaStore, DeltaStoreBusyError, type DeltaRuntimeCallInput } from "./store.ts";
|
|
8
8
|
import { classifyArtifactKind } from "./classifier.ts";
|
|
9
9
|
import { readDeltaGitSnapshot } from "./git-observer.ts";
|
|
10
10
|
|
|
@@ -28,70 +28,129 @@ export async function createAmbientDeltaRecorder(
|
|
|
28
28
|
if (isDeltaDisabled()) {
|
|
29
29
|
return noopRecorder;
|
|
30
30
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
let actorId: string | undefined;
|
|
32
|
+
let sessionId: string | undefined;
|
|
33
|
+
let accepting = true;
|
|
34
|
+
let closed = false;
|
|
35
|
+
let queue = Promise.resolve();
|
|
36
|
+
|
|
37
|
+
const withStore = async (fn: (store: DeltaStore) => Promise<void>): Promise<void> => {
|
|
38
|
+
const store = await openDeltaStoreWithRetry(workspaceRoot);
|
|
39
|
+
try {
|
|
40
|
+
await fn(store);
|
|
41
|
+
} finally {
|
|
42
|
+
await store.close();
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const enqueue = (fn: (store: DeltaStore) => Promise<void>): Promise<void> => {
|
|
47
|
+
queue = queue.then(() => safeDelta(() => withStore(fn)));
|
|
48
|
+
return queue;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const ensureSession = async (store: DeltaStore): Promise<{ actorId: string; sessionId: string } | null> => {
|
|
52
|
+
if (actorId && sessionId) {
|
|
53
|
+
return { actorId, sessionId };
|
|
54
|
+
}
|
|
55
|
+
actorId = await store.ensureActor("forge", "forge-cli", { pid: process.pid });
|
|
56
|
+
sessionId = await store.createSession({
|
|
35
57
|
source,
|
|
36
58
|
summary,
|
|
37
59
|
metadata: { actorId },
|
|
38
60
|
git: readDeltaGitSnapshot(workspaceRoot),
|
|
39
61
|
});
|
|
40
|
-
return {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
},
|
|
79
|
-
});
|
|
62
|
+
return { actorId, sessionId };
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
await enqueue(async (store) => {
|
|
66
|
+
await ensureSession(store);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
get sessionId() {
|
|
71
|
+
return sessionId;
|
|
72
|
+
},
|
|
73
|
+
async recordRuntimeCall(input) {
|
|
74
|
+
if (!accepting) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
await enqueue(async (store) => {
|
|
78
|
+
const session = await ensureSession(store);
|
|
79
|
+
if (!session) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const failedCode = input.diagnosticCode ?? diagnosticCode(input.diagnostics);
|
|
83
|
+
await store.appendOperation({
|
|
84
|
+
sessionId: session.sessionId,
|
|
85
|
+
actorId: session.actorId,
|
|
86
|
+
kind: input.result === "denied"
|
|
87
|
+
? "runtime.entry.denied"
|
|
88
|
+
: input.result === "failed"
|
|
89
|
+
? "runtime.entry.failed"
|
|
90
|
+
: "runtime.entry.executed",
|
|
91
|
+
summary: `${input.entryName} ${input.result ?? "executed"}`,
|
|
92
|
+
data: {
|
|
93
|
+
entryName: input.entryName,
|
|
94
|
+
entryKind: input.entryKind,
|
|
95
|
+
result: input.result,
|
|
96
|
+
traceId: input.traceId,
|
|
97
|
+
diagnosticCode: failedCode,
|
|
98
|
+
},
|
|
99
|
+
runtimeCall: { ...input, diagnosticCode: failedCode },
|
|
80
100
|
});
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
101
|
+
});
|
|
102
|
+
},
|
|
103
|
+
async recordAgentTool(input) {
|
|
104
|
+
if (!accepting) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
await enqueue(async (store) => {
|
|
108
|
+
const session = await ensureSession(store);
|
|
109
|
+
if (!session) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
await store.appendOperation({
|
|
113
|
+
sessionId: session.sessionId,
|
|
114
|
+
actorId: session.actorId,
|
|
115
|
+
kind: "agent.tool.called",
|
|
116
|
+
summary: `${input.toolName} ${input.status}`,
|
|
117
|
+
data: {
|
|
118
|
+
toolName: input.toolName,
|
|
119
|
+
risk: input.risk,
|
|
120
|
+
status: input.status,
|
|
121
|
+
traceId: input.traceId,
|
|
122
|
+
durationMs: input.durationMs,
|
|
123
|
+
},
|
|
89
124
|
});
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
125
|
+
});
|
|
126
|
+
},
|
|
127
|
+
async recordFileChanged(path, changeType = "modified") {
|
|
128
|
+
if (!accepting) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
await enqueue(async (store) => {
|
|
132
|
+
const session = await ensureSession(store);
|
|
133
|
+
if (!session) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
await store.recordFilePath(session.sessionId, path, changeType);
|
|
137
|
+
});
|
|
138
|
+
},
|
|
139
|
+
async close(closeSummary) {
|
|
140
|
+
if (closed) {
|
|
141
|
+
await queue;
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
accepting = false;
|
|
145
|
+
closed = true;
|
|
146
|
+
await enqueue(async (store) => {
|
|
147
|
+
if (!sessionId) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
await store.endSession(sessionId, closeSummary);
|
|
151
|
+
});
|
|
152
|
+
},
|
|
153
|
+
};
|
|
95
154
|
}
|
|
96
155
|
|
|
97
156
|
export async function recordParsedCliCommand(input: {
|
|
@@ -343,6 +402,25 @@ const noopRecorder: AmbientDeltaRecorder = {
|
|
|
343
402
|
async close() {},
|
|
344
403
|
};
|
|
345
404
|
|
|
405
|
+
const DELTA_STORE_RETRY_DELAYS_MS = [25, 75, 150];
|
|
406
|
+
|
|
407
|
+
async function openDeltaStoreWithRetry(workspaceRoot: string): Promise<DeltaStore> {
|
|
408
|
+
for (let attempt = 0; ; attempt += 1) {
|
|
409
|
+
try {
|
|
410
|
+
return await DeltaStore.open(workspaceRoot);
|
|
411
|
+
} catch (error) {
|
|
412
|
+
if (!(error instanceof DeltaStoreBusyError) || attempt >= DELTA_STORE_RETRY_DELAYS_MS.length) {
|
|
413
|
+
throw error;
|
|
414
|
+
}
|
|
415
|
+
await sleep(DELTA_STORE_RETRY_DELAYS_MS[attempt] ?? 0);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function sleep(ms: number): Promise<void> {
|
|
421
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
422
|
+
}
|
|
423
|
+
|
|
346
424
|
async function safeDelta(fn: () => Promise<void>): Promise<void> {
|
|
347
425
|
try {
|
|
348
426
|
await fn();
|
package/src/forge/version.ts
CHANGED