@noopolis/daimon 0.2.0 → 0.2.2
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/README.md +6 -0
- package/dist/contracts/attentionContract.d.ts +25 -0
- package/dist/contracts/attentionContract.d.ts.map +1 -1
- package/dist/contracts/attentionContract.js +8 -1
- package/dist/contracts/grokBrokerTaskClock.d.ts +18 -0
- package/dist/contracts/grokBrokerTaskClock.d.ts.map +1 -0
- package/dist/contracts/grokBrokerTaskClock.js +16 -0
- package/dist/contracts/organizationRuntimeContract.d.ts +76 -0
- package/dist/contracts/organizationRuntimeContract.d.ts.map +1 -1
- package/dist/contracts/organizationRuntimeContract.js +2 -0
- package/dist/contracts/runtimeContractManifest.d.ts +222 -15
- package/dist/contracts/runtimeContractManifest.d.ts.map +1 -1
- package/dist/contracts/runtimeContractManifest.js +35 -10
- package/dist/contracts/shellEnvironmentPolicy.d.ts +3 -0
- package/dist/contracts/shellEnvironmentPolicy.d.ts.map +1 -0
- package/dist/contracts/shellEnvironmentPolicy.js +5 -0
- package/dist/contracts/wakeMomentContract.d.ts +61 -0
- package/dist/contracts/wakeMomentContract.d.ts.map +1 -0
- package/dist/contracts/wakeMomentContract.js +27 -0
- package/dist/core/types.d.ts +2 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/examples/jungianPlayAgent.d.ts +1 -0
- package/dist/examples/jungianPlayAgent.d.ts.map +1 -1
- package/dist/examples/jungianPlayAgent.js +6 -2
- package/dist/observability/causalEvents.d.ts +3 -3
- package/dist/observability/causalEvents.d.ts.map +1 -1
- package/dist/observability/causalEvents.js +4 -4
- package/dist/pi/cliEngineSpawn.d.ts +2 -2
- package/dist/pi/cliEngineSpawn.d.ts.map +1 -1
- package/dist/pi/cliEngineSpawn.js +7 -2
- package/dist/pi/cliEnvironment.d.ts.map +1 -1
- package/dist/pi/cliEnvironment.js +2 -0
- package/dist/pi/cliMcpRegistration.d.ts +1 -1
- package/dist/pi/cliMcpRegistration.d.ts.map +1 -1
- package/dist/pi/cliMcpRegistration.js +1 -1
- package/dist/pi/cliSession.d.ts +2 -0
- package/dist/pi/cliSession.d.ts.map +1 -1
- package/dist/pi/cliSession.js +3 -80
- package/dist/pi/cliSessionMcp.d.ts +9 -0
- package/dist/pi/cliSessionMcp.d.ts.map +1 -0
- package/dist/pi/cliSessionMcp.js +80 -0
- package/dist/pi/engineTaskClock.d.ts +9 -0
- package/dist/pi/engineTaskClock.d.ts.map +1 -0
- package/dist/pi/engineTaskClock.js +28 -0
- package/dist/pi/grokHomeMcpRegistration.d.ts.map +1 -1
- package/dist/pi/grokHomeMcpRegistration.js +2 -1
- package/dist/pi/grokSandbox.js +1 -1
- package/dist/pi/memoryClock.d.ts +25 -0
- package/dist/pi/memoryClock.d.ts.map +1 -0
- package/dist/pi/memoryClock.js +18 -0
- package/dist/pi/piAgentHandle.d.ts +3 -1
- package/dist/pi/piAgentHandle.d.ts.map +1 -1
- package/dist/pi/piAgentHandle.js +5 -2
- package/dist/pi/piHarness.d.ts +8 -2
- package/dist/pi/piHarness.d.ts.map +1 -1
- package/dist/pi/piHarness.js +28 -12
- package/dist/pi/taskClockProcess.d.ts +6 -0
- package/dist/pi/taskClockProcess.d.ts.map +1 -0
- package/dist/pi/taskClockProcess.js +8 -0
- package/dist/pi/turnCausal.d.ts +4 -2
- package/dist/pi/turnCausal.d.ts.map +1 -1
- package/dist/pi/turnCausal.js +4 -4
- package/dist/pi/wakeAcceptance.d.ts +1 -1
- package/dist/pi/wakeAcceptance.d.ts.map +1 -1
- package/dist/pi/wakeAcceptance.js +2 -2
- package/dist/runtime/agentSkillsDirectory.d.ts +25 -0
- package/dist/runtime/agentSkillsDirectory.d.ts.map +1 -0
- package/dist/runtime/agentSkillsDirectory.js +101 -0
- package/dist/runtime/agySubscriptionBootstrap.js +1 -1
- package/dist/runtime/agySubscriptionRealm.js +1 -1
- package/dist/runtime/attention.d.ts +5 -0
- package/dist/runtime/attention.d.ts.map +1 -1
- package/dist/runtime/attention.js +14 -1
- package/dist/runtime/attentionDispatcher.d.ts +14 -2
- package/dist/runtime/attentionDispatcher.d.ts.map +1 -1
- package/dist/runtime/attentionDispatcher.js +127 -33
- package/dist/runtime/cli.js +5 -0
- package/dist/runtime/contract-manifest.json +1 -1
- package/dist/runtime/contract-manifest.sha256 +1 -1
- package/dist/runtime/engineBrokerCapabilities.d.ts +10 -0
- package/dist/runtime/engineBrokerCapabilities.d.ts.map +1 -1
- package/dist/runtime/engineBrokerCapabilities.js +10 -0
- package/dist/runtime/engineBrokerControlClient.d.ts +12 -1
- package/dist/runtime/engineBrokerControlClient.d.ts.map +1 -1
- package/dist/runtime/engineBrokerControlClient.js +139 -43
- package/dist/runtime/engineBrokerCredentialJournal.d.ts +2 -0
- package/dist/runtime/engineBrokerCredentialJournal.d.ts.map +1 -1
- package/dist/runtime/engineBrokerCredentialJournal.js +9 -0
- package/dist/runtime/engineBrokerInferenceProtocol.d.ts +3 -2
- package/dist/runtime/engineBrokerInferenceProtocol.d.ts.map +1 -1
- package/dist/runtime/engineBrokerInferenceProtocol.js +4 -4
- package/dist/runtime/engineBrokerMcpCallLog.d.ts +11 -0
- package/dist/runtime/engineBrokerMcpCallLog.d.ts.map +1 -1
- package/dist/runtime/engineBrokerMcpCallLog.js +13 -1
- package/dist/runtime/engineBrokerMcpFacade.d.ts +5 -2
- package/dist/runtime/engineBrokerMcpFacade.d.ts.map +1 -1
- package/dist/runtime/engineBrokerMcpFacade.js +5 -2
- package/dist/runtime/engineBrokerNativeClient.js +2 -2
- package/dist/runtime/engineBrokerProtocol.d.ts +42 -10
- package/dist/runtime/engineBrokerProtocol.d.ts.map +1 -1
- package/dist/runtime/engineBrokerProtocol.js +97 -14
- package/dist/runtime/engineBrokerSealLedger.d.ts.map +1 -1
- package/dist/runtime/engineBrokerSealLedger.js +9 -0
- package/dist/runtime/engineBrokerService.d.ts +8 -2
- package/dist/runtime/engineBrokerService.d.ts.map +1 -1
- package/dist/runtime/engineBrokerService.js +95 -37
- package/dist/runtime/engineBrokerServiceCli.js +1 -1
- package/dist/runtime/engineBrokerServiceConfig.d.ts +15 -2
- package/dist/runtime/engineBrokerServiceConfig.d.ts.map +1 -1
- package/dist/runtime/engineBrokerServiceConfig.js +53 -11
- package/dist/runtime/engineBrokerTurnRegistry.d.ts +1 -1
- package/dist/runtime/engineBrokerTurnRegistry.d.ts.map +1 -1
- package/dist/runtime/engineBrokerTurnRegistry.js +7 -6
- package/dist/runtime/engineDispatcher.d.ts +2 -0
- package/dist/runtime/engineDispatcher.d.ts.map +1 -1
- package/dist/runtime/engineDispatcher.js +14 -2
- package/dist/runtime/engineReadiness.js +1 -1
- package/dist/runtime/grokBrokerCredentialAuthority.d.ts +1 -0
- package/dist/runtime/grokBrokerCredentialAuthority.d.ts.map +1 -1
- package/dist/runtime/grokBrokerCredentialAuthority.js +48 -22
- package/dist/runtime/grokBrokerCredentialReader.d.ts +1 -0
- package/dist/runtime/grokBrokerCredentialReader.d.ts.map +1 -1
- package/dist/runtime/grokBrokerCredentialReader.js +2 -1
- package/dist/runtime/grokBrokerProjection.d.ts +26 -2
- package/dist/runtime/grokBrokerProjection.d.ts.map +1 -1
- package/dist/runtime/grokBrokerProjection.js +18 -7
- package/dist/runtime/grokBrokerProviderClock.d.ts +26 -0
- package/dist/runtime/grokBrokerProviderClock.d.ts.map +1 -0
- package/dist/runtime/grokBrokerProviderClock.js +133 -0
- package/dist/runtime/grokBrokerProxy.d.ts +17 -3
- package/dist/runtime/grokBrokerProxy.d.ts.map +1 -1
- package/dist/runtime/grokBrokerProxy.js +73 -9
- package/dist/runtime/grokBrokerProxyJson.d.ts +10 -0
- package/dist/runtime/grokBrokerProxyJson.d.ts.map +1 -0
- package/dist/runtime/grokBrokerProxyJson.js +31 -0
- package/dist/runtime/grokBrokerProxyRequest.d.ts.map +1 -1
- package/dist/runtime/grokBrokerProxyRequest.js +17 -6
- package/dist/runtime/grokBrokerRealmLease.js +1 -1
- package/dist/runtime/grokBrokerRefresh.js +1 -1
- package/dist/runtime/grokBrokerRunBudget.d.ts +20 -0
- package/dist/runtime/grokBrokerRunBudget.d.ts.map +1 -0
- package/dist/runtime/grokBrokerRunBudget.js +85 -0
- package/dist/runtime/grokBrokerSlot.d.ts +6 -0
- package/dist/runtime/grokBrokerSlot.d.ts.map +1 -0
- package/dist/runtime/grokBrokerSlot.js +21 -0
- package/dist/runtime/grokBrokerTaskClock.d.ts +32 -0
- package/dist/runtime/grokBrokerTaskClock.d.ts.map +1 -0
- package/dist/runtime/grokBrokerTaskClock.js +120 -0
- package/dist/runtime/grokBrokerTurnMeter.d.ts +55 -3
- package/dist/runtime/grokBrokerTurnMeter.d.ts.map +1 -1
- package/dist/runtime/grokBrokerTurnMeter.js +90 -4
- package/dist/runtime/grokBrokerWorkerConfig.d.ts +5 -2
- package/dist/runtime/grokBrokerWorkerConfig.d.ts.map +1 -1
- package/dist/runtime/grokBrokerWorkerConfig.js +22 -3
- package/dist/runtime/grokEngineBroker.d.ts +43 -3
- package/dist/runtime/grokEngineBroker.d.ts.map +1 -1
- package/dist/runtime/grokEngineBroker.js +159 -71
- package/dist/runtime/grokEngineBrokerTurn.d.ts +32 -8
- package/dist/runtime/grokEngineBrokerTurn.d.ts.map +1 -1
- package/dist/runtime/grokEngineBrokerTurn.js +204 -21
- package/dist/runtime/grokEngineBrokerTurnEnd.d.ts +88 -0
- package/dist/runtime/grokEngineBrokerTurnEnd.d.ts.map +1 -0
- package/dist/runtime/grokEngineBrokerTurnEnd.js +148 -0
- package/dist/runtime/grokInferenceProxy.d.ts.map +1 -1
- package/dist/runtime/grokInferenceProxy.js +3 -0
- package/dist/runtime/grokSubscriptionRealm.js +1 -1
- package/dist/runtime/grokWorkerAttestation.d.ts +12 -5
- package/dist/runtime/grokWorkerAttestation.d.ts.map +1 -1
- package/dist/runtime/grokWorkerAttestation.js +5 -3
- package/dist/runtime/grokWorkerHomeAttestation.d.ts +51 -5
- package/dist/runtime/grokWorkerHomeAttestation.d.ts.map +1 -1
- package/dist/runtime/grokWorkerHomeAttestation.js +103 -8
- package/dist/runtime/grokWorkerSkills.d.ts +86 -0
- package/dist/runtime/grokWorkerSkills.d.ts.map +1 -0
- package/dist/runtime/grokWorkerSkills.js +135 -0
- package/dist/runtime/index.d.ts +10 -2
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/runtime/index.js +8 -1
- package/dist/runtime/native/daimon-engine-broker +0 -0
- package/dist/runtime/organizationRuntime.d.ts +3 -0
- package/dist/runtime/organizationRuntime.d.ts.map +1 -1
- package/dist/runtime/organizationRuntimeAdmission.d.ts +6 -0
- package/dist/runtime/organizationRuntimeAdmission.d.ts.map +1 -0
- package/dist/runtime/organizationRuntimeAdmission.js +22 -0
- package/dist/runtime/organizationRuntimeControl.d.ts +9 -0
- package/dist/runtime/organizationRuntimeControl.d.ts.map +1 -1
- package/dist/runtime/organizationRuntimeControl.js +72 -24
- package/dist/runtime/organizationRuntimeHost.d.ts.map +1 -1
- package/dist/runtime/organizationRuntimeHost.js +13 -4
- package/dist/runtime/organizationRuntimeParsing.d.ts.map +1 -1
- package/dist/runtime/organizationRuntimeParsing.js +4 -1
- package/dist/runtime/organizationRuntimeReadiness.d.ts.map +1 -1
- package/dist/runtime/organizationRuntimeReadiness.js +8 -0
- package/dist/runtime/organizationRuntimeRecording.d.ts +7 -0
- package/dist/runtime/organizationRuntimeRecording.d.ts.map +1 -0
- package/dist/runtime/organizationRuntimeRecording.js +58 -0
- package/dist/runtime/physicalReadiness.d.ts +12 -0
- package/dist/runtime/physicalReadiness.d.ts.map +1 -1
- package/dist/runtime/physicalReadiness.js +43 -9
- package/dist/runtime/productionAgentTools.d.ts.map +1 -1
- package/dist/runtime/productionAgentTools.js +13 -5
- package/dist/runtime/relocateRuntimeConfig.d.ts +14 -0
- package/dist/runtime/relocateRuntimeConfig.d.ts.map +1 -0
- package/dist/runtime/relocateRuntimeConfig.js +38 -0
- package/dist/runtime/schedule.d.ts.map +1 -1
- package/dist/runtime/schedule.js +12 -7
- package/dist/runtime/storeCoordination.d.ts +10 -0
- package/dist/runtime/storeCoordination.d.ts.map +1 -1
- package/dist/runtime/storeCoordination.js +23 -0
- package/dist/runtime/taskClock.d.ts +42 -0
- package/dist/runtime/taskClock.d.ts.map +1 -0
- package/dist/runtime/taskClock.js +102 -0
- package/dist/runtime/taskClockProcess.d.ts +17 -0
- package/dist/runtime/taskClockProcess.d.ts.map +1 -0
- package/dist/runtime/taskClockProcess.js +58 -0
- package/dist/runtime/turnRequestLedger.d.ts +0 -5
- package/dist/runtime/turnRequestLedger.d.ts.map +1 -1
- package/dist/runtime/turnRequestLedger.js +23 -21
- package/dist/runtime/wakeAcceptanceClaimDiagnostics.d.ts +34 -0
- package/dist/runtime/wakeAcceptanceClaimDiagnostics.d.ts.map +1 -0
- package/dist/runtime/wakeAcceptanceClaimDiagnostics.js +79 -0
- package/dist/runtime/wakeAcceptanceIndex.d.ts +55 -0
- package/dist/runtime/wakeAcceptanceIndex.d.ts.map +1 -0
- package/dist/runtime/wakeAcceptanceIndex.js +158 -0
- package/dist/runtime/wakeAcceptanceProcessIdentity.d.ts +9 -1
- package/dist/runtime/wakeAcceptanceProcessIdentity.d.ts.map +1 -1
- package/dist/runtime/wakeAcceptanceProcessIdentity.js +5 -1
- package/dist/runtime/wakeAcceptanceRecord.d.ts +2 -0
- package/dist/runtime/wakeAcceptanceRecord.d.ts.map +1 -1
- package/dist/runtime/wakeAcceptanceRecord.js +4 -2
- package/dist/runtime/wakeAcceptanceRetention.d.ts +25 -1
- package/dist/runtime/wakeAcceptanceRetention.d.ts.map +1 -1
- package/dist/runtime/wakeAcceptanceRetention.js +28 -2
- package/dist/runtime/wakeAcceptanceStore.d.ts +8 -8
- package/dist/runtime/wakeAcceptanceStore.d.ts.map +1 -1
- package/dist/runtime/wakeAcceptanceStore.js +66 -63
- package/dist/runtime/wakeFuse.d.ts +58 -3
- package/dist/runtime/wakeFuse.d.ts.map +1 -1
- package/dist/runtime/wakeFuse.js +106 -7
- package/dist/runtime/wakeMomentFs.d.ts +27 -0
- package/dist/runtime/wakeMomentFs.d.ts.map +1 -0
- package/dist/runtime/wakeMomentFs.js +223 -0
- package/dist/runtime/wakeMomentHistory.d.ts +26 -0
- package/dist/runtime/wakeMomentHistory.d.ts.map +1 -0
- package/dist/runtime/wakeMomentHistory.js +109 -0
- package/dist/runtime/wakeMomentIo.d.ts +22 -0
- package/dist/runtime/wakeMomentIo.d.ts.map +1 -0
- package/dist/runtime/wakeMomentIo.js +114 -0
- package/dist/runtime/wakeMomentRecorder.d.ts +33 -0
- package/dist/runtime/wakeMomentRecorder.d.ts.map +1 -0
- package/dist/runtime/wakeMomentRecorder.js +81 -0
- package/dist/runtime/wakeMomentRetention.d.ts +7 -0
- package/dist/runtime/wakeMomentRetention.d.ts.map +1 -0
- package/dist/runtime/wakeMomentRetention.js +143 -0
- package/dist/runtime/wakeMomentRows.d.ts +8 -0
- package/dist/runtime/wakeMomentRows.d.ts.map +1 -0
- package/dist/runtime/wakeMomentRows.js +66 -0
- package/dist/runtime/wakeMomentSnapshot.d.ts +56 -0
- package/dist/runtime/wakeMomentSnapshot.d.ts.map +1 -0
- package/dist/runtime/wakeMomentSnapshot.js +248 -0
- package/package.json +4 -4
|
@@ -1,25 +1,36 @@
|
|
|
1
|
+
import { brokerTaskClockFileFor } from "./grokBrokerSlot.js";
|
|
2
|
+
import { brokerClockObservation, parseBrokerProcessClock, prepareBrokerTaskClock, writeBrokerTaskClock } from "./grokBrokerTaskClock.js";
|
|
3
|
+
import { GrokBrokerProviderClock } from "./grokBrokerProviderClock.js";
|
|
1
4
|
import { createHash, randomUUID } from "node:crypto";
|
|
5
|
+
import { engineBrokerCapabilityTtlMs } from "./engineBrokerCapabilities.js";
|
|
2
6
|
import { decodeGrokHeadlessTurn } from "../pi/grokHeadlessResult.js";
|
|
3
7
|
import { decodeGrokStreamUsage } from "../pi/grokStreamUsage.js";
|
|
4
|
-
import { ENGINE_BROKER_VERSION } from "./engineBrokerProtocol.js";
|
|
8
|
+
import { ENGINE_BROKER_VERSION, ENGINE_BROKER_TASK_CLOCK_RESET_FAILED } from "./engineBrokerProtocol.js";
|
|
5
9
|
import { lowerEngineBrokerTurnLimits, mapGrokReportedModel } from "./engineBrokerTurnAccounting.js";
|
|
6
10
|
import { NativeBrokerTurnFailure } from "./engineBrokerNativeClient.js";
|
|
7
11
|
import { engineBrokerRequestLedgerPathFor, engineBrokerSealLedgerPathFor } from "./engineBrokerServiceConfig.js";
|
|
8
12
|
import { ensureBrokerTurnLedgered } from "./grokEngineBrokerLedger.js";
|
|
9
13
|
import { finishBrokerTurnWithUsage } from "./grokEngineBrokerMetering.js";
|
|
10
14
|
import { GrokBrokerTurnMeter } from "./grokBrokerTurnMeter.js";
|
|
15
|
+
import { awaitGrokWorkerIdentityEmpty, findGrokWorkerProcess, GROK_WORKER_REAP_WAIT_MS, grokWorkerIdentityEmpty, watchGrokBrokerTurnEnd } from "./grokEngineBrokerTurnEnd.js";
|
|
11
16
|
import { GrokWorkerAttestationFailure } from "./grokWorkerAttestation.js";
|
|
12
17
|
export class EngineBrokerTurnFailure extends Error {
|
|
13
18
|
code;
|
|
14
19
|
diagnostic;
|
|
15
20
|
accounting;
|
|
16
21
|
mcpCalls;
|
|
17
|
-
|
|
22
|
+
shellClock;
|
|
23
|
+
providerClock;
|
|
24
|
+
cleanupDiagnostic;
|
|
25
|
+
constructor(code, diagnostic, accounting, mcpCalls, shellClock, providerClock, cleanupDiagnostic) {
|
|
18
26
|
super("engine broker turn failed");
|
|
19
27
|
this.code = code;
|
|
20
28
|
this.diagnostic = diagnostic;
|
|
21
29
|
this.accounting = accounting;
|
|
22
30
|
this.mcpCalls = mcpCalls;
|
|
31
|
+
this.shellClock = shellClock;
|
|
32
|
+
this.providerClock = providerClock;
|
|
33
|
+
this.cleanupDiagnostic = cleanupDiagnostic;
|
|
23
34
|
}
|
|
24
35
|
}
|
|
25
36
|
const limitReasonFor = { tokens: "token_ceiling", requests: "request_ceiling", timeout: "wake_timeout" };
|
|
@@ -38,37 +49,91 @@ const usageOf = (usage) => ({ input: usage.input, cacheRead: usage.cacheRead, ca
|
|
|
38
49
|
* metered through {@link finishBrokerTurnWithUsage}; a replayed turn returns its
|
|
39
50
|
* sealed accounting before any of this runs and never meters again.
|
|
40
51
|
*/
|
|
41
|
-
export async function runGrokEngineBrokerTurn(deps, registration, wakeId, prompt, mcpEndpoint, signal, overrides) {
|
|
52
|
+
export async function runGrokEngineBrokerTurn(deps, registration, wakeId, prompt, mcpEndpoint, signal, overrides, processClock) {
|
|
42
53
|
const { agentId } = registration, declared = registration.model.model;
|
|
54
|
+
const writeClock = deps.writeTaskClock ?? ((value) => writeBrokerTaskClock(value, brokerTaskClockFileFor(registration.slot)));
|
|
43
55
|
let limits;
|
|
44
56
|
try {
|
|
45
57
|
limits = lowerEngineBrokerTurnLimits(registration.limits, overrides);
|
|
58
|
+
if (processClock !== undefined)
|
|
59
|
+
parseBrokerProcessClock(processClock);
|
|
46
60
|
}
|
|
47
61
|
catch {
|
|
48
62
|
throw new EngineBrokerTurnFailure("invalid_request", undefined, { outcome: "failed", usage: null, model: declared, requests: 0, limitReason: "none" });
|
|
49
63
|
}
|
|
50
|
-
const turnId = createHash("sha256").update(`${agentId}\0${wakeId}`).digest("hex");
|
|
51
|
-
const request = { version: ENGINE_BROKER_VERSION, kind: "start_turn", requestId: randomUUID(), turnId, agentId, wakeId, prompt, mcpEndpoint, ...(overrides === undefined ? {} : { limits: overrides }) };
|
|
64
|
+
const turnId = createHash("sha256").update(`${registration.slot}\0${agentId}\0${wakeId}`).digest("hex");
|
|
65
|
+
const request = { version: ENGINE_BROKER_VERSION, kind: "start_turn", requestId: randomUUID(), turnId, slot: registration.slot, agentId, wakeId, prompt, mcpEndpoint, ...(overrides === undefined ? {} : { limits: overrides }), ...(processClock === undefined ? {} : { processClock }) };
|
|
52
66
|
const begun = await deps.turns.begin(request, declared);
|
|
53
67
|
const metering = { usageLedgerPath: registration.usageLedgerPath, requestLedgerPath: engineBrokerRequestLedgerPathFor(registration.usageLedgerPath), sealLedgerPath: engineBrokerSealLedgerPathFor(registration.usageLedgerPath), agentId, wakeId };
|
|
54
68
|
if (begun !== "start") {
|
|
55
69
|
await ensureBrokerTurnLedgered(begun.ledger, turnId, metering);
|
|
56
70
|
return replay(begun.replay);
|
|
57
71
|
}
|
|
72
|
+
const clock = processClock === undefined ? undefined : new GrokBrokerProviderClock(processClock);
|
|
58
73
|
const controller = new AbortController();
|
|
74
|
+
let worker;
|
|
59
75
|
const meter = new GrokBrokerTurnMeter(limits, () => controller.abort());
|
|
60
|
-
|
|
76
|
+
let cancelledByCaller = false;
|
|
77
|
+
const onAbort = () => { cancelledByCaller = true; controller.abort(); };
|
|
61
78
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
62
|
-
if (signal?.aborted)
|
|
79
|
+
if (signal?.aborted) {
|
|
80
|
+
cancelledByCaller = true;
|
|
63
81
|
controller.abort();
|
|
82
|
+
}
|
|
64
83
|
const timer = setTimeout(() => meter.trip("timeout"), limits.timeoutMs);
|
|
65
84
|
timer.unref?.();
|
|
85
|
+
// What was in flight, and which worker held the final reply, at the instant the
|
|
86
|
+
// turn ended — read before killing the worker can close its calls or empty its
|
|
87
|
+
// identity. A final reply the 1 s watchdog poll never saw is caught here too.
|
|
88
|
+
let inFlightAtEnd = false;
|
|
89
|
+
controller.signal.addEventListener("abort", () => {
|
|
90
|
+
let mcp = 0;
|
|
91
|
+
try {
|
|
92
|
+
mcp = deps.mcp.activity?.(turnId)?.inFlight ?? 0;
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
mcp = 0;
|
|
96
|
+
}
|
|
97
|
+
inFlightAtEnd = mcp > 0 || meter.busy();
|
|
98
|
+
if (meter.finalReply() !== undefined)
|
|
99
|
+
worker ??= (deps.observeWorker ?? findGrokWorkerProcess)(registration.workerUid).catch(() => undefined);
|
|
100
|
+
}, { once: true });
|
|
101
|
+
let shellClock;
|
|
102
|
+
let resetClock = deps.clockedSlot === true;
|
|
103
|
+
let nativeStarted = false;
|
|
104
|
+
let workerReaped = false;
|
|
105
|
+
let sealedFailure;
|
|
66
106
|
let nativeDiagnostic, attested = false, output, rejected = false, sealed;
|
|
107
|
+
// Set when the broker itself ended the turn (`grokEngineBrokerTurnEnd.ts`); the worker seen at the final reply names the turn it ended.
|
|
108
|
+
let endedBy;
|
|
109
|
+
let watchdog, isolationGuard;
|
|
67
110
|
try {
|
|
68
|
-
const
|
|
111
|
+
const isolation = await deps.prepareIsolation(registration);
|
|
112
|
+
isolationGuard = isolation.guard;
|
|
113
|
+
// Publication may throw after rename. Claim cleanup before attempting it.
|
|
114
|
+
resetClock ||= isolation.configVariant === "clocked";
|
|
115
|
+
const coverage = await prepareBrokerTaskClock(isolation.configVariant, processClock, writeClock);
|
|
116
|
+
if (isolation.configVariant === "clocked" || processClock !== undefined)
|
|
117
|
+
shellClock = coverage;
|
|
69
118
|
deps.proxy.registerIsolationGuard(turnId, isolationGuard);
|
|
70
|
-
deps.proxy.registerTurn(turnId, { policy: registration.model, meter });
|
|
71
|
-
const
|
|
119
|
+
deps.proxy.registerTurn(turnId, { policy: registration.model, meter, clock });
|
|
120
|
+
const capabilityTtlMs = engineBrokerCapabilityTtlMs(limits.timeoutMs);
|
|
121
|
+
const providerCapability = deps.proxy.capabilities.issue(agentId, turnId, capabilityTtlMs), mcpCapability = deps.mcp.register(agentId, turnId, mcpEndpoint, capabilityTtlMs);
|
|
122
|
+
watchdog = watchGrokBrokerTurnEnd({
|
|
123
|
+
meter, activity: () => deps.mcp.activity?.(turnId), signal: controller.signal, ...(deps.turnEnd === undefined ? {} : { timing: deps.turnEnd }),
|
|
124
|
+
// The worker is read once, as soon as it has made a request — long before
|
|
125
|
+
// anything can end the turn and reap it — so a final reply is never left
|
|
126
|
+
// without the worker that gave it.
|
|
127
|
+
onPoll: () => { if (worker === undefined && meter.snapshot().requests > 0)
|
|
128
|
+
worker = (deps.observeWorker ?? findGrokWorkerProcess)(registration.workerUid).catch(() => undefined); },
|
|
129
|
+
onFinalReply: () => { worker ??= (deps.observeWorker ?? findGrokWorkerProcess)(registration.workerUid).catch(() => undefined); },
|
|
130
|
+
// An idle stall is a deadline of its own: it trips the meter as a timeout, so it seals like one.
|
|
131
|
+
onEnd: (reason) => { endedBy = reason; if (reason === "idle")
|
|
132
|
+
meter.trip("timeout");
|
|
133
|
+
else
|
|
134
|
+
controller.abort(); }
|
|
135
|
+
});
|
|
136
|
+
nativeStarted = true;
|
|
72
137
|
const result = await deps.runNative({ slot: registration.slot, requestId: request.requestId, turnId, agentId, wakeId, prompt, providerCapability, mcpCapability }, controller.signal);
|
|
73
138
|
nativeDiagnostic = result.diagnostic;
|
|
74
139
|
output = result.text;
|
|
@@ -86,8 +151,16 @@ export async function runGrokEngineBrokerTurn(deps, registration, wakeId, prompt
|
|
|
86
151
|
throw new Error("engine broker turn limit reached");
|
|
87
152
|
const usage = decoded.usage === undefined ? streamOrMeterUsage(stream, snapshot) : usageOf(decoded.usage);
|
|
88
153
|
const accounting = { outcome: "completed", usage, model: declared, requests: requestCount(stream, snapshot), limitReason: "none" };
|
|
89
|
-
const
|
|
90
|
-
const
|
|
154
|
+
const observation = brokerClockObservation(shellClock, clock?.snapshot());
|
|
155
|
+
const completed = {
|
|
156
|
+
version: request.version, kind: "completed", requestId: request.requestId, turnId,
|
|
157
|
+
text: decoded.text, workerPid: result.workerPid, workerUid: result.workerUid,
|
|
158
|
+
workerStartTime: result.startTicks.toString(), ...observation, ...accounting
|
|
159
|
+
};
|
|
160
|
+
const result_ = {
|
|
161
|
+
text: completed.text, workerPid: completed.workerPid, workerUid: completed.workerUid,
|
|
162
|
+
workerStartTime: completed.workerStartTime, ...observation, ...accounting
|
|
163
|
+
};
|
|
91
164
|
await finishBrokerTurnWithUsage(deps.turns, request, completed, metering, { notionalUsd: decoded.usage?.notionalUsd ?? 0, complete: decoded.usage?.complete ?? false, estimatedRequests: snapshot.estimatedRequests, requests: requestRows(stream, snapshot), ...(stream.sessionId === undefined ? {} : { session: stream.sessionId }) }, () => { sealed = result_; });
|
|
92
165
|
return result_;
|
|
93
166
|
}
|
|
@@ -95,6 +168,34 @@ export async function runGrokEngineBrokerTurn(deps, registration, wakeId, prompt
|
|
|
95
168
|
// Once the completed record is published it is the durable truth: anything
|
|
96
169
|
// failing after that (metering) must neither re-seal the turn as failed nor
|
|
97
170
|
// append a second row.
|
|
171
|
+
if (sealed !== undefined)
|
|
172
|
+
return sealed;
|
|
173
|
+
watchdog?.stop();
|
|
174
|
+
// A turn this broker aborted is sealed only once its worker identity is
|
|
175
|
+
// empty (bounded): the agent's next turn must not start a worker the
|
|
176
|
+
// previous launcher handler's identity-wide reap would kill.
|
|
177
|
+
if (controller.signal.aborted) {
|
|
178
|
+
workerReaped = await awaitGrokWorkerIdentityEmpty(registration.workerUid, deps.workerReapWaitMs ?? GROK_WORKER_REAP_WAIT_MS, deps.workerIdentityEmpty ?? ((uid) => grokWorkerIdentityEmpty(uid, undefined, resetClock)));
|
|
179
|
+
}
|
|
180
|
+
// Not aborted (an output-limit failure, say): nothing was killed, so the live view is the true one.
|
|
181
|
+
if (!controller.signal.aborted) {
|
|
182
|
+
let mcp = 0;
|
|
183
|
+
try {
|
|
184
|
+
mcp = deps.mcp.activity?.(turnId)?.inFlight ?? 0;
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
mcp = 0;
|
|
188
|
+
}
|
|
189
|
+
inFlightAtEnd = mcp > 0 || meter.busy();
|
|
190
|
+
}
|
|
191
|
+
const vetoed = cancelledByCaller || rejected || (output !== undefined && !attested) || inFlightAtEnd;
|
|
192
|
+
// Finished work is completed work. A worker that gave its final reply and was
|
|
193
|
+
// then ended by the broker, refused its stdout by the launcher's output bound,
|
|
194
|
+
// or left idle into the deadline did its job; only its exit went wrong.
|
|
195
|
+
const finished = vetoed ? undefined : await completedFromFinalReply(deps, registration, request, meter, error, endedBy, worker, isolationGuard, metering, (value) => { sealed = value; }, shellClock, clock).catch(() => undefined);
|
|
196
|
+
if (finished !== undefined)
|
|
197
|
+
return finished;
|
|
198
|
+
// Sealed completed but its ledger append failed: the published record is the truth, exactly as on the success path.
|
|
98
199
|
if (sealed !== undefined)
|
|
99
200
|
return sealed;
|
|
100
201
|
const snapshot = meter.snapshot();
|
|
@@ -112,27 +213,109 @@ export async function runGrokEngineBrokerTurn(deps, registration, wakeId, prompt
|
|
|
112
213
|
catch {
|
|
113
214
|
mcpCalls = undefined;
|
|
114
215
|
}
|
|
115
|
-
const
|
|
216
|
+
const observation = brokerClockObservation(shellClock, clock?.snapshot());
|
|
217
|
+
const failed = {
|
|
218
|
+
version: request.version, kind: "failed", requestId: request.requestId, turnId, code,
|
|
219
|
+
...observation, ...(diagnostic ? { diagnostic } : {}),
|
|
220
|
+
...(mcpCalls === undefined ? {} : { mcpCalls }), ...accounting
|
|
221
|
+
};
|
|
116
222
|
const reason = snapshot.limitReason !== "none" ? limitReasonFor[snapshot.limitReason] : rejected ? "turn_rejected" : "unknown";
|
|
117
|
-
|
|
118
|
-
|
|
223
|
+
const failure = new EngineBrokerTurnFailure(code, diagnostic, accounting, mcpCalls, shellClock, observation.providerClock);
|
|
224
|
+
await finishBrokerTurnWithUsage(deps.turns, request, failed, metering, {
|
|
225
|
+
notionalUsd: 0, complete: false, reason, estimatedRequests: snapshot.estimatedRequests,
|
|
226
|
+
requests: requestRows(stream, snapshot), ...(stream?.sessionId === undefined ? {} : { session: stream.sessionId })
|
|
227
|
+
}, () => { sealedFailure = failure; });
|
|
228
|
+
throw failure;
|
|
119
229
|
}
|
|
120
230
|
finally {
|
|
231
|
+
watchdog?.stop();
|
|
121
232
|
clearTimeout(timer);
|
|
122
233
|
signal?.removeEventListener("abort", onAbort);
|
|
123
234
|
meter.abortInFlight();
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
235
|
+
try {
|
|
236
|
+
deps.proxy.revokeTurn(turnId);
|
|
237
|
+
deps.proxy.revokeIsolationGuard(turnId);
|
|
238
|
+
deps.proxy.capabilities.revoke(turnId);
|
|
239
|
+
deps.mcp.revoke(turnId);
|
|
240
|
+
}
|
|
241
|
+
finally {
|
|
242
|
+
if (resetClock) {
|
|
243
|
+
try {
|
|
244
|
+
// A disconnected launcher client can settle before its worker is reaped.
|
|
245
|
+
// Never change the clock of a surviving shell, or release the slot as healthy.
|
|
246
|
+
if (nativeStarted && !workerReaped) {
|
|
247
|
+
workerReaped = await awaitGrokWorkerIdentityEmpty(registration.workerUid, deps.workerReapWaitMs ?? GROK_WORKER_REAP_WAIT_MS, deps.workerIdentityEmpty ?? ((uid) => grokWorkerIdentityEmpty(uid, undefined, true)));
|
|
248
|
+
if (!workerReaped)
|
|
249
|
+
throw new Error("Grok broker task clock worker did not terminate");
|
|
250
|
+
}
|
|
251
|
+
await writeClock(undefined);
|
|
252
|
+
}
|
|
253
|
+
catch (error) {
|
|
254
|
+
deps.taskClockFailed?.();
|
|
255
|
+
// Cleanup cannot replace a published outcome. Report the live fence
|
|
256
|
+
// on the wire without rewriting the seal or its accounting.
|
|
257
|
+
if (sealed !== undefined)
|
|
258
|
+
return { ...sealed, cleanupDiagnostic: ENGINE_BROKER_TASK_CLOCK_RESET_FAILED };
|
|
259
|
+
if (sealedFailure !== undefined)
|
|
260
|
+
throw new EngineBrokerTurnFailure(sealedFailure.code, sealedFailure.diagnostic, sealedFailure.accounting, sealedFailure.mcpCalls, sealedFailure.shellClock, sealedFailure.providerClock, ENGINE_BROKER_TASK_CLOCK_RESET_FAILED);
|
|
261
|
+
throw new Error("Grok broker task clock cleanup failed; restart required", { cause: error });
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
128
265
|
}
|
|
129
266
|
}
|
|
130
267
|
function replay(response) {
|
|
131
268
|
const accounting = { outcome: response.outcome, usage: response.usage, model: response.model, requests: response.requests, limitReason: response.limitReason };
|
|
132
269
|
if (response.kind === "completed")
|
|
133
|
-
return {
|
|
270
|
+
return {
|
|
271
|
+
text: response.text, workerPid: response.workerPid, workerUid: response.workerUid,
|
|
272
|
+
workerStartTime: response.workerStartTime,
|
|
273
|
+
...brokerClockObservation(response.shellClock, response.providerClock), ...accounting, outcome: "completed"
|
|
274
|
+
};
|
|
134
275
|
const code = response.code === "turn_conflict" || response.code === "unavailable" ? "engine_failed" : response.code;
|
|
135
|
-
throw new EngineBrokerTurnFailure(code, response.diagnostic, accounting, response.mcpCalls);
|
|
276
|
+
throw new EngineBrokerTurnFailure(code, response.diagnostic, accounting, response.mcpCalls, response.shellClock, response.providerClock);
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* The completed seal for a turn whose model gave its final reply but whose
|
|
280
|
+
* worker never delivered stdout: the broker ended it (`final_reply`), the
|
|
281
|
+
* launcher's output bound refused its stream (`output_limit`, which publishes
|
|
282
|
+
* nothing), or it idled into the wall-clock deadline. The reply is the one the
|
|
283
|
+
* proxy saw, already bounded with a marker (`boundGrokFinalReply`), the usage is
|
|
284
|
+
* what the proxy metered, and the worker is the one the launcher reported or
|
|
285
|
+
* the broker observed while it was alive. `undefined` — and the ordinary
|
|
286
|
+
* failure seal — whenever any of that is missing, the isolation attestation
|
|
287
|
+
* fails, or the failure was anything else (a token or request limit, a client
|
|
288
|
+
* cancellation, an engine fault before the reply). The caller vetoes it outright
|
|
289
|
+
* for a caller cancellation, a rejected or unattested stdout, and a model
|
|
290
|
+
* request or MCP tool call still in flight.
|
|
291
|
+
*/
|
|
292
|
+
async function completedFromFinalReply(deps, registration, request, meter, error, endedBy, worker, isolationGuard, metering, onSealed, shellClock, clock) {
|
|
293
|
+
const final = meter.finalReply(), snapshot = meter.snapshot();
|
|
294
|
+
if (final === undefined || isolationGuard === undefined || snapshot.usage === null)
|
|
295
|
+
return undefined;
|
|
296
|
+
// A token or request limit is never finished work, whatever else failed beside it.
|
|
297
|
+
if (snapshot.limitReason === "tokens" || snapshot.limitReason === "requests")
|
|
298
|
+
return undefined;
|
|
299
|
+
const outputLimited = error instanceof NativeBrokerTurnFailure && error.diagnostic.failureClass === "output_limit" && error.diagnostic.workerPid > 0;
|
|
300
|
+
if (!(endedBy === "final_reply" || outputLimited || (snapshot.limitReason === "timeout" && endedBy !== "idle")))
|
|
301
|
+
return undefined;
|
|
302
|
+
const identity = outputLimited ? { pid: error.diagnostic.workerPid, uid: error.diagnostic.workerUid, startTicks: error.diagnostic.startTicks } : await (async () => { const seen = (await worker) ?? await (deps.observeWorker ?? findGrokWorkerProcess)(registration.workerUid).catch(() => undefined); return seen === undefined ? undefined : { ...seen, uid: registration.workerUid }; })();
|
|
303
|
+
if (identity === undefined || identity.uid !== registration.workerUid)
|
|
304
|
+
return undefined;
|
|
305
|
+
await isolationGuard();
|
|
306
|
+
const accounting = { outcome: "completed", usage: snapshot.usage, model: registration.model.model, requests: snapshot.requests, limitReason: "none" };
|
|
307
|
+
const observation = brokerClockObservation(shellClock, clock?.snapshot());
|
|
308
|
+
const completed = {
|
|
309
|
+
version: request.version, kind: "completed", requestId: request.requestId, turnId: request.turnId,
|
|
310
|
+
text: final.text, workerPid: identity.pid, workerUid: identity.uid, workerStartTime: identity.startTicks,
|
|
311
|
+
...observation, ...accounting
|
|
312
|
+
};
|
|
313
|
+
const result = {
|
|
314
|
+
text: completed.text, workerPid: completed.workerPid, workerUid: completed.workerUid,
|
|
315
|
+
workerStartTime: completed.workerStartTime, ...observation, ...accounting
|
|
316
|
+
};
|
|
317
|
+
await finishBrokerTurnWithUsage(deps.turns, request, completed, metering, { notionalUsd: 0, complete: false, estimatedRequests: snapshot.estimatedRequests, requests: requestRows(undefined, snapshot) }, () => onSealed(result));
|
|
318
|
+
return result;
|
|
136
319
|
}
|
|
137
320
|
/** The proxy saw every forwarded request; the stream is the fallback when no request crossed this proxy. */
|
|
138
321
|
const requestCount = (stream, snapshot) => snapshot.requests > 0 ? snapshot.requests : stream?.requests.length ?? 0;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { EngineBrokerMcpActivity } from "./engineBrokerMcpCallLog.js";
|
|
2
|
+
import type { GrokBrokerTurnMeter } from "./grokBrokerTurnMeter.js";
|
|
3
|
+
/**
|
|
4
|
+
* When a brokered turn is over, decided by the broker rather than by the
|
|
5
|
+
* worker's exit.
|
|
6
|
+
*
|
|
7
|
+
* Grok 1.0.34 answers, logs `handle_prompt.done ok:true`, ends its session
|
|
8
|
+
* `turn_ended completed` — and then does not exit. Production measured it: 61
|
|
9
|
+
* of 128 turns over two days sat 8–27 minutes after their last model request
|
|
10
|
+
* until the wall-clock limit killed them, and each was then sealed
|
|
11
|
+
* `failed/timeout` with its work already done. Every one still had its MCP GET
|
|
12
|
+
* tunnel open, but that is a symptom: against the real 1.0.34 binary the tunnel
|
|
13
|
+
* held open, closed, refused (405) or absent all exit ~55 ms after `result`,
|
|
14
|
+
* and Grok reopens a closed tunnel within milliseconds, so closing it cannot
|
|
15
|
+
* end a worker. The launcher only publishes a worker's output when the worker
|
|
16
|
+
* exits, so waiting for the exit made the deadline the only way a turn could
|
|
17
|
+
* end.
|
|
18
|
+
*
|
|
19
|
+
* The proxy sees every model answer, so the broker knows when the model has
|
|
20
|
+
* given its final reply: a successful response that called no tool and
|
|
21
|
+
* stopped. From that point:
|
|
22
|
+
*
|
|
23
|
+
* 1. `onFinalReply` runs once — the broker notes the worker it will end while
|
|
24
|
+
* that worker is certainly still alive.
|
|
25
|
+
* 2. After {@link GROK_BROKER_TURN_END}`.finalGraceMs` with no new model request
|
|
26
|
+
* and no tool call in flight, `onEnd("final_reply")` runs: the broker ends
|
|
27
|
+
* the worker and seals the turn completed with that reply.
|
|
28
|
+
*
|
|
29
|
+
* A new model request clears the final reply (the meter forgets it on admit),
|
|
30
|
+
* so a model that keeps going is never cut off. Independently, a turn with no
|
|
31
|
+
* model request and no tool call in flight and no activity of either kind for
|
|
32
|
+
* `idleMs` ends as `onEnd("idle")` — the backstop for a worker that stalls
|
|
33
|
+
* without ever answering. Shell tools Grok runs itself are invisible here, so
|
|
34
|
+
* the idle bound is minutes, not seconds.
|
|
35
|
+
*/
|
|
36
|
+
export type GrokBrokerTurnEndTiming = Readonly<{
|
|
37
|
+
finalGraceMs: number;
|
|
38
|
+
idleMs: number;
|
|
39
|
+
pollMs: number;
|
|
40
|
+
}>;
|
|
41
|
+
export declare const GROK_BROKER_TURN_END: GrokBrokerTurnEndTiming;
|
|
42
|
+
/** How long a turn the broker aborted waits for its worker identity to empty before it is sealed. */
|
|
43
|
+
export declare const GROK_WORKER_REAP_WAIT_MS = 10000;
|
|
44
|
+
export type GrokBrokerTurnEndReason = "final_reply" | "idle";
|
|
45
|
+
export declare function watchGrokBrokerTurnEnd(input: Readonly<{
|
|
46
|
+
meter: Pick<GrokBrokerTurnMeter, "finalReply" | "busy" | "lastActivityAt">;
|
|
47
|
+
activity: () => EngineBrokerMcpActivity | undefined;
|
|
48
|
+
onFinalReply: () => void;
|
|
49
|
+
/** Every poll, before any decision: the broker records the worker while it is certainly alive. */
|
|
50
|
+
onPoll?: () => void;
|
|
51
|
+
onEnd: (reason: GrokBrokerTurnEndReason) => void;
|
|
52
|
+
timing?: GrokBrokerTurnEndTiming;
|
|
53
|
+
now?: () => number;
|
|
54
|
+
/** The turn's own abort: once anything else ended the turn, the watchdog never claims it. */
|
|
55
|
+
signal?: AbortSignal;
|
|
56
|
+
}>): Readonly<{
|
|
57
|
+
stop(): void;
|
|
58
|
+
}>;
|
|
59
|
+
/** The launcher-forked root of a worker identity's process tree: its pid and `/proc/<pid>/stat` start time, as the launcher itself reports them. */
|
|
60
|
+
export type GrokWorkerProcess = Readonly<{
|
|
61
|
+
pid: number;
|
|
62
|
+
startTicks: string;
|
|
63
|
+
}>;
|
|
64
|
+
/**
|
|
65
|
+
* Finds the worker the launcher started for `uid`, read-only from `/proc`.
|
|
66
|
+
*
|
|
67
|
+
* A turn the broker ends early never receives the launcher's result frame (a
|
|
68
|
+
* cancelled client is not written to), yet a completed response must name the
|
|
69
|
+
* worker it came from. One identity carries at most one turn at a time
|
|
70
|
+
* (`native/AGENTS.md`), so a process owned by `uid` whose parent is *not* owned
|
|
71
|
+
* by `uid` is a worker the launcher forked. A previous turn's worker can still
|
|
72
|
+
* be alive beside it — a hung worker is exactly what this module exists for, and
|
|
73
|
+
* its kill is the launcher's — so the newest such root (the latest start time)
|
|
74
|
+
* is this turn's: turns of one identity are sequential.
|
|
75
|
+
*/
|
|
76
|
+
export declare function findGrokWorkerProcess(uid: number, procRoot?: string): Promise<GrokWorkerProcess | undefined>;
|
|
77
|
+
/** No process at all, zombies included, owned by `uid`. */
|
|
78
|
+
export declare function grokWorkerIdentityEmpty(uid: number, procRoot?: string, requireReadable?: boolean): Promise<boolean>;
|
|
79
|
+
/**
|
|
80
|
+
* Waits, bounded, for the worker identity to empty after the broker ended a
|
|
81
|
+
* turn. Ending a turn kills the launcher's client; the launcher handler then
|
|
82
|
+
* kills and reaps the identity on its own schedule. Sealing first would let
|
|
83
|
+
* the agent's next turn start a worker on the same identity that the previous
|
|
84
|
+
* handler's identity-wide reap then kills. The bound keeps a wedged launcher
|
|
85
|
+
* from holding the agent: past it the turn is sealed anyway, as before.
|
|
86
|
+
*/
|
|
87
|
+
export declare function awaitGrokWorkerIdentityEmpty(uid: number, timeoutMs: number, empty?: (uid: number) => Promise<boolean>, pollMs?: number, stableMs?: number): Promise<boolean>;
|
|
88
|
+
//# sourceMappingURL=grokEngineBrokerTurnEnd.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grokEngineBrokerTurnEnd.d.ts","sourceRoot":"","sources":["../../src/runtime/grokEngineBrokerTurnEnd.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,6BAA6B,CAAC;AAC3E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAEpE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,MAAM,MAAM,uBAAuB,GAAG,QAAQ,CAAC;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AACzG,eAAO,MAAM,oBAAoB,EAAE,uBAAqG,CAAC;AACzI,qGAAqG;AACrG,eAAO,MAAM,wBAAwB,QAAS,CAAC;AAC/C,MAAM,MAAM,uBAAuB,GAAG,aAAa,GAAG,MAAM,CAAC;AAE7D,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,QAAQ,CAAC;IACrD,KAAK,EAAE,IAAI,CAAC,mBAAmB,EAAE,YAAY,GAAG,MAAM,GAAG,gBAAgB,CAAC,CAAC;IAC3E,QAAQ,EAAE,MAAM,uBAAuB,GAAG,SAAS,CAAC;IACpD,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,kGAAkG;IAClG,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,KAAK,EAAE,CAAC,MAAM,EAAE,uBAAuB,KAAK,IAAI,CAAC;IACjD,MAAM,CAAC,EAAE,uBAAuB,CAAC;IACjC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,6FAA6F;IAC7F,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,CAAC,GAAG,QAAQ,CAAC;IAAE,IAAI,IAAI,IAAI,CAAA;CAAE,CAAC,CAyB9B;AAED,oJAAoJ;AACpJ,MAAM,MAAM,iBAAiB,GAAG,QAAQ,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAE9E;;;;;;;;;;;GAWG;AACH,wBAAsB,qBAAqB,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,SAAU,GAAG,OAAO,CAAC,iBAAiB,GAAG,SAAS,CAAC,CAqBnH;AAED,2DAA2D;AAC3D,wBAAsB,uBAAuB,CAC3C,GAAG,EAAE,MAAM,EAAE,QAAQ,SAAU,EAAE,eAAe,UAAQ,GACvD,OAAO,CAAC,OAAO,CAAC,CAgBlB;AAED;;;;;;;GAOG;AACH,wBAAsB,4BAA4B,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,GAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAA2B,EAAE,MAAM,SAAM,EAAE,QAAQ,SAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAarM"}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { readdir, readFile } from "node:fs/promises";
|
|
2
|
+
import { performance } from "node:perf_hooks";
|
|
3
|
+
export const GROK_BROKER_TURN_END = Object.freeze({ finalGraceMs: 15_000, idleMs: 10 * 60_000, pollMs: 1_000 });
|
|
4
|
+
/** How long a turn the broker aborted waits for its worker identity to empty before it is sealed. */
|
|
5
|
+
export const GROK_WORKER_REAP_WAIT_MS = 10_000;
|
|
6
|
+
export function watchGrokBrokerTurnEnd(input) {
|
|
7
|
+
const timing = input.timing ?? GROK_BROKER_TURN_END, now = input.now ?? Date.now;
|
|
8
|
+
let notified, done = false;
|
|
9
|
+
const check = () => {
|
|
10
|
+
if (done)
|
|
11
|
+
return;
|
|
12
|
+
if (input.signal?.aborted === true) {
|
|
13
|
+
stop();
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
try {
|
|
17
|
+
input.onPoll?.();
|
|
18
|
+
}
|
|
19
|
+
catch { /* observation only */ }
|
|
20
|
+
const final = input.meter.finalReply();
|
|
21
|
+
if (final !== undefined && notified !== final.index) {
|
|
22
|
+
notified = final.index;
|
|
23
|
+
try {
|
|
24
|
+
input.onFinalReply();
|
|
25
|
+
}
|
|
26
|
+
catch { /* closing streams is best effort; the grace below still ends the turn */ }
|
|
27
|
+
}
|
|
28
|
+
let activity;
|
|
29
|
+
try {
|
|
30
|
+
activity = input.activity();
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
activity = undefined;
|
|
34
|
+
}
|
|
35
|
+
if (input.meter.busy() || (activity?.inFlight ?? 0) > 0)
|
|
36
|
+
return;
|
|
37
|
+
const quietSince = Math.max(input.meter.lastActivityAt(), activity?.lastActivityAt ?? 0);
|
|
38
|
+
const reason = final !== undefined && now() - Math.max(final.at, quietSince) >= timing.finalGraceMs ? "final_reply"
|
|
39
|
+
: now() - quietSince >= timing.idleMs ? "idle" : undefined;
|
|
40
|
+
if (reason === undefined)
|
|
41
|
+
return;
|
|
42
|
+
stop();
|
|
43
|
+
input.onEnd(reason);
|
|
44
|
+
};
|
|
45
|
+
const timer = setInterval(check, timing.pollMs);
|
|
46
|
+
timer.unref?.();
|
|
47
|
+
const stop = () => { done = true; clearInterval(timer); };
|
|
48
|
+
return { stop };
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Finds the worker the launcher started for `uid`, read-only from `/proc`.
|
|
52
|
+
*
|
|
53
|
+
* A turn the broker ends early never receives the launcher's result frame (a
|
|
54
|
+
* cancelled client is not written to), yet a completed response must name the
|
|
55
|
+
* worker it came from. One identity carries at most one turn at a time
|
|
56
|
+
* (`native/AGENTS.md`), so a process owned by `uid` whose parent is *not* owned
|
|
57
|
+
* by `uid` is a worker the launcher forked. A previous turn's worker can still
|
|
58
|
+
* be alive beside it — a hung worker is exactly what this module exists for, and
|
|
59
|
+
* its kill is the launcher's — so the newest such root (the latest start time)
|
|
60
|
+
* is this turn's: turns of one identity are sequential.
|
|
61
|
+
*/
|
|
62
|
+
export async function findGrokWorkerProcess(uid, procRoot = "/proc") {
|
|
63
|
+
let entries;
|
|
64
|
+
try {
|
|
65
|
+
entries = await readdir(procRoot);
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
const owned = new Map();
|
|
71
|
+
for (const entry of entries) {
|
|
72
|
+
if (!/^\d+$/u.test(entry))
|
|
73
|
+
continue;
|
|
74
|
+
try {
|
|
75
|
+
const status = await readFile(`${procRoot}/${entry}/status`, "utf8");
|
|
76
|
+
const real = /^Uid:\s+(\d+)/mu.exec(status)?.[1];
|
|
77
|
+
if (real === undefined || Number(real) !== uid)
|
|
78
|
+
continue;
|
|
79
|
+
const stat = await readFile(`${procRoot}/${entry}/stat`, "utf8");
|
|
80
|
+
const fields = stat.slice(stat.lastIndexOf(")") + 1).trim().split(/\s+/u);
|
|
81
|
+
// After the command: state(0) ppid(1) … starttime is field 22 of stat, index 19 here.
|
|
82
|
+
const parent = Number(fields[1]), startTicks = fields[19];
|
|
83
|
+
if (fields[0] === "Z" || !Number.isSafeInteger(parent) || startTicks === undefined || !/^[1-9][0-9]*$/u.test(startTicks))
|
|
84
|
+
continue;
|
|
85
|
+
owned.set(Number(entry), { parent, startTicks });
|
|
86
|
+
}
|
|
87
|
+
catch { /* a process exiting mid-scan is not part of the answer */ }
|
|
88
|
+
}
|
|
89
|
+
const roots = [...owned].filter(([, value]) => !owned.has(value.parent)).sort(([, left], [, right]) => BigInt(right.startTicks) > BigInt(left.startTicks) ? 1 : BigInt(right.startTicks) < BigInt(left.startTicks) ? -1 : 0);
|
|
90
|
+
const newest = roots[0];
|
|
91
|
+
return newest === undefined ? undefined : { pid: newest[0], startTicks: newest[1].startTicks };
|
|
92
|
+
}
|
|
93
|
+
/** No process at all, zombies included, owned by `uid`. */
|
|
94
|
+
export async function grokWorkerIdentityEmpty(uid, procRoot = "/proc", requireReadable = false) {
|
|
95
|
+
let entries;
|
|
96
|
+
try {
|
|
97
|
+
entries = await readdir(procRoot);
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return !requireReadable;
|
|
101
|
+
}
|
|
102
|
+
for (const entry of entries) {
|
|
103
|
+
if (!/^\d+$/u.test(entry))
|
|
104
|
+
continue;
|
|
105
|
+
try {
|
|
106
|
+
const status = await readFile(`${procRoot}/${entry}/status`, "utf8");
|
|
107
|
+
// Zombies count, exactly as the launcher's own reap counts them.
|
|
108
|
+
if (Number(/^Uid:\s+(\d+)/mu.exec(status)?.[1]) !== uid)
|
|
109
|
+
continue;
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
// ENOENT is a process exiting mid-scan; an unreadable identity is not empty.
|
|
114
|
+
if (requireReadable && error.code !== "ENOENT")
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Waits, bounded, for the worker identity to empty after the broker ended a
|
|
122
|
+
* turn. Ending a turn kills the launcher's client; the launcher handler then
|
|
123
|
+
* kills and reaps the identity on its own schedule. Sealing first would let
|
|
124
|
+
* the agent's next turn start a worker on the same identity that the previous
|
|
125
|
+
* handler's identity-wide reap then kills. The bound keeps a wedged launcher
|
|
126
|
+
* from holding the agent: past it the turn is sealed anyway, as before.
|
|
127
|
+
*/
|
|
128
|
+
export async function awaitGrokWorkerIdentityEmpty(uid, timeoutMs, empty = grokWorkerIdentityEmpty, pollMs = 100, stableMs = 300) {
|
|
129
|
+
// Empty must hold across a window longer than the launcher's own reap pass
|
|
130
|
+
// (10 ms between scans): one empty glimpse can fall between its kill and its
|
|
131
|
+
// re-scan, and that re-scan would kill the agent's next worker.
|
|
132
|
+
// Cleanup must measure elapsed real time even if the wall clock changes.
|
|
133
|
+
const deadline = performance.now() + timeoutMs;
|
|
134
|
+
let emptySince;
|
|
135
|
+
for (;;) {
|
|
136
|
+
const now = performance.now();
|
|
137
|
+
if (await empty(uid).catch(() => false)) {
|
|
138
|
+
emptySince ??= now;
|
|
139
|
+
if (now - emptySince >= stableMs)
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
else
|
|
143
|
+
emptySince = undefined;
|
|
144
|
+
if (now >= deadline)
|
|
145
|
+
return false;
|
|
146
|
+
await new Promise((resolve) => setTimeout(resolve, pollMs));
|
|
147
|
+
}
|
|
148
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"grokInferenceProxy.d.ts","sourceRoot":"","sources":["../../src/runtime/grokInferenceProxy.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"grokInferenceProxy.d.ts","sourceRoot":"","sources":["../../src/runtime/grokInferenceProxy.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAGhD,OAAO,KAAK,EAAE,6BAA6B,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAE9F,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAGpE,MAAM,MAAM,uBAAuB,GAAG,QAAQ,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAEzK,yGAAyG;AACzG,eAAO,MAAM,8BAA8B,+BAA2B,CAAC;AAEvE;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,uBAAuB,CAAC,KAAK,EAAE,uBAAuB,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,EAAE,mBAAmB,EAAE,SAAS,EAAE,6BAA6B,EAAE,QAAQ,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAkC1N"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { GrokBrokerRunLimit } from "./grokBrokerRunBudget.js";
|
|
1
2
|
import { createHash } from "node:crypto";
|
|
2
3
|
import { GrokBrokerProxyRefusal } from "./grokBrokerProxy.js";
|
|
3
4
|
import { parseGrokUpstreamUsage } from "./grokBrokerTurnMeter.js";
|
|
@@ -57,6 +58,8 @@ export async function serveGrokInferenceGrant(input, response, grants, authority
|
|
|
57
58
|
}
|
|
58
59
|
catch (error) {
|
|
59
60
|
settle?.(undefined);
|
|
61
|
+
if (error instanceof GrokBrokerRunLimit)
|
|
62
|
+
return json(response, 429, '{"error":"run_request_limit"}');
|
|
60
63
|
// Same rule as the subject path: a policy miss is non-retryable (400), because a
|
|
61
64
|
// retryable 503 makes the client re-send a request the broker will never accept,
|
|
62
65
|
// charging estimated usage for every attempt. 503 stays for transient faults only.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
2
|
import { constants } from "node:fs";
|
|
3
|
-
import { spawn } from "
|
|
3
|
+
import { spawnRealTime as spawn } from "./taskClockProcess.js";
|
|
4
4
|
import { lstat, open, rename, unlink } from "node:fs/promises";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { terminateChild, trackCliChild } from "../pi/cliProcess.js";
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { GrokWorkerConfigVariant } from "../contracts/grokBrokerTaskClock.js";
|
|
2
|
+
import { type GrokWorkerConfigPins } from "./grokWorkerHomeAttestation.js";
|
|
1
3
|
import { type GrokWorkerTmpOptions } from "./grokWorkerTmpAttestation.js";
|
|
2
4
|
/**
|
|
3
5
|
* The per-turn freshness watermark taken before the worker is launched: the
|
|
@@ -10,6 +12,7 @@ export type GrokWorkerAttestationSnapshot = Readonly<{
|
|
|
10
12
|
ino: number;
|
|
11
13
|
size: number;
|
|
12
14
|
denyPaths: readonly string[];
|
|
15
|
+
configVariant?: GrokWorkerConfigVariant;
|
|
13
16
|
}>;
|
|
14
17
|
type Snapshot = GrokWorkerAttestationSnapshot;
|
|
15
18
|
export declare class GrokWorkerAttestationFailure extends Error {
|
|
@@ -50,7 +53,8 @@ export declare function parseGrokWorkerSandboxProfile(bytes: Uint8Array, profile
|
|
|
50
53
|
* the events watermark it requires the 1.0.34 layout: events under
|
|
51
54
|
* `$GROK_HOME/sessions/` (the root `sandbox-events.jsonl` stays empty on
|
|
52
55
|
* 1.0.34), and a root-owned read-only worker home whose `config.toml` hashes to
|
|
53
|
-
* the declared renderer output
|
|
56
|
+
* the declared renderer output and whose `skills/` is exactly the declared
|
|
57
|
+
* skills (`skillsSha256`; absent for none) (`grokWorkerHomeAttestation.ts`).
|
|
54
58
|
*
|
|
55
59
|
* It also refuses a deny list bubblewrap could not materialize
|
|
56
60
|
* (`grokWorkerDenyPlacement.ts`), naming the entry and the ancestor that stops
|
|
@@ -69,7 +73,8 @@ export declare function prepareGrokWorkerAttestation(input: Readonly<{
|
|
|
69
73
|
workerUid: number;
|
|
70
74
|
workerGid?: number;
|
|
71
75
|
brokerGid: number;
|
|
72
|
-
configSha256:
|
|
76
|
+
configSha256: GrokWorkerConfigPins;
|
|
77
|
+
skillsSha256?: string;
|
|
73
78
|
registeredWorkers?: readonly Readonly<{
|
|
74
79
|
profilePath: string;
|
|
75
80
|
workerUid: number;
|
|
@@ -78,7 +83,9 @@ export declare function prepareGrokWorkerAttestation(input: Readonly<{
|
|
|
78
83
|
uid: number;
|
|
79
84
|
gid: number;
|
|
80
85
|
tmp?: GrokWorkerTmpOptions;
|
|
81
|
-
}>): Promise<Snapshot
|
|
86
|
+
}>): Promise<Snapshot & {
|
|
87
|
+
configVariant: GrokWorkerConfigVariant;
|
|
88
|
+
}>;
|
|
82
89
|
/** The per-turn attestation input for one registration, carrying every registered worker so sibling temp is attested too. */
|
|
83
90
|
export declare const grokBrokerAttestationInput: <T extends Readonly<{
|
|
84
91
|
profilePath: string;
|
|
@@ -88,9 +95,9 @@ export declare const grokBrokerAttestationInput: <T extends Readonly<{
|
|
|
88
95
|
}>>(registration: T, registrations: readonly Readonly<{
|
|
89
96
|
profilePath: string;
|
|
90
97
|
workerUid: number;
|
|
91
|
-
}>[], configSha256:
|
|
98
|
+
}>[], configSha256: GrokWorkerConfigPins) => T & {
|
|
92
99
|
brokerGid: number;
|
|
93
|
-
configSha256:
|
|
100
|
+
configSha256: GrokWorkerConfigPins;
|
|
94
101
|
registeredWorkers: {
|
|
95
102
|
profilePath: string;
|
|
96
103
|
workerUid: number;
|