@sema-agent/core 2.13.0 → 4.0.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 +2345 -0
- package/dist/agents/cascade.d.ts +6 -1
- package/dist/agents/cascade.js +76 -32
- package/dist/agents/cumulative-stats.d.ts +29 -0
- package/dist/agents/cumulative-stats.js +29 -0
- package/dist/agents/observer.d.ts +4 -0
- package/dist/agents/observer.js +11 -10
- package/dist/agents/repair-loop.d.ts +2 -0
- package/dist/agents/repair-loop.js +110 -3
- package/dist/agents/retain-ledger.d.ts +20 -2
- package/dist/agents/retain-ledger.js +38 -8
- package/dist/agents/roster-store.d.ts +4 -0
- package/dist/agents/roster-store.js +20 -1
- package/dist/agents/subagent.js +66 -64
- package/dist/agents/teacher.d.ts +1 -9
- package/dist/agents/teacher.js +23 -24
- package/dist/agents/team.js +7 -4
- package/dist/agents/verify.d.ts +15 -2
- package/dist/agents/verify.js +39 -3
- package/dist/bin/sema-tb.js +2 -2
- package/dist/brain/anthropic.js +24 -21
- package/dist/brain/circuit-breaker.d.ts +1 -0
- package/dist/brain/circuit-breaker.js +26 -9
- package/dist/brain/degrading.js +13 -1
- package/dist/brain/failover.js +2 -0
- package/dist/brain/openai.js +24 -21
- package/dist/brain/repetition.js +10 -1
- package/dist/brain/routing.js +5 -1
- package/dist/brain/status-sink.js +5 -1
- package/dist/brain/stream-engine.js +5 -2
- package/dist/brain/stream-shared.d.ts +1 -0
- package/dist/brain/stream-shared.js +14 -0
- package/dist/brain/terminal-cause.d.ts +4 -0
- package/dist/brain/terminal-cause.js +12 -0
- package/dist/brain/tool-call-id.d.ts +1 -0
- package/dist/brain/tool-call-id.js +3 -0
- package/dist/brain/tool-call-repair.js +34 -15
- package/dist/core/auto-compaction.js +33 -32
- package/dist/core/background-agent-store.d.ts +22 -22
- package/dist/core/background-agent-store.js +19 -23
- package/dist/core/checkpoint-store.d.ts +7 -13
- package/dist/core/checkpoint-store.js +39 -15
- package/dist/core/consolidate-scope.js +4 -3
- package/dist/core/context-edit.js +3 -0
- package/dist/core/context-guard.js +3 -0
- package/dist/core/file-snapshot-store.d.ts +8 -0
- package/dist/core/file-snapshot-store.js +4 -3
- package/dist/core/git-worktree-env.d.ts +6 -1
- package/dist/core/git-worktree-env.js +12 -1
- package/dist/core/hooks.d.ts +4 -1
- package/dist/core/hooks.js +35 -7
- package/dist/core/lsp-diagnostics.js +13 -7
- package/dist/core/lsp-protocol.d.ts +1 -1
- package/dist/core/lsp-protocol.js +20 -8
- package/dist/core/lsp-session.d.ts +12 -2
- package/dist/core/lsp-session.js +114 -49
- package/dist/core/lsp.d.ts +13 -1
- package/dist/core/lsp.js +65 -9
- package/dist/core/mailbox-store.d.ts +7 -10
- package/dist/core/mcp.d.ts +7 -10
- package/dist/core/mcp.js +7 -3
- package/dist/core/memory-engine/data-plane.js +4 -0
- package/dist/core/memory-engine/engine.d.ts +2 -2
- package/dist/core/memory-engine/engine.js +96 -31
- package/dist/core/memory-engine/file-backend.d.ts +2 -2
- package/dist/core/memory-engine/file-backend.js +38 -39
- package/dist/core/memory-engine/layout.d.ts +8 -0
- package/dist/core/memory-engine/layout.js +71 -4
- package/dist/core/memory-engine/scope-contract.js +9 -3
- package/dist/core/memory-engine/types.d.ts +1 -1
- package/dist/core/memory.js +3 -0
- package/dist/core/permission-rules.d.ts +2 -1
- package/dist/core/permission-rules.js +24 -3
- package/dist/core/runner/active-skill-scope.d.ts +0 -1
- package/dist/core/runner/active-skill-scope.js +1 -15
- package/dist/core/runner/assemble-result.d.ts +3 -8
- package/dist/core/runner/assemble-result.js +3 -5
- package/dist/core/runner/compaction-call-options.d.ts +7 -0
- package/dist/core/runner/compaction-call-options.js +30 -0
- package/dist/core/runner/prepare-task.d.ts +6 -23
- package/dist/core/runner/prepare-task.js +31 -53
- package/dist/core/runner/runtask.d.ts +1 -0
- package/dist/core/runner/runtask.js +86 -178
- package/dist/core/runner/session-rule-policy.js +4 -1
- package/dist/core/runner/teardown-bounded.d.ts +7 -0
- package/dist/core/runner/teardown-bounded.js +36 -0
- package/dist/core/runner/tool-disclosure.d.ts +2 -5
- package/dist/core/runner/turn-attachments.d.ts +5 -15
- package/dist/core/runner/turn-attachments.js +1 -1
- package/dist/core/runner/usage-accounting.d.ts +6 -0
- package/dist/core/runner/usage-accounting.js +9 -8
- package/dist/core/safe-notify.d.ts +17 -0
- package/dist/core/safe-notify.js +59 -0
- package/dist/core/secret-env.js +5 -1
- package/dist/core/sensitive-path-policy.js +11 -4
- package/dist/core/store-contracts/checkpoint-store-contract.js +1 -1
- package/dist/core/store-contracts/session-repo-contract.js +40 -0
- package/dist/core/stub-env.d.ts +2 -6
- package/dist/core/surrogate-safe-slice.d.ts +4 -0
- package/dist/core/surrogate-safe-slice.js +18 -0
- package/dist/core/task-registry-monitor.js +15 -14
- package/dist/core/task-registry-shared.d.ts +22 -0
- package/dist/core/task-registry-shared.js +16 -0
- package/dist/core/task-registry.d.ts +1 -0
- package/dist/core/task-registry.js +40 -57
- package/dist/core/tool-errors.js +5 -4
- package/dist/core/tool-name-aliases.js +2 -1
- package/dist/core/tool-policy.js +72 -19
- package/dist/core/tool-result-store.d.ts +1 -0
- package/dist/core/tool-result-store.js +17 -1
- package/dist/core/trace.d.ts +3 -9
- package/dist/core/types.d.ts +16 -12
- package/dist/core/untrusted-egress.js +7 -5
- package/dist/core/workflow-journal-store.d.ts +9 -20
- package/dist/core/workflow-run-store.d.ts +11 -10
- package/dist/core/workflow-run-store.js +22 -16
- package/dist/engine/compaction/compaction.d.ts +1 -0
- package/dist/engine/compaction/compaction.js +14 -5
- package/dist/engine/compaction/utils.js +4 -2
- package/dist/engine/execution-env/node-execution-env.d.ts +2 -24
- package/dist/engine/harness/agent-harness.d.ts +1 -0
- package/dist/engine/harness/agent-harness.js +13 -33
- package/dist/engine/harness/types.d.ts +29 -67
- package/dist/engine/llm/types.d.ts +1 -1
- package/dist/engine/loop/agent-loop.js +11 -4
- package/dist/engine/loop/types.d.ts +11 -9
- package/dist/engine/lsp/frame-decoder.js +6 -3
- package/dist/engine/lsp/node-lsp-manager.d.ts +2 -0
- package/dist/engine/lsp/node-lsp-manager.js +103 -26
- package/dist/engine/lsp/stdio-lsp-transport.js +16 -7
- package/dist/engine/session/memory-repo.d.ts +2 -6
- package/dist/engine/session/session.d.ts +5 -30
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -1
- package/dist/internal/harness-types.d.ts +3 -1
- package/dist/internal/harness.d.ts +1 -1
- package/dist/internal/harness.js +1 -1
- package/dist/orchestration/run-spec.js +7 -4
- package/dist/orchestration/run-workflow-tool.js +3 -35
- package/dist/orchestration/workflow-governance.js +43 -9
- package/dist/orchestration/workflow-sandbox-conformance.js +85 -51
- package/dist/orchestration/workflow-script-store.js +34 -4
- package/dist/orchestration/workflow.d.ts +3 -11
- package/dist/orchestration/workflow.js +164 -204
- package/dist/prompt-assembly/artifact-store.d.ts +2 -0
- package/dist/prompt-assembly/artifact-store.js +39 -24
- package/dist/prompt-assembly/assemble.js +38 -7
- package/dist/prompt-assembly/epoch.js +19 -2
- package/dist/prompt-assembly/event-registry.js +3 -3
- package/dist/prompt-assembly/explain.js +1 -1
- package/dist/prompt-assembly/tool-catalog.d.ts +10 -9
- package/dist/prompt-assembly/tool-catalog.js +16 -1
- package/dist/prompt-assembly/turn-snapshot.d.ts +2 -5
- package/dist/prompt-assembly/turn-snapshot.js +4 -1
- package/dist/stores/file/background-agent-store.d.ts +3 -12
- package/dist/stores/file/background-agent-store.js +3 -41
- package/dist/stores/file/checkpoint-store.js +6 -6
- package/dist/stores/file/fs-atomic.d.ts +3 -0
- package/dist/stores/file/fs-atomic.js +53 -8
- package/dist/stores/file/index.d.ts +1 -0
- package/dist/stores/file/index.js +7 -0
- package/dist/stores/file/mailbox-store.d.ts +2 -6
- package/dist/stores/file/session-policy-store.js +15 -1
- package/dist/stores/file/session-store.d.ts +4 -6
- package/dist/stores/file/session-store.js +28 -1
- package/dist/stores/file/workflow-journal-store.d.ts +3 -11
- package/dist/stores/file/workflow-run-store.d.ts +3 -7
- package/dist/stores/file/workflow-run-store.js +4 -17
- package/dist/tools/fs/bash-readonly-classifier.d.ts +2 -0
- package/dist/tools/fs/bash-readonly-classifier.js +73 -21
- package/dist/tools/fs/fs-bash.d.ts +1 -1
- package/dist/tools/fs/fs-bash.js +407 -91
- package/dist/tools/fs/fs-pdf.d.ts +2 -12
- package/dist/tools/fs/fs-shared.js +2 -2
- package/dist/tools/fs/notebook.d.ts +2 -5
- package/dist/tools/fs/safety.d.ts +3 -0
- package/dist/tools/fs/safety.js +43 -14
- package/dist/tools/fs/search.js +5 -5
- package/dist/tools/loop-tick.js +1 -1
- package/dist/tools/monitor.d.ts +1 -0
- package/dist/tools/monitor.js +57 -9
- package/dist/tools/worktree.d.ts +4 -7
- package/dist/tools/worktree.js +235 -143
- package/package.json +9 -2
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { mkdirSync, promises as fs } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { verifyPromptArtifact, } from "./artifact.js";
|
|
4
|
+
function normalizedEnvelope(parsed, artifact) {
|
|
5
|
+
return { ...parsed, artifact };
|
|
6
|
+
}
|
|
4
7
|
export class MemoryPromptArtifactStore {
|
|
5
8
|
byDigest = new Map();
|
|
6
9
|
async get(artifactDigest, opts) {
|
|
@@ -17,7 +20,7 @@ export class MemoryPromptArtifactStore {
|
|
|
17
20
|
const v = verifyPromptArtifact(parsed, opts);
|
|
18
21
|
if (!v.ok || v.artifact.artifactDigest !== artifactDigest)
|
|
19
22
|
return undefined;
|
|
20
|
-
return parsed;
|
|
23
|
+
return normalizedEnvelope(parsed, v.artifact);
|
|
21
24
|
}
|
|
22
25
|
async put(envelope) {
|
|
23
26
|
this.byDigest.set(envelope.artifact.artifactDigest, JSON.stringify(envelope));
|
|
@@ -54,7 +57,7 @@ export class FilePromptArtifactStore {
|
|
|
54
57
|
const v = verifyPromptArtifact(parsed, opts);
|
|
55
58
|
if (!v.ok || v.artifact.artifactDigest !== artifactDigest)
|
|
56
59
|
return undefined;
|
|
57
|
-
return parsed;
|
|
60
|
+
return normalizedEnvelope(parsed, v.artifact);
|
|
58
61
|
}
|
|
59
62
|
async put(envelope) {
|
|
60
63
|
const file = this.fileFor(envelope.artifact.artifactDigest);
|
|
@@ -113,6 +116,12 @@ export class CenterPromptSource {
|
|
|
113
116
|
engineVersion;
|
|
114
117
|
candidate;
|
|
115
118
|
state = "unavailable";
|
|
119
|
+
tail = Promise.resolve();
|
|
120
|
+
exclusive(fn) {
|
|
121
|
+
const run = this.tail.then(fn, fn);
|
|
122
|
+
this.tail = run.then(() => undefined, () => undefined);
|
|
123
|
+
return run;
|
|
124
|
+
}
|
|
116
125
|
constructor(opts) {
|
|
117
126
|
this.store = opts.store;
|
|
118
127
|
this.stateStore = opts.stateStore ?? new MemoryPromptSourceStateStore();
|
|
@@ -128,20 +137,22 @@ export class CenterPromptSource {
|
|
|
128
137
|
return env?.artifact;
|
|
129
138
|
}
|
|
130
139
|
async restore() {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
140
|
+
return this.exclusive(async () => {
|
|
141
|
+
const state = await this.stateStore.load();
|
|
142
|
+
if (state?.state === "disabled") {
|
|
143
|
+
this.candidate = undefined;
|
|
144
|
+
this.state = "disabled";
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
if (state?.state !== "active") {
|
|
148
|
+
this.candidate = undefined;
|
|
149
|
+
this.state = "unavailable";
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const env = await this.store.get(state.artifactDigest, { engineVersion: this.engineVersion });
|
|
153
|
+
this.candidate = env !== undefined ? { artifact: env.artifact, sourceRevision: state.sourceRevision } : undefined;
|
|
154
|
+
this.state = env !== undefined ? "active" : "unavailable";
|
|
155
|
+
});
|
|
145
156
|
}
|
|
146
157
|
sourceState() {
|
|
147
158
|
return this.state;
|
|
@@ -151,15 +162,19 @@ export class CenterPromptSource {
|
|
|
151
162
|
if (!v.ok)
|
|
152
163
|
return v;
|
|
153
164
|
const envelope = raw;
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
165
|
+
return this.exclusive(async () => {
|
|
166
|
+
await this.store.put(envelope);
|
|
167
|
+
await this.stateStore.save({ state: "active", artifactDigest: v.artifact.artifactDigest, sourceRevision: envelope.sourceRevision });
|
|
168
|
+
this.candidate = { artifact: v.artifact, sourceRevision: envelope.sourceRevision };
|
|
169
|
+
this.state = "active";
|
|
170
|
+
return { ok: true, artifactDigest: v.artifact.artifactDigest };
|
|
171
|
+
});
|
|
159
172
|
}
|
|
160
173
|
async applyDisabled(sourceRevision) {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
174
|
+
return this.exclusive(async () => {
|
|
175
|
+
await this.stateStore.save({ state: "disabled", sourceRevision });
|
|
176
|
+
this.candidate = undefined;
|
|
177
|
+
this.state = "disabled";
|
|
178
|
+
});
|
|
164
179
|
}
|
|
165
180
|
}
|
|
@@ -40,6 +40,7 @@ function mountDeclarations(basePack, decls, onWarn) {
|
|
|
40
40
|
const counters = {};
|
|
41
41
|
const extra = [];
|
|
42
42
|
let replacesRoleBase = false;
|
|
43
|
+
let replacedRoleBaseBy;
|
|
43
44
|
for (const d of decls) {
|
|
44
45
|
if (!d.id || d.id.startsWith("core/") || d.id.length > 128 || !/^[a-z0-9][a-z0-9/._-]*$/.test(d.id)) {
|
|
45
46
|
throw new Error(`prompt-provider stableBlocks: invalid section id ${JSON.stringify(d.id).slice(0, 140)} (must match ^[a-z0-9][a-z0-9/._-]*$, ≤128 chars, non-core/)`);
|
|
@@ -47,8 +48,10 @@ function mountDeclarations(basePack, decls, onWarn) {
|
|
|
47
48
|
const base = SLOT_BASE[d.slot];
|
|
48
49
|
if (base === undefined)
|
|
49
50
|
throw new Error(`prompt-provider stableBlocks: unknown slot "${String(d.slot)}" ("${d.id}")`);
|
|
50
|
-
if (d.slot === "identity")
|
|
51
|
+
if (d.slot === "identity") {
|
|
51
52
|
replacesRoleBase = true;
|
|
53
|
+
replacedRoleBaseBy ??= d.id;
|
|
54
|
+
}
|
|
52
55
|
const n = (counters[d.slot] = (counters[d.slot] ?? 0) + 1);
|
|
53
56
|
const declaredContentHash = verifyDeclaredContentHash(d, onWarn);
|
|
54
57
|
extra.push({
|
|
@@ -68,10 +71,21 @@ function mountDeclarations(basePack, decls, onWarn) {
|
|
|
68
71
|
});
|
|
69
72
|
}
|
|
70
73
|
return {
|
|
71
|
-
|
|
72
|
-
|
|
74
|
+
pack: {
|
|
75
|
+
...basePack,
|
|
76
|
+
sections: [...basePack.sections.filter((s) => !(replacesRoleBase && s.id === "core/role.base")), ...extra],
|
|
77
|
+
},
|
|
78
|
+
...(replacedRoleBaseBy !== undefined && basePack.sections.some((s) => s.id === "core/role.base")
|
|
79
|
+
? { replacedRoleBaseBy }
|
|
80
|
+
: {}),
|
|
73
81
|
};
|
|
74
82
|
}
|
|
83
|
+
function warnRoleBaseEviction(replacedRoleBaseBy, origin, onWarn) {
|
|
84
|
+
if (replacedRoleBaseBy === undefined || origin === "pack-default")
|
|
85
|
+
return;
|
|
86
|
+
const what = origin === "task-system-prompt" ? "the caller's per-task systemPrompt" : "the PromptProvider's own role text";
|
|
87
|
+
onWarn?.(`prompt-constitution: identity declaration "${replacedRoleBaseBy}" supplants the role base, so ${what} does NOT compose into this prompt (identity sections replace the role layer by design). Fold that text into the declaration, or use a scenario/behavior slot to keep the role base.`, "prompt-constitution");
|
|
88
|
+
}
|
|
75
89
|
export function assemblePrompt(inputs) {
|
|
76
90
|
const { provider, facts } = inputs;
|
|
77
91
|
const onWarn = inputs.onWarn
|
|
@@ -93,6 +107,7 @@ export function assemblePrompt(inputs) {
|
|
|
93
107
|
};
|
|
94
108
|
let pack = SEMA_DEFAULT_PACK;
|
|
95
109
|
let roleBase = inputs.userSystemPrompt ?? DEFAULT_SYSTEM_PROMPT;
|
|
110
|
+
let roleBaseOrigin = inputs.userSystemPrompt !== undefined ? "task-system-prompt" : "pack-default";
|
|
96
111
|
let constitution = "core";
|
|
97
112
|
let mountedCenter = [];
|
|
98
113
|
if (!inputs.isDefaultProvider && provider.stableBlocks) {
|
|
@@ -108,6 +123,10 @@ export function assemblePrompt(inputs) {
|
|
|
108
123
|
if (inputs.centerDeclarations && inputs.centerDeclarations.length > 0) {
|
|
109
124
|
onWarn?.("prompt-constitution: a center prompt artifact is adopted but the provider's assembled-identity pass-through owns the prompt — the center sections cannot mount on this migration path.", "prompt-constitution");
|
|
110
125
|
}
|
|
126
|
+
const droppedDecls = decls.filter((d) => d !== assembledIdentity);
|
|
127
|
+
if (droppedDecls.length > 0) {
|
|
128
|
+
onWarn?.(`prompt-provider stableBlocks: the assembled-identity pass-through owns the prompt — ${droppedDecls.length} other declaration(s) do NOT compose this run: ${droppedDecls.map((d) => `"${d.id}" (${d.slot})`).join(", ")}. Declare ONLY the role layer in the identity block to let them mount.`, "prompt-constitution");
|
|
129
|
+
}
|
|
111
130
|
const renderInputsPT = (date) => ({
|
|
112
131
|
roleBase: assembledIdentity.text,
|
|
113
132
|
facts,
|
|
@@ -153,29 +172,41 @@ export function assemblePrompt(inputs) {
|
|
|
153
172
|
return true;
|
|
154
173
|
});
|
|
155
174
|
mountedCenter = centerDecls;
|
|
156
|
-
|
|
175
|
+
const mountedSB = mountDeclarations(SEMA_DEFAULT_PACK, [...centerDecls, ...decls], onWarn);
|
|
176
|
+
pack = mountedSB.pack;
|
|
177
|
+
warnRoleBaseEviction(mountedSB.replacedRoleBaseBy, roleBaseOrigin, onWarn);
|
|
157
178
|
}
|
|
158
179
|
else if (!inputs.isDefaultProvider && provider.stableSystem) {
|
|
159
180
|
const roleOut = provider.stableSystem(stableCtx);
|
|
160
181
|
if (provider.replaceAll) {
|
|
161
182
|
constitution = "replaced";
|
|
162
183
|
roleBase = roleOut;
|
|
184
|
+
roleBaseOrigin = "provider";
|
|
163
185
|
pack = subsetPack(SEMA_DEFAULT_PACK, { exclude: REPLACE_ALL_EXCLUDED, roleBaseLegacyId: "provider.replace_all" });
|
|
164
186
|
}
|
|
165
187
|
else if (roleOut.includes(CYBER_RISK) && roleOut.includes(URL_SAFETY) && roleOut.includes(HARNESS_SECTION_ANCHOR)) {
|
|
166
188
|
constitution = "provider-assembled";
|
|
167
189
|
roleBase = roleOut;
|
|
190
|
+
roleBaseOrigin = "provider";
|
|
168
191
|
pack = subsetPack(SEMA_DEFAULT_PACK, { keep: OPAQUE_KEPT, roleBaseLegacyId: "provider.assembled" });
|
|
169
192
|
onWarn?.("prompt-constitution: this PromptProvider returns an already-assembled prompt (constitution anchor found). Core now appends the constitution structurally — return ONLY the role base from stableSystem (or set replaceAll:true to own the whole base). Passed through un-doubled.", "prompt-constitution");
|
|
170
193
|
}
|
|
171
194
|
else {
|
|
172
195
|
roleBase = roleOut;
|
|
196
|
+
roleBaseOrigin = "provider";
|
|
173
197
|
}
|
|
174
198
|
}
|
|
175
199
|
if (inputs.centerDeclarations && inputs.centerDeclarations.length > 0 && !(!inputs.isDefaultProvider && provider.stableBlocks)) {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
200
|
+
if (constitution === "provider-assembled") {
|
|
201
|
+
onWarn?.("prompt-constitution: a center prompt artifact is adopted but this PromptProvider returned an already-assembled prompt — the pass-through owns the prompt and the center sections cannot mount on this migration path (an identity section would evict the provider text that carries the constitution). Return ONLY the role base from stableSystem to let center sections compose.", "prompt-constitution");
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
const composedCenter = inputs.centerDeclarations;
|
|
205
|
+
const mountedC = mountDeclarations(pack, composedCenter, onWarn);
|
|
206
|
+
pack = mountedC.pack;
|
|
207
|
+
warnRoleBaseEviction(mountedC.replacedRoleBaseBy, roleBaseOrigin, onWarn);
|
|
208
|
+
mountedCenter = composedCenter;
|
|
209
|
+
}
|
|
179
210
|
}
|
|
180
211
|
const renderInputs = (date) => ({
|
|
181
212
|
roleBase,
|
|
@@ -20,12 +20,29 @@ const PROBE_FACTS_OFF = {
|
|
|
20
20
|
teammateEnabled: false,
|
|
21
21
|
orchestrationDeferred: false,
|
|
22
22
|
isSubagent: false,
|
|
23
|
+
promptProfile: "simple",
|
|
24
|
+
fableMitigations: false,
|
|
25
|
+
};
|
|
26
|
+
const PROBE_FACTS_ON = {
|
|
27
|
+
policyEnabled: true,
|
|
28
|
+
hooksEnabled: true,
|
|
29
|
+
isolationEnabled: true,
|
|
30
|
+
withinTaskCompactionEnabled: true,
|
|
31
|
+
supervisorEnabled: true,
|
|
32
|
+
orchestrationEnabled: true,
|
|
33
|
+
awarenessEnabled: true,
|
|
34
|
+
goalEnabled: true,
|
|
35
|
+
worktreeIsolated: true,
|
|
36
|
+
teammateEnabled: true,
|
|
37
|
+
orchestrationDeferred: true,
|
|
38
|
+
isSubagent: true,
|
|
39
|
+
promptProfile: "classic",
|
|
40
|
+
fableMitigations: true,
|
|
23
41
|
};
|
|
24
|
-
const PROBE_FACTS_ON = Object.fromEntries(Object.keys(PROBE_FACTS_OFF).map((k) => [k, true]));
|
|
25
42
|
const PROBE_VECTORS = [
|
|
26
43
|
PROBE_FACTS_OFF,
|
|
27
44
|
PROBE_FACTS_ON,
|
|
28
|
-
...Object.keys(PROBE_FACTS_OFF).map((k) => ({ ...PROBE_FACTS_OFF, [k]:
|
|
45
|
+
...Object.keys(PROBE_FACTS_OFF).map((k) => ({ ...PROBE_FACTS_OFF, [k]: PROBE_FACTS_ON[k] })),
|
|
29
46
|
];
|
|
30
47
|
const PROBE_INPUTS_BASE = {
|
|
31
48
|
roleBase: "\u0000probe-role\u0000",
|
|
@@ -4,7 +4,7 @@ export const EVENT_PROMPT_REGISTRY = new Map([
|
|
|
4
4
|
{ kind: "changed_files", carrier: "message.user-prefix", trust: "operator", dedupe: "replace-by-key", defaultPolicy: "off", rendererRef: "turn-attachments.ts#renderChangedFiles" },
|
|
5
5
|
{ kind: "plan_mode", carrier: "message.user-prefix", trust: "operator", dedupe: "replace-by-key", defaultPolicy: "off", rendererRef: "turn-attachments.ts#PLAN_MODE_FULL_BODY" },
|
|
6
6
|
{ kind: "date_change", carrier: "message.user-prefix", trust: "operator", dedupe: "replace-by-key", defaultPolicy: "always", rendererRef: "turn-attachments.ts#renderDateChange" },
|
|
7
|
-
{ kind: "instructions_change", carrier: "message.user-prefix", trust: "operator", dedupe: "replace-by-key", maxBytes: 512, defaultPolicy: "always", rendererRef: "turn-attachments.ts#
|
|
7
|
+
{ kind: "instructions_change", carrier: "message.user-prefix", trust: "operator", dedupe: "replace-by-key", maxBytes: 512, defaultPolicy: "always", rendererRef: "turn-attachments.ts#collectInstructionsChange" },
|
|
8
8
|
{ kind: "budget_usd", carrier: "message.user-prefix", trust: "operator", dedupe: "replace-by-key", defaultPolicy: "off", rendererRef: "turn-attachments.ts#renderBudgetUsd" },
|
|
9
9
|
{ kind: "background_tasks", carrier: "message.user-prefix", trust: "operator", dedupe: "replace-by-key", defaultPolicy: "off", rendererRef: "turn-attachments.ts#renderBackgroundTasks" },
|
|
10
10
|
{ kind: "tools_delta", carrier: "message.user-prefix", trust: "external", dedupe: "replace-by-key", defaultPolicy: "off", rendererRef: "turn-attachments.ts#renderToolsDelta" },
|
|
@@ -12,8 +12,8 @@ export const EVENT_PROMPT_REGISTRY = new Map([
|
|
|
12
12
|
{ kind: "skills_listing", carrier: "message.user-prefix", trust: "operator", dedupe: "replace-by-key", defaultPolicy: "on", rendererRef: "turn-attachments.ts#renderSkillsListingDelta" },
|
|
13
13
|
{ kind: "mcp_instructions", carrier: "message.user-prefix", trust: "external", dedupe: "replace-by-key", defaultPolicy: "off", rendererRef: "turn-attachments.ts#renderMcpInstructionsDelta" },
|
|
14
14
|
{ kind: "mcp_dropped_tools", carrier: "message.user-prefix", trust: "external", dedupe: "replace-by-key", defaultPolicy: "off", rendererRef: "turn-attachments.ts#renderMcpDroppedTools" },
|
|
15
|
-
{ kind: "deadline_converge", carrier: "message.user-prefix", trust: "operator", dedupe: "once-per-run", defaultPolicy: "always", rendererRef: "runtask.ts#
|
|
16
|
-
{ kind: "deadline_deliver_now", carrier: "message.user-prefix", trust: "operator", dedupe: "once-per-run", defaultPolicy: "always", rendererRef: "runtask.ts#
|
|
15
|
+
{ kind: "deadline_converge", carrier: "message.user-prefix", trust: "operator", dedupe: "once-per-run", defaultPolicy: "always", rendererRef: "runtask.ts#nudgeSchedule" },
|
|
16
|
+
{ kind: "deadline_deliver_now", carrier: "message.user-prefix", trust: "operator", dedupe: "once-per-run", defaultPolicy: "always", rendererRef: "runtask.ts#nudgeSchedule" },
|
|
17
17
|
{ kind: "deadline_finalize", carrier: "message.user-prefix", trust: "operator", dedupe: "once-per-run", defaultPolicy: "always", rendererRef: "runtask.ts#buildFinalizeText" },
|
|
18
18
|
{ kind: "final_verification", carrier: "message.user-prefix", trust: "operator", dedupe: "once-per-run", defaultPolicy: "always", rendererRef: "runtask.ts#final-verification" },
|
|
19
19
|
].map((e) => [e.kind, e]));
|
|
@@ -30,7 +30,7 @@ export function describeDefaultPack() {
|
|
|
30
30
|
return {
|
|
31
31
|
packId: SEMA_DEFAULT_PACK.packId,
|
|
32
32
|
roleBaseText: DEFAULT_SYSTEM_PROMPT,
|
|
33
|
-
sections: SEMA_DEFAULT_PACK.sections.map((s) => ({
|
|
33
|
+
sections: [...SEMA_DEFAULT_PACK.sections].sort((a, b) => a.rank - b.rank).map((s) => ({
|
|
34
34
|
id: s.id,
|
|
35
35
|
slot: s.slot,
|
|
36
36
|
rank: s.rank,
|
|
@@ -11,6 +11,15 @@ export interface ToolContractDescriptor {
|
|
|
11
11
|
wireSchemaDigest: string;
|
|
12
12
|
cardId: string;
|
|
13
13
|
}
|
|
14
|
+
export interface ToolManifestRow {
|
|
15
|
+
wireName: string;
|
|
16
|
+
aliases: string[];
|
|
17
|
+
contractId: string;
|
|
18
|
+
implementationRevision: string;
|
|
19
|
+
cardId: string;
|
|
20
|
+
shapeDigest: string;
|
|
21
|
+
wireSchemaDigest: string;
|
|
22
|
+
}
|
|
14
23
|
export declare function computeShapeDigest(schema: TSchema | undefined): string;
|
|
15
24
|
export declare function computeWireSchemaDigest(schema: TSchema | undefined, description: string): string;
|
|
16
25
|
export interface AttachContractInput {
|
|
@@ -22,15 +31,7 @@ export interface AttachContractInput {
|
|
|
22
31
|
export declare function attachToolContract(tool: AgentTool, input: AttachContractInput): void;
|
|
23
32
|
export declare function getToolContract(tool: AgentTool, fallbackOrigin?: ToolOrigin): ToolContractDescriptor;
|
|
24
33
|
export declare function hasToolContract(tool: AgentTool): boolean;
|
|
25
|
-
export declare function projectToolManifest(tools: AgentTool[], originOf?: (tool: AgentTool) => ToolOrigin):
|
|
26
|
-
wireName: string;
|
|
27
|
-
aliases: string[];
|
|
28
|
-
contractId: string;
|
|
29
|
-
implementationRevision: string;
|
|
30
|
-
cardId: string;
|
|
31
|
-
shapeDigest: string;
|
|
32
|
-
wireSchemaDigest: string;
|
|
33
|
-
}>;
|
|
34
|
+
export declare function projectToolManifest(tools: AgentTool[], originOf?: (tool: AgentTool) => ToolOrigin): ToolManifestRow[];
|
|
34
35
|
export declare function auditToolCollisions(tools: AgentTool[]): {
|
|
35
36
|
yields: Array<{
|
|
36
37
|
name: string;
|
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
+
const PRESENTATION_KEYWORDS = new Set(["description", "title", "examples", "$id"]);
|
|
3
|
+
const SCHEMA_NAME_MAPS = new Set([
|
|
4
|
+
"properties",
|
|
5
|
+
"patternProperties",
|
|
6
|
+
"definitions",
|
|
7
|
+
"$defs",
|
|
8
|
+
"dependentSchemas",
|
|
9
|
+
]);
|
|
2
10
|
function stripPresentation(node) {
|
|
3
11
|
if (Array.isArray(node))
|
|
4
12
|
return node.map(stripPresentation);
|
|
@@ -6,8 +14,15 @@ function stripPresentation(node) {
|
|
|
6
14
|
return node;
|
|
7
15
|
const out = {};
|
|
8
16
|
for (const [k, v] of Object.entries(node)) {
|
|
9
|
-
if (k
|
|
17
|
+
if (PRESENTATION_KEYWORDS.has(k))
|
|
10
18
|
continue;
|
|
19
|
+
if (SCHEMA_NAME_MAPS.has(k) && v !== null && typeof v === "object" && !Array.isArray(v)) {
|
|
20
|
+
const named = {};
|
|
21
|
+
for (const [name, sub] of Object.entries(v))
|
|
22
|
+
named[name] = stripPresentation(sub);
|
|
23
|
+
out[k] = named;
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
11
26
|
out[k] = k === "required" && Array.isArray(v) ? [...v].sort() : stripPresentation(v);
|
|
12
27
|
}
|
|
13
28
|
return out;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ToolFingerprintInput } from "../core/cache-break-detector.js";
|
|
1
2
|
export interface RawPrefixInputs {
|
|
2
3
|
provider: string;
|
|
3
4
|
modelId: string;
|
|
@@ -9,11 +10,7 @@ export interface RawPrefixInputs {
|
|
|
9
10
|
cacheControlBoundary: boolean;
|
|
10
11
|
}>;
|
|
11
12
|
stableSystemText: string;
|
|
12
|
-
toolWire: ReadonlyArray<
|
|
13
|
-
name: string;
|
|
14
|
-
description: string;
|
|
15
|
-
parameters: unknown;
|
|
16
|
-
}>;
|
|
13
|
+
toolWire: ReadonlyArray<ToolFingerprintInput>;
|
|
17
14
|
thinkingLevel: string;
|
|
18
15
|
maxTokens: number | undefined;
|
|
19
16
|
hasOutputSchema: boolean;
|
|
@@ -61,7 +61,10 @@ export function buildTurnPromptSnapshot(raw, api, promptHasher) {
|
|
|
61
61
|
loweringVersion: `${lowering.version}/${lowering.form}/${api}`,
|
|
62
62
|
};
|
|
63
63
|
const bump = () => {
|
|
64
|
-
|
|
64
|
+
const next = identityOf(elements);
|
|
65
|
+
if (next === snapshot.cacheIdentity)
|
|
66
|
+
return;
|
|
67
|
+
snapshot.cacheIdentity = next;
|
|
65
68
|
snapshot.onChange?.(snapshot);
|
|
66
69
|
};
|
|
67
70
|
const snapshot = {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type BackgroundAgentRecord, type BackgroundAgentRowSummary, type BackgroundAgentStore } from "../../core/background-agent-store.js";
|
|
1
|
+
import { type BackgroundAgentReapOptions, type BackgroundAgentRecord, type BackgroundAgentRowSummary, type BackgroundAgentStore, type BackgroundAgentUpdateIfExpect } from "../../core/background-agent-store.js";
|
|
2
2
|
export interface FileBackgroundAgentStoreOptions {
|
|
3
3
|
fsync?: boolean;
|
|
4
4
|
compactEvery?: number;
|
|
@@ -18,12 +18,7 @@ export declare class FileBackgroundAgentStore implements BackgroundAgentStore {
|
|
|
18
18
|
update(handle: string, scope: string, record: BackgroundAgentRecord, expect: {
|
|
19
19
|
rev: number;
|
|
20
20
|
}): Promise<boolean>;
|
|
21
|
-
updateIf(handle: string, scope: string, record: BackgroundAgentRecord, expect:
|
|
22
|
-
rev: number;
|
|
23
|
-
status?: BackgroundAgentRecord["status"];
|
|
24
|
-
parkedCheckpointToken?: string | null;
|
|
25
|
-
parkClaimId?: string | null;
|
|
26
|
-
}): Promise<boolean>;
|
|
21
|
+
updateIf(handle: string, scope: string, record: BackgroundAgentRecord, expect: BackgroundAgentUpdateIfExpect): Promise<boolean>;
|
|
27
22
|
listBySession(scope: string, sessionId: string, opts?: {
|
|
28
23
|
status?: BackgroundAgentRecord["status"];
|
|
29
24
|
limit?: number;
|
|
@@ -36,11 +31,7 @@ export declare class FileBackgroundAgentStore implements BackgroundAgentStore {
|
|
|
36
31
|
delete(handle: string, scope: string, expect?: {
|
|
37
32
|
rev: number;
|
|
38
33
|
}): Promise<boolean>;
|
|
39
|
-
reap(scope: string, now: number, opts?:
|
|
40
|
-
maxAgeMs?: number;
|
|
41
|
-
keep?: number;
|
|
42
|
-
staleRunningMaxAgeMs?: number;
|
|
43
|
-
}): Promise<number>;
|
|
34
|
+
reap(scope: string, now: number, opts?: BackgroundAgentReapOptions): Promise<number>;
|
|
44
35
|
compactNow(): void;
|
|
45
36
|
get size(): number;
|
|
46
37
|
close(): void;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
|
-
import { BackgroundAgentStoreError, STALE_RUNNING_REAP_ATTRIBUTION, } from "../../core/background-agent-store.js";
|
|
2
|
+
import { BackgroundAgentStoreError, STALE_RUNNING_REAP_ATTRIBUTION, queryBackgroundAgents, } from "../../core/background-agent-store.js";
|
|
3
3
|
import { SharedLedgerTable } from "./shared-ledger.js";
|
|
4
4
|
const agentLedgers = new SharedLedgerTable({
|
|
5
5
|
keyOf: (r) => FileBackgroundAgentStore.key(r.handle, r.scope),
|
|
@@ -92,31 +92,10 @@ export class FileBackgroundAgentStore {
|
|
|
92
92
|
});
|
|
93
93
|
}
|
|
94
94
|
async listBySession(scope, sessionId, opts) {
|
|
95
|
-
|
|
96
|
-
for (const r of this.rows.values()) {
|
|
97
|
-
if (r.scope !== scope)
|
|
98
|
-
continue;
|
|
99
|
-
const anchor = r.sessionScoped ? r.owner : r.parentSessionId;
|
|
100
|
-
if (anchor !== sessionId && r.rootSessionId !== sessionId)
|
|
101
|
-
continue;
|
|
102
|
-
if (opts?.status !== undefined && r.status !== opts.status)
|
|
103
|
-
continue;
|
|
104
|
-
out.push(toSummary(r));
|
|
105
|
-
}
|
|
106
|
-
out.sort((a, b) => b.spawnedAt - a.spawnedAt);
|
|
107
|
-
return opts?.limit !== undefined ? out.slice(0, Math.max(0, opts.limit)) : out;
|
|
95
|
+
return queryBackgroundAgents(this.rows.values(), { scope, sessionId, status: opts?.status, limit: opts?.limit });
|
|
108
96
|
}
|
|
109
97
|
async listByScope(scope, opts) {
|
|
110
|
-
|
|
111
|
-
for (const r of this.rows.values()) {
|
|
112
|
-
if (r.scope !== scope)
|
|
113
|
-
continue;
|
|
114
|
-
if (opts?.status !== undefined && r.status !== opts.status)
|
|
115
|
-
continue;
|
|
116
|
-
out.push(toSummary(r));
|
|
117
|
-
}
|
|
118
|
-
out.sort((a, b) => b.spawnedAt - a.spawnedAt);
|
|
119
|
-
return opts?.limit !== undefined ? out.slice(0, Math.max(0, opts.limit)) : out;
|
|
98
|
+
return queryBackgroundAgents(this.rows.values(), { scope, status: opts?.status, limit: opts?.limit });
|
|
120
99
|
}
|
|
121
100
|
async listScopes() {
|
|
122
101
|
return [...new Set([...this.rows.values()].map((r) => r.scope))].sort();
|
|
@@ -205,20 +184,3 @@ export class FileBackgroundAgentStore {
|
|
|
205
184
|
this.ledger.release();
|
|
206
185
|
}
|
|
207
186
|
}
|
|
208
|
-
function toSummary(r) {
|
|
209
|
-
return {
|
|
210
|
-
handle: r.handle,
|
|
211
|
-
owner: r.owner,
|
|
212
|
-
sessionScoped: r.sessionScoped,
|
|
213
|
-
...(r.sessionId !== undefined ? { sessionId: r.sessionId } : {}),
|
|
214
|
-
...(r.parentSessionId !== undefined ? { parentSessionId: r.parentSessionId } : {}),
|
|
215
|
-
...(r.rootSessionId !== undefined ? { rootSessionId: r.rootSessionId } : {}),
|
|
216
|
-
...(r.name !== undefined ? { name: r.name } : {}),
|
|
217
|
-
...(r.agentType !== undefined ? { agentType: r.agentType } : {}),
|
|
218
|
-
status: r.status,
|
|
219
|
-
spawnedAt: r.spawnedAt,
|
|
220
|
-
updatedAt: r.updatedAt,
|
|
221
|
-
...(r.settledAt !== undefined ? { settledAt: r.settledAt } : {}),
|
|
222
|
-
...(r.usage !== undefined ? { usage: structuredClone(r.usage) } : {}),
|
|
223
|
-
};
|
|
224
|
-
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
|
-
import { CheckpointError, summarizeCheckpoint, validatePendingSteer, winnerFromOutcome, } from "../../core/checkpoint-store.js";
|
|
2
|
+
import { CheckpointError, checkpointOccMatches, checkpointRowMatches, summarizeCheckpoint, validatePendingSteer, winnerFromOutcome, } from "../../core/checkpoint-store.js";
|
|
3
3
|
import { SharedLedgerTable } from "./shared-ledger.js";
|
|
4
4
|
function applyCheckpointEvent(cps, ev) {
|
|
5
5
|
if (ev.t === "put") {
|
|
@@ -80,10 +80,10 @@ export class FileCheckpointStore {
|
|
|
80
80
|
throw new Error("injected fault: resolve before commit");
|
|
81
81
|
}
|
|
82
82
|
const cp = this.cps.get(token);
|
|
83
|
-
if (!cp
|
|
83
|
+
if (!checkpointRowMatches(cp, scope, "pending")) {
|
|
84
84
|
return false;
|
|
85
85
|
}
|
|
86
|
-
if (
|
|
86
|
+
if (!checkpointOccMatches(cp, expect)) {
|
|
87
87
|
return false;
|
|
88
88
|
}
|
|
89
89
|
const winner = winnerFromOutcome(outcome);
|
|
@@ -99,7 +99,7 @@ export class FileCheckpointStore {
|
|
|
99
99
|
async reopen(token, scope, reason) {
|
|
100
100
|
return this.withLock(token, () => {
|
|
101
101
|
const cp = this.cps.get(token);
|
|
102
|
-
if (!cp
|
|
102
|
+
if (!checkpointRowMatches(cp, scope, "resolved")) {
|
|
103
103
|
return false;
|
|
104
104
|
}
|
|
105
105
|
const rev = (cp.rev ?? 0) + 1;
|
|
@@ -111,7 +111,7 @@ export class FileCheckpointStore {
|
|
|
111
111
|
const clean = validatePendingSteer(steer);
|
|
112
112
|
return this.withLock(token, () => {
|
|
113
113
|
const cp = this.cps.get(token);
|
|
114
|
-
if (!cp
|
|
114
|
+
if (!checkpointRowMatches(cp, scope, "pending")) {
|
|
115
115
|
return false;
|
|
116
116
|
}
|
|
117
117
|
this.commit({ t: "steer", token, steer: clean }, this.fsyncEnabled);
|
|
@@ -121,7 +121,7 @@ export class FileCheckpointStore {
|
|
|
121
121
|
async expire(token, scope) {
|
|
122
122
|
return this.withLock(token, () => {
|
|
123
123
|
const cp = this.cps.get(token);
|
|
124
|
-
if (!cp
|
|
124
|
+
if (!checkpointRowMatches(cp, scope, "pending")) {
|
|
125
125
|
return false;
|
|
126
126
|
}
|
|
127
127
|
this.commit({ t: "expire", token }, true);
|
|
@@ -10,7 +10,10 @@ export declare class AppendLog {
|
|
|
10
10
|
private fd;
|
|
11
11
|
private disposed;
|
|
12
12
|
private readonly path;
|
|
13
|
+
private boundary;
|
|
13
14
|
constructor(path: string);
|
|
15
|
+
private sizeOrUnknown;
|
|
16
|
+
private repairTornTail;
|
|
14
17
|
append(record: unknown, fsync: boolean): void;
|
|
15
18
|
close(): void;
|
|
16
19
|
closeForSwap(): void;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { closeSync, constants as FS, existsSync, linkSync, mkdirSync, openSync, readFileSync, realpathSync, renameSync, truncateSync, unlinkSync, writeFileSync, writeSync, fsyncSync, } from "node:fs";
|
|
1
|
+
import { closeSync, constants as FS, existsSync, fstatSync, linkSync, mkdirSync, openSync, readFileSync, readSync, realpathSync, renameSync, truncateSync, unlinkSync, writeFileSync, writeSync, fsyncSync, } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { basename, dirname, join, resolve as resolvePath } from "node:path";
|
|
4
4
|
import { createHash, randomBytes } from "node:crypto";
|
|
@@ -148,30 +148,75 @@ export class AppendLog {
|
|
|
148
148
|
fd;
|
|
149
149
|
disposed = false;
|
|
150
150
|
path;
|
|
151
|
+
boundary = -1;
|
|
151
152
|
constructor(path) {
|
|
152
153
|
this.path = path;
|
|
153
154
|
ensureDir(dirnameOf(path));
|
|
154
155
|
truncateTornTail(path);
|
|
155
|
-
this.fd = openSync(path, "a", 0o600);
|
|
156
|
+
this.fd = openSync(path, "a+", 0o600);
|
|
157
|
+
this.boundary = this.sizeOrUnknown();
|
|
158
|
+
}
|
|
159
|
+
sizeOrUnknown() {
|
|
160
|
+
try {
|
|
161
|
+
return fstatSync(this.fd).size;
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
return -1;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
repairTornTail() {
|
|
168
|
+
const size = this.sizeOrUnknown();
|
|
169
|
+
if (size < 0) {
|
|
170
|
+
this.boundary = -1;
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
if (size === this.boundary)
|
|
174
|
+
return;
|
|
175
|
+
if (size === 0) {
|
|
176
|
+
this.boundary = 0;
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
const tail = Buffer.allocUnsafe(1);
|
|
180
|
+
try {
|
|
181
|
+
if (readSync(this.fd, tail, 0, 1, size - 1) === 1 && tail[0] === 0x0a) {
|
|
182
|
+
this.boundary = size;
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
truncateTornTail(this.path);
|
|
190
|
+
this.boundary = this.sizeOrUnknown();
|
|
156
191
|
}
|
|
157
192
|
append(record, fsync) {
|
|
158
193
|
if (this.fd < 0 && !this.disposed) {
|
|
159
194
|
truncateTornTail(this.path);
|
|
160
|
-
this.fd = openSync(this.path, "a", 0o600);
|
|
195
|
+
this.fd = openSync(this.path, "a+", 0o600);
|
|
196
|
+
this.boundary = this.sizeOrUnknown();
|
|
161
197
|
}
|
|
162
198
|
if (this.fd < 0) {
|
|
163
199
|
const e = new Error("append: this log was closed (its session/box was deleted or replaced) — refusing to write through a released descriptor");
|
|
164
200
|
e.code = "log_closed";
|
|
165
201
|
throw e;
|
|
166
202
|
}
|
|
203
|
+
this.repairTornTail();
|
|
167
204
|
const buf = Buffer.from(`${JSON.stringify(record)}\n`, "utf8");
|
|
168
205
|
let written = 0;
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
206
|
+
try {
|
|
207
|
+
while (written < buf.length) {
|
|
208
|
+
const n = writeSync(this.fd, buf, written, buf.length - written);
|
|
209
|
+
if (n <= 0)
|
|
210
|
+
throw new Error(`append: write made no progress (${written}/${buf.length} bytes) — refusing to leave a torn record`);
|
|
211
|
+
written += n;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
catch (err) {
|
|
215
|
+
this.boundary = -1;
|
|
216
|
+
throw err;
|
|
174
217
|
}
|
|
218
|
+
if (this.boundary >= 0)
|
|
219
|
+
this.boundary += buf.length;
|
|
175
220
|
if (fsync)
|
|
176
221
|
fsyncSync(this.fd);
|
|
177
222
|
}
|
|
@@ -38,6 +38,7 @@ export declare class FileStorageBackend {
|
|
|
38
38
|
private readonly fileCheckpoints;
|
|
39
39
|
private readonly fileMemory;
|
|
40
40
|
private readonly ttl;
|
|
41
|
+
private readonly fileSessions;
|
|
41
42
|
private readonly fileWorkflowJournal;
|
|
42
43
|
constructor(opts?: FileStorageBackendOptions);
|
|
43
44
|
dispose(): Promise<void>;
|