open-multi-agent-kit 0.95.2 → 0.96.0
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 +20 -0
- package/README.md +15 -0
- package/dist/approvals/approval-receipt-store.d.ts.map +1 -1
- package/dist/approvals/approval-receipt-store.js +25 -0
- package/dist/approvals/approval-receipt-store.js.map +1 -1
- package/dist/core/agent-session.d.ts +2 -2
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +41 -17
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/provider-resilience.d.ts +7 -0
- package/dist/core/provider-resilience.d.ts.map +1 -1
- package/dist/core/provider-resilience.js +11 -0
- package/dist/core/provider-resilience.js.map +1 -1
- package/dist/core/subagent-orchestration.d.ts.map +1 -1
- package/dist/core/subagent-orchestration.js +4 -2
- package/dist/core/subagent-orchestration.js.map +1 -1
- package/dist/core/tools/diagnostics.d.ts.map +1 -1
- package/dist/core/tools/diagnostics.js +8 -2
- package/dist/core/tools/diagnostics.js.map +1 -1
- package/dist/guardrails/evidence-protocol.d.ts +10 -0
- package/dist/guardrails/evidence-protocol.d.ts.map +1 -0
- package/dist/guardrails/evidence-protocol.js +34 -0
- package/dist/guardrails/evidence-protocol.js.map +1 -0
- package/dist/guardrails/evidence-system.d.ts +3 -1
- package/dist/guardrails/evidence-system.d.ts.map +1 -1
- package/dist/guardrails/evidence-system.js +22 -14
- package/dist/guardrails/evidence-system.js.map +1 -1
- package/dist/guardrails/replay-ledger-lock-owner.d.ts.map +1 -1
- package/dist/guardrails/replay-ledger-lock-owner.js +3 -1
- package/dist/guardrails/replay-ledger-lock-owner.js.map +1 -1
- package/dist/guardrails/replay-payload-hash.d.ts +6 -0
- package/dist/guardrails/replay-payload-hash.d.ts.map +1 -0
- package/dist/guardrails/replay-payload-hash.js +24 -0
- package/dist/guardrails/replay-payload-hash.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/types/evidence.d.ts +11 -3
- package/dist/types/evidence.d.ts.map +1 -1
- package/dist/types/evidence.js.map +1 -1
- package/docs/book-to-skill.md +79 -0
- package/docs/compaction.md +10 -0
- package/docs/docs.json +8 -0
- package/docs/index.md +3 -0
- package/docs/models.md +12 -0
- package/docs/provider-resilience.md +37 -56
- package/docs/providers.md +4 -0
- package/docs/run-protocol.md +72 -0
- package/docs/sdk.md +15 -2
- package/docs/sessions.md +6 -0
- package/docs/settings.md +2 -0
- package/docs/skills.md +9 -1
- package/docs/usage.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 +37 -15
- package/package.json +8 -5
- package/dist/core/context-budget-compressors.d.ts +0 -50
- package/dist/core/context-budget-compressors.d.ts.map +0 -1
- package/dist/core/context-budget-compressors.js +0 -166
- package/dist/core/context-budget-compressors.js.map +0 -1
- package/dist/core/extensions/loader.js.bak-sharedjiti-20260812-111246 +0 -553
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.96.0] - 2026-08-16
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Added `omk-protocol`, the versioned `TaskSpec -> ExecutionAttempt -> Observation -> EvaluationResult -> RuntimeDecision` contract package, with runtime validators, explicit waivers, and pure semantic and runtime-decision reducers.
|
|
10
|
+
- Added `evidenceReceiptToObservation()` to project integrity-checked EvidenceReceipt v3 cores into immutable protocol facts. Legacy mutable `EvidenceStatus` and `TaskContract` verdict APIs remain compatible but are deprecated.
|
|
11
|
+
- Added the optional `omk-book-to-skill` package with compile/update commands, a pinned upstream workflow, advisory generated-skill scanning, and SHA-256 source/artifact provenance checks. Python extractors remain outside OMK core.
|
|
12
|
+
- Added deterministically seeded, bounded `fast-check` model and property suites for WPL transitions, replay migration and CAS, evidence freshness, subagent topology, run-journal CAS, and timeout/abort settlement ordering.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- New replay events declare `jcs-rfc8785-v2` and hash RFC 8785-canonical payloads. Events without an algorithm remain verified as `json-stringify-v1`; mixed ledgers and exports preserve legacy records without rewriting them.
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- NVIDIA NIM's `z-ai/glm-5.2` entry now transmits `reasoning_effort`, including the generated `max` thinking level; other NVIDIA models keep conservative compatibility defaults.
|
|
21
|
+
- Billing-cycle and quota exhaustion, including provider 403 usage-limit responses, now classify as `provider.rate_limit` and can switch to the first configured, authenticated resilience candidate before retry. Each attempt remains journaled, and a recovered retry ends with a later `completed` termination.
|
|
22
|
+
- Subagent DAG scheduling now sorts simultaneously ready lanes by lane ID, so topology aggregation does not depend on input insertion order.
|
|
23
|
+
- Local release bundles now include `omk-adaptorch-wpl`, allowing isolated installs of the full packed workspace without resolving that dependency from the registry.
|
|
24
|
+
|
|
5
25
|
## [0.95.2] - 2026-08-15
|
|
6
26
|
|
|
7
27
|
### Changed
|
package/README.md
CHANGED
|
@@ -40,6 +40,16 @@ Then:
|
|
|
40
40
|
|
|
41
41
|
OMK stores user configuration under `~/.omk/agent/` and project configuration under `.omk/`.
|
|
42
42
|
|
|
43
|
+
## Optional document compiler
|
|
44
|
+
|
|
45
|
+
Install `omk-book-to-skill` to compile documents into reusable skills without adding Python extractors to OMK core:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
omk install npm:omk-book-to-skill@0.95.2
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
It provides compile, update, and verification commands plus a local SHA-256 provenance manifest. See [Book to Skill](https://github.com/dmae97/omk/blob/main/packages/coding-agent/docs/book-to-skill.md).
|
|
52
|
+
|
|
43
53
|
## Providers & Models
|
|
44
54
|
|
|
45
55
|
OMK includes provider adapters for Anthropic, OpenAI Codex, Google, OpenCode, Kimi, Qwen, ZAI/GLM, Grok, and other compatible services. Availability depends on the credentials and endpoints configured on the current machine.
|
|
@@ -47,11 +57,14 @@ OMK includes provider adapters for Anthropic, OpenAI Codex, Google, OpenCode, Ki
|
|
|
47
57
|
- `/login` adds or selects credentials.
|
|
48
58
|
- `/logout` removes credentials for a selected provider.
|
|
49
59
|
- `/model` lists models available to the current credential set.
|
|
60
|
+
- NVIDIA NIM's `z-ai/glm-5.2` entry transmits reasoning effort, including `/thinking max`.
|
|
50
61
|
- The optional status rail shows independent provider quota windows when an official API or passive response signal is available.
|
|
51
62
|
- Missing quota values are shown as unavailable rather than estimated.
|
|
52
63
|
|
|
53
64
|
OAuth credentials are stored locally in `~/.omk/agent/auth.json`. Multi-account providers keep accounts separate and send requests only through the explicitly selected account.
|
|
54
65
|
|
|
66
|
+
Quota and billing-cycle errors are classified as rate limits and can switch to the first authenticated resilience candidate before retry. See [Provider resilience](https://github.com/dmae97/omk/blob/main/packages/coding-agent/docs/provider-resilience.md).
|
|
67
|
+
|
|
55
68
|
See [Provider setup](https://github.com/dmae97/omk/blob/main/packages/coding-agent/docs/providers.md) for provider-specific authentication, quota behavior, and environment variables.
|
|
56
69
|
|
|
57
70
|
### Kimi For Coding
|
|
@@ -142,6 +155,8 @@ See [RPC](https://github.com/dmae97/omk/blob/main/packages/coding-agent/docs/rpc
|
|
|
142
155
|
|
|
143
156
|
The TypeScript SDK exposes session creation, provider configuration, tools, and event streams. Extensions can add commands, tools, UI components, themes, and resource loaders without patching OMK core.
|
|
144
157
|
|
|
158
|
+
`omk-protocol` provides the versioned `TaskSpec -> ExecutionAttempt -> Observation -> EvaluationResult -> RuntimeDecision` contracts and pure semantic reducers. See [Run Protocol v1](https://github.com/dmae97/omk/blob/main/packages/coding-agent/docs/run-protocol.md) for the implemented scope and migration boundaries.
|
|
159
|
+
|
|
145
160
|
- [SDK](https://github.com/dmae97/omk/blob/main/packages/coding-agent/docs/sdk.md)
|
|
146
161
|
- [Extensions](https://github.com/dmae97/omk/blob/main/packages/coding-agent/docs/extensions.md)
|
|
147
162
|
- [Skills](https://github.com/dmae97/omk/blob/main/packages/coding-agent/docs/skills.md)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"approval-receipt-store.d.ts","sourceRoot":"","sources":["../../src/approvals/approval-receipt-store.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,KAAK,eAAe,EAAqD,MAAM,uBAAuB,CAAC;AAKhH,MAAM,WAAW,0BAA0B;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;CACjB;AAsDD,qBAAa,oBAAoB;IAChC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAEzB,YAAY,OAAO,EAAE,MAAM,EAG1B;IAED,OAAO,CAAC,sBAAsB;IAa9B,OAAO,CAAC,mBAAmB;IAO3B,OAAO,CAAC,OAAO;IAIf,OAAO,CAAC,kBAAkB;IAW1B,KAAK,CAAC,OAAO,EAAE,eAAe,GAAG,0BAA0B,
|
|
1
|
+
{"version":3,"file":"approval-receipt-store.d.ts","sourceRoot":"","sources":["../../src/approvals/approval-receipt-store.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,KAAK,eAAe,EAAqD,MAAM,uBAAuB,CAAC;AAKhH,MAAM,WAAW,0BAA0B;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;CACjB;AAsDD,qBAAa,oBAAoB;IAChC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAEzB,YAAY,OAAO,EAAE,MAAM,EAG1B;IAED,OAAO,CAAC,sBAAsB;IAa9B,OAAO,CAAC,mBAAmB;IAO3B,OAAO,CAAC,OAAO;IAIf,OAAO,CAAC,kBAAkB;IAW1B,KAAK,CAAC,OAAO,EAAE,eAAe,GAAG,0BAA0B,CA4D1D;IAED,GAAG,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAe9B;IAED,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,eAAe,CAUvC;IAED,cAAc,IAAI,MAAM,EAAE,CASzB;CACD","sourcesContent":["import {\n\tcloseSync,\n\tconstants,\n\tfchmodSync,\n\tfstatSync,\n\tfsyncSync,\n\tlstatSync,\n\tmkdirSync,\n\topenSync,\n\treaddirSync,\n\treadSync,\n\twriteFileSync,\n} from \"node:fs\";\nimport { join, resolve } from \"node:path\";\nimport { type ApprovalReceipt, serializeApprovalReceipt, validateApprovalReceipt } from \"./approval-receipt.ts\";\n\nconst MAX_RECEIPT_BYTES = 1024 * 1024;\nconst NOFOLLOW = constants.O_NOFOLLOW ?? 0;\n\nexport interface ApprovalReceiptWriteResult {\n\tpath: string;\n\tcreated: boolean;\n}\n\ninterface RootIdentity {\n\tdev: number;\n\tino: number;\n}\n\nfunction isErrno(error: unknown, code: string): boolean {\n\treturn error instanceof Error && \"code\" in error && error.code === code;\n}\n\nfunction assertOwnedByCurrentUser(uid: number, label: string): void {\n\tconst getuid = process.getuid;\n\tif (typeof getuid === \"function\" && uid !== getuid()) {\n\t\tthrow new Error(`${label} is not owned by the current user`);\n\t}\n}\n\nfunction receiptFileName(receiptId: string): string {\n\tif (!/^approval-[a-f0-9]{32}$/u.test(receiptId)) throw new Error(\"Approval receipt id is invalid\");\n\treturn `${receiptId}.json`;\n}\n\nfunction assertPrivateReceiptFd(fd: number, filePath: string): void {\n\tconst stat = fstatSync(fd);\n\tif (!stat.isFile()) throw new Error(`Approval receipt is not a regular file: ${filePath}`);\n\tif (stat.nlink !== 1) throw new Error(`Approval receipt must not be hard-linked: ${filePath}`);\n\tassertOwnedByCurrentUser(stat.uid, `Approval receipt ${filePath}`);\n\tif ((stat.mode & 0o777) !== 0o600) {\n\t\tthrow new Error(`Approval receipt permissions must be 0600: ${filePath}`);\n\t}\n\tif (stat.size > MAX_RECEIPT_BYTES) throw new Error(`Approval receipt exceeds ${MAX_RECEIPT_BYTES} bytes`);\n}\n\nfunction readReceiptFromFd(fd: number, filePath: string): ApprovalReceipt {\n\tassertPrivateReceiptFd(fd, filePath);\n\tconst buffer = Buffer.alloc(MAX_RECEIPT_BYTES + 1);\n\tlet offset = 0;\n\twhile (offset < buffer.length) {\n\t\tconst read = readSync(fd, buffer, offset, buffer.length - offset, offset);\n\t\tif (read === 0) break;\n\t\toffset += read;\n\t}\n\tif (offset > MAX_RECEIPT_BYTES) throw new Error(`Approval receipt exceeds ${MAX_RECEIPT_BYTES} bytes`);\n\tassertPrivateReceiptFd(fd, filePath);\n\tlet value: unknown;\n\ttry {\n\t\tvalue = JSON.parse(buffer.subarray(0, offset).toString(\"utf8\")) as unknown;\n\t} catch {\n\t\tthrow new Error(`Approval receipt is not valid JSON: ${filePath}`);\n\t}\n\treturn validateApprovalReceipt(value);\n}\n\nexport class ApprovalReceiptStore {\n\treadonly rootDir: string;\n\n\tconstructor(rootDir: string) {\n\t\tthis.rootDir = resolve(rootDir);\n\t\tthis.ensurePrivateDirectory();\n\t}\n\n\tprivate ensurePrivateDirectory(): RootIdentity {\n\t\tmkdirSync(this.rootDir, { recursive: true, mode: 0o700 });\n\t\tconst stat = lstatSync(this.rootDir);\n\t\tif (stat.isSymbolicLink() || !stat.isDirectory()) {\n\t\t\tthrow new Error(`Approval receipt root must be a real directory: ${this.rootDir}`);\n\t\t}\n\t\tassertOwnedByCurrentUser(stat.uid, `Approval receipt root ${this.rootDir}`);\n\t\tif ((stat.mode & 0o777) !== 0o700) {\n\t\t\tthrow new Error(`Approval receipt root permissions must be 0700: ${this.rootDir}`);\n\t\t}\n\t\treturn { dev: stat.dev, ino: stat.ino };\n\t}\n\n\tprivate assertRootUnchanged(expected: RootIdentity): void {\n\t\tconst current = this.ensurePrivateDirectory();\n\t\tif (current.dev !== expected.dev || current.ino !== expected.ino) {\n\t\t\tthrow new Error(\"Approval receipt root changed during the operation\");\n\t\t}\n\t}\n\n\tprivate pathFor(receiptId: string): string {\n\t\treturn join(this.rootDir, receiptFileName(receiptId));\n\t}\n\n\tprivate openReceiptForRead(filePath: string): number {\n\t\tconst fd = openSync(filePath, constants.O_RDONLY | NOFOLLOW);\n\t\ttry {\n\t\t\tassertPrivateReceiptFd(fd, filePath);\n\t\t\treturn fd;\n\t\t} catch (error) {\n\t\t\tcloseSync(fd);\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\twrite(receipt: ApprovalReceipt): ApprovalReceiptWriteResult {\n\t\tconst validated = validateApprovalReceipt(receipt);\n\t\tconst rootIdentity = this.ensurePrivateDirectory();\n\t\tconst filePath = this.pathFor(validated.core.receiptId);\n\t\tconst canonical = serializeApprovalReceipt(validated);\n\t\tconst bytes = Buffer.from(canonical, \"utf8\");\n\t\tif (bytes.byteLength > MAX_RECEIPT_BYTES) throw new Error(`Approval receipt exceeds ${MAX_RECEIPT_BYTES} bytes`);\n\n\t\t// Explicit existence/symlink pre-check. O_EXCL/O_NOFOLLOW are honored on\n\t\t// proper Linux but silently ignored on some platforms (e.g. WSL2), which\n\t\t// would silently void the never-overwrite and symlink-rejection invariants\n\t\t// (hard rule: no silent degraded operation). Enforce them via lstat so the\n\t\t// guarantees hold on every platform; O_EXCL below stays as the atomic\n\t\t// backstop for the create path on capable platforms.\n\t\tlet existingStat: ReturnType<typeof lstatSync> | null = null;\n\t\ttry {\n\t\t\texistingStat = lstatSync(filePath);\n\t\t} catch (error) {\n\t\t\tif (!isErrno(error, \"ENOENT\")) throw error;\n\t\t}\n\t\tif (existingStat !== null) {\n\t\t\tif (existingStat.isSymbolicLink()) {\n\t\t\t\tthrow new Error(`Approval receipt must not be a symbolic link: ${filePath}`);\n\t\t\t}\n\t\t\tconst prior = this.read(validated.core.receiptId);\n\t\t\tif (serializeApprovalReceipt(prior) !== canonical) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Approval receipt already exists with different content and is never overwritten: ${validated.core.receiptId}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tthis.assertRootUnchanged(rootIdentity);\n\t\t\treturn { path: filePath, created: false };\n\t\t}\n\n\t\tlet fd: number;\n\t\ttry {\n\t\t\tfd = openSync(filePath, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | NOFOLLOW, 0o600);\n\t\t} catch (error) {\n\t\t\tif (!isErrno(error, \"EEXIST\")) throw error;\n\t\t\tconst existing = this.read(validated.core.receiptId);\n\t\t\tif (serializeApprovalReceipt(existing) !== canonical) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Approval receipt already exists with different content and is never overwritten: ${validated.core.receiptId}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tthis.assertRootUnchanged(rootIdentity);\n\t\t\treturn { path: filePath, created: false };\n\t\t}\n\n\t\ttry {\n\t\t\tfchmodSync(fd, 0o600);\n\t\t\tassertPrivateReceiptFd(fd, filePath);\n\t\t\twriteFileSync(fd, bytes);\n\t\t\tfsyncSync(fd);\n\t\t\tassertPrivateReceiptFd(fd, filePath);\n\t\t} finally {\n\t\t\tcloseSync(fd);\n\t\t}\n\t\tthis.assertRootUnchanged(rootIdentity);\n\t\treturn { path: filePath, created: true };\n\t}\n\n\thas(receiptId: string): boolean {\n\t\tconst rootIdentity = this.ensurePrivateDirectory();\n\t\tlet fd: number;\n\t\ttry {\n\t\t\tfd = this.openReceiptForRead(this.pathFor(receiptId));\n\t\t} catch (error) {\n\t\t\tif (isErrno(error, \"ENOENT\")) {\n\t\t\t\tthis.assertRootUnchanged(rootIdentity);\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t\tcloseSync(fd);\n\t\tthis.assertRootUnchanged(rootIdentity);\n\t\treturn true;\n\t}\n\n\tread(receiptId: string): ApprovalReceipt {\n\t\tconst rootIdentity = this.ensurePrivateDirectory();\n\t\tconst filePath = this.pathFor(receiptId);\n\t\tconst fd = this.openReceiptForRead(filePath);\n\t\ttry {\n\t\t\treturn readReceiptFromFd(fd, filePath);\n\t\t} finally {\n\t\t\tcloseSync(fd);\n\t\t\tthis.assertRootUnchanged(rootIdentity);\n\t\t}\n\t}\n\n\tlistReceiptIds(): string[] {\n\t\tconst rootIdentity = this.ensurePrivateDirectory();\n\t\tconst ids = readdirSync(this.rootDir)\n\t\t\t.map((name) => /^(approval-[a-f0-9]{32})\\.json$/u.exec(name)?.[1])\n\t\t\t.filter((id): id is string => id !== undefined)\n\t\t\t.sort((left, right) => left.localeCompare(right));\n\t\tfor (const id of ids) this.has(id);\n\t\tthis.assertRootUnchanged(rootIdentity);\n\t\treturn ids;\n\t}\n}\n"]}
|
|
@@ -98,6 +98,31 @@ export class ApprovalReceiptStore {
|
|
|
98
98
|
const bytes = Buffer.from(canonical, "utf8");
|
|
99
99
|
if (bytes.byteLength > MAX_RECEIPT_BYTES)
|
|
100
100
|
throw new Error(`Approval receipt exceeds ${MAX_RECEIPT_BYTES} bytes`);
|
|
101
|
+
// Explicit existence/symlink pre-check. O_EXCL/O_NOFOLLOW are honored on
|
|
102
|
+
// proper Linux but silently ignored on some platforms (e.g. WSL2), which
|
|
103
|
+
// would silently void the never-overwrite and symlink-rejection invariants
|
|
104
|
+
// (hard rule: no silent degraded operation). Enforce them via lstat so the
|
|
105
|
+
// guarantees hold on every platform; O_EXCL below stays as the atomic
|
|
106
|
+
// backstop for the create path on capable platforms.
|
|
107
|
+
let existingStat = null;
|
|
108
|
+
try {
|
|
109
|
+
existingStat = lstatSync(filePath);
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
if (!isErrno(error, "ENOENT"))
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
115
|
+
if (existingStat !== null) {
|
|
116
|
+
if (existingStat.isSymbolicLink()) {
|
|
117
|
+
throw new Error(`Approval receipt must not be a symbolic link: ${filePath}`);
|
|
118
|
+
}
|
|
119
|
+
const prior = this.read(validated.core.receiptId);
|
|
120
|
+
if (serializeApprovalReceipt(prior) !== canonical) {
|
|
121
|
+
throw new Error(`Approval receipt already exists with different content and is never overwritten: ${validated.core.receiptId}`);
|
|
122
|
+
}
|
|
123
|
+
this.assertRootUnchanged(rootIdentity);
|
|
124
|
+
return { path: filePath, created: false };
|
|
125
|
+
}
|
|
101
126
|
let fd;
|
|
102
127
|
try {
|
|
103
128
|
fd = openSync(filePath, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | NOFOLLOW, 0o600);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"approval-receipt-store.js","sourceRoot":"","sources":["../../src/approvals/approval-receipt-store.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n\tcloseSync,\n\tconstants,\n\tfchmodSync,\n\tfstatSync,\n\tfsyncSync,\n\tlstatSync,\n\tmkdirSync,\n\topenSync,\n\treaddirSync,\n\treadSync,\n\twriteFileSync,\n} from \"node:fs\";\nimport { join, resolve } from \"node:path\";\nimport { type ApprovalReceipt, serializeApprovalReceipt, validateApprovalReceipt } from \"./approval-receipt.ts\";\n\nconst MAX_RECEIPT_BYTES = 1024 * 1024;\nconst NOFOLLOW = constants.O_NOFOLLOW ?? 0;\n\nexport interface ApprovalReceiptWriteResult {\n\tpath: string;\n\tcreated: boolean;\n}\n\ninterface RootIdentity {\n\tdev: number;\n\tino: number;\n}\n\nfunction isErrno(error: unknown, code: string): boolean {\n\treturn error instanceof Error && \"code\" in error && error.code === code;\n}\n\nfunction assertOwnedByCurrentUser(uid: number, label: string): void {\n\tconst getuid = process.getuid;\n\tif (typeof getuid === \"function\" && uid !== getuid()) {\n\t\tthrow new Error(`${label} is not owned by the current user`);\n\t}\n}\n\nfunction receiptFileName(receiptId: string): string {\n\tif (!/^approval-[a-f0-9]{32}$/u.test(receiptId)) throw new Error(\"Approval receipt id is invalid\");\n\treturn `${receiptId}.json`;\n}\n\nfunction assertPrivateReceiptFd(fd: number, filePath: string): void {\n\tconst stat = fstatSync(fd);\n\tif (!stat.isFile()) throw new Error(`Approval receipt is not a regular file: ${filePath}`);\n\tif (stat.nlink !== 1) throw new Error(`Approval receipt must not be hard-linked: ${filePath}`);\n\tassertOwnedByCurrentUser(stat.uid, `Approval receipt ${filePath}`);\n\tif ((stat.mode & 0o777) !== 0o600) {\n\t\tthrow new Error(`Approval receipt permissions must be 0600: ${filePath}`);\n\t}\n\tif (stat.size > MAX_RECEIPT_BYTES) throw new Error(`Approval receipt exceeds ${MAX_RECEIPT_BYTES} bytes`);\n}\n\nfunction readReceiptFromFd(fd: number, filePath: string): ApprovalReceipt {\n\tassertPrivateReceiptFd(fd, filePath);\n\tconst buffer = Buffer.alloc(MAX_RECEIPT_BYTES + 1);\n\tlet offset = 0;\n\twhile (offset < buffer.length) {\n\t\tconst read = readSync(fd, buffer, offset, buffer.length - offset, offset);\n\t\tif (read === 0) break;\n\t\toffset += read;\n\t}\n\tif (offset > MAX_RECEIPT_BYTES) throw new Error(`Approval receipt exceeds ${MAX_RECEIPT_BYTES} bytes`);\n\tassertPrivateReceiptFd(fd, filePath);\n\tlet value: unknown;\n\ttry {\n\t\tvalue = JSON.parse(buffer.subarray(0, offset).toString(\"utf8\")) as unknown;\n\t} catch {\n\t\tthrow new Error(`Approval receipt is not valid JSON: ${filePath}`);\n\t}\n\treturn validateApprovalReceipt(value);\n}\n\nexport class ApprovalReceiptStore {\n\treadonly rootDir: string;\n\n\tconstructor(rootDir: string) {\n\t\tthis.rootDir = resolve(rootDir);\n\t\tthis.ensurePrivateDirectory();\n\t}\n\n\tprivate ensurePrivateDirectory(): RootIdentity {\n\t\tmkdirSync(this.rootDir, { recursive: true, mode: 0o700 });\n\t\tconst stat = lstatSync(this.rootDir);\n\t\tif (stat.isSymbolicLink() || !stat.isDirectory()) {\n\t\t\tthrow new Error(`Approval receipt root must be a real directory: ${this.rootDir}`);\n\t\t}\n\t\tassertOwnedByCurrentUser(stat.uid, `Approval receipt root ${this.rootDir}`);\n\t\tif ((stat.mode & 0o777) !== 0o700) {\n\t\t\tthrow new Error(`Approval receipt root permissions must be 0700: ${this.rootDir}`);\n\t\t}\n\t\treturn { dev: stat.dev, ino: stat.ino };\n\t}\n\n\tprivate assertRootUnchanged(expected: RootIdentity): void {\n\t\tconst current = this.ensurePrivateDirectory();\n\t\tif (current.dev !== expected.dev || current.ino !== expected.ino) {\n\t\t\tthrow new Error(\"Approval receipt root changed during the operation\");\n\t\t}\n\t}\n\n\tprivate pathFor(receiptId: string): string {\n\t\treturn join(this.rootDir, receiptFileName(receiptId));\n\t}\n\n\tprivate openReceiptForRead(filePath: string): number {\n\t\tconst fd = openSync(filePath, constants.O_RDONLY | NOFOLLOW);\n\t\ttry {\n\t\t\tassertPrivateReceiptFd(fd, filePath);\n\t\t\treturn fd;\n\t\t} catch (error) {\n\t\t\tcloseSync(fd);\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\twrite(receipt: ApprovalReceipt): ApprovalReceiptWriteResult {\n\t\tconst validated = validateApprovalReceipt(receipt);\n\t\tconst rootIdentity = this.ensurePrivateDirectory();\n\t\tconst filePath = this.pathFor(validated.core.receiptId);\n\t\tconst canonical = serializeApprovalReceipt(validated);\n\t\tconst bytes = Buffer.from(canonical, \"utf8\");\n\t\tif (bytes.byteLength > MAX_RECEIPT_BYTES) throw new Error(`Approval receipt exceeds ${MAX_RECEIPT_BYTES} bytes`);\n\n\t\tlet fd: number;\n\t\ttry {\n\t\t\tfd = openSync(filePath, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | NOFOLLOW, 0o600);\n\t\t} catch (error) {\n\t\t\tif (!isErrno(error, \"EEXIST\")) throw error;\n\t\t\tconst existing = this.read(validated.core.receiptId);\n\t\t\tif (serializeApprovalReceipt(existing) !== canonical) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Approval receipt already exists with different content and is never overwritten: ${validated.core.receiptId}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tthis.assertRootUnchanged(rootIdentity);\n\t\t\treturn { path: filePath, created: false };\n\t\t}\n\n\t\ttry {\n\t\t\tfchmodSync(fd, 0o600);\n\t\t\tassertPrivateReceiptFd(fd, filePath);\n\t\t\twriteFileSync(fd, bytes);\n\t\t\tfsyncSync(fd);\n\t\t\tassertPrivateReceiptFd(fd, filePath);\n\t\t} finally {\n\t\t\tcloseSync(fd);\n\t\t}\n\t\tthis.assertRootUnchanged(rootIdentity);\n\t\treturn { path: filePath, created: true };\n\t}\n\n\thas(receiptId: string): boolean {\n\t\tconst rootIdentity = this.ensurePrivateDirectory();\n\t\tlet fd: number;\n\t\ttry {\n\t\t\tfd = this.openReceiptForRead(this.pathFor(receiptId));\n\t\t} catch (error) {\n\t\t\tif (isErrno(error, \"ENOENT\")) {\n\t\t\t\tthis.assertRootUnchanged(rootIdentity);\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t\tcloseSync(fd);\n\t\tthis.assertRootUnchanged(rootIdentity);\n\t\treturn true;\n\t}\n\n\tread(receiptId: string): ApprovalReceipt {\n\t\tconst rootIdentity = this.ensurePrivateDirectory();\n\t\tconst filePath = this.pathFor(receiptId);\n\t\tconst fd = this.openReceiptForRead(filePath);\n\t\ttry {\n\t\t\treturn readReceiptFromFd(fd, filePath);\n\t\t} finally {\n\t\t\tcloseSync(fd);\n\t\t\tthis.assertRootUnchanged(rootIdentity);\n\t\t}\n\t}\n\n\tlistReceiptIds(): string[] {\n\t\tconst rootIdentity = this.ensurePrivateDirectory();\n\t\tconst ids = readdirSync(this.rootDir)\n\t\t\t.map((name) => /^(approval-[a-f0-9]{32})\\.json$/u.exec(name)?.[1])\n\t\t\t.filter((id): id is string => id !== undefined)\n\t\t\t.sort((left, right) => left.localeCompare(right));\n\t\tfor (const id of ids) this.has(id);\n\t\tthis.assertRootUnchanged(rootIdentity);\n\t\treturn ids;\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"approval-receipt-store.js","sourceRoot":"","sources":["../../src/approvals/approval-receipt-store.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n\tcloseSync,\n\tconstants,\n\tfchmodSync,\n\tfstatSync,\n\tfsyncSync,\n\tlstatSync,\n\tmkdirSync,\n\topenSync,\n\treaddirSync,\n\treadSync,\n\twriteFileSync,\n} from \"node:fs\";\nimport { join, resolve } from \"node:path\";\nimport { type ApprovalReceipt, serializeApprovalReceipt, validateApprovalReceipt } from \"./approval-receipt.ts\";\n\nconst MAX_RECEIPT_BYTES = 1024 * 1024;\nconst NOFOLLOW = constants.O_NOFOLLOW ?? 0;\n\nexport interface ApprovalReceiptWriteResult {\n\tpath: string;\n\tcreated: boolean;\n}\n\ninterface RootIdentity {\n\tdev: number;\n\tino: number;\n}\n\nfunction isErrno(error: unknown, code: string): boolean {\n\treturn error instanceof Error && \"code\" in error && error.code === code;\n}\n\nfunction assertOwnedByCurrentUser(uid: number, label: string): void {\n\tconst getuid = process.getuid;\n\tif (typeof getuid === \"function\" && uid !== getuid()) {\n\t\tthrow new Error(`${label} is not owned by the current user`);\n\t}\n}\n\nfunction receiptFileName(receiptId: string): string {\n\tif (!/^approval-[a-f0-9]{32}$/u.test(receiptId)) throw new Error(\"Approval receipt id is invalid\");\n\treturn `${receiptId}.json`;\n}\n\nfunction assertPrivateReceiptFd(fd: number, filePath: string): void {\n\tconst stat = fstatSync(fd);\n\tif (!stat.isFile()) throw new Error(`Approval receipt is not a regular file: ${filePath}`);\n\tif (stat.nlink !== 1) throw new Error(`Approval receipt must not be hard-linked: ${filePath}`);\n\tassertOwnedByCurrentUser(stat.uid, `Approval receipt ${filePath}`);\n\tif ((stat.mode & 0o777) !== 0o600) {\n\t\tthrow new Error(`Approval receipt permissions must be 0600: ${filePath}`);\n\t}\n\tif (stat.size > MAX_RECEIPT_BYTES) throw new Error(`Approval receipt exceeds ${MAX_RECEIPT_BYTES} bytes`);\n}\n\nfunction readReceiptFromFd(fd: number, filePath: string): ApprovalReceipt {\n\tassertPrivateReceiptFd(fd, filePath);\n\tconst buffer = Buffer.alloc(MAX_RECEIPT_BYTES + 1);\n\tlet offset = 0;\n\twhile (offset < buffer.length) {\n\t\tconst read = readSync(fd, buffer, offset, buffer.length - offset, offset);\n\t\tif (read === 0) break;\n\t\toffset += read;\n\t}\n\tif (offset > MAX_RECEIPT_BYTES) throw new Error(`Approval receipt exceeds ${MAX_RECEIPT_BYTES} bytes`);\n\tassertPrivateReceiptFd(fd, filePath);\n\tlet value: unknown;\n\ttry {\n\t\tvalue = JSON.parse(buffer.subarray(0, offset).toString(\"utf8\")) as unknown;\n\t} catch {\n\t\tthrow new Error(`Approval receipt is not valid JSON: ${filePath}`);\n\t}\n\treturn validateApprovalReceipt(value);\n}\n\nexport class ApprovalReceiptStore {\n\treadonly rootDir: string;\n\n\tconstructor(rootDir: string) {\n\t\tthis.rootDir = resolve(rootDir);\n\t\tthis.ensurePrivateDirectory();\n\t}\n\n\tprivate ensurePrivateDirectory(): RootIdentity {\n\t\tmkdirSync(this.rootDir, { recursive: true, mode: 0o700 });\n\t\tconst stat = lstatSync(this.rootDir);\n\t\tif (stat.isSymbolicLink() || !stat.isDirectory()) {\n\t\t\tthrow new Error(`Approval receipt root must be a real directory: ${this.rootDir}`);\n\t\t}\n\t\tassertOwnedByCurrentUser(stat.uid, `Approval receipt root ${this.rootDir}`);\n\t\tif ((stat.mode & 0o777) !== 0o700) {\n\t\t\tthrow new Error(`Approval receipt root permissions must be 0700: ${this.rootDir}`);\n\t\t}\n\t\treturn { dev: stat.dev, ino: stat.ino };\n\t}\n\n\tprivate assertRootUnchanged(expected: RootIdentity): void {\n\t\tconst current = this.ensurePrivateDirectory();\n\t\tif (current.dev !== expected.dev || current.ino !== expected.ino) {\n\t\t\tthrow new Error(\"Approval receipt root changed during the operation\");\n\t\t}\n\t}\n\n\tprivate pathFor(receiptId: string): string {\n\t\treturn join(this.rootDir, receiptFileName(receiptId));\n\t}\n\n\tprivate openReceiptForRead(filePath: string): number {\n\t\tconst fd = openSync(filePath, constants.O_RDONLY | NOFOLLOW);\n\t\ttry {\n\t\t\tassertPrivateReceiptFd(fd, filePath);\n\t\t\treturn fd;\n\t\t} catch (error) {\n\t\t\tcloseSync(fd);\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\twrite(receipt: ApprovalReceipt): ApprovalReceiptWriteResult {\n\t\tconst validated = validateApprovalReceipt(receipt);\n\t\tconst rootIdentity = this.ensurePrivateDirectory();\n\t\tconst filePath = this.pathFor(validated.core.receiptId);\n\t\tconst canonical = serializeApprovalReceipt(validated);\n\t\tconst bytes = Buffer.from(canonical, \"utf8\");\n\t\tif (bytes.byteLength > MAX_RECEIPT_BYTES) throw new Error(`Approval receipt exceeds ${MAX_RECEIPT_BYTES} bytes`);\n\n\t\t// Explicit existence/symlink pre-check. O_EXCL/O_NOFOLLOW are honored on\n\t\t// proper Linux but silently ignored on some platforms (e.g. WSL2), which\n\t\t// would silently void the never-overwrite and symlink-rejection invariants\n\t\t// (hard rule: no silent degraded operation). Enforce them via lstat so the\n\t\t// guarantees hold on every platform; O_EXCL below stays as the atomic\n\t\t// backstop for the create path on capable platforms.\n\t\tlet existingStat: ReturnType<typeof lstatSync> | null = null;\n\t\ttry {\n\t\t\texistingStat = lstatSync(filePath);\n\t\t} catch (error) {\n\t\t\tif (!isErrno(error, \"ENOENT\")) throw error;\n\t\t}\n\t\tif (existingStat !== null) {\n\t\t\tif (existingStat.isSymbolicLink()) {\n\t\t\t\tthrow new Error(`Approval receipt must not be a symbolic link: ${filePath}`);\n\t\t\t}\n\t\t\tconst prior = this.read(validated.core.receiptId);\n\t\t\tif (serializeApprovalReceipt(prior) !== canonical) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Approval receipt already exists with different content and is never overwritten: ${validated.core.receiptId}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tthis.assertRootUnchanged(rootIdentity);\n\t\t\treturn { path: filePath, created: false };\n\t\t}\n\n\t\tlet fd: number;\n\t\ttry {\n\t\t\tfd = openSync(filePath, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | NOFOLLOW, 0o600);\n\t\t} catch (error) {\n\t\t\tif (!isErrno(error, \"EEXIST\")) throw error;\n\t\t\tconst existing = this.read(validated.core.receiptId);\n\t\t\tif (serializeApprovalReceipt(existing) !== canonical) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Approval receipt already exists with different content and is never overwritten: ${validated.core.receiptId}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tthis.assertRootUnchanged(rootIdentity);\n\t\t\treturn { path: filePath, created: false };\n\t\t}\n\n\t\ttry {\n\t\t\tfchmodSync(fd, 0o600);\n\t\t\tassertPrivateReceiptFd(fd, filePath);\n\t\t\twriteFileSync(fd, bytes);\n\t\t\tfsyncSync(fd);\n\t\t\tassertPrivateReceiptFd(fd, filePath);\n\t\t} finally {\n\t\t\tcloseSync(fd);\n\t\t}\n\t\tthis.assertRootUnchanged(rootIdentity);\n\t\treturn { path: filePath, created: true };\n\t}\n\n\thas(receiptId: string): boolean {\n\t\tconst rootIdentity = this.ensurePrivateDirectory();\n\t\tlet fd: number;\n\t\ttry {\n\t\t\tfd = this.openReceiptForRead(this.pathFor(receiptId));\n\t\t} catch (error) {\n\t\t\tif (isErrno(error, \"ENOENT\")) {\n\t\t\t\tthis.assertRootUnchanged(rootIdentity);\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t\tcloseSync(fd);\n\t\tthis.assertRootUnchanged(rootIdentity);\n\t\treturn true;\n\t}\n\n\tread(receiptId: string): ApprovalReceipt {\n\t\tconst rootIdentity = this.ensurePrivateDirectory();\n\t\tconst filePath = this.pathFor(receiptId);\n\t\tconst fd = this.openReceiptForRead(filePath);\n\t\ttry {\n\t\t\treturn readReceiptFromFd(fd, filePath);\n\t\t} finally {\n\t\t\tcloseSync(fd);\n\t\t\tthis.assertRootUnchanged(rootIdentity);\n\t\t}\n\t}\n\n\tlistReceiptIds(): string[] {\n\t\tconst rootIdentity = this.ensurePrivateDirectory();\n\t\tconst ids = readdirSync(this.rootDir)\n\t\t\t.map((name) => /^(approval-[a-f0-9]{32})\\.json$/u.exec(name)?.[1])\n\t\t\t.filter((id): id is string => id !== undefined)\n\t\t\t.sort((left, right) => left.localeCompare(right));\n\t\tfor (const id of ids) this.has(id);\n\t\tthis.assertRootUnchanged(rootIdentity);\n\t\treturn ids;\n\t}\n}\n"]}
|
|
@@ -293,7 +293,7 @@ export declare class AgentSession {
|
|
|
293
293
|
private _pendingNextTurnMessages;
|
|
294
294
|
private _compactionAbortController;
|
|
295
295
|
private _autoCompactionAbortController;
|
|
296
|
-
private
|
|
296
|
+
private _overflowRecoveryAttempts;
|
|
297
297
|
private _thresholdCompactionEmergency;
|
|
298
298
|
private _compactionHysteresisState;
|
|
299
299
|
private _branchSummaryAbortController;
|
|
@@ -789,6 +789,7 @@ export declare class AgentSession {
|
|
|
789
789
|
private _effectiveTurnContextWindow;
|
|
790
790
|
private _checkProjectedCompaction;
|
|
791
791
|
private _checkCompaction;
|
|
792
|
+
private _overflowCompactionSettings;
|
|
792
793
|
private _runAutoCompaction;
|
|
793
794
|
/**
|
|
794
795
|
* Toggle auto-compaction setting.
|
|
@@ -832,7 +833,6 @@ export declare class AgentSession {
|
|
|
832
833
|
private _refreshToolRegistry;
|
|
833
834
|
private _buildRuntime;
|
|
834
835
|
reload(): Promise<void>;
|
|
835
|
-
private _isNonRetryableProviderLimitError;
|
|
836
836
|
/**
|
|
837
837
|
* Check if an error is retryable (overloaded, rate limit, server errors).
|
|
838
838
|
* Context overflow errors are NOT retryable (handled by compaction instead).
|