@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
|
@@ -16,7 +16,17 @@ export class StdioLspTransport {
|
|
|
16
16
|
for (const frame of this.decode(chunk))
|
|
17
17
|
this.onMessage(frame);
|
|
18
18
|
});
|
|
19
|
-
child.stdin.on("error", () =>
|
|
19
|
+
child.stdin.on("error", (e) => this.failAll(toError(e)));
|
|
20
|
+
const stdout = child.stdout;
|
|
21
|
+
if (typeof stdout.removeListener === "function" && typeof stdout.once === "function") {
|
|
22
|
+
for (const event of ["end", "close", "error"]) {
|
|
23
|
+
stdout.on(event, (arg) => {
|
|
24
|
+
if (Buffer.isBuffer(arg))
|
|
25
|
+
return;
|
|
26
|
+
this.failAll(new Error(`LSP server stdout ${event}`));
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
20
30
|
child.on("exit", () => this.failAll(new Error("LSP server exited")));
|
|
21
31
|
child.on("error", (e) => this.failAll(toError(e)));
|
|
22
32
|
}
|
|
@@ -36,7 +46,8 @@ export class StdioLspTransport {
|
|
|
36
46
|
try {
|
|
37
47
|
this.send({ jsonrpc: "2.0", id: msg.id, error: { code: -32601, message: `Method not found: ${String(msg.method)}` } });
|
|
38
48
|
}
|
|
39
|
-
catch {
|
|
49
|
+
catch (e) {
|
|
50
|
+
this.failAll(toError(e));
|
|
40
51
|
}
|
|
41
52
|
}
|
|
42
53
|
return;
|
|
@@ -121,10 +132,7 @@ export class StdioLspTransport {
|
|
|
121
132
|
this.send({ jsonrpc: "2.0", id, method, params });
|
|
122
133
|
}
|
|
123
134
|
catch (e) {
|
|
124
|
-
|
|
125
|
-
cleanup();
|
|
126
|
-
reject(brandLspFailure(e instanceof Error ? e : new Error(String(e)), "server_terminated"));
|
|
127
|
-
}
|
|
135
|
+
this.failAll(brandLspFailure(e instanceof Error ? e : new Error(String(e)), "server_terminated"));
|
|
128
136
|
}
|
|
129
137
|
});
|
|
130
138
|
}
|
|
@@ -134,7 +142,8 @@ export class StdioLspTransport {
|
|
|
134
142
|
try {
|
|
135
143
|
this.send({ jsonrpc: "2.0", method, params });
|
|
136
144
|
}
|
|
137
|
-
catch {
|
|
145
|
+
catch (e) {
|
|
146
|
+
this.failAll(toError(e));
|
|
138
147
|
}
|
|
139
148
|
}
|
|
140
149
|
async close() {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Session, type SessionMetadata, type SessionRepo, type SessionTreeEntry } from "../harness/types.js";
|
|
1
|
+
import { type Session, type SessionForkOptions, type SessionMetadata, type SessionRepo, type SessionTreeEntry } from "../harness/types.js";
|
|
2
2
|
export declare class InMemorySessionRepo implements SessionRepo<SessionMetadata, {
|
|
3
3
|
id?: string;
|
|
4
4
|
}> {
|
|
@@ -9,11 +9,7 @@ export declare class InMemorySessionRepo implements SessionRepo<SessionMetadata,
|
|
|
9
9
|
open(metadata: SessionMetadata): Promise<Session>;
|
|
10
10
|
list(): Promise<SessionMetadata[]>;
|
|
11
11
|
delete(metadata: SessionMetadata): Promise<void>;
|
|
12
|
-
fork(sourceMetadata: SessionMetadata, options:
|
|
13
|
-
entryId?: string;
|
|
14
|
-
position?: "before" | "at";
|
|
15
|
-
id?: string;
|
|
16
|
-
}): Promise<Session>;
|
|
12
|
+
fork(sourceMetadata: SessionMetadata, options: SessionForkOptions): Promise<Session>;
|
|
17
13
|
exportEntries(sessionId: string): Promise<SessionTreeEntry[]>;
|
|
18
14
|
importEntries(sessionId: string, owner: string | undefined, entries: SessionTreeEntry[]): Promise<void>;
|
|
19
15
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { ImageContent, TextContent } from "../llm/index.js";
|
|
2
2
|
import type { AgentMessage } from "../loop/types.js";
|
|
3
|
-
import type { Session, SessionContext, SessionMetadata, SessionStorage, SessionTreeEntry } from "../harness/types.js";
|
|
3
|
+
import type { Session, SessionContext, SessionMetadata, SessionStorage, SessionTreeEntry, WorkspaceState } from "../harness/types.js";
|
|
4
4
|
import type { BuildContextOptions } from "../harness/types.js";
|
|
5
|
+
import type { PromptEpochDescriptor } from "../../prompt-assembly/epoch.js";
|
|
5
6
|
export declare function buildSessionContext(pathEntries: SessionTreeEntry[], opts?: BuildContextOptions): SessionContext;
|
|
6
7
|
export declare class StoredSession<TMetadata extends SessionMetadata = SessionMetadata> implements Session<TMetadata> {
|
|
7
8
|
private storage;
|
|
@@ -19,40 +20,14 @@ export declare class StoredSession<TMetadata extends SessionMetadata = SessionMe
|
|
|
19
20
|
appendMessage(message: AgentMessage): Promise<string>;
|
|
20
21
|
appendThinkingLevelChange(thinkingLevel: string): Promise<string>;
|
|
21
22
|
appendModelChange(provider: string, modelId: string): Promise<string>;
|
|
22
|
-
appendPromptEpoch(descriptor:
|
|
23
|
-
epoch: number;
|
|
24
|
-
artifactDigest: string;
|
|
25
|
-
packId: string;
|
|
26
|
-
assemblyApi: number;
|
|
27
|
-
activatedBy: "session_start" | "compaction" | "legacy_migration";
|
|
28
|
-
centerArtifactDigest?: string;
|
|
29
|
-
sourceRevision?: string;
|
|
30
|
-
}): Promise<string>;
|
|
23
|
+
appendPromptEpoch(descriptor: PromptEpochDescriptor): Promise<string>;
|
|
31
24
|
appendAnnouncedListing(snapshot: {
|
|
32
25
|
agents?: readonly string[];
|
|
33
26
|
skills?: readonly string[];
|
|
34
27
|
models?: readonly string[];
|
|
35
28
|
}): Promise<string>;
|
|
36
|
-
appendWorkspaceState(state:
|
|
37
|
-
|
|
38
|
-
handsCwd?: string;
|
|
39
|
-
activeWorktree?: {
|
|
40
|
-
worktreeDir: string;
|
|
41
|
-
originalCwd: string;
|
|
42
|
-
baseSha: string;
|
|
43
|
-
entered?: boolean;
|
|
44
|
-
};
|
|
45
|
-
}): Promise<string>;
|
|
46
|
-
getWorkspaceState(): Promise<{
|
|
47
|
-
taskRoot: string;
|
|
48
|
-
handsCwd?: string;
|
|
49
|
-
activeWorktree?: {
|
|
50
|
-
worktreeDir: string;
|
|
51
|
-
originalCwd: string;
|
|
52
|
-
baseSha: string;
|
|
53
|
-
entered?: boolean;
|
|
54
|
-
};
|
|
55
|
-
} | undefined>;
|
|
29
|
+
appendWorkspaceState(state: WorkspaceState): Promise<string>;
|
|
30
|
+
getWorkspaceState(): Promise<WorkspaceState | undefined>;
|
|
56
31
|
getAnnouncedListing(): Promise<{
|
|
57
32
|
agents?: string[];
|
|
58
33
|
skills?: string[];
|
package/dist/index.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ export { assembleCodeTools, type CodeToolsConfig, CODE_ROLE } from "./scenarios/
|
|
|
14
14
|
export { capAggregateToolResults, AGGREGATE_TOOL_RESULT_BUDGET_CHARS, DEFAULT_BUDGET_EXEMPT_TOOLS, type AggregateBudgetOptions, } from "./core/tool-result-budget.js";
|
|
15
15
|
export { capAggregateMediaBytes, AGGREGATE_MEDIA_BUDGET_BYTES, type MediaStripInfo } from "./core/media-byte-cap.js";
|
|
16
16
|
export { createAskUserQuestionTool, createDurableQuestionPolicy, QUESTION_AWAITS_RESUME, type OnQuestion, type AskQuestion, type AskQuestionOption, type AskQuestionRequest, type QuestionAnswer, type QuestionAnswerItem, } from "./core/ask-question.js";
|
|
17
|
-
export { createLspTool, gitCheckIgnoreFilter, LSP_OPERATIONS, LSP_FAILURE_BRAND, brandLspFailure, lspFailureOf, type LspNoneReason, type LspOperation, type LspServerManager, type LspSession, type LspResult, type LspLocation, type LspSymbolInfo, type LspRequestParams, type LspToolOptions, type LspTransport, type LspReadText, } from "./core/lsp.js";
|
|
17
|
+
export { createLspTool, gitCheckIgnoreFilter, LSP_OPERATIONS, LSP_FAILURE_BRAND, brandLspFailure, lspFailureOf, SharedAbortScope, settleOnAbort, type LspNoneReason, type LspOperation, type LspServerManager, type LspSession, type LspResult, type LspLocation, type LspSymbolInfo, type LspRequestParams, type LspToolOptions, type LspTransport, type LspReadText, } from "./core/lsp.js";
|
|
18
18
|
export { buildRequest, parseResult, callHierarchyMethod, pathToUri } from "./core/lsp-protocol.js";
|
|
19
19
|
export { TransportLspSession, type SessionWarmup } from "./core/lsp-session.js";
|
|
20
20
|
export { LspDiagnosticsRegistry, formatDiagnosticsBlock, formatDiagnosticsSummary, type LspDiagnostic, type LspFileDiagnostics, } from "./core/lsp-diagnostics.js";
|
|
@@ -27,6 +27,7 @@ export { BrainError, classifyHttp, type BrainErrorCode } from "./brain/errors.js
|
|
|
27
27
|
export { looksDegenerate, inspectDegenerate, trimDegenerateTail } from "./brain/repetition.js";
|
|
28
28
|
export type { RepetitionEvent, RepetitionInspection } from "./brain/repetition.js";
|
|
29
29
|
export { computeCostMicroUsd, modelCostToPricing, type ModelPricing, type TokenCounts, } from "./core/pricing.js";
|
|
30
|
+
export { cacheFamilyOf, promptTokensOf, uncachedInputTokensOf, type CacheFamily } from "./core/runner/usage-accounting.js";
|
|
30
31
|
export { emitTrace, type TraceEvent, type TracerHook } from "./core/trace.js";
|
|
31
32
|
export { InMemoryStrategyStore, type StrategyStore, type StoredStrategy } from "./core/strategy-store.js";
|
|
32
33
|
export { createSqlTool, validateReadOnlySql, type SqlToolOptions } from "./tools/sql.js";
|
|
@@ -56,6 +57,7 @@ export { getShellConfig, isWslBashLauncher } from "./engine/execution-env/node-e
|
|
|
56
57
|
export { isSecretEnvKey, scrubSecretEnv } from "./core/secret-env.js";
|
|
57
58
|
export { MAX_EXEC_OUTPUT_BYTES, RollingTailBuffer, markTruncated } from "./core/exec-output-tail.js";
|
|
58
59
|
export type { ExecutionEnv, FileInfo, Result, FileErrorCode, ExecutionErrorCode } from "./internal/harness.js";
|
|
60
|
+
export type { ExecResult } from "./internal/harness.js";
|
|
59
61
|
export { RemoteExecutionError, hasDestroy, isRemoteExecutionEnv, isSuspendable, isIsolated, missingRestoreSurface, isRetryableRemoteErrorCode, RETRYABLE_REMOTE_ERROR_CODES, } from "./core/remote-env.js";
|
|
60
62
|
export { withRetry } from "./core/with-retry.js";
|
|
61
63
|
export type { RetryPolicy, RetryResult } from "./core/with-retry.js";
|
package/dist/index.js
CHANGED
|
@@ -12,7 +12,7 @@ export { assembleCodeTools, CODE_ROLE } from "./scenarios/full-body.js";
|
|
|
12
12
|
export { capAggregateToolResults, AGGREGATE_TOOL_RESULT_BUDGET_CHARS, DEFAULT_BUDGET_EXEMPT_TOOLS, } from "./core/tool-result-budget.js";
|
|
13
13
|
export { capAggregateMediaBytes, AGGREGATE_MEDIA_BUDGET_BYTES } from "./core/media-byte-cap.js";
|
|
14
14
|
export { createAskUserQuestionTool, createDurableQuestionPolicy, QUESTION_AWAITS_RESUME, } from "./core/ask-question.js";
|
|
15
|
-
export { createLspTool, gitCheckIgnoreFilter, LSP_OPERATIONS, LSP_FAILURE_BRAND, brandLspFailure, lspFailureOf, } from "./core/lsp.js";
|
|
15
|
+
export { createLspTool, gitCheckIgnoreFilter, LSP_OPERATIONS, LSP_FAILURE_BRAND, brandLspFailure, lspFailureOf, SharedAbortScope, settleOnAbort, } from "./core/lsp.js";
|
|
16
16
|
export { buildRequest, parseResult, callHierarchyMethod, pathToUri } from "./core/lsp-protocol.js";
|
|
17
17
|
export { TransportLspSession } from "./core/lsp-session.js";
|
|
18
18
|
export { LspDiagnosticsRegistry, formatDiagnosticsBlock, formatDiagnosticsSummary, } from "./core/lsp-diagnostics.js";
|
|
@@ -24,6 +24,7 @@ export { selectModel, selectModelOrThrow } from "./core/select-model.js";
|
|
|
24
24
|
export { BrainError, classifyHttp } from "./brain/errors.js";
|
|
25
25
|
export { looksDegenerate, inspectDegenerate, trimDegenerateTail } from "./brain/repetition.js";
|
|
26
26
|
export { computeCostMicroUsd, modelCostToPricing, } from "./core/pricing.js";
|
|
27
|
+
export { cacheFamilyOf, promptTokensOf, uncachedInputTokensOf } from "./core/runner/usage-accounting.js";
|
|
27
28
|
export { emitTrace } from "./core/trace.js";
|
|
28
29
|
export { InMemoryStrategyStore } from "./core/strategy-store.js";
|
|
29
30
|
export { createSqlTool, validateReadOnlySql } from "./tools/sql.js";
|
|
@@ -3,5 +3,7 @@ export type { InvokedSkillRetention } from "../engine/compaction/utils.js";
|
|
|
3
3
|
export type { AgentCoreRuntimeDeps } from "../engine/loop/runtime-deps.js";
|
|
4
4
|
export type { AgentMessage, AgentTool, AgentToolResult, AgentToolUpdateCallback, ThinkingLevel, ToolExecutionMode, } from "../engine/loop/types.js";
|
|
5
5
|
export type { AgentHarnessEvent, CompactionSettings, ExecutionEnv, ExecutionErrorCode, FileErrorCode, FileInfo, Result, Session, SessionMetadata, SessionRepo, SessionStorage, SessionTreeEntry, Skill, } from "../engine/harness/types.js";
|
|
6
|
-
export type { ExecutionEnvExecOptions } from "../engine/harness/types.js";
|
|
6
|
+
export type { ExecutionEnvExecOptions, ExecResult } from "../engine/harness/types.js";
|
|
7
7
|
export type { SessionWriteOptions, CompactionEntry } from "../engine/harness/types.js";
|
|
8
|
+
export type { ActiveWorktreeSession, WorkspaceState } from "../engine/harness/types.js";
|
|
9
|
+
export type { SessionForkOptions } from "../engine/harness/types.js";
|
|
@@ -2,7 +2,7 @@ export * from "./harness-types.js";
|
|
|
2
2
|
export { AgentHarness } from "../engine/harness/agent-harness.js";
|
|
3
3
|
export { CompactionError, ExecutionError, FileError, ok, err } from "../engine/harness/types.js";
|
|
4
4
|
export { DEFAULT_CHARS_PER_TOKEN, DEFAULT_CLAMP_TOLERANCE, DEFAULT_COMPACTION_SETTINGS, compact, computeFileLists, dryRunSummarizationClamp, estimateContextTokens, estimateTokens, prepareCompaction, shouldCompact, summaryOutputBudgetTokens, } from "../engine/compaction/compaction.js";
|
|
5
|
-
export { SKILL_RETENTION_PER_SKILL_MAX_CHARS, SKILL_RETENTION_TOTAL_MAX_CHARS, SKILL_RETENTION_TRUNCATION_MARKER, formatPersistedOutputRefs, readCompactionActiveTools, readRetainedInvokedSkills, renderInvokedSkillsRetention, } from "../engine/compaction/utils.js";
|
|
5
|
+
export { SKILL_RETENTION_PER_SKILL_MAX_CHARS, SKILL_RETENTION_TOTAL_MAX_CHARS, SKILL_RETENTION_TRUNCATION_MARKER, formatFileOperations, formatPersistedOutputRefs, readCompactionActiveTools, readRetainedInvokedSkills, renderInvokedSkillsRetention, } from "../engine/compaction/utils.js";
|
|
6
6
|
export { NodeExecutionEnv } from "../engine/execution-env/node-execution-env.js";
|
|
7
7
|
export { StoredSession, buildSessionContext } from "../engine/session/session.js";
|
|
8
8
|
export { getEntriesToFork } from "../engine/session/repo-utils.js";
|
package/dist/internal/harness.js
CHANGED
|
@@ -2,7 +2,7 @@ export * from "./harness-types.js";
|
|
|
2
2
|
export { AgentHarness } from "../engine/harness/agent-harness.js";
|
|
3
3
|
export { CompactionError, ExecutionError, FileError, ok, err } from "../engine/harness/types.js";
|
|
4
4
|
export { DEFAULT_CHARS_PER_TOKEN, DEFAULT_CLAMP_TOLERANCE, DEFAULT_COMPACTION_SETTINGS, compact, computeFileLists, dryRunSummarizationClamp, estimateContextTokens, estimateTokens, prepareCompaction, shouldCompact, summaryOutputBudgetTokens, } from "../engine/compaction/compaction.js";
|
|
5
|
-
export { SKILL_RETENTION_PER_SKILL_MAX_CHARS, SKILL_RETENTION_TOTAL_MAX_CHARS, SKILL_RETENTION_TRUNCATION_MARKER, formatPersistedOutputRefs, readCompactionActiveTools, readRetainedInvokedSkills, renderInvokedSkillsRetention, } from "../engine/compaction/utils.js";
|
|
5
|
+
export { SKILL_RETENTION_PER_SKILL_MAX_CHARS, SKILL_RETENTION_TOTAL_MAX_CHARS, SKILL_RETENTION_TRUNCATION_MARKER, formatFileOperations, formatPersistedOutputRefs, readCompactionActiveTools, readRetainedInvokedSkills, renderInvokedSkillsRetention, } from "../engine/compaction/utils.js";
|
|
6
6
|
export { NodeExecutionEnv } from "../engine/execution-env/node-execution-env.js";
|
|
7
7
|
export { StoredSession, buildSessionContext } from "../engine/session/session.js";
|
|
8
8
|
export { getEntriesToFork } from "../engine/session/repo-utils.js";
|
|
@@ -4,8 +4,10 @@ import { isAbsolute, resolve, sep } from "node:path";
|
|
|
4
4
|
import { combinePolicies } from "../core/tool-policy.js";
|
|
5
5
|
import { delimitUntrusted } from "../core/untrusted-text.js";
|
|
6
6
|
import { isDurablePause } from "../agents/suspend-guard.js";
|
|
7
|
+
import { isAbsolutePathForm, writeTargetPath } from "../tools/fs/safety.js";
|
|
8
|
+
import { canonicalToolName } from "../core/tool-name-aliases.js";
|
|
7
9
|
import { captureBaseline, resolveFrozenPaths, restoreFrozenPaths, runOracle, snapshotFrozenPaths, specError, } from "../core/spec-contract.js";
|
|
8
|
-
const WRITE_TOOLS = new Set(["Write", "Edit", "MultiEdit", "NotebookEdit"]);
|
|
10
|
+
const WRITE_TOOLS = new Set(["Write", "Edit", "MultiEdit", "NotebookEdit"].map(canonicalToolName));
|
|
9
11
|
const CASE_INSENSITIVE_FS = process.platform === "darwin" || process.platform === "win32";
|
|
10
12
|
const pathKey = (p) => {
|
|
11
13
|
let n = p.replace(/\\/g, "/");
|
|
@@ -17,13 +19,14 @@ function frozenDenyPolicy(rootDir, frozenResolved) {
|
|
|
17
19
|
const frozen = new Set(frozenResolved.map(pathKey));
|
|
18
20
|
return {
|
|
19
21
|
check(req) {
|
|
20
|
-
|
|
22
|
+
const toolName = canonicalToolName(req.toolName);
|
|
23
|
+
if (!WRITE_TOOLS.has(toolName))
|
|
21
24
|
return { action: "allow" };
|
|
22
25
|
const args = req.args;
|
|
23
|
-
const raw =
|
|
26
|
+
const raw = writeTargetPath(toolName, args);
|
|
24
27
|
if (typeof raw !== "string" || raw.length === 0)
|
|
25
28
|
return { action: "allow" };
|
|
26
|
-
const absForm =
|
|
29
|
+
const absForm = isAbsolutePathForm(raw);
|
|
27
30
|
const abs = pathKey(absForm ? raw : resolve(rootDir, raw));
|
|
28
31
|
if (frozen.has(abs)) {
|
|
29
32
|
return { action: "deny", reason: `${raw} is part of the frozen specification surface (read-only).` };
|
|
@@ -456,7 +456,7 @@ export async function createRunWorkflowTool(d) {
|
|
|
456
456
|
},
|
|
457
457
|
});
|
|
458
458
|
}
|
|
459
|
-
|
|
459
|
+
{
|
|
460
460
|
let notified = false;
|
|
461
461
|
const fire = (notification) => {
|
|
462
462
|
if (notified)
|
|
@@ -467,6 +467,8 @@ export async function createRunWorkflowTool(d) {
|
|
|
467
467
|
}
|
|
468
468
|
catch {
|
|
469
469
|
}
|
|
470
|
+
if (!d.notifier)
|
|
471
|
+
return;
|
|
470
472
|
void Promise.resolve()
|
|
471
473
|
.then(() => d.notifier.notify({
|
|
472
474
|
runId,
|
|
@@ -507,40 +509,6 @@ export async function createRunWorkflowTool(d) {
|
|
|
507
509
|
}))
|
|
508
510
|
.catch(() => { });
|
|
509
511
|
}
|
|
510
|
-
else {
|
|
511
|
-
let notified = false;
|
|
512
|
-
const fire = (notification) => {
|
|
513
|
-
if (notified)
|
|
514
|
-
return;
|
|
515
|
-
notified = true;
|
|
516
|
-
try {
|
|
517
|
-
d.taskNotification?.(notification);
|
|
518
|
-
}
|
|
519
|
-
catch {
|
|
520
|
-
}
|
|
521
|
-
};
|
|
522
|
-
handle.done
|
|
523
|
-
.then((res) => fire({
|
|
524
|
-
task_id: runId,
|
|
525
|
-
task_type: "workflow",
|
|
526
|
-
...(ctx.toolCallId !== undefined ? { toolUseId: ctx.toolCallId } : {}),
|
|
527
|
-
status: "completed",
|
|
528
|
-
summary: completionSummary(meta.name, res.run),
|
|
529
|
-
result: boundedRedactedSummary(res.result, 4000),
|
|
530
|
-
usage: workflowUsageBlock(res.run),
|
|
531
|
-
diagnostics: workflowDiagnostics(runId, d.journalStore?.locator?.(runId, res.run.scope), res.run.resume?.journalEntries === 0),
|
|
532
|
-
completionId: res.run.completionId,
|
|
533
|
-
}), (err) => fire({
|
|
534
|
-
task_id: runId,
|
|
535
|
-
task_type: "workflow",
|
|
536
|
-
...(ctx.toolCallId !== undefined ? { toolUseId: ctx.toolCallId } : {}),
|
|
537
|
-
status: "failed",
|
|
538
|
-
summary: failureSummary(err, runId),
|
|
539
|
-
diagnostics: workflowDiagnostics(runId, undefined),
|
|
540
|
-
...(completionIdFromRejection(err) !== undefined ? { completionId: completionIdFromRejection(err) } : {}),
|
|
541
|
-
}))
|
|
542
|
-
.catch(() => { });
|
|
543
|
-
}
|
|
544
512
|
return {
|
|
545
513
|
details: { type: "workflow-run", status: "backgrounded", workflowRunId: runId },
|
|
546
514
|
content: JSON.stringify({
|
|
@@ -37,6 +37,32 @@ function minDefined(...vals) {
|
|
|
37
37
|
const xs = vals.filter((v) => typeof v === "number" && Number.isFinite(v));
|
|
38
38
|
return xs.length ? Math.min(...xs) : undefined;
|
|
39
39
|
}
|
|
40
|
+
function minPositiveDefined(...vals) {
|
|
41
|
+
const xs = vals.filter((v) => typeof v === "number" && Number.isFinite(v) && v > 0);
|
|
42
|
+
return xs.length ? Math.min(...xs) : undefined;
|
|
43
|
+
}
|
|
44
|
+
const NON_POSITIVE_LIMIT_MSG = (field, what) => `agent(spec): \`limits.${field}\` must be a POSITIVE number — a value of 0 or less DISABLES the ${what} ` +
|
|
45
|
+
`downstream (it is a sentinel, not a tighter ceiling), and a workflow script may only tighten a limit, never remove it`;
|
|
46
|
+
function pickImageInput(el, i) {
|
|
47
|
+
const at = `agent(spec): \`images[${i}]\``;
|
|
48
|
+
if (typeof el !== "object" || el === null || Array.isArray(el)) {
|
|
49
|
+
throw new WorkflowScriptError(`${at} must be an object — either { url } or { data, mimeType }`);
|
|
50
|
+
}
|
|
51
|
+
const o = el;
|
|
52
|
+
if ("url" in o) {
|
|
53
|
+
if (typeof o.url !== "string" || o.url.length === 0) {
|
|
54
|
+
throw new WorkflowScriptError(`${at}.url must be a non-empty string`);
|
|
55
|
+
}
|
|
56
|
+
return { url: o.url };
|
|
57
|
+
}
|
|
58
|
+
if (typeof o.data !== "string") {
|
|
59
|
+
throw new WorkflowScriptError(`${at}.data must be a base64 string (or supply { url } instead)`);
|
|
60
|
+
}
|
|
61
|
+
if (typeof o.mimeType !== "string" || o.mimeType.length === 0) {
|
|
62
|
+
throw new WorkflowScriptError(`${at}.mimeType must be a non-empty string, e.g. "image/png"`);
|
|
63
|
+
}
|
|
64
|
+
return { data: o.data, mimeType: o.mimeType };
|
|
65
|
+
}
|
|
40
66
|
function pickWhitelist(scriptSpec) {
|
|
41
67
|
if (typeof scriptSpec !== "object" || scriptSpec === null || Array.isArray(scriptSpec)) {
|
|
42
68
|
throw new WorkflowScriptError("agent(spec): spec must be an object with at least an `objective` string");
|
|
@@ -60,7 +86,7 @@ function pickWhitelist(scriptSpec) {
|
|
|
60
86
|
if (s.images !== undefined) {
|
|
61
87
|
if (!Array.isArray(s.images))
|
|
62
88
|
throw new WorkflowScriptError("agent(spec): `images` must be an array");
|
|
63
|
-
safe.images = s.images;
|
|
89
|
+
safe.images = s.images.map(pickImageInput);
|
|
64
90
|
}
|
|
65
91
|
if (s.limits !== undefined) {
|
|
66
92
|
if (typeof s.limits !== "object" || s.limits === null || Array.isArray(s.limits)) {
|
|
@@ -71,11 +97,15 @@ function pickWhitelist(scriptSpec) {
|
|
|
71
97
|
if (l.maxTurns !== undefined) {
|
|
72
98
|
if (typeof l.maxTurns !== "number" || !Number.isFinite(l.maxTurns))
|
|
73
99
|
throw new WorkflowScriptError("agent(spec): `limits.maxTurns` must be a number");
|
|
100
|
+
if (l.maxTurns <= 0)
|
|
101
|
+
throw new WorkflowScriptError(NON_POSITIVE_LIMIT_MSG("maxTurns", "turn cap"));
|
|
74
102
|
limits.maxTurns = l.maxTurns;
|
|
75
103
|
}
|
|
76
104
|
if (l.timeoutSec !== undefined) {
|
|
77
105
|
if (typeof l.timeoutSec !== "number" || !Number.isFinite(l.timeoutSec))
|
|
78
106
|
throw new WorkflowScriptError("agent(spec): `limits.timeoutSec` must be a number");
|
|
107
|
+
if (l.timeoutSec <= 0)
|
|
108
|
+
throw new WorkflowScriptError(NON_POSITIVE_LIMIT_MSG("timeoutSec", "wall-clock window"));
|
|
79
109
|
limits.timeoutSec = l.timeoutSec;
|
|
80
110
|
}
|
|
81
111
|
safe.limits = limits;
|
|
@@ -116,20 +146,24 @@ function clampResourceLimits(safe, base, caps) {
|
|
|
116
146
|
}
|
|
117
147
|
const requestedTimeoutSec = safe.limits?.timeoutSec;
|
|
118
148
|
const requestedMaxTurns = safe.limits?.maxTurns;
|
|
119
|
-
const timeoutSec =
|
|
120
|
-
const maxTurns =
|
|
149
|
+
const timeoutSec = minPositiveDefined(safe.limits?.timeoutSec, base.limits?.timeoutSec, caps?.perAgentTimeoutSec);
|
|
150
|
+
const maxTurns = minPositiveDefined(safe.limits?.maxTurns, base.limits?.maxTurns, caps?.childMaxTurns);
|
|
121
151
|
if (timeoutSec !== undefined && timeoutSec !== requestedTimeoutSec) {
|
|
122
152
|
notes.push({ field: "timeoutSec", requested: requestedTimeoutSec, applied: timeoutSec });
|
|
123
153
|
}
|
|
124
154
|
if (maxTurns !== undefined && maxTurns !== requestedMaxTurns) {
|
|
125
155
|
notes.push({ field: "maxTurns", requested: requestedMaxTurns, applied: maxTurns });
|
|
126
156
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
157
|
+
const baseOtherLimits = { ...base.limits };
|
|
158
|
+
delete baseOtherLimits.maxTurns;
|
|
159
|
+
delete baseOtherLimits.timeoutSec;
|
|
160
|
+
const rebuilt = {
|
|
161
|
+
...baseOtherLimits,
|
|
162
|
+
...(maxTurns !== undefined ? { maxTurns } : {}),
|
|
163
|
+
...(timeoutSec !== undefined ? { timeoutSec } : {}),
|
|
164
|
+
};
|
|
165
|
+
if (base.limits !== undefined || Object.keys(rebuilt).length > 0)
|
|
166
|
+
safe.limits = rebuilt;
|
|
133
167
|
return notes;
|
|
134
168
|
}
|
|
135
169
|
export function buildGovernedChildSpec(scriptSpec, baseline, models, caps, onResourceClamp) {
|
|
@@ -1,20 +1,31 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
const META = "export const meta = { name: 'conformance', description: 'sandbox escape probes' }\n";
|
|
3
|
+
const WITNESS_FN = "__probeWitness";
|
|
4
|
+
const WITNESS_PRELUDE = `function ${WITNESS_FN}(v) {\n` +
|
|
5
|
+
` if (v === null || v === undefined) return null\n` +
|
|
6
|
+
` var t = typeof v\n` +
|
|
7
|
+
` if (t === 'function') return { hostCapabilityReached: true, via: 'function' }\n` +
|
|
8
|
+
` if (t !== 'object') return null\n` +
|
|
9
|
+
` var names = ['pid', 'env', 'version', 'mainModule', 'binding']\n` +
|
|
10
|
+
` var hit = []\n` +
|
|
11
|
+
` for (var i = 0; i < names.length; i++) { try { if (names[i] in v) hit.push(names[i]) } catch (err) { } }\n` +
|
|
12
|
+
` return hit.length > 0 ? { hostCapabilityReached: true, via: 'object', keys: hit } : null\n` +
|
|
13
|
+
`}\n`;
|
|
3
14
|
const ESCAPE_BODIES = [
|
|
4
|
-
`return agent.constructor.constructor("return typeof process !== 'undefined' ? process : null")()`,
|
|
5
|
-
`return log.constructor.constructor("return typeof process !== 'undefined' ? process : null")()`,
|
|
6
|
-
`return budget.constructor.constructor("return typeof process !== 'undefined' ? process : null")()`,
|
|
7
|
-
`return ({}).constructor.constructor("return typeof process !== 'undefined' ? process : null")()`,
|
|
8
|
-
`return args.constructor.constructor("return typeof require !== 'undefined' ? require : null")()`,
|
|
9
|
-
`const r = await agent({ objective: 'x' }); return (r ?? {}).constructor.constructor("return typeof require !== 'undefined' ? require : null")()`,
|
|
10
|
-
`return (typeof process !== 'undefined' ? process : null)`,
|
|
11
|
-
`return (typeof require !== 'undefined' ? require : null)`,
|
|
12
|
-
`return (typeof globalThis !== 'undefined' && globalThis.process) ? globalThis.process : null`,
|
|
15
|
+
`return ${WITNESS_FN}(agent.constructor.constructor("return typeof process !== 'undefined' ? process : null")())`,
|
|
16
|
+
`return ${WITNESS_FN}(log.constructor.constructor("return typeof process !== 'undefined' ? process : null")())`,
|
|
17
|
+
`return ${WITNESS_FN}(budget.constructor.constructor("return typeof process !== 'undefined' ? process : null")())`,
|
|
18
|
+
`return ${WITNESS_FN}(({}).constructor.constructor("return typeof process !== 'undefined' ? process : null")())`,
|
|
19
|
+
`return ${WITNESS_FN}(args.constructor.constructor("return typeof require !== 'undefined' ? require : null")())`,
|
|
20
|
+
`const r = await agent({ objective: 'x' }); return ${WITNESS_FN}((r ?? {}).constructor.constructor("return typeof require !== 'undefined' ? require : null")())`,
|
|
21
|
+
`return ${WITNESS_FN}(typeof process !== 'undefined' ? process : null)`,
|
|
22
|
+
`return ${WITNESS_FN}(typeof require !== 'undefined' ? require : null)`,
|
|
23
|
+
`return ${WITNESS_FN}((typeof globalThis !== 'undefined' && globalThis.process) ? globalThis.process : null)`,
|
|
13
24
|
];
|
|
14
25
|
const ERROR_PATH_BODIES = [
|
|
15
|
-
`try { await agent({ objective: 'x' }); return null } catch (e) { return e.constructor.constructor("return typeof process !== 'undefined' ? process : null")() }`,
|
|
16
|
-
`try { await parallel([]); return null } catch (e) { return e.constructor.constructor("return typeof require !== 'undefined' ? require : null")() }`,
|
|
17
|
-
`try { await agent({}); return null } catch (e) { return (e && e.constructor && e.constructor.constructor) ? e.constructor.constructor("return (typeof globalThis !== 'undefined' && globalThis.process) ? globalThis.process : null")() : null }`,
|
|
26
|
+
`try { await agent({ objective: 'x' }); return null } catch (e) { return ${WITNESS_FN}(e.constructor.constructor("return typeof process !== 'undefined' ? process : null")()) }`,
|
|
27
|
+
`try { await parallel([]); return null } catch (e) { return ${WITNESS_FN}(e.constructor.constructor("return typeof require !== 'undefined' ? require : null")()) }`,
|
|
28
|
+
`try { await agent({}); return null } catch (e) { return ${WITNESS_FN}((e && e.constructor && e.constructor.constructor) ? e.constructor.constructor("return (typeof globalThis !== 'undefined' && globalThis.process) ? globalThis.process : null")() : null) }`,
|
|
18
29
|
];
|
|
19
30
|
const POLLUTION_BODIES = [
|
|
20
31
|
`return JSON.parse('{"__proto__":{"polluted":1}}')`,
|
|
@@ -24,9 +35,9 @@ const POLLUTION_BODIES = [
|
|
|
24
35
|
`return [JSON.parse('{"__proto__":{"polluted":1}}')]`,
|
|
25
36
|
];
|
|
26
37
|
const SIGNAL_BODIES = [
|
|
27
|
-
`return (typeof signal !== 'undefined' && signal) ? signal.constructor.constructor("return typeof process !== 'undefined' ? process : null")() : null`,
|
|
28
|
-
`return (typeof input !== 'undefined' && input && input.signal) ? input.signal.constructor.constructor("return typeof process !== 'undefined' ? process : null")() : null`,
|
|
29
|
-
`try { return (typeof signal !== 'undefined' && signal) ? (signal.addEventListener ? signal.addEventListener.constructor("return typeof process !== 'undefined' ? process : null")() : null) : null } catch (e) { return null }`,
|
|
38
|
+
`return ${WITNESS_FN}((typeof signal !== 'undefined' && signal) ? signal.constructor.constructor("return typeof process !== 'undefined' ? process : null")() : null)`,
|
|
39
|
+
`return ${WITNESS_FN}((typeof input !== 'undefined' && input && input.signal) ? input.signal.constructor.constructor("return typeof process !== 'undefined' ? process : null")() : null)`,
|
|
40
|
+
`try { return ${WITNESS_FN}((typeof signal !== 'undefined' && signal) ? (signal.addEventListener ? signal.addEventListener.constructor("return typeof process !== 'undefined' ? process : null")() : null) : null) } catch (e) { return null }`,
|
|
30
41
|
];
|
|
31
42
|
function benignPrimitives() {
|
|
32
43
|
return {
|
|
@@ -84,6 +95,49 @@ function looksLikeHostCapability(v) {
|
|
|
84
95
|
}
|
|
85
96
|
return false;
|
|
86
97
|
}
|
|
98
|
+
function witnessReportsHostCapability(v) {
|
|
99
|
+
return typeof v === "object" && v !== null && v.hostCapabilityReached === true;
|
|
100
|
+
}
|
|
101
|
+
function reachedHostCapability(v) {
|
|
102
|
+
return looksLikeHostCapability(v) || witnessReportsHostCapability(v);
|
|
103
|
+
}
|
|
104
|
+
const CANARY_MARKER = "workflow-conformance-canary";
|
|
105
|
+
let canarySeq = 0;
|
|
106
|
+
const CANARY_BODY = `const c = args.canary\n` +
|
|
107
|
+
`return { marker: '${CANARY_MARKER}', nonce: c.nonce, sum: c.x + c.y, joined: ['${CANARY_MARKER}', c.nonce].join(':') }`;
|
|
108
|
+
async function runLivenessCanary(runner) {
|
|
109
|
+
const nonce = `n${++canarySeq}`;
|
|
110
|
+
const scriptArgs = { canary: { nonce, x: 17, y: 25 } };
|
|
111
|
+
let result;
|
|
112
|
+
try {
|
|
113
|
+
const out = await runner.run({ scriptSource: META + CANARY_BODY, primitives: benignPrimitives(), scriptArgs });
|
|
114
|
+
result = out.result;
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
return { ok: false, detail: `run() REJECTED with: ${err instanceof Error ? err.message : String(err)}` };
|
|
118
|
+
}
|
|
119
|
+
const expected = { marker: CANARY_MARKER, nonce, sum: 42, joined: `${CANARY_MARKER}:${nonce}` };
|
|
120
|
+
try {
|
|
121
|
+
assert.deepEqual(result, expected);
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
return { ok: false, detail: `run() returned ${JSON.stringify(result)} — expected ${JSON.stringify(expected)}` };
|
|
125
|
+
}
|
|
126
|
+
return { ok: true, detail: "" };
|
|
127
|
+
}
|
|
128
|
+
async function runProbeBody(runner, scriptSource, primitives, family, signal) {
|
|
129
|
+
try {
|
|
130
|
+
const out = await runner.run({ scriptSource, primitives, scriptArgs: { probe: true }, signal });
|
|
131
|
+
return { executed: true, result: out.result };
|
|
132
|
+
}
|
|
133
|
+
catch (err) {
|
|
134
|
+
const live = await runLivenessCanary(runner);
|
|
135
|
+
assert.ok(live.ok, `${family}: the probe threw AND the runner then failed the liveness canary — this runner is BROKEN, ` +
|
|
136
|
+
`not blocking, so nothing about its membrane was proven. probe: ${scriptSource.slice(META.length)} | ` +
|
|
137
|
+
`probe threw: ${err instanceof Error ? err.message : String(err)} | canary: ${live.detail}`);
|
|
138
|
+
return { executed: false };
|
|
139
|
+
}
|
|
140
|
+
}
|
|
87
141
|
export async function assertWorkflowPrimitivesWiring(runner) {
|
|
88
142
|
assert.equal(runner.safeForUntrustedScripts, true, "wiring conformance targets a HARD runner (safeForUntrustedScripts must be true)");
|
|
89
143
|
const hostSeen = { phaseCalls: [], scopedBodyRan: false, logs: [] };
|
|
@@ -144,54 +198,34 @@ export async function assertWorkflowPrimitivesWiring(runner) {
|
|
|
144
198
|
}
|
|
145
199
|
export async function assertWorkflowSandboxConformance(runner) {
|
|
146
200
|
assert.equal(runner.safeForUntrustedScripts, true, "conformance is only meaningful for a HARD runner (safeForUntrustedScripts must be true)");
|
|
201
|
+
const live = await runLivenessCanary(runner);
|
|
202
|
+
assert.ok(live.ok, `POSITIVE CONTROL FAILED — this runner did not execute the canary script and return its value, so the ` +
|
|
203
|
+
`escape battery below cannot certify anything about it (a runner that runs nothing produces the same ` +
|
|
204
|
+
`"nothing leaked" results as a perfect sandbox). ${live.detail}`);
|
|
147
205
|
for (const escBody of ESCAPE_BODIES) {
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
try {
|
|
151
|
-
const out = await runner.run({ scriptSource, primitives: benignPrimitives(), scriptArgs: { probe: true } });
|
|
152
|
-
result = out.result;
|
|
153
|
-
}
|
|
154
|
-
catch {
|
|
206
|
+
const out = await runProbeBody(runner, META + WITNESS_PRELUDE + escBody, benignPrimitives(), "sandbox escape");
|
|
207
|
+
if (!out.executed)
|
|
155
208
|
continue;
|
|
156
|
-
}
|
|
157
|
-
assert.equal(looksLikeHostCapability(result), false, `sandbox escape REACHED a host capability via: ${escBody}`);
|
|
209
|
+
assert.equal(reachedHostCapability(out.result), false, `sandbox escape REACHED a host capability via: ${escBody}`);
|
|
158
210
|
}
|
|
159
211
|
for (const errBody of ERROR_PATH_BODIES) {
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
try {
|
|
163
|
-
const out = await runner.run({ scriptSource, primitives: throwingPrimitives(), scriptArgs: { probe: true } });
|
|
164
|
-
result = out.result;
|
|
165
|
-
}
|
|
166
|
-
catch {
|
|
212
|
+
const out = await runProbeBody(runner, META + WITNESS_PRELUDE + errBody, throwingPrimitives(), "error-path");
|
|
213
|
+
if (!out.executed)
|
|
167
214
|
continue;
|
|
168
|
-
}
|
|
169
|
-
assert.equal(looksLikeHostCapability(result), false, `error-path: a host-side primitive error leaked a host capability via: ${errBody}`);
|
|
215
|
+
assert.equal(reachedHostCapability(out.result), false, `error-path: a host-side primitive error leaked a host capability via: ${errBody}`);
|
|
170
216
|
}
|
|
171
217
|
for (const polBody of POLLUTION_BODIES) {
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
try {
|
|
175
|
-
const out = await runner.run({ scriptSource, primitives: benignPrimitives(), scriptArgs: { probe: true } });
|
|
176
|
-
result = out.result;
|
|
177
|
-
}
|
|
178
|
-
catch {
|
|
218
|
+
const out = await runProbeBody(runner, META + polBody, benignPrimitives(), "prototype pollution");
|
|
219
|
+
if (!out.executed)
|
|
179
220
|
continue;
|
|
180
|
-
}
|
|
181
|
-
assert.equal(graphHasInjectedProtoKey(result), false, `prototype pollution: the marshalled result carries an injected __proto__/constructor/prototype own key via: ${polBody}`);
|
|
221
|
+
assert.equal(graphHasInjectedProtoKey(out.result), false, `prototype pollution: the marshalled result carries an injected __proto__/constructor/prototype own key via: ${polBody}`);
|
|
182
222
|
}
|
|
183
223
|
const hostSignal = new AbortController().signal;
|
|
184
224
|
for (const sigBody of SIGNAL_BODIES) {
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
try {
|
|
188
|
-
const out = await runner.run({ scriptSource, primitives: benignPrimitives(), scriptArgs: { probe: true }, signal: hostSignal });
|
|
189
|
-
result = out.result;
|
|
190
|
-
}
|
|
191
|
-
catch {
|
|
225
|
+
const out = await runProbeBody(runner, META + WITNESS_PRELUDE + sigBody, benignPrimitives(), "signal-path", hostSignal);
|
|
226
|
+
if (!out.executed)
|
|
192
227
|
continue;
|
|
193
|
-
}
|
|
194
|
-
assert.equal(looksLikeHostCapability(result), false, `signal-path: the host AbortSignal leaked a host capability via: ${sigBody}`);
|
|
228
|
+
assert.equal(reachedHostCapability(out.result), false, `signal-path: the host AbortSignal leaked a host capability via: ${sigBody}`);
|
|
195
229
|
}
|
|
196
230
|
}
|
|
197
231
|
const DETERMINISM_FORBIDDEN_BODIES = [
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { mkdirSync, readFileSync, writeFileSync, existsSync } from "node:fs";
|
|
2
|
-
import { join, resolve, sep } from "node:path";
|
|
1
|
+
import { mkdirSync, readFileSync, realpathSync, writeFileSync, existsSync } from "node:fs";
|
|
2
|
+
import { basename, dirname, join, resolve, sep } from "node:path";
|
|
3
3
|
import { sanitizeScope } from "../stores/file/fs-atomic.js";
|
|
4
4
|
function isPlainObject(v) {
|
|
5
5
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
@@ -30,6 +30,26 @@ export function normalizeStringArg(callArgs, defaultArgs, key = "topic") {
|
|
|
30
30
|
function safeFileStem(id) {
|
|
31
31
|
return /^[A-Za-z0-9._-]{1,120}$/.test(id) && !id.startsWith(".") ? id : undefined;
|
|
32
32
|
}
|
|
33
|
+
function containedBy(root, p) {
|
|
34
|
+
return p === root || p.startsWith(root + sep);
|
|
35
|
+
}
|
|
36
|
+
function canonicalizeDeepestExisting(p) {
|
|
37
|
+
let cur = p;
|
|
38
|
+
const missingTail = [];
|
|
39
|
+
for (;;) {
|
|
40
|
+
try {
|
|
41
|
+
const real = realpathSync(cur);
|
|
42
|
+
return missingTail.length === 0 ? real : join(real, ...[...missingTail].reverse());
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
const parent = dirname(cur);
|
|
46
|
+
if (parent === cur)
|
|
47
|
+
return undefined;
|
|
48
|
+
missingTail.push(basename(cur));
|
|
49
|
+
cur = parent;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
33
53
|
export function createFileWorkflowScriptStore(dir) {
|
|
34
54
|
const root = resolve(dir);
|
|
35
55
|
const scopeDir = (scope) => join(root, sanitizeScope(scope));
|
|
@@ -48,10 +68,20 @@ export function createFileWorkflowScriptStore(dir) {
|
|
|
48
68
|
load(scriptPath, scope) {
|
|
49
69
|
const base = scopeDir(scope);
|
|
50
70
|
const full = resolve(scriptPath);
|
|
51
|
-
if (
|
|
71
|
+
if (!containedBy(base, full)) {
|
|
52
72
|
throw new Error(`workflow script store: scriptPath is outside this scope's script partition`);
|
|
53
73
|
}
|
|
54
|
-
|
|
74
|
+
const physRoot = canonicalizeDeepestExisting(root);
|
|
75
|
+
const physBase = canonicalizeDeepestExisting(base);
|
|
76
|
+
const physFull = canonicalizeDeepestExisting(full);
|
|
77
|
+
const refusePhysical = () => {
|
|
78
|
+
throw new Error(`workflow script store: scriptPath is outside this scope's script partition after symlink resolution`);
|
|
79
|
+
};
|
|
80
|
+
if (physRoot !== undefined && physBase !== undefined && !containedBy(physRoot, physBase))
|
|
81
|
+
refusePhysical();
|
|
82
|
+
if (physBase !== undefined && physFull !== undefined && !containedBy(physBase, physFull))
|
|
83
|
+
refusePhysical();
|
|
84
|
+
return readFileSync(physFull ?? full, "utf-8");
|
|
55
85
|
},
|
|
56
86
|
resolveName(name) {
|
|
57
87
|
if (/^wf_/i.test(name))
|