open-multi-agent-kit 1.2.3 → 1.2.4
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 +14 -0
- package/README.md +2 -2
- package/dist/cli/help.d.ts.map +1 -1
- package/dist/cli/help.js +2 -0
- package/dist/cli/help.js.map +1 -1
- package/dist/commands/doctor-provider.d.ts.map +1 -1
- package/dist/commands/doctor-provider.js +2 -13
- package/dist/commands/doctor-provider.js.map +1 -1
- package/dist/commands/provider-adopt-cli.d.ts +24 -0
- package/dist/commands/provider-adopt-cli.d.ts.map +1 -0
- package/dist/commands/provider-adopt-cli.js +182 -0
- package/dist/commands/provider-adopt-cli.js.map +1 -0
- package/dist/commands/provider-adopt-status.d.ts +21 -0
- package/dist/commands/provider-adopt-status.d.ts.map +1 -0
- package/dist/commands/provider-adopt-status.js +84 -0
- package/dist/commands/provider-adopt-status.js.map +1 -0
- package/dist/commands/provider-known-apis.d.ts +2 -0
- package/dist/commands/provider-known-apis.d.ts.map +1 -0
- package/dist/commands/provider-known-apis.js +28 -0
- package/dist/commands/provider-known-apis.js.map +1 -0
- package/dist/commands/run-command.d.ts.map +1 -1
- package/dist/commands/run-command.js +4 -0
- package/dist/commands/run-command.js.map +1 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +6 -0
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/auth-storage.d.ts +16 -16
- package/dist/core/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +94 -111
- package/dist/core/auth-storage.js.map +1 -1
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js +13 -10
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/core/compaction/inert-tail.d.ts +23 -0
- package/dist/core/compaction/inert-tail.d.ts.map +1 -0
- package/dist/core/compaction/inert-tail.js +108 -0
- package/dist/core/compaction/inert-tail.js.map +1 -0
- package/dist/core/compaction/provenance.d.ts +30 -0
- package/dist/core/compaction/provenance.d.ts.map +1 -0
- package/dist/core/compaction/provenance.js +59 -0
- package/dist/core/compaction/provenance.js.map +1 -0
- package/dist/core/compaction/transaction.d.ts.map +1 -1
- package/dist/core/compaction/transaction.js +4 -1
- package/dist/core/compaction/transaction.js.map +1 -1
- package/dist/core/external-credential-sources.d.ts +48 -0
- package/dist/core/external-credential-sources.d.ts.map +1 -0
- package/dist/core/external-credential-sources.js +176 -0
- package/dist/core/external-credential-sources.js.map +1 -0
- package/dist/core/model-registry.d.ts +11 -0
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +28 -14
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/oauth-account-import.d.ts +52 -0
- package/dist/core/oauth-account-import.d.ts.map +1 -0
- package/dist/core/oauth-account-import.js +125 -0
- package/dist/core/oauth-account-import.js.map +1 -0
- package/dist/core/oauth-credential.d.ts +22 -0
- package/dist/core/oauth-credential.d.ts.map +1 -0
- package/dist/core/oauth-credential.js +36 -0
- package/dist/core/oauth-credential.js.map +1 -0
- package/dist/core/session-compaction-service.d.ts +2 -2
- package/dist/core/session-compaction-service.d.ts.map +1 -1
- package/dist/core/session-compaction-service.js +20 -57
- package/dist/core/session-compaction-service.js.map +1 -1
- package/dist/core/verified-run/owned-execution.d.ts.map +1 -1
- package/dist/core/verified-run/owned-execution.js +13 -1
- package/dist/core/verified-run/owned-execution.js.map +1 -1
- package/docs/compaction.md +2 -0
- package/docs/extensions.md +1 -1
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/npm-shrinkwrap.json +18 -18
- package/package.json +6 -6
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth-credential.js","sourceRoot":"","sources":["../../src/core/oauth-credential.ts"],"names":[],"mappings":"AAkBA,MAAM,UAAU,yBAAyB,CAAC,UAA2B,EAAoB;IACxF,MAAM,EACL,IAAI,EAAE,KAAK,EACX,QAAQ,EAAE,SAAS,EACnB,aAAa,EAAE,cAAc,EAC7B,WAAW,EAAE,YAAY,EACzB,GAAG,WAAW,EACd,GAAG,UAAU,CAAC;IACf,OAAO,WAAW,CAAC;AAAA,CACnB;AAED,MAAM,UAAU,gBAAgB,CAAC,UAA2B,EAAsB;IACjF,IAAI,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,QAAQ,CAAC,IAAI,UAAU,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1E,OAAO,UAAU,CAAC,QAAQ,CAAC;IAC5B,CAAC;IACD,OAAO,CAAC,yBAAyB,CAAC,UAAU,CAAC,CAAC,CAAC;AAAA,CAC/C;AAED,MAAM,UAAU,0BAA0B,CAAC,KAAc,EAAE,YAAoB,EAAU;IACxF,IAAI,YAAY,IAAI,CAAC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC;QAChF,OAAO,CAAC,CAAC;IACV,CAAC;IACD,OAAO,CAAC,CAAC,KAAK,GAAG,YAAY,CAAC,GAAG,YAAY,CAAC,GAAG,YAAY,CAAC;AAAA,CAC9D;AAED,MAAM,UAAU,4BAA4B,CAAC,UAA2B,EAAE,YAAoB,EAAU;IACvG,OAAO,0BAA0B,CAAC,UAAU,CAAC,aAAa,IAAI,UAAU,CAAC,WAAW,EAAE,YAAY,CAAC,CAAC;AAAA,CACpG;AAED,MAAM,UAAU,qBAAqB,CAAC,QAA4B,EAAE,aAAa,GAAG,CAAC,EAAmB;IACvG,MAAM,aAAa,GAAG,0BAA0B,CAAC,aAAa,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IACjF,MAAM,QAAQ,GAAG,QAAQ,CAAC,aAAa,CAAC,CAAC;IACzC,IAAI,CAAC,QAAQ,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CAAC,oDAAoD,CAAC,CAAC;IACvE,CAAC;IACD,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3B,OAAO,EAAE,GAAG,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;IACvC,CAAC;IACD,OAAO;QACN,GAAG,QAAQ;QACX,IAAI,EAAE,OAAO;QACb,QAAQ;QACR,aAAa,EAAE,aAAa;KAC5B,CAAC;AAAA,CACF","sourcesContent":["/**\n * Pure helpers for the stored `oauth` credential shape: account lists, the\n * selected-account index, and building a credential back from an account list.\n * Extracted from auth-storage.ts so the store keeps its size budget; nothing here\n * touches the storage lock or disk.\n */\nimport type { OAuthCredentials } from \"omk-ai\";\n\nexport type OAuthCredential = {\n\ttype: \"oauth\";\n\t/** Complete account list when more than one subscription account is configured. */\n\taccounts?: OAuthCredentials[];\n\t/** Index of the account explicitly selected for this provider. */\n\tactiveAccount?: number;\n\t/** Legacy cursor accepted when migrating older auth.json files. */\n\tnextAccount?: number;\n} & OAuthCredentials;\n\nexport function stripOAuthStorageMetadata(credential: OAuthCredential): OAuthCredentials {\n\tconst {\n\t\ttype: _type,\n\t\taccounts: _accounts,\n\t\tactiveAccount: _activeAccount,\n\t\tnextAccount: _nextAccount,\n\t\t...credentials\n\t} = credential;\n\treturn credentials;\n}\n\nexport function getOAuthAccounts(credential: OAuthCredential): OAuthCredentials[] {\n\tif (Array.isArray(credential.accounts) && credential.accounts.length > 0) {\n\t\treturn credential.accounts;\n\t}\n\treturn [stripOAuthStorageMetadata(credential)];\n}\n\nexport function normalizeOAuthAccountIndex(index: unknown, accountCount: number): number {\n\tif (accountCount <= 0 || typeof index !== \"number\" || !Number.isInteger(index)) {\n\t\treturn 0;\n\t}\n\treturn ((index % accountCount) + accountCount) % accountCount;\n}\n\nexport function getSelectedOAuthAccountIndex(credential: OAuthCredential, accountCount: number): number {\n\treturn normalizeOAuthAccountIndex(credential.activeAccount ?? credential.nextAccount, accountCount);\n}\n\nexport function createOAuthCredential(accounts: OAuthCredentials[], activeAccount = 0): OAuthCredential {\n\tconst selectedIndex = normalizeOAuthAccountIndex(activeAccount, accounts.length);\n\tconst selected = accounts[selectedIndex];\n\tif (!selected) {\n\t\tthrow new Error(\"OAuth credential must contain at least one account\");\n\t}\n\tif (accounts.length === 1) {\n\t\treturn { ...selected, type: \"oauth\" };\n\t}\n\treturn {\n\t\t...selected,\n\t\ttype: \"oauth\",\n\t\taccounts,\n\t\tactiveAccount: selectedIndex,\n\t};\n}\n"]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Api, Message, Model } from "omk-ai";
|
|
2
2
|
import { type CompactionControlState } from "./compaction/control-state.ts";
|
|
3
3
|
import type { CompactionResult } from "./compaction/index.ts";
|
|
4
|
-
import { type CompactionBarrierResult, type CompactionEnvelope, type
|
|
4
|
+
import { type CompactionBarrierResult, type CompactionEnvelope, type CompactionSourceIdentity, type CompactionTransaction } from "./compaction/transaction.ts";
|
|
5
5
|
import type { SessionIntegrityReport } from "./session-integrity.ts";
|
|
6
6
|
import type { CompactionEntry, SessionEntry, SessionManager } from "./session-manager.ts";
|
|
7
7
|
export interface CapturedCompactionState {
|
|
@@ -47,6 +47,7 @@ export declare class SessionCompactionService {
|
|
|
47
47
|
private readonly deps;
|
|
48
48
|
constructor(deps: SessionCompactionServiceDeps);
|
|
49
49
|
captureState(): CapturedCompactionState;
|
|
50
|
+
private readSessionBytes;
|
|
50
51
|
private captureStateLocked;
|
|
51
52
|
barrierError(barrier: CompactionBarrierResult): Error;
|
|
52
53
|
evaluateBarrier(capture: CapturedCompactionState, includeMissingTailAsPending: boolean, excludedPendingIds?: ReadonlySet<string>): CompactionBarrierResult;
|
|
@@ -56,7 +57,6 @@ export declare class SessionCompactionService {
|
|
|
56
57
|
};
|
|
57
58
|
priorCommittedSourceDigests(): string[];
|
|
58
59
|
beginTransaction(compactionModel: Model<Api>, emergency: boolean): BegunCompaction;
|
|
59
|
-
captureProvenance(capture: CapturedCompactionState): CompactionPreservedProvenanceInput;
|
|
60
60
|
detailsWithEnvelope(details: unknown, envelope: CompactionEnvelope): unknown;
|
|
61
61
|
commit(begun: BegunCompaction, result: CompactionResult, fromExtension: boolean): CommittedCompaction;
|
|
62
62
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-compaction-service.d.ts","sourceRoot":"","sources":["../../src/core/session-compaction-service.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Session compaction service: capture / barrier / repair / transaction / commit.\n *\n * Extracted from AgentSession so the session keeps lifecycle orchestration\n * (abort controllers, extension events, auth, model resolution) while this\n * owns the integrity-critical compaction state machine. Every method preserves\n * the exact semantics of the original session implementation — the\n * compaction-transaction / session-integrity suites pin them.\n */\nimport { createHash, randomUUID } from \"node:crypto\";\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { repairTranscriptIntegrity } from \"omk-agent-core\";\nimport type { Api, Message, Model, ToolResultMessage } from \"omk-ai\";\nimport { sanitizeBinaryOutput } from \"../utils/shell.ts\";\nimport {\n\ttype CompactionControlState,\n\tcontrolStateDigest,\n\tcontrolStateProvenance,\n\tvalidateControlState,\n} from \"./compaction/control-state.ts\";\nimport type { CompactionResult } from \"./compaction/index.ts\";\nimport {\n\ttype CompactionBarrierResult,\n\ttype CompactionEnvelope,\n\ttype CompactionPreservedProvenanceInput,\n\ttype CompactionSourceIdentity,\n\ttype CompactionTransaction,\n\tcreateCompactionEnvelope,\n\tcreateCompactionSourceIdentity,\n\tcreateCompactionTransaction,\n\tdecideCompactionCommit,\n\tevaluateCompactionBarrier,\n\tredactCredentialShapedContent,\n\tvalidateCompactionEnvelope,\n} from \"./compaction/transaction.ts\";\nimport { redactSensitiveTextForced } from \"./redaction.ts\";\nimport type { SessionIntegrityReport } from \"./session-integrity.ts\";\nimport { inspectSessionIntegrity } from \"./session-integrity.ts\";\nimport type { CompactionEntry, SessionEntry, SessionManager } from \"./session-manager.ts\";\n\nexport interface CapturedCompactionState {\n\treadonly report: SessionIntegrityReport;\n\treadonly branchEntries: readonly SessionEntry[];\n\treadonly revision: CompactionTransaction[\"baseRevision\"];\n\treadonly source: CompactionSourceIdentity;\n\t/**\n\t * Digest of the host control-state snapshot captured under the same commit\n\t * lock as `source`. `null` when no control authority is attached. A change\n\t * between capture and commit discards the summary like a source mismatch.\n\t */\n\treadonly controlStateDigest: string | null;\n\treadonly controlState: CompactionControlState | null;\n}\n\nexport interface BegunCompaction {\n\treadonly capture: CapturedCompactionState;\n\treadonly transaction: CompactionTransaction;\n}\n\nexport interface CommittedCompaction {\n\treadonly entry: CompactionEntry;\n\treadonly envelope: CompactionEnvelope;\n}\n\nexport interface SessionCompactionServiceDeps {\n\treadonly sessionManager: SessionManager;\n\treadonly pendingToolCallIds: () => ReadonlySet<string>;\n\treadonly getUserMessageText: (message: Message) => string;\n\treadonly cwd: string;\n\t/**\n\t * Optional host control authority. Called inside the compaction commit lock\n\t * so the snapshot is bound to the same captured source as the summary. Must\n\t * be deterministic for unchanged state — a changed snapshot discards the\n\t * in-flight summary at commit. Return `null` only when no authority exists.\n\t */\n\treadonly controlState?: () => CompactionControlState | null;\n\treadonly invalidateContextBudget: () => void;\n\t/** Refresh agent messages from the session manager after tail repair/commit. */\n\treadonly refreshAgentMessages: () => void;\n\t/** Hysteresis bookkeeping after a successful commit. */\n\treadonly recordCommit: () => void;\n}\n\nexport class SessionCompactionService {\n\tprivate readonly deps: SessionCompactionServiceDeps;\n\n\tconstructor(deps: SessionCompactionServiceDeps) {\n\t\tthis.deps = deps;\n\t}\n\n\tcaptureState(): CapturedCompactionState {\n\t\treturn this.deps.sessionManager.withCompactionCommitLock(() => this.captureStateLocked());\n\t}\n\n\tprivate captureStateLocked(): CapturedCompactionState {\n\t\tconst { sessionManager } = this.deps;\n\t\tconst sessionFile = sessionManager.getSessionFile();\n\t\tconst bytes =\n\t\t\tsessionFile && existsSync(sessionFile)\n\t\t\t\t? new Uint8Array(readFileSync(sessionFile))\n\t\t\t\t: new TextEncoder().encode(\n\t\t\t\t\t\t`${[sessionManager.getHeader(), ...sessionManager.getEntries()]\n\t\t\t\t\t\t\t.filter((entry) => entry !== null)\n\t\t\t\t\t\t\t.map((entry) => JSON.stringify(entry))\n\t\t\t\t\t\t\t.join(\"\\n\")}\\n`,\n\t\t\t\t\t);\n\t\tconst report = inspectSessionIntegrity(bytes, { activeLeafId: sessionManager.getLeafId() });\n\t\tconst branchEntries = report.activeBranch;\n\t\tlet latestCompactionIndex = -1;\n\t\tfor (let index = branchEntries.length - 1; index >= 0; index -= 1) {\n\t\t\tif (branchEntries[index]?.type === \"compaction\") {\n\t\t\t\tlatestCompactionIndex = index;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tconst latestCompaction = branchEntries[latestCompactionIndex];\n\t\tconst firstKeptIndex =\n\t\t\tlatestCompaction?.type === \"compaction\"\n\t\t\t\t? branchEntries.findIndex((entry) => entry.id === latestCompaction.firstKeptEntryId)\n\t\t\t\t: -1;\n\t\tconst sourceEntries = branchEntries.slice(\n\t\t\tlatestCompactionIndex < 0 ? 0 : firstKeptIndex < 0 ? latestCompactionIndex : firstKeptIndex,\n\t\t);\n\t\tconst firstEntry = sourceEntries[0];\n\t\tconst lastEntry = sourceEntries[sourceEntries.length - 1];\n\t\tif (!firstEntry || !lastEntry || report.activeLeafId === null) {\n\t\t\tconst barrier = evaluateCompactionBarrier(report, [...this.deps.pendingToolCallIds()]);\n\t\t\tif (barrier.status !== \"ready\") throw this.barrierError(barrier);\n\t\t\tthrow new Error(\"Nothing to compact: the active session branch is empty\");\n\t\t}\n\t\tconst revision = sessionManager.getDurableHeadToken();\n\t\tconst source = createCompactionSourceIdentity({\n\t\t\tsessionId: revision.sessionId,\n\t\t\tentryIds: sourceEntries.map((entry) => entry.id),\n\t\t\tfirstEntryId: firstEntry.id,\n\t\t\tlastEntryId: lastEntry.id,\n\t\t\tsourceSha256: createHash(\"sha256\")\n\t\t\t\t.update(sourceEntries.map((entry) => JSON.stringify(entry)).join(\"\\n\"), \"utf8\")\n\t\t\t\t.digest(\"hex\"),\n\t\t\tactiveLeafId: report.activeLeafId,\n\t\t\tmessageCount: report.activeMessages.length,\n\t\t});\n\t\tconst controlState = validateControlState(this.deps.controlState?.() ?? null);\n\t\treturn {\n\t\t\treport,\n\t\t\tbranchEntries,\n\t\t\trevision,\n\t\t\tsource,\n\t\t\tcontrolStateDigest: controlStateDigest(controlState),\n\t\t\tcontrolState,\n\t\t};\n\t}\n\n\tbarrierError(barrier: CompactionBarrierResult): Error {\n\t\tif (barrier.status === \"defer\") {\n\t\t\treturn new Error(`Compaction deferred until the transcript closes (${barrier.reason})`);\n\t\t}\n\t\treturn new Error(\n\t\t\t`Compaction failed closed on transcript integrity (${barrier.reason}). Run the session doctor before retrying.`,\n\t\t);\n\t}\n\n\tevaluateBarrier(\n\t\tcapture: CapturedCompactionState,\n\t\tincludeMissingTailAsPending: boolean,\n\t\texcludedPendingIds: ReadonlySet<string> = new Set(),\n\t): CompactionBarrierResult {\n\t\tconst pending = new Set([...this.deps.pendingToolCallIds()].filter((id) => !excludedPendingIds.has(id)));\n\t\tif (includeMissingTailAsPending) {\n\t\t\tfor (const issue of capture.report.transcript?.issues ?? []) {\n\t\t\t\tif (issue.kind === \"missing_result\") pending.add(issue.toolCallId);\n\t\t\t}\n\t\t}\n\t\treturn evaluateCompactionBarrier(capture.report, [...pending]);\n\t}\n\n\trepairEmergencyTail(capture: CapturedCompactionState): {\n\t\treadonly capture: CapturedCompactionState;\n\t\treadonly repairedToolCallIds: ReadonlySet<string>;\n\t} {\n\t\tconst { sessionManager } = this.deps;\n\t\tconst barrier = this.evaluateBarrier(capture, true);\n\t\tif (barrier.status !== \"defer\" || barrier.reason !== \"missing_active_tail_results\") {\n\t\t\tif (barrier.status !== \"ready\") throw this.barrierError(barrier);\n\t\t\treturn { capture, repairedToolCallIds: new Set() };\n\t\t}\n\t\tconst repairedMessages = repairTranscriptIntegrity(\n\t\t\t[...capture.report.activeMessages],\n\t\t\t\"Tool result missing; synthesized to close an emergency compaction barrier\",\n\t\t);\n\t\tconst inserted = repairedMessages.slice(capture.report.activeMessages.length);\n\t\tconst repairedToolCallIds = new Set<string>();\n\t\tfor (const message of inserted) {\n\t\t\tif (message.role !== \"toolResult\") {\n\t\t\t\tthrow new Error(\"Emergency compaction repair produced a non-tool result\");\n\t\t\t}\n\t\t\tconst toolResult: ToolResultMessage = message;\n\t\t\trepairedToolCallIds.add(toolResult.toolCallId);\n\t\t\tsessionManager.appendMessage(toolResult);\n\t\t}\n\t\tsessionManager.appendCustomEntry(\"compaction_transcript_repaired\", {\n\t\t\tinsertedToolCallIds: [...repairedToolCallIds],\n\t\t\treason: \"emergency_compaction\",\n\t\t});\n\t\tthis.deps.invalidateContextBudget();\n\t\tconst closedCapture = this.captureState();\n\t\tconst closedBarrier = this.evaluateBarrier(closedCapture, false, repairedToolCallIds);\n\t\tif (closedBarrier.status !== \"ready\") throw this.barrierError(closedBarrier);\n\t\tthis.deps.refreshAgentMessages();\n\t\treturn { capture: closedCapture, repairedToolCallIds };\n\t}\n\n\tpriorCommittedSourceDigests(): string[] {\n\t\tconst digests: string[] = [];\n\t\tfor (const entry of this.deps.sessionManager.getEntries()) {\n\t\t\tif (entry.type !== \"compaction\" || typeof entry.details !== \"object\" || entry.details === null) continue;\n\t\t\tif (Object.getOwnPropertyDescriptor(entry.details, \"compactionEnvelope\") === undefined) continue;\n\t\t\tconst envelope = validateCompactionEnvelope(Reflect.get(entry.details, \"compactionEnvelope\"));\n\t\t\tif (envelope.summary !== entry.summary) {\n\t\t\t\tthrow new Error(`Compaction entry ${entry.id} has invalid provenance. Run the session doctor.`);\n\t\t\t}\n\t\t\tdigests.push(envelope.source.sourceSha256);\n\t\t}\n\t\treturn digests;\n\t}\n\n\tbeginTransaction(compactionModel: Model<Api>, emergency: boolean): BegunCompaction {\n\t\tlet capture = this.captureState();\n\t\tif (emergency) {\n\t\t\tcapture = this.repairEmergencyTail(capture).capture;\n\t\t} else {\n\t\t\tconst barrier = this.evaluateBarrier(capture, false);\n\t\t\tif (barrier.status !== \"ready\") throw this.barrierError(barrier);\n\t\t}\n\t\tconst transaction = createCompactionTransaction({\n\t\t\ttransactionId: randomUUID(),\n\t\t\tbaseRevision: capture.revision,\n\t\t\tsource: capture.source,\n\t\t\tcreatedAt: new Date().toISOString(),\n\t\t\tmodel: { provider: compactionModel.provider, id: compactionModel.id },\n\t\t\tpreserved: this.captureProvenance(capture),\n\t\t});\n\t\tif (this.priorCommittedSourceDigests().includes(transaction.source.sourceSha256)) {\n\t\t\tthrow new Error(\"This exact compaction source was already compacted\");\n\t\t}\n\t\treturn { capture, transaction };\n\t}\n\n\tcaptureProvenance(capture: CapturedCompactionState): CompactionPreservedProvenanceInput {\n\t\tlet latestIntent = \"Continue the current session\";\n\t\tfor (let index = capture.report.activeMessages.length - 1; index >= 0; index -= 1) {\n\t\t\tconst message = capture.report.activeMessages[index];\n\t\t\tif (message?.role !== \"user\") continue;\n\t\t\tconst candidate = redactCredentialShapedContent(\n\t\t\t\tsanitizeBinaryOutput(redactSensitiveTextForced(this.deps.getUserMessageText(message)).trim())\n\t\t\t\t\t.replace(/[\\u0000-\\u0008\\u000b\\u000c\\u000e-\\u001f\\u007f]/gu, \"\")\n\t\t\t\t\t.slice(0, 16_384),\n\t\t\t);\n\t\t\tif (candidate.length > 0) latestIntent = candidate;\n\t\t\tbreak;\n\t\t}\n\t\tconst modelHistory = capture.branchEntries\n\t\t\t.flatMap((entry) => {\n\t\t\t\tif (entry.type === \"model_change\") {\n\t\t\t\t\treturn [{ entryId: entry.id, provider: entry.provider, modelId: entry.modelId }];\n\t\t\t\t}\n\t\t\t\tif (entry.type === \"message\" && entry.message.role === \"assistant\") {\n\t\t\t\t\treturn [{ entryId: entry.id, provider: entry.message.provider, modelId: entry.message.model }];\n\t\t\t\t}\n\t\t\t\treturn [];\n\t\t\t})\n\t\t\t.slice(-256);\n\t\tconst customEntryIds = (customType: string): string[] =>\n\t\t\tcapture.branchEntries\n\t\t\t\t.filter((entry) => entry.type === \"custom\" && entry.customType === customType)\n\t\t\t\t.map((entry) => entry.id);\n\t\tconst control = controlStateProvenance(capture.controlState);\n\t\treturn {\n\t\t\tlatestIntent,\n\t\t\topenTasks: control.openTasks,\n\t\t\tlaneIds: customEntryIds(\"lane\"),\n\t\t\tacceptancePredicateIds: customEntryIds(\"acceptance_predicate\"),\n\t\t\tevidenceReceiptIds: customEntryIds(\"evidence_receipt\"),\n\t\t\tblockerReasons: control.blockerReasons,\n\t\t\trepairEventIds: [\n\t\t\t\t...customEntryIds(\"transcript_repaired\"),\n\t\t\t\t...customEntryIds(\"compaction_transcript_repaired\"),\n\t\t\t],\n\t\t\tbranch: control.branch,\n\t\t\tworktree: this.deps.cwd,\n\t\t\tmodelHistory,\n\t\t\tnextAction: redactCredentialShapedContent(latestIntent.slice(0, 4096)) || \"Continue the current session\",\n\t\t};\n\t}\n\n\tdetailsWithEnvelope(details: unknown, envelope: CompactionEnvelope): unknown {\n\t\tif (typeof details === \"object\" && details !== null && !Array.isArray(details)) {\n\t\t\treturn { ...details, compactionEnvelope: envelope };\n\t\t}\n\t\treturn {\n\t\t\tcompactionEnvelope: envelope,\n\t\t\t...(details === undefined ? {} : { resultDetails: details }),\n\t\t};\n\t}\n\n\tcommit(begun: BegunCompaction, result: CompactionResult, fromExtension: boolean): CommittedCompaction {\n\t\tif (!begun.transaction.source.entryIds.includes(result.firstKeptEntryId)) {\n\t\t\tthrow new Error(\"Compaction first-kept entry is outside the captured source\");\n\t\t}\n\t\tconst committed = this.deps.sessionManager.withCompactionCommitLock(() => {\n\t\t\tconst current = this.captureState();\n\t\t\tconst barrier = this.evaluateBarrier(current, false);\n\t\t\tconst decision = decideCompactionCommit({\n\t\t\t\ttransaction: begun.transaction,\n\t\t\t\tcurrentRevision: current.revision,\n\t\t\t\tcurrentSource: current.source,\n\t\t\t\tbarrier,\n\t\t\t\tpriorCommittedSourceDigests: this.priorCommittedSourceDigests(),\n\t\t\t});\n\t\t\tswitch (decision.decision) {\n\t\t\t\tcase \"duplicate\":\n\t\t\t\t\tthrow new Error(\"This exact compaction source was already compacted\");\n\t\t\t\tcase \"stale\":\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`Session changed during compaction (${decision.reason}); generated summary was discarded`,\n\t\t\t\t\t);\n\t\t\t\tcase \"defer\":\n\t\t\t\tcase \"fail_closed\":\n\t\t\t\t\tthrow this.barrierError(barrier);\n\t\t\t\tcase \"commit\": {\n\t\t\t\t\tif (current.controlStateDigest !== begun.capture.controlStateDigest) {\n\t\t\t\t\t\tthrow new Error(\"Control state changed during compaction; generated summary was discarded\");\n\t\t\t\t\t}\n\t\t\t\t\tconst envelope = createCompactionEnvelope({\n\t\t\t\t\t\ttransaction: begun.transaction,\n\t\t\t\t\t\tdecision,\n\t\t\t\t\t\tsummary: result.summary,\n\t\t\t\t\t\tsummarySha256: createHash(\"sha256\").update(result.summary, \"utf8\").digest(\"hex\"),\n\t\t\t\t\t});\n\t\t\t\t\tconst entryId = this.deps.sessionManager.appendCompaction(\n\t\t\t\t\t\tresult.summary,\n\t\t\t\t\t\tresult.firstKeptEntryId,\n\t\t\t\t\t\tresult.tokensBefore,\n\t\t\t\t\t\tthis.detailsWithEnvelope(result.details, envelope),\n\t\t\t\t\t\tfromExtension,\n\t\t\t\t\t);\n\t\t\t\t\tconst entry = this.deps.sessionManager.getEntry(entryId);\n\t\t\t\t\tif (!entry || entry.type !== \"compaction\") {\n\t\t\t\t\t\tthrow new Error(\"Compaction commit did not produce a compaction entry\");\n\t\t\t\t\t}\n\t\t\t\t\treturn { entry, envelope };\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t\tthis.deps.recordCommit();\n\t\treturn committed;\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"session-compaction-service.d.ts","sourceRoot":"","sources":["../../src/core/session-compaction-service.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Session compaction service: capture / barrier / repair / transaction / commit.\n *\n * Extracted from AgentSession so the session keeps lifecycle orchestration\n * (abort controllers, extension events, auth, model resolution) while this\n * owns the integrity-critical compaction state machine. Every method preserves\n * the exact semantics of the original session implementation — the\n * compaction-transaction / session-integrity suites pin them.\n */\nimport { createHash, randomUUID } from \"node:crypto\";\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { repairTranscriptIntegrity } from \"omk-agent-core\";\nimport type { Api, Message, Model, ToolResultMessage } from \"omk-ai\";\nimport { type CompactionControlState, controlStateDigest, validateControlState } from \"./compaction/control-state.ts\";\nimport type { CompactionResult } from \"./compaction/index.ts\";\nimport { decideCommitOverInertTail } from \"./compaction/inert-tail.ts\";\nimport { buildPreservedProvenance, PROVENANCE_CUSTOM_TYPES } from \"./compaction/provenance.ts\";\nimport {\n\ttype CompactionBarrierResult,\n\ttype CompactionEnvelope,\n\ttype CompactionSourceIdentity,\n\ttype CompactionTransaction,\n\tcreateCompactionEnvelope,\n\tcreateCompactionSourceIdentity,\n\tcreateCompactionTransaction,\n\tevaluateCompactionBarrier,\n\tvalidateCompactionEnvelope,\n} from \"./compaction/transaction.ts\";\nimport type { SessionIntegrityReport } from \"./session-integrity.ts\";\nimport { inspectSessionIntegrity } from \"./session-integrity.ts\";\nimport type { CompactionEntry, SessionEntry, SessionManager } from \"./session-manager.ts\";\n\nexport interface CapturedCompactionState {\n\treadonly report: SessionIntegrityReport;\n\treadonly branchEntries: readonly SessionEntry[];\n\treadonly revision: CompactionTransaction[\"baseRevision\"];\n\treadonly source: CompactionSourceIdentity;\n\t/**\n\t * Digest of the host control-state snapshot captured under the same commit\n\t * lock as `source`. `null` when no control authority is attached. A change\n\t * between capture and commit discards the summary like a source mismatch.\n\t */\n\treadonly controlStateDigest: string | null;\n\treadonly controlState: CompactionControlState | null;\n}\n\nexport interface BegunCompaction {\n\treadonly capture: CapturedCompactionState;\n\treadonly transaction: CompactionTransaction;\n}\n\nexport interface CommittedCompaction {\n\treadonly entry: CompactionEntry;\n\treadonly envelope: CompactionEnvelope;\n}\n\nexport interface SessionCompactionServiceDeps {\n\treadonly sessionManager: SessionManager;\n\treadonly pendingToolCallIds: () => ReadonlySet<string>;\n\treadonly getUserMessageText: (message: Message) => string;\n\treadonly cwd: string;\n\t/**\n\t * Optional host control authority. Called inside the compaction commit lock\n\t * so the snapshot is bound to the same captured source as the summary. Must\n\t * be deterministic for unchanged state — a changed snapshot discards the\n\t * in-flight summary at commit. Return `null` only when no authority exists.\n\t */\n\treadonly controlState?: () => CompactionControlState | null;\n\treadonly invalidateContextBudget: () => void;\n\t/** Refresh agent messages from the session manager after tail repair/commit. */\n\treadonly refreshAgentMessages: () => void;\n\t/** Hysteresis bookkeeping after a successful commit. */\n\treadonly recordCommit: () => void;\n}\n\nexport class SessionCompactionService {\n\tprivate readonly deps: SessionCompactionServiceDeps;\n\n\tconstructor(deps: SessionCompactionServiceDeps) {\n\t\tthis.deps = deps;\n\t}\n\n\tcaptureState(): CapturedCompactionState {\n\t\treturn this.deps.sessionManager.withCompactionCommitLock(() => this.captureStateLocked(this.readSessionBytes()));\n\t}\n\n\tprivate readSessionBytes(): Uint8Array {\n\t\tconst { sessionManager } = this.deps;\n\t\tconst sessionFile = sessionManager.getSessionFile();\n\t\treturn sessionFile && existsSync(sessionFile)\n\t\t\t? new Uint8Array(readFileSync(sessionFile))\n\t\t\t: new TextEncoder().encode(\n\t\t\t\t\t`${[sessionManager.getHeader(), ...sessionManager.getEntries()]\n\t\t\t\t\t\t.filter((entry) => entry !== null)\n\t\t\t\t\t\t.map((entry) => JSON.stringify(entry))\n\t\t\t\t\t\t.join(\"\\n\")}\\n`,\n\t\t\t\t);\n\t}\n\n\tprivate captureStateLocked(bytes: Uint8Array): CapturedCompactionState {\n\t\tconst { sessionManager } = this.deps;\n\t\tconst report = inspectSessionIntegrity(bytes, { activeLeafId: sessionManager.getLeafId() });\n\t\tconst branchEntries = report.activeBranch;\n\t\tlet latestCompactionIndex = -1;\n\t\tfor (let index = branchEntries.length - 1; index >= 0; index -= 1) {\n\t\t\tif (branchEntries[index]?.type === \"compaction\") {\n\t\t\t\tlatestCompactionIndex = index;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tconst latestCompaction = branchEntries[latestCompactionIndex];\n\t\tconst firstKeptIndex =\n\t\t\tlatestCompaction?.type === \"compaction\"\n\t\t\t\t? branchEntries.findIndex((entry) => entry.id === latestCompaction.firstKeptEntryId)\n\t\t\t\t: -1;\n\t\tconst sourceEntries = branchEntries.slice(\n\t\t\tlatestCompactionIndex < 0 ? 0 : firstKeptIndex < 0 ? latestCompactionIndex : firstKeptIndex,\n\t\t);\n\t\tconst firstEntry = sourceEntries[0];\n\t\tconst lastEntry = sourceEntries[sourceEntries.length - 1];\n\t\tif (!firstEntry || !lastEntry || report.activeLeafId === null) {\n\t\t\tconst barrier = evaluateCompactionBarrier(report, [...this.deps.pendingToolCallIds()]);\n\t\t\tif (barrier.status !== \"ready\") throw this.barrierError(barrier);\n\t\t\tthrow new Error(\"Nothing to compact: the active session branch is empty\");\n\t\t}\n\t\tconst revision = sessionManager.getDurableHeadToken();\n\t\tconst source = createCompactionSourceIdentity({\n\t\t\tsessionId: revision.sessionId,\n\t\t\tentryIds: sourceEntries.map((entry) => entry.id),\n\t\t\tfirstEntryId: firstEntry.id,\n\t\t\tlastEntryId: lastEntry.id,\n\t\t\tsourceSha256: createHash(\"sha256\")\n\t\t\t\t.update(sourceEntries.map((entry) => JSON.stringify(entry)).join(\"\\n\"), \"utf8\")\n\t\t\t\t.digest(\"hex\"),\n\t\t\tactiveLeafId: report.activeLeafId,\n\t\t\tmessageCount: report.activeMessages.length,\n\t\t});\n\t\tconst controlState = validateControlState(this.deps.controlState?.() ?? null);\n\t\treturn {\n\t\t\treport,\n\t\t\tbranchEntries,\n\t\t\trevision,\n\t\t\tsource,\n\t\t\tcontrolStateDigest: controlStateDigest(controlState),\n\t\t\tcontrolState,\n\t\t};\n\t}\n\n\tbarrierError(barrier: CompactionBarrierResult): Error {\n\t\tif (barrier.status === \"defer\") {\n\t\t\treturn new Error(`Compaction deferred until the transcript closes (${barrier.reason})`);\n\t\t}\n\t\treturn new Error(\n\t\t\t`Compaction failed closed on transcript integrity (${barrier.reason}). Run the session doctor before retrying.`,\n\t\t);\n\t}\n\n\tevaluateBarrier(\n\t\tcapture: CapturedCompactionState,\n\t\tincludeMissingTailAsPending: boolean,\n\t\texcludedPendingIds: ReadonlySet<string> = new Set(),\n\t): CompactionBarrierResult {\n\t\tconst pending = new Set([...this.deps.pendingToolCallIds()].filter((id) => !excludedPendingIds.has(id)));\n\t\tif (includeMissingTailAsPending) {\n\t\t\tfor (const issue of capture.report.transcript?.issues ?? []) {\n\t\t\t\tif (issue.kind === \"missing_result\") pending.add(issue.toolCallId);\n\t\t\t}\n\t\t}\n\t\treturn evaluateCompactionBarrier(capture.report, [...pending]);\n\t}\n\n\trepairEmergencyTail(capture: CapturedCompactionState): {\n\t\treadonly capture: CapturedCompactionState;\n\t\treadonly repairedToolCallIds: ReadonlySet<string>;\n\t} {\n\t\tconst { sessionManager } = this.deps;\n\t\tconst barrier = this.evaluateBarrier(capture, true);\n\t\tif (barrier.status !== \"defer\" || barrier.reason !== \"missing_active_tail_results\") {\n\t\t\tif (barrier.status !== \"ready\") throw this.barrierError(barrier);\n\t\t\treturn { capture, repairedToolCallIds: new Set() };\n\t\t}\n\t\tconst repairedMessages = repairTranscriptIntegrity(\n\t\t\t[...capture.report.activeMessages],\n\t\t\t\"Tool result missing; synthesized to close an emergency compaction barrier\",\n\t\t);\n\t\tconst inserted = repairedMessages.slice(capture.report.activeMessages.length);\n\t\tconst repairedToolCallIds = new Set<string>();\n\t\tfor (const message of inserted) {\n\t\t\tif (message.role !== \"toolResult\") {\n\t\t\t\tthrow new Error(\"Emergency compaction repair produced a non-tool result\");\n\t\t\t}\n\t\t\tconst toolResult: ToolResultMessage = message;\n\t\t\trepairedToolCallIds.add(toolResult.toolCallId);\n\t\t\tsessionManager.appendMessage(toolResult);\n\t\t}\n\t\tsessionManager.appendCustomEntry(PROVENANCE_CUSTOM_TYPES.compactionTranscriptRepaired, {\n\t\t\tinsertedToolCallIds: [...repairedToolCallIds],\n\t\t\treason: \"emergency_compaction\",\n\t\t});\n\t\tthis.deps.invalidateContextBudget();\n\t\tconst closedCapture = this.captureState();\n\t\tconst closedBarrier = this.evaluateBarrier(closedCapture, false, repairedToolCallIds);\n\t\tif (closedBarrier.status !== \"ready\") throw this.barrierError(closedBarrier);\n\t\tthis.deps.refreshAgentMessages();\n\t\treturn { capture: closedCapture, repairedToolCallIds };\n\t}\n\n\tpriorCommittedSourceDigests(): string[] {\n\t\tconst digests: string[] = [];\n\t\tfor (const entry of this.deps.sessionManager.getEntries()) {\n\t\t\tif (entry.type !== \"compaction\" || typeof entry.details !== \"object\" || entry.details === null) continue;\n\t\t\tif (Object.getOwnPropertyDescriptor(entry.details, \"compactionEnvelope\") === undefined) continue;\n\t\t\tconst envelope = validateCompactionEnvelope(Reflect.get(entry.details, \"compactionEnvelope\"));\n\t\t\tif (envelope.summary !== entry.summary) {\n\t\t\t\tthrow new Error(`Compaction entry ${entry.id} has invalid provenance. Run the session doctor.`);\n\t\t\t}\n\t\t\tdigests.push(envelope.source.sourceSha256);\n\t\t}\n\t\treturn digests;\n\t}\n\n\tbeginTransaction(compactionModel: Model<Api>, emergency: boolean): BegunCompaction {\n\t\tlet capture = this.captureState();\n\t\tif (emergency) {\n\t\t\tcapture = this.repairEmergencyTail(capture).capture;\n\t\t} else {\n\t\t\tconst barrier = this.evaluateBarrier(capture, false);\n\t\t\tif (barrier.status !== \"ready\") throw this.barrierError(barrier);\n\t\t}\n\t\tconst transaction = createCompactionTransaction({\n\t\t\ttransactionId: randomUUID(),\n\t\t\tbaseRevision: capture.revision,\n\t\t\tsource: capture.source,\n\t\t\tcreatedAt: new Date().toISOString(),\n\t\t\tmodel: { provider: compactionModel.provider, id: compactionModel.id },\n\t\t\tpreserved: buildPreservedProvenance(capture, this.deps.getUserMessageText, this.deps.cwd),\n\t\t});\n\t\tif (this.priorCommittedSourceDigests().includes(transaction.source.sourceSha256)) {\n\t\t\tthrow new Error(\"This exact compaction source was already compacted\");\n\t\t}\n\t\treturn { capture, transaction };\n\t}\n\n\tdetailsWithEnvelope(details: unknown, envelope: CompactionEnvelope): unknown {\n\t\tif (typeof details === \"object\" && details !== null && !Array.isArray(details)) {\n\t\t\treturn { ...details, compactionEnvelope: envelope };\n\t\t}\n\t\treturn {\n\t\t\tcompactionEnvelope: envelope,\n\t\t\t...(details === undefined ? {} : { resultDetails: details }),\n\t\t};\n\t}\n\n\tcommit(begun: BegunCompaction, result: CompactionResult, fromExtension: boolean): CommittedCompaction {\n\t\tif (!begun.transaction.source.entryIds.includes(result.firstKeptEntryId)) {\n\t\t\tthrow new Error(\"Compaction first-kept entry is outside the captured source\");\n\t\t}\n\t\tconst committed = this.deps.sessionManager.withCompactionCommitLock(() => {\n\t\t\tconst bytes = this.readSessionBytes();\n\t\t\tconst current = this.captureStateLocked(bytes);\n\t\t\tconst barrier = this.evaluateBarrier(current, false);\n\t\t\tconst { transaction, decision } = decideCommitOverInertTail({\n\t\t\t\ttransaction: begun.transaction,\n\t\t\t\tcurrentRevision: current.revision,\n\t\t\t\tcurrentSource: current.source,\n\t\t\t\tcurrentBytes: bytes,\n\t\t\t\tbarrier,\n\t\t\t\tpriorCommittedSourceDigests: this.priorCommittedSourceDigests(),\n\t\t\t\t// Extension state written during summarization must not livelock compaction,\n\t\t\t\t// but an extension-provided summary may read that state: never rebase it.\n\t\t\t\trebaseAllowed: !fromExtension,\n\t\t\t});\n\t\t\tswitch (decision.decision) {\n\t\t\t\tcase \"duplicate\":\n\t\t\t\t\tthrow new Error(\"This exact compaction source was already compacted\");\n\t\t\t\tcase \"stale\":\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`Session changed during compaction (${decision.reason}); generated summary was discarded`,\n\t\t\t\t\t);\n\t\t\t\tcase \"defer\":\n\t\t\t\tcase \"fail_closed\":\n\t\t\t\t\tthrow this.barrierError(barrier);\n\t\t\t\tcase \"commit\": {\n\t\t\t\t\tif (current.controlStateDigest !== begun.capture.controlStateDigest) {\n\t\t\t\t\t\tthrow new Error(\"Control state changed during compaction; generated summary was discarded\");\n\t\t\t\t\t}\n\t\t\t\t\tconst envelope = createCompactionEnvelope({\n\t\t\t\t\t\ttransaction,\n\t\t\t\t\t\tdecision,\n\t\t\t\t\t\tsummary: result.summary,\n\t\t\t\t\t\tsummarySha256: createHash(\"sha256\").update(result.summary, \"utf8\").digest(\"hex\"),\n\t\t\t\t\t});\n\t\t\t\t\tconst entryId = this.deps.sessionManager.appendCompaction(\n\t\t\t\t\t\tresult.summary,\n\t\t\t\t\t\tresult.firstKeptEntryId,\n\t\t\t\t\t\tresult.tokensBefore,\n\t\t\t\t\t\tthis.detailsWithEnvelope(result.details, envelope),\n\t\t\t\t\t\tfromExtension,\n\t\t\t\t\t);\n\t\t\t\t\tconst entry = this.deps.sessionManager.getEntry(entryId);\n\t\t\t\t\tif (!entry || entry.type !== \"compaction\") {\n\t\t\t\t\t\tthrow new Error(\"Compaction commit did not produce a compaction entry\");\n\t\t\t\t\t}\n\t\t\t\t\treturn { entry, envelope };\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t\tthis.deps.recordCommit();\n\t\treturn committed;\n\t}\n}\n"]}
|
|
@@ -10,10 +10,10 @@
|
|
|
10
10
|
import { createHash, randomUUID } from "node:crypto";
|
|
11
11
|
import { existsSync, readFileSync } from "node:fs";
|
|
12
12
|
import { repairTranscriptIntegrity } from "omk-agent-core";
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
13
|
+
import { controlStateDigest, validateControlState } from "./compaction/control-state.js";
|
|
14
|
+
import { decideCommitOverInertTail } from "./compaction/inert-tail.js";
|
|
15
|
+
import { buildPreservedProvenance, PROVENANCE_CUSTOM_TYPES } from "./compaction/provenance.js";
|
|
16
|
+
import { createCompactionEnvelope, createCompactionSourceIdentity, createCompactionTransaction, evaluateCompactionBarrier, validateCompactionEnvelope, } from "./compaction/transaction.js";
|
|
17
17
|
import { inspectSessionIntegrity } from "./session-integrity.js";
|
|
18
18
|
export class SessionCompactionService {
|
|
19
19
|
deps;
|
|
@@ -21,17 +21,20 @@ export class SessionCompactionService {
|
|
|
21
21
|
this.deps = deps;
|
|
22
22
|
}
|
|
23
23
|
captureState() {
|
|
24
|
-
return this.deps.sessionManager.withCompactionCommitLock(() => this.captureStateLocked());
|
|
24
|
+
return this.deps.sessionManager.withCompactionCommitLock(() => this.captureStateLocked(this.readSessionBytes()));
|
|
25
25
|
}
|
|
26
|
-
|
|
26
|
+
readSessionBytes() {
|
|
27
27
|
const { sessionManager } = this.deps;
|
|
28
28
|
const sessionFile = sessionManager.getSessionFile();
|
|
29
|
-
|
|
29
|
+
return sessionFile && existsSync(sessionFile)
|
|
30
30
|
? new Uint8Array(readFileSync(sessionFile))
|
|
31
31
|
: new TextEncoder().encode(`${[sessionManager.getHeader(), ...sessionManager.getEntries()]
|
|
32
32
|
.filter((entry) => entry !== null)
|
|
33
33
|
.map((entry) => JSON.stringify(entry))
|
|
34
34
|
.join("\n")}\n`);
|
|
35
|
+
}
|
|
36
|
+
captureStateLocked(bytes) {
|
|
37
|
+
const { sessionManager } = this.deps;
|
|
35
38
|
const report = inspectSessionIntegrity(bytes, { activeLeafId: sessionManager.getLeafId() });
|
|
36
39
|
const branchEntries = report.activeBranch;
|
|
37
40
|
let latestCompactionIndex = -1;
|
|
@@ -111,7 +114,7 @@ export class SessionCompactionService {
|
|
|
111
114
|
repairedToolCallIds.add(toolResult.toolCallId);
|
|
112
115
|
sessionManager.appendMessage(toolResult);
|
|
113
116
|
}
|
|
114
|
-
sessionManager.appendCustomEntry(
|
|
117
|
+
sessionManager.appendCustomEntry(PROVENANCE_CUSTOM_TYPES.compactionTranscriptRepaired, {
|
|
115
118
|
insertedToolCallIds: [...repairedToolCallIds],
|
|
116
119
|
reason: "emergency_compaction",
|
|
117
120
|
});
|
|
@@ -154,58 +157,13 @@ export class SessionCompactionService {
|
|
|
154
157
|
source: capture.source,
|
|
155
158
|
createdAt: new Date().toISOString(),
|
|
156
159
|
model: { provider: compactionModel.provider, id: compactionModel.id },
|
|
157
|
-
preserved: this.
|
|
160
|
+
preserved: buildPreservedProvenance(capture, this.deps.getUserMessageText, this.deps.cwd),
|
|
158
161
|
});
|
|
159
162
|
if (this.priorCommittedSourceDigests().includes(transaction.source.sourceSha256)) {
|
|
160
163
|
throw new Error("This exact compaction source was already compacted");
|
|
161
164
|
}
|
|
162
165
|
return { capture, transaction };
|
|
163
166
|
}
|
|
164
|
-
captureProvenance(capture) {
|
|
165
|
-
let latestIntent = "Continue the current session";
|
|
166
|
-
for (let index = capture.report.activeMessages.length - 1; index >= 0; index -= 1) {
|
|
167
|
-
const message = capture.report.activeMessages[index];
|
|
168
|
-
if (message?.role !== "user")
|
|
169
|
-
continue;
|
|
170
|
-
const candidate = redactCredentialShapedContent(sanitizeBinaryOutput(redactSensitiveTextForced(this.deps.getUserMessageText(message)).trim())
|
|
171
|
-
.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/gu, "")
|
|
172
|
-
.slice(0, 16_384));
|
|
173
|
-
if (candidate.length > 0)
|
|
174
|
-
latestIntent = candidate;
|
|
175
|
-
break;
|
|
176
|
-
}
|
|
177
|
-
const modelHistory = capture.branchEntries
|
|
178
|
-
.flatMap((entry) => {
|
|
179
|
-
if (entry.type === "model_change") {
|
|
180
|
-
return [{ entryId: entry.id, provider: entry.provider, modelId: entry.modelId }];
|
|
181
|
-
}
|
|
182
|
-
if (entry.type === "message" && entry.message.role === "assistant") {
|
|
183
|
-
return [{ entryId: entry.id, provider: entry.message.provider, modelId: entry.message.model }];
|
|
184
|
-
}
|
|
185
|
-
return [];
|
|
186
|
-
})
|
|
187
|
-
.slice(-256);
|
|
188
|
-
const customEntryIds = (customType) => capture.branchEntries
|
|
189
|
-
.filter((entry) => entry.type === "custom" && entry.customType === customType)
|
|
190
|
-
.map((entry) => entry.id);
|
|
191
|
-
const control = controlStateProvenance(capture.controlState);
|
|
192
|
-
return {
|
|
193
|
-
latestIntent,
|
|
194
|
-
openTasks: control.openTasks,
|
|
195
|
-
laneIds: customEntryIds("lane"),
|
|
196
|
-
acceptancePredicateIds: customEntryIds("acceptance_predicate"),
|
|
197
|
-
evidenceReceiptIds: customEntryIds("evidence_receipt"),
|
|
198
|
-
blockerReasons: control.blockerReasons,
|
|
199
|
-
repairEventIds: [
|
|
200
|
-
...customEntryIds("transcript_repaired"),
|
|
201
|
-
...customEntryIds("compaction_transcript_repaired"),
|
|
202
|
-
],
|
|
203
|
-
branch: control.branch,
|
|
204
|
-
worktree: this.deps.cwd,
|
|
205
|
-
modelHistory,
|
|
206
|
-
nextAction: redactCredentialShapedContent(latestIntent.slice(0, 4096)) || "Continue the current session",
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
167
|
detailsWithEnvelope(details, envelope) {
|
|
210
168
|
if (typeof details === "object" && details !== null && !Array.isArray(details)) {
|
|
211
169
|
return { ...details, compactionEnvelope: envelope };
|
|
@@ -220,14 +178,19 @@ export class SessionCompactionService {
|
|
|
220
178
|
throw new Error("Compaction first-kept entry is outside the captured source");
|
|
221
179
|
}
|
|
222
180
|
const committed = this.deps.sessionManager.withCompactionCommitLock(() => {
|
|
223
|
-
const
|
|
181
|
+
const bytes = this.readSessionBytes();
|
|
182
|
+
const current = this.captureStateLocked(bytes);
|
|
224
183
|
const barrier = this.evaluateBarrier(current, false);
|
|
225
|
-
const decision =
|
|
184
|
+
const { transaction, decision } = decideCommitOverInertTail({
|
|
226
185
|
transaction: begun.transaction,
|
|
227
186
|
currentRevision: current.revision,
|
|
228
187
|
currentSource: current.source,
|
|
188
|
+
currentBytes: bytes,
|
|
229
189
|
barrier,
|
|
230
190
|
priorCommittedSourceDigests: this.priorCommittedSourceDigests(),
|
|
191
|
+
// Extension state written during summarization must not livelock compaction,
|
|
192
|
+
// but an extension-provided summary may read that state: never rebase it.
|
|
193
|
+
rebaseAllowed: !fromExtension,
|
|
231
194
|
});
|
|
232
195
|
switch (decision.decision) {
|
|
233
196
|
case "duplicate":
|
|
@@ -242,7 +205,7 @@ export class SessionCompactionService {
|
|
|
242
205
|
throw new Error("Control state changed during compaction; generated summary was discarded");
|
|
243
206
|
}
|
|
244
207
|
const envelope = createCompactionEnvelope({
|
|
245
|
-
transaction
|
|
208
|
+
transaction,
|
|
246
209
|
decision,
|
|
247
210
|
summary: result.summary,
|
|
248
211
|
summarySha256: createHash("sha256").update(result.summary, "utf8").digest("hex"),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-compaction-service.js","sourceRoot":"","sources":["../../src/core/session-compaction-service.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Session compaction service: capture / barrier / repair / transaction / commit.\n *\n * Extracted from AgentSession so the session keeps lifecycle orchestration\n * (abort controllers, extension events, auth, model resolution) while this\n * owns the integrity-critical compaction state machine. Every method preserves\n * the exact semantics of the original session implementation — the\n * compaction-transaction / session-integrity suites pin them.\n */\nimport { createHash, randomUUID } from \"node:crypto\";\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { repairTranscriptIntegrity } from \"omk-agent-core\";\nimport type { Api, Message, Model, ToolResultMessage } from \"omk-ai\";\nimport { sanitizeBinaryOutput } from \"../utils/shell.ts\";\nimport {\n\ttype CompactionControlState,\n\tcontrolStateDigest,\n\tcontrolStateProvenance,\n\tvalidateControlState,\n} from \"./compaction/control-state.ts\";\nimport type { CompactionResult } from \"./compaction/index.ts\";\nimport {\n\ttype CompactionBarrierResult,\n\ttype CompactionEnvelope,\n\ttype CompactionPreservedProvenanceInput,\n\ttype CompactionSourceIdentity,\n\ttype CompactionTransaction,\n\tcreateCompactionEnvelope,\n\tcreateCompactionSourceIdentity,\n\tcreateCompactionTransaction,\n\tdecideCompactionCommit,\n\tevaluateCompactionBarrier,\n\tredactCredentialShapedContent,\n\tvalidateCompactionEnvelope,\n} from \"./compaction/transaction.ts\";\nimport { redactSensitiveTextForced } from \"./redaction.ts\";\nimport type { SessionIntegrityReport } from \"./session-integrity.ts\";\nimport { inspectSessionIntegrity } from \"./session-integrity.ts\";\nimport type { CompactionEntry, SessionEntry, SessionManager } from \"./session-manager.ts\";\n\nexport interface CapturedCompactionState {\n\treadonly report: SessionIntegrityReport;\n\treadonly branchEntries: readonly SessionEntry[];\n\treadonly revision: CompactionTransaction[\"baseRevision\"];\n\treadonly source: CompactionSourceIdentity;\n\t/**\n\t * Digest of the host control-state snapshot captured under the same commit\n\t * lock as `source`. `null` when no control authority is attached. A change\n\t * between capture and commit discards the summary like a source mismatch.\n\t */\n\treadonly controlStateDigest: string | null;\n\treadonly controlState: CompactionControlState | null;\n}\n\nexport interface BegunCompaction {\n\treadonly capture: CapturedCompactionState;\n\treadonly transaction: CompactionTransaction;\n}\n\nexport interface CommittedCompaction {\n\treadonly entry: CompactionEntry;\n\treadonly envelope: CompactionEnvelope;\n}\n\nexport interface SessionCompactionServiceDeps {\n\treadonly sessionManager: SessionManager;\n\treadonly pendingToolCallIds: () => ReadonlySet<string>;\n\treadonly getUserMessageText: (message: Message) => string;\n\treadonly cwd: string;\n\t/**\n\t * Optional host control authority. Called inside the compaction commit lock\n\t * so the snapshot is bound to the same captured source as the summary. Must\n\t * be deterministic for unchanged state — a changed snapshot discards the\n\t * in-flight summary at commit. Return `null` only when no authority exists.\n\t */\n\treadonly controlState?: () => CompactionControlState | null;\n\treadonly invalidateContextBudget: () => void;\n\t/** Refresh agent messages from the session manager after tail repair/commit. */\n\treadonly refreshAgentMessages: () => void;\n\t/** Hysteresis bookkeeping after a successful commit. */\n\treadonly recordCommit: () => void;\n}\n\nexport class SessionCompactionService {\n\tprivate readonly deps: SessionCompactionServiceDeps;\n\n\tconstructor(deps: SessionCompactionServiceDeps) {\n\t\tthis.deps = deps;\n\t}\n\n\tcaptureState(): CapturedCompactionState {\n\t\treturn this.deps.sessionManager.withCompactionCommitLock(() => this.captureStateLocked());\n\t}\n\n\tprivate captureStateLocked(): CapturedCompactionState {\n\t\tconst { sessionManager } = this.deps;\n\t\tconst sessionFile = sessionManager.getSessionFile();\n\t\tconst bytes =\n\t\t\tsessionFile && existsSync(sessionFile)\n\t\t\t\t? new Uint8Array(readFileSync(sessionFile))\n\t\t\t\t: new TextEncoder().encode(\n\t\t\t\t\t\t`${[sessionManager.getHeader(), ...sessionManager.getEntries()]\n\t\t\t\t\t\t\t.filter((entry) => entry !== null)\n\t\t\t\t\t\t\t.map((entry) => JSON.stringify(entry))\n\t\t\t\t\t\t\t.join(\"\\n\")}\\n`,\n\t\t\t\t\t);\n\t\tconst report = inspectSessionIntegrity(bytes, { activeLeafId: sessionManager.getLeafId() });\n\t\tconst branchEntries = report.activeBranch;\n\t\tlet latestCompactionIndex = -1;\n\t\tfor (let index = branchEntries.length - 1; index >= 0; index -= 1) {\n\t\t\tif (branchEntries[index]?.type === \"compaction\") {\n\t\t\t\tlatestCompactionIndex = index;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tconst latestCompaction = branchEntries[latestCompactionIndex];\n\t\tconst firstKeptIndex =\n\t\t\tlatestCompaction?.type === \"compaction\"\n\t\t\t\t? branchEntries.findIndex((entry) => entry.id === latestCompaction.firstKeptEntryId)\n\t\t\t\t: -1;\n\t\tconst sourceEntries = branchEntries.slice(\n\t\t\tlatestCompactionIndex < 0 ? 0 : firstKeptIndex < 0 ? latestCompactionIndex : firstKeptIndex,\n\t\t);\n\t\tconst firstEntry = sourceEntries[0];\n\t\tconst lastEntry = sourceEntries[sourceEntries.length - 1];\n\t\tif (!firstEntry || !lastEntry || report.activeLeafId === null) {\n\t\t\tconst barrier = evaluateCompactionBarrier(report, [...this.deps.pendingToolCallIds()]);\n\t\t\tif (barrier.status !== \"ready\") throw this.barrierError(barrier);\n\t\t\tthrow new Error(\"Nothing to compact: the active session branch is empty\");\n\t\t}\n\t\tconst revision = sessionManager.getDurableHeadToken();\n\t\tconst source = createCompactionSourceIdentity({\n\t\t\tsessionId: revision.sessionId,\n\t\t\tentryIds: sourceEntries.map((entry) => entry.id),\n\t\t\tfirstEntryId: firstEntry.id,\n\t\t\tlastEntryId: lastEntry.id,\n\t\t\tsourceSha256: createHash(\"sha256\")\n\t\t\t\t.update(sourceEntries.map((entry) => JSON.stringify(entry)).join(\"\\n\"), \"utf8\")\n\t\t\t\t.digest(\"hex\"),\n\t\t\tactiveLeafId: report.activeLeafId,\n\t\t\tmessageCount: report.activeMessages.length,\n\t\t});\n\t\tconst controlState = validateControlState(this.deps.controlState?.() ?? null);\n\t\treturn {\n\t\t\treport,\n\t\t\tbranchEntries,\n\t\t\trevision,\n\t\t\tsource,\n\t\t\tcontrolStateDigest: controlStateDigest(controlState),\n\t\t\tcontrolState,\n\t\t};\n\t}\n\n\tbarrierError(barrier: CompactionBarrierResult): Error {\n\t\tif (barrier.status === \"defer\") {\n\t\t\treturn new Error(`Compaction deferred until the transcript closes (${barrier.reason})`);\n\t\t}\n\t\treturn new Error(\n\t\t\t`Compaction failed closed on transcript integrity (${barrier.reason}). Run the session doctor before retrying.`,\n\t\t);\n\t}\n\n\tevaluateBarrier(\n\t\tcapture: CapturedCompactionState,\n\t\tincludeMissingTailAsPending: boolean,\n\t\texcludedPendingIds: ReadonlySet<string> = new Set(),\n\t): CompactionBarrierResult {\n\t\tconst pending = new Set([...this.deps.pendingToolCallIds()].filter((id) => !excludedPendingIds.has(id)));\n\t\tif (includeMissingTailAsPending) {\n\t\t\tfor (const issue of capture.report.transcript?.issues ?? []) {\n\t\t\t\tif (issue.kind === \"missing_result\") pending.add(issue.toolCallId);\n\t\t\t}\n\t\t}\n\t\treturn evaluateCompactionBarrier(capture.report, [...pending]);\n\t}\n\n\trepairEmergencyTail(capture: CapturedCompactionState): {\n\t\treadonly capture: CapturedCompactionState;\n\t\treadonly repairedToolCallIds: ReadonlySet<string>;\n\t} {\n\t\tconst { sessionManager } = this.deps;\n\t\tconst barrier = this.evaluateBarrier(capture, true);\n\t\tif (barrier.status !== \"defer\" || barrier.reason !== \"missing_active_tail_results\") {\n\t\t\tif (barrier.status !== \"ready\") throw this.barrierError(barrier);\n\t\t\treturn { capture, repairedToolCallIds: new Set() };\n\t\t}\n\t\tconst repairedMessages = repairTranscriptIntegrity(\n\t\t\t[...capture.report.activeMessages],\n\t\t\t\"Tool result missing; synthesized to close an emergency compaction barrier\",\n\t\t);\n\t\tconst inserted = repairedMessages.slice(capture.report.activeMessages.length);\n\t\tconst repairedToolCallIds = new Set<string>();\n\t\tfor (const message of inserted) {\n\t\t\tif (message.role !== \"toolResult\") {\n\t\t\t\tthrow new Error(\"Emergency compaction repair produced a non-tool result\");\n\t\t\t}\n\t\t\tconst toolResult: ToolResultMessage = message;\n\t\t\trepairedToolCallIds.add(toolResult.toolCallId);\n\t\t\tsessionManager.appendMessage(toolResult);\n\t\t}\n\t\tsessionManager.appendCustomEntry(\"compaction_transcript_repaired\", {\n\t\t\tinsertedToolCallIds: [...repairedToolCallIds],\n\t\t\treason: \"emergency_compaction\",\n\t\t});\n\t\tthis.deps.invalidateContextBudget();\n\t\tconst closedCapture = this.captureState();\n\t\tconst closedBarrier = this.evaluateBarrier(closedCapture, false, repairedToolCallIds);\n\t\tif (closedBarrier.status !== \"ready\") throw this.barrierError(closedBarrier);\n\t\tthis.deps.refreshAgentMessages();\n\t\treturn { capture: closedCapture, repairedToolCallIds };\n\t}\n\n\tpriorCommittedSourceDigests(): string[] {\n\t\tconst digests: string[] = [];\n\t\tfor (const entry of this.deps.sessionManager.getEntries()) {\n\t\t\tif (entry.type !== \"compaction\" || typeof entry.details !== \"object\" || entry.details === null) continue;\n\t\t\tif (Object.getOwnPropertyDescriptor(entry.details, \"compactionEnvelope\") === undefined) continue;\n\t\t\tconst envelope = validateCompactionEnvelope(Reflect.get(entry.details, \"compactionEnvelope\"));\n\t\t\tif (envelope.summary !== entry.summary) {\n\t\t\t\tthrow new Error(`Compaction entry ${entry.id} has invalid provenance. Run the session doctor.`);\n\t\t\t}\n\t\t\tdigests.push(envelope.source.sourceSha256);\n\t\t}\n\t\treturn digests;\n\t}\n\n\tbeginTransaction(compactionModel: Model<Api>, emergency: boolean): BegunCompaction {\n\t\tlet capture = this.captureState();\n\t\tif (emergency) {\n\t\t\tcapture = this.repairEmergencyTail(capture).capture;\n\t\t} else {\n\t\t\tconst barrier = this.evaluateBarrier(capture, false);\n\t\t\tif (barrier.status !== \"ready\") throw this.barrierError(barrier);\n\t\t}\n\t\tconst transaction = createCompactionTransaction({\n\t\t\ttransactionId: randomUUID(),\n\t\t\tbaseRevision: capture.revision,\n\t\t\tsource: capture.source,\n\t\t\tcreatedAt: new Date().toISOString(),\n\t\t\tmodel: { provider: compactionModel.provider, id: compactionModel.id },\n\t\t\tpreserved: this.captureProvenance(capture),\n\t\t});\n\t\tif (this.priorCommittedSourceDigests().includes(transaction.source.sourceSha256)) {\n\t\t\tthrow new Error(\"This exact compaction source was already compacted\");\n\t\t}\n\t\treturn { capture, transaction };\n\t}\n\n\tcaptureProvenance(capture: CapturedCompactionState): CompactionPreservedProvenanceInput {\n\t\tlet latestIntent = \"Continue the current session\";\n\t\tfor (let index = capture.report.activeMessages.length - 1; index >= 0; index -= 1) {\n\t\t\tconst message = capture.report.activeMessages[index];\n\t\t\tif (message?.role !== \"user\") continue;\n\t\t\tconst candidate = redactCredentialShapedContent(\n\t\t\t\tsanitizeBinaryOutput(redactSensitiveTextForced(this.deps.getUserMessageText(message)).trim())\n\t\t\t\t\t.replace(/[\\u0000-\\u0008\\u000b\\u000c\\u000e-\\u001f\\u007f]/gu, \"\")\n\t\t\t\t\t.slice(0, 16_384),\n\t\t\t);\n\t\t\tif (candidate.length > 0) latestIntent = candidate;\n\t\t\tbreak;\n\t\t}\n\t\tconst modelHistory = capture.branchEntries\n\t\t\t.flatMap((entry) => {\n\t\t\t\tif (entry.type === \"model_change\") {\n\t\t\t\t\treturn [{ entryId: entry.id, provider: entry.provider, modelId: entry.modelId }];\n\t\t\t\t}\n\t\t\t\tif (entry.type === \"message\" && entry.message.role === \"assistant\") {\n\t\t\t\t\treturn [{ entryId: entry.id, provider: entry.message.provider, modelId: entry.message.model }];\n\t\t\t\t}\n\t\t\t\treturn [];\n\t\t\t})\n\t\t\t.slice(-256);\n\t\tconst customEntryIds = (customType: string): string[] =>\n\t\t\tcapture.branchEntries\n\t\t\t\t.filter((entry) => entry.type === \"custom\" && entry.customType === customType)\n\t\t\t\t.map((entry) => entry.id);\n\t\tconst control = controlStateProvenance(capture.controlState);\n\t\treturn {\n\t\t\tlatestIntent,\n\t\t\topenTasks: control.openTasks,\n\t\t\tlaneIds: customEntryIds(\"lane\"),\n\t\t\tacceptancePredicateIds: customEntryIds(\"acceptance_predicate\"),\n\t\t\tevidenceReceiptIds: customEntryIds(\"evidence_receipt\"),\n\t\t\tblockerReasons: control.blockerReasons,\n\t\t\trepairEventIds: [\n\t\t\t\t...customEntryIds(\"transcript_repaired\"),\n\t\t\t\t...customEntryIds(\"compaction_transcript_repaired\"),\n\t\t\t],\n\t\t\tbranch: control.branch,\n\t\t\tworktree: this.deps.cwd,\n\t\t\tmodelHistory,\n\t\t\tnextAction: redactCredentialShapedContent(latestIntent.slice(0, 4096)) || \"Continue the current session\",\n\t\t};\n\t}\n\n\tdetailsWithEnvelope(details: unknown, envelope: CompactionEnvelope): unknown {\n\t\tif (typeof details === \"object\" && details !== null && !Array.isArray(details)) {\n\t\t\treturn { ...details, compactionEnvelope: envelope };\n\t\t}\n\t\treturn {\n\t\t\tcompactionEnvelope: envelope,\n\t\t\t...(details === undefined ? {} : { resultDetails: details }),\n\t\t};\n\t}\n\n\tcommit(begun: BegunCompaction, result: CompactionResult, fromExtension: boolean): CommittedCompaction {\n\t\tif (!begun.transaction.source.entryIds.includes(result.firstKeptEntryId)) {\n\t\t\tthrow new Error(\"Compaction first-kept entry is outside the captured source\");\n\t\t}\n\t\tconst committed = this.deps.sessionManager.withCompactionCommitLock(() => {\n\t\t\tconst current = this.captureState();\n\t\t\tconst barrier = this.evaluateBarrier(current, false);\n\t\t\tconst decision = decideCompactionCommit({\n\t\t\t\ttransaction: begun.transaction,\n\t\t\t\tcurrentRevision: current.revision,\n\t\t\t\tcurrentSource: current.source,\n\t\t\t\tbarrier,\n\t\t\t\tpriorCommittedSourceDigests: this.priorCommittedSourceDigests(),\n\t\t\t});\n\t\t\tswitch (decision.decision) {\n\t\t\t\tcase \"duplicate\":\n\t\t\t\t\tthrow new Error(\"This exact compaction source was already compacted\");\n\t\t\t\tcase \"stale\":\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`Session changed during compaction (${decision.reason}); generated summary was discarded`,\n\t\t\t\t\t);\n\t\t\t\tcase \"defer\":\n\t\t\t\tcase \"fail_closed\":\n\t\t\t\t\tthrow this.barrierError(barrier);\n\t\t\t\tcase \"commit\": {\n\t\t\t\t\tif (current.controlStateDigest !== begun.capture.controlStateDigest) {\n\t\t\t\t\t\tthrow new Error(\"Control state changed during compaction; generated summary was discarded\");\n\t\t\t\t\t}\n\t\t\t\t\tconst envelope = createCompactionEnvelope({\n\t\t\t\t\t\ttransaction: begun.transaction,\n\t\t\t\t\t\tdecision,\n\t\t\t\t\t\tsummary: result.summary,\n\t\t\t\t\t\tsummarySha256: createHash(\"sha256\").update(result.summary, \"utf8\").digest(\"hex\"),\n\t\t\t\t\t});\n\t\t\t\t\tconst entryId = this.deps.sessionManager.appendCompaction(\n\t\t\t\t\t\tresult.summary,\n\t\t\t\t\t\tresult.firstKeptEntryId,\n\t\t\t\t\t\tresult.tokensBefore,\n\t\t\t\t\t\tthis.detailsWithEnvelope(result.details, envelope),\n\t\t\t\t\t\tfromExtension,\n\t\t\t\t\t);\n\t\t\t\t\tconst entry = this.deps.sessionManager.getEntry(entryId);\n\t\t\t\t\tif (!entry || entry.type !== \"compaction\") {\n\t\t\t\t\t\tthrow new Error(\"Compaction commit did not produce a compaction entry\");\n\t\t\t\t\t}\n\t\t\t\t\treturn { entry, envelope };\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t\tthis.deps.recordCommit();\n\t\treturn committed;\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"session-compaction-service.js","sourceRoot":"","sources":["../../src/core/session-compaction-service.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Session compaction service: capture / barrier / repair / transaction / commit.\n *\n * Extracted from AgentSession so the session keeps lifecycle orchestration\n * (abort controllers, extension events, auth, model resolution) while this\n * owns the integrity-critical compaction state machine. Every method preserves\n * the exact semantics of the original session implementation — the\n * compaction-transaction / session-integrity suites pin them.\n */\nimport { createHash, randomUUID } from \"node:crypto\";\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { repairTranscriptIntegrity } from \"omk-agent-core\";\nimport type { Api, Message, Model, ToolResultMessage } from \"omk-ai\";\nimport { type CompactionControlState, controlStateDigest, validateControlState } from \"./compaction/control-state.ts\";\nimport type { CompactionResult } from \"./compaction/index.ts\";\nimport { decideCommitOverInertTail } from \"./compaction/inert-tail.ts\";\nimport { buildPreservedProvenance, PROVENANCE_CUSTOM_TYPES } from \"./compaction/provenance.ts\";\nimport {\n\ttype CompactionBarrierResult,\n\ttype CompactionEnvelope,\n\ttype CompactionSourceIdentity,\n\ttype CompactionTransaction,\n\tcreateCompactionEnvelope,\n\tcreateCompactionSourceIdentity,\n\tcreateCompactionTransaction,\n\tevaluateCompactionBarrier,\n\tvalidateCompactionEnvelope,\n} from \"./compaction/transaction.ts\";\nimport type { SessionIntegrityReport } from \"./session-integrity.ts\";\nimport { inspectSessionIntegrity } from \"./session-integrity.ts\";\nimport type { CompactionEntry, SessionEntry, SessionManager } from \"./session-manager.ts\";\n\nexport interface CapturedCompactionState {\n\treadonly report: SessionIntegrityReport;\n\treadonly branchEntries: readonly SessionEntry[];\n\treadonly revision: CompactionTransaction[\"baseRevision\"];\n\treadonly source: CompactionSourceIdentity;\n\t/**\n\t * Digest of the host control-state snapshot captured under the same commit\n\t * lock as `source`. `null` when no control authority is attached. A change\n\t * between capture and commit discards the summary like a source mismatch.\n\t */\n\treadonly controlStateDigest: string | null;\n\treadonly controlState: CompactionControlState | null;\n}\n\nexport interface BegunCompaction {\n\treadonly capture: CapturedCompactionState;\n\treadonly transaction: CompactionTransaction;\n}\n\nexport interface CommittedCompaction {\n\treadonly entry: CompactionEntry;\n\treadonly envelope: CompactionEnvelope;\n}\n\nexport interface SessionCompactionServiceDeps {\n\treadonly sessionManager: SessionManager;\n\treadonly pendingToolCallIds: () => ReadonlySet<string>;\n\treadonly getUserMessageText: (message: Message) => string;\n\treadonly cwd: string;\n\t/**\n\t * Optional host control authority. Called inside the compaction commit lock\n\t * so the snapshot is bound to the same captured source as the summary. Must\n\t * be deterministic for unchanged state — a changed snapshot discards the\n\t * in-flight summary at commit. Return `null` only when no authority exists.\n\t */\n\treadonly controlState?: () => CompactionControlState | null;\n\treadonly invalidateContextBudget: () => void;\n\t/** Refresh agent messages from the session manager after tail repair/commit. */\n\treadonly refreshAgentMessages: () => void;\n\t/** Hysteresis bookkeeping after a successful commit. */\n\treadonly recordCommit: () => void;\n}\n\nexport class SessionCompactionService {\n\tprivate readonly deps: SessionCompactionServiceDeps;\n\n\tconstructor(deps: SessionCompactionServiceDeps) {\n\t\tthis.deps = deps;\n\t}\n\n\tcaptureState(): CapturedCompactionState {\n\t\treturn this.deps.sessionManager.withCompactionCommitLock(() => this.captureStateLocked(this.readSessionBytes()));\n\t}\n\n\tprivate readSessionBytes(): Uint8Array {\n\t\tconst { sessionManager } = this.deps;\n\t\tconst sessionFile = sessionManager.getSessionFile();\n\t\treturn sessionFile && existsSync(sessionFile)\n\t\t\t? new Uint8Array(readFileSync(sessionFile))\n\t\t\t: new TextEncoder().encode(\n\t\t\t\t\t`${[sessionManager.getHeader(), ...sessionManager.getEntries()]\n\t\t\t\t\t\t.filter((entry) => entry !== null)\n\t\t\t\t\t\t.map((entry) => JSON.stringify(entry))\n\t\t\t\t\t\t.join(\"\\n\")}\\n`,\n\t\t\t\t);\n\t}\n\n\tprivate captureStateLocked(bytes: Uint8Array): CapturedCompactionState {\n\t\tconst { sessionManager } = this.deps;\n\t\tconst report = inspectSessionIntegrity(bytes, { activeLeafId: sessionManager.getLeafId() });\n\t\tconst branchEntries = report.activeBranch;\n\t\tlet latestCompactionIndex = -1;\n\t\tfor (let index = branchEntries.length - 1; index >= 0; index -= 1) {\n\t\t\tif (branchEntries[index]?.type === \"compaction\") {\n\t\t\t\tlatestCompactionIndex = index;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tconst latestCompaction = branchEntries[latestCompactionIndex];\n\t\tconst firstKeptIndex =\n\t\t\tlatestCompaction?.type === \"compaction\"\n\t\t\t\t? branchEntries.findIndex((entry) => entry.id === latestCompaction.firstKeptEntryId)\n\t\t\t\t: -1;\n\t\tconst sourceEntries = branchEntries.slice(\n\t\t\tlatestCompactionIndex < 0 ? 0 : firstKeptIndex < 0 ? latestCompactionIndex : firstKeptIndex,\n\t\t);\n\t\tconst firstEntry = sourceEntries[0];\n\t\tconst lastEntry = sourceEntries[sourceEntries.length - 1];\n\t\tif (!firstEntry || !lastEntry || report.activeLeafId === null) {\n\t\t\tconst barrier = evaluateCompactionBarrier(report, [...this.deps.pendingToolCallIds()]);\n\t\t\tif (barrier.status !== \"ready\") throw this.barrierError(barrier);\n\t\t\tthrow new Error(\"Nothing to compact: the active session branch is empty\");\n\t\t}\n\t\tconst revision = sessionManager.getDurableHeadToken();\n\t\tconst source = createCompactionSourceIdentity({\n\t\t\tsessionId: revision.sessionId,\n\t\t\tentryIds: sourceEntries.map((entry) => entry.id),\n\t\t\tfirstEntryId: firstEntry.id,\n\t\t\tlastEntryId: lastEntry.id,\n\t\t\tsourceSha256: createHash(\"sha256\")\n\t\t\t\t.update(sourceEntries.map((entry) => JSON.stringify(entry)).join(\"\\n\"), \"utf8\")\n\t\t\t\t.digest(\"hex\"),\n\t\t\tactiveLeafId: report.activeLeafId,\n\t\t\tmessageCount: report.activeMessages.length,\n\t\t});\n\t\tconst controlState = validateControlState(this.deps.controlState?.() ?? null);\n\t\treturn {\n\t\t\treport,\n\t\t\tbranchEntries,\n\t\t\trevision,\n\t\t\tsource,\n\t\t\tcontrolStateDigest: controlStateDigest(controlState),\n\t\t\tcontrolState,\n\t\t};\n\t}\n\n\tbarrierError(barrier: CompactionBarrierResult): Error {\n\t\tif (barrier.status === \"defer\") {\n\t\t\treturn new Error(`Compaction deferred until the transcript closes (${barrier.reason})`);\n\t\t}\n\t\treturn new Error(\n\t\t\t`Compaction failed closed on transcript integrity (${barrier.reason}). Run the session doctor before retrying.`,\n\t\t);\n\t}\n\n\tevaluateBarrier(\n\t\tcapture: CapturedCompactionState,\n\t\tincludeMissingTailAsPending: boolean,\n\t\texcludedPendingIds: ReadonlySet<string> = new Set(),\n\t): CompactionBarrierResult {\n\t\tconst pending = new Set([...this.deps.pendingToolCallIds()].filter((id) => !excludedPendingIds.has(id)));\n\t\tif (includeMissingTailAsPending) {\n\t\t\tfor (const issue of capture.report.transcript?.issues ?? []) {\n\t\t\t\tif (issue.kind === \"missing_result\") pending.add(issue.toolCallId);\n\t\t\t}\n\t\t}\n\t\treturn evaluateCompactionBarrier(capture.report, [...pending]);\n\t}\n\n\trepairEmergencyTail(capture: CapturedCompactionState): {\n\t\treadonly capture: CapturedCompactionState;\n\t\treadonly repairedToolCallIds: ReadonlySet<string>;\n\t} {\n\t\tconst { sessionManager } = this.deps;\n\t\tconst barrier = this.evaluateBarrier(capture, true);\n\t\tif (barrier.status !== \"defer\" || barrier.reason !== \"missing_active_tail_results\") {\n\t\t\tif (barrier.status !== \"ready\") throw this.barrierError(barrier);\n\t\t\treturn { capture, repairedToolCallIds: new Set() };\n\t\t}\n\t\tconst repairedMessages = repairTranscriptIntegrity(\n\t\t\t[...capture.report.activeMessages],\n\t\t\t\"Tool result missing; synthesized to close an emergency compaction barrier\",\n\t\t);\n\t\tconst inserted = repairedMessages.slice(capture.report.activeMessages.length);\n\t\tconst repairedToolCallIds = new Set<string>();\n\t\tfor (const message of inserted) {\n\t\t\tif (message.role !== \"toolResult\") {\n\t\t\t\tthrow new Error(\"Emergency compaction repair produced a non-tool result\");\n\t\t\t}\n\t\t\tconst toolResult: ToolResultMessage = message;\n\t\t\trepairedToolCallIds.add(toolResult.toolCallId);\n\t\t\tsessionManager.appendMessage(toolResult);\n\t\t}\n\t\tsessionManager.appendCustomEntry(PROVENANCE_CUSTOM_TYPES.compactionTranscriptRepaired, {\n\t\t\tinsertedToolCallIds: [...repairedToolCallIds],\n\t\t\treason: \"emergency_compaction\",\n\t\t});\n\t\tthis.deps.invalidateContextBudget();\n\t\tconst closedCapture = this.captureState();\n\t\tconst closedBarrier = this.evaluateBarrier(closedCapture, false, repairedToolCallIds);\n\t\tif (closedBarrier.status !== \"ready\") throw this.barrierError(closedBarrier);\n\t\tthis.deps.refreshAgentMessages();\n\t\treturn { capture: closedCapture, repairedToolCallIds };\n\t}\n\n\tpriorCommittedSourceDigests(): string[] {\n\t\tconst digests: string[] = [];\n\t\tfor (const entry of this.deps.sessionManager.getEntries()) {\n\t\t\tif (entry.type !== \"compaction\" || typeof entry.details !== \"object\" || entry.details === null) continue;\n\t\t\tif (Object.getOwnPropertyDescriptor(entry.details, \"compactionEnvelope\") === undefined) continue;\n\t\t\tconst envelope = validateCompactionEnvelope(Reflect.get(entry.details, \"compactionEnvelope\"));\n\t\t\tif (envelope.summary !== entry.summary) {\n\t\t\t\tthrow new Error(`Compaction entry ${entry.id} has invalid provenance. Run the session doctor.`);\n\t\t\t}\n\t\t\tdigests.push(envelope.source.sourceSha256);\n\t\t}\n\t\treturn digests;\n\t}\n\n\tbeginTransaction(compactionModel: Model<Api>, emergency: boolean): BegunCompaction {\n\t\tlet capture = this.captureState();\n\t\tif (emergency) {\n\t\t\tcapture = this.repairEmergencyTail(capture).capture;\n\t\t} else {\n\t\t\tconst barrier = this.evaluateBarrier(capture, false);\n\t\t\tif (barrier.status !== \"ready\") throw this.barrierError(barrier);\n\t\t}\n\t\tconst transaction = createCompactionTransaction({\n\t\t\ttransactionId: randomUUID(),\n\t\t\tbaseRevision: capture.revision,\n\t\t\tsource: capture.source,\n\t\t\tcreatedAt: new Date().toISOString(),\n\t\t\tmodel: { provider: compactionModel.provider, id: compactionModel.id },\n\t\t\tpreserved: buildPreservedProvenance(capture, this.deps.getUserMessageText, this.deps.cwd),\n\t\t});\n\t\tif (this.priorCommittedSourceDigests().includes(transaction.source.sourceSha256)) {\n\t\t\tthrow new Error(\"This exact compaction source was already compacted\");\n\t\t}\n\t\treturn { capture, transaction };\n\t}\n\n\tdetailsWithEnvelope(details: unknown, envelope: CompactionEnvelope): unknown {\n\t\tif (typeof details === \"object\" && details !== null && !Array.isArray(details)) {\n\t\t\treturn { ...details, compactionEnvelope: envelope };\n\t\t}\n\t\treturn {\n\t\t\tcompactionEnvelope: envelope,\n\t\t\t...(details === undefined ? {} : { resultDetails: details }),\n\t\t};\n\t}\n\n\tcommit(begun: BegunCompaction, result: CompactionResult, fromExtension: boolean): CommittedCompaction {\n\t\tif (!begun.transaction.source.entryIds.includes(result.firstKeptEntryId)) {\n\t\t\tthrow new Error(\"Compaction first-kept entry is outside the captured source\");\n\t\t}\n\t\tconst committed = this.deps.sessionManager.withCompactionCommitLock(() => {\n\t\t\tconst bytes = this.readSessionBytes();\n\t\t\tconst current = this.captureStateLocked(bytes);\n\t\t\tconst barrier = this.evaluateBarrier(current, false);\n\t\t\tconst { transaction, decision } = decideCommitOverInertTail({\n\t\t\t\ttransaction: begun.transaction,\n\t\t\t\tcurrentRevision: current.revision,\n\t\t\t\tcurrentSource: current.source,\n\t\t\t\tcurrentBytes: bytes,\n\t\t\t\tbarrier,\n\t\t\t\tpriorCommittedSourceDigests: this.priorCommittedSourceDigests(),\n\t\t\t\t// Extension state written during summarization must not livelock compaction,\n\t\t\t\t// but an extension-provided summary may read that state: never rebase it.\n\t\t\t\trebaseAllowed: !fromExtension,\n\t\t\t});\n\t\t\tswitch (decision.decision) {\n\t\t\t\tcase \"duplicate\":\n\t\t\t\t\tthrow new Error(\"This exact compaction source was already compacted\");\n\t\t\t\tcase \"stale\":\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`Session changed during compaction (${decision.reason}); generated summary was discarded`,\n\t\t\t\t\t);\n\t\t\t\tcase \"defer\":\n\t\t\t\tcase \"fail_closed\":\n\t\t\t\t\tthrow this.barrierError(barrier);\n\t\t\t\tcase \"commit\": {\n\t\t\t\t\tif (current.controlStateDigest !== begun.capture.controlStateDigest) {\n\t\t\t\t\t\tthrow new Error(\"Control state changed during compaction; generated summary was discarded\");\n\t\t\t\t\t}\n\t\t\t\t\tconst envelope = createCompactionEnvelope({\n\t\t\t\t\t\ttransaction,\n\t\t\t\t\t\tdecision,\n\t\t\t\t\t\tsummary: result.summary,\n\t\t\t\t\t\tsummarySha256: createHash(\"sha256\").update(result.summary, \"utf8\").digest(\"hex\"),\n\t\t\t\t\t});\n\t\t\t\t\tconst entryId = this.deps.sessionManager.appendCompaction(\n\t\t\t\t\t\tresult.summary,\n\t\t\t\t\t\tresult.firstKeptEntryId,\n\t\t\t\t\t\tresult.tokensBefore,\n\t\t\t\t\t\tthis.detailsWithEnvelope(result.details, envelope),\n\t\t\t\t\t\tfromExtension,\n\t\t\t\t\t);\n\t\t\t\t\tconst entry = this.deps.sessionManager.getEntry(entryId);\n\t\t\t\t\tif (!entry || entry.type !== \"compaction\") {\n\t\t\t\t\t\tthrow new Error(\"Compaction commit did not produce a compaction entry\");\n\t\t\t\t\t}\n\t\t\t\t\treturn { entry, envelope };\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t\tthis.deps.recordCommit();\n\t\treturn committed;\n\t}\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"owned-execution.d.ts","sourceRoot":"","sources":["../../../src/core/verified-run/owned-execution.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAkB,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAIvD,MAAM,WAAW,eAAe;IAC/B,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,UAAU,CAAC;IACrC,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CACzB;AACD,MAAM,WAAW,cAAc;IAC9B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;IAChC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC3B;AAkCD;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CACtC,OAAO,EAAE,kBAAkB,EAC3B,OAAO,EAAE,eAAe,EACxB,MAAM,EAAE;IACP,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,SAAS,EAAE,YAAY,CAAC;IACjC,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;CAC9B,GACC,OAAO,CAAC,cAAc,CAAC,
|
|
1
|
+
{"version":3,"file":"owned-execution.d.ts","sourceRoot":"","sources":["../../../src/core/verified-run/owned-execution.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAkB,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAIvD,MAAM,WAAW,eAAe;IAC/B,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,UAAU,CAAC;IACrC,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CACzB;AACD,MAAM,WAAW,cAAc;IAC9B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;IAChC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC3B;AAkCD;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CACtC,OAAO,EAAE,kBAAkB,EAC3B,OAAO,EAAE,eAAe,EACxB,MAAM,EAAE;IACP,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,SAAS,EAAE,YAAY,CAAC;IACjC,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;CAC9B,GACC,OAAO,CAAC,cAAc,CAAC,CAuGzB","sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { realpathSync } from \"node:fs\";\nimport { performance } from \"node:perf_hooks\";\nimport type { ResourceClaimInput } from \"../../coordination/resource.ts\";\nimport type { GrantToken } from \"../../coordination/types.ts\";\nimport type { RunAuthority } from \"./authority-runtime.ts\";\nimport { executeSandbox, type SandboxOutcome } from \"./broker.ts\";\nimport type { VerifiedRunJournal } from \"./journal.ts\";\nimport type { NamespaceIdentity } from \"./namespace-identity.ts\";\nimport { digestObject, VerifiedRunError } from \"./storage.ts\";\n\nexport interface OwnedRunCommand {\n\treadonly role: \"writer\" | \"verifier\";\n\treadonly argv: readonly string[];\n\treadonly workspace: string;\n\treadonly deadline: number;\n\treadonly claimId: string | null;\n\treadonly taskId?: string;\n}\nexport interface OwnedRunResult {\n\treadonly executionId: string;\n\treadonly result: SandboxOutcome;\n\treadonly timeoutMs: number;\n}\n\n/**\n * The filesystem claim a dispatched process is authorized for: exactly the\n * workspace bind it receives inside the namespace — nothing wider. Keys are\n * the resolved absolute path minus the leading slash so prefix containment\n * matches real tree containment; `realpathSync` keeps two spellings of the\n * same directory one claim.\n */\nfunction workspaceClaim(\n\tworkspace: string,\n\taccess: \"read\" | \"write\",\n\tgeneration: number,\n): readonly ResourceClaimInput[] {\n\tlet root: string;\n\ttry {\n\t\troot = realpathSync(workspace);\n\t} catch {\n\t\tthrow new VerifiedRunError(\"authority_claim\");\n\t}\n\treturn Object.freeze([\n\t\t{\n\t\t\tnamespace: \"filesystem\",\n\t\t\tinstanceId: \"verified-run\",\n\t\t\tcanonicalKey: root.replace(/^\\/+/, \"\"),\n\t\t\taccess,\n\t\t\tgeneration: String(generation),\n\t\t},\n\t]);\n}\n\n/** Rejection codes that can only be produced before the supervisor spawned. */\nconst NEVER_SPAWNED = new Set([\"cancelled\", \"deadline\", \"unsupported_boundary\"]);\n\n/**\n * Mandatory durable dispatch intent, shared by command and AgentSession\n * writers. There is no ungated execution lane: a journal without an anchored\n * budget cannot commit a process identity, so the supervisor boundary would\n * be unprovable and must refuse dispatch. Every dispatch now also crosses the\n * durable authority boundary — grant → intent → start → termination witness\n * — so a restart can reconcile effects the journal alone cannot settle.\n */\nexport async function executeRunCommand(\n\tjournal: VerifiedRunJournal,\n\trequest: OwnedRunCommand,\n\tpolicy: {\n\t\treadonly cleanupMs: number;\n\t\treadonly maxOutputBytes: number;\n\t\treadonly authority: RunAuthority;\n\t\treadonly signal?: AbortSignal;\n\t},\n): Promise<OwnedRunResult> {\n\tif (Math.floor(request.deadline - performance.now()) <= 0) throw new VerifiedRunError(\"deadline\");\n\tif (policy.signal?.aborted) throw new VerifiedRunError(\"cancelled\");\n\t// The supervisor boundary only exists once a budget (and therefore the\n\t// gated driver) is anchored. Dispatching without it would run code whose\n\t// termination cannot be witnessed — refuse rather than degrade.\n\tif (!journal.state.budget) throw new VerifiedRunError(\"unsupported_boundary\");\n\tconst authority = policy.authority;\n\tconst generation = journal.state.generation;\n\tconst executionId = randomUUID();\n\tconst claims = workspaceClaim(request.workspace, request.role === \"writer\" ? \"write\" : \"read\", generation);\n\tconst timeoutMs = Math.floor(request.deadline - performance.now());\n\tconst admission = authority.store.acquire({\n\t\tsessionId: authority.sessionId,\n\t\tincarnation: authority.incarnation,\n\t\tcommandId: executionId,\n\t\tintentDigest: digestObject({ argv: request.argv, claims, role: request.role }),\n\t\tclaims,\n\t\tnow: Date.now(),\n\t\tttl: timeoutMs + policy.cleanupMs + 30000,\n\t});\n\tif (admission.status !== \"granted\") {\n\t\t// A fresh executionId can only collide with a conflicting or wedged\n\t\t// claim — quarantined effects hold theirs until witnessed (fail closed).\n\t\tthrow new VerifiedRunError(\"authority_blocked\");\n\t}\n\tconst token: GrantToken = admission.token;\n\tlet supervisorInvoked = false;\n\tlet readyObserved = false;\n\tlet result: SandboxOutcome;\n\ttry {\n\t\tjournal.append({\n\t\t\tkind: \"dispatch\",\n\t\t\texecutionId,\n\t\t\trole: request.role,\n\t\t\tclaimId: request.claimId,\n\t\t\t...(request.taskId === undefined ? {} : { taskId: request.taskId }),\n\t\t});\n\t\tif (timeoutMs <= 0 || policy.signal?.aborted) {\n\t\t\tconst failure = policy.signal?.aborted ? \"cancelled\" : \"deadline\";\n\t\t\t// Provably pre-spawn: the intent never reached the adapter, so the grant\n\t\t\t// settles immediately instead of wedging as an unwitnessed live effect.\n\t\t\tauthority.store.cancel(token);\n\t\t\tauthority.store.confirmTerminated(token);\n\t\t\tjournal.append({ kind: \"exited\", executionId, failure });\n\t\t\tthrow new VerifiedRunError(failure);\n\t\t}\n\t\t// The intent commits before the adapter is invoked: from this record on,\n\t\t// the effect may be live and a crash must quarantine it, never assume\n\t\t// it did not run.\n\t\tif (!authority.store.dispatchIntent(token, executionId)) throw new VerifiedRunError(\"authority\");\n\t\tsupervisorInvoked = true;\n\t\tresult = await executeSandbox({\n\t\t\tworkspace: request.workspace,\n\t\t\targv: request.argv,\n\t\t\twritable: request.role === \"writer\",\n\t\t\ttimeoutMs: Math.floor(request.deadline - performance.now()),\n\t\t\tcleanupMs: policy.cleanupMs,\n\t\t\tmaxOutputBytes: policy.maxOutputBytes,\n\t\t\t...(policy.signal ? { signal: policy.signal } : {}),\n\t\t\tonReady: (identity: NamespaceIdentity) => {\n\t\t\t\treadyObserved = true;\n\t\t\t\tif (journal.state.generation !== generation) throw new VerifiedRunError(\"stale_generation\");\n\t\t\t\tjournal.append({ kind: \"process_ready\", executionId, identity });\n\t\t\t\tif (!authority.store.effectStarted(token, claims, identity)) throw new VerifiedRunError(\"authority\");\n\t\t\t},\n\t\t});\n\t} catch (error) {\n\t\t// Only pre-spawn rejections prove nothing ran; everything else may have\n\t\t// a live or dying effect, which stays owned until a witness or a later\n\t\t// reconcile settles it.\n\t\ttry {\n\t\t\tauthority.store.cancel(token);\n\t\t\tif (\n\t\t\t\t!supervisorInvoked ||\n\t\t\t\t(!readyObserved && error instanceof VerifiedRunError && NEVER_SPAWNED.has(error.code))\n\t\t\t) {\n\t\t\t\tauthority.store.confirmTerminated(token);\n\t\t\t\t// The intent provably never reached a process: journal the exit the same\n\t\t\t\t// way the pre-spawn timeout branch does, or the run quarantines forever\n\t\t\t\t// on an open executionId — the slow-runner flake where the second\n\t\t\t\t// deadline evaluation crosses zero between dispatch and spawn.\n\t\t\t\tif (journal.state.activeExecutionIds.includes(executionId)) {\n\t\t\t\t\tjournal.append({\n\t\t\t\t\t\tkind: \"exited\",\n\t\t\t\t\t\texecutionId,\n\t\t\t\t\t\tfailure: error instanceof VerifiedRunError ? error.code : \"execution_failed\",\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (cleanupError) {\n\t\t\tthrow new AggregateError([error, cleanupError], \"Execution ownership cleanup failed\");\n\t\t}\n\t\tthrow error;\n\t}\n\tif (result.failure === \"cancelled\") authority.store.cancel(token);\n\t// Resolve means both witnesses fired: the direct child closed and the\n\t// recorded namespace drained empty. That is the termination-observed event\n\t// — the grant releases its claims here and nowhere else.\n\tif (!authority.store.confirmTerminated(token)) throw new VerifiedRunError(\"authority\");\n\tif (journal.state.generation !== generation) throw new VerifiedRunError(\"stale_generation\");\n\tjournal.append({ kind: \"exited\", executionId, failure: request.role === \"writer\" ? result.failure : null });\n\treturn { executionId, result, timeoutMs };\n}\n"]}
|
|
@@ -119,8 +119,20 @@ export async function executeRunCommand(journal, request, policy) {
|
|
|
119
119
|
try {
|
|
120
120
|
authority.store.cancel(token);
|
|
121
121
|
if (!supervisorInvoked ||
|
|
122
|
-
(!readyObserved && error instanceof VerifiedRunError && NEVER_SPAWNED.has(error.code)))
|
|
122
|
+
(!readyObserved && error instanceof VerifiedRunError && NEVER_SPAWNED.has(error.code))) {
|
|
123
123
|
authority.store.confirmTerminated(token);
|
|
124
|
+
// The intent provably never reached a process: journal the exit the same
|
|
125
|
+
// way the pre-spawn timeout branch does, or the run quarantines forever
|
|
126
|
+
// on an open executionId — the slow-runner flake where the second
|
|
127
|
+
// deadline evaluation crosses zero between dispatch and spawn.
|
|
128
|
+
if (journal.state.activeExecutionIds.includes(executionId)) {
|
|
129
|
+
journal.append({
|
|
130
|
+
kind: "exited",
|
|
131
|
+
executionId,
|
|
132
|
+
failure: error instanceof VerifiedRunError ? error.code : "execution_failed",
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
}
|
|
124
136
|
}
|
|
125
137
|
catch (cleanupError) {
|
|
126
138
|
throw new AggregateError([error, cleanupError], "Execution ownership cleanup failed");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"owned-execution.js","sourceRoot":"","sources":["../../../src/core/verified-run/owned-execution.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { realpathSync } from \"node:fs\";\nimport { performance } from \"node:perf_hooks\";\nimport type { ResourceClaimInput } from \"../../coordination/resource.ts\";\nimport type { GrantToken } from \"../../coordination/types.ts\";\nimport type { RunAuthority } from \"./authority-runtime.ts\";\nimport { executeSandbox, type SandboxOutcome } from \"./broker.ts\";\nimport type { VerifiedRunJournal } from \"./journal.ts\";\nimport type { NamespaceIdentity } from \"./namespace-identity.ts\";\nimport { digestObject, VerifiedRunError } from \"./storage.ts\";\n\nexport interface OwnedRunCommand {\n\treadonly role: \"writer\" | \"verifier\";\n\treadonly argv: readonly string[];\n\treadonly workspace: string;\n\treadonly deadline: number;\n\treadonly claimId: string | null;\n\treadonly taskId?: string;\n}\nexport interface OwnedRunResult {\n\treadonly executionId: string;\n\treadonly result: SandboxOutcome;\n\treadonly timeoutMs: number;\n}\n\n/**\n * The filesystem claim a dispatched process is authorized for: exactly the\n * workspace bind it receives inside the namespace — nothing wider. Keys are\n * the resolved absolute path minus the leading slash so prefix containment\n * matches real tree containment; `realpathSync` keeps two spellings of the\n * same directory one claim.\n */\nfunction workspaceClaim(\n\tworkspace: string,\n\taccess: \"read\" | \"write\",\n\tgeneration: number,\n): readonly ResourceClaimInput[] {\n\tlet root: string;\n\ttry {\n\t\troot = realpathSync(workspace);\n\t} catch {\n\t\tthrow new VerifiedRunError(\"authority_claim\");\n\t}\n\treturn Object.freeze([\n\t\t{\n\t\t\tnamespace: \"filesystem\",\n\t\t\tinstanceId: \"verified-run\",\n\t\t\tcanonicalKey: root.replace(/^\\/+/, \"\"),\n\t\t\taccess,\n\t\t\tgeneration: String(generation),\n\t\t},\n\t]);\n}\n\n/** Rejection codes that can only be produced before the supervisor spawned. */\nconst NEVER_SPAWNED = new Set([\"cancelled\", \"deadline\", \"unsupported_boundary\"]);\n\n/**\n * Mandatory durable dispatch intent, shared by command and AgentSession\n * writers. There is no ungated execution lane: a journal without an anchored\n * budget cannot commit a process identity, so the supervisor boundary would\n * be unprovable and must refuse dispatch. Every dispatch now also crosses the\n * durable authority boundary — grant → intent → start → termination witness\n * — so a restart can reconcile effects the journal alone cannot settle.\n */\nexport async function executeRunCommand(\n\tjournal: VerifiedRunJournal,\n\trequest: OwnedRunCommand,\n\tpolicy: {\n\t\treadonly cleanupMs: number;\n\t\treadonly maxOutputBytes: number;\n\t\treadonly authority: RunAuthority;\n\t\treadonly signal?: AbortSignal;\n\t},\n): Promise<OwnedRunResult> {\n\tif (Math.floor(request.deadline - performance.now()) <= 0) throw new VerifiedRunError(\"deadline\");\n\tif (policy.signal?.aborted) throw new VerifiedRunError(\"cancelled\");\n\t// The supervisor boundary only exists once a budget (and therefore the\n\t// gated driver) is anchored. Dispatching without it would run code whose\n\t// termination cannot be witnessed — refuse rather than degrade.\n\tif (!journal.state.budget) throw new VerifiedRunError(\"unsupported_boundary\");\n\tconst authority = policy.authority;\n\tconst generation = journal.state.generation;\n\tconst executionId = randomUUID();\n\tconst claims = workspaceClaim(request.workspace, request.role === \"writer\" ? \"write\" : \"read\", generation);\n\tconst timeoutMs = Math.floor(request.deadline - performance.now());\n\tconst admission = authority.store.acquire({\n\t\tsessionId: authority.sessionId,\n\t\tincarnation: authority.incarnation,\n\t\tcommandId: executionId,\n\t\tintentDigest: digestObject({ argv: request.argv, claims, role: request.role }),\n\t\tclaims,\n\t\tnow: Date.now(),\n\t\tttl: timeoutMs + policy.cleanupMs + 30000,\n\t});\n\tif (admission.status !== \"granted\") {\n\t\t// A fresh executionId can only collide with a conflicting or wedged\n\t\t// claim — quarantined effects hold theirs until witnessed (fail closed).\n\t\tthrow new VerifiedRunError(\"authority_blocked\");\n\t}\n\tconst token: GrantToken = admission.token;\n\tlet supervisorInvoked = false;\n\tlet readyObserved = false;\n\tlet result: SandboxOutcome;\n\ttry {\n\t\tjournal.append({\n\t\t\tkind: \"dispatch\",\n\t\t\texecutionId,\n\t\t\trole: request.role,\n\t\t\tclaimId: request.claimId,\n\t\t\t...(request.taskId === undefined ? {} : { taskId: request.taskId }),\n\t\t});\n\t\tif (timeoutMs <= 0 || policy.signal?.aborted) {\n\t\t\tconst failure = policy.signal?.aborted ? \"cancelled\" : \"deadline\";\n\t\t\t// Provably pre-spawn: the intent never reached the adapter, so the grant\n\t\t\t// settles immediately instead of wedging as an unwitnessed live effect.\n\t\t\tauthority.store.cancel(token);\n\t\t\tauthority.store.confirmTerminated(token);\n\t\t\tjournal.append({ kind: \"exited\", executionId, failure });\n\t\t\tthrow new VerifiedRunError(failure);\n\t\t}\n\t\t// The intent commits before the adapter is invoked: from this record on,\n\t\t// the effect may be live and a crash must quarantine it, never assume\n\t\t// it did not run.\n\t\tif (!authority.store.dispatchIntent(token, executionId)) throw new VerifiedRunError(\"authority\");\n\t\tsupervisorInvoked = true;\n\t\tresult = await executeSandbox({\n\t\t\tworkspace: request.workspace,\n\t\t\targv: request.argv,\n\t\t\twritable: request.role === \"writer\",\n\t\t\ttimeoutMs: Math.floor(request.deadline - performance.now()),\n\t\t\tcleanupMs: policy.cleanupMs,\n\t\t\tmaxOutputBytes: policy.maxOutputBytes,\n\t\t\t...(policy.signal ? { signal: policy.signal } : {}),\n\t\t\tonReady: (identity: NamespaceIdentity) => {\n\t\t\t\treadyObserved = true;\n\t\t\t\tif (journal.state.generation !== generation) throw new VerifiedRunError(\"stale_generation\");\n\t\t\t\tjournal.append({ kind: \"process_ready\", executionId, identity });\n\t\t\t\tif (!authority.store.effectStarted(token, claims, identity)) throw new VerifiedRunError(\"authority\");\n\t\t\t},\n\t\t});\n\t} catch (error) {\n\t\t// Only pre-spawn rejections prove nothing ran; everything else may have\n\t\t// a live or dying effect, which stays owned until a witness or a later\n\t\t// reconcile settles it.\n\t\ttry {\n\t\t\tauthority.store.cancel(token);\n\t\t\tif (\n\t\t\t\t!supervisorInvoked ||\n\t\t\t\t(!readyObserved && error instanceof VerifiedRunError && NEVER_SPAWNED.has(error.code))\n\t\t\t)\n\t\t\t\tauthority.store.confirmTerminated(token);\n\t\t} catch (cleanupError) {\n\t\t\tthrow new AggregateError([error, cleanupError], \"Execution ownership cleanup failed\");\n\t\t}\n\t\tthrow error;\n\t}\n\tif (result.failure === \"cancelled\") authority.store.cancel(token);\n\t// Resolve means both witnesses fired: the direct child closed and the\n\t// recorded namespace drained empty. That is the termination-observed event\n\t// — the grant releases its claims here and nowhere else.\n\tif (!authority.store.confirmTerminated(token)) throw new VerifiedRunError(\"authority\");\n\tif (journal.state.generation !== generation) throw new VerifiedRunError(\"stale_generation\");\n\tjournal.append({ kind: \"exited\", executionId, failure: request.role === \"writer\" ? result.failure : null });\n\treturn { executionId, result, timeoutMs };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"owned-execution.js","sourceRoot":"","sources":["../../../src/core/verified-run/owned-execution.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { realpathSync } from \"node:fs\";\nimport { performance } from \"node:perf_hooks\";\nimport type { ResourceClaimInput } from \"../../coordination/resource.ts\";\nimport type { GrantToken } from \"../../coordination/types.ts\";\nimport type { RunAuthority } from \"./authority-runtime.ts\";\nimport { executeSandbox, type SandboxOutcome } from \"./broker.ts\";\nimport type { VerifiedRunJournal } from \"./journal.ts\";\nimport type { NamespaceIdentity } from \"./namespace-identity.ts\";\nimport { digestObject, VerifiedRunError } from \"./storage.ts\";\n\nexport interface OwnedRunCommand {\n\treadonly role: \"writer\" | \"verifier\";\n\treadonly argv: readonly string[];\n\treadonly workspace: string;\n\treadonly deadline: number;\n\treadonly claimId: string | null;\n\treadonly taskId?: string;\n}\nexport interface OwnedRunResult {\n\treadonly executionId: string;\n\treadonly result: SandboxOutcome;\n\treadonly timeoutMs: number;\n}\n\n/**\n * The filesystem claim a dispatched process is authorized for: exactly the\n * workspace bind it receives inside the namespace — nothing wider. Keys are\n * the resolved absolute path minus the leading slash so prefix containment\n * matches real tree containment; `realpathSync` keeps two spellings of the\n * same directory one claim.\n */\nfunction workspaceClaim(\n\tworkspace: string,\n\taccess: \"read\" | \"write\",\n\tgeneration: number,\n): readonly ResourceClaimInput[] {\n\tlet root: string;\n\ttry {\n\t\troot = realpathSync(workspace);\n\t} catch {\n\t\tthrow new VerifiedRunError(\"authority_claim\");\n\t}\n\treturn Object.freeze([\n\t\t{\n\t\t\tnamespace: \"filesystem\",\n\t\t\tinstanceId: \"verified-run\",\n\t\t\tcanonicalKey: root.replace(/^\\/+/, \"\"),\n\t\t\taccess,\n\t\t\tgeneration: String(generation),\n\t\t},\n\t]);\n}\n\n/** Rejection codes that can only be produced before the supervisor spawned. */\nconst NEVER_SPAWNED = new Set([\"cancelled\", \"deadline\", \"unsupported_boundary\"]);\n\n/**\n * Mandatory durable dispatch intent, shared by command and AgentSession\n * writers. There is no ungated execution lane: a journal without an anchored\n * budget cannot commit a process identity, so the supervisor boundary would\n * be unprovable and must refuse dispatch. Every dispatch now also crosses the\n * durable authority boundary — grant → intent → start → termination witness\n * — so a restart can reconcile effects the journal alone cannot settle.\n */\nexport async function executeRunCommand(\n\tjournal: VerifiedRunJournal,\n\trequest: OwnedRunCommand,\n\tpolicy: {\n\t\treadonly cleanupMs: number;\n\t\treadonly maxOutputBytes: number;\n\t\treadonly authority: RunAuthority;\n\t\treadonly signal?: AbortSignal;\n\t},\n): Promise<OwnedRunResult> {\n\tif (Math.floor(request.deadline - performance.now()) <= 0) throw new VerifiedRunError(\"deadline\");\n\tif (policy.signal?.aborted) throw new VerifiedRunError(\"cancelled\");\n\t// The supervisor boundary only exists once a budget (and therefore the\n\t// gated driver) is anchored. Dispatching without it would run code whose\n\t// termination cannot be witnessed — refuse rather than degrade.\n\tif (!journal.state.budget) throw new VerifiedRunError(\"unsupported_boundary\");\n\tconst authority = policy.authority;\n\tconst generation = journal.state.generation;\n\tconst executionId = randomUUID();\n\tconst claims = workspaceClaim(request.workspace, request.role === \"writer\" ? \"write\" : \"read\", generation);\n\tconst timeoutMs = Math.floor(request.deadline - performance.now());\n\tconst admission = authority.store.acquire({\n\t\tsessionId: authority.sessionId,\n\t\tincarnation: authority.incarnation,\n\t\tcommandId: executionId,\n\t\tintentDigest: digestObject({ argv: request.argv, claims, role: request.role }),\n\t\tclaims,\n\t\tnow: Date.now(),\n\t\tttl: timeoutMs + policy.cleanupMs + 30000,\n\t});\n\tif (admission.status !== \"granted\") {\n\t\t// A fresh executionId can only collide with a conflicting or wedged\n\t\t// claim — quarantined effects hold theirs until witnessed (fail closed).\n\t\tthrow new VerifiedRunError(\"authority_blocked\");\n\t}\n\tconst token: GrantToken = admission.token;\n\tlet supervisorInvoked = false;\n\tlet readyObserved = false;\n\tlet result: SandboxOutcome;\n\ttry {\n\t\tjournal.append({\n\t\t\tkind: \"dispatch\",\n\t\t\texecutionId,\n\t\t\trole: request.role,\n\t\t\tclaimId: request.claimId,\n\t\t\t...(request.taskId === undefined ? {} : { taskId: request.taskId }),\n\t\t});\n\t\tif (timeoutMs <= 0 || policy.signal?.aborted) {\n\t\t\tconst failure = policy.signal?.aborted ? \"cancelled\" : \"deadline\";\n\t\t\t// Provably pre-spawn: the intent never reached the adapter, so the grant\n\t\t\t// settles immediately instead of wedging as an unwitnessed live effect.\n\t\t\tauthority.store.cancel(token);\n\t\t\tauthority.store.confirmTerminated(token);\n\t\t\tjournal.append({ kind: \"exited\", executionId, failure });\n\t\t\tthrow new VerifiedRunError(failure);\n\t\t}\n\t\t// The intent commits before the adapter is invoked: from this record on,\n\t\t// the effect may be live and a crash must quarantine it, never assume\n\t\t// it did not run.\n\t\tif (!authority.store.dispatchIntent(token, executionId)) throw new VerifiedRunError(\"authority\");\n\t\tsupervisorInvoked = true;\n\t\tresult = await executeSandbox({\n\t\t\tworkspace: request.workspace,\n\t\t\targv: request.argv,\n\t\t\twritable: request.role === \"writer\",\n\t\t\ttimeoutMs: Math.floor(request.deadline - performance.now()),\n\t\t\tcleanupMs: policy.cleanupMs,\n\t\t\tmaxOutputBytes: policy.maxOutputBytes,\n\t\t\t...(policy.signal ? { signal: policy.signal } : {}),\n\t\t\tonReady: (identity: NamespaceIdentity) => {\n\t\t\t\treadyObserved = true;\n\t\t\t\tif (journal.state.generation !== generation) throw new VerifiedRunError(\"stale_generation\");\n\t\t\t\tjournal.append({ kind: \"process_ready\", executionId, identity });\n\t\t\t\tif (!authority.store.effectStarted(token, claims, identity)) throw new VerifiedRunError(\"authority\");\n\t\t\t},\n\t\t});\n\t} catch (error) {\n\t\t// Only pre-spawn rejections prove nothing ran; everything else may have\n\t\t// a live or dying effect, which stays owned until a witness or a later\n\t\t// reconcile settles it.\n\t\ttry {\n\t\t\tauthority.store.cancel(token);\n\t\t\tif (\n\t\t\t\t!supervisorInvoked ||\n\t\t\t\t(!readyObserved && error instanceof VerifiedRunError && NEVER_SPAWNED.has(error.code))\n\t\t\t) {\n\t\t\t\tauthority.store.confirmTerminated(token);\n\t\t\t\t// The intent provably never reached a process: journal the exit the same\n\t\t\t\t// way the pre-spawn timeout branch does, or the run quarantines forever\n\t\t\t\t// on an open executionId — the slow-runner flake where the second\n\t\t\t\t// deadline evaluation crosses zero between dispatch and spawn.\n\t\t\t\tif (journal.state.activeExecutionIds.includes(executionId)) {\n\t\t\t\t\tjournal.append({\n\t\t\t\t\t\tkind: \"exited\",\n\t\t\t\t\t\texecutionId,\n\t\t\t\t\t\tfailure: error instanceof VerifiedRunError ? error.code : \"execution_failed\",\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (cleanupError) {\n\t\t\tthrow new AggregateError([error, cleanupError], \"Execution ownership cleanup failed\");\n\t\t}\n\t\tthrow error;\n\t}\n\tif (result.failure === \"cancelled\") authority.store.cancel(token);\n\t// Resolve means both witnesses fired: the direct child closed and the\n\t// recorded namespace drained empty. That is the termination-observed event\n\t// — the grant releases its claims here and nowhere else.\n\tif (!authority.store.confirmTerminated(token)) throw new VerifiedRunError(\"authority\");\n\tif (journal.state.generation !== generation) throw new VerifiedRunError(\"stale_generation\");\n\tjournal.append({ kind: \"exited\", executionId, failure: request.role === \"writer\" ? result.failure : null });\n\treturn { executionId, result, timeoutMs };\n}\n"]}
|
package/docs/compaction.md
CHANGED
|
@@ -132,6 +132,8 @@ This fallback still must pass the normal compaction transaction checks.
|
|
|
132
132
|
5. **Sanitize and append**: Deterministically redact sensitive values, then save the `CompactionEntry` with its summary, preserved-rule details, and `firstKeptEntryId`. Exact `[REDACTED]` assignment placeholders are valid; appended data and unredacted credential-shaped literals remain rejected.
|
|
133
133
|
6. **Reload**: Session reloads, using summary + messages from `firstKeptEntryId` onwards.
|
|
134
134
|
|
|
135
|
+
The summary is committed only if the session still matches what was summarized. Extension state appended with `appendEntry` while the built-in summary is generated does not count as a change, because the summarizer never reads it; the compaction entry is then appended after those entries. A summary returned by a `session_before_compact` handler gets no such allowance, since the extension may have read its own state. A new message, model change, provenance entry, rewritten file or branch move does count: the summary is discarded with `Session changed during compaction (revision_mismatch)`. This check is the same for every model.
|
|
136
|
+
|
|
135
137
|
```
|
|
136
138
|
Before compaction:
|
|
137
139
|
|
package/docs/extensions.md
CHANGED
|
@@ -1380,7 +1380,7 @@ See [send-user-message.ts](../examples/extensions/send-user-message.ts) for a co
|
|
|
1380
1380
|
|
|
1381
1381
|
### omk.appendEntry(customType, data?)
|
|
1382
1382
|
|
|
1383
|
-
Persist extension state (does NOT participate in LLM context).
|
|
1383
|
+
Persist extension state (does NOT participate in LLM context). Appending while a compaction is summarizing does not discard that compaction; see [Compaction](compaction.md#how-it-works).
|
|
1384
1384
|
|
|
1385
1385
|
```typescript
|
|
1386
1386
|
omk.appendEntry("my-state", { count: 42 });
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "omk-extension-custom-provider-anthropic",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.4",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "omk-extension-custom-provider-anthropic",
|
|
9
|
-
"version": "1.2.
|
|
9
|
+
"version": "1.2.4",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@anthropic-ai/sdk": "0.52.0"
|
|
12
12
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "omk-extension-gondolin",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.4",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "omk-extension-gondolin",
|
|
9
|
-
"version": "1.2.
|
|
9
|
+
"version": "1.2.4",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@earendil-works/gondolin": "0.12.0"
|
|
12
12
|
}
|