@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
|
@@ -32,6 +32,7 @@ export class FileStorageBackend {
|
|
|
32
32
|
fileCheckpoints;
|
|
33
33
|
fileMemory;
|
|
34
34
|
ttl;
|
|
35
|
+
fileSessions;
|
|
35
36
|
fileWorkflowJournal;
|
|
36
37
|
constructor(opts = {}) {
|
|
37
38
|
this.root = resolveDataRoot(opts.root);
|
|
@@ -39,6 +40,7 @@ export class FileStorageBackend {
|
|
|
39
40
|
this.lock.acquire();
|
|
40
41
|
try {
|
|
41
42
|
const repo = new FileSessionRepo(this.root);
|
|
43
|
+
this.fileSessions = repo;
|
|
42
44
|
this.ttl = new TtlSessionStore({ repo, evict: opts.evict ?? "forget" });
|
|
43
45
|
this.sessionStore = this.ttl;
|
|
44
46
|
this.fileCheckpoints = new FileCheckpointStore(this.root, opts.checkpoint);
|
|
@@ -63,6 +65,11 @@ export class FileStorageBackend {
|
|
|
63
65
|
}
|
|
64
66
|
catch {
|
|
65
67
|
}
|
|
68
|
+
try {
|
|
69
|
+
await this.fileSessions.dispose();
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
}
|
|
66
73
|
this.fileCheckpoints.close();
|
|
67
74
|
try {
|
|
68
75
|
await this.fileMemory.flushEmbeds();
|
|
@@ -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,22 +1,20 @@
|
|
|
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;
|
|
5
|
+
private readonly joined;
|
|
5
6
|
constructor(root: string);
|
|
6
7
|
private pathFor;
|
|
7
8
|
private read;
|
|
8
9
|
private storage;
|
|
10
|
+
dispose(): Promise<void>;
|
|
9
11
|
create(options?: {
|
|
10
12
|
id?: string;
|
|
11
13
|
}): Promise<Session>;
|
|
12
14
|
open(metadata: SessionMetadata): Promise<Session>;
|
|
13
15
|
list(): Promise<SessionMetadata[]>;
|
|
14
16
|
delete(metadata: SessionMetadata): Promise<void>;
|
|
15
|
-
fork(sourceMetadata: SessionMetadata, options?:
|
|
16
|
-
entryId?: string;
|
|
17
|
-
position?: "before" | "at";
|
|
18
|
-
id?: string;
|
|
19
|
-
}): Promise<Session>;
|
|
17
|
+
fork(sourceMetadata: SessionMetadata, options?: SessionForkOptions): Promise<Session>;
|
|
20
18
|
exportEntries(sessionId: string): Promise<SessionTreeEntry[]>;
|
|
21
19
|
importEntries(sessionId: string, owner: string | undefined, entries: SessionTreeEntry[]): Promise<void>;
|
|
22
20
|
}
|
|
@@ -20,11 +20,19 @@ function evictSharedSessionStorage(canonical) {
|
|
|
20
20
|
class FileSessionStorage extends BaseSessionStorage {
|
|
21
21
|
log;
|
|
22
22
|
canonicalPath;
|
|
23
|
+
holders = new Set();
|
|
23
24
|
constructor(log, meta, entries, canonicalPath) {
|
|
24
25
|
super(meta, entries);
|
|
25
26
|
this.log = log;
|
|
26
27
|
this.canonicalPath = canonicalPath;
|
|
27
28
|
}
|
|
29
|
+
addHolder(holder) {
|
|
30
|
+
this.holders.add(holder);
|
|
31
|
+
}
|
|
32
|
+
removeHolder(holder) {
|
|
33
|
+
this.holders.delete(holder);
|
|
34
|
+
return this.holders.size === 0;
|
|
35
|
+
}
|
|
28
36
|
closeLog() {
|
|
29
37
|
this.log.close();
|
|
30
38
|
}
|
|
@@ -46,6 +54,7 @@ class FileSessionStorage extends BaseSessionStorage {
|
|
|
46
54
|
export class FileSessionRepo {
|
|
47
55
|
dir;
|
|
48
56
|
tmpDir;
|
|
57
|
+
joined = new Map();
|
|
49
58
|
constructor(root) {
|
|
50
59
|
this.dir = join(root, "sessions");
|
|
51
60
|
this.tmpDir = join(root, "tmp");
|
|
@@ -77,19 +86,37 @@ export class FileSessionRepo {
|
|
|
77
86
|
storage(id, createdAt, entries, forkedFrom) {
|
|
78
87
|
const canonical = canonicalStoreKey(this.pathFor(id));
|
|
79
88
|
const live = sharedSessionStorages.get(canonical)?.deref();
|
|
80
|
-
if (live !== undefined)
|
|
89
|
+
if (live !== undefined) {
|
|
90
|
+
live.addHolder(this);
|
|
91
|
+
this.joined.set(canonical, new WeakRef(live));
|
|
81
92
|
return live;
|
|
93
|
+
}
|
|
82
94
|
const log = new AppendLog(this.pathFor(id));
|
|
83
95
|
const created = new FileSessionStorage(log, { id, createdAt, ...(forkedFrom !== undefined ? { forkedFrom } : {}) }, entries, canonical);
|
|
84
96
|
sharedSessionStorages.set(canonical, new WeakRef(created));
|
|
97
|
+
created.addHolder(this);
|
|
98
|
+
this.joined.set(canonical, new WeakRef(created));
|
|
85
99
|
sessionStorageFinalizer.register(created, { canonical, log }, created);
|
|
86
100
|
return created;
|
|
87
101
|
}
|
|
102
|
+
async dispose() {
|
|
103
|
+
for (const [canonical, ref] of this.joined) {
|
|
104
|
+
const mine = ref.deref();
|
|
105
|
+
if (mine === undefined)
|
|
106
|
+
continue;
|
|
107
|
+
if (sharedSessionStorages.get(canonical)?.deref() !== mine)
|
|
108
|
+
continue;
|
|
109
|
+
if (mine.removeHolder(this))
|
|
110
|
+
evictSharedSessionStorage(canonical);
|
|
111
|
+
}
|
|
112
|
+
this.joined.clear();
|
|
113
|
+
}
|
|
88
114
|
async create(options = {}) {
|
|
89
115
|
const id = options.id ?? uuidv7();
|
|
90
116
|
const createdAt = new Date().toISOString();
|
|
91
117
|
const path = this.pathFor(id);
|
|
92
118
|
if (!existsSync(path)) {
|
|
119
|
+
evictSharedSessionStorage(canonicalStoreKey(path));
|
|
93
120
|
atomicWriteFile(this.tmpDir, path, `${JSON.stringify({ kind: "meta", id, createdAt })}\n`);
|
|
94
121
|
return new StoredSession(this.storage(id, createdAt, []));
|
|
95
122
|
}
|
|
@@ -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)
|
|
@@ -14,6 +14,8 @@ export interface CompoundReadonlyVerdict {
|
|
|
14
14
|
reason?: string;
|
|
15
15
|
outOfRootRead?: true;
|
|
16
16
|
outOfRootPaths?: readonly string[];
|
|
17
|
+
checkedPaths?: readonly string[];
|
|
18
|
+
undecidedPaths?: readonly string[];
|
|
17
19
|
}
|
|
18
20
|
export declare function formatOutOfRootReadApprovalOption(directory: string): string;
|
|
19
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;
|
|
@@ -179,6 +188,11 @@ function collectSegmentBoundaryFindings(tokens, boundary) {
|
|
|
179
188
|
const args = tokens.folded.slice(1);
|
|
180
189
|
const findings = [];
|
|
181
190
|
const candidates = [];
|
|
191
|
+
const bareWordOperands = [];
|
|
192
|
+
const argGlobs = (k) => {
|
|
193
|
+
const raw = tokens.raw[k + 1];
|
|
194
|
+
return raw !== undefined && hasUnquotedGlobMetachar(raw);
|
|
195
|
+
};
|
|
182
196
|
for (const rawArg of tokens.raw.slice(1)) {
|
|
183
197
|
if (!hasUnquotedExpansionMetachar(rawArg))
|
|
184
198
|
continue;
|
|
@@ -190,14 +204,18 @@ function collectSegmentBoundaryFindings(tokens, boundary) {
|
|
|
190
204
|
];
|
|
191
205
|
}
|
|
192
206
|
if (name === "cd") {
|
|
193
|
-
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
|
+
}
|
|
194
212
|
if (target === undefined) {
|
|
195
213
|
return [{ kind: "unresolvable", reason: '`cd` with no argument targets the home directory, which cannot be checked against the allowed directories — not auto-allowed' }];
|
|
196
214
|
}
|
|
197
215
|
if (target === "-") {
|
|
198
216
|
return [{ kind: "unresolvable", reason: '`cd -` targets the previous working directory, which cannot be resolved statically — not auto-allowed' }];
|
|
199
217
|
}
|
|
200
|
-
candidates.push(target);
|
|
218
|
+
candidates.push({ text: target, globbed: false });
|
|
201
219
|
}
|
|
202
220
|
else {
|
|
203
221
|
const patternSuppliedByFlag = name === "grep" && args.some(isGrepPatternFlagToken);
|
|
@@ -214,8 +232,8 @@ function collectSegmentBoundaryFindings(tokens, boundary) {
|
|
|
214
232
|
continue;
|
|
215
233
|
if (t.startsWith("-") && t !== "-") {
|
|
216
234
|
for (const payload of attachedOptionPayloads(t)) {
|
|
217
|
-
if (
|
|
218
|
-
candidates.push(payload);
|
|
235
|
+
if (isAbsolutePathForm(payload) || isPathShapedToken(payload))
|
|
236
|
+
candidates.push({ text: payload, globbed: argGlobs(k) });
|
|
219
237
|
}
|
|
220
238
|
continue;
|
|
221
239
|
}
|
|
@@ -226,10 +244,18 @@ function collectSegmentBoundaryFindings(tokens, boundary) {
|
|
|
226
244
|
if (isGrepPatternSlot)
|
|
227
245
|
continue;
|
|
228
246
|
if (isPathShapedToken(t))
|
|
229
|
-
candidates.push(t);
|
|
247
|
+
candidates.push({ text: t, globbed: argGlobs(k) });
|
|
248
|
+
else
|
|
249
|
+
bareWordOperands.push({ text: t, globbed: argGlobs(k) });
|
|
230
250
|
}
|
|
231
251
|
}
|
|
232
|
-
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) {
|
|
233
259
|
const resolved = resolveOperandLexically(boundary.cwd ?? boundary.roots[0], candidate, boundary.homeDir);
|
|
234
260
|
if (resolved === undefined) {
|
|
235
261
|
findings.push({
|
|
@@ -238,9 +264,20 @@ function collectSegmentBoundaryFindings(tokens, boundary) {
|
|
|
238
264
|
});
|
|
239
265
|
continue;
|
|
240
266
|
}
|
|
241
|
-
if (
|
|
267
|
+
if (withinAnyRoot(boundary.roots, resolved)) {
|
|
268
|
+
if (!globbed)
|
|
269
|
+
findings.push({ kind: "inside", path: resolved });
|
|
270
|
+
}
|
|
271
|
+
else
|
|
242
272
|
findings.push({ kind: "outside", command: name, path: resolved });
|
|
243
273
|
}
|
|
274
|
+
for (const { text: operand, globbed } of bareWordOperands) {
|
|
275
|
+
const resolved = resolveOperandLexically(boundary.cwd ?? boundary.roots[0], operand, boundary.homeDir);
|
|
276
|
+
if (resolved === undefined)
|
|
277
|
+
continue;
|
|
278
|
+
if (!globbed && withinAnyRoot(boundary.roots, resolved))
|
|
279
|
+
findings.push({ kind: "inside", path: resolved });
|
|
280
|
+
}
|
|
244
281
|
return findings;
|
|
245
282
|
}
|
|
246
283
|
export function classifyCompoundReadonlyDetailed(command, allow, boundary) {
|
|
@@ -383,22 +420,37 @@ export function classifyCompoundReadonlyDetailed(command, allow, boundary) {
|
|
|
383
420
|
}
|
|
384
421
|
function evaluateReadBoundary(foldedSegments, boundary) {
|
|
385
422
|
const outside = [];
|
|
423
|
+
const inside = [];
|
|
424
|
+
const undecided = [];
|
|
386
425
|
for (const toks of foldedSegments) {
|
|
387
426
|
for (const finding of collectSegmentBoundaryFindings(toks, boundary)) {
|
|
388
427
|
if (finding.kind === "unresolvable")
|
|
389
428
|
return { reason: finding.reason };
|
|
429
|
+
if (finding.kind === "inside") {
|
|
430
|
+
if (!inside.includes(finding.path))
|
|
431
|
+
inside.push(finding.path);
|
|
432
|
+
continue;
|
|
433
|
+
}
|
|
434
|
+
if (finding.kind === "undecided") {
|
|
435
|
+
if (!undecided.includes(finding.path))
|
|
436
|
+
undecided.push(finding.path);
|
|
437
|
+
continue;
|
|
438
|
+
}
|
|
390
439
|
if (!outside.some((o) => o.path === finding.path))
|
|
391
440
|
outside.push(finding);
|
|
392
441
|
}
|
|
393
442
|
}
|
|
443
|
+
const undecidedField = undecided.length > 0 ? { undecidedPaths: undecided } : {};
|
|
394
444
|
if (outside.length === 0)
|
|
395
|
-
return {};
|
|
445
|
+
return inside.length > 0 ? { checkedPaths: inside, ...undecidedField } : { ...undecidedField };
|
|
396
446
|
const paths = outside.map((o) => `"${o.path}"`).join(", ");
|
|
397
447
|
const allowed = boundary.roots.length > 0 ? boundary.roots.join(", ") : "(none)";
|
|
398
448
|
return {
|
|
399
449
|
reason: `"${outside[0].command}" reads ${paths}, outside the allowed directories for this session: ${allowed} — not auto-allowed`,
|
|
400
450
|
outOfRootRead: true,
|
|
401
451
|
outOfRootPaths: outside.map((o) => o.path),
|
|
452
|
+
...(inside.length > 0 ? { checkedPaths: inside } : {}),
|
|
453
|
+
...undecidedField,
|
|
402
454
|
};
|
|
403
455
|
}
|
|
404
456
|
export function classifySimpleCommandReadBoundary(command, boundary) {
|
|
@@ -14,7 +14,7 @@ export interface ExecClampOption {
|
|
|
14
14
|
}) => () => void;
|
|
15
15
|
}
|
|
16
16
|
export declare function bashExitOneInterpretation(command: string): string | undefined;
|
|
17
|
-
export declare function canAutoBackground(command: string): boolean;
|
|
17
|
+
export declare function canAutoBackground(command: string, depth?: number): boolean;
|
|
18
18
|
export declare function createBashTool(env: ExecutionEnv, rootCanonical: string, coAuthor?: string | false, cwdRef?: CwdRef, taskOpts?: {
|
|
19
19
|
taskRegistry?: TaskRegistry;
|
|
20
20
|
taskOwner?: string;
|