@sema-agent/core 3.0.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 +2 -1
- package/dist/agents/cascade.js +31 -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.js +27 -19
- package/dist/agents/retain-ledger.d.ts +2 -0
- package/dist/agents/retain-ledger.js +6 -5
- package/dist/agents/roster-store.js +3 -5
- package/dist/agents/subagent.js +66 -64
- package/dist/agents/teacher.d.ts +1 -9
- package/dist/agents/teacher.js +21 -24
- package/dist/agents/verify.js +15 -14
- package/dist/brain/anthropic.js +24 -21
- package/dist/brain/openai.js +24 -21
- 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 +2 -1
- package/dist/core/auto-compaction.js +16 -25
- 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 +6 -13
- package/dist/core/checkpoint-store.js +11 -5
- package/dist/core/file-snapshot-store.d.ts +8 -0
- package/dist/core/file-snapshot-store.js +4 -3
- package/dist/core/hooks.d.ts +3 -1
- package/dist/core/hooks.js +8 -18
- package/dist/core/lsp.js +4 -3
- 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/engine.d.ts +0 -1
- package/dist/core/memory-engine/engine.js +24 -34
- package/dist/core/memory-engine/file-backend.d.ts +1 -2
- package/dist/core/memory-engine/file-backend.js +25 -36
- package/dist/core/memory-engine/layout.d.ts +8 -0
- package/dist/core/memory-engine/layout.js +60 -4
- package/dist/core/memory-engine/scope-contract.js +9 -3
- package/dist/core/memory-engine/types.d.ts +1 -1
- 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 +2 -7
- package/dist/core/runner/assemble-result.js +3 -4
- 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 +20 -53
- package/dist/core/runner/runtask.d.ts +1 -0
- package/dist/core/runner/runtask.js +65 -156
- 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/safe-notify.d.ts +17 -0
- package/dist/core/safe-notify.js +59 -0
- package/dist/core/sensitive-path-policy.js +2 -1
- package/dist/core/store-contracts/checkpoint-store-contract.js +1 -1
- 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 +8 -13
- 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 +10 -5
- 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 +2 -9
- package/dist/core/types.d.ts +15 -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/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/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/orchestration/run-spec.js +7 -4
- package/dist/orchestration/run-workflow-tool.js +3 -35
- package/dist/orchestration/workflow.d.ts +3 -11
- package/dist/orchestration/workflow.js +164 -204
- package/dist/prompt-assembly/tool-catalog.d.ts +10 -9
- package/dist/prompt-assembly/turn-snapshot.d.ts +2 -5
- 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/mailbox-store.d.ts +2 -6
- package/dist/stores/file/session-policy-store.js +15 -1
- package/dist/stores/file/session-store.d.ts +2 -6
- 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 +1 -0
- package/dist/tools/fs/bash-readonly-classifier.js +60 -25
- package/dist/tools/fs/fs-bash.js +324 -96
- 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/monitor.d.ts +1 -0
- package/dist/tools/monitor.js +7 -5
- package/dist/tools/worktree.d.ts +2 -7
- package/dist/tools/worktree.js +3 -1
- package/package.json +6 -2
|
@@ -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,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;
|
|
@@ -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);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type MailboxLease, type MailboxStore } from "../../core/mailbox-store.js";
|
|
1
|
+
import { type MailboxAppendMessage, type MailboxLease, type MailboxStore } from "../../core/mailbox-store.js";
|
|
2
2
|
export interface FileMailboxStoreOptions {
|
|
3
3
|
fsync?: boolean;
|
|
4
4
|
compactEvery?: number;
|
|
@@ -15,11 +15,7 @@ export declare class FileMailboxStore implements MailboxStore {
|
|
|
15
15
|
private load;
|
|
16
16
|
private commit;
|
|
17
17
|
private compact;
|
|
18
|
-
append(scope: string, handle: string, msg:
|
|
19
|
-
from?: string;
|
|
20
|
-
content: string;
|
|
21
|
-
sentAt: number;
|
|
22
|
-
}): Promise<number>;
|
|
18
|
+
append(scope: string, handle: string, msg: MailboxAppendMessage): Promise<number>;
|
|
23
19
|
claimLease(scope: string, handle: string, owner: string, ttlMs: number, now?: number): Promise<MailboxLease | null>;
|
|
24
20
|
ack(scope: string, handle: string, owner: string, upToSeq: number): Promise<void>;
|
|
25
21
|
releaseLease(scope: string, handle: string, owner: string): Promise<void>;
|
|
@@ -105,11 +105,25 @@ export class FileSessionPolicyStore {
|
|
|
105
105
|
for (const file of files) {
|
|
106
106
|
if (!file.endsWith(".json"))
|
|
107
107
|
continue;
|
|
108
|
+
let raw;
|
|
109
|
+
try {
|
|
110
|
+
raw = readFileSync(join(this.dir, file), "utf8");
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
108
115
|
let parsed;
|
|
109
116
|
try {
|
|
110
|
-
parsed = JSON.parse(
|
|
117
|
+
parsed = JSON.parse(raw);
|
|
111
118
|
}
|
|
112
119
|
catch {
|
|
120
|
+
try {
|
|
121
|
+
unlinkSync(join(this.dir, file));
|
|
122
|
+
}
|
|
123
|
+
catch (err) {
|
|
124
|
+
if (err.code !== "ENOENT")
|
|
125
|
+
throw err;
|
|
126
|
+
}
|
|
113
127
|
continue;
|
|
114
128
|
}
|
|
115
129
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Session, SessionMetadata, SessionRepo, SessionTreeEntry } from "../../internal/harness.js";
|
|
1
|
+
import type { Session, SessionForkOptions, SessionMetadata, SessionRepo, SessionTreeEntry } from "../../internal/harness.js";
|
|
2
2
|
export declare class FileSessionRepo implements SessionRepo {
|
|
3
3
|
private readonly dir;
|
|
4
4
|
private readonly tmpDir;
|
|
@@ -14,11 +14,7 @@ export declare class FileSessionRepo implements SessionRepo {
|
|
|
14
14
|
open(metadata: SessionMetadata): Promise<Session>;
|
|
15
15
|
list(): Promise<SessionMetadata[]>;
|
|
16
16
|
delete(metadata: SessionMetadata): Promise<void>;
|
|
17
|
-
fork(sourceMetadata: SessionMetadata, options?:
|
|
18
|
-
entryId?: string;
|
|
19
|
-
position?: "before" | "at";
|
|
20
|
-
id?: string;
|
|
21
|
-
}): Promise<Session>;
|
|
17
|
+
fork(sourceMetadata: SessionMetadata, options?: SessionForkOptions): Promise<Session>;
|
|
22
18
|
exportEntries(sessionId: string): Promise<SessionTreeEntry[]>;
|
|
23
19
|
importEntries(sessionId: string, owner: string | undefined, entries: SessionTreeEntry[]): Promise<void>;
|
|
24
20
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type WorkflowJournalEntry, type WorkflowJournalStore } from "../../core/workflow-journal-store.js";
|
|
1
|
+
import { type WorkflowJournalEntry, type WorkflowJournalStore, type ResumeClaimArgs } from "../../core/workflow-journal-store.js";
|
|
2
2
|
export { MAX_JOURNAL_RESULT_BYTES, oversizeJournalResult } from "../../core/workflow-journal-store.js";
|
|
3
3
|
export declare const RESUME_CLAIM_TTL_MS: number;
|
|
4
4
|
export declare const RESUME_CLAIM_RELEASE_GRACE_MS = 5000;
|
|
@@ -24,19 +24,11 @@ export declare class FileWorkflowJournalStore implements WorkflowJournalStore {
|
|
|
24
24
|
private readClaimRaw;
|
|
25
25
|
private unlinkIfUnchanged;
|
|
26
26
|
private sweepExpiredClaims;
|
|
27
|
-
resumeClaim(input: {
|
|
28
|
-
sourceRunId: string;
|
|
29
|
-
newRunId: string;
|
|
30
|
-
scope: string;
|
|
31
|
-
}): Promise<{
|
|
27
|
+
resumeClaim(input: ResumeClaimArgs): Promise<{
|
|
32
28
|
granted: boolean;
|
|
33
29
|
holder?: string;
|
|
34
30
|
}>;
|
|
35
|
-
releaseResumeClaim(input:
|
|
36
|
-
sourceRunId: string;
|
|
37
|
-
newRunId: string;
|
|
38
|
-
scope: string;
|
|
39
|
-
}): Promise<void>;
|
|
31
|
+
releaseResumeClaim(input: ResumeClaimArgs): Promise<void>;
|
|
40
32
|
deleteByRun(runId: string): Promise<number>;
|
|
41
33
|
dispose(): void;
|
|
42
34
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type WorkflowRunStore, type WorkflowRunSummary } from "../../core/workflow-run-store.js";
|
|
2
|
-
import type { WorkflowRun
|
|
1
|
+
import { type WorkflowRunListByScopeOptions, type WorkflowRunStore, type WorkflowRunSummary } from "../../core/workflow-run-store.js";
|
|
2
|
+
import type { WorkflowRun } from "../../orchestration/workflow.js";
|
|
3
3
|
export interface FileWorkflowRunStoreOptions {
|
|
4
4
|
fsync?: boolean;
|
|
5
5
|
compactEvery?: number;
|
|
@@ -18,11 +18,7 @@ export declare class FileWorkflowRunStore implements WorkflowRunStore {
|
|
|
18
18
|
update(id: string, scope: string, run: WorkflowRun, expect?: {
|
|
19
19
|
rev: number;
|
|
20
20
|
}): Promise<boolean>;
|
|
21
|
-
listByScope(scope: string, opts?:
|
|
22
|
-
status?: WorkflowRunStatus;
|
|
23
|
-
limit?: number;
|
|
24
|
-
session?: string;
|
|
25
|
-
}): Promise<WorkflowRunSummary[]>;
|
|
21
|
+
listByScope(scope: string, opts?: WorkflowRunListByScopeOptions): Promise<WorkflowRunSummary[]>;
|
|
26
22
|
reap(scope: string, now: number, opts?: {
|
|
27
23
|
maxAgeMs?: number;
|
|
28
24
|
keep?: number;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
|
-
import { WorkflowRunStoreError, isTerminalWorkflowStatus,
|
|
2
|
+
import { WorkflowRunStoreError, isTerminalWorkflowStatus, nextWorkflowRunOnUpdate, queryWorkflowRuns, } from "../../core/workflow-run-store.js";
|
|
3
3
|
import { SharedLedgerTable } from "./shared-ledger.js";
|
|
4
4
|
const runLedgers = new SharedLedgerTable({
|
|
5
5
|
keyOf: (run) => run.id,
|
|
@@ -53,28 +53,15 @@ export class FileWorkflowRunStore {
|
|
|
53
53
|
}
|
|
54
54
|
async update(id, scope, run, expect) {
|
|
55
55
|
return this.withLock(id, () => {
|
|
56
|
-
const
|
|
57
|
-
if (!
|
|
56
|
+
const next = nextWorkflowRunOnUpdate(this.runs.get(id), id, scope, run, expect);
|
|
57
|
+
if (!next)
|
|
58
58
|
return false;
|
|
59
|
-
}
|
|
60
|
-
if (expect !== undefined && (cur.rev ?? 0) !== expect.rev) {
|
|
61
|
-
return false;
|
|
62
|
-
}
|
|
63
|
-
const next = structuredClone(run);
|
|
64
|
-
next.id = id;
|
|
65
|
-
next.scope = scope;
|
|
66
|
-
next.rev = (cur.rev ?? 0) + 1;
|
|
67
59
|
this.commit({ t: "update", id, run: next }, this.fsyncEnabled);
|
|
68
60
|
return true;
|
|
69
61
|
});
|
|
70
62
|
}
|
|
71
63
|
async listByScope(scope, opts) {
|
|
72
|
-
|
|
73
|
-
(opts?.status === undefined || r.status === opts.status) &&
|
|
74
|
-
(opts?.session === undefined || r.originatingSessionId === opts.session));
|
|
75
|
-
rows.sort((a, b) => b.createdAt - a.createdAt);
|
|
76
|
-
const limited = opts?.limit !== undefined ? rows.slice(0, Math.max(0, opts.limit)) : rows;
|
|
77
|
-
return limited.map(summarizeWorkflowRun);
|
|
64
|
+
return queryWorkflowRuns(this.runs.values(), scope, opts);
|
|
78
65
|
}
|
|
79
66
|
async reap(scope, now, opts) {
|
|
80
67
|
if (opts?.maxAgeMs === undefined && opts?.keep === undefined)
|
|
@@ -15,6 +15,7 @@ export interface CompoundReadonlyVerdict {
|
|
|
15
15
|
outOfRootRead?: true;
|
|
16
16
|
outOfRootPaths?: readonly string[];
|
|
17
17
|
checkedPaths?: readonly string[];
|
|
18
|
+
undecidedPaths?: readonly string[];
|
|
18
19
|
}
|
|
19
20
|
export declare function formatOutOfRootReadApprovalOption(directory: string): string;
|
|
20
21
|
export declare function classifyCompoundReadonlyDetailed(command: string, allow: ReadonlySet<string>, boundary?: BashReadonlyRootBoundary): CompoundReadonlyVerdict;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isBlockedDevicePath, normalizeAbsPathLexically, withinAnyRoot } from "./safety.js";
|
|
1
|
+
import { isAbsolutePathForm, isBlockedDevicePath, normalizeAbsPathLexically, withinAnyRoot } from "./safety.js";
|
|
2
2
|
export const BASH_READONLY_DEFAULT_ALLOW = [
|
|
3
3
|
"ls", "cat", "head", "tail", "wc", "pwd", "echo", "whoami", "uname",
|
|
4
4
|
"grep", "cut", "tr", "basename", "dirname", "stat", "du", "df", "which",
|
|
@@ -56,31 +56,24 @@ export function formatOutOfRootReadApprovalOption(directory) {
|
|
|
56
56
|
return `Yes, allow reading from ${leaf || directory}/ from this project`;
|
|
57
57
|
}
|
|
58
58
|
const NO_PATH_OPERAND_COMMANDS = new Set(["pwd", "echo", "whoami", "uname", "which", "tr", "basename", "dirname"]);
|
|
59
|
-
function isAbsolutePathToken(p) {
|
|
60
|
-
return p.startsWith("/") || /^[A-Za-z]:[\\/]/.test(p);
|
|
61
|
-
}
|
|
62
59
|
function isPathShapedToken(tok) {
|
|
63
60
|
return tok.includes("/") || tok.startsWith("~") || tok === "." || tok === "..";
|
|
64
61
|
}
|
|
65
|
-
function normalizeAbsPathLexicalEitherFamily(p) {
|
|
66
|
-
const s = p.replace(/\\/g, "/");
|
|
67
|
-
return /^[A-Za-z]:\//.test(s) ? s.slice(0, 2) + normalizeAbsPathLexically(s.slice(2)) : normalizeAbsPathLexically(s);
|
|
68
|
-
}
|
|
69
62
|
function resolveOperandLexically(base, operand, homeDir) {
|
|
70
63
|
let raw = operand;
|
|
71
64
|
if (raw === "~" || raw.startsWith("~/")) {
|
|
72
|
-
if (homeDir === undefined || !
|
|
65
|
+
if (homeDir === undefined || !isAbsolutePathForm(homeDir))
|
|
73
66
|
return undefined;
|
|
74
67
|
raw = raw === "~" ? homeDir : `${homeDir.replace(/[/\\]+$/, "")}/${raw.slice(2)}`;
|
|
75
68
|
}
|
|
76
69
|
else if (raw.startsWith("~")) {
|
|
77
70
|
return undefined;
|
|
78
71
|
}
|
|
79
|
-
if (
|
|
80
|
-
return
|
|
81
|
-
if (base === undefined || !
|
|
72
|
+
if (isAbsolutePathForm(raw))
|
|
73
|
+
return normalizeAbsPathLexically(raw);
|
|
74
|
+
if (base === undefined || !isAbsolutePathForm(base))
|
|
82
75
|
return undefined;
|
|
83
|
-
return
|
|
76
|
+
return normalizeAbsPathLexically(`${base.replace(/[/\\]+$/, "")}/${raw}`);
|
|
84
77
|
}
|
|
85
78
|
function tokenizeSegment(segment) {
|
|
86
79
|
const raw = segment
|
|
@@ -107,6 +100,22 @@ function hasUnquotedExpansionMetachar(rawToken) {
|
|
|
107
100
|
}
|
|
108
101
|
return false;
|
|
109
102
|
}
|
|
103
|
+
function hasUnquotedGlobMetachar(rawToken) {
|
|
104
|
+
let open;
|
|
105
|
+
for (const ch of rawToken) {
|
|
106
|
+
if (open === undefined && (ch === '"' || ch === "'")) {
|
|
107
|
+
open = ch;
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
if (open === ch) {
|
|
111
|
+
open = undefined;
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (open === undefined && (ch === "*" || ch === "?" || ch === "["))
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
110
119
|
function longOptionNameOf(tok) {
|
|
111
120
|
if (!tok.startsWith("--") || tok.length === 2)
|
|
112
121
|
return undefined;
|
|
@@ -180,6 +189,10 @@ function collectSegmentBoundaryFindings(tokens, boundary) {
|
|
|
180
189
|
const findings = [];
|
|
181
190
|
const candidates = [];
|
|
182
191
|
const bareWordOperands = [];
|
|
192
|
+
const argGlobs = (k) => {
|
|
193
|
+
const raw = tokens.raw[k + 1];
|
|
194
|
+
return raw !== undefined && hasUnquotedGlobMetachar(raw);
|
|
195
|
+
};
|
|
183
196
|
for (const rawArg of tokens.raw.slice(1)) {
|
|
184
197
|
if (!hasUnquotedExpansionMetachar(rawArg))
|
|
185
198
|
continue;
|
|
@@ -191,14 +204,18 @@ function collectSegmentBoundaryFindings(tokens, boundary) {
|
|
|
191
204
|
];
|
|
192
205
|
}
|
|
193
206
|
if (name === "cd") {
|
|
194
|
-
const
|
|
207
|
+
const targetIdx = args.findIndex((t) => !t.startsWith("-") || t === "-");
|
|
208
|
+
const target = targetIdx === -1 ? undefined : args[targetIdx];
|
|
209
|
+
if (target !== undefined && argGlobs(targetIdx)) {
|
|
210
|
+
return [{ kind: "unresolvable", reason: `\`cd\` is given the pattern "${target}", which the shell expands to a directory this check cannot know — not auto-allowed` }];
|
|
211
|
+
}
|
|
195
212
|
if (target === undefined) {
|
|
196
213
|
return [{ kind: "unresolvable", reason: '`cd` with no argument targets the home directory, which cannot be checked against the allowed directories — not auto-allowed' }];
|
|
197
214
|
}
|
|
198
215
|
if (target === "-") {
|
|
199
216
|
return [{ kind: "unresolvable", reason: '`cd -` targets the previous working directory, which cannot be resolved statically — not auto-allowed' }];
|
|
200
217
|
}
|
|
201
|
-
candidates.push(target);
|
|
218
|
+
candidates.push({ text: target, globbed: false });
|
|
202
219
|
}
|
|
203
220
|
else {
|
|
204
221
|
const patternSuppliedByFlag = name === "grep" && args.some(isGrepPatternFlagToken);
|
|
@@ -215,8 +232,8 @@ function collectSegmentBoundaryFindings(tokens, boundary) {
|
|
|
215
232
|
continue;
|
|
216
233
|
if (t.startsWith("-") && t !== "-") {
|
|
217
234
|
for (const payload of attachedOptionPayloads(t)) {
|
|
218
|
-
if (
|
|
219
|
-
candidates.push(payload);
|
|
235
|
+
if (isAbsolutePathForm(payload) || isPathShapedToken(payload))
|
|
236
|
+
candidates.push({ text: payload, globbed: argGlobs(k) });
|
|
220
237
|
}
|
|
221
238
|
continue;
|
|
222
239
|
}
|
|
@@ -227,12 +244,18 @@ function collectSegmentBoundaryFindings(tokens, boundary) {
|
|
|
227
244
|
if (isGrepPatternSlot)
|
|
228
245
|
continue;
|
|
229
246
|
if (isPathShapedToken(t))
|
|
230
|
-
candidates.push(t);
|
|
247
|
+
candidates.push({ text: t, globbed: argGlobs(k) });
|
|
231
248
|
else
|
|
232
|
-
bareWordOperands.push(t);
|
|
249
|
+
bareWordOperands.push({ text: t, globbed: argGlobs(k) });
|
|
233
250
|
}
|
|
234
251
|
}
|
|
235
|
-
for (
|
|
252
|
+
for (let k = 0; k < args.length; k++) {
|
|
253
|
+
if (!argGlobs(k))
|
|
254
|
+
continue;
|
|
255
|
+
const resolved = resolveOperandLexically(boundary.cwd ?? boundary.roots[0], args[k], boundary.homeDir);
|
|
256
|
+
findings.push({ kind: "undecided", path: resolved ?? args[k] });
|
|
257
|
+
}
|
|
258
|
+
for (const { text: candidate, globbed } of candidates) {
|
|
236
259
|
const resolved = resolveOperandLexically(boundary.cwd ?? boundary.roots[0], candidate, boundary.homeDir);
|
|
237
260
|
if (resolved === undefined) {
|
|
238
261
|
findings.push({
|
|
@@ -241,14 +264,18 @@ function collectSegmentBoundaryFindings(tokens, boundary) {
|
|
|
241
264
|
});
|
|
242
265
|
continue;
|
|
243
266
|
}
|
|
244
|
-
if (withinAnyRoot(boundary.roots, resolved))
|
|
245
|
-
|
|
267
|
+
if (withinAnyRoot(boundary.roots, resolved)) {
|
|
268
|
+
if (!globbed)
|
|
269
|
+
findings.push({ kind: "inside", path: resolved });
|
|
270
|
+
}
|
|
246
271
|
else
|
|
247
272
|
findings.push({ kind: "outside", command: name, path: resolved });
|
|
248
273
|
}
|
|
249
|
-
for (const operand of bareWordOperands) {
|
|
274
|
+
for (const { text: operand, globbed } of bareWordOperands) {
|
|
250
275
|
const resolved = resolveOperandLexically(boundary.cwd ?? boundary.roots[0], operand, boundary.homeDir);
|
|
251
|
-
if (resolved
|
|
276
|
+
if (resolved === undefined)
|
|
277
|
+
continue;
|
|
278
|
+
if (!globbed && withinAnyRoot(boundary.roots, resolved))
|
|
252
279
|
findings.push({ kind: "inside", path: resolved });
|
|
253
280
|
}
|
|
254
281
|
return findings;
|
|
@@ -394,6 +421,7 @@ export function classifyCompoundReadonlyDetailed(command, allow, boundary) {
|
|
|
394
421
|
function evaluateReadBoundary(foldedSegments, boundary) {
|
|
395
422
|
const outside = [];
|
|
396
423
|
const inside = [];
|
|
424
|
+
const undecided = [];
|
|
397
425
|
for (const toks of foldedSegments) {
|
|
398
426
|
for (const finding of collectSegmentBoundaryFindings(toks, boundary)) {
|
|
399
427
|
if (finding.kind === "unresolvable")
|
|
@@ -403,12 +431,18 @@ function evaluateReadBoundary(foldedSegments, boundary) {
|
|
|
403
431
|
inside.push(finding.path);
|
|
404
432
|
continue;
|
|
405
433
|
}
|
|
434
|
+
if (finding.kind === "undecided") {
|
|
435
|
+
if (!undecided.includes(finding.path))
|
|
436
|
+
undecided.push(finding.path);
|
|
437
|
+
continue;
|
|
438
|
+
}
|
|
406
439
|
if (!outside.some((o) => o.path === finding.path))
|
|
407
440
|
outside.push(finding);
|
|
408
441
|
}
|
|
409
442
|
}
|
|
443
|
+
const undecidedField = undecided.length > 0 ? { undecidedPaths: undecided } : {};
|
|
410
444
|
if (outside.length === 0)
|
|
411
|
-
return inside.length > 0 ? { checkedPaths: inside } : {};
|
|
445
|
+
return inside.length > 0 ? { checkedPaths: inside, ...undecidedField } : { ...undecidedField };
|
|
412
446
|
const paths = outside.map((o) => `"${o.path}"`).join(", ");
|
|
413
447
|
const allowed = boundary.roots.length > 0 ? boundary.roots.join(", ") : "(none)";
|
|
414
448
|
return {
|
|
@@ -416,6 +450,7 @@ function evaluateReadBoundary(foldedSegments, boundary) {
|
|
|
416
450
|
outOfRootRead: true,
|
|
417
451
|
outOfRootPaths: outside.map((o) => o.path),
|
|
418
452
|
...(inside.length > 0 ? { checkedPaths: inside } : {}),
|
|
453
|
+
...undecidedField,
|
|
419
454
|
};
|
|
420
455
|
}
|
|
421
456
|
export function classifySimpleCommandReadBoundary(command, boundary) {
|