@deksden-com/dd-flow-cli 0.9.0-beta.144 → 0.9.0-beta.145
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +59 -0
- package/README.md +13 -8
- package/dist/build-info.json +7 -7
- package/dist/cli/command-inputs.js +9 -3
- package/dist/cli/command-metadata.js +33 -14
- package/dist/cli/help.js +32 -1
- package/dist/cli/hook-ingress.js +27 -3
- package/dist/cli/memory-flow-admission.js +7 -3
- package/dist/cli/memory-init-invocation.js +10 -3
- package/dist/cli/memory-init.js +74 -68
- package/dist/cli/memory-storage-admission.js +3 -2
- package/dist/cli/memory-storage-qualification.js +105 -0
- package/dist/cli/memory-storage-transition.js +123 -0
- package/dist/cli/memory-upgrade.js +189 -0
- package/dist/cli/native-hook-ingress.js +14 -4
- package/dist/cli/native-hook-preparation.js +55 -0
- package/dist/cli/response-file.js +2 -1
- package/dist/cli/run-cli.js +70 -32
- package/dist/cli/setup.js +2 -1
- package/dist/cli.js +12 -0
- package/dist/domain/stage-handler-identities.js +8 -0
- package/dist/harness-runtime/bin/dd-agy.mjs +3 -3
- package/dist/harness-runtime/bin/dd-grok.mjs +3 -3
- package/dist/harness-runtime/bin/dd-zcode.mjs +5 -5
- package/dist/harness-runtime/lib/agy-error-boundary.mjs +58 -0
- package/dist/harness-runtime/lib/dd-agy-daemon.mjs +62 -20
- package/dist/harness-runtime/lib/dd-agy.mjs +17 -3
- package/dist/harness-runtime/lib/dd-droid.mjs +1 -1
- package/dist/harness-runtime/lib/dd-grok-daemon.mjs +70 -13
- package/dist/harness-runtime/lib/dd-grok.mjs +6 -3
- package/dist/harness-runtime/lib/dd-zcode.mjs +44 -14
- package/dist/harness-runtime/lib/delegation-instructions.mjs +1 -1
- package/dist/harness-runtime/lib/entity-references.d.mts +2 -0
- package/dist/harness-runtime/lib/entity-references.mjs +9 -0
- package/dist/harness-runtime/lib/managed-daemon.mjs +13 -6
- package/dist/harness-runtime/lib/native-hook-command.mjs +4 -4
- package/dist/harness-runtime/lib/opencode-plugin.mjs +3 -3
- package/dist/harness-runtime/lib/operation-errors.mjs +1 -1
- package/dist/harness-runtime/lib/provider-overload-policy.d.mts +4 -2
- package/dist/harness-runtime/lib/provider-overload-policy.mjs +19 -6
- package/dist/harness-runtime/lib/runner-events.mjs +3 -1
- package/dist/harness-runtime/lib/setup-progress.mjs +8 -6
- package/dist/schemas/mb-upgrade-stage-result.schema.json +863 -0
- package/dist/schemas/memory-repair-work-result.schema.json +9 -2
- package/dist/schemas/release-impact.schema.json +3 -3
- package/dist/schemas/stage-start-response.schema.json +4 -0
- package/dist/services/accepted-intent.js +13 -1
- package/dist/services/canon.js +43 -7
- package/dist/services/cli-operation-classifier.js +1 -1
- package/dist/services/cli-upgrade-allowlist.js +1 -1
- package/dist/services/code-checks.js +11 -4
- package/dist/services/compatibility-preflight.js +5 -2
- package/dist/services/controller-fanout.js +21 -4
- package/dist/services/controller-memory-task.js +45 -0
- package/dist/services/controller-memory-worker.js +48 -0
- package/dist/services/dashboard.js +2 -2
- package/dist/services/descriptor-run-start.js +26 -19
- package/dist/services/descriptor-stage-coordinator.js +30 -10
- package/dist/services/descriptor-stage-start.js +40 -4
- package/dist/services/engines.js +64 -14
- package/dist/services/eval-snapshots.js +104 -93
- package/dist/services/external-work-launch.js +5 -3
- package/dist/services/flow-catalog.js +8 -4
- package/dist/services/frozen-engine.js +113 -11
- package/dist/services/hook-memory-routing.js +188 -0
- package/dist/services/hook-recovery-guard.js +14 -0
- package/dist/services/hook-session-observation.js +24 -0
- package/dist/services/hooks.js +27 -8
- package/dist/services/hybrid-contracts.js +2 -13
- package/dist/services/hybrid-digest.js +14 -0
- package/dist/services/installed-flow-source.js +25 -4
- package/dist/services/lifecycle-invocations.js +33 -5
- package/dist/services/memory-accepted-composition.js +5 -4
- package/dist/services/memory-completed-correction-wave.js +23 -15
- package/dist/services/memory-compose-handler.js +163 -21
- package/dist/services/memory-composition-acceptance.js +186 -10
- package/dist/services/memory-composition-graph.js +42 -11
- package/dist/services/memory-composition-registration.js +23 -8
- package/dist/services/memory-composition-work-custody.js +14 -9
- package/dist/services/memory-composition-work-results.js +15 -3
- package/dist/services/memory-corrected-review-batch.js +10 -3
- package/dist/services/memory-corrected-review-findings.js +4 -2
- package/dist/services/memory-corrected-review-inputs.js +10 -5
- package/dist/services/memory-corrected-review-receipt.js +17 -9
- package/dist/services/memory-corrected-review-registration.js +3 -2
- package/dist/services/memory-correction-check-receipt.js +20 -4
- package/dist/services/memory-custody-snapshot.js +36 -0
- package/dist/services/memory-finish-evidence.js +44 -22
- package/dist/services/memory-finish-handler.js +37 -23
- package/dist/services/memory-finish-integration.js +218 -48
- package/dist/services/memory-finish-readiness.js +66 -19
- package/dist/services/memory-git-integration.js +87 -18
- package/dist/services/memory-historical-corrected-review.js +21 -15
- package/dist/services/memory-historical-original-review.js +22 -9
- package/dist/services/memory-historical-repair-registration.js +14 -8
- package/dist/services/memory-historical-second-review.js +30 -11
- package/dist/services/memory-init-contexts.js +28 -2
- package/dist/services/memory-init-controller-start.js +19 -8
- package/dist/services/memory-init-git-policy.js +50 -21
- package/dist/services/memory-init-lint-capability.js +21 -3
- package/dist/services/memory-init-native-capacity.js +13 -0
- package/dist/services/memory-init-preflight.js +11 -1
- package/dist/services/memory-init-request-candidate.js +73 -17
- package/dist/services/memory-init-request-input.js +23 -7
- package/dist/services/memory-init-run-bundle.js +49 -12
- package/dist/services/memory-init-storage.js +17 -7
- package/dist/services/memory-layer.js +68 -4
- package/dist/services/memory-lint-config.js +111 -2
- package/dist/services/memory-native-review-copy.js +5 -67
- package/dist/services/memory-normal-flow-entry.js +12 -0
- package/dist/services/memory-ordinary-defaults.js +22 -5
- package/dist/services/memory-preflight-handler.js +15 -0
- package/dist/services/memory-repair-current-view.js +1 -1
- package/dist/services/memory-repair-document-delta.js +70 -33
- package/dist/services/memory-repair-history.js +12 -7
- package/dist/services/memory-repair-physical-binding.js +1 -119
- package/dist/services/memory-repair-publication-plan.js +5 -3
- package/dist/services/memory-repair-publication-source.js +1 -1
- package/dist/services/memory-repair-publication.js +3 -2
- package/dist/services/memory-repair-registration.js +2 -1
- package/dist/services/memory-repair-work-baseline.js +4 -2
- package/dist/services/memory-repair-work-completion.js +32 -12
- package/dist/services/memory-repair-work-results.js +21 -2
- package/dist/services/memory-repair-workspace.js +11 -2
- package/dist/services/memory-repair-writer-copy.js +2 -1
- package/dist/services/memory-result-binding.js +78 -12
- package/dist/services/memory-retained-review-authority.js +17 -8
- package/dist/services/memory-retained-second-baseline.js +1 -1
- package/dist/services/memory-retained-second-repairs.js +12 -6
- package/dist/services/memory-review-correction-plan.js +47 -12
- package/dist/services/memory-review-handler.js +17 -17
- package/dist/services/memory-review-inputs.js +55 -9
- package/dist/services/memory-review-limits.js +10 -0
- package/dist/services/memory-review-receipt.js +2 -2
- package/dist/services/memory-review-registration.js +13 -4
- package/dist/services/memory-review-work-custody.js +3 -1
- package/dist/services/memory-review-work-input.js +26 -7
- package/dist/services/memory-review-work-results.js +26 -4
- package/dist/services/memory-second-corrected-review-inputs.js +5 -3
- package/dist/services/memory-second-correction-plan.js +2 -1
- package/dist/services/memory-second-repair-launch.js +4 -4
- package/dist/services/memory-second-repair-source.js +1 -1
- package/dist/services/memory-stage-dispatch.js +38 -1
- package/dist/services/memory-stage-results.js +113 -22
- package/dist/services/memory-upgrade-analysis-handler.js +243 -0
- package/dist/services/memory-upgrade-bootstrap-readiness.js +251 -0
- package/dist/services/memory-upgrade-evidence.js +100 -0
- package/dist/services/memory-upgrade-inventory.js +442 -0
- package/dist/services/memory-upgrade-publication-custody.js +73 -0
- package/dist/services/memory-upgrade-publication.js +413 -0
- package/dist/services/memory-upgrade-selection.js +430 -0
- package/dist/services/memory-upgrade-start.js +60 -0
- package/dist/services/memory-upgrade-storage-readiness.js +340 -0
- package/dist/services/memory-verification-acceptance.js +38 -17
- package/dist/services/memory-verification-checks.js +77 -23
- package/dist/services/memory-verification-receipt.js +6 -5
- package/dist/services/memory-verify-handler.js +15 -13
- package/dist/services/memory-work-result-json.js +12 -0
- package/dist/services/memory-work-result-preparation.js +5 -4
- package/dist/services/merge-server.js +4 -2
- package/dist/services/migrations.js +127 -98
- package/dist/services/physical-startup.js +45 -0
- package/dist/services/project-registry.js +97 -0
- package/dist/services/project-summary.js +1 -1
- package/dist/services/projects.js +3 -93
- package/dist/services/prompt-delivery.js +71 -0
- package/dist/services/provider-capacity.js +69 -8
- package/dist/services/recovery-snapshot-database.js +32 -4
- package/dist/services/repair-intents.js +1 -12
- package/dist/services/repair-receipt.js +13 -0
- package/dist/services/run-control-worker.js +35 -18
- package/dist/services/run-controller-process.js +2 -6
- package/dist/services/run-controller-recovery.js +6 -1
- package/dist/services/run-controller.js +131 -80
- package/dist/services/run-recovery-guard.js +30 -0
- package/dist/services/run-recovery.js +11 -44
- package/dist/services/run-start-custody.js +17 -5
- package/dist/services/runtime-scope-worker.js +46 -19
- package/dist/services/sessions.js +3 -24
- package/dist/services/setup.js +63 -13
- package/dist/services/stage-handlers.js +9 -7
- package/dist/services/stage-pause.js +108 -44
- package/dist/services/version-status.js +1 -1
- package/dist/services/vnext-fanout.js +21 -9
- package/dist/services/vnext-plan-review.js +3 -5
- package/dist/services/work-registry.js +80 -33
- package/dist/services/workspace-files.js +1 -1
- package/dist/shared/entity-references.js +1 -8
- package/dist/storage/data-preservation.js +65 -0
- package/dist/storage/database.js +53 -19
- package/dist/storage/import-guard.js +84 -27
- package/dist/storage/memory-upgrade-schema-bootstrap.js +38 -0
- package/dist/storage/paired-writer-migration.js +424 -0
- package/dist/storage/writer-contract.js +11 -4
- package/dist/storage/writer-migration.js +313 -52
- package/dist/storage/writer-restore.js +217 -0
- package/package.json +8 -4
|
@@ -19,14 +19,15 @@ import { agyWorkspaceHooks } from "./agy-hooks.mjs";
|
|
|
19
19
|
import { durableDaemonDispatch, completeDaemonShutdown, assertDaemonDispatch, assertDaemonRestartable, trackDaemonConnections, retireDaemonEndpoint, retireFailedDaemon } from "./daemon-operations.mjs";
|
|
20
20
|
import path from "node:path";
|
|
21
21
|
import { observeModel } from "./model-observations.mjs";
|
|
22
|
-
import { AGY_VERSION_TIMEOUT_MS, AgyError, agyTerminalFailure, assertProfile, executable, observedProfile, runAgy, usageSnapshot } from "./dd-agy.mjs";
|
|
22
|
+
import { AGY_VERSION_TIMEOUT_MS, AgyError, agyTerminalFailure, agyModelArgs, assertProfile, executable, observedProfile, runAgy, usageSnapshot } from "./dd-agy.mjs";
|
|
23
|
+
import { recoveredAgyErrorBoundary } from "./agy-error-boundary.mjs";
|
|
23
24
|
import { settledNativeTree } from "./native-children.mjs";
|
|
24
25
|
import { retainStartupFailure, prepareRuntimeOwner, cleanupFailedStart, cleanupFailedDaemonStart, confirmDaemonProcess, confirmDaemonStopped, finishDaemonProcess, scheduleDaemonProcessHeartbeat, registerDaemonProcess, stopProcessGroup } from "./managed-daemon.mjs";
|
|
25
26
|
import { readNativeLines, streamNativeDiagnostics, nativeObservationError, admitNativeRequestBuffer } from "./native-streams.mjs";
|
|
26
27
|
|
|
27
28
|
const REQUEST_SCHEMA = "dd-agy/daemon-request@1", RESPONSE_SCHEMA = "dd-agy/daemon-response@1", STATE_SCHEMA = "dd-agy/daemon-state@1";
|
|
28
29
|
class DaemonError extends AgyError {}
|
|
29
|
-
export async function captureAgyTranscript(runtimeRoot, child, rootSessionId, ownedSessionIds = [rootSessionId]) {
|
|
30
|
+
export async function captureAgyTranscript(runtimeRoot, child, rootSessionId, ownedSessionIds = [rootSessionId], inspect = null) {
|
|
30
31
|
const session = { harness_id: "antigravity-cli", session_id: child.provider_session_id };
|
|
31
32
|
let handle;
|
|
32
33
|
try {
|
|
@@ -43,6 +44,7 @@ export async function captureAgyTranscript(runtimeRoot, child, rootSessionId, ow
|
|
|
43
44
|
const { bytesRead } = await handle.read(buffer, 0, buffer.length, 0);
|
|
44
45
|
const bytes = buffer.subarray(0, bytesRead), prefix = bytes.subarray(0, bytes.lastIndexOf(10) + 1);
|
|
45
46
|
const source = { locator: child.log_uri, byte_start: 0, byte_end: prefix.length, sha256: createHash("sha256").update(prefix).digest("hex") };
|
|
47
|
+
if (prefix.length === bytesRead && stat.size <= maximum) inspect?.(prefix.toString("utf8"), source);
|
|
46
48
|
const events = agyTranscriptObservations(prefix.toString("utf8"), child.provider_session_id, child.parent_provider_session_id ?? rootSessionId, source);
|
|
47
49
|
if (prefix.length !== bytesRead || stat.size > maximum) events.push({ session, reason: stat.size > maximum ? "agy_transcript_size_limit" : "agy_transcript_partial_tail" });
|
|
48
50
|
return events;
|
|
@@ -224,8 +226,8 @@ export const stopDaemon = async options => { const timeoutMs = options.timeoutMs
|
|
|
224
226
|
export class Runtime {
|
|
225
227
|
constructor(paths, state) { this.paths = paths; this.state = state; this.config = state.config; this.child = null; this.providerProcess = state.provider_process ?? null; this.providerFinalization = Promise.resolve(); this.buffer = ""; this.init = null; this.readyPromise = null; this.active = null; this.exited = false; this.toolObservations = new ToolObservations(); for (const reason of state.tool_observation_gaps ?? []) this.toolObservations.gaps.add(reason); for (const event of state.tool_observations ?? []) this.toolObservations.add(event); if (state.tool_calls?.length && !state.tool_observations) this.toolObservations.gaps.add("legacy_tools_not_attributed"); this.turnGeneration = state.turn_generation ?? 0; this.descendants = new Map((state.descendants ?? []).map(child => [child.provider_session_id, child])); this.lastResult = state.last_result ?? null; this.sessionObservations = new Map(state.session_observations ?? []); this.startupError = null; this.persisting = Promise.resolve(); this.draining = Promise.resolve(); this.lastActivityAt = state.last_activity_at ?? null; this.usageIngest = null; this.lastProviderEvent = null; }
|
|
226
228
|
persist(patch = {}) { Object.assign(this.state, patch, { pid: process.pid, updated_at: new Date().toISOString(), last_activity_at: this.lastActivityAt, session_observations: [...this.sessionObservations], tool_observations: [...this.toolObservations.calls.values(), ...this.toolObservations.sources.values()], tool_observation_gaps: [...this.toolObservations.gaps], turn_generation: this.turnGeneration, descendants: [...this.descendants.values()], sessions: this.init ? [{ provider_session_id: this.init.conversation_id, root_provider_session_id: this.init.conversation_id, cwd: this.config.cwd }] : [] }); const snapshot = structuredClone(this.state); const queued = performance.now(); this.persisting = this.persisting.catch(() => {}).then(async () => { const started = performance.now(); await writeJson(this.paths.state, snapshot); this.persistTiming = { queue_ms: Math.round(started - queued), write_ms: Math.round(performance.now() - started) }; }); return this.persisting; }
|
|
227
|
-
async journal(kind, payload = {}) { const line = `${JSON.stringify({ schema_id: "dd-agy/daemon-event@1", event_id: randomUUID(), observed_at: new Date().toISOString(), kind, ...payload })}\n`; for (const journal of new Set([this.paths.journal, this.config.journal].filter(Boolean))) { await mkdir(path.dirname(journal), { recursive: true, mode: 0o700 }); await appendFile(journal, line, { mode: 0o600 }); } }
|
|
228
|
-
args(sessionId) { return ["--input-format", "stream-json", "--output-format", "stream-json",
|
|
229
|
+
async journal(kind, payload = {}, reservedPosition = null) { const position = reservedPosition ?? (this.journalPosition = (this.journalPosition ?? 0) + 1); const line = `${JSON.stringify({ schema_id: "dd-agy/daemon-event@1", event_id: randomUUID(), position, observed_at: new Date().toISOString(), kind, ...payload })}\n`; for (const journal of new Set([this.paths.journal, this.config.journal].filter(Boolean))) { await mkdir(path.dirname(journal), { recursive: true, mode: 0o700 }); await appendFile(journal, line, { mode: 0o600 }); } return position; }
|
|
230
|
+
args(sessionId) { return ["--input-format", "stream-json", "--output-format", "stream-json", ...agyModelArgs(this.config.model, this.config.reasoning), "--mode", this.config.mode, "--dangerously-skip-permissions", "--print-timeout", "0", `--gemini_dir=${this.paths.gemini}`, "--app_data_dir=runtime", ...(sessionId ? ["--conversation", sessionId] : ["--new-project"])]; }
|
|
229
231
|
requireSessionIdentity(requestedId) {
|
|
230
232
|
const observedId = this.init?.conversation_id ?? null;
|
|
231
233
|
if (!requestedId || requestedId === observedId) return observedId;
|
|
@@ -288,7 +290,7 @@ export class Runtime {
|
|
|
288
290
|
const providerProcess = this.providerProcess;
|
|
289
291
|
try { await this.persist({ provider_process: providerProcess }); }
|
|
290
292
|
catch (error) { await cleanupFailedStart(this.config, providerProcess, null, error); throw error; }
|
|
291
|
-
try { assertDaemonDispatch(this.paths?.dir); this.child = spawn(target.command, target.args, { cwd: this.config.cwd, env: sanitizedEnv(this.config), detached: true, stdio: ["pipe", "pipe", "pipe"] }); }
|
|
293
|
+
try { assertDaemonDispatch(this.paths?.dir); this.connectionId = randomUUID(); this.child = spawn(target.command, target.args, { cwd: this.config.cwd, env: sanitizedEnv(this.config), detached: true, stdio: ["pipe", "pipe", "pipe"] }); }
|
|
292
294
|
catch (error) { await cleanupFailedStart(this.config, providerProcess, null, error); throw error; }
|
|
293
295
|
const providerChild = this.child;
|
|
294
296
|
let allowProviderFinalization;
|
|
@@ -334,10 +336,7 @@ export class Runtime {
|
|
|
334
336
|
// child can close while the asynchronous write is still pending.
|
|
335
337
|
if (event.event === "result" && ["ERROR", "CANCELLED"].includes(event.result?.status)) {
|
|
336
338
|
this.lastTerminalFailure ??= this.state.last_terminal_failure;
|
|
337
|
-
|
|
338
|
-
&& this.lastTerminalFailure.details.provider_session_id === (event.result.conversation_id || this.init?.conversation_id || null)
|
|
339
|
-
&& (this.active?.previousError === event.result.error || this.lastResult?.status === "ERROR" && this.lastResult.error === event.result.error));
|
|
340
|
-
event.terminal_metadata = repeated ? this.lastTerminalFailure.details : { observed_at: new Date().toISOString(), provider_session_id: event.result.conversation_id || this.init?.conversation_id || null,
|
|
339
|
+
event.terminal_metadata = { observed_at: new Date().toISOString(), provider_session_id: event.result.conversation_id || this.init?.conversation_id || null,
|
|
341
340
|
operation_id: this.active?.operationId ?? null, turn_generation: this.active?.generation ?? null, journal_locator: this.paths.journal, native_source: "agy.result" };
|
|
342
341
|
if (!this.init) this.startupError ??= agyTerminalFailure(event.result, event.terminal_metadata);
|
|
343
342
|
this.receivedTerminalMetadata = event.terminal_metadata;
|
|
@@ -399,16 +398,24 @@ export class Runtime {
|
|
|
399
398
|
return;
|
|
400
399
|
}
|
|
401
400
|
const current = this.active;
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
401
|
+
if (current?.dispatched === false) {
|
|
402
|
+
const error = new DaemonError("operation_observation_lost", "Unexpected native terminal before this stream request was dispatched", false, { operation_id: current.operationId, turn_generation: current.generation, native_outcome: "unknown" });
|
|
403
|
+
this.active = null; current.reject(error);
|
|
404
|
+
await this.journal("unexpected_terminal_before_dispatch", { result, operation_id: current.operationId });
|
|
405
|
+
await this.persist({ unclaimed_activity: true, active_tree: true });
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
const terminalMetadata = { received_at: new Date().toISOString(), ...(retainedMetadata ?? this.receivedTerminalMetadata ?? { observed_at: new Date().toISOString(), provider_session_id: result.conversation_id || this.init?.conversation_id || null, operation_id: current?.operationId ?? null,
|
|
409
|
+
turn_generation: current?.generation ?? null, journal_locator: this.paths.journal, native_source: "agy.result" }) };
|
|
407
410
|
this.receivedTerminalMetadata = null;
|
|
408
411
|
// The ordered stream returns one result per input. Model usage counters
|
|
409
412
|
// are not request IDs: native command rejections can report zero turns.
|
|
410
413
|
// prompt() drains earlier events before accepting another input.
|
|
411
|
-
if (!current && this.lastResult) {
|
|
414
|
+
if (!current && this.lastResult) {
|
|
415
|
+
await this.journal("stale_terminal_observed", { result });
|
|
416
|
+
if (!isDeepStrictEqual(result, this.lastResult)) await this.persist({ unclaimed_activity: true, active_tree: true });
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
412
419
|
this.lastResult = result; this.markTerminalActivity(result);
|
|
413
420
|
const rootStop = this.sessionObservations.get(this.init?.conversation_id)?.stop;
|
|
414
421
|
const pendingTerminal = current && result.status === "SUCCESS";
|
|
@@ -418,7 +425,12 @@ export class Runtime {
|
|
|
418
425
|
try {
|
|
419
426
|
await this.persist({ active_tree: true, last_result: result, last_terminal_metadata: terminalMetadata });
|
|
420
427
|
if (current) current.terminalPersisted = true;
|
|
421
|
-
|
|
428
|
+
const terminalPosition = await this.journal("provider_event", { event: providerEvent ?? { event: "result", result } });
|
|
429
|
+
terminalMetadata.stream_terminal_position = terminalPosition;
|
|
430
|
+
if (current?.outboundPosition && current.dispatchJournalSettled && !current.dispatchObservationError && this.config.daemonId && current.connectionId && current.operationId) terminalMetadata.request_proof = {
|
|
431
|
+
kind: "stream_request", daemon_instance: this.config.daemonId, connection_id: current.connectionId, operation_id: current.operationId,
|
|
432
|
+
turn_generation: current.generation, session_id: this.init.conversation_id, outbound_position: current.outboundPosition, terminal_position: terminalPosition
|
|
433
|
+
};
|
|
422
434
|
await this.captureChildTools();
|
|
423
435
|
} catch (error) { return this.failTerminalObservation(current, result, terminalMetadata, error); }
|
|
424
436
|
try { this.usageIngest = await forwardUsage(this.config, result.conversation_id ?? this.init.conversation_id, result, this.toolSnapshot(), [...this.descendants.values()]); }
|
|
@@ -429,9 +441,19 @@ export class Runtime {
|
|
|
429
441
|
if (!current) return;
|
|
430
442
|
if (this.active !== current) return;
|
|
431
443
|
if (result.status === "ERROR" || result.status === "CANCELLED") {
|
|
444
|
+
await this.observeHookFailures();
|
|
445
|
+
if (this.active !== current) return;
|
|
432
446
|
this.active = null;
|
|
433
447
|
const settled = this.receipt().settled;
|
|
434
|
-
|
|
448
|
+
if (result.status === "ERROR" && settled && rootStop?.fullyIdle === true && !this.state.last_hook_rejection && current.operationId && this.paths.runtime) {
|
|
449
|
+
const root = this.init.conversation_id, transcript = path.join(this.paths.runtime, "brain", root, ".system_generated", "logs", "transcript.jsonl");
|
|
450
|
+
await captureAgyTranscript(this.paths.runtime, { provider_session_id: root, parent_provider_session_id: root, log_uri: new URL(`file://${transcript}`).href }, root, [root], (text, source) => {
|
|
451
|
+
const boundary = recoveredAgyErrorBoundary(text, result.error, current.stepFloor);
|
|
452
|
+
const hook = current.nativeHookEvents?.get(boundary?.boundary_step);
|
|
453
|
+
if (boundary && hook) terminalMetadata.recovered_error_boundary = { ...boundary, receipt: { ...boundary.receipt, native_hook_event_key: hook, daemon_instance: this.config.daemonId }, source };
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
this.lastTerminalFailure = agyTerminalFailure(result, { ...terminalMetadata, native_turn_items: { observed: settled, possible_effects: true, pending: !settled }, provider_error_scope: "terminal_result", usage_ingest: this.usageIngest });
|
|
435
457
|
try { await this.persist({ active_tree: !settled, last_terminal_failure: errorRecord(this.lastTerminalFailure) }); }
|
|
436
458
|
catch (error) { return this.failTerminalObservation(current, result, terminalMetadata, error); }
|
|
437
459
|
return current.reject(this.lastTerminalFailure);
|
|
@@ -491,7 +513,8 @@ export class Runtime {
|
|
|
491
513
|
const reply = new Promise((resolve, reject) => { resolveTurn = resolve; rejectTurn = reject; });
|
|
492
514
|
// Cancellation can reject while admission persistence is still pending.
|
|
493
515
|
void reply.catch(() => {});
|
|
494
|
-
|
|
516
|
+
this.connectionId ??= randomUUID();
|
|
517
|
+
const current = { resolve: resolveTurn, reject: rejectTurn, generation: ++this.turnGeneration, operationId, connectionId: this.connectionId, dispatched: false };
|
|
495
518
|
let resolveDispatchJournal;
|
|
496
519
|
const dispatchJournal = current.dispatchJournal = new Promise(resolve => { resolveDispatchJournal = resolve; });
|
|
497
520
|
this.promptObservation = current;
|
|
@@ -504,6 +527,7 @@ export class Runtime {
|
|
|
504
527
|
this.active = current;
|
|
505
528
|
this.hookRequests = new Map();
|
|
506
529
|
const root = this.init?.conversation_id, prior = this.sessionObservations.get(root) ?? {};
|
|
530
|
+
current.stepFloor = prior.last_step_index ?? -1;
|
|
507
531
|
this.sessionObservations.set(root, { ...prior, step_floor: prior.last_step_index ?? null, stop: null, last_hook: null });
|
|
508
532
|
this.lastProviderEvent = null; this.progressSteps = new Map(); this.progressHooks = new Set(); this.progress = undefined;
|
|
509
533
|
try {
|
|
@@ -511,10 +535,11 @@ export class Runtime {
|
|
|
511
535
|
assertDispatch();
|
|
512
536
|
assertDaemonDispatch(this.paths?.dir);
|
|
513
537
|
if (this.active !== current) throw new DaemonError("operation_cancelled", "prompt reservation was superseded");
|
|
538
|
+
current.outboundPosition = this.journalPosition = (this.journalPosition ?? 0) + 1;
|
|
514
539
|
current.dispatched = true;
|
|
515
540
|
this.child.stdin.write(`${JSON.stringify({ event: "user", message: { content: text } })}\n`, error => {
|
|
516
541
|
if (error) { this.drainingError ??= nativeObservationError(error); current.dispatchObservationError = this.drainingError; current.reject(this.drainingError); finishDispatchJournal(); }
|
|
517
|
-
else void Promise.resolve().then(() => this.journal("native_dispatch", { operation_id: operationId, provider_session_id: this.init?.conversation_id,
|
|
542
|
+
else void Promise.resolve().then(() => this.journal("native_dispatch", { operation_id: operationId, provider_session_id: this.init?.conversation_id, turn_generation: current.generation, connection_id: current.connectionId }, current.outboundPosition)).then(finishDispatchJournal, cause => {
|
|
518
543
|
this.drainingError ??= nativeObservationError(cause); current.dispatchObservationError = this.drainingError;
|
|
519
544
|
current.reject(this.drainingError); finishDispatchJournal();
|
|
520
545
|
});
|
|
@@ -552,6 +577,20 @@ export class Runtime {
|
|
|
552
577
|
});
|
|
553
578
|
}
|
|
554
579
|
return result;
|
|
580
|
+
} catch (error) {
|
|
581
|
+
await dispatchJournal;
|
|
582
|
+
if (error.code === "native_outcome_observation_failed") throw error;
|
|
583
|
+
if (current.dispatchObservationError) throw current.dispatchObservationError;
|
|
584
|
+
const metadata = current.terminalMetadata;
|
|
585
|
+
if (error === this.lastTerminalFailure && metadata && !metadata.request_proof && current.outboundPosition && metadata.stream_terminal_position && this.config.daemonId && current.operationId) {
|
|
586
|
+
metadata.request_proof = { kind: "stream_request", daemon_instance: this.config.daemonId, connection_id: current.connectionId, operation_id: current.operationId,
|
|
587
|
+
turn_generation: current.generation, session_id: this.init.conversation_id, outbound_position: current.outboundPosition, terminal_position: metadata.stream_terminal_position };
|
|
588
|
+
const proven = agyTerminalFailure(current.terminalResult, { ...error.details, ...metadata });
|
|
589
|
+
this.lastTerminalFailure = proven;
|
|
590
|
+
await this.persist({ last_terminal_metadata: metadata, last_terminal_failure: errorRecord(proven) });
|
|
591
|
+
throw proven;
|
|
592
|
+
}
|
|
593
|
+
throw error;
|
|
555
594
|
} finally {
|
|
556
595
|
clearInterval(timer); current.observerReturned = true;
|
|
557
596
|
if (current.dispatchJournalSettled && this.promptObservation === current) this.promptObservation = null;
|
|
@@ -606,7 +645,10 @@ export class Runtime {
|
|
|
606
645
|
async observeHook(event, payload, budget = null) { const conversationId = payload.conversationId ?? this.init?.conversation_id; const child = await this.childForHook(conversationId); budget?.remaining("identity"); const prior = this.sessionObservations.get(conversationId) ?? {}; const execution = Number.isInteger(payload.executionNum) ? payload.executionNum : null; const step = Number.isInteger(payload.stepIdx) ? payload.stepIdx : null; const floor = prior.step_floor; const stale = step !== null && ((Number.isInteger(floor) && step <= floor) || (child && Number.isInteger(prior.last_step_index) && step < prior.last_step_index)); if (stale) { await this.journal("stale_hook", { hook_event: event, conversation_id: conversationId, step_index: step }); return { stale: true }; } const fingerprint = createHash("sha256").update(JSON.stringify({ event, payload })).digest("hex"); this.progressHooks ??= new Set(); const scoped = this.active && (!child || child.activity_generation === this.active.generation); const progressKey = JSON.stringify([conversationId, step, execution, event, payload.toolCall ?? null, payload.fullyIdle ?? null]); if (scoped && step !== null && (["PreToolUse", "PostToolUse"].includes(event) || event === "Stop" && payload.fullyIdle === true) && !this.progressHooks.has(progressKey) && this.progressHooks.size < 1_024) { this.progressHooks.add(progressKey); this.markActivity(); } if (conversationId && !stale) this.sessionObservations.set(conversationId, { ...prior, ...(step !== null ? { last_step_index: Math.max(prior.last_step_index ?? -1, step) } : {}), last_hook: fingerprint, ...(event === "PreToolUse" ? { last_pre_hook: fingerprint } : {}), ...(payload.transcriptPath ? { transcript_path: payload.transcriptPath } : {}), ...(event === "Stop" ? { stop: { fullyIdle: payload.fullyIdle === true, terminationReason: payload.terminationReason ?? null, executionNum: payload.executionNum ?? null } } : {}) }); if (!stale && event === "PreToolUse" && prior.last_pre_hook !== fingerprint) this.childActivity(conversationId); if (child && !stale && event === "Stop" && payload.fullyIdle === true) this.descendants.set(conversationId, { ...this.descendants.get(conversationId), tree_settled: true, settlement_evidence: "child_fully_idle_stop" }); const terminalPending = this.active?.terminalResult === this.lastResult; if (!child && !stale && event === "Stop" && payload.fullyIdle === true && (!this.active || terminalPending) && ["SUCCESS", "ERROR", "CANCELLED"].includes(this.lastResult?.status)) for (const [id, descendant] of this.descendants) if (descendant.status === "unknown") this.descendants.set(id, { ...descendant, status: "settled_by_root", settlement_evidence: "root_fully_idle_stop" }); const parent = child?.parent_provider_session_id ?? null;
|
|
607
646
|
const observed = payload.model ? observedProfile(payload) : {};
|
|
608
647
|
await observeModel({ journal: this.config.journal, harness: "antigravity-cli", sessionId: conversationId, parentSessionId: parent, requested: this.config, observed, source: { channel: "native.hook", scope: "configured", ...(!payload.model ? { non_asserting: true } : {}) }, evidence: payload.model ? "configured" : "unavailable", reason: payload.model ? null : "hook_omits_current_model" });
|
|
609
|
-
const eventId = createHash("sha256").update(JSON.stringify({ daemon: this.config.daemonId, event, conversationId, fully_idle: payload.fullyIdle ?? null, termination_reason: payload.terminationReason ?? null, execution: payload.executionNum ?? null, step: payload.stepIdx ?? null, tool: payload.toolCall?.name ?? null, args: payload.toolCall?.args ?? null })).digest("hex");
|
|
648
|
+
const eventId = createHash("sha256").update(JSON.stringify({ daemon: this.config.daemonId, event, conversationId, fully_idle: payload.fullyIdle ?? null, termination_reason: payload.terminationReason ?? null, execution: payload.executionNum ?? null, step: payload.stepIdx ?? null, tool: payload.toolCall?.name ?? null, args: payload.toolCall?.args ?? null })).digest("hex");
|
|
649
|
+
await this.persist(); await this.journal("hook", { stale, hook_event: event, event_id: eventId, conversation_id: conversationId, turn_generation: this.turnGeneration, execution_num: execution, step_index: step, termination_reason: payload.terminationReason ?? null, fully_idle: payload.fullyIdle ?? null, persistence_timing: this.persistTiming ?? null });
|
|
650
|
+
if (!stale && !child && event === "PreToolUse" && Number.isInteger(step) && this.active) { this.active.nativeHookEvents ??= new Map(); this.active.nativeHookEvents.set(step, `${eventId}:before`); }
|
|
651
|
+
await this.completePendingTurn(); return { event_id: eventId, daemon_id: this.config.daemonId, launch_cwd: this.config.cwd, parent_conversation_id: parent, profile: observed, requested_profile: { provider: this.config.provider, model: this.config.model, reasoning: this.config.reasoning, mode: this.config.mode }, project_root: this.config.projectRoot, dd_flow_bin: this.config.ddFlowBin, dd_flow_home: this.config.ddFlowHome };
|
|
610
652
|
}
|
|
611
653
|
async finalizeProviderForControl() {
|
|
612
654
|
const pending = this.providerFinalization;
|
|
@@ -4,7 +4,7 @@ import path from "node:path";
|
|
|
4
4
|
import { SETUP_STARTUP_MS } from './setup-progress.mjs';
|
|
5
5
|
|
|
6
6
|
import { checkObservedProfile } from "./model-observations.mjs";
|
|
7
|
-
import { classifyProviderError, safeProviderError } from "./provider-overload-policy.mjs";
|
|
7
|
+
import { classifyProviderError, safeProviderError, streamRequestKey } from "./provider-overload-policy.mjs";
|
|
8
8
|
|
|
9
9
|
const DEFAULT_MODEL = "gemini-3.1-pro-high";
|
|
10
10
|
export const AGY_VERSION_TIMEOUT_MS = 30_000;
|
|
@@ -14,14 +14,22 @@ export class AgyError extends Error {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
export function agyTerminalFailure(result, metadata = {}) {
|
|
17
|
-
|
|
17
|
+
let native = typeof result.error === "object" && result.error ? result.error : { message: result.error ?? "Antigravity returned ERROR" };
|
|
18
|
+
if (typeof result.error === "string" && /^AGY_ERROR\s*[: ]/.test(result.error)) {
|
|
19
|
+
try {
|
|
20
|
+
const envelope = JSON.parse(result.error.replace(/^AGY_ERROR\s*:?\s*/, ""));
|
|
21
|
+
const error = envelope.error ?? envelope;
|
|
22
|
+
if (error && typeof error === "object" && !Array.isArray(error)) native = error;
|
|
23
|
+
} catch { /* A malformed envelope remains a native failure, never evidence. */ }
|
|
24
|
+
}
|
|
18
25
|
const category = classifyProviderError(native);
|
|
19
26
|
const legacy = { provider_quota_exhausted: "agy_provider_quota_exhausted", provider_limit_unknown: "agy_provider_limit_unknown", provider_auth_failed: "agy_provider_account", provider_failed: "agy_provider_failed" };
|
|
20
27
|
let code = result.status === "CANCELLED" ? "agy_turn_cancelled" : legacy[category] ?? category;
|
|
21
28
|
// A local generation/usage count is not a native request identity.
|
|
22
29
|
const identity = result.turn_id ?? result.request_id;
|
|
23
30
|
const turn = typeof identity === "string" && identity ? identity : null;
|
|
24
|
-
const
|
|
31
|
+
const stream = streamRequestKey(metadata.request_proof);
|
|
32
|
+
const proofMissing = result.status === "ERROR" && ["provider_capacity_transient", "provider_rate_limited"].includes(category) && !turn && !stream;
|
|
25
33
|
if (proofMissing) code = "provider_overload_evidence_unavailable";
|
|
26
34
|
return new AgyError(code, String(native.message ?? "Antigravity returned ERROR"), code === "provider_capacity_transient" || code === "provider_rate_limited", {
|
|
27
35
|
provider_result: result, ...metadata, provider_error: safeProviderError(native), native_source: "agy.result", terminal_status: result.status === "ERROR" ? "failed" : "cancelled",
|
|
@@ -57,6 +65,12 @@ export function observedProfile(init = {}, requested = {}) {
|
|
|
57
65
|
|
|
58
66
|
export function assertProfile(requested, observed) { return checkObservedProfile(requested, observed, { required: requested.permission_mode ? ["permission_mode"] : [] }); }
|
|
59
67
|
|
|
68
|
+
export function agyModelArgs(model, reasoning) {
|
|
69
|
+
const encoded = /^gemini-.+-(low|medium|high)$/.exec(model)?.[1];
|
|
70
|
+
if (encoded && encoded !== reasoning) throw new AgyError("agy_profile_invalid", "Requested reasoning conflicts with the selected Gemini model");
|
|
71
|
+
return ["--model", model, ...(encoded ? [] : ["--effort", reasoning])];
|
|
72
|
+
}
|
|
73
|
+
|
|
60
74
|
export async function doctor(options = {}) {
|
|
61
75
|
const bin = options.bin ?? process.env.DD_AGY_BIN ?? "agy", temporary = await mkdtemp(path.join(os.tmpdir(), "dd-agy-doctor-"));
|
|
62
76
|
try {
|
|
@@ -521,7 +521,7 @@ export class DroidRuntime {
|
|
|
521
521
|
if (!this.config.ddFlowBin || payload.tool_name !== "Execute") return {};
|
|
522
522
|
// Usage is ingested by inspect/turn completion, never ahead of admission.
|
|
523
523
|
try {
|
|
524
|
-
return await this.flow("event", "handle", { schema_id: "dd-flow/droid-tool-event@1", phase: event === "PreToolUse" ? "before" : "after", event_id: eventId, daemon_id: this.config.daemonId, session: { provider_session_id: id, parent_provider_session_id: parent, directory: this.config.cwd }, tool: payload.tool_name, input: payload.tool_input, profile, transcript_path: native.transcript_path, outcome: { status: payload.tool_response?.is_error ? "error" : "completed" } }, budget.remaining("admission"
|
|
524
|
+
return await this.flow("event", "handle", { schema_id: "dd-flow/droid-tool-event@1", phase: event === "PreToolUse" ? "before" : "after", event_id: eventId, daemon_id: this.config.daemonId, session: { provider_session_id: id, parent_provider_session_id: parent, directory: this.config.cwd }, tool: payload.tool_name, input: payload.tool_input, profile, transcript_path: native.transcript_path, outcome: { status: payload.tool_response?.is_error ? "error" : "completed" } }, budget.remaining("admission"));
|
|
525
525
|
} catch (error) {
|
|
526
526
|
if (event === "PreToolUse" && isProvenShellCorrection(error)) {
|
|
527
527
|
const refusal = errorRecord(error);
|
|
@@ -187,8 +187,53 @@ export async function stopDaemon(options) { const timeoutMs = options.timeoutMs
|
|
|
187
187
|
export class Runtime {
|
|
188
188
|
constructor(paths, state, bridge, initialized) { this.paths = paths; this.state = state; this.bridge = bridge; this.initialized = initialized; this.sessions = new Map((state.sessions ?? []).map((item) => [item.provider_session_id, item])); this.descendants = new Map((state.descendants ?? []).map(child => [child.provider_session_id, child])); for (const session of this.sessions.values()) if (session.parent_provider_session_id && !this.descendants.has(session.provider_session_id)) this.descendants.set(session.provider_session_id, { provider_session_id: session.provider_session_id, parent_provider_session_id: session.parent_provider_session_id, status: "unknown", source: "legacy.daemon_state", outcome_error: { code: "native_child_outcome_unavailable", message: "Retained Grok child has no durable outcome receipt" } }); this.toolUsage = new Map(); this.loadedSessionId = null; this.active = null; this.server = null; this.ready = false; this.persisting = Promise.resolve(); }
|
|
189
189
|
persist(patch = {}) { Object.assign(this.state, patch, { pid: process.pid, updated_at: new Date().toISOString(), sessions: [...this.sessions.values()], descendants: [...this.descendants.values()] }); const snapshot = structuredClone(this.state); this.persisting = this.persisting.catch(() => {}).then(() => writeState(this.paths.state, snapshot)); return this.persisting; }
|
|
190
|
-
|
|
191
|
-
|
|
190
|
+
async reduceTerminal(outcome) {
|
|
191
|
+
const session = this.sessions.get(outcome.session_id), turn = session?.native_prompt_operation;
|
|
192
|
+
if (outcome.method !== "session/prompt" || !turn || typeof outcome.connection_id !== "string" || !outcome.connection_id
|
|
193
|
+
|| !Number.isSafeInteger(outcome.request_id) || outcome.request_id < 1 || !Number.isSafeInteger(outcome.native_sequence) || outcome.native_sequence < 1 || turn.daemon_id !== this.state.daemon_id
|
|
194
|
+
|| turn.connection_id !== outcome.connection_id || turn.operation_id !== outcome.operation_id || turn.generation !== outcome.generation
|
|
195
|
+
|| turn.native_request_id !== outcome.request_id) return;
|
|
196
|
+
const message = outcome.terminal_response;
|
|
197
|
+
const kind = message?.error ? "failed" : message?.result?.stopReason;
|
|
198
|
+
if (!["failed", "end_turn", "cancelled"].includes(kind)) return;
|
|
199
|
+
const receipt = { kind, ...turn, request_id: outcome.request_id, native_sequence: outcome.native_sequence };
|
|
200
|
+
if (session.native_root_receipt && (session.native_root_receipt.kind !== kind || session.native_root_receipt.request_id !== outcome.request_id)) {
|
|
201
|
+
this.state.observation_error = { code: "native_terminal_outcome_conflict", message: "Conflicting native terminal outcomes for the same prompt" };
|
|
202
|
+
await this.persist();
|
|
203
|
+
throw new DaemonError(this.state.observation_error.code, this.state.observation_error.message);
|
|
204
|
+
}
|
|
205
|
+
if (session.native_root_receipt) return;
|
|
206
|
+
if (session.native_activity && session.native_activity.connection_id === outcome.connection_id && session.native_activity.sequence > outcome.native_sequence) {
|
|
207
|
+
return await this.invalidateTerminal(session);
|
|
208
|
+
}
|
|
209
|
+
session.native_root_receipt = receipt;
|
|
210
|
+
await this.persist();
|
|
211
|
+
}
|
|
212
|
+
async invalidateTerminal(session) {
|
|
213
|
+
session.native_root_receipt = null;
|
|
214
|
+
this.state.observation_error = { code: "native_activity_after_terminal", message: "Productive root activity followed its native terminal outcome" };
|
|
215
|
+
await this.persist();
|
|
216
|
+
}
|
|
217
|
+
async observeRootActivity(message, observation) {
|
|
218
|
+
const session = this.sessions.get(message?.params?.sessionId), turn = session?.native_prompt_operation;
|
|
219
|
+
if (!observation?.productive || !turn || session.parent_provider_session_id || turn.connection_id !== observation.connection_id) return;
|
|
220
|
+
if (session.native_activity?.connection_id !== observation.connection_id || session.native_activity.sequence < observation.sequence) session.native_activity = { connection_id: observation.connection_id, sequence: observation.sequence };
|
|
221
|
+
const receipt = session.native_root_receipt;
|
|
222
|
+
if (receipt?.connection_id === observation.connection_id && observation.sequence > receipt.native_sequence) await this.invalidateTerminal(session);
|
|
223
|
+
}
|
|
224
|
+
async drainTerminal() { await this.bridge.terminalOutcomes; await this.persisting; if (this.bridge.terminalProjectionError) throw this.bridge.terminalProjectionError; }
|
|
225
|
+
settledRoot(id) { return !this.active && !this.rootRunning({ provider_session_id: id }) && this.topology(id).every(child => !this.rootRunning({ provider_session_id: child.provider_session_id }) && !child.outcome_error && !child.activity_status); }
|
|
226
|
+
async restoreTerminals() {
|
|
227
|
+
if (!this.state.config?.journal) return;
|
|
228
|
+
let contents;
|
|
229
|
+
try { contents = await readFile(this.state.config.journal, "utf8"); } catch (error) { if (error.code === "ENOENT") return; throw error; }
|
|
230
|
+
for (const line of contents.split("\n").filter(Boolean)) {
|
|
231
|
+
const event = JSON.parse(line);
|
|
232
|
+
if (["native_terminal_outcome", "native_terminal_after_hook_failure"].includes(event.kind)) await this.reduceTerminal(event.payload);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
options(params) { const sessionId = params.sessionId; const session = sessionId ? this.sessions.get(sessionId) : null; const root = session?.root_provider_session_id ?? sessionId; return { ...this.state.config, ...params, cwd: params.cwd ?? session?.cwd ?? this.state.config.cwd, initialized: this.initialized, daemonId: this.state.daemon_id, rootProviderSessionId: root, toolUsage: this.toolUsage, allowBackground: true, rootSettled: id => this.settledRoot(id), liveSession: params.liveSession ?? Boolean(sessionId && sessionId === this.loadedSessionId) }; }
|
|
236
|
+
track(result, before = null) { if (!result?.provider_session_id) return; const session = this.sessions.get(result.provider_session_id); const root = result.parent_provider_session_id ? (this.sessions.get(result.parent_provider_session_id)?.root_provider_session_id ?? result.parent_provider_session_id) : (session?.root_provider_session_id ?? result.provider_session_id); this.sessions.set(result.provider_session_id, { ...session, native_root_receipt: session?.native_root_receipt ?? null, provider_session_id: result.provider_session_id, adapter_session_id: result.adapter_session_id ?? result.provider_session_id, parent_provider_session_id: result.parent_provider_session_id ?? session?.parent_provider_session_id ?? null, root_provider_session_id: root, cwd: result.cwd ?? result.target?.newCwd ?? result.info?.cwd ?? session?.cwd ?? null }); for (const child of this.running(result)) this.recordDescendant({ ...child, stale_activity: Boolean(before && before.get(child.sessionId ?? child.subagentId ?? child.provider_session_id) !== this.descendants.get(child.sessionId ?? child.subagentId ?? child.provider_session_id)) }, root, "x.ai/subagent/list_running", "running"); for (const child of result.descendants ?? []) this.recordDescendant(child, root, child.source ?? "x.ai/subagent/event", child.status ?? "unknown"); }
|
|
192
237
|
recordDescendant(value, root, source, fallback) {
|
|
193
238
|
const id = value?.provider_session_id ?? value?.session_id ?? value?.sessionId ?? value?.childSessionId ?? value?.subagentId ?? value?.child_session_id ?? value?.subagent_id;
|
|
194
239
|
const parent = value?.parent_provider_session_id ?? value?.parentSessionId ?? value?.parent_session_id ?? root;
|
|
@@ -302,13 +347,14 @@ export class Runtime {
|
|
|
302
347
|
}
|
|
303
348
|
return modelFacts;
|
|
304
349
|
}
|
|
305
|
-
topology(root) { if (this.state.observation_error) { const error = this.state.observation_error; throw new DaemonError(error.code, error.message, false, error.details); } return [...this.descendants.values()].filter((child) => child.parent_provider_session_id === root); }
|
|
350
|
+
topology(root) { if (this.state.observation_error) { const error = this.state.observation_error; throw new DaemonError(error.code, error.message, false, error.details); } return [...this.descendants.values()].filter((child) => child.parent_provider_session_id === root || this.sessions.get(child.provider_session_id)?.root_provider_session_id === root); }
|
|
306
351
|
observeToolCall(message) { this.toolObservations ??= new ToolObservations(); this.toolObservations.add(acpToolObservation(message, { harness: "grok-acp" })); const sessionId = message?.params?.sessionId; if (sessionId) this.toolUsage.set(sessionId, this.toolObservations.summary(sessionId)); }
|
|
307
352
|
running(result) { return result?.evidence?.subagents?.subagents ?? result?.subagents?.subagents ?? []; }
|
|
308
353
|
treeRunning(result) {
|
|
309
354
|
const evidence = result?.evidence ?? result;
|
|
310
355
|
const children = evidence?.subagents?.subagents;
|
|
311
|
-
return this.rootRunning({ ...result, ...evidence }) || !Array.isArray(children) || children.length > 0 || Boolean(evidence?.subagents?.observation_error)
|
|
356
|
+
return this.rootRunning({ ...result, ...evidence }) || !Array.isArray(children) || children.length > 0 || Boolean(evidence?.subagents?.observation_error)
|
|
357
|
+
|| this.topology(result?.provider_session_id).some(child => this.rootRunning({ provider_session_id: child.provider_session_id }) || child.outcome_error || child.activity_status);
|
|
312
358
|
}
|
|
313
359
|
rootRunning(result) {
|
|
314
360
|
const status = String(result?.info?.status ?? result?.info?.state ?? result?.info?.data?.status ?? "unknown").toLowerCase();
|
|
@@ -316,7 +362,7 @@ export class Runtime {
|
|
|
316
362
|
// Grok may retire a completed child Session before session/info can be
|
|
317
363
|
// inspected. A retained TaskOutput for that exact attempt is native
|
|
318
364
|
// terminal proof; missing session/info alone is not.
|
|
319
|
-
if (status === "unknown" &&
|
|
365
|
+
if (status === "unknown" && ["x.ai/tool/TaskOutput", "x.ai/subagent/event"].includes(child?.source) && child.attempt_id && !child.outcome_error && !child.activity_status
|
|
320
366
|
&& ["completed", "failed", "cancelled"].includes(child.status)) return false;
|
|
321
367
|
// Grok 1.0.17 omits status from session/info. Only a durable native
|
|
322
368
|
// creation/end_turn receipt can then establish an idle root. Dispatch
|
|
@@ -326,6 +372,7 @@ export class Runtime {
|
|
|
326
372
|
return !["completed", "failed", "cancelled", "stopped", "idle"].includes(status);
|
|
327
373
|
}
|
|
328
374
|
async refreshTree(cancel = false) {
|
|
375
|
+
await this.drainTerminal();
|
|
329
376
|
const running = [];
|
|
330
377
|
for (const session of [...this.sessions.values()].filter(item => !item.parent_provider_session_id)) {
|
|
331
378
|
const id = session.provider_session_id;
|
|
@@ -357,15 +404,21 @@ export class Runtime {
|
|
|
357
404
|
const timer = setInterval(() => { void this.observeHookFailures(current).catch(error => current.reject(error)); }, 100);
|
|
358
405
|
// Only a new native turn invalidates its own root proof. Creation, fork
|
|
359
406
|
// and local archive export must not erase an unrelated Session's receipt.
|
|
360
|
-
if (name === "session.prompt" && this.sessions.has(sessionId))
|
|
407
|
+
if (name === "session.prompt" && this.sessions.has(sessionId)) {
|
|
408
|
+
const session = this.sessions.get(sessionId);
|
|
409
|
+
session.native_root_receipt = null;
|
|
410
|
+
this.bridge.dispatchGeneration = current.generation;
|
|
411
|
+
session.native_prompt_operation = { daemon_id: this.state.daemon_id, connection_id: this.bridge.connectionId, operation_id: operationId, generation: current.generation };
|
|
412
|
+
}
|
|
361
413
|
try {
|
|
362
414
|
await this.persist({ active_tree: true, active_operation: name });
|
|
363
415
|
assertDaemonDispatch(this.paths?.dir);
|
|
364
416
|
const before = new Map(this.descendants);
|
|
365
417
|
const result = await Promise.race([task(), hookFailed]);
|
|
418
|
+
await this.drainTerminal();
|
|
366
419
|
this.track(result, before);
|
|
367
420
|
const session = this.sessions.get(result.provider_session_id);
|
|
368
|
-
if (session &&
|
|
421
|
+
if (session && ["session.create", "session.fork"].includes(name)) session.native_root_receipt = { kind: "created", daemon_id: this.state.daemon_id };
|
|
369
422
|
// Archive requires an idle tree before entering this exclusive section
|
|
370
423
|
// and performs no native work. Its file receipt is not tree evidence.
|
|
371
424
|
const running = name === "session.archive" ? false : this.treeRunning(result);
|
|
@@ -375,11 +428,7 @@ export class Runtime {
|
|
|
375
428
|
// A matched JSON-RPC error completes this native prompt request. It is
|
|
376
429
|
// root-turn proof, not proof that any descendants have stopped. A lost
|
|
377
430
|
// response, bridge error, or failure before dispatch earns no receipt.
|
|
378
|
-
|
|
379
|
-
if (name === "session.prompt" && response?.method === "session/prompt" && response.session_id === sessionId) {
|
|
380
|
-
const session = this.sessions.get(sessionId);
|
|
381
|
-
if (session) session.native_root_receipt = { kind: "failed", daemon_id: this.state.daemon_id, request_id: response.request_id };
|
|
382
|
-
}
|
|
431
|
+
try { await this.drainTerminal(); } catch (storage) { error.cleanup_error ??= errorRecord(storage); }
|
|
383
432
|
try { await hookBudget(1_000, null).run(() => this.persist({ active_tree: true, active_operation: null }), "failure_persistence"); }
|
|
384
433
|
catch (storage) { error.cleanup_error = errorRecord(storage); }
|
|
385
434
|
throw error;
|
|
@@ -441,10 +490,11 @@ export class Runtime {
|
|
|
441
490
|
|
|
442
491
|
export async function serveDaemon(stateDir) {
|
|
443
492
|
const paths = locations(stateDir); const state = await readState(paths.dir); if (!state || state.schema_id !== STATE_SCHEMA) throw new DaemonError("daemon_state_missing", "daemon state is missing or incompatible"); let runtime;
|
|
444
|
-
const bridge = new AcpBridge({ ...state.config, initializeTimeoutMs: 60_000, commandArgs: ["agent", "--no-leader", "--always-approve", "--model", state.config.model, "--reasoning-effort", state.config.reasoning, "stdio"], env: isolatedEnv(state.config), clientInfo: { name: "dd-grok", version: "0.1.0" }, permission: "allow", onNotification: async (message) => {
|
|
493
|
+
const bridge = new AcpBridge({ ...state.config, initializeTimeoutMs: 60_000, commandArgs: ["agent", "--no-leader", "--always-approve", "--model", state.config.model, "--reasoning-effort", state.config.reasoning, "stdio"], env: isolatedEnv(state.config), clientInfo: { name: "dd-grok", version: "0.1.0" }, permission: "allow", onNotification: async (message, observation) => {
|
|
445
494
|
const sessionId = message?.params?.sessionId;
|
|
446
495
|
if (!runtime || !sessionId) return;
|
|
447
496
|
try {
|
|
497
|
+
await runtime.observeRootActivity(message, observation);
|
|
448
498
|
runtime.observeToolCall(message);
|
|
449
499
|
for (const fact of runtime.observeSubagentEvent(message)) {
|
|
450
500
|
const source = { channel: "x.ai/subagent_spawned", scope: "configured", attempt_id: fact.attemptId, parent_prompt_id: fact.parentPromptId,
|
|
@@ -463,6 +513,13 @@ export async function serveDaemon(stateDir) {
|
|
|
463
513
|
const rootProviderSessionId = session?.root_provider_session_id ?? sessionId;
|
|
464
514
|
await observeAcpToolCall(state.config, { daemonId: state.daemon_id, rootProviderSessionId, parentProviderSessionId: session?.parent_provider_session_id ?? null, observedProfile: bridge.modelProfiles?.get(sessionId) ?? {} }, message);
|
|
465
515
|
} }); const initialized = await startManagedBridge({ config: state.config, state, bridge, kind: "grok-provider", publish: snapshot => writeState(paths.state, snapshot), log: paths.log }); runtime = new Runtime(paths, state, bridge, initialized);
|
|
516
|
+
bridge.configure({ onTerminalOutcome: outcome => runtime.reduceTerminal(outcome), onPromptDispatch: async dispatch => {
|
|
517
|
+
const turn = runtime.sessions.get(dispatch.session_id)?.native_prompt_operation;
|
|
518
|
+
if (!turn || turn.operation_id !== dispatch.operation_id || turn.connection_id !== dispatch.connection_id || turn.generation !== dispatch.generation) throw new DaemonError("native_dispatch_binding_mismatch", "Native prompt dispatch differs from retained operation");
|
|
519
|
+
turn.native_request_id = dispatch.native_request_id;
|
|
520
|
+
await runtime.persist();
|
|
521
|
+
} });
|
|
522
|
+
await runtime.restoreTerminals();
|
|
466
523
|
for (const fact of await readModelObservations(state.config.journal)) {
|
|
467
524
|
if (fact.harness === "grok-acp" && fact.source?.channel === "x.ai/subagent_spawned" && runtime.sessions.has(fact.session_id)
|
|
468
525
|
&& (runtime.descendants.get(fact.session_id)?.attempt_id ?? null) === (fact.source.attempt_id ?? null) && fact.observed?.model) {
|
|
@@ -102,7 +102,7 @@ async function identity(bridge, options) {
|
|
|
102
102
|
return sessionId;
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
-
async function cancelTree(bridge, sessionId) {
|
|
105
|
+
async function cancelTree(bridge, sessionId, options = {}) {
|
|
106
106
|
const before = await subagents(bridge, sessionId).catch(error => ({ observation_error: { code: error.code, message: error.message } })); const cancellations = [];
|
|
107
107
|
for (const child of before.subagents ?? []) {
|
|
108
108
|
const childId = child.sessionId ?? child.subagentId;
|
|
@@ -119,7 +119,10 @@ async function cancelTree(bridge, sessionId) {
|
|
|
119
119
|
if (!(after.subagents ?? []).length) {
|
|
120
120
|
const root = await sessionInfo(bridge, sessionId).catch(error => ({ observation_error: { code: error.code, message: error.message } }));
|
|
121
121
|
const status = root?.status ?? root?.state ?? root?.data?.status ?? null;
|
|
122
|
-
|
|
122
|
+
await bridge.terminalOutcomes;
|
|
123
|
+
const retained = options.rootSettled?.(sessionId) === true;
|
|
124
|
+
if (status === null && !retained) continue;
|
|
125
|
+
return { before, after, cancellations, root_status: status, settled: options.rootSettled ? retained : ["completed", "failed", "cancelled", "stopped", "idle"].includes(String(status).toLowerCase()) };
|
|
123
126
|
}
|
|
124
127
|
}
|
|
125
128
|
const root = await sessionInfo(bridge, sessionId).catch(() => null);
|
|
@@ -163,7 +166,7 @@ export async function promptSessionWithBridge(bridge, options) {
|
|
|
163
166
|
export async function inspectSession(options) { requireJournal(options); return await withBridge(options, async (bridge, initialized) => await inspectSessionWithBridge(bridge, { ...options, initialized })); }
|
|
164
167
|
export async function inspectSessionWithBridge(bridge, options) { requireJournal(options); const sessionId = await identity(bridge, options); const evidence = await inspect(bridge, sessionId, options); evidence.tool_calls = sessionToolCalls(options, bridge, sessionId); const usage_ingest_error = await forwardUsageBestEffort(options, sessionId, evidence.usage, evidence.tool_calls); if (usage_ingest_error) evidence.usage_ingest_error = usage_ingest_error; return receipt(options, { harness: "grok-acp", provider_session_id: sessionId, adapter_session_id: sessionId, ...evidence }); }
|
|
165
168
|
export async function cancelSession(options) { requireJournal(options); return await withBridge(options, async (bridge) => await cancelSessionWithBridge(bridge, options)); }
|
|
166
|
-
export async function cancelSessionWithBridge(bridge, options) { requireJournal(options); const sessionId = await identity(bridge, options); return receipt(options, { harness: "grok-acp", provider_session_id: sessionId, adapter_session_id: sessionId, ...await cancelTree(bridge, sessionId) }); }
|
|
169
|
+
export async function cancelSessionWithBridge(bridge, options) { requireJournal(options); const sessionId = await identity(bridge, options); return receipt(options, { harness: "grok-acp", provider_session_id: sessionId, adapter_session_id: sessionId, ...await cancelTree(bridge, sessionId, options) }); }
|
|
167
170
|
export async function forkSession(options) { requireJournal(options); return await withBridge(options, async (bridge) => await forkSessionWithBridge(bridge, options)); }
|
|
168
171
|
export async function forkSessionWithBridge(bridge, options) {
|
|
169
172
|
requireJournal(options); const sessionId = await identity(bridge, options); const target = options.target;
|
|
@@ -3,7 +3,7 @@ import { ToolObservations, acpToolObservation, zcodePhysicalSession } from "./to
|
|
|
3
3
|
import { hookBudget, invokeNativeHook } from "./native-hook-command.mjs";
|
|
4
4
|
import { parseLifecycleCommand, continuationCommand, commandOption } from "./lifecycle-command.mjs";
|
|
5
5
|
import { spawn } from "node:child_process";
|
|
6
|
-
import { createHash } from "node:crypto";
|
|
6
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
7
7
|
import { createReadStream } from 'node:fs';
|
|
8
8
|
import { publishSetup, SETUP_STARTUP_MS } from './setup-progress.mjs';
|
|
9
9
|
import { appendFile, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
@@ -141,6 +141,10 @@ export class AcpBridge {
|
|
|
141
141
|
this.expiredRequests = new Map();
|
|
142
142
|
this.sessionActivity = new Map();
|
|
143
143
|
this.notifications = Promise.resolve();
|
|
144
|
+
this.terminalOutcomes = Promise.resolve();
|
|
145
|
+
this.terminalRequests = new Map();
|
|
146
|
+
this.connectionId = randomUUID();
|
|
147
|
+
this.receivedSequence = 0;
|
|
144
148
|
this.lifecycleNotifications = new Set();
|
|
145
149
|
this.notificationError = null;
|
|
146
150
|
this.bridgeStderr = "";
|
|
@@ -202,16 +206,27 @@ export class AcpBridge {
|
|
|
202
206
|
receive(line) {
|
|
203
207
|
let message;
|
|
204
208
|
try { message = JSON.parse(line); } catch { this.journal.write("malformed", { line }); return; }
|
|
209
|
+
const observation = { connection_id: this.connectionId, sequence: ++this.receivedSequence, productive: isProductiveSessionNotification(message) };
|
|
205
210
|
const request = message.id !== undefined ? this.pending.get(message.id) : null;
|
|
206
211
|
const expired = message.id !== undefined ? this.expiredRequests.get(message.id) : null;
|
|
207
212
|
const isResponse = message.id !== undefined && !message.method;
|
|
208
213
|
const rejected = isResponse && this.rejectedHookRequests?.get(message.id);
|
|
209
|
-
if (rejected) {
|
|
210
|
-
this.
|
|
211
|
-
const terminal = { request_id: message.id, method:
|
|
214
|
+
if (rejected || isResponse && (request?.method === "session/prompt" || this.terminalRequests.has(message.id))) {
|
|
215
|
+
const owner = rejected ?? request ?? this.terminalRequests.get(message.id);
|
|
216
|
+
const terminal = { request_id: message.id, method: owner.method, session_id: owner.sessionId, terminal_response: message,
|
|
217
|
+
connection_id: this.connectionId, operation_id: owner.operationId ?? null, generation: owner.generation ?? null, native_sequence: observation.sequence };
|
|
218
|
+
this.terminalOutcomes = this.terminalOutcomes.then(async () => {
|
|
219
|
+
await this.journal.write(rejected ? "native_terminal_after_hook_failure" : "native_terminal_outcome", terminal);
|
|
220
|
+
await this.journal.flush();
|
|
221
|
+
await this.options.onTerminalOutcome?.(terminal);
|
|
222
|
+
this.terminalRequests.set(message.id, owner);
|
|
223
|
+
while (this.terminalRequests.size > 128) this.terminalRequests.delete(this.terminalRequests.keys().next().value);
|
|
224
|
+
if (rejected) this.rejectedHookRequests.delete(message.id);
|
|
225
|
+
});
|
|
226
|
+
void this.terminalOutcomes.catch(storage => { this.terminalProjectionError ??= storage; if (rejected) rejected.error.cleanup_error ??= errorRecord(storage); });
|
|
227
|
+
if (rejected) {
|
|
212
228
|
rejected.error.details = { ...rejected.error.details, native_terminal_after_hook_failure: terminal };
|
|
213
|
-
|
|
214
|
-
catch (storage) { rejected.error.cleanup_error ??= errorRecord(storage); }
|
|
229
|
+
}
|
|
215
230
|
}
|
|
216
231
|
if (!isResponse || request) this.journal.writeInbound(message, request?.method);
|
|
217
232
|
if (typeof message.params?.sessionId === "string" && isProductiveSessionNotification(message)) this.observeActivity(message.params.sessionId, message.params.update);
|
|
@@ -305,7 +320,7 @@ export class AcpBridge {
|
|
|
305
320
|
return;
|
|
306
321
|
}
|
|
307
322
|
if (message.method) this.notifications = this.notifications
|
|
308
|
-
.then(async () => { await this.observeModelUpdate(message); await this.options.onNotification?.(message); })
|
|
323
|
+
.then(async () => { await this.observeModelUpdate(message); await this.options.onNotification?.(message, observation); })
|
|
309
324
|
.catch(async (error) => {
|
|
310
325
|
const failure = { code: error.code ?? "notification_observation_failed", message: error.message, ...(error.details ? { details: error.details } : {}), session_id: message.params?.sessionId ?? null, tool_call_id: message.params?.update?.toolCallId ?? null, event_time: now(), delivery_stage: "acp_evidence", completeness: "incomplete" };
|
|
311
326
|
try { await this.journal.write("notification_failure", failure); }
|
|
@@ -405,6 +420,7 @@ export class AcpBridge {
|
|
|
405
420
|
}
|
|
406
421
|
|
|
407
422
|
request(method, params = {}, timeoutMs = 30_000, { idleSessionId = null, idleTimeoutMs = null, inactivityMode = "reject" } = {}) {
|
|
423
|
+
if (method === "session/prompt" && (this.rejectedHookRequests?.size ?? 0) + [...this.pending.values()].filter(request => request.method === "session/prompt").length >= 32) return Promise.reject(Object.assign(new Error("Unreconciled native terminal evidence limit reached"), { code: "native_terminal_evidence_pending" }));
|
|
408
424
|
if (timeoutMs !== null) validateObservationDuration(timeoutMs);
|
|
409
425
|
if (idleTimeoutMs !== null) validateObservationDuration(idleTimeoutMs);
|
|
410
426
|
if ((this.lifecycleFailure || this.notificationError?.code === "profile_integrity_violation") && ["session/new", "session/prompt", "session/fork"].includes(method)) return Promise.reject(this.lifecycleFailure ?? this.notificationError);
|
|
@@ -467,9 +483,19 @@ export class AcpBridge {
|
|
|
467
483
|
void this.journal.write("inactivity_observation_failure", { method, request_id: id, session_id: idleSessionId, error: { code: error?.code ?? "inactivity_observation_failed", message: error?.message ?? String(error) } }).catch(() => {});
|
|
468
484
|
});
|
|
469
485
|
}, Math.max(10, Math.min(1_000, Math.floor(validIdleTimeout / 4)))) : null;
|
|
470
|
-
this.pending.set(id, { id, resolve, reject, timer, idleTimer, method, sessionId: params.sessionId ?? null, dispatchedAt: Date.now(), publish: operationProgressReporter() });
|
|
471
|
-
|
|
472
|
-
|
|
486
|
+
this.pending.set(id, { id, resolve, reject, timer, idleTimer, method, sessionId: params.sessionId ?? null, operationId: this.dispatchOperationId ?? process.env.DD_EVAL_OPERATION_ID ?? null, generation: this.dispatchGeneration ?? null, dispatchedAt: Date.now(), publish: operationProgressReporter() });
|
|
487
|
+
if (method === "session/prompt") {
|
|
488
|
+
void (async () => {
|
|
489
|
+
const dispatch = { operation_id: this.pending.get(id)?.operationId, session_id: params.sessionId, native_request_id: id,
|
|
490
|
+
connection_id: this.connectionId, generation: this.pending.get(id)?.generation };
|
|
491
|
+
await this.journal.write("native_dispatch_prepared", dispatch);
|
|
492
|
+
await this.journal.flush();
|
|
493
|
+
await this.options.onPromptDispatch?.(dispatch);
|
|
494
|
+
if (!this.pending.has(id) || this.pending.get(id).timedOut || this.lifecycleFailure) return;
|
|
495
|
+
this.send({ jsonrpc: "2.0", id, method, params });
|
|
496
|
+
await this.journal.write("native_dispatch", dispatch);
|
|
497
|
+
})().catch(finish);
|
|
498
|
+
} else this.send({ jsonrpc: "2.0", id, method, params });
|
|
473
499
|
});
|
|
474
500
|
}
|
|
475
501
|
|
|
@@ -488,14 +514,14 @@ export class AcpBridge {
|
|
|
488
514
|
rejectAll(error) {
|
|
489
515
|
for (const [id, pending] of this.pending) {
|
|
490
516
|
if (this.notificationError === error && pending.method === "session/prompt") {
|
|
491
|
-
this.rejectedHookRequests ??= new Map(); this.rejectedHookRequests.set(id, { method: pending.method, sessionId: pending.sessionId, error });
|
|
492
|
-
if (this.rejectedHookRequests.size > 32) this.rejectedHookRequests.delete(this.rejectedHookRequests.keys().next().value);
|
|
517
|
+
this.rejectedHookRequests ??= new Map(); this.rejectedHookRequests.set(id, { method: pending.method, sessionId: pending.sessionId, operationId: pending.operationId, generation: pending.generation, error });
|
|
493
518
|
}
|
|
494
519
|
clearTimeout(pending.timer); if (pending.idleTimer) clearInterval(pending.idleTimer); pending.reject(error);
|
|
495
520
|
}
|
|
496
521
|
this.pending.clear();
|
|
497
522
|
}
|
|
498
523
|
async flush() {
|
|
524
|
+
await this.terminalOutcomes;
|
|
499
525
|
if (this.notificationError) throw this.notificationError;
|
|
500
526
|
try { await Promise.all([...this.lifecycleNotifications]); await this.notifications; await this.journal.flush(); }
|
|
501
527
|
catch (error) { throw this.notificationError ?? error; }
|
|
@@ -507,6 +533,7 @@ export class AcpBridge {
|
|
|
507
533
|
// Draining them must not relabel a successful physical shutdown as failed.
|
|
508
534
|
return await closeBridgeProcess(this, async () => {
|
|
509
535
|
await Promise.all([this.nativeDrain, this.diagnostics]);
|
|
536
|
+
await this.terminalOutcomes;
|
|
510
537
|
await Promise.allSettled([...this.lifecycleNotifications, this.notifications]);
|
|
511
538
|
if (this.notificationError) await hookBudget(1_000, null).run(() => this.journal.flush(), "failure_drain");
|
|
512
539
|
else await this.journal.flush();
|
|
@@ -964,6 +991,7 @@ export function zcodeLifecycleQualification(sha256, bridgeCommit, harnessContrac
|
|
|
964
991
|
native_nested_children: false, reason: qualified ? null : "native_event_contract_unqualified" };
|
|
965
992
|
}
|
|
966
993
|
|
|
994
|
+
let doctorDigestScanOrdinal = 0;
|
|
967
995
|
export async function doctor(options = {}) {
|
|
968
996
|
const zcodeAcp = await commandOutput(options.bin, ["--version"]);
|
|
969
997
|
const zcodeAcpCommit = await commandOutput(options.bin, ["--dd-harness-commit"]);
|
|
@@ -971,11 +999,13 @@ export async function doctor(options = {}) {
|
|
|
971
999
|
const zcodePath = options.zcodePath ?? process.env.ZCODE_BIN ?? process.env.ZCODE_PATH ?? "/Applications/ZCode.app/Contents/Resources/glm/zcode.cjs";
|
|
972
1000
|
const zcode = await commandOutput(zcodePath, ["--version"]);
|
|
973
1001
|
const hash = createHash('sha256'); let bytes = 0;
|
|
1002
|
+
const scan = `zcode-digest:${zcodePath}:scan-${++doctorDigestScanOrdinal}`;
|
|
1003
|
+
publishSetup('probe_started', scan);
|
|
974
1004
|
for await (const chunk of createReadStream(zcodePath)) {
|
|
975
1005
|
hash.update(chunk); bytes += chunk.length;
|
|
976
|
-
if (bytes % (4 * 1024 * 1024) < chunk.length) publishSetup('identity_digest_progress', bytes);
|
|
1006
|
+
if (bytes % (4 * 1024 * 1024) < chunk.length) publishSetup('identity_digest_progress', `${scan}:${bytes}`);
|
|
977
1007
|
}
|
|
978
|
-
const sha256 = hash.digest('hex'); publishSetup('identity_digest_completed');
|
|
1008
|
+
const sha256 = hash.digest('hex'); publishSetup('identity_digest_completed', scan);
|
|
979
1009
|
const qualification = zcodeLifecycleQualification(sha256, zcodeAcpCommit, ddHarness);
|
|
980
1010
|
return { compatible: qualification.status === "qualified", contract_supported: supportedZcodeHarnessContract(ddHarness), lifecycle_qualification: qualification, observed_runtime: { zcode, zcode_acp: zcodeAcp, zcode_acp_commit: zcodeAcpCommit, dd_harness_contract: ddHarness } };
|
|
981
1011
|
}
|