@deksden-com/dd-flow-cli 0.9.0-beta.145 → 0.9.0-beta.149
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 +71 -0
- package/dist/build-info.json +7 -7
- package/dist/cli/command-inputs.js +6 -2
- package/dist/cli/command-metadata.js +18 -2
- package/dist/cli/diagnostic-invocation.js +46 -0
- package/dist/cli/diagnostic.js +120 -0
- package/dist/cli/help.js +35 -0
- package/dist/cli/memory-flow-admission.js +4 -2
- package/dist/cli/memory-init.js +5 -2
- package/dist/cli/run-cli.js +16 -15
- package/dist/cli.js +6 -0
- package/dist/domain/diagnostic-aspects.js +103 -0
- package/dist/domain/diagnostic-flows.js +8 -0
- package/dist/harness-runtime/lib/agy-native-inspection.mjs +216 -0
- package/dist/harness-runtime/lib/agy-native_pb.js +117 -0
- package/dist/harness-runtime/lib/daemon-operations.d.mts +1 -0
- package/dist/harness-runtime/lib/daemon-operations.mjs +84 -10
- package/dist/harness-runtime/lib/dd-agy-daemon.mjs +306 -34
- package/dist/harness-runtime/lib/dd-codex-daemon.mjs +27 -13
- package/dist/harness-runtime/lib/dd-codex.mjs +4 -0
- package/dist/harness-runtime/lib/dd-droid-daemon.mjs +4 -3
- package/dist/harness-runtime/lib/dd-droid.mjs +70 -28
- package/dist/harness-runtime/lib/dd-grok-daemon.mjs +3 -3
- package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +66 -20
- package/dist/harness-runtime/lib/dd-opencode.mjs +30 -3
- package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +7 -6
- package/dist/harness-runtime/lib/driver-recovery.mjs +9 -8
- package/dist/harness-runtime/lib/managed-daemon.d.mts +1 -1
- package/dist/harness-runtime/lib/managed-daemon.mjs +11 -3
- package/dist/harness-runtime/lib/native-children.d.mts +8 -1
- package/dist/harness-runtime/lib/native-children.mjs +114 -8
- package/dist/harness-runtime/lib/native-request-proof.mjs +1 -1
- package/dist/harness-runtime/lib/observation-clock.d.mts +2 -2
- package/dist/harness-runtime/lib/observation-clock.mjs +7 -4
- package/dist/harness-runtime/lib/provider-overload-policy.d.mts +1 -1
- package/dist/harness-runtime/lib/provider-overload-policy.mjs +1 -1
- package/dist/harness-runtime/lib/tool-observations.mjs +1 -1
- package/dist/schemas/diagnostic-stage-result.schema.json +354 -0
- package/dist/schemas/diagnostic-work-result.schema.json +296 -0
- package/dist/schemas/execution-bundle.schema.json +7 -1
- package/dist/schemas/project-flow-pack-manifest.schema.json +1 -1
- package/dist/services/check-environment.js +21 -0
- package/dist/services/check-runner-evidence.js +267 -15
- package/dist/services/code-checks.js +23 -15
- package/dist/services/controller-execution.js +10 -4
- package/dist/services/controller-fanout.js +13 -6
- package/dist/services/controller-memory-worker.js +3 -2
- package/dist/services/dashboard.js +2 -2
- package/dist/services/descriptor-run-settlement.js +5 -3
- package/dist/services/descriptor-run-start.js +22 -3
- package/dist/services/descriptor-stage-coordinator.js +37 -4
- package/dist/services/descriptor-stage-settlement.js +2 -1
- package/dist/services/diagnostic-evidence.js +73 -0
- package/dist/services/diagnostic-report-publication.js +72 -0
- package/dist/services/diagnostic-report.js +67 -0
- package/dist/services/diagnostic-run-bundle.js +74 -0
- package/dist/services/diagnostic-snapshot.js +159 -0
- package/dist/services/diagnostic-stage-handlers.js +108 -0
- package/dist/services/diagnostic-stage-results.js +132 -0
- package/dist/services/diagnostic-start.js +84 -0
- package/dist/services/diagnostic-work-batch.js +34 -0
- package/dist/services/diagnostic-work-collection.js +97 -0
- package/dist/services/diagnostic-work-registration.js +101 -0
- package/dist/services/diagnostic-work-results.js +93 -0
- package/dist/services/eval-snapshots.js +85 -29
- package/dist/services/execution-bundles.js +19 -7
- package/dist/services/flow-catalog.js +6 -1
- package/dist/services/frozen-engine.js +37 -2
- package/dist/services/hybrid-contracts.js +2 -0
- package/dist/services/installed-flow-source.js +25 -3
- package/dist/services/lifecycle-invocations.js +147 -34
- package/dist/services/memory-init-contexts.js +16 -0
- package/dist/services/memory-init-controller-start.js +12 -4
- package/dist/services/memory-init-lint-capability.js +5 -5
- package/dist/services/memory-init-native-capacity.js +4 -4
- package/dist/services/memory-init-preflight.js +10 -16
- package/dist/services/memory-init-worktrunk-capability.js +3 -7
- package/dist/services/memory-layer.js +4 -3
- package/dist/services/memory-tool-probe.js +25 -0
- package/dist/services/memory-verification-checks.js +15 -4
- package/dist/services/native-daemon-history.js +2 -1
- package/dist/services/provider-capacity.js +43 -38
- package/dist/services/release-cache.js +8 -0
- package/dist/services/run-control.js +2 -1
- package/dist/services/run-controller-adapter.js +2 -1
- package/dist/services/run-controller-process.js +2 -1
- package/dist/services/run-controller-recovery.js +2 -1
- package/dist/services/run-controller.js +85 -15
- package/dist/services/run-fork.js +1 -1
- package/dist/services/runs.js +6 -1
- package/dist/services/runtime-scope-resume.js +12 -6
- package/dist/services/runtime-scope-stop.js +3 -2
- package/dist/services/runtime-service.js +2 -1
- package/dist/services/stage-handlers.js +16 -3
- package/dist/services/stage-pause.js +4 -1
- package/dist/services/version-status.js +1 -1
- package/dist/services/vnext-code.js +2 -2
- package/dist/services/vnext-fanout.js +31 -1
- package/dist/services/work-registry.js +37 -6
- package/dist/services/workspace-bootstrap.js +2 -1
- package/package.json +6 -3
|
@@ -3,11 +3,14 @@ import { SETUP_STARTUP_MS, startupObserver, observationDetails } from './setup-p
|
|
|
3
3
|
import { callDaemonObserved } from './daemon-observation.mjs';
|
|
4
4
|
import { operationProgressReporter } from './operation-progress.mjs';
|
|
5
5
|
import { prepareDaemonRequest } from "./native-request-proof.mjs";
|
|
6
|
+
import { inspectAgyNative, stopAgyNativeTree, ownedAgyEndpoint, readAgyNativeQualification, AGY_NATIVE_INSPECTION_CONTRACT } from './agy-native-inspection.mjs';
|
|
7
|
+
import { processSnapshot } from './process-snapshot.mjs';
|
|
8
|
+
import { streamRequestKey } from './provider-overload-policy.mjs';
|
|
6
9
|
import { captureDispatchGuard, cancelPendingDispatch } from "./dispatch-fence.mjs";
|
|
7
10
|
import { errorRecord, isProvenShellCorrection } from "./operation-errors.mjs";
|
|
8
11
|
import { ToolObservations, agyToolObservation, agyTranscriptObservations } from "./tool-observations.mjs";
|
|
9
12
|
import { constants } from "node:fs";
|
|
10
|
-
import { fileURLToPath } from "node:url";
|
|
13
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
11
14
|
import { createHash, randomUUID } from "node:crypto";
|
|
12
15
|
import { appendFile, chmod, lstat, mkdir, open, readFile, realpath, rename, unlink, writeFile } from "node:fs/promises";
|
|
13
16
|
import { spawn } from "node:child_process";
|
|
@@ -21,12 +24,45 @@ import path from "node:path";
|
|
|
21
24
|
import { observeModel } from "./model-observations.mjs";
|
|
22
25
|
import { AGY_VERSION_TIMEOUT_MS, AgyError, agyTerminalFailure, agyModelArgs, assertProfile, executable, observedProfile, runAgy, usageSnapshot } from "./dd-agy.mjs";
|
|
23
26
|
import { recoveredAgyErrorBoundary } from "./agy-error-boundary.mjs";
|
|
24
|
-
import { settledNativeTree } from "./native-children.mjs";
|
|
27
|
+
import { settledNativeTree, nativeExecutionReceipt } from "./native-children.mjs";
|
|
25
28
|
import { retainStartupFailure, prepareRuntimeOwner, cleanupFailedStart, cleanupFailedDaemonStart, confirmDaemonProcess, confirmDaemonStopped, finishDaemonProcess, scheduleDaemonProcessHeartbeat, registerDaemonProcess, stopProcessGroup } from "./managed-daemon.mjs";
|
|
26
29
|
import { readNativeLines, streamNativeDiagnostics, nativeObservationError, admitNativeRequestBuffer } from "./native-streams.mjs";
|
|
27
30
|
|
|
28
31
|
const REQUEST_SCHEMA = "dd-agy/daemon-request@1", RESPONSE_SCHEMA = "dd-agy/daemon-response@1", STATE_SCHEMA = "dd-agy/daemon-state@1";
|
|
29
32
|
class DaemonError extends AgyError {}
|
|
33
|
+
// Only unambiguously correlated native tool results can introduce children or
|
|
34
|
+
// classify services. This transcript format has no tool-call IDs: parallel
|
|
35
|
+
// calls therefore retain unknown resources, never FIFO-derived authority.
|
|
36
|
+
export function agyTranscriptFacts(text, source, sessionId) {
|
|
37
|
+
const children = [], resources = [], calls = [];
|
|
38
|
+
if (!source?.locator || !source?.sha256 || !sessionId) return { children, resources };
|
|
39
|
+
for (const line of text.trim().split('\n')) {
|
|
40
|
+
let event;
|
|
41
|
+
try { event = JSON.parse(line); } catch { return { children: [], resources: [] }; }
|
|
42
|
+
if (event.source !== 'MODEL') continue;
|
|
43
|
+
if (event.type === 'PLANNER_RESPONSE') { calls.length = 0; if (Array.isArray(event.tool_calls) && event.tool_calls.length === 1) calls.push(event.tool_calls[0]); continue; }
|
|
44
|
+
if (event.type !== 'GENERIC') continue;
|
|
45
|
+
const call = calls.shift(), content = typeof event.content === 'string' ? event.content : '';
|
|
46
|
+
const native_source = { ...source, step_index: event.step_index };
|
|
47
|
+
if (call?.name === 'invoke_subagent' && event.status === 'DONE') {
|
|
48
|
+
const match = content.match(/^Created At: [^\n]+\nCompleted At: [^\n]+\nCreated the following subagent:\n(\{[\s\S]*?\})\nThe subagent will send you a message/);
|
|
49
|
+
if (!match) continue;
|
|
50
|
+
try {
|
|
51
|
+
const child = JSON.parse(match[1]);
|
|
52
|
+
if (/^[\w-]+$/.test(child.conversationId) && typeof child.logAbsoluteUri === 'string') children.push({ provider_session_id: child.conversationId, parent_provider_session_id: sessionId, log_uri: child.logAbsoluteUri, workspace_uris: child.workspaceUris ?? [], status: 'unknown', native_source });
|
|
53
|
+
} catch { /* No partial/guessed native edge. */ }
|
|
54
|
+
}
|
|
55
|
+
if (event.status !== 'RUNNING') continue;
|
|
56
|
+
const task = content.match(/^Created At: [^\n]+\nTool is running as a background task with task id: ([\w-]+\/task-\d+)\n/);
|
|
57
|
+
if (!task || !task[1].startsWith(`${sessionId}/`)) continue;
|
|
58
|
+
let daemon = call?.name === 'run_command' ? call.args?.IsDaemon : null;
|
|
59
|
+
if (typeof daemon === 'string') { try { daemon = JSON.parse(daemon); } catch { daemon = null; } }
|
|
60
|
+
resources.push({ resource_id: task[1], provider_session_id: sessionId, classification: daemon === true ? 'support_service' : daemon === false ? 'required_work' : 'unknown', state: 'unknown', native_source,
|
|
61
|
+
...(typeof daemon === 'boolean' ? { native_classification: { tool: 'run_command', IsDaemon: daemon } } : {}) });
|
|
62
|
+
}
|
|
63
|
+
return { children, resources };
|
|
64
|
+
}
|
|
65
|
+
export const agyTranscriptResources = (text, source, sessionId) => agyTranscriptFacts(text, source, sessionId).resources;
|
|
30
66
|
export async function captureAgyTranscript(runtimeRoot, child, rootSessionId, ownedSessionIds = [rootSessionId], inspect = null) {
|
|
31
67
|
const session = { harness_id: "antigravity-cli", session_id: child.provider_session_id };
|
|
32
68
|
let handle;
|
|
@@ -173,7 +209,7 @@ export async function callDaemon(stateDir, operation, params = {}, timeoutMs = 3
|
|
|
173
209
|
return await callDaemonObserved({ stateDir, socket: locations(stateDir).socket, request, responseSchema: RESPONSE_SCHEMA, harness: 'agy', timeoutMs });
|
|
174
210
|
}
|
|
175
211
|
|
|
176
|
-
function sameConfig(state, config) { return ["cwd","journal","bin","provider","model","reasoning","mode","projectRoot","ddFlowBin","ddFlowHome","resourceHome","noFlow","livenessTimeoutMs"].every(key => (state.config?.[key] ?? null) === (config[key] ?? null)); }
|
|
212
|
+
function sameConfig(state, config) { return ["cwd","journal","bin","provider","model","reasoning","mode","projectRoot","ddFlowBin","ddFlowHome","resourceHome","noFlow","livenessTimeoutMs","nativeQualificationPath"].every(key => (state.config?.[key] ?? null) === (config[key] ?? null)); }
|
|
177
213
|
// Called only after authorizeRetainedDaemonResume verifies the old daemon,
|
|
178
214
|
// provider processes and operation ledger. Native outcomes remain historical;
|
|
179
215
|
// their old liveness flags cannot describe the replacement process tree.
|
|
@@ -183,6 +219,8 @@ export function retainedAgyState(previous, sessionId) {
|
|
|
183
219
|
return {
|
|
184
220
|
...previous,
|
|
185
221
|
unclaimed_activity: false,
|
|
222
|
+
native_resources_entries: [],
|
|
223
|
+
native_topology_error: null,
|
|
186
224
|
retained_tree_settlement: { daemon_id: previous.daemon_id, session_id: sessionId, turn_generation: previous.turn_generation ?? 0, evidence: "verified_clean_daemon_shutdown" },
|
|
187
225
|
session_observations: (previous.session_observations ?? []).map(([id, observation]) => [id, id === sessionId ? { ...observation, retained_stop: observation.stop ?? null, stop: null } : observation]),
|
|
188
226
|
descendants: (previous.descendants ?? []).map(child => child.tree_settled === true ? child : { ...child, tree_settled: true, settlement_evidence: "prior_clean_daemon_shutdown", settlement_daemon_id: previous.daemon_id })
|
|
@@ -192,6 +230,7 @@ export async function startDaemon(options) {
|
|
|
192
230
|
const paths = locations(options.stateDir); await mkdir(paths.dir, { recursive: true, mode: 0o700 }); await chmod(paths.dir, 0o700);
|
|
193
231
|
const previous = await readState(paths.dir), noFlow = options.noFlow === true, cwd = await realpath(absolute(options.cwd, "--cwd")), config = { cwd, journal: options.journal ? absolute(options.journal, "--journal") : paths.journal, bin: options.bin ?? process.env.DD_AGY_BIN ?? "agy", provider: String(options.provider ?? "google"), model: String(options.model ?? "gemini-3.1-pro-high"), reasoning: String(options.reasoning ?? "high"), mode: String(options.mode ?? "accept-edits"), projectRoot: noFlow ? cwd : await realpath(absolute(options.projectRoot, "--project-root")), ddFlowBin: noFlow ? null : absolute(options.ddFlowBin, "--dd-flow-bin"), ddFlowHome: noFlow ? null : absolute(options.ddFlowHome, "--dd-flow-home"), resourceHome: noFlow ? null : (process.env.DD_FLOW_RESOURCE_HOME ? absolute(process.env.DD_FLOW_RESOURCE_HOME, "DD_FLOW_RESOURCE_HOME") : null), env: noFlow ? {} : { DD_FLOW_HOME: absolute(options.ddFlowHome, "--dd-flow-home") }, entryPath: absolute(options.entryPath, "dd-agy entry"), daemonId: randomUUID(), resumeSessionId: options.sessionId ?? null, geminiDir: paths.gemini, appDataDir: "runtime", temporary: paths.temporary, noFlow };
|
|
194
232
|
config.livenessTimeoutMs = options.livenessTimeoutMs ?? 1_800_000;
|
|
233
|
+
config.nativeQualificationPath = process.env.DD_AGY_NATIVE_QUALIFICATION ? absolute(process.env.DD_AGY_NATIVE_QUALIFICATION, 'DD_AGY_NATIVE_QUALIFICATION') : null;
|
|
195
234
|
validateObservationDuration(config.livenessTimeoutMs);
|
|
196
235
|
await prepareRuntimeOwner(config, paths.dir);
|
|
197
236
|
assertDaemonRestartable(previous);
|
|
@@ -203,7 +242,7 @@ export async function startDaemon(options) {
|
|
|
203
242
|
const version = (await runAgy(config.bin, ["--version"], { cwd: config.cwd, env: sanitizedEnv(config), timeoutMs: AGY_VERSION_TIMEOUT_MS })).stdout.trim();
|
|
204
243
|
const resourceProcess = await registerDaemonProcess(config, { kind: "agy-daemon", owner: `agy:${path.basename(paths.dir)}`, operation: `agy-daemon:${path.basename(paths.dir)}`, stdout: paths.log, stderr: paths.log });
|
|
205
244
|
const retained = retainedAgyState(previous, config.resumeSessionId);
|
|
206
|
-
await writeJson(paths.state, { schema_id: STATE_SCHEMA, daemon_id: config.daemonId, shutdown_state: "starting", active_tree: false, unclaimed_activity: retained.unclaimed_activity ?? false, retained_tree_settlement: retained.retained_tree_settlement ?? null, versions: { agy: version }, config, sessions: [], session_observations: retained.session_observations ?? [], descendants: retained.descendants ?? [], ...(Array.isArray(retained.tool_observations) ? { tool_observations: retained.tool_observations } : {}), tool_observation_gaps: retained.tool_observation_gaps ?? [], tool_calls: retained.tool_calls ?? [], completed_tool_steps: retained.completed_tool_steps ?? [], tool_identity_complete: retained.tool_identity_complete !== false, turn_generation: retained.turn_generation ?? 0, last_result: retained.last_result ?? null, resource_process: resourceProcess });
|
|
245
|
+
await writeJson(paths.state, { schema_id: STATE_SCHEMA, daemon_id: config.daemonId, shutdown_state: "starting", active_tree: false, unclaimed_activity: retained.unclaimed_activity ?? false, retained_tree_settlement: retained.retained_tree_settlement ?? null, versions: { agy: version }, config, sessions: [], session_observations: retained.session_observations ?? [], descendants: retained.descendants ?? [], native_resources_entries: retained.native_resources_entries ?? [], native_topology_error: retained.native_topology_error ?? null, ...(Array.isArray(retained.tool_observations) ? { tool_observations: retained.tool_observations } : {}), tool_observation_gaps: retained.tool_observation_gaps ?? [], tool_calls: retained.tool_calls ?? [], completed_tool_steps: retained.completed_tool_steps ?? [], tool_identity_complete: retained.tool_identity_complete !== false, turn_generation: retained.turn_generation ?? 0, last_result: retained.last_result ?? null, resource_process: resourceProcess });
|
|
207
246
|
const log = await open(paths.log, "a", 0o600), child = spawn(process.execPath, [config.entryPath, "daemon", "serve", "--state-dir", paths.dir], { cwd: config.cwd, env: sanitizedEnv(config), detached: true, stdio: ["ignore", log.fd, log.fd] }); child.unref(); await log.close();
|
|
208
247
|
try {
|
|
209
248
|
await confirmDaemonProcess(config, resourceProcess, child);
|
|
@@ -224,10 +263,17 @@ export async function startDaemon(options) {
|
|
|
224
263
|
export const stopDaemon = async options => { const timeoutMs = options.timeoutMs ?? 30_000; return await confirmDaemonStopped(() => callDaemon(options.stateDir, "daemon.stop", { cancelTree: options.cancelTree === true }, timeoutMs), locations(options.stateDir).socket, timeoutMs, { readState: () => readState(options.stateDir) }); };
|
|
225
264
|
|
|
226
265
|
export class Runtime {
|
|
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;
|
|
228
|
-
|
|
266
|
+
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;
|
|
267
|
+
const saved = state.native_observation_episode;
|
|
268
|
+
if (saved?.clock) {
|
|
269
|
+
this.observationEpisode = { operationId: saved.operation_id, generation: saved.turn_generation, connectionId: saved.connection_id, terminalMetadata: state.last_terminal_metadata,
|
|
270
|
+
clock: new ObservationClock({ timeoutMs: saved.clock.timeout_ms, saved: saved.clock }) };
|
|
271
|
+
this.connectionId = saved.connection_id;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
persist(patch = {}) { Object.assign(this.state, patch, { native_observation_episode: this.episodeSnapshot(), 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; }
|
|
229
275
|
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"])]; }
|
|
276
|
+
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", ...(this.nativeLogFile ? ['--log-file', this.nativeLogFile] : []), ...(this.nativeCsrfToken ? [`--csrf_token=${this.nativeCsrfToken}`] : []), `--gemini_dir=${this.paths.gemini}`, "--app_data_dir=runtime", ...(sessionId ? ["--conversation", sessionId] : ["--new-project"])]; }
|
|
231
277
|
requireSessionIdentity(requestedId) {
|
|
232
278
|
const observedId = this.init?.conversation_id ?? null;
|
|
233
279
|
if (!requestedId || requestedId === observedId) return observedId;
|
|
@@ -235,7 +281,7 @@ export class Runtime {
|
|
|
235
281
|
harness: "antigravity-cli", expected_session_id: requestedId, observed_session_id: observedId
|
|
236
282
|
});
|
|
237
283
|
}
|
|
238
|
-
markActivity() { const current = this.active; this.lastActivityAt = new Date().toISOString(); this.progress = { cursor: (this.progress?.cursor ?? 0) + 1, observed_at: Date.now() }; void current?.publishProgress?.('native_progress', { session_id: this.init?.conversation_id, observed_at: this.lastActivityAt }).catch(error => { if(this.active === current) current.reject(error); }); }
|
|
284
|
+
markActivity() { const current = this.active ?? this.observationEpisode; this.nativeRevision = (this.nativeRevision ?? 0) + 1; this.state.native_execution_revision = this.nativeRevision; this.nativeProof = null; this.nativeAbort?.abort(); this.lastActivityAt = new Date().toISOString(); this.progress = { cursor: (this.progress?.cursor ?? 0) + 1, observed_at: Date.now() }; current?.clock?.sample(this.progress); void current?.publishProgress?.('native_progress', { session_id: this.init?.conversation_id, observed_at: this.lastActivityAt }).catch(error => { if(this.active === current) current.reject(error); }); }
|
|
239
285
|
markTerminalActivity(result) {
|
|
240
286
|
const current = this.active;
|
|
241
287
|
if (!current) return;
|
|
@@ -245,11 +291,12 @@ export class Runtime {
|
|
|
245
291
|
current.terminalProgress.add(key); this.markActivity();
|
|
246
292
|
}
|
|
247
293
|
observeProviderActivity(event) {
|
|
248
|
-
|
|
294
|
+
const owner = this.active ?? this.observationEpisode;
|
|
295
|
+
if (!owner || owner.connectionId !== this.connectionId || owner.generation !== this.turnGeneration) return;
|
|
249
296
|
const root = this.init?.conversation_id, step = event.step_update;
|
|
250
297
|
const id = step?.conversation_id ?? event.result?.conversation_id ?? root;
|
|
251
298
|
let parent = id; const seen = new Set();
|
|
252
|
-
while (parent && parent !== root && !seen.has(parent)) { seen.add(parent); const child = this.descendants.get(parent); if (child?.activity_generation !==
|
|
299
|
+
while (parent && parent !== root && !seen.has(parent)) { seen.add(parent); const child = this.descendants.get(parent); if (child?.activity_generation !== owner.generation) return; parent = child.parent_provider_session_id; }
|
|
253
300
|
if (!root || parent !== root) return;
|
|
254
301
|
if (event.event === "result") { if (["SUCCESS", "ERROR", "CANCELLED"].includes(event.result?.status)) this.markTerminalActivity(event.result); return; }
|
|
255
302
|
if (event.event !== "step_update") return;
|
|
@@ -267,7 +314,7 @@ export class Runtime {
|
|
|
267
314
|
// Native step snapshots can alternate/replay; scope retained dedup to the
|
|
268
315
|
// current step. Too many unique snapshots becomes uncertain, never a grant.
|
|
269
316
|
if (snapshots.size >= 1_024) return;
|
|
270
|
-
snapshots.add(fingerprint); this.progressSteps.set(id, { index, seen: snapshots }); this.markActivity();
|
|
317
|
+
snapshots.add(fingerprint); this.progressSteps.set(id, { index, seen: snapshots }); this.invalidateSettlement(id); this.markActivity();
|
|
271
318
|
}
|
|
272
319
|
async start(sessionId = null) {
|
|
273
320
|
if (this.drainingError) throw this.drainingError;
|
|
@@ -280,6 +327,11 @@ export class Runtime {
|
|
|
280
327
|
await this.providerFinalization;
|
|
281
328
|
if (this.drainingError) throw this.drainingError;
|
|
282
329
|
assertDaemonDispatch(this.paths?.dir);
|
|
330
|
+
this.nativeAbort?.abort(); this.nativeProof = null;
|
|
331
|
+
this.nativeLogFile = path.join(this.paths.dir, `native-${randomUUID()}.log`);
|
|
332
|
+
this.nativeCsrfToken = randomUUID();
|
|
333
|
+
this.admissionClock = null;
|
|
334
|
+
this.nativeQualification = await readAgyNativeQualification(this.paths.dir, this.config.bin, this.config.nativeQualificationPath);
|
|
283
335
|
const target = executable(this.config.bin, this.args(sessionId)); this.exited = false; this.buffer = ""; this.init = null;
|
|
284
336
|
let readyResolve, readyReject; this.readyPromise = new Promise((resolve, reject) => { readyResolve = resolve; readyReject = reject; });
|
|
285
337
|
// The provider can reject before durable registration finishes. Keep a
|
|
@@ -293,6 +345,7 @@ export class Runtime {
|
|
|
293
345
|
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"] }); }
|
|
294
346
|
catch (error) { await cleanupFailedStart(this.config, providerProcess, null, error); throw error; }
|
|
295
347
|
const providerChild = this.child;
|
|
348
|
+
this.nativeLaunch = { logFile: this.nativeLogFile, providerPid: providerChild.pid, providerBirth: (await processSnapshot()).find(item => item.pid === providerChild.pid)?.started, connectionId: this.connectionId };
|
|
296
349
|
let allowProviderFinalization;
|
|
297
350
|
const providerRegistered = new Promise(resolve => { allowProviderFinalization = resolve; });
|
|
298
351
|
const observationFailure = error => { this.drainingError ??= error; readyReject(error); this.active?.reject(error); };
|
|
@@ -356,25 +409,53 @@ export class Runtime {
|
|
|
356
409
|
// Tree liveness is broader than current-Turn model progress: fresh work
|
|
357
410
|
// from a retained child invalidates settlement without adopting this Turn.
|
|
358
411
|
if (fresh && (sessionId === this.init?.conversation_id || this.descendants.has(sessionId))) this.childActivity(sessionId);
|
|
359
|
-
if (Number.isInteger(step.step_index)) this.sessionObservations.set(sessionId, { ...observation, last_step_index: Math.max(observation.last_step_index ?? -1, step.step_index) });
|
|
412
|
+
if (Number.isInteger(step.step_index)) this.sessionObservations.set(sessionId, { ...this.sessionObservations.get(sessionId), ...(step.step_index > (observation.last_step_index ?? -1) ? { last_execution_num: null } : {}), last_step_index: Math.max(observation.last_step_index ?? -1, step.step_index) });
|
|
360
413
|
this.toolObservations.add(agyToolObservation(step, this.init?.conversation_id));
|
|
361
414
|
for (const child of step.subagent_info?.subagents ?? []) if (child.conversation_id) {
|
|
362
415
|
const prior = this.descendants.get(child.conversation_id);
|
|
416
|
+
if (child.conversation_id === this.init?.conversation_id || prior && prior.parent_provider_session_id !== sessionId) {
|
|
417
|
+
this.state.native_topology_error = 'agy_native_ancestry_contradiction'; this.invalidateSettlement(sessionId); continue;
|
|
418
|
+
}
|
|
363
419
|
this.descendants.set(child.conversation_id, { ...prior, provider_session_id: child.conversation_id, parent_provider_session_id: sessionId ?? null, role: child.role ?? null, subagent_type: child.type_name ?? null, log_uri: child.log_uri ?? null, workspace_uris: child.workspace_uris ?? [], status: prior?.status ?? "unknown",
|
|
420
|
+
...(bound && Number.isInteger(step.step_index) ? { native_source: { kind: 'agy.step_update.subagent_info', parent_provider_session_id: sessionId, step_index: step.step_index } } : {}),
|
|
364
421
|
...(fresh && bound && this.active ? { activity_generation: this.active.generation } : {}) });
|
|
365
422
|
}
|
|
366
423
|
}
|
|
367
424
|
toolSnapshot() { const summary = this.toolObservations.summary(); const failures_by_name = {}; for (const event of this.toolObservations.calls.values()) if (event.status === "failed") failures_by_name[event.name] = (failures_by_name[event.name] ?? 0) + 1; return { ...summary, by_name: summary.by_tool, failures_by_name }; }
|
|
368
425
|
async captureChildTools() {
|
|
369
426
|
this.capturedToolFacts ??= new Map();
|
|
427
|
+
this.nativeResources ??= new Map(this.state.native_resources_entries?.map(item => [item.resource_id, item]) ?? []);
|
|
370
428
|
const root = this.init?.conversation_id ?? this.state.sessions?.[0]?.provider_session_id ?? this.lastResult?.conversation_id;
|
|
371
|
-
|
|
429
|
+
const queue = root && this.paths.runtime ? [{ provider_session_id: root, parent_provider_session_id: root, log_uri: pathToFileURL(path.join(this.paths.runtime, 'brain', root, '.system_generated', 'logs', 'transcript.jsonl')).href }, ...this.descendants.values()] : [...this.descendants.values()];
|
|
430
|
+
const visited = new Set();
|
|
431
|
+
for (let index = 0; index < queue.length && index < 256; index++) {
|
|
432
|
+
const child = queue[index];
|
|
433
|
+
if (visited.has(child.provider_session_id)) continue;
|
|
434
|
+
visited.add(child.provider_session_id);
|
|
372
435
|
const ancestors = new Set(); let parent = child.parent_provider_session_id;
|
|
373
436
|
while (parent && parent !== root && this.descendants.has(parent) && !ancestors.has(parent)) {
|
|
374
437
|
ancestors.add(parent); parent = this.descendants.get(parent).parent_provider_session_id;
|
|
375
438
|
}
|
|
376
439
|
const events = parent === root && root
|
|
377
|
-
? await captureAgyTranscript(this.paths.runtime, child, root, [root, ...ancestors])
|
|
440
|
+
? await captureAgyTranscript(this.paths.runtime, child, root, [root, ...ancestors], (text, source) => {
|
|
441
|
+
const facts = agyTranscriptFacts(text, source, child.provider_session_id);
|
|
442
|
+
for (const resource of facts.resources) this.nativeResources.set(resource.resource_id, resource);
|
|
443
|
+
for (const discovered of facts.children) {
|
|
444
|
+
const expected = path.join(this.paths.runtime, 'brain', discovered.provider_session_id, '.system_generated', 'logs', 'transcript.jsonl');
|
|
445
|
+
const sourceBrain = path.resolve(path.dirname(fileURLToPath(source.locator)), '../../..');
|
|
446
|
+
const sibling = path.join(sourceBrain, discovered.provider_session_id, '.system_generated', 'logs', 'transcript.jsonl');
|
|
447
|
+
let locator;
|
|
448
|
+
try { locator = fileURLToPath(discovered.log_uri); } catch { locator = null; }
|
|
449
|
+
if (locator !== expected && locator !== sibling) { this.state.native_topology_error = 'agy_native_locator_unbound'; this.invalidateSettlement(root); continue; }
|
|
450
|
+
const prior = this.descendants.get(discovered.provider_session_id);
|
|
451
|
+
if (discovered.provider_session_id === root || prior && (prior.parent_provider_session_id !== discovered.parent_provider_session_id || prior.log_uri && prior.log_uri !== discovered.log_uri)) {
|
|
452
|
+
this.state.native_topology_error = 'agy_native_ancestry_contradiction'; this.invalidateSettlement(root); continue;
|
|
453
|
+
}
|
|
454
|
+
const value = { ...discovered, ...prior, native_source: discovered.native_source };
|
|
455
|
+
this.descendants.set(value.provider_session_id, value);
|
|
456
|
+
queue.push(value);
|
|
457
|
+
}
|
|
458
|
+
})
|
|
378
459
|
: [{ session: { harness_id: "antigravity-cli", session_id: child.provider_session_id }, reason: "agy_transcript_ancestry_unproven" }];
|
|
379
460
|
for (const event of events) {
|
|
380
461
|
this.toolObservations.add(event);
|
|
@@ -384,6 +465,8 @@ export class Runtime {
|
|
|
384
465
|
this.capturedToolFacts.set(key, encoded);
|
|
385
466
|
}
|
|
386
467
|
}
|
|
468
|
+
if (queue.length > 256 && visited.size < new Set(queue.map(item => item.provider_session_id)).size) { this.state.native_topology_error = 'agy_native_inventory_limit'; this.invalidateSettlement(root); }
|
|
469
|
+
this.state.native_resources_entries = [...this.nativeResources.values()];
|
|
387
470
|
}
|
|
388
471
|
async finishTurn(result, retainedMetadata = null, providerEvent = null) {
|
|
389
472
|
if (result.status === "RUNNING") { await this.persist({ active_tree: true }); return; }
|
|
@@ -420,7 +503,7 @@ export class Runtime {
|
|
|
420
503
|
const rootStop = this.sessionObservations.get(this.init?.conversation_id)?.stop;
|
|
421
504
|
const pendingTerminal = current && result.status === "SUCCESS";
|
|
422
505
|
if (current) { current.terminalResult = result; current.terminalMetadata = terminalMetadata; current.terminalPersisted = false; }
|
|
423
|
-
if (pendingTerminal && rootStop?.fullyIdle === true) 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" });
|
|
506
|
+
if (pendingTerminal && rootStop?.source_bound === true && rootStop.fullyIdle === true) 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" });
|
|
424
507
|
this.state.terminal_observation_pending = true;
|
|
425
508
|
try {
|
|
426
509
|
await this.persist({ active_tree: true, last_result: result, last_terminal_metadata: terminalMetadata });
|
|
@@ -438,6 +521,7 @@ export class Runtime {
|
|
|
438
521
|
try { await this.persist({ terminal_observation_pending: false }); }
|
|
439
522
|
catch (error) { this.state.terminal_observation_pending = true; return this.failTerminalObservation(current, result, terminalMetadata, error); }
|
|
440
523
|
this.state.terminal_observation_pending = false;
|
|
524
|
+
this.queueNativeInspection();
|
|
441
525
|
if (!current) return;
|
|
442
526
|
if (this.active !== current) return;
|
|
443
527
|
if (result.status === "ERROR" || result.status === "CANCELLED") {
|
|
@@ -466,6 +550,7 @@ export class Runtime {
|
|
|
466
550
|
const current = this.active;
|
|
467
551
|
if (current?.observationError) throw current.observationError;
|
|
468
552
|
if (current?.terminalResult?.status !== "SUCCESS" || current.terminalResult !== this.lastResult) return;
|
|
553
|
+
this.queueNativeInspection();
|
|
469
554
|
await this.persist({ active_tree: !this.receipt().settled });
|
|
470
555
|
// Hooks can invalidate the tree while persistence is pending. Never
|
|
471
556
|
// publish a proof captured before that asynchronous boundary.
|
|
@@ -486,26 +571,127 @@ export class Runtime {
|
|
|
486
571
|
if (current) { current.observationError = error; current.reject(error); }
|
|
487
572
|
throw error;
|
|
488
573
|
}
|
|
574
|
+
queueNativeInspection() {
|
|
575
|
+
const owner = this.active ?? this.observationEpisode, proof = owner?.terminalMetadata?.request_proof;
|
|
576
|
+
if (!owner?.clock || !proof || this.state.terminal_observation_pending || this.nativeFlight || this.nativeProof || this.exited) return;
|
|
577
|
+
if (proof.connection_id !== this.connectionId || proof.daemon_instance !== this.config.daemonId || proof.turn_generation !== this.turnGeneration || proof.operation_id !== owner.operationId || proof.session_id !== this.init?.conversation_id) { this.nativeInspection = { state: 'unproven', reason: 'native_owner_binding_changed' }; return; }
|
|
578
|
+
if (owner.clock.sample(this.progress)) { this.nativeInspection = { state: 'unproven', reason: 'observation_budget_exhausted' }; return; }
|
|
579
|
+
// The observer's raw wire result is never authority before P3 qualification.
|
|
580
|
+
if (!this.nativeObserver && !this.nativeQualification?.qualified) { this.nativeInspection = { state: 'unproven', reason: 'native_inspection_not_qualified' }; return; }
|
|
581
|
+
const revision = this.nativeRevision ?? 0, connection = this.connectionId;
|
|
582
|
+
const controller = this.nativeAbort = new AbortController();
|
|
583
|
+
let recording = false;
|
|
584
|
+
this.nativeInspection = { state: 'inspection_pending', reason: 'native_idle_barrier_pending' };
|
|
585
|
+
this.nativeFlight = Promise.resolve().then(async () => {
|
|
586
|
+
await owner.dispatchJournal;
|
|
587
|
+
const clock = owner.clock.state(), remainingMs = Math.max(0, clock.timeout_ms - clock.elapsed_ms);
|
|
588
|
+
if (!remainingMs || owner.clock.sample(this.progress)) throw new Error('observation_budget_exhausted');
|
|
589
|
+
const endpoint = this.nativeObserver ? null : await ownedAgyEndpoint(this.nativeLaunch);
|
|
590
|
+
const observed = await (this.nativeObserver ?? inspectAgyNative)({ endpoint, sessionId: proof.session_id, cwd: this.config.cwd, remainingMs, signal: controller.signal, rootRelation: this.nativeQualification?.rootRelation, csrfToken: this.nativeCsrfToken });
|
|
591
|
+
if (!this.nativeObserver && JSON.stringify(await ownedAgyEndpoint(this.nativeLaunch)) !== JSON.stringify(endpoint)) throw new Error('native_endpoint_changed');
|
|
592
|
+
const current = () => !controller.signal.aborted && this.connectionId === connection && (this.nativeRevision ?? 0) === revision && this.turnGeneration === owner.generation && this.observationEpisode === owner && !this.exited && !owner.clock.sample(this.progress);
|
|
593
|
+
if (!current()) return;
|
|
594
|
+
if (!observed.idle) { this.nativeInspection = { state: 'inspection_pending', reason: observed.reason ?? 'native_wait_pending' }; return; }
|
|
595
|
+
const candidate = { daemon_instance: this.config.daemonId, connection_id: connection, operation_id: owner.operationId, turn_generation: owner.generation, request_proof: proof, revision, method: observed.method, source: 'native_idle_barrier' };
|
|
596
|
+
recording = true;
|
|
597
|
+
await this.journal('native_tree_settlement', { proof: candidate });
|
|
598
|
+
if (!current()) return;
|
|
599
|
+
// Persist conservatively before publishing. A crash may require another
|
|
600
|
+
// read-only observation; no concurrent reader gets an uncommitted grant.
|
|
601
|
+
await this.persist({ active_tree: true });
|
|
602
|
+
if (!current()) return;
|
|
603
|
+
this.nativeProof = candidate;
|
|
604
|
+
this.state.native_execution_revision = revision;
|
|
605
|
+
this.state.unclaimed_activity = false;
|
|
606
|
+
this.projectNativeIdleChildren();
|
|
607
|
+
this.nativeInspection = { state: 'ready', reason: 'native_wait_fully_idle' };
|
|
608
|
+
this.state.active_tree = !this.receipt().settled;
|
|
609
|
+
await this.completePendingTurn();
|
|
610
|
+
}).catch(error => {
|
|
611
|
+
if (!controller.signal.aborted) {
|
|
612
|
+
this.nativeProof = null;
|
|
613
|
+
const transient = !recording && ['grpc_4', 'grpc_14'].includes(error.details?.reason) && !owner.clock.sample(this.progress);
|
|
614
|
+
this.nativeInspection = { state: transient ? 'inspection_pending' : 'unproven', reason: error.details?.reason ?? error.message ?? 'native_inspection_failed' };
|
|
615
|
+
if (recording && this.active === owner && owner.terminalResult) {
|
|
616
|
+
try { this.failTerminalObservation(owner, owner.terminalResult, owner.terminalMetadata, error); } catch { /* original prompt owns the typed failure */ }
|
|
617
|
+
}
|
|
618
|
+
if (recording) this.state.terminal_observation_pending = true;
|
|
619
|
+
}
|
|
620
|
+
})
|
|
621
|
+
.finally(() => { this.nativeFlight = null; });
|
|
622
|
+
}
|
|
623
|
+
projectNativeIdleChildren() {
|
|
624
|
+
for (const [id, child] of this.descendants) this.descendants.set(id, { ...child, ...(!['completed', 'failed', 'cancelled'].includes(child.status) ? { previous_native_status: child.status, status: 'settled_by_root' } : {}), ...(child.native_turn_items ? { native_turn_items: { ...child.native_turn_items, observed: true, pending: false } } : {}), tree_settled: true, settlement_evidence: 'native_idle_barrier' });
|
|
625
|
+
}
|
|
626
|
+
async reconcileRetainedAdmission() {
|
|
627
|
+
const retained = this.state.retained_tree_settlement;
|
|
628
|
+
const fresh = !this.lastResult;
|
|
629
|
+
if (this.active || this.receipt().settled || !fresh && (retained?.evidence !== 'verified_clean_daemon_shutdown' || retained.session_id !== this.init?.conversation_id)) return;
|
|
630
|
+
if (!this.nativeObserver && !this.nativeQualification?.qualified) return;
|
|
631
|
+
if (this.admissionFlight) return await this.admissionFlight;
|
|
632
|
+
const clock = this.admissionClock ??= new ObservationClock({ timeoutMs: SETUP_STARTUP_MS });
|
|
633
|
+
if (!clock || clock.sample()) throw new DaemonError('agy_native_inspection_unavailable', 'Retained AGY native admission budget exhausted');
|
|
634
|
+
const connection = this.connectionId, revision = this.nativeRevision ?? 0, generation = this.turnGeneration;
|
|
635
|
+
const controller = this.nativeAbort = new AbortController();
|
|
636
|
+
this.admissionFlight = (async () => {
|
|
637
|
+
const endpoint = this.nativeObserver ? null : await ownedAgyEndpoint(this.nativeLaunch);
|
|
638
|
+
const observed = await (this.nativeObserver ?? inspectAgyNative)({ endpoint, sessionId: this.init.conversation_id, cwd: this.config.cwd, remainingMs: Math.max(1, clock.timeoutMs - clock.elapsed), signal: controller.signal, rootRelation: this.nativeQualification?.rootRelation, csrfToken: this.nativeCsrfToken });
|
|
639
|
+
if (!this.nativeObserver && JSON.stringify(await ownedAgyEndpoint(this.nativeLaunch)) !== JSON.stringify(endpoint)) throw new DaemonError('agy_native_inspection_unavailable', 'AGY admission endpoint changed');
|
|
640
|
+
const current = () => !controller.signal.aborted && !this.exited && !this.active && !clock.sample() && this.connectionId === connection && this.turnGeneration === generation && (this.nativeRevision ?? 0) === revision;
|
|
641
|
+
if (!observed.idle || !current()) throw new DaemonError('agy_native_inspection_unavailable', 'AGY fresh native admission is unproven');
|
|
642
|
+
// Admission is not the old stream request: no invented operation/Turn or
|
|
643
|
+
// request proof. The old failed result remains historical diagnostics.
|
|
644
|
+
const candidate = { daemon_instance: this.config.daemonId, connection_id: connection, turn_generation: generation, revision, method: observed.method, source: 'native_idle_admission' };
|
|
645
|
+
await this.journal('native_idle_admission', { proof: candidate });
|
|
646
|
+
if (!current()) throw new DaemonError('agy_native_inspection_unavailable', 'AGY native admission changed during observation persistence');
|
|
647
|
+
await this.persist({ active_tree: true });
|
|
648
|
+
if (!current()) throw new DaemonError('agy_native_inspection_unavailable', 'AGY native admission changed during observation persistence');
|
|
649
|
+
this.nativeProof = candidate;
|
|
650
|
+
this.state.native_execution_revision = revision;
|
|
651
|
+
this.state.unclaimed_activity = false; this.projectNativeIdleChildren();
|
|
652
|
+
this.state.active_tree = !this.receipt().settled;
|
|
653
|
+
})().catch(error => { this.nativeProof = null; this.nativeInspection = { state: 'unproven', reason: error.details?.reason ?? error.code ?? 'native_admission_unproven' }; throw error; }).finally(() => { this.admissionFlight = null; });
|
|
654
|
+
return await this.admissionFlight;
|
|
655
|
+
}
|
|
656
|
+
episodeSnapshot() {
|
|
657
|
+
const owner = this.observationEpisode;
|
|
658
|
+
return owner?.clock ? { operation_id: owner.operationId, turn_generation: owner.generation, connection_id: owner.connectionId, clock: owner.clock.state() } : null;
|
|
659
|
+
}
|
|
489
660
|
receipt(result = this.lastResult) {
|
|
490
661
|
if (this.state.last_terminal_failure && !this.lastTerminalFailure) this.lastTerminalFailure = this.state.last_terminal_failure;
|
|
491
|
-
const
|
|
662
|
+
const metadata = this.active?.terminalResult === result ? this.active.terminalMetadata : this.state.last_result === result ? this.state.last_terminal_metadata : this.lastTerminalFailure && this.lastTerminalFailure.details?.provider_result?.error === result?.error
|
|
492
663
|
&& this.lastTerminalFailure.details.provider_session_id === (result?.conversation_id || this.init?.conversation_id || null) ? this.lastTerminalFailure.details : null;
|
|
664
|
+
const terminalMetadata = metadata?.request_proof && (metadata.request_proof.connection_id !== this.connectionId || metadata.request_proof.daemon_instance !== this.config.daemonId)
|
|
665
|
+
? { ...metadata, request_proof: undefined } : metadata;
|
|
493
666
|
const assistantText = typeof result?.response === "string" ? result.response : typeof result?.text === "string" ? result.text : null;
|
|
494
667
|
const root = this.init?.conversation_id; const observation = this.sessionObservations.get(root);
|
|
495
668
|
const treeSettled = [...this.descendants.values()].every(child => child.tree_settled === true || ["completed", "failed", "cancelled", "settled_by_root"].includes(child.status));
|
|
496
|
-
//
|
|
497
|
-
//
|
|
498
|
-
|
|
499
|
-
const rootSettled =
|
|
669
|
+
// SUCCESS can coexist with background tasks. Only a current native idle
|
|
670
|
+
// proof (or verified prior clean retirement before new activity) closes it.
|
|
671
|
+
const retained = this.state.retained_tree_settlement;
|
|
672
|
+
const rootSettled = this.nativeProof?.turn_generation === this.turnGeneration && this.nativeProof.connection_id === this.connectionId && this.nativeProof.revision === (this.nativeRevision ?? 0)
|
|
673
|
+
|| observation?.stop?.source_bound === true && observation.stop?.fullyIdle === true && observation.stop.generation === this.turnGeneration
|
|
674
|
+
|| retained?.evidence === "verified_clean_daemon_shutdown" && retained.session_id === root && retained.turn_generation === this.turnGeneration && !this.child;
|
|
500
675
|
const terminalPending = this.active?.terminalResult === result;
|
|
501
|
-
|
|
502
|
-
|
|
676
|
+
// A positively inspected owned scope before its first Turn has no model
|
|
677
|
+
// result to manufacture. Native admission alone proves this empty execution.
|
|
678
|
+
const emptyExecution = !result && this.turnGeneration === 0 && !this.active && !this.descendants.size && this.nativeProof?.source === 'native_idle_admission';
|
|
679
|
+
const executionTerminal = emptyExecution || result && ["SUCCESS", "ERROR", "CANCELLED"].includes(result.status);
|
|
680
|
+
const settled = Boolean(!this.state.native_topology_error && !this.state.terminal_observation_pending && !this.active?.observationError && !this.state.unclaimed_activity && (!this.active || terminalPending) && executionTerminal && treeSettled && rootSettled);
|
|
681
|
+
const clock = this.observationEpisode?.clock;
|
|
682
|
+
if (clock) clock.sample(this.progress);
|
|
683
|
+
const exhausted = clock && (clock.observationLost || clock.elapsed >= clock.timeoutMs);
|
|
684
|
+
const nativeSettlement = { confirmed: Boolean(settled), state: settled ? 'ready' : exhausted ? 'unproven' : this.nativeInspection?.state ?? 'unproven', reason: settled ? 'current_root_idle_proven' : exhausted ? 'observation_budget_exhausted' : this.nativeInspection?.reason ?? 'current_root_idle_proof_missing', capability: AGY_NATIVE_INSPECTION_CONTRACT, operation_id: terminalMetadata?.request_proof?.operation_id, request_key: streamRequestKey(terminalMetadata?.request_proof), provider_session_id: root, daemon_instance: this.config.daemonId, connection_epoch: this.connectionId, generation: this.turnGeneration, ...(this.nativeProof ? { source: this.nativeProof.source, method: this.nativeProof.method, proof_sha256: createHash('sha256').update(JSON.stringify(this.nativeProof)).digest('hex') } : {}), ...(clock ? { observation_clock: clock.state() } : {}) };
|
|
685
|
+
return nativeExecutionReceipt({ harness: "antigravity-cli", runtime_family: "antigravity", provider_session_id: root ?? null, adapter_session_id: root ?? null, cwd: this.config.cwd, profile: this.profile, result, ...(terminalMetadata ? { terminal_metadata: terminalMetadata, ...(terminalMetadata.turn_id ? { terminal_turn_id: terminalMetadata.turn_id } : {}) } : {}), ...(assistantText === null ? {} : { assistant_text: assistantText }), usage: usageSnapshot(result ?? {}, this.toolSnapshot()), usage_ingest: this.usageIngest, descendants: [...this.descendants.values()], ...settledNativeTree(root, [...this.descendants.values()], settled, !this.state.unclaimed_activity && (!this.active || terminalPending) && rootSettled && ["SUCCESS", "ERROR", "CANCELLED"].includes(result?.status)), native_turn_items: { observed: Boolean(terminalMetadata?.request_proof), possible_effects: true, pending: !settled, settled, ...(terminalMetadata?.request_proof ? { request_proof: terminalMetadata.request_proof } : {}) }, transcript_path: observation?.transcript_path ?? null, settled, native_settled: settled, physical_stopped: this.exited === true, native_settlement: nativeSettlement, }, { daemonId: this.config.daemonId, generation: this.turnGeneration, revision: this.nativeRevision ?? 0, resources: { coverage: "partial", entries: [...(this.nativeResources?.values() ?? this.state.native_resources_entries ?? [])] } });
|
|
503
686
|
}
|
|
504
687
|
async zeroUsage() { await forwardUsage(this.config, this.init.conversation_id, { usage: {} }, this.toolSnapshot(), []); }
|
|
505
688
|
async prompt(text, assertDispatch = captureDispatchGuard(this), operationId = null, livenessTimeoutMs = 1_800_000) {
|
|
506
689
|
validateObservationDuration(livenessTimeoutMs);
|
|
507
690
|
if (!text) throw new DaemonError("prompt_required", "prompt is required");
|
|
508
691
|
await this.start(); await this.draining;
|
|
692
|
+
if (!this.nativeObserver && !this.nativeQualification?.qualified && this.config.bin && this.paths.dir) this.nativeQualification = await readAgyNativeQualification(this.paths.dir, this.config.bin, this.config.nativeQualificationPath);
|
|
693
|
+
if (!this.nativeObserver && !this.nativeQualification?.qualified) throw new DaemonError('agy_native_inspection_unqualified', 'AGY whole-tree inspection requires a matching native qualification receipt before productive dispatch', false, { capability: AGY_NATIVE_INSPECTION_CONTRACT });
|
|
694
|
+
await this.reconcileRetainedAdmission();
|
|
509
695
|
if (this.active || this.promptObservation) throw new DaemonError("operation_in_progress", "an Antigravity turn or its mandatory dispatch observation is already running");
|
|
510
696
|
if (this.state.last_hook_rejection && !this.receipt().settled) throw new DaemonError("tree_not_settled", "Rejected hook does not prove that the prior Antigravity tree stopped", false, { primary_error: this.state.last_hook_rejection });
|
|
511
697
|
if (this.state.unclaimed_activity || (this.lastResult && !this.receipt().settled)) throw new DaemonError("tree_not_settled", "the prior Antigravity agent tree is not fully settled");
|
|
@@ -515,6 +701,9 @@ export class Runtime {
|
|
|
515
701
|
void reply.catch(() => {});
|
|
516
702
|
this.connectionId ??= randomUUID();
|
|
517
703
|
const current = { resolve: resolveTurn, reject: rejectTurn, generation: ++this.turnGeneration, operationId, connectionId: this.connectionId, dispatched: false };
|
|
704
|
+
this.nativeAbort?.abort(); this.nativeProof = null; this.nativeInspection = null;
|
|
705
|
+
current.clock = new ObservationClock({ timeoutMs: livenessTimeoutMs, onGap: gap => { void this.journal('observation_gap', { gap, operation_id: operationId }).catch(() => {}); } });
|
|
706
|
+
this.observationEpisode = current;
|
|
518
707
|
let resolveDispatchJournal;
|
|
519
708
|
const dispatchJournal = current.dispatchJournal = new Promise(resolve => { resolveDispatchJournal = resolve; });
|
|
520
709
|
this.promptObservation = current;
|
|
@@ -549,9 +738,10 @@ export class Runtime {
|
|
|
549
738
|
else if (this.active === current) this.active = null;
|
|
550
739
|
current.reject(error); finishDispatchJournal();
|
|
551
740
|
}
|
|
552
|
-
const clock =
|
|
741
|
+
const clock = current.clock;
|
|
553
742
|
let checking = false;
|
|
554
743
|
const timer = setInterval(() => {
|
|
744
|
+
if (this.active === current && current.terminalResult && this.nativeInspection?.state === 'inspection_pending' && !this.nativeFlight) this.queueNativeInspection();
|
|
555
745
|
if (this.active !== current || checking || !clock.sample(this.progress)) return;
|
|
556
746
|
checking = true; const cursor = this.progress?.cursor;
|
|
557
747
|
void this.completePendingTurn().then(() => {
|
|
@@ -580,7 +770,18 @@ export class Runtime {
|
|
|
580
770
|
} catch (error) {
|
|
581
771
|
await dispatchJournal;
|
|
582
772
|
if (error.code === "native_outcome_observation_failed") throw error;
|
|
583
|
-
if (current.dispatchObservationError)
|
|
773
|
+
if (current.dispatchObservationError) {
|
|
774
|
+
if (!current.terminalResult) throw current.dispatchObservationError;
|
|
775
|
+
const cause = current.dispatchObservationError;
|
|
776
|
+
const failed = current.terminalResult.status === 'SUCCESS'
|
|
777
|
+
? new DaemonError('native_outcome_observation_failed', 'Antigravity completed natively but its dispatch journal could not be saved', false, { native_outcome: this.receipt(current.terminalResult), settled: false, observation_error: errorRecord(cause) })
|
|
778
|
+
: agyTerminalFailure(current.terminalResult, { ...current.terminalMetadata, native_outcome: current.terminalResult, settled: false, native_turn_items: { observed: false, possible_effects: true, pending: true }, observation_error: errorRecord(cause) });
|
|
779
|
+
failed.cause = cause.cause ?? cause;
|
|
780
|
+
current.observationError = failed; current.dispatchObservationError = failed;
|
|
781
|
+
this.drainingError = failed; this.state.terminal_observation_pending = true;
|
|
782
|
+
if (current.terminalResult.status !== 'SUCCESS') this.lastTerminalFailure = failed;
|
|
783
|
+
throw failed;
|
|
784
|
+
}
|
|
584
785
|
const metadata = current.terminalMetadata;
|
|
585
786
|
if (error === this.lastTerminalFailure && metadata && !metadata.request_proof && current.outboundPosition && metadata.stream_terminal_position && this.config.daemonId && current.operationId) {
|
|
586
787
|
metadata.request_proof = { kind: "stream_request", daemon_instance: this.config.daemonId, connection_id: current.connectionId, operation_id: current.operationId,
|
|
@@ -597,18 +798,36 @@ export class Runtime {
|
|
|
597
798
|
}
|
|
598
799
|
}
|
|
599
800
|
childActivity(sessionId) {
|
|
801
|
+
this.invalidateSettlement(sessionId);
|
|
600
802
|
if (sessionId === this.init?.conversation_id && !this.active) this.state.unclaimed_activity = true;
|
|
601
803
|
const child = this.descendants.get(sessionId);
|
|
602
804
|
if (!child) return;
|
|
603
805
|
this.descendants.set(sessionId, { ...child, previous_terminal_status: child.status === "unknown" ? child.previous_terminal_status : child.status, status: "unknown", tree_settled: false, settlement_evidence: null, settlement_daemon_id: null });
|
|
604
806
|
}
|
|
807
|
+
invalidateSettlement(sessionId) {
|
|
808
|
+
this.nativeProof = null; this.nativeRevision = (this.nativeRevision ?? 0) + 1; this.nativeAbort?.abort();
|
|
809
|
+
this.state.native_execution_revision = this.nativeRevision;
|
|
810
|
+
if (this.init) this.state.active_tree = true;
|
|
811
|
+
this.state.retained_tree_settlement = null;
|
|
812
|
+
for (const id of new Set([this.init?.conversation_id, sessionId])) {
|
|
813
|
+
const observation = this.sessionObservations.get(id);
|
|
814
|
+
if (observation?.stop) this.sessionObservations.set(id, { ...observation, stop: null });
|
|
815
|
+
}
|
|
816
|
+
}
|
|
605
817
|
async childForHook(conversationId) {
|
|
606
818
|
if (conversationId === this.init?.conversation_id) return null;
|
|
819
|
+
await this.captureChildTools();
|
|
607
820
|
const deadline = Date.now() + 1_000;
|
|
608
|
-
while (!this.descendants.has(conversationId) && Date.now() < deadline) await new Promise(resolve => setTimeout(resolve,
|
|
821
|
+
while (!this.descendants.has(conversationId) && Date.now() < deadline) { await new Promise(resolve => setTimeout(resolve, 25)); await this.captureChildTools(); }
|
|
609
822
|
const child = this.descendants.get(conversationId);
|
|
610
823
|
if (!child) throw new DaemonError("agy_child_identity_unconfirmed", "Antigravity hook has no confirmed native parent", true, { conversation_id: conversationId });
|
|
611
|
-
|
|
824
|
+
const seen = new Set([conversationId]); let current = child;
|
|
825
|
+
while (current.parent_provider_session_id !== this.init?.conversation_id) {
|
|
826
|
+
const parent = this.descendants.get(current.parent_provider_session_id);
|
|
827
|
+
if (this.state.native_topology_error || !current.native_source || !parent || seen.has(parent.provider_session_id)) throw new DaemonError("agy_child_parent_unqualified", "Antigravity worker has no source-bound ancestry to the controlled Session", false, { conversation_id: conversationId, parent_conversation_id: current.parent_provider_session_id });
|
|
828
|
+
seen.add(parent.provider_session_id); current = parent;
|
|
829
|
+
}
|
|
830
|
+
if (this.state.native_topology_error) throw new DaemonError("agy_child_parent_unqualified", "Antigravity native ancestry is contradictory");
|
|
612
831
|
return child;
|
|
613
832
|
}
|
|
614
833
|
async rejectHook(error, event, payload, current = this.active) {
|
|
@@ -642,12 +861,44 @@ export class Runtime {
|
|
|
642
861
|
await this.rejectHook(Object.assign(new Error(failure.message ?? "Native hook failed"), { code: failure.code, details: failure.details }), binding.event, binding.payload, binding.current);
|
|
643
862
|
}
|
|
644
863
|
}
|
|
645
|
-
async observeHook(event, payload, budget = null) {
|
|
864
|
+
async observeHook(event, payload, budget = null) {
|
|
865
|
+
const conversationId = payload.conversationId ?? this.init?.conversation_id;
|
|
866
|
+
const child = await this.childForHook(conversationId); budget?.remaining("identity");
|
|
867
|
+
const prior = this.sessionObservations.get(conversationId) ?? {};
|
|
868
|
+
const execution = Number.isInteger(payload.executionNum) ? payload.executionNum : null;
|
|
869
|
+
const step = Number.isInteger(payload.stepIdx) ? payload.stepIdx : null;
|
|
870
|
+
const stale = step !== null && (Number.isInteger(prior.step_floor) && step <= prior.step_floor
|
|
871
|
+
|| Number.isInteger(prior.last_step_index) && step < prior.last_step_index
|
|
872
|
+
|| step === prior.last_step_index && execution !== null && Number.isInteger(prior.last_execution_num) && execution < prior.last_execution_num);
|
|
873
|
+
if (stale) { await this.journal("stale_hook", { hook_event: event, conversation_id: conversationId, step_index: step }); return { stale: true }; }
|
|
874
|
+
const fingerprint = createHash("sha256").update(JSON.stringify({ event, payload })).digest("hex");
|
|
875
|
+
if (event === "Stop" && prior.last_stop_hook === fingerprint && prior.last_stop_generation === this.turnGeneration) return { stale: true };
|
|
876
|
+
this.progressHooks ??= new Set();
|
|
877
|
+
const scoped = this.active && (!child || child.activity_generation === this.active.generation);
|
|
878
|
+
const sourceBound = Boolean(scoped && step !== null && step > (this.active.stepFloor ?? -1)
|
|
879
|
+
&& this.progressSteps?.get(conversationId)?.index === step);
|
|
880
|
+
const progressKey = JSON.stringify([conversationId, step, execution, event, payload.toolCall ?? null, payload.fullyIdle ?? null]);
|
|
881
|
+
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(); }
|
|
882
|
+
if (conversationId) this.sessionObservations.set(conversationId, { ...prior,
|
|
883
|
+
...(step !== null ? { last_step_index: step, last_execution_num: execution ?? (step === prior.last_step_index ? prior.last_execution_num : null) } : {}),
|
|
884
|
+
last_hook: fingerprint, ...(event === "PreToolUse" ? { last_pre_hook: fingerprint } : {}),
|
|
885
|
+
...(payload.transcriptPath ? { transcript_path: payload.transcriptPath } : {}),
|
|
886
|
+
...(event === "Stop" ? { last_stop_hook: fingerprint, last_stop_generation: this.turnGeneration, stop: { fullyIdle: payload.fullyIdle === true, source_bound: sourceBound, terminationReason: payload.terminationReason ?? null, executionNum: execution } } : {}) });
|
|
887
|
+
if (event === "PreToolUse" && prior.last_pre_hook !== fingerprint) this.childActivity(conversationId);
|
|
888
|
+
if (child && event === "Stop" && sourceBound && payload.fullyIdle === true) this.descendants.set(conversationId, { ...this.descendants.get(conversationId), tree_settled: true, settlement_evidence: "child_fully_idle_stop" });
|
|
889
|
+
const terminalPending = this.active?.terminalResult === this.lastResult;
|
|
890
|
+
if (!child && event === "Stop" && sourceBound && 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" });
|
|
891
|
+
const parent = child?.parent_provider_session_id ?? null;
|
|
892
|
+
if (event === "Stop" && !stale) {
|
|
893
|
+
const observation = this.sessionObservations.get(conversationId);
|
|
894
|
+
this.sessionObservations.set(conversationId, { ...observation, stop: { ...observation.stop, generation: this.turnGeneration } });
|
|
895
|
+
}
|
|
646
896
|
const observed = payload.model ? observedProfile(payload) : {};
|
|
647
897
|
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" });
|
|
648
898
|
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
899
|
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
900
|
if (!stale && !child && event === "PreToolUse" && Number.isInteger(step) && this.active) { this.active.nativeHookEvents ??= new Map(); this.active.nativeHookEvents.set(step, `${eventId}:before`); }
|
|
901
|
+
this.queueNativeInspection();
|
|
651
902
|
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 };
|
|
652
903
|
}
|
|
653
904
|
async finalizeProviderForControl() {
|
|
@@ -675,19 +926,40 @@ export class Runtime {
|
|
|
675
926
|
}
|
|
676
927
|
async prepareStop(cancelTree) {
|
|
677
928
|
const current = this.active, priorResult = this.lastResult;
|
|
678
|
-
|
|
679
|
-
if (cancelTree) await this.cancel();
|
|
929
|
+
if (!cancelTree && !current && !priorResult) await this.reconcileRetainedAdmission();
|
|
930
|
+
if (cancelTree && (this.active || !this.receipt().settled)) await this.cancel();
|
|
680
931
|
else if (this.active || this.state.active_tree || this.state.unclaimed_activity || (this.lastResult && !this.receipt().settled)) throw new DaemonError("tree_not_settled", "Antigravity root or child tree is still running");
|
|
681
932
|
// A physical kill is settlement, not a native clean terminal receipt.
|
|
682
933
|
// In particular, the previous Turn's SUCCESS cannot certify a cancelled
|
|
683
934
|
// in-flight Turn that exited without its own result.
|
|
684
935
|
const currentResult = !current || current.terminalResult === this.lastResult || this.lastResult !== priorResult;
|
|
685
|
-
|
|
686
|
-
|
|
936
|
+
if (currentResult && this.receipt().settled) await this.retireNativeServices();
|
|
937
|
+
const clean = currentResult && this.receipt().settled;
|
|
938
|
+
return { ...this.receipt(), clean, settled: Boolean(clean), native_settled: Boolean(clean), ...(clean ? {} : { native_settlement: { confirmed: false, reason: "native_terminal_unconfirmed" } }) };
|
|
939
|
+
}
|
|
940
|
+
async retireNativeServices() {
|
|
941
|
+
const services = [...(this.nativeResources?.values() ?? [])].filter(item => item.classification === 'support_service' && item.state !== 'stopped');
|
|
942
|
+
// Unknown classification/unsupported binaries keep the existing cleanup
|
|
943
|
+
// blocker. A fixture PID printed to stdout is never a production mapping.
|
|
944
|
+
if (!services.length || !this.nativeQualification?.qualified) return;
|
|
945
|
+
const revision = this.nativeRevision ?? 0;
|
|
946
|
+
const endpoint = this.nativeStopper ? null : await ownedAgyEndpoint(this.nativeLaunch);
|
|
947
|
+
const input = { endpoint, sessionId: this.init.conversation_id, cwd: this.config.cwd,
|
|
948
|
+
csrfToken: this.nativeCsrfToken, remainingMs: 10_000, rootRelation: this.nativeQualification.rootRelation };
|
|
949
|
+
const stopped = await (this.nativeStopper ?? stopAgyNativeTree)(input, [...this.descendants.keys()]);
|
|
950
|
+
const observed = await (this.nativeObserver ?? inspectAgyNative)(input);
|
|
951
|
+
if (!this.nativeStopper && !isDeepStrictEqual(await ownedAgyEndpoint(this.nativeLaunch), endpoint)
|
|
952
|
+
|| !observed.idle || revision !== (this.nativeRevision ?? 0) || !this.receipt().settled)
|
|
953
|
+
throw new DaemonError('native_resources_unproven', 'AGY native service retirement lost its owner/execution fence');
|
|
954
|
+
// This is native handle retirement, not physical cleanup. Shared shutdown
|
|
955
|
+
// still re-observes the retained local PID/birth inventory after close.
|
|
956
|
+
for (const service of services) this.nativeResources.set(service.resource_id, { ...service, state: 'stopped',
|
|
957
|
+
native_retirement: { method: stopped.method, stopped_session_ids: stopped.stopped_session_ids, revision } });
|
|
958
|
+
await this.persist({ native_resources_entries: [...this.nativeResources.values()] });
|
|
687
959
|
}
|
|
688
|
-
async cancel() {
|
|
960
|
+
async cancel() { if (!this.active && this.receipt().settled) return { ...this.receipt(), cancelled: true }; this.nativeAbort?.abort(); this.nativeProof = null; this.nativeRevision = (this.nativeRevision ?? 0) + 1; this.state.native_execution_revision = this.nativeRevision; cancelPendingDispatch(this); if (!this.child) return this.receipt(); if (!this.exited) { try { process.kill(-this.child.pid, "SIGINT"); } catch (error) { if (error.code !== "ESRCH") throw error; } const deadline = Date.now() + 5000; while (!this.exited && Date.now() < deadline) await new Promise(resolve => setTimeout(resolve, 100)); if (!this.exited) await stopProcessGroup(this.child, 5_000); } await this.finalizeProviderForControl(); await this.persist({ active_tree: false }); return { ...this.receipt(), cancelled: true };
|
|
689
961
|
}
|
|
690
|
-
async close(cancelTree) { if (cancelTree) cancelPendingDispatch(this); if (!cancelTree && !this.exited && (this.active || this.state.active_tree || this.state.unclaimed_activity || (this.lastResult && !this.receipt().settled))) throw new DaemonError("tree_not_settled", "Antigravity root or child tree is still running"); if (cancelTree) await this.cancel(); else if (this.child && !this.exited) this.child.stdin.end(); const deadline = Date.now() + 5000; while (!this.exited && Date.now() < deadline) await new Promise(resolve => setTimeout(resolve, 100)); if (!this.exited) await this.cancel(); await this.finalizeProviderForControl(); }
|
|
962
|
+
async close(cancelTree) { if (cancelTree) cancelPendingDispatch(this); if (!cancelTree && !this.exited && (this.active || this.state.active_tree || this.state.unclaimed_activity || (this.lastResult && !this.receipt().settled))) throw new DaemonError("tree_not_settled", "Antigravity root or child tree is still running"); if (cancelTree) await this.cancel(); else if (this.child && !this.exited) this.child.stdin.end(); const deadline = Date.now() + 5000; while (!this.exited && Date.now() < deadline) await new Promise(resolve => setTimeout(resolve, 100)); if (!this.exited) { if (!this.active && this.receipt().settled) await stopProcessGroup(this.child, 5_000); else await this.cancel(); } await this.finalizeProviderForControl(); }
|
|
691
963
|
async abortProvider(reason) {
|
|
692
964
|
const child = this.child, providerProcess = this.providerProcess;
|
|
693
965
|
await stopProcessGroup(child);
|
|
@@ -742,7 +1014,7 @@ async function dispatch(runtime, request) {
|
|
|
742
1014
|
if (request.operation === "daemon.status") { await runtime.observeHookFailures(); return { daemon_id: runtime.config.daemonId, shutdown_state: runtime.state.shutdown_state, active_tree: runtime.state.active_tree, active_operation: runtime.active?.operationId ?? null, last_hook_rejection: runtime.state.last_hook_rejection ?? null, last_activity_at: runtime.lastActivityAt, provider_ready: Boolean(runtime.init && !runtime.exited), pid: process.pid, provider_pid: runtime.child?.pid ?? null, versions: runtime.state.versions, config: runtime.config, sessions: runtime.state.sessions ?? [], receipt: runtime.receipt() }; }
|
|
743
1015
|
if (request.operation === "session.create") { if (params.sessionId) throw new DaemonError("invalid_create", "create does not accept a Session ID"); if (params.prompt) throw new DaemonError("invalid_create", "create does not execute a prompt; use session.prompt"); await runtime.start(); await runtime.zeroUsage(); return runtime.receipt(); }
|
|
744
1016
|
if (request.operation === "session.prompt") { const assertDispatch = captureDispatchGuard(runtime); await runtime.start(params.sessionId ?? null); runtime.requireSessionIdentity(params.sessionId); return await runtime.prompt(params.prompt, assertDispatch, request.id, params.livenessTimeoutMs ?? runtime.config.livenessTimeoutMs ?? 1_800_000); }
|
|
745
|
-
if (request.operation === "session.inspect") { runtime.requireSessionIdentity(params.sessionId); await runtime.captureChildTools(); return runtime.receipt(); }
|
|
1017
|
+
if (request.operation === "session.inspect") { runtime.requireSessionIdentity(params.sessionId); await runtime.captureChildTools(); void runtime.reconcileRetainedAdmission().catch(error => { runtime.nativeInspection = { state: 'unproven', reason: error.details?.reason ?? error.code ?? 'native_admission_unproven' }; }); runtime.queueNativeInspection(); await runtime.persist({ native_observation_episode: runtime.episodeSnapshot() }); return runtime.receipt(); }
|
|
746
1018
|
if (request.operation === "session.resume") { await runtime.start(params.sessionId ?? runtime.init?.conversation_id ?? null); runtime.requireSessionIdentity(params.sessionId); return runtime.receipt(); }
|
|
747
1019
|
if (request.operation === "session.cancel") { runtime.requireSessionIdentity(params.sessionId); return await runtime.cancel(); }
|
|
748
1020
|
if (request.operation === "hook.observe") {
|