@sema-agent/core 2.13.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2345 -0
- package/dist/agents/cascade.d.ts +6 -1
- package/dist/agents/cascade.js +76 -32
- package/dist/agents/cumulative-stats.d.ts +29 -0
- package/dist/agents/cumulative-stats.js +29 -0
- package/dist/agents/observer.d.ts +4 -0
- package/dist/agents/observer.js +11 -10
- package/dist/agents/repair-loop.d.ts +2 -0
- package/dist/agents/repair-loop.js +110 -3
- package/dist/agents/retain-ledger.d.ts +20 -2
- package/dist/agents/retain-ledger.js +38 -8
- package/dist/agents/roster-store.d.ts +4 -0
- package/dist/agents/roster-store.js +20 -1
- package/dist/agents/subagent.js +66 -64
- package/dist/agents/teacher.d.ts +1 -9
- package/dist/agents/teacher.js +23 -24
- package/dist/agents/team.js +7 -4
- package/dist/agents/verify.d.ts +15 -2
- package/dist/agents/verify.js +39 -3
- package/dist/bin/sema-tb.js +2 -2
- package/dist/brain/anthropic.js +24 -21
- package/dist/brain/circuit-breaker.d.ts +1 -0
- package/dist/brain/circuit-breaker.js +26 -9
- package/dist/brain/degrading.js +13 -1
- package/dist/brain/failover.js +2 -0
- package/dist/brain/openai.js +24 -21
- package/dist/brain/repetition.js +10 -1
- package/dist/brain/routing.js +5 -1
- package/dist/brain/status-sink.js +5 -1
- package/dist/brain/stream-engine.js +5 -2
- package/dist/brain/stream-shared.d.ts +1 -0
- package/dist/brain/stream-shared.js +14 -0
- package/dist/brain/terminal-cause.d.ts +4 -0
- package/dist/brain/terminal-cause.js +12 -0
- package/dist/brain/tool-call-id.d.ts +1 -0
- package/dist/brain/tool-call-id.js +3 -0
- package/dist/brain/tool-call-repair.js +34 -15
- package/dist/core/auto-compaction.js +33 -32
- package/dist/core/background-agent-store.d.ts +22 -22
- package/dist/core/background-agent-store.js +19 -23
- package/dist/core/checkpoint-store.d.ts +7 -13
- package/dist/core/checkpoint-store.js +39 -15
- package/dist/core/consolidate-scope.js +4 -3
- package/dist/core/context-edit.js +3 -0
- package/dist/core/context-guard.js +3 -0
- package/dist/core/file-snapshot-store.d.ts +8 -0
- package/dist/core/file-snapshot-store.js +4 -3
- package/dist/core/git-worktree-env.d.ts +6 -1
- package/dist/core/git-worktree-env.js +12 -1
- package/dist/core/hooks.d.ts +4 -1
- package/dist/core/hooks.js +35 -7
- package/dist/core/lsp-diagnostics.js +13 -7
- package/dist/core/lsp-protocol.d.ts +1 -1
- package/dist/core/lsp-protocol.js +20 -8
- package/dist/core/lsp-session.d.ts +12 -2
- package/dist/core/lsp-session.js +114 -49
- package/dist/core/lsp.d.ts +13 -1
- package/dist/core/lsp.js +65 -9
- package/dist/core/mailbox-store.d.ts +7 -10
- package/dist/core/mcp.d.ts +7 -10
- package/dist/core/mcp.js +7 -3
- package/dist/core/memory-engine/data-plane.js +4 -0
- package/dist/core/memory-engine/engine.d.ts +2 -2
- package/dist/core/memory-engine/engine.js +96 -31
- package/dist/core/memory-engine/file-backend.d.ts +2 -2
- package/dist/core/memory-engine/file-backend.js +38 -39
- package/dist/core/memory-engine/layout.d.ts +8 -0
- package/dist/core/memory-engine/layout.js +71 -4
- package/dist/core/memory-engine/scope-contract.js +9 -3
- package/dist/core/memory-engine/types.d.ts +1 -1
- package/dist/core/memory.js +3 -0
- package/dist/core/permission-rules.d.ts +2 -1
- package/dist/core/permission-rules.js +24 -3
- package/dist/core/runner/active-skill-scope.d.ts +0 -1
- package/dist/core/runner/active-skill-scope.js +1 -15
- package/dist/core/runner/assemble-result.d.ts +3 -8
- package/dist/core/runner/assemble-result.js +3 -5
- package/dist/core/runner/compaction-call-options.d.ts +7 -0
- package/dist/core/runner/compaction-call-options.js +30 -0
- package/dist/core/runner/prepare-task.d.ts +6 -23
- package/dist/core/runner/prepare-task.js +31 -53
- package/dist/core/runner/runtask.d.ts +1 -0
- package/dist/core/runner/runtask.js +86 -178
- package/dist/core/runner/session-rule-policy.js +4 -1
- package/dist/core/runner/teardown-bounded.d.ts +7 -0
- package/dist/core/runner/teardown-bounded.js +36 -0
- package/dist/core/runner/tool-disclosure.d.ts +2 -5
- package/dist/core/runner/turn-attachments.d.ts +5 -15
- package/dist/core/runner/turn-attachments.js +1 -1
- package/dist/core/runner/usage-accounting.d.ts +6 -0
- package/dist/core/runner/usage-accounting.js +9 -8
- package/dist/core/safe-notify.d.ts +17 -0
- package/dist/core/safe-notify.js +59 -0
- package/dist/core/secret-env.js +5 -1
- package/dist/core/sensitive-path-policy.js +11 -4
- package/dist/core/store-contracts/checkpoint-store-contract.js +1 -1
- package/dist/core/store-contracts/session-repo-contract.js +40 -0
- package/dist/core/stub-env.d.ts +2 -6
- package/dist/core/surrogate-safe-slice.d.ts +4 -0
- package/dist/core/surrogate-safe-slice.js +18 -0
- package/dist/core/task-registry-monitor.js +15 -14
- package/dist/core/task-registry-shared.d.ts +22 -0
- package/dist/core/task-registry-shared.js +16 -0
- package/dist/core/task-registry.d.ts +1 -0
- package/dist/core/task-registry.js +40 -57
- package/dist/core/tool-errors.js +5 -4
- package/dist/core/tool-name-aliases.js +2 -1
- package/dist/core/tool-policy.js +72 -19
- package/dist/core/tool-result-store.d.ts +1 -0
- package/dist/core/tool-result-store.js +17 -1
- package/dist/core/trace.d.ts +3 -9
- package/dist/core/types.d.ts +16 -12
- package/dist/core/untrusted-egress.js +7 -5
- package/dist/core/workflow-journal-store.d.ts +9 -20
- package/dist/core/workflow-run-store.d.ts +11 -10
- package/dist/core/workflow-run-store.js +22 -16
- package/dist/engine/compaction/compaction.d.ts +1 -0
- package/dist/engine/compaction/compaction.js +14 -5
- package/dist/engine/compaction/utils.js +4 -2
- package/dist/engine/execution-env/node-execution-env.d.ts +2 -24
- package/dist/engine/harness/agent-harness.d.ts +1 -0
- package/dist/engine/harness/agent-harness.js +13 -33
- package/dist/engine/harness/types.d.ts +29 -67
- package/dist/engine/llm/types.d.ts +1 -1
- package/dist/engine/loop/agent-loop.js +11 -4
- package/dist/engine/loop/types.d.ts +11 -9
- package/dist/engine/lsp/frame-decoder.js +6 -3
- package/dist/engine/lsp/node-lsp-manager.d.ts +2 -0
- package/dist/engine/lsp/node-lsp-manager.js +103 -26
- package/dist/engine/lsp/stdio-lsp-transport.js +16 -7
- package/dist/engine/session/memory-repo.d.ts +2 -6
- package/dist/engine/session/session.d.ts +5 -30
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -1
- package/dist/internal/harness-types.d.ts +3 -1
- package/dist/internal/harness.d.ts +1 -1
- package/dist/internal/harness.js +1 -1
- package/dist/orchestration/run-spec.js +7 -4
- package/dist/orchestration/run-workflow-tool.js +3 -35
- package/dist/orchestration/workflow-governance.js +43 -9
- package/dist/orchestration/workflow-sandbox-conformance.js +85 -51
- package/dist/orchestration/workflow-script-store.js +34 -4
- package/dist/orchestration/workflow.d.ts +3 -11
- package/dist/orchestration/workflow.js +164 -204
- package/dist/prompt-assembly/artifact-store.d.ts +2 -0
- package/dist/prompt-assembly/artifact-store.js +39 -24
- package/dist/prompt-assembly/assemble.js +38 -7
- package/dist/prompt-assembly/epoch.js +19 -2
- package/dist/prompt-assembly/event-registry.js +3 -3
- package/dist/prompt-assembly/explain.js +1 -1
- package/dist/prompt-assembly/tool-catalog.d.ts +10 -9
- package/dist/prompt-assembly/tool-catalog.js +16 -1
- package/dist/prompt-assembly/turn-snapshot.d.ts +2 -5
- package/dist/prompt-assembly/turn-snapshot.js +4 -1
- package/dist/stores/file/background-agent-store.d.ts +3 -12
- package/dist/stores/file/background-agent-store.js +3 -41
- package/dist/stores/file/checkpoint-store.js +6 -6
- package/dist/stores/file/fs-atomic.d.ts +3 -0
- package/dist/stores/file/fs-atomic.js +53 -8
- package/dist/stores/file/index.d.ts +1 -0
- package/dist/stores/file/index.js +7 -0
- package/dist/stores/file/mailbox-store.d.ts +2 -6
- package/dist/stores/file/session-policy-store.js +15 -1
- package/dist/stores/file/session-store.d.ts +4 -6
- package/dist/stores/file/session-store.js +28 -1
- package/dist/stores/file/workflow-journal-store.d.ts +3 -11
- package/dist/stores/file/workflow-run-store.d.ts +3 -7
- package/dist/stores/file/workflow-run-store.js +4 -17
- package/dist/tools/fs/bash-readonly-classifier.d.ts +2 -0
- package/dist/tools/fs/bash-readonly-classifier.js +73 -21
- package/dist/tools/fs/fs-bash.d.ts +1 -1
- package/dist/tools/fs/fs-bash.js +407 -91
- package/dist/tools/fs/fs-pdf.d.ts +2 -12
- package/dist/tools/fs/fs-shared.js +2 -2
- package/dist/tools/fs/notebook.d.ts +2 -5
- package/dist/tools/fs/safety.d.ts +3 -0
- package/dist/tools/fs/safety.js +43 -14
- package/dist/tools/fs/search.js +5 -5
- package/dist/tools/loop-tick.js +1 -1
- package/dist/tools/monitor.d.ts +1 -0
- package/dist/tools/monitor.js +57 -9
- package/dist/tools/worktree.d.ts +4 -7
- package/dist/tools/worktree.js +235 -143
- package/package.json +9 -2
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { McpDroppedTool } from "../mcp.js";
|
|
1
2
|
import { type SkillListingEntry } from "./synthetic-tools.js";
|
|
2
3
|
export declare const TODO_REMINDER_CONFIG: {
|
|
3
4
|
readonly TURNS_SINCE_WRITE: 10;
|
|
@@ -112,11 +113,7 @@ export interface AttachmentInputs {
|
|
|
112
113
|
}>;
|
|
113
114
|
removed: readonly string[];
|
|
114
115
|
};
|
|
115
|
-
mcpDroppedTools?: ReadonlyArray<
|
|
116
|
-
server: string;
|
|
117
|
-
tool: string;
|
|
118
|
-
reason: string;
|
|
119
|
-
}>;
|
|
116
|
+
mcpDroppedTools?: ReadonlyArray<McpDroppedTool>;
|
|
120
117
|
}
|
|
121
118
|
export declare function collectDueAttachments(state: AttachmentState, inp: AttachmentInputs): readonly TurnAttachment[];
|
|
122
119
|
export declare function renderToolSearchUsageReminder(undiscovered: readonly string[]): string;
|
|
@@ -178,14 +175,7 @@ export declare function renderMcpInstructionsDelta(added: ReadonlyArray<{
|
|
|
178
175
|
server: string;
|
|
179
176
|
text: string;
|
|
180
177
|
}>, removed: readonly string[]): string | undefined;
|
|
181
|
-
export declare function renderMcpDroppedTools(entries: ReadonlyArray<
|
|
182
|
-
server: string;
|
|
183
|
-
tool: string;
|
|
184
|
-
reason: string;
|
|
185
|
-
}>): string;
|
|
178
|
+
export declare function renderMcpDroppedTools(entries: ReadonlyArray<McpDroppedTool>): string;
|
|
186
179
|
export declare const MCP_DROPPED_BATCH_MAX_BYTES = 4096;
|
|
187
|
-
export declare function selectMcpDroppedBatch<T extends
|
|
188
|
-
|
|
189
|
-
tool: string;
|
|
190
|
-
reason: string;
|
|
191
|
-
}>(entries: ReadonlyArray<T>): T[];
|
|
180
|
+
export declare function selectMcpDroppedBatch<T extends McpDroppedTool>(entries: ReadonlyArray<T>): T[];
|
|
181
|
+
export declare function clipToBytes(s: string, maxBytes: number): string;
|
|
@@ -7,6 +7,11 @@ export declare function promptTokensOf(family: CacheFamily, u: {
|
|
|
7
7
|
cacheRead: number;
|
|
8
8
|
cacheWrite: number;
|
|
9
9
|
}): number;
|
|
10
|
+
export declare function uncachedInputTokensOf(family: CacheFamily, u: {
|
|
11
|
+
input: number;
|
|
12
|
+
cacheRead: number;
|
|
13
|
+
cacheWrite: number;
|
|
14
|
+
}): number;
|
|
10
15
|
export declare function usageCostMicroUsd(family: CacheFamily, u: {
|
|
11
16
|
input?: number;
|
|
12
17
|
output?: number;
|
|
@@ -14,5 +19,6 @@ export declare function usageCostMicroUsd(family: CacheFamily, u: {
|
|
|
14
19
|
cacheWrite?: number;
|
|
15
20
|
}, pricing: ModelPricing): {
|
|
16
21
|
totalInputTokens: number;
|
|
22
|
+
uncachedInputTokens: number;
|
|
17
23
|
costMicroUsd: number;
|
|
18
24
|
};
|
|
@@ -12,18 +12,19 @@ export function cacheFamilyOf(model) {
|
|
|
12
12
|
export function promptTokensOf(family, u) {
|
|
13
13
|
return family === "input-excludes-cached" ? u.input + u.cacheRead + u.cacheWrite : u.input;
|
|
14
14
|
}
|
|
15
|
+
export function uncachedInputTokensOf(family, u) {
|
|
16
|
+
return Math.max(0, promptTokensOf(family, u) - u.cacheRead - u.cacheWrite);
|
|
17
|
+
}
|
|
15
18
|
export function usageCostMicroUsd(family, u, pricing) {
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
cacheWrite: u.cacheWrite || 0,
|
|
20
|
-
});
|
|
19
|
+
const normalized = { input: u.input || 0, cacheRead: u.cacheRead || 0, cacheWrite: u.cacheWrite || 0 };
|
|
20
|
+
const totalInputTokens = promptTokensOf(family, normalized);
|
|
21
|
+
const uncachedInputTokens = uncachedInputTokensOf(family, normalized);
|
|
21
22
|
const costMicroUsd = computeCostMicroUsd({
|
|
22
23
|
totalInputTokens,
|
|
23
|
-
cacheReadTokens:
|
|
24
|
-
cacheWriteTokens:
|
|
24
|
+
cacheReadTokens: normalized.cacheRead,
|
|
25
|
+
cacheWriteTokens: normalized.cacheWrite,
|
|
25
26
|
cacheWriteTokensLong: 0,
|
|
26
27
|
outputTokens: u.output || 0,
|
|
27
28
|
}, pricing);
|
|
28
|
-
return { totalInputTokens, costMicroUsd };
|
|
29
|
+
return { totalInputTokens, uncachedInputTokens, costMicroUsd };
|
|
29
30
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface SafeNotifyFailure {
|
|
2
|
+
readonly site: string;
|
|
3
|
+
readonly error: Error;
|
|
4
|
+
readonly failures: number;
|
|
5
|
+
}
|
|
6
|
+
export interface SafeNotifierOptions {
|
|
7
|
+
readonly onError?: (failure: SafeNotifyFailure) => void;
|
|
8
|
+
}
|
|
9
|
+
export interface SafeNotifier {
|
|
10
|
+
notify(run: () => void, site: string): boolean;
|
|
11
|
+
notifyAsync(run: () => unknown, site: string): Promise<boolean>;
|
|
12
|
+
readonly failures: number;
|
|
13
|
+
failuresAt(site: string): number;
|
|
14
|
+
lastErrorAt(site: string): Error | undefined;
|
|
15
|
+
failedSites(): readonly string[];
|
|
16
|
+
}
|
|
17
|
+
export declare function createSafeNotifier(opts?: SafeNotifierOptions): SafeNotifier;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export function createSafeNotifier(opts) {
|
|
2
|
+
const onError = opts?.onError;
|
|
3
|
+
let sites;
|
|
4
|
+
let total = 0;
|
|
5
|
+
const record = (site, thrown) => {
|
|
6
|
+
const error = thrown instanceof Error ? thrown : new Error(String(thrown));
|
|
7
|
+
total += 1;
|
|
8
|
+
if (sites === undefined)
|
|
9
|
+
sites = new Map();
|
|
10
|
+
const state = sites.get(site);
|
|
11
|
+
if (state !== undefined) {
|
|
12
|
+
state.count += 1;
|
|
13
|
+
state.last = error;
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
sites.set(site, { count: 1, last: error });
|
|
17
|
+
if (onError === undefined)
|
|
18
|
+
return;
|
|
19
|
+
try {
|
|
20
|
+
onError({ site, error, failures: 1 });
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
return {
|
|
26
|
+
notify(run, site) {
|
|
27
|
+
try {
|
|
28
|
+
run();
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
catch (e) {
|
|
32
|
+
record(site, e);
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
async notifyAsync(run, site) {
|
|
37
|
+
try {
|
|
38
|
+
await run();
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
catch (e) {
|
|
42
|
+
record(site, e);
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
get failures() {
|
|
47
|
+
return total;
|
|
48
|
+
},
|
|
49
|
+
failuresAt(site) {
|
|
50
|
+
return sites?.get(site)?.count ?? 0;
|
|
51
|
+
},
|
|
52
|
+
lastErrorAt(site) {
|
|
53
|
+
return sites?.get(site)?.last;
|
|
54
|
+
},
|
|
55
|
+
failedSites() {
|
|
56
|
+
return sites === undefined ? [] : [...sites.keys()];
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
package/dist/core/secret-env.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
const SECRET_ENV_RE = /(^|_)(KEY|TOKEN|SECRET|SECRETS|PASSWORD|PASSWD|CREDENTIAL|CREDENTIALS|PRIVATEKEY|APIKEY|ACCESS_KEY|SESSION_TOKEN)(?:_(?:\d+|ID))?$/i;
|
|
2
|
-
const SECRET_ENV_EXACT_NAMES = new Set([
|
|
2
|
+
const SECRET_ENV_EXACT_NAMES = new Set([
|
|
3
|
+
"DATABASE_URL", "CONNECTION_STRING", "DSN", "NPM_AUTH",
|
|
4
|
+
"PGPASSWORD", "MYSQL_PWD", "SSHPASS", "GPG_PASSPHRASE", "GH_PAT", "GITHUB_PAT",
|
|
5
|
+
"NPM_CONFIG__AUTH", "NPM_CONFIG__AUTHTOKEN",
|
|
6
|
+
]);
|
|
3
7
|
export function isSecretEnvKey(key) {
|
|
4
8
|
return SECRET_ENV_RE.test(key) || SECRET_ENV_EXACT_NAMES.has(key.toUpperCase());
|
|
5
9
|
}
|
|
@@ -14,6 +14,9 @@ export const RECOMMENDED_SENSITIVE_PATTERNS = [
|
|
|
14
14
|
".ssh",
|
|
15
15
|
".git/hooks",
|
|
16
16
|
".git/config",
|
|
17
|
+
".git/modules",
|
|
18
|
+
".mcp.json",
|
|
19
|
+
".claude*",
|
|
17
20
|
".aws",
|
|
18
21
|
".config/gcloud",
|
|
19
22
|
".azure",
|
|
@@ -24,15 +27,19 @@ export const RECOMMENDED_SENSITIVE_PATTERNS = [
|
|
|
24
27
|
".zsh_history",
|
|
25
28
|
];
|
|
26
29
|
function segmentGlobToRegExp(segment) {
|
|
27
|
-
const escaped = segment.replace(/[.+^${}()|[\]
|
|
28
|
-
return new RegExp(`^${escaped}
|
|
30
|
+
const escaped = segment.replace(/[.+^${}()|[\]\\?]/g, "\\$&").replace(/\*/g, "[^/]*");
|
|
31
|
+
return new RegExp(`^${escaped}$`, CASE_INSENSITIVE_FS ? "i" : "");
|
|
29
32
|
}
|
|
33
|
+
const CASE_INSENSITIVE_FS = process.platform === "darwin" || process.platform === "win32";
|
|
30
34
|
function compilePatterns(patterns) {
|
|
31
35
|
const out = [];
|
|
32
36
|
for (const raw of patterns) {
|
|
33
37
|
const segments = raw.split("/").filter(Boolean).map(segmentGlobToRegExp);
|
|
34
|
-
if (segments.length
|
|
35
|
-
|
|
38
|
+
if (segments.length === 0) {
|
|
39
|
+
throw new Error(`createSensitivePathPolicy: pattern ${JSON.stringify(raw)} contains no path segments and would guard nothing. ` +
|
|
40
|
+
`Patterns are "/"-separated runs of path SEGMENTS (e.g. ".ssh", ".git/hooks", "*.pem"); remove the entry or spell the segments.`);
|
|
41
|
+
}
|
|
42
|
+
out.push({ raw, segments });
|
|
36
43
|
}
|
|
37
44
|
return out;
|
|
38
45
|
}
|
|
@@ -20,7 +20,7 @@ export function createCheckpointFixture(over = {}) {
|
|
|
20
20
|
},
|
|
21
21
|
state: {
|
|
22
22
|
activeTools: [],
|
|
23
|
-
nestedStats: { tokens: 0, turns: 0, tasks: 0, costMicroUsd: 0 },
|
|
23
|
+
nestedStats: { tokens: 0, turns: 0, tasks: 0, costMicroUsd: 0, anyUnpriced: false },
|
|
24
24
|
},
|
|
25
25
|
status: "pending",
|
|
26
26
|
createdAt: 1_700_000_000_000,
|
|
@@ -32,5 +32,45 @@ export async function sessionRepoContract(make, runAssertion) {
|
|
|
32
32
|
const repo = make();
|
|
33
33
|
await assert.rejects(repo.open({ id: "0198fa00-0000-7000-8000-0000000000ff", createdAt: "" }), /not.?found/i);
|
|
34
34
|
});
|
|
35
|
+
run("delete() makes the id not_found — the history is gone, not merely hidden from open()", async () => {
|
|
36
|
+
const repo = make();
|
|
37
|
+
const sess = await repo.create({ id: A });
|
|
38
|
+
await sess.getStorage().appendEntry(SESSION_ENTRY("e1", null));
|
|
39
|
+
await repo.delete({ id: A, createdAt: "" });
|
|
40
|
+
await assert.rejects(repo.open({ id: A, createdAt: "" }), /not.?found/i);
|
|
41
|
+
const fresh = await repo.create({ id: A });
|
|
42
|
+
assert.deepEqual((await fresh.getStorage().getEntries()).map((e) => e.id), []);
|
|
43
|
+
});
|
|
44
|
+
run("fork() copies the log, stamps forkedFrom, and leaves the SOURCE untouched", async () => {
|
|
45
|
+
const repo = make();
|
|
46
|
+
const src = await repo.create({ id: A });
|
|
47
|
+
await src.getStorage().appendEntry(SESSION_ENTRY("e1", null));
|
|
48
|
+
await src.getStorage().appendEntry(SESSION_ENTRY("e2", "e1"));
|
|
49
|
+
const B = "0198fa00-0000-7000-8000-00000000000b";
|
|
50
|
+
const forked = await repo.fork({ id: A, createdAt: "" }, { id: B });
|
|
51
|
+
assert.deepEqual((await forked.getStorage().getEntries()).map((e) => e.id), ["e1", "e2"]);
|
|
52
|
+
assert.equal((await forked.getMetadata()).forkedFrom, A, "fork lineage must be stamped (a reap walks it)");
|
|
53
|
+
await src.getStorage().appendEntry(SESSION_ENTRY("e3", "e2"));
|
|
54
|
+
assert.deepEqual((await (await repo.open({ id: A, createdAt: "" })).getStorage().getEntries()).map((e) => e.id), ["e1", "e2", "e3"]);
|
|
55
|
+
assert.deepEqual((await (await repo.open({ id: B, createdAt: "" })).getStorage().getEntries()).map((e) => e.id), ["e1", "e2"]);
|
|
56
|
+
});
|
|
57
|
+
run("exportEntries → importEntries round-trips the log verbatim, and the imported session REPLACES what was there", async () => {
|
|
58
|
+
const repo = make();
|
|
59
|
+
if (repo.exportEntries === undefined || repo.importEntries === undefined)
|
|
60
|
+
return;
|
|
61
|
+
const src = await repo.create({ id: A });
|
|
62
|
+
await src.getStorage().appendEntry(SESSION_ENTRY("e1", null));
|
|
63
|
+
await src.getStorage().appendEntry(SESSION_ENTRY("e2", "e1"));
|
|
64
|
+
const donor = await repo.exportEntries(A);
|
|
65
|
+
assert.deepEqual(donor.map((e) => e.id), ["e1", "e2"]);
|
|
66
|
+
const C = "0198fa00-0000-7000-8000-00000000000c";
|
|
67
|
+
const target = await repo.create({ id: C });
|
|
68
|
+
await target.getStorage().appendEntry(SESSION_ENTRY("old", null));
|
|
69
|
+
await repo.importEntries(C, undefined, donor);
|
|
70
|
+
const after = await repo.exportEntries(C);
|
|
71
|
+
assert.deepEqual(after.map((e) => e.id), ["e1", "e2"]);
|
|
72
|
+
assert.deepEqual(after.map((e) => e.parentId), [null, "e1"]);
|
|
73
|
+
assert.equal((await repo.exportEntries(A)).length, 2, "importing into C must not disturb the donor session");
|
|
74
|
+
});
|
|
35
75
|
await settle();
|
|
36
76
|
}
|
package/dist/core/stub-env.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ExecutionEnv, ExecutionError, FileError, type FileInfo, type Result } from "../internal/harness.js";
|
|
1
|
+
import { type ExecResult, type ExecutionEnv, ExecutionError, FileError, type FileInfo, type Result } from "../internal/harness.js";
|
|
2
2
|
export declare class StubExecutionEnv implements ExecutionEnv {
|
|
3
3
|
cwd: string;
|
|
4
4
|
constructor(cwd?: string);
|
|
@@ -18,10 +18,6 @@ export declare class StubExecutionEnv implements ExecutionEnv {
|
|
|
18
18
|
remove(path: string): Promise<Result<void, FileError>>;
|
|
19
19
|
createTempDir(): Promise<Result<string, FileError>>;
|
|
20
20
|
createTempFile(): Promise<Result<string, FileError>>;
|
|
21
|
-
exec(): Promise<Result<
|
|
22
|
-
stdout: string;
|
|
23
|
-
stderr: string;
|
|
24
|
-
exitCode: number;
|
|
25
|
-
}, ExecutionError>>;
|
|
21
|
+
exec(): Promise<Result<ExecResult, ExecutionError>>;
|
|
26
22
|
cleanup(): Promise<void>;
|
|
27
23
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export const isHighSurrogate = (c) => c >= 0xd800 && c <= 0xdbff;
|
|
2
|
+
export const isLowSurrogate = (c) => c >= 0xdc00 && c <= 0xdfff;
|
|
3
|
+
export function sliceHeadSafe(s, keep) {
|
|
4
|
+
if (keep <= 0)
|
|
5
|
+
return "";
|
|
6
|
+
if (s.length <= keep)
|
|
7
|
+
return s;
|
|
8
|
+
const head = s.slice(0, keep);
|
|
9
|
+
return keep > 0 && isHighSurrogate(head.charCodeAt(keep - 1)) ? head.slice(0, -1) : head;
|
|
10
|
+
}
|
|
11
|
+
export function sliceTailSafe(s, keep) {
|
|
12
|
+
if (keep <= 0)
|
|
13
|
+
return "";
|
|
14
|
+
if (s.length <= keep)
|
|
15
|
+
return s;
|
|
16
|
+
const tail = s.slice(-keep);
|
|
17
|
+
return keep > 0 && isLowSurrogate(tail.charCodeAt(0)) ? tail.slice(1) : tail;
|
|
18
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { delimitUntrusted } from "./untrusted-text.js";
|
|
2
|
-
import { assertOwnership, defaultMonitorTimers, MONITOR_MAX_TIMEOUT_MS, MONITOR_DEFAULT_TIMEOUT_MS, MONITOR_BATCH_WINDOW_MS, MONITOR_MAX_BATCHES_PER_MINUTE, MONITOR_STORM_BURST, MONITOR_STORM_KILL_AFTER_MS, MONITOR_LINE_BUF_CAP, MONITOR_SPILL_CAP_CHARS, TASK_OUTPUT_MAX_CHARS, mintCompletionId, clipMonitorEvent, clipMonitorLine, terminalTaskSummary, accountDroppedBytes, rollSpoolText, statusFromBackground, droppedGapNote, firstDropNote, alreadyTerminalStopNote, clipTaskOutput, sleepPollStep, } from "./task-registry-shared.js";
|
|
2
|
+
import { assertOwnership, defaultMonitorTimers, MONITOR_MAX_TIMEOUT_MS, MONITOR_DEFAULT_TIMEOUT_MS, MONITOR_BATCH_WINDOW_MS, MONITOR_MAX_BATCHES_PER_MINUTE, MONITOR_STORM_BURST, MONITOR_STORM_KILL_AFTER_MS, MONITOR_LINE_BUF_CAP, MONITOR_SPILL_CAP_CHARS, TASK_OUTPUT_MAX_CHARS, mintCompletionId, clipMonitorEvent, clipMonitorLine, terminalTaskSummary, accountDroppedBytes, renderSpoolBody, rollSpoolText, statusFromBackground, droppedGapNote, firstDropNote, alreadyTerminalStopNote, clipTaskOutput, sleepPollStep, } from "./task-registry-shared.js";
|
|
3
3
|
import { buildToolResultRef, OFFLOAD_TOOL_NAME } from "./tool-result-store.js";
|
|
4
4
|
export function registerMonitorLane(core, input) {
|
|
5
5
|
assertOwnership(input, "registerMonitor");
|
|
@@ -42,7 +42,13 @@ export function registerMonitorLane(core, input) {
|
|
|
42
42
|
...(input.sessionId !== undefined ? { spillSessionId: input.sessionId } : {}),
|
|
43
43
|
};
|
|
44
44
|
core.handles.set(id, handle);
|
|
45
|
-
|
|
45
|
+
try {
|
|
46
|
+
startMonitorWatcherLane(core, id);
|
|
47
|
+
}
|
|
48
|
+
catch (e) {
|
|
49
|
+
core.handles.delete(id);
|
|
50
|
+
throw e;
|
|
51
|
+
}
|
|
46
52
|
return id;
|
|
47
53
|
}
|
|
48
54
|
function spillRolledMonitorChunk(handle, stream, dropped) {
|
|
@@ -312,18 +318,13 @@ export async function pollMonitorLane(handle, filter, deadline, signal) {
|
|
|
312
318
|
const shape = (text) => clipTaskOutput(rx ? text.split(/\r?\n/).filter((l) => rx.test(l)).join("\n") : text, handle.outputFile);
|
|
313
319
|
const running = handle.status === "running";
|
|
314
320
|
const retrieval = running && deadline !== undefined && Date.now() >= deadline ? "timeout" : "success";
|
|
315
|
-
const
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
: spool.
|
|
321
|
-
|
|
322
|
-
: "";
|
|
323
|
-
const drainNote = spool.drainFailed === true ? "[!] the final output drain at stop failed — the tail of the output may be missing\n" : "";
|
|
324
|
-
const stdoutHeading = droppedNote !== "" || drainNote !== "" ? "stdout (re-readable spool — INCOMPLETE, see note above)" : "stdout (full, re-readable)";
|
|
325
|
-
const body = `status: ${displayStatus}\n${rolledNote}${droppedNote}${drainNote}--- ${stdoutHeading} ---\n${shape(spool.stdout) || "(none)"}\n` +
|
|
326
|
-
`--- stderr ---\n${shape(spool.stderr) || "(none)"}`;
|
|
321
|
+
const body = renderSpoolBody(spool, {
|
|
322
|
+
running,
|
|
323
|
+
handleStatus: handle.status,
|
|
324
|
+
shape,
|
|
325
|
+
rolledExtraClause: monitorSpillNote(handle),
|
|
326
|
+
drainNote: spool.drainFailed === true ? "[!] the final output drain at stop failed — the tail of the output may be missing\n" : "",
|
|
327
|
+
});
|
|
327
328
|
return {
|
|
328
329
|
content: delimitUntrusted(`TaskOutput ${handle.id}`, body),
|
|
329
330
|
details: {
|
|
@@ -252,6 +252,28 @@ export declare function accountDroppedBytes(spool: {
|
|
|
252
252
|
truncated?: boolean;
|
|
253
253
|
bytesDroppedBeforeCursor?: number;
|
|
254
254
|
}): void;
|
|
255
|
+
export declare function spoolDropNote(acct: {
|
|
256
|
+
droppedBytes?: number;
|
|
257
|
+
dropUnknown?: true;
|
|
258
|
+
}): string;
|
|
259
|
+
export interface RenderableSpool {
|
|
260
|
+
stdout: string;
|
|
261
|
+
stderr: string;
|
|
262
|
+
rolledChars: number;
|
|
263
|
+
exitCode?: number;
|
|
264
|
+
timedOut?: boolean;
|
|
265
|
+
timeoutSec?: number;
|
|
266
|
+
droppedBytes?: number;
|
|
267
|
+
dropUnknown?: true;
|
|
268
|
+
}
|
|
269
|
+
export interface SpoolBodyOptions {
|
|
270
|
+
running: boolean;
|
|
271
|
+
handleStatus: SemaTaskStatus;
|
|
272
|
+
shape: (text: string) => string;
|
|
273
|
+
rolledExtraClause: string;
|
|
274
|
+
drainNote: string;
|
|
275
|
+
}
|
|
276
|
+
export declare function renderSpoolBody(spool: RenderableSpool, opts: SpoolBodyOptions): string;
|
|
255
277
|
export declare function droppedGapNote(spool: {
|
|
256
278
|
droppedBytes?: number;
|
|
257
279
|
dropUnknown?: true;
|
|
@@ -102,6 +102,22 @@ export function accountDroppedBytes(spool, poll) {
|
|
|
102
102
|
else if (poll.truncated === true)
|
|
103
103
|
spool.dropUnknown = true;
|
|
104
104
|
}
|
|
105
|
+
export function spoolDropNote(acct) {
|
|
106
|
+
if ((acct.droppedBytes ?? 0) > 0)
|
|
107
|
+
return `[!] ${acct.droppedBytes} earlier byte(s) permanently dropped before this window (8MB buffer evicted)\n`;
|
|
108
|
+
if (acct.dropUnknown === true)
|
|
109
|
+
return "[!] an unknown amount of earlier output was permanently dropped before this window (buffer evicted)\n";
|
|
110
|
+
return "";
|
|
111
|
+
}
|
|
112
|
+
export function renderSpoolBody(spool, opts) {
|
|
113
|
+
const timedOutClause = spool.timedOut === true ? ` (timed out after ${spool.timeoutSec !== undefined ? `${spool.timeoutSec}s` : "its background time budget"})` : "";
|
|
114
|
+
const displayStatus = (spool.exitCode !== undefined ? `exited(code ${spool.exitCode})` : opts.running ? "running" : opts.handleStatus) + timedOutClause;
|
|
115
|
+
const rolledNote = spool.rolledChars > 0 ? `[!] ${spool.rolledChars} earlier char(s) dropped from the spool (memory bound)${opts.rolledExtraClause}\n` : "";
|
|
116
|
+
const droppedNote = spoolDropNote(spool);
|
|
117
|
+
const stdoutHeading = droppedNote !== "" || opts.drainNote !== "" ? "stdout (re-readable spool — INCOMPLETE, see note above)" : "stdout (full, re-readable)";
|
|
118
|
+
return (`status: ${displayStatus}\n${rolledNote}${droppedNote}${opts.drainNote}--- ${stdoutHeading} ---\n${opts.shape(spool.stdout) || "(none)"}\n` +
|
|
119
|
+
`--- stderr ---\n${opts.shape(spool.stderr) || "(none)"}`);
|
|
120
|
+
}
|
|
105
121
|
export function droppedGapNote(spool) {
|
|
106
122
|
if ((spool.droppedBytes ?? 0) > 0)
|
|
107
123
|
return ` [!] ${spool.droppedBytes} earlier byte(s) of output were permanently dropped before this window (execution-environment tail buffer evicted) — the event stream and this spool are INCOMPLETE.`;
|
|
@@ -69,6 +69,7 @@ export declare class TaskRegistry {
|
|
|
69
69
|
private sessionReapHooks;
|
|
70
70
|
private bgQuiescenceWatchers;
|
|
71
71
|
private legacyToTaskId;
|
|
72
|
+
private readonly notifier;
|
|
72
73
|
private readonly core;
|
|
73
74
|
durableAgentArmed(id: string): boolean;
|
|
74
75
|
durableAgentRowProbe(id: string): (() => Promise<boolean>) | undefined;
|
|
@@ -4,9 +4,10 @@ import { hasBackgroundShell } from "./background-shell.js";
|
|
|
4
4
|
import { shutdownDebug } from "./shutdown-debug.js";
|
|
5
5
|
import { canAccessAgentRecord, } from "./background-agent-store.js";
|
|
6
6
|
import { delimitUntrusted } from "./untrusted-text.js";
|
|
7
|
+
import { createSafeNotifier } from "./safe-notify.js";
|
|
7
8
|
import { registerMonitorLane, pollMonitorLane, stopMonitorLane } from "./task-registry-monitor.js";
|
|
8
9
|
import { registerWorkflowLane, pollWorkflowLane, stopWorkflowLane } from "./task-registry-workflow.js";
|
|
9
|
-
import { mintCompletionId, canAccessWorkflowRun, formatWorkflowRun, clipTaskOutput, assertOwnership, sleepPollStep, statusFromBackground, rollSpoolText, accountDroppedBytes, droppedGapNote, alreadyTerminalStopNote, terminalTaskSummary, TASK_OUTPUT_MAX_CHARS, MONITOR_BATCH_WINDOW_MS, MONITOR_DEFAULT_TIMEOUT_MS, MONITOR_MAX_TIMEOUT_MS, MONITOR_MAX_BATCHES_PER_MINUTE, canAccess, DURABLE_AGENT_HANDLE_RE, } from "./task-registry-shared.js";
|
|
10
|
+
import { mintCompletionId, canAccessWorkflowRun, formatWorkflowRun, clipTaskOutput, assertOwnership, sleepPollStep, statusFromBackground, rollSpoolText, accountDroppedBytes, renderSpoolBody, spoolDropNote, droppedGapNote, alreadyTerminalStopNote, terminalTaskSummary, TASK_OUTPUT_MAX_CHARS, MONITOR_BATCH_WINDOW_MS, MONITOR_DEFAULT_TIMEOUT_MS, MONITOR_MAX_TIMEOUT_MS, MONITOR_MAX_BATCHES_PER_MINUTE, canAccess, DURABLE_AGENT_HANDLE_RE, } from "./task-registry-shared.js";
|
|
10
11
|
export { normalizeAgentName, DURABLE_AGENT_HEARTBEAT_MS, DURABLE_AGENT_HANDLE_RE, BG_AGENT_REAP_STOP_ERROR } from "./task-registry-shared.js";
|
|
11
12
|
import { TASK_OUTPUT_TOOL_NAME, TASK_STOP_TOOL_NAME, TASK_OUTPUT_ALIASES, TASK_STOP_ALIASES, TASK_OUTPUT_CONTRACT, TASK_STOP_CONTRACT, TASK_OUTPUT_MISSING_ID_MESSAGE, TASK_STOP_MISSING_ID_MESSAGE, TASK_STOP_PARAMS, resolveTaskIdArg, REGISTRY_TASK_TOOL_CAPS, composeTaskOutputDescription, composeTaskOutputParams, composeTaskStopDescription, } from "./task-tool-shape.js";
|
|
12
13
|
import { durableAgentArmedLane, durableAgentRowProbeLane, beginDurableClaimLane, endDurableClaimLane, reapDurableAgentsLane, releaseDurableTranscriptAnchorLane, bindBackgroundAgentSessionLane, registerBackgroundAgentLane, parkBackgroundAgentLane, reconcileParkedAgentsLane, claimParkedAgentLane, rollbackParkedClaimLane, consumeParkedFlipLane, finalizeParkedResumeLane, settleBackgroundAgentLane, abortBackgroundAgentsForOwnerLane, serveDurableAgentRowLane, resolveBackgroundAgentByNameLane, markRetainedContinuationLane, reviveBackgroundAgentLane, settleRevivedAgentLane, unmarkRetainedContinuationLane, attachAgentNotifyLane, deliverToRunningAgentLane, runningBackgroundAgentLabelsLane, runningAgentFooterLane, notFoundRunningAgentsTail, pollBackgroundAgentLane, stopBackgroundAgentLane, } from "./task-registry-agent.js";
|
|
@@ -47,6 +48,31 @@ function bashTerminalResult(spool) {
|
|
|
47
48
|
const err = spool.stderr.slice(-2_000);
|
|
48
49
|
return err.length > 0 ? `${out}${out.length > 0 ? "\n" : ""}--- stderr (tail) ---\n${err}` : out;
|
|
49
50
|
}
|
|
51
|
+
async function drainBashTailBestEffort(handle) {
|
|
52
|
+
if (handle.outputFile === undefined)
|
|
53
|
+
return;
|
|
54
|
+
try {
|
|
55
|
+
const drained = await handle.env.pollBackground(handle.shellId);
|
|
56
|
+
if (drained.ok && handle.spool !== undefined)
|
|
57
|
+
accountDroppedBytes(handle.spool, drained.value);
|
|
58
|
+
if (drained.ok && (drained.value.stdout.length > 0 || drained.value.stderr.length > 0)) {
|
|
59
|
+
if (handle.spool !== undefined) {
|
|
60
|
+
const spool = handle.spool;
|
|
61
|
+
const cap = 2 * TASK_OUTPUT_MAX_CHARS;
|
|
62
|
+
spool.stdout = rollSpoolText(spool, spool.stdout + drained.value.stdout, cap);
|
|
63
|
+
spool.stderr = rollSpoolText(spool, spool.stderr + drained.value.stderr, cap);
|
|
64
|
+
const ap = await handle.env.appendFile(handle.outputFile, drained.value.stdout + drained.value.stderr);
|
|
65
|
+
if (!ap.ok)
|
|
66
|
+
spool.mirrorFailed = true;
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
await handle.env.appendFile(handle.outputFile, drained.value.stdout + drained.value.stderr);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
}
|
|
75
|
+
}
|
|
50
76
|
function bashMirrorGapNote(handle) {
|
|
51
77
|
return handle.outputFile !== undefined && handle.spool?.mirrorFailed === true
|
|
52
78
|
? " [!] The output file is INCOMPLETE (one or more writes to it failed) — trust the result text / TaskOutput over the file."
|
|
@@ -77,6 +103,7 @@ export class TaskRegistry {
|
|
|
77
103
|
sessionReapHooks = new Set();
|
|
78
104
|
bgQuiescenceWatchers = new Map();
|
|
79
105
|
legacyToTaskId = new Map();
|
|
106
|
+
notifier = createSafeNotifier();
|
|
80
107
|
core = (() => {
|
|
81
108
|
const self = this;
|
|
82
109
|
return {
|
|
@@ -407,22 +434,7 @@ export class TaskRegistry {
|
|
|
407
434
|
handle.stoppedBy = handle.stopSource ?? source;
|
|
408
435
|
handle.updatedAt = Date.now();
|
|
409
436
|
mintCompletionId(handle);
|
|
410
|
-
|
|
411
|
-
try {
|
|
412
|
-
const drained = await handle.env.pollBackground(handle.shellId);
|
|
413
|
-
if (drained.ok && (drained.value.stdout.length > 0 || drained.value.stderr.length > 0)) {
|
|
414
|
-
const spool = (handle.spool ??= { stdout: "", stderr: "", rolledChars: 0 });
|
|
415
|
-
const cap = 2 * TASK_OUTPUT_MAX_CHARS;
|
|
416
|
-
spool.stdout = rollSpoolText(spool, spool.stdout + drained.value.stdout, cap);
|
|
417
|
-
spool.stderr = rollSpoolText(spool, spool.stderr + drained.value.stderr, cap);
|
|
418
|
-
const ap = await handle.env.appendFile(handle.outputFile, drained.value.stdout + drained.value.stderr);
|
|
419
|
-
if (!ap.ok)
|
|
420
|
-
spool.mirrorFailed = true;
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
catch {
|
|
424
|
-
}
|
|
425
|
-
}
|
|
437
|
+
await drainBashTailBestEffort(handle);
|
|
426
438
|
const resultText = handle.spool !== undefined ? bashTerminalResult(handle.spool) : "";
|
|
427
439
|
this.notifyTerminalOnce(handle, () => handle.onTerminal?.({
|
|
428
440
|
task_id: handle.id,
|
|
@@ -521,21 +533,13 @@ export class TaskRegistry {
|
|
|
521
533
|
this.settleBackgroundAgent(handle.id, { status: "killed", error: "session released", cycle: handle.reviveCycle ?? 0 });
|
|
522
534
|
if (reapTerminalNote !== undefined && handle.terminalNotified !== true) {
|
|
523
535
|
handle.terminalNotified = true;
|
|
524
|
-
|
|
525
|
-
reapTerminalNote();
|
|
526
|
-
}
|
|
527
|
-
catch {
|
|
528
|
-
}
|
|
536
|
+
this.notifier.notify(() => reapTerminalNote(), "taskRegistry.reapTerminalNote");
|
|
529
537
|
}
|
|
530
538
|
reaped++;
|
|
531
539
|
}
|
|
532
540
|
this.pokeBgQuiescence(sessionId);
|
|
533
541
|
for (const hook of this.sessionReapHooks) {
|
|
534
|
-
|
|
535
|
-
hook(sessionId, scope);
|
|
536
|
-
}
|
|
537
|
-
catch {
|
|
538
|
-
}
|
|
542
|
+
this.notifier.notify(() => hook(sessionId, scope), "taskRegistry.onSessionReap");
|
|
539
543
|
}
|
|
540
544
|
return reaped;
|
|
541
545
|
}
|
|
@@ -585,11 +589,7 @@ export class TaskRegistry {
|
|
|
585
589
|
this.bgQuiescenceWatchers.delete(oldest);
|
|
586
590
|
queueMicrotask(() => {
|
|
587
591
|
for (const victim of victims) {
|
|
588
|
-
|
|
589
|
-
victim("evicted");
|
|
590
|
-
}
|
|
591
|
-
catch {
|
|
592
|
-
}
|
|
592
|
+
this.notifier.notify(() => victim("evicted"), "taskRegistry.bgQuiescence.evicted");
|
|
593
593
|
}
|
|
594
594
|
});
|
|
595
595
|
}
|
|
@@ -617,22 +617,14 @@ export class TaskRegistry {
|
|
|
617
617
|
return;
|
|
618
618
|
this.bgQuiescenceWatchers.delete(owner);
|
|
619
619
|
for (const cb of set) {
|
|
620
|
-
|
|
621
|
-
cb("quiescent");
|
|
622
|
-
}
|
|
623
|
-
catch {
|
|
624
|
-
}
|
|
620
|
+
this.notifier.notify(() => cb("quiescent"), "taskRegistry.bgQuiescence.quiescent");
|
|
625
621
|
}
|
|
626
622
|
}
|
|
627
623
|
notifyTerminalOnce(handle, send) {
|
|
628
624
|
if (handle.terminalNotified === true)
|
|
629
625
|
return;
|
|
630
626
|
handle.terminalNotified = true;
|
|
631
|
-
|
|
632
|
-
send();
|
|
633
|
-
}
|
|
634
|
-
catch {
|
|
635
|
-
}
|
|
627
|
+
this.notifier.notify(() => send(), "taskRegistry.notifyTerminalOnce");
|
|
636
628
|
this.pokeBgQuiescence(handle.owner);
|
|
637
629
|
}
|
|
638
630
|
startBashWatcher(id, onTerminal) {
|
|
@@ -1049,17 +1041,7 @@ The durable record is still marked running (last update ${new Date(row.updatedAt
|
|
|
1049
1041
|
const shape = (text) => clipTaskOutput(rx ? text.split(/\r?\n/).filter((l) => rx.test(l)).join("\n") : text, handle.outputFile);
|
|
1050
1042
|
const running = handle.status === "running";
|
|
1051
1043
|
const retrieval = running && deadline !== undefined && Date.now() >= deadline ? "timeout" : "success";
|
|
1052
|
-
const
|
|
1053
|
-
const displayStatus = (spool.exitCode !== undefined ? `exited(code ${spool.exitCode})` : running ? "running" : handle.status) + timedOutClause;
|
|
1054
|
-
const rolledNote = spool.rolledChars > 0 ? `[!] ${spool.rolledChars} earlier char(s) dropped from the spool (memory bound)\n` : "";
|
|
1055
|
-
const droppedNote = (spool.droppedBytes ?? 0) > 0
|
|
1056
|
-
? `[!] ${spool.droppedBytes} earlier byte(s) permanently dropped before this window (8MB buffer evicted)\n`
|
|
1057
|
-
: spool.dropUnknown === true
|
|
1058
|
-
? "[!] an unknown amount of earlier output was permanently dropped before this window (buffer evicted)\n"
|
|
1059
|
-
: "";
|
|
1060
|
-
const stdoutHeading = droppedNote !== "" ? "stdout (re-readable spool — INCOMPLETE, see note above)" : "stdout (full, re-readable)";
|
|
1061
|
-
const body = `status: ${displayStatus}\n${rolledNote}${droppedNote}--- ${stdoutHeading} ---\n${shape(spool.stdout) || "(none)"}\n` +
|
|
1062
|
-
`--- stderr ---\n${shape(spool.stderr) || "(none)"}`;
|
|
1044
|
+
const body = renderSpoolBody(spool, { running, handleStatus: handle.status, shape, rolledExtraClause: "", drainNote: "" });
|
|
1063
1045
|
return {
|
|
1064
1046
|
content: delimitUntrusted(`TaskOutput ${handle.id}`, body),
|
|
1065
1047
|
details: {
|
|
@@ -1089,7 +1071,7 @@ The durable record is still marked running (last update ${new Date(row.updatedAt
|
|
|
1089
1071
|
};
|
|
1090
1072
|
let stdout = "";
|
|
1091
1073
|
let stderr = "";
|
|
1092
|
-
|
|
1074
|
+
const dropAcct = {};
|
|
1093
1075
|
for (;;) {
|
|
1094
1076
|
const r = await handle.env.pollBackground(handle.shellId);
|
|
1095
1077
|
if (!r.ok) {
|
|
@@ -1107,7 +1089,7 @@ The durable record is still marked running (last update ${new Date(row.updatedAt
|
|
|
1107
1089
|
}
|
|
1108
1090
|
stdout = roll(stdout + r.value.stdout);
|
|
1109
1091
|
stderr = roll(stderr + r.value.stderr);
|
|
1110
|
-
|
|
1092
|
+
accountDroppedBytes(dropAcct, r.value);
|
|
1111
1093
|
handle.status = statusFromBackground(r.value.status, r.value.exitCode);
|
|
1112
1094
|
if (handle.status === "killed" && handle.stoppedBy === undefined)
|
|
1113
1095
|
handle.stoppedBy = handle.stopSource ?? (r.value.timedOut === true ? "timeout" : "system");
|
|
@@ -1120,7 +1102,7 @@ The durable record is still marked running (last update ${new Date(row.updatedAt
|
|
|
1120
1102
|
const shape = (text) => clipTaskOutput(rx ? text.split(/\r?\n/).filter((l) => rx.test(l)).join("\n") : text, handle.outputFile);
|
|
1121
1103
|
const timedOutClause = r.value.timedOut === true ? ` (timed out after ${r.value.timeoutSec !== undefined ? `${r.value.timeoutSec}s` : "its background time budget"})` : "";
|
|
1122
1104
|
const displayStatus = (r.value.status === "exited" ? `exited(code ${r.value.exitCode})` : r.value.status) + timedOutClause;
|
|
1123
|
-
const droppedNote = (
|
|
1105
|
+
const droppedNote = spoolDropNote(dropAcct) +
|
|
1124
1106
|
(rolledChars > 0 ? `[!] ${rolledChars} accumulated char(s) dropped from the middle during the blocked wait (memory bound)\n` : "");
|
|
1125
1107
|
const body = `status: ${displayStatus}\n${droppedNote}--- new stdout ---\n${shape(stdout) || "(none)"}\n` +
|
|
1126
1108
|
`--- new stderr ---\n${shape(stderr) || "(none)"}`;
|
|
@@ -1136,7 +1118,7 @@ The durable record is still marked running (last update ${new Date(row.updatedAt
|
|
|
1136
1118
|
details: {
|
|
1137
1119
|
status: r.value.status,
|
|
1138
1120
|
exitCode: r.value.exitCode,
|
|
1139
|
-
bytesDroppedBeforeCursor:
|
|
1121
|
+
bytesDroppedBeforeCursor: dropAcct.droppedBytes ?? 0,
|
|
1140
1122
|
...(r.value.timedOut === true ? { timedOut: true, ...(r.value.timeoutSec !== undefined ? { timeoutSec: r.value.timeoutSec } : {}) } : {}),
|
|
1141
1123
|
},
|
|
1142
1124
|
},
|
|
@@ -1205,6 +1187,7 @@ The durable record is still marked running (last update ${new Date(row.updatedAt
|
|
|
1205
1187
|
handle.status = "killed";
|
|
1206
1188
|
handle.updatedAt = Date.now();
|
|
1207
1189
|
mintCompletionId(handle);
|
|
1190
|
+
await drainBashTailBestEffort(handle);
|
|
1208
1191
|
this.notifyTerminalOnce(handle, () => explicitStopBashTerminalNote(handle, false));
|
|
1209
1192
|
return {
|
|
1210
1193
|
content: `Terminated ${handle.id}.`,
|