@sema-agent/core 2.1.0 → 2.2.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/dist/agents/observer.d.ts +14 -0
- package/dist/agents/observer.js +58 -9
- package/dist/agents/send-message-tool.js +55 -10
- package/dist/agents/subagent.d.ts +1 -0
- package/dist/agents/subagent.js +69 -15
- package/dist/core/context-edit.js +16 -3
- package/dist/core/file-snapshot-store.js +10 -1
- package/dist/core/runner/prepare-task.d.ts +1 -0
- package/dist/core/runner/prepare-task.js +29 -5
- package/dist/core/runner/runtask.js +9 -0
- package/dist/core/runner/synthetic-tools.d.ts +1 -0
- package/dist/core/runner/synthetic-tools.js +18 -15
- package/dist/core/runner/turn-attachments.d.ts +12 -2
- package/dist/core/runner/turn-attachments.js +33 -3
- package/dist/core/task-registry-agent.d.ts +9 -1
- package/dist/core/task-registry-agent.js +23 -2
- package/dist/core/task-registry-monitor.js +79 -24
- package/dist/core/task-registry-shared.d.ts +13 -1
- package/dist/core/task-registry-shared.js +21 -0
- package/dist/core/task-registry.d.ts +2 -0
- package/dist/core/task-registry.js +24 -26
- package/dist/core/types.d.ts +3 -1
- package/dist/engine/session/memory-repo.js +5 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/orchestration/workflow-size-guideline.d.ts +6 -1
- package/dist/orchestration/workflow-size-guideline.js +19 -9
- package/dist/orchestration/workflow.d.ts +1 -0
- package/dist/orchestration/workflow.js +18 -2
- package/dist/prompt-assembly/assemble.js +3 -7
- package/dist/prompt-assembly/packs/sema-default.js +8 -5
- package/dist/prompts/coordinator.d.ts +1 -1
- package/dist/prompts/coordinator.js +45 -0
- package/dist/prompts/default.d.ts +4 -5
- package/dist/prompts/default.js +16 -18
- package/dist/prompts/simple-sections.d.ts +3 -1
- package/dist/prompts/simple-sections.js +11 -1
- package/dist/stores/file/workflow-journal-store.d.ts +7 -1
- package/dist/stores/file/workflow-journal-store.js +70 -33
- package/dist/tools/fs/bash-readonly-classifier.js +20 -1
- package/dist/tools/fs/fs-bash.d.ts +1 -0
- package/dist/tools/fs/fs-bash.js +10 -3
- package/dist/tools/fs/fs-read.js +10 -10
- package/dist/tools/fs/fs-search-tools.js +42 -7
- package/dist/tools/fs/fs-write.js +18 -6
- package/dist/tools/fs/index.d.ts +1 -0
- package/dist/tools/fs/index.js +2 -1
- package/dist/tools/fs/safety.d.ts +4 -0
- package/dist/tools/fs/safety.js +102 -6
- package/dist/tools/fs/search.d.ts +1 -0
- package/dist/tools/fs/search.js +23 -3
- package/dist/tools/monitor.js +1 -1
- package/package.json +3 -2
|
@@ -26,6 +26,7 @@ import { cacheFamilyOf, usageCostMicroUsd } from "./usage-accounting.js";
|
|
|
26
26
|
import { assembleResult, errorCodeOf } from "./assemble-result.js";
|
|
27
27
|
import { ATTACHMENT_BYTE_CAP, CHANGED_FILES_MAX, AGENT_LISTING_REMOVED_HEADER, SKILLS_LISTING_DELTA_HEADER, SKILLS_LISTING_REMOVED_HEADER, advanceCadenceClock, agentListingDeltaHeader, agentListingInitialHeader, replayAnnouncedListing, replayAnnouncedModels, collectDateChange, collectDueAttachments, collectInstructionsChange, commitAgentListing, commitInstructionsChange, commitSkillsListing, createAttachmentState, rebaseCadenceWindows, reduceToolEnd, renderAgentListingDelta, renderMcpDroppedTools, renderMcpInstructionsDelta, renderOrphanedBackgroundTasks, selectMcpDroppedBatch, renderSkillsListingDelta, renderToolsDelta, stampWriteAnchor } from "./turn-attachments.js";
|
|
28
28
|
import { prepareTask } from "./prepare-task.js";
|
|
29
|
+
import { TOOL_SEARCH_NAME } from "./tool-disclosure.js";
|
|
29
30
|
import { hasVerifiableStructureSignal } from "./grounding-signal.js";
|
|
30
31
|
import { hasDestroy, isIsolated } from "../remote-env.js";
|
|
31
32
|
import { hasBackgroundShell, sweepBackgroundShells } from "../background-shell.js";
|
|
@@ -150,6 +151,8 @@ function writeFamilyOfCanonical(name) {
|
|
|
150
151
|
return "task";
|
|
151
152
|
if (name === "TodoWrite")
|
|
152
153
|
return "todo";
|
|
154
|
+
if (name === TOOL_SEARCH_NAME)
|
|
155
|
+
return "tool_search";
|
|
153
156
|
return undefined;
|
|
154
157
|
}
|
|
155
158
|
function toolResultMsg(toolCallId, toolName, text, isError) {
|
|
@@ -366,6 +369,10 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
|
|
|
366
369
|
const mcpRemovals = mcpInstructionsOn && mcpDelta.pendingRemovals.length > 0 ? [...mcpDelta.pendingRemovals] : undefined;
|
|
367
370
|
const mcpDropped = mcpInstructionsOn && prepared.mcp.droppedTools.length > 0 ? selectMcpDroppedBatch(prepared.mcp.droppedTools) : undefined;
|
|
368
371
|
const budgetUsdOn = rs.attach.attachmentsCfg?.budgetUsd === true && rs.budget.maxCostMicroUsd !== undefined && rs.turn.lastTurnHadToolCalls;
|
|
372
|
+
const toolSearchReminderOn = rs.attach.attachmentsCfg?.toolSearchReminder === true && prepared.deferredToolNames !== undefined;
|
|
373
|
+
const undiscoveredTools = toolSearchReminderOn
|
|
374
|
+
? [...prepared.deferredToolNames].filter((n) => !prepared.activeTools.has(n)).sort()
|
|
375
|
+
: undefined;
|
|
369
376
|
due.push(...collectDueAttachments(rs.attach.attachState, {
|
|
370
377
|
turn: stats.turns,
|
|
371
378
|
cadenceTurn: rs.counters.cadenceTurns,
|
|
@@ -375,6 +382,7 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
|
|
|
375
382
|
config: {
|
|
376
383
|
todoReminder: rs.attach.attachmentsCfg?.todoReminder === true,
|
|
377
384
|
...(rs.attach.attachmentsCfg?.todoReminderMode !== undefined ? { todoReminderMode: rs.attach.attachmentsCfg.todoReminderMode } : {}),
|
|
385
|
+
toolSearchReminder: toolSearchReminderOn,
|
|
378
386
|
changedFiles: changedFilesOn,
|
|
379
387
|
planModeReminder: rs.attach.attachmentsCfg?.planModeReminder === true,
|
|
380
388
|
budgetUsd: budgetUsdOn,
|
|
@@ -384,6 +392,7 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
|
|
|
384
392
|
skillsListing: rs.attach.skillsListingOn,
|
|
385
393
|
mcpInstructions: mcpInstructionsOn,
|
|
386
394
|
},
|
|
395
|
+
...(undiscoveredTools !== undefined && undiscoveredTools.length > 0 ? { undiscoveredTools } : {}),
|
|
387
396
|
...(changed !== undefined ? { changedFiles: changed } : {}),
|
|
388
397
|
...(budgetUsdOn && rs.budget.maxCostMicroUsd !== undefined
|
|
389
398
|
? { budgetUsd: { used: stats.costMicroUsd / 1e6, total: rs.budget.maxCostMicroUsd / 1e6 } }
|
|
@@ -47,6 +47,7 @@ export interface SkillListingEntry {
|
|
|
47
47
|
files?: Array<{
|
|
48
48
|
path: string;
|
|
49
49
|
}>;
|
|
50
|
+
declaredRank?: number;
|
|
50
51
|
}
|
|
51
52
|
export declare function normalizeSkills(skills: SkillLike[] | undefined): SkillLike[];
|
|
52
53
|
export declare const SKILLS_LISTING_PROBE_HEADER = "The following skills are available for this task.";
|
|
@@ -85,7 +85,7 @@ export function createReportFindingsTool() {
|
|
|
85
85
|
}
|
|
86
86
|
export const SKILL_TOOL_NAME = "Skill";
|
|
87
87
|
export const SKILL_CONTENT_MAX_CHARS = 1024 * 1024;
|
|
88
|
-
const SKILL_DESC_MAX_CHARS =
|
|
88
|
+
const SKILL_DESC_MAX_CHARS = 1_536;
|
|
89
89
|
export const SKILLS_BLOCK_MAX_BYTES = 8_000;
|
|
90
90
|
export const SKILL_ATTACHMENTS_MAX_CHARS = 50_000;
|
|
91
91
|
export const SKILL_ATTACHMENT_PATH_MAX_CHARS = 512;
|
|
@@ -121,11 +121,11 @@ export function normalizeSkills(skills) {
|
|
|
121
121
|
export const SKILLS_LISTING_PROBE_HEADER = "The following skills are available for this task.";
|
|
122
122
|
export function skillListingLine(s) {
|
|
123
123
|
const desc = s.description.length > SKILL_DESC_MAX_CHARS
|
|
124
|
-
? `${s.description.slice(0, SKILL_DESC_MAX_CHARS)}
|
|
124
|
+
? `${s.description.slice(0, SKILL_DESC_MAX_CHARS - 1)}…`
|
|
125
125
|
: s.description;
|
|
126
126
|
return `- ${s.name}: ${desc}${skillFilesSuffix(s.files)}`;
|
|
127
127
|
}
|
|
128
|
-
const SKILLS_BLOCK_CAP_MARKER = "(block size cap reached —
|
|
128
|
+
const SKILLS_BLOCK_CAP_MARKER = "(block size cap reached — lower-priority skills are listed name-only; descriptions via the tool)";
|
|
129
129
|
export function buildSkillsBlock(skills) {
|
|
130
130
|
if (skills.length === 0)
|
|
131
131
|
return undefined;
|
|
@@ -145,20 +145,23 @@ export function buildSkillsBlock(skills) {
|
|
|
145
145
|
const fullTotal = fullLines.reduce((acc, l) => acc + lineCost(l), 0);
|
|
146
146
|
if (fullTotal <= budget)
|
|
147
147
|
return render(fullLines);
|
|
148
|
-
const suffixNameCost = new Array(skills.length + 1);
|
|
149
|
-
suffixNameCost[skills.length] = 0;
|
|
150
|
-
for (let i = skills.length - 1; i >= 0; i--)
|
|
151
|
-
suffixNameCost[i] = suffixNameCost[i + 1] + lineCost(nameLines[i]);
|
|
152
148
|
const markerCost = lineCost(SKILLS_BLOCK_CAP_MARKER);
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
149
|
+
const floorCost = markerCost + nameLines.reduce((acc, l) => acc + lineCost(l), 0);
|
|
150
|
+
if (floorCost <= budget) {
|
|
151
|
+
let slack = budget - floorCost;
|
|
152
|
+
const byPriority = skills
|
|
153
|
+
.map((s, i) => ({ i, rank: s.declaredRank ?? i }))
|
|
154
|
+
.toSorted((a, b) => a.rank - b.rank || a.i - b.i);
|
|
155
|
+
const withDescription = new Set();
|
|
156
|
+
for (const { i } of byPriority) {
|
|
157
|
+
const delta = lineCost(fullLines[i]) - lineCost(nameLines[i]);
|
|
158
|
+
if (delta <= slack) {
|
|
159
|
+
withDescription.add(i);
|
|
160
|
+
slack -= delta;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return render([SKILLS_BLOCK_CAP_MARKER, ...skills.map((_s, i) => (withDescription.has(i) ? fullLines[i] : nameLines[i]))]);
|
|
159
164
|
}
|
|
160
|
-
if (bestK >= 0)
|
|
161
|
-
return render([...fullLines.slice(0, bestK), SKILLS_BLOCK_CAP_MARKER, ...nameLines.slice(bestK)]);
|
|
162
165
|
for (let m = skills.length - 1; m >= 0; m--) {
|
|
163
166
|
const overflowLine = `(… +${skills.length - m} more skills not listed — the ${SKILL_TOOL_NAME} tool serves them all)`;
|
|
164
167
|
const cost = markerCost + nameLines.slice(0, m).reduce((acc, l) => acc + lineCost(l), 0) + lineCost(overflowLine);
|
|
@@ -7,10 +7,14 @@ export declare const PLAN_MODE_ATTACHMENT_CONFIG: {
|
|
|
7
7
|
readonly TURNS_BETWEEN_ATTACHMENTS: 5;
|
|
8
8
|
readonly FULL_REMINDER_EVERY_N_ATTACHMENTS: 5;
|
|
9
9
|
};
|
|
10
|
+
export declare const TOOL_SEARCH_REMINDER_CONFIG: {
|
|
11
|
+
readonly EVERY_N_TURNS: 15;
|
|
12
|
+
readonly MAX_NAMES: 10;
|
|
13
|
+
};
|
|
10
14
|
export declare const CHANGED_FILES_MAX = 20;
|
|
11
15
|
export declare const CHANGED_FILES_MTIME_EPS_MS = 2000;
|
|
12
16
|
export declare const ATTACHMENT_BYTE_CAP: number;
|
|
13
|
-
export type AttachmentSource = "todo_reminder" | "task_reminder" | "changed_files" | "plan_mode" | "date_change" | "instructions_change" | "budget_usd" | "background_tasks" | "tools_delta" | "agent_listing" | "skills_listing" | "mcp_instructions" | "mcp_dropped_tools";
|
|
17
|
+
export type AttachmentSource = "todo_reminder" | "task_reminder" | "tool_search_usage_reminder" | "changed_files" | "plan_mode" | "date_change" | "instructions_change" | "budget_usd" | "background_tasks" | "tools_delta" | "agent_listing" | "skills_listing" | "mcp_instructions" | "mcp_dropped_tools";
|
|
14
18
|
export interface AgentListingEntry {
|
|
15
19
|
name: string;
|
|
16
20
|
description: string;
|
|
@@ -42,6 +46,8 @@ export interface AttachmentState {
|
|
|
42
46
|
todoLastWriteTurn?: number;
|
|
43
47
|
lastTodoReminderTurn: number;
|
|
44
48
|
lastTaskReminderTurn: number;
|
|
49
|
+
toolSearchLastUseTurn?: number;
|
|
50
|
+
lastToolSearchReminderTurn: number;
|
|
45
51
|
planAttachmentCount: number;
|
|
46
52
|
lastPlanReminderTurn: number;
|
|
47
53
|
surfacedMtime: Map<string, number>;
|
|
@@ -60,7 +66,7 @@ export interface InstructionsChangeState {
|
|
|
60
66
|
export declare const INSTRUCTIONS_CHANGE_BYTE_CAP = 512;
|
|
61
67
|
export declare function createAttachmentState(): AttachmentState;
|
|
62
68
|
export declare function reduceToolEnd(state: AttachmentState, details: unknown): void;
|
|
63
|
-
export type WriteFamily = "todo" | "task";
|
|
69
|
+
export type WriteFamily = "todo" | "task" | "tool_search";
|
|
64
70
|
export declare function stampWriteAnchor(state: AttachmentState, family: WriteFamily, clock: number): void;
|
|
65
71
|
export declare function advanceCadenceClock(state: AttachmentState, clock: number, content: unknown, writeFamilyOf: (toolName: string) => WriteFamily | undefined): number;
|
|
66
72
|
export declare function rebaseCadenceWindows(state: AttachmentState, clock: number): void;
|
|
@@ -77,6 +83,7 @@ export interface AttachmentInputs {
|
|
|
77
83
|
config: {
|
|
78
84
|
todoReminder: boolean;
|
|
79
85
|
todoReminderMode?: "baseline" | "off";
|
|
86
|
+
toolSearchReminder?: boolean;
|
|
80
87
|
changedFiles: boolean;
|
|
81
88
|
planModeReminder: boolean;
|
|
82
89
|
budgetUsd?: boolean;
|
|
@@ -86,6 +93,7 @@ export interface AttachmentInputs {
|
|
|
86
93
|
skillsListing?: boolean;
|
|
87
94
|
mcpInstructions?: boolean;
|
|
88
95
|
};
|
|
96
|
+
undiscoveredTools?: readonly string[];
|
|
89
97
|
changedFiles?: ReadonlyArray<{
|
|
90
98
|
path: string;
|
|
91
99
|
mtimeMs: number;
|
|
@@ -111,6 +119,7 @@ export interface AttachmentInputs {
|
|
|
111
119
|
}>;
|
|
112
120
|
}
|
|
113
121
|
export declare function collectDueAttachments(state: AttachmentState, inp: AttachmentInputs): readonly TurnAttachment[];
|
|
122
|
+
export declare function renderToolSearchUsageReminder(undiscovered: readonly string[]): string;
|
|
114
123
|
export declare function renderDateChange(newDate: string): string;
|
|
115
124
|
export declare function collectDateChange(state: DateChangeState, today: string): TurnAttachment | undefined;
|
|
116
125
|
export declare function collectInstructionsChange(state: InstructionsChangeState, probed: ReadonlyArray<{
|
|
@@ -144,6 +153,7 @@ export interface McpToolsDeltaFacts {
|
|
|
144
153
|
export declare function renderToolsDelta(input: {
|
|
145
154
|
added?: readonly string[];
|
|
146
155
|
} & McpToolsDeltaFacts): string | undefined;
|
|
156
|
+
export declare const AGENT_TOOLS_NOTE_DEFAULT = "All tools";
|
|
147
157
|
export declare const AGENT_CONCURRENCY_NOTE = "When you launch multiple agents for independent work, send them in a single message with multiple tool uses so they run concurrently.";
|
|
148
158
|
export declare const AMBIENT_CONTEXT_NOTE = "This is ambient context \u2014 do not narrate it to the user unless they ask or it is directly relevant to their request.";
|
|
149
159
|
export declare function agentListingInitialHeader(toolName: string): string;
|
|
@@ -10,6 +10,10 @@ export const PLAN_MODE_ATTACHMENT_CONFIG = {
|
|
|
10
10
|
TURNS_BETWEEN_ATTACHMENTS: 5,
|
|
11
11
|
FULL_REMINDER_EVERY_N_ATTACHMENTS: 5,
|
|
12
12
|
};
|
|
13
|
+
export const TOOL_SEARCH_REMINDER_CONFIG = {
|
|
14
|
+
EVERY_N_TURNS: 15,
|
|
15
|
+
MAX_NAMES: 10,
|
|
16
|
+
};
|
|
13
17
|
export const CHANGED_FILES_MAX = 20;
|
|
14
18
|
export const CHANGED_FILES_MTIME_EPS_MS = 2000;
|
|
15
19
|
export const ATTACHMENT_BYTE_CAP = 8 * 1024;
|
|
@@ -20,6 +24,7 @@ export function createAttachmentState() {
|
|
|
20
24
|
return {
|
|
21
25
|
lastTodoReminderTurn: 0,
|
|
22
26
|
lastTaskReminderTurn: 0,
|
|
27
|
+
lastToolSearchReminderTurn: 0,
|
|
23
28
|
planAttachmentCount: 0,
|
|
24
29
|
lastPlanReminderTurn: 0,
|
|
25
30
|
surfacedMtime: new Map(),
|
|
@@ -84,6 +89,8 @@ export function reduceToolEnd(state, details) {
|
|
|
84
89
|
export function stampWriteAnchor(state, family, clock) {
|
|
85
90
|
if (family === "task")
|
|
86
91
|
state.taskLastWriteTurn = clock;
|
|
92
|
+
else if (family === "tool_search")
|
|
93
|
+
state.toolSearchLastUseTurn = clock;
|
|
87
94
|
else
|
|
88
95
|
state.todoLastWriteTurn = clock;
|
|
89
96
|
}
|
|
@@ -109,6 +116,8 @@ export function rebaseCadenceWindows(state, clock) {
|
|
|
109
116
|
state.todoLastWriteTurn = clock;
|
|
110
117
|
state.lastTaskReminderTurn = clock;
|
|
111
118
|
state.lastTodoReminderTurn = clock;
|
|
119
|
+
state.toolSearchLastUseTurn = clock;
|
|
120
|
+
state.lastToolSearchReminderTurn = clock;
|
|
112
121
|
}
|
|
113
122
|
function countStatuses(items) {
|
|
114
123
|
const counts = {};
|
|
@@ -138,6 +147,17 @@ export function collectDueAttachments(state, inp) {
|
|
|
138
147
|
}
|
|
139
148
|
}
|
|
140
149
|
}
|
|
150
|
+
if (inp.config.toolSearchReminder &&
|
|
151
|
+
inp.undiscoveredTools !== undefined &&
|
|
152
|
+
inp.undiscoveredTools.length > 0 &&
|
|
153
|
+
!(out ?? []).some((a) => a.source === "todo_reminder" || a.source === "task_reminder")) {
|
|
154
|
+
const cadenceNow = inp.cadenceTurn ?? inp.turn;
|
|
155
|
+
const sinceUse = state.toolSearchLastUseTurn === undefined ? Number.POSITIVE_INFINITY : cadenceNow - state.toolSearchLastUseTurn;
|
|
156
|
+
if (sinceUse >= TOOL_SEARCH_REMINDER_CONFIG.EVERY_N_TURNS && cadenceNow - state.lastToolSearchReminderTurn >= TOOL_SEARCH_REMINDER_CONFIG.EVERY_N_TURNS) {
|
|
157
|
+
state.lastToolSearchReminderTurn = cadenceNow;
|
|
158
|
+
(out ??= []).push({ source: "tool_search_usage_reminder", body: renderToolSearchUsageReminder(inp.undiscoveredTools) });
|
|
159
|
+
}
|
|
160
|
+
}
|
|
141
161
|
if (inp.config.planModeReminder && inp.planActive) {
|
|
142
162
|
const due = state.planAttachmentCount === 0 ||
|
|
143
163
|
inp.turn - state.lastPlanReminderTurn >= PLAN_MODE_ATTACHMENT_CONFIG.TURNS_BETWEEN_ATTACHMENTS;
|
|
@@ -228,6 +248,15 @@ function renderTaskReminder(state) {
|
|
|
228
248
|
}
|
|
229
249
|
return message;
|
|
230
250
|
}
|
|
251
|
+
export function renderToolSearchUsageReminder(undiscovered) {
|
|
252
|
+
const shown = undiscovered.slice(0, TOOL_SEARCH_REMINDER_CONFIG.MAX_NAMES);
|
|
253
|
+
const more = undiscovered.length - shown.length;
|
|
254
|
+
const names = `${shown.join(", ")}${more > 0 ? ` (+${more} more)` : ""}`;
|
|
255
|
+
return (`Some available tools' schemas are not loaded in this conversation yet: ${names}. Before concluding a ` +
|
|
256
|
+
`capability is missing or building a workaround, use ${TOOL_SEARCH_TOOL_NAME} to find and load relevant tools — ` +
|
|
257
|
+
`keywords to search, or query "select:<name>[,<name>...]" for specific tools. Calling a tool before its schema ` +
|
|
258
|
+
`is loaded will fail. This is just a gentle reminder - ignore if not applicable to the current work.`);
|
|
259
|
+
}
|
|
231
260
|
export function renderDateChange(newDate) {
|
|
232
261
|
return `The date has changed. Today's date is now ${newDate}. DO NOT mention this to the user explicitly because they are already aware.`;
|
|
233
262
|
}
|
|
@@ -399,6 +428,7 @@ export function renderToolsDelta(input) {
|
|
|
399
428
|
}
|
|
400
429
|
return blocks.length > 0 ? blocks.join("\n\n") : undefined;
|
|
401
430
|
}
|
|
431
|
+
export const AGENT_TOOLS_NOTE_DEFAULT = "All tools";
|
|
402
432
|
export const AGENT_CONCURRENCY_NOTE = "When you launch multiple agents for independent work, send them in a single message with multiple tool uses so they run concurrently.";
|
|
403
433
|
export const AMBIENT_CONTEXT_NOTE = "This is ambient context — do not narrate it to the user unless they ask or it is directly relevant to their request.";
|
|
404
434
|
export function agentListingInitialHeader(toolName) {
|
|
@@ -451,7 +481,7 @@ export function agentListingDeltaHeader(toolName) {
|
|
|
451
481
|
export function renderAgentListingDelta(state, entries, toolName, models) {
|
|
452
482
|
const line = (e) => {
|
|
453
483
|
const base = e.description ? `- ${e.name}: ${e.description}` : `- ${e.name}`;
|
|
454
|
-
return
|
|
484
|
+
return `${base} (Tools: ${e.tools ?? AGENT_TOOLS_NOTE_DEFAULT})`;
|
|
455
485
|
};
|
|
456
486
|
const announced = state.announcedAgentTypes;
|
|
457
487
|
if (announced === undefined) {
|
|
@@ -464,8 +494,8 @@ export function renderAgentListingDelta(state, entries, toolName, models) {
|
|
|
464
494
|
blocks.push(modelsAvailableLine(models));
|
|
465
495
|
return blocks.join("\n\n");
|
|
466
496
|
}
|
|
467
|
-
const added = entries.filter((e) => !announced.has(e.name));
|
|
468
|
-
const removed = [...announced.keys()].filter((n) => !entries.some((e) => e.name === n));
|
|
497
|
+
const added = entries.filter((e) => !announced.has(e.name)).toSorted((a, b) => a.name.localeCompare(b.name));
|
|
498
|
+
const removed = [...announced.keys()].filter((n) => !entries.some((e) => e.name === n)).sort();
|
|
469
499
|
const modelsDrifted = state.announcedModels !== undefined &&
|
|
470
500
|
models !== undefined &&
|
|
471
501
|
(state.announcedModels.length !== models.length || state.announcedModels.some((m, i) => m !== models[i]));
|
|
@@ -110,6 +110,14 @@ export declare function deliverToRunningAgentLane(core: DurableAgentCore, id: st
|
|
|
110
110
|
reason: "not_found" | "not_running" | "no_channel";
|
|
111
111
|
}>;
|
|
112
112
|
export declare function runningBackgroundAgentLabelsLane(core: DurableAgentCore, access: TaskAccess): string[];
|
|
113
|
+
export declare function runningAgentFooterLane(core: DurableAgentCore, access: TaskAccess): {
|
|
114
|
+
named: string[];
|
|
115
|
+
background: string[];
|
|
116
|
+
};
|
|
117
|
+
export declare function notFoundRunningAgentsTail(footer: {
|
|
118
|
+
named: string[];
|
|
119
|
+
background: string[];
|
|
120
|
+
}): string;
|
|
113
121
|
export declare function serveDurableAgentRowLane(row: BackgroundAgentRecord): UnifiedTaskResult;
|
|
114
|
-
export declare function pollBackgroundAgentLane(handle: BackgroundAgentTaskHandle, deadline?: number, signal?: AbortSignal): Promise<UnifiedTaskResult>;
|
|
122
|
+
export declare function pollBackgroundAgentLane(handle: BackgroundAgentTaskHandle, deadline?: number, signal?: AbortSignal, oneShot?: boolean): Promise<UnifiedTaskResult>;
|
|
115
123
|
export declare function stopBackgroundAgentLane(core: DurableAgentCore, handle: BackgroundAgentTaskHandle): Promise<UnifiedTaskResult>;
|
|
@@ -972,6 +972,24 @@ export function runningBackgroundAgentLabelsLane(core, access) {
|
|
|
972
972
|
}
|
|
973
973
|
return out;
|
|
974
974
|
}
|
|
975
|
+
export function runningAgentFooterLane(core, access) {
|
|
976
|
+
const named = [];
|
|
977
|
+
const background = [];
|
|
978
|
+
for (const h of core.handles.values()) {
|
|
979
|
+
if (h.type !== "background_agent" || h.status !== "running" || !canAccess(h, access))
|
|
980
|
+
continue;
|
|
981
|
+
if (h.name !== undefined && h.name !== "") {
|
|
982
|
+
named.push(h.name);
|
|
983
|
+
continue;
|
|
984
|
+
}
|
|
985
|
+
background.push(h.description ? `${h.id} (${h.description})` : h.id);
|
|
986
|
+
}
|
|
987
|
+
return { named, background };
|
|
988
|
+
}
|
|
989
|
+
export function notFoundRunningAgentsTail(footer) {
|
|
990
|
+
return ((footer.named.length > 0 ? `. Running named agents: ${footer.named.join(", ")}` : "") +
|
|
991
|
+
(footer.background.length > 0 ? `. Running background agents: ${footer.background.join(", ")}` : ""));
|
|
992
|
+
}
|
|
975
993
|
export function serveDurableAgentRowLane(row) {
|
|
976
994
|
if (row.status === "parked") {
|
|
977
995
|
return {
|
|
@@ -1004,7 +1022,7 @@ ${clipTaskOutput(row.finalOutputFull ?? row.finalOutput)}` : "(no result text)"}
|
|
|
1004
1022
|
},
|
|
1005
1023
|
};
|
|
1006
1024
|
}
|
|
1007
|
-
export async function pollBackgroundAgentLane(handle, deadline, signal) {
|
|
1025
|
+
export async function pollBackgroundAgentLane(handle, deadline, signal, oneShot) {
|
|
1008
1026
|
while (handle.status === "running" && deadline !== undefined && Date.now() < deadline && !signal?.aborted) {
|
|
1009
1027
|
await sleepPollStep(deadline, signal);
|
|
1010
1028
|
}
|
|
@@ -1024,7 +1042,10 @@ The agent is durably suspended, waiting for an approval decision. It resumes whe
|
|
|
1024
1042
|
};
|
|
1025
1043
|
}
|
|
1026
1044
|
const body = running
|
|
1027
|
-
?
|
|
1045
|
+
? oneShot === true
|
|
1046
|
+
? `status: running
|
|
1047
|
+
This is a ONE-SHOT submission — there is no later turn for a background notification to land in, so do NOT end your turn expecting one. Actively wait instead: TaskOutput({ task_id: "${handle.id}", block: true }). If it is still running after the wait, wait again (bounded) rather than ending the turn, or write out your best available answer now if you are near your own time budget.`
|
|
1048
|
+
: `status: running
|
|
1028
1049
|
The agent is still working — you will be notified when it completes.`
|
|
1029
1050
|
: `status: ${handle.status}
|
|
1030
1051
|
${handle.error ? `error: ${handle.error}
|
|
@@ -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, TASK_OUTPUT_MAX_CHARS, mintCompletionId, clipMonitorEvent, 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, TASK_OUTPUT_MAX_CHARS, mintCompletionId, clipMonitorEvent, clipMonitorLine, terminalTaskSummary, accountDroppedBytes, rollSpoolText, statusFromBackground, droppedGapNote, firstDropNote, alreadyTerminalStopNote, clipTaskOutput, sleepPollStep, } from "./task-registry-shared.js";
|
|
3
3
|
export function registerMonitorLane(core, input) {
|
|
4
4
|
assertOwnership(input, "registerMonitor");
|
|
5
5
|
const id = core.mintTaskId("monitor");
|
|
@@ -9,6 +9,8 @@ export function registerMonitorLane(core, input) {
|
|
|
9
9
|
const timeoutMs = persistent
|
|
10
10
|
? undefined
|
|
11
11
|
: Math.min(MONITOR_MAX_TIMEOUT_MS, Math.max(1, Math.floor(input.timeoutMs ?? MONITOR_DEFAULT_TIMEOUT_MS)));
|
|
12
|
+
const maxBatchesPerMinute = Math.max(1, Math.floor(input.maxBatchesPerMinute ?? MONITOR_MAX_BATCHES_PER_MINUTE));
|
|
13
|
+
const stormBurst = Math.max(1, Math.floor(input.stormBurst ?? MONITOR_STORM_BURST));
|
|
12
14
|
const handle = {
|
|
13
15
|
id,
|
|
14
16
|
type: "monitor",
|
|
@@ -26,9 +28,12 @@ export function registerMonitorLane(core, input) {
|
|
|
26
28
|
spool: { stdout: "", stderr: "", rolledChars: 0 },
|
|
27
29
|
lineBuf: "",
|
|
28
30
|
seq: 0,
|
|
29
|
-
|
|
31
|
+
stormTokens: stormBurst,
|
|
32
|
+
stormBurst,
|
|
33
|
+
stormRefillIntervalMs: Math.max(1, Math.round(60_000 / maxBatchesPerMinute)),
|
|
34
|
+
stormRefilledAt: timers.now(),
|
|
30
35
|
batchWindowMs: Math.max(1, Math.floor(input.batchWindowMs ?? MONITOR_BATCH_WINDOW_MS)),
|
|
31
|
-
maxBatchesPerMinute
|
|
36
|
+
maxBatchesPerMinute,
|
|
32
37
|
...(timeoutMs !== undefined ? { timeoutMs, deadlineAt: timers.now() + timeoutMs } : {}),
|
|
33
38
|
timers,
|
|
34
39
|
...(input.onEvent !== undefined ? { onEvent: input.onEvent } : {}),
|
|
@@ -44,10 +49,23 @@ export function absorbMonitorPollLane(handle, v) {
|
|
|
44
49
|
spool.stdout = rollSpoolText(spool, spool.stdout + v.stdout, ROLL_CAP);
|
|
45
50
|
spool.stderr = rollSpoolText(spool, spool.stderr + v.stderr, ROLL_CAP);
|
|
46
51
|
handle.lineBuf += v.stdout;
|
|
52
|
+
if (handle.lineBuf.length > MONITOR_LINE_BUF_CAP)
|
|
53
|
+
handle.lineBuf = handle.lineBuf.slice(-MONITOR_LINE_BUF_CAP);
|
|
47
54
|
const parts = handle.lineBuf.split(/\r?\n/);
|
|
48
55
|
handle.lineBuf = parts.pop() ?? "";
|
|
49
56
|
return parts.filter((l) => l !== "");
|
|
50
57
|
}
|
|
58
|
+
function consumeStormToken(handle, now) {
|
|
59
|
+
const gained = Math.floor((now - handle.stormRefilledAt) / handle.stormRefillIntervalMs);
|
|
60
|
+
if (gained > 0) {
|
|
61
|
+
handle.stormTokens = Math.min(handle.stormBurst, handle.stormTokens + gained);
|
|
62
|
+
handle.stormRefilledAt += gained * handle.stormRefillIntervalMs;
|
|
63
|
+
}
|
|
64
|
+
if (handle.stormTokens <= 0)
|
|
65
|
+
return false;
|
|
66
|
+
handle.stormTokens -= 1;
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
51
69
|
export function emitMonitorEventLane(handle, n) {
|
|
52
70
|
try {
|
|
53
71
|
handle.onEvent?.(n);
|
|
@@ -75,7 +93,8 @@ export function startMonitorWatcherLane(core, id) {
|
|
|
75
93
|
handle.lineBuf = "";
|
|
76
94
|
return out;
|
|
77
95
|
};
|
|
78
|
-
const
|
|
96
|
+
const pendingSuppressionNote = () => consumePendingSuppressionNote(handle);
|
|
97
|
+
const terminal = (status, detail, finalLines, exitCode) => {
|
|
79
98
|
handle.updatedAt = Date.now();
|
|
80
99
|
mintCompletionId(handle);
|
|
81
100
|
stop();
|
|
@@ -85,8 +104,8 @@ export function startMonitorWatcherLane(core, id) {
|
|
|
85
104
|
...(handle.toolUseId !== undefined ? { toolUseId: handle.toolUseId } : {}),
|
|
86
105
|
status,
|
|
87
106
|
...(status === "killed" && handle.stoppedBy !== undefined ? { stoppedBy: handle.stoppedBy } : {}),
|
|
88
|
-
summary: `${(handle.description ?? "monitor").slice(0, 200)} — ${
|
|
89
|
-
...(finalLines.length > 0 ? { lines: finalLines, result: clipMonitorEvent(finalLines.join("\n")), ...(status === "killed" ? { partial: true } : {}) } : {}),
|
|
107
|
+
summary: `${terminalTaskSummary("monitor", (handle.description ?? "monitor").slice(0, 200), status, exitCode)} — ${detail}${droppedGapNote(handle.spool)}`,
|
|
108
|
+
...(finalLines.length > 0 ? { lines: finalLines.map(clipMonitorLine), result: clipMonitorEvent(finalLines.map(clipMonitorLine).join("\n")), ...(status === "killed" ? { partial: true } : {}) } : {}),
|
|
90
109
|
...(handle.completionId !== undefined ? { completionId: handle.completionId } : {}),
|
|
91
110
|
}));
|
|
92
111
|
};
|
|
@@ -107,7 +126,7 @@ export function startMonitorWatcherLane(core, id) {
|
|
|
107
126
|
if ((r.error.code === "timeout" || r.error.code === "io") && ++pollFailures <= MAX_RETRYABLE_POLL_FAILURES)
|
|
108
127
|
return;
|
|
109
128
|
handle.status = "failed";
|
|
110
|
-
return terminal("failed", `monitor lost its process (${r.error.message}); watch ended
|
|
129
|
+
return terminal("failed", `monitor lost its process (${r.error.message}); watch ended.${pendingSuppressionNote()}`, pendingFinalLines([]));
|
|
111
130
|
}
|
|
112
131
|
pollFailures = 0;
|
|
113
132
|
const spool = handle.spool;
|
|
@@ -127,7 +146,7 @@ export function startMonitorWatcherLane(core, id) {
|
|
|
127
146
|
: r.value.timedOut === true
|
|
128
147
|
? `timed out after ${r.value.timeoutSec !== undefined ? `${r.value.timeoutSec}s` : "its background time budget"} (the execution environment's background time budget); the process was killed and the watch ended.`
|
|
129
148
|
: `${r.value.status}; watch ended.`;
|
|
130
|
-
return terminal(handle.status === "completed" ? "completed" : handle.status === "killed" ? "killed" : "failed", label
|
|
149
|
+
return terminal(handle.status === "completed" ? "completed" : handle.status === "killed" ? "killed" : "failed", `${label}${pendingSuppressionNote()}`, pendingFinalLines(lines), r.value.status === "exited" ? r.value.exitCode : undefined);
|
|
131
150
|
}
|
|
132
151
|
const now = timers.now();
|
|
133
152
|
if (handle.deadlineAt !== undefined && now >= handle.deadlineAt) {
|
|
@@ -137,45 +156,74 @@ export function startMonitorWatcherLane(core, id) {
|
|
|
137
156
|
return stop();
|
|
138
157
|
if (!k.ok && k.error.code !== "not_found") {
|
|
139
158
|
handle.status = "failed";
|
|
140
|
-
return terminal("failed", `timed out after ${handle.timeoutMs}ms, but the kill failed (${k.error.message}); the process may still be running. Watch ended
|
|
159
|
+
return terminal("failed", `timed out after ${handle.timeoutMs}ms, but the kill failed (${k.error.message}); the process may still be running. Watch ended.${pendingSuppressionNote()}`, pendingFinalLines(lines));
|
|
141
160
|
}
|
|
142
161
|
handle.status = "killed";
|
|
143
162
|
handle.stoppedBy = handle.stopSource ?? "timeout";
|
|
144
|
-
return terminal("killed", `timed out after ${handle.timeoutMs}ms; the process was killed and the watch ended
|
|
163
|
+
return terminal("killed", `timed out after ${handle.timeoutMs}ms; the process was killed and the watch ended.${pendingSuppressionNote()}`, pendingFinalLines(lines));
|
|
145
164
|
}
|
|
146
165
|
if (lines.length === 0) {
|
|
147
166
|
if (r.value.stdout.length > 0 || r.value.stderr.length > 0)
|
|
148
167
|
handle.updatedAt = Date.now();
|
|
149
168
|
return;
|
|
150
169
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
170
|
+
if (!consumeStormToken(handle, now)) {
|
|
171
|
+
const previousSuppressedAt = handle.lastSuppressedAt;
|
|
172
|
+
handle.suppressedBatches = (handle.suppressedBatches ?? 0) + 1;
|
|
173
|
+
handle.lastSuppressedAt = now;
|
|
174
|
+
if (previousSuppressedAt !== undefined && now - previousSuppressedAt > handle.stormRefillIntervalMs * 3) {
|
|
175
|
+
handle.overloadSince = now;
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
handle.overloadSince ??= now;
|
|
179
|
+
}
|
|
180
|
+
handle.updatedAt = Date.now();
|
|
181
|
+
if (now - handle.overloadSince <= MONITOR_STORM_KILL_AFTER_MS)
|
|
182
|
+
return;
|
|
183
|
+
const suppressed = handle.suppressedBatches;
|
|
184
|
+
const overloadSecs = Math.round((now - handle.overloadSince) / 1000);
|
|
155
185
|
core.markStopSource(id, "system");
|
|
156
186
|
const k = await handle.env.killBackground(handle.shellId);
|
|
157
187
|
if (core.handles.get(id) !== handle || handle.status !== "running")
|
|
158
188
|
return stop();
|
|
159
189
|
if (!k.ok && k.error.code !== "not_found") {
|
|
160
190
|
handle.status = "failed";
|
|
161
|
-
return terminal("failed", `auto-stop triggered (
|
|
191
|
+
return terminal("failed", `auto-stop triggered (${suppressed} event batch(es) suppressed over ${overloadSecs}s of sustained overload), but the kill failed (${k.error.message}); the process may still be running. Watch ended.`, pendingFinalLines(lines));
|
|
162
192
|
}
|
|
163
193
|
handle.status = "killed";
|
|
164
194
|
handle.stoppedBy = handle.stopSource ?? "system";
|
|
165
|
-
return terminal("killed", `auto-stopped:
|
|
195
|
+
return terminal("killed", `auto-stopped: too much output (${suppressed} event batch(es) suppressed over ${overloadSecs}s of sustained overload; sustained budget is ${handle.maxBatchesPerMinute} batches/minute). ` +
|
|
166
196
|
`The process was killed; its full output remains readable via TaskOutput. Restart it with a quieter command (filter its output) if you still need the watch.`, pendingFinalLines(lines));
|
|
167
197
|
}
|
|
198
|
+
if ((handle.suppressedBatches ?? 0) > 0) {
|
|
199
|
+
const suppressed = handle.suppressedBatches;
|
|
200
|
+
handle.suppressedBatches = 0;
|
|
201
|
+
if (handle.lastSuppressedAt !== undefined && now - handle.lastSuppressedAt > handle.stormRefillIntervalMs * 3)
|
|
202
|
+
handle.overloadSince = undefined;
|
|
203
|
+
handle.seq += 1;
|
|
204
|
+
const note = `[${suppressed} events suppressed — output rate too high. Consider using TaskStop to restart this monitor with a more selective filter.]`;
|
|
205
|
+
emit({
|
|
206
|
+
task_id: id,
|
|
207
|
+
task_type: "monitor",
|
|
208
|
+
...(handle.toolUseId !== undefined ? { toolUseId: handle.toolUseId } : {}),
|
|
209
|
+
status: "event",
|
|
210
|
+
seq: handle.seq,
|
|
211
|
+
summary: `${(handle.description ?? "monitor").slice(0, 200)} — ${suppressed} event batch(es) suppressed (output rate too high); the suppressed output is still readable via TaskOutput`,
|
|
212
|
+
result: note,
|
|
213
|
+
});
|
|
214
|
+
}
|
|
168
215
|
handle.seq += 1;
|
|
169
216
|
handle.updatedAt = Date.now();
|
|
217
|
+
const shown = lines.map(clipMonitorLine);
|
|
170
218
|
emit({
|
|
171
219
|
task_id: id,
|
|
172
220
|
task_type: "monitor",
|
|
173
221
|
...(handle.toolUseId !== undefined ? { toolUseId: handle.toolUseId } : {}),
|
|
174
222
|
status: "event",
|
|
175
223
|
seq: handle.seq,
|
|
176
|
-
lines,
|
|
224
|
+
lines: shown,
|
|
177
225
|
summary: `${(handle.description ?? "monitor").slice(0, 200)} — ${lines.length} new output line(s)${firstDropNote(spool)}`,
|
|
178
|
-
result: clipMonitorEvent(
|
|
226
|
+
result: clipMonitorEvent(shown.join("\n")),
|
|
179
227
|
});
|
|
180
228
|
}
|
|
181
229
|
catch (e) {
|
|
@@ -184,7 +232,7 @@ export function startMonitorWatcherLane(core, id) {
|
|
|
184
232
|
if (++pollFailures <= MAX_RETRYABLE_POLL_FAILURES)
|
|
185
233
|
return;
|
|
186
234
|
handle.status = "failed";
|
|
187
|
-
terminal("failed", `monitor watcher crashed (${e instanceof Error ? e.message : String(e)}); watch ended
|
|
235
|
+
terminal("failed", `monitor watcher crashed (${e instanceof Error ? e.message : String(e)}); watch ended.${pendingSuppressionNote()}`, pendingFinalLines([]));
|
|
188
236
|
}
|
|
189
237
|
finally {
|
|
190
238
|
ticking = false;
|
|
@@ -244,6 +292,13 @@ export async function pollMonitorLane(handle, filter, deadline, signal) {
|
|
|
244
292
|
},
|
|
245
293
|
};
|
|
246
294
|
}
|
|
295
|
+
function consumePendingSuppressionNote(handle) {
|
|
296
|
+
const suppressed = handle.suppressedBatches ?? 0;
|
|
297
|
+
if (suppressed <= 0)
|
|
298
|
+
return "";
|
|
299
|
+
handle.suppressedBatches = 0;
|
|
300
|
+
return ` [${suppressed} event batch(es) suppressed before the watch ended — output rate was too high; the suppressed output is still readable via TaskOutput.]`;
|
|
301
|
+
}
|
|
247
302
|
export async function stopMonitorLane(core, handle) {
|
|
248
303
|
const wasRunning = handle.status === "running";
|
|
249
304
|
if (handle.watcher !== undefined) {
|
|
@@ -266,9 +321,9 @@ export async function stopMonitorLane(core, handle) {
|
|
|
266
321
|
...(handle.toolUseId !== undefined ? { toolUseId: handle.toolUseId } : {}),
|
|
267
322
|
status: "event",
|
|
268
323
|
seq: handle.seq,
|
|
269
|
-
lines: finalLines,
|
|
324
|
+
lines: finalLines.map(clipMonitorLine),
|
|
270
325
|
summary: `${(handle.description ?? "monitor").slice(0, 200)} — ${finalLines.length} final output line(s) drained at stop${firstDropNote(handle.spool)}`,
|
|
271
|
-
result: clipMonitorEvent(finalLines.join("\n")),
|
|
326
|
+
result: clipMonitorEvent(finalLines.map(clipMonitorLine).join("\n")),
|
|
272
327
|
});
|
|
273
328
|
}
|
|
274
329
|
}
|
|
@@ -285,7 +340,7 @@ export async function stopMonitorLane(core, handle) {
|
|
|
285
340
|
const r = await handle.env.killBackground(handle.shellId);
|
|
286
341
|
if (handle.status !== "running") {
|
|
287
342
|
return {
|
|
288
|
-
content: wasRunning ? `Terminated ${handle.id}.${drainNote}` : alreadyTerminalStopNote(handle.id, handle.status),
|
|
343
|
+
content: wasRunning ? `Terminated ${handle.id}.${drainNote}${consumePendingSuppressionNote(handle)}` : alreadyTerminalStopNote(handle.id, handle.status),
|
|
289
344
|
details: {
|
|
290
345
|
task_id: handle.id,
|
|
291
346
|
type: "monitor",
|
|
@@ -305,7 +360,7 @@ export async function stopMonitorLane(core, handle) {
|
|
|
305
360
|
mintCompletionId(handle);
|
|
306
361
|
core.pokeBgQuiescence(handle.owner);
|
|
307
362
|
return {
|
|
308
|
-
content: `Terminated ${handle.id} (the process was already gone).${drainNote}`,
|
|
363
|
+
content: `Terminated ${handle.id} (the process was already gone).${drainNote}${consumePendingSuppressionNote(handle)}`,
|
|
309
364
|
details: {
|
|
310
365
|
task_id: handle.id,
|
|
311
366
|
type: "monitor",
|
|
@@ -334,7 +389,7 @@ export async function stopMonitorLane(core, handle) {
|
|
|
334
389
|
core.pokeBgQuiescence(handle.owner);
|
|
335
390
|
}
|
|
336
391
|
return {
|
|
337
|
-
content: `Terminated ${handle.id}.${drainNote}`,
|
|
392
|
+
content: `Terminated ${handle.id}.${drainNote}${consumePendingSuppressionNote(handle)}`,
|
|
338
393
|
details: {
|
|
339
394
|
task_id: handle.id,
|
|
340
395
|
type: "monitor",
|
|
@@ -161,7 +161,13 @@ export interface MonitorTaskHandle extends SemaTaskHandle {
|
|
|
161
161
|
};
|
|
162
162
|
lineBuf: string;
|
|
163
163
|
seq: number;
|
|
164
|
-
|
|
164
|
+
stormTokens: number;
|
|
165
|
+
stormBurst: number;
|
|
166
|
+
stormRefillIntervalMs: number;
|
|
167
|
+
stormRefilledAt: number;
|
|
168
|
+
suppressedBatches?: number;
|
|
169
|
+
overloadSince?: number;
|
|
170
|
+
lastSuppressedAt?: number;
|
|
165
171
|
batchWindowMs: number;
|
|
166
172
|
maxBatchesPerMinute: number;
|
|
167
173
|
persistent?: true;
|
|
@@ -190,6 +196,7 @@ export interface RegisterMonitorInput extends TaskAccess {
|
|
|
190
196
|
timers?: MonitorTimers;
|
|
191
197
|
batchWindowMs?: number;
|
|
192
198
|
maxBatchesPerMinute?: number;
|
|
199
|
+
stormBurst?: number;
|
|
193
200
|
now?: number;
|
|
194
201
|
}
|
|
195
202
|
export interface RegisterWorkflowInput extends TaskAccess {
|
|
@@ -208,7 +215,12 @@ export declare const MONITOR_BATCH_WINDOW_MS = 200;
|
|
|
208
215
|
export declare const MONITOR_DEFAULT_TIMEOUT_MS = 300000;
|
|
209
216
|
export declare const MONITOR_MAX_TIMEOUT_MS = 3600000;
|
|
210
217
|
export declare const MONITOR_MAX_BATCHES_PER_MINUTE = 50;
|
|
218
|
+
export declare const MONITOR_STORM_BURST = 10;
|
|
219
|
+
export declare const MONITOR_STORM_KILL_AFTER_MS = 30000;
|
|
220
|
+
export declare const MONITOR_LINE_BUF_CAP = 1048576;
|
|
211
221
|
export declare function clipMonitorEvent(s: string): string;
|
|
222
|
+
export declare function clipMonitorLine(s: string): string;
|
|
223
|
+
export declare function terminalTaskSummary(kind: "bash" | "monitor", label: string, status: "completed" | "failed" | "killed", exitCode?: number): string;
|
|
212
224
|
export declare const TASK_OUTPUT_MAX_CHARS = 160000;
|
|
213
225
|
export declare function clipTaskOutput(s: string, fullOutputPath?: string): string;
|
|
214
226
|
export declare function statusFromBackground(status: string, exitCode?: number): SemaTaskStatus;
|
|
@@ -32,10 +32,31 @@ export const MONITOR_BATCH_WINDOW_MS = 200;
|
|
|
32
32
|
export const MONITOR_DEFAULT_TIMEOUT_MS = 300_000;
|
|
33
33
|
export const MONITOR_MAX_TIMEOUT_MS = 3_600_000;
|
|
34
34
|
export const MONITOR_MAX_BATCHES_PER_MINUTE = 50;
|
|
35
|
+
export const MONITOR_STORM_BURST = 10;
|
|
36
|
+
export const MONITOR_STORM_KILL_AFTER_MS = 30_000;
|
|
35
37
|
const MONITOR_EVENT_RESULT_CAP = 2_000;
|
|
38
|
+
const MONITOR_EVENT_LINE_CAP = 500;
|
|
39
|
+
export const MONITOR_LINE_BUF_CAP = 1_048_576;
|
|
36
40
|
export function clipMonitorEvent(s) {
|
|
37
41
|
return s.length <= MONITOR_EVENT_RESULT_CAP ? s : `${s.slice(0, MONITOR_EVENT_RESULT_CAP)}\n…[+${s.length - MONITOR_EVENT_RESULT_CAP} chars truncated]`;
|
|
38
42
|
}
|
|
43
|
+
export function clipMonitorLine(s) {
|
|
44
|
+
return s.length <= MONITOR_EVENT_LINE_CAP ? s : `${s.slice(0, MONITOR_EVENT_LINE_CAP)}...(truncated)`;
|
|
45
|
+
}
|
|
46
|
+
export function terminalTaskSummary(kind, label, status, exitCode) {
|
|
47
|
+
if (kind === "monitor") {
|
|
48
|
+
if (status === "completed")
|
|
49
|
+
return `Monitor "${label}" stream ended`;
|
|
50
|
+
if (status === "failed")
|
|
51
|
+
return `Monitor "${label}" script failed${exitCode !== undefined ? ` (exit ${exitCode})` : ""}`;
|
|
52
|
+
return `Monitor "${label}" stopped`;
|
|
53
|
+
}
|
|
54
|
+
if (status === "completed")
|
|
55
|
+
return `Background command "${label}" completed${exitCode !== undefined ? ` (exit code ${exitCode})` : ""}`;
|
|
56
|
+
if (status === "failed")
|
|
57
|
+
return `Background command "${label}" failed${exitCode !== undefined ? ` with exit code ${exitCode}` : ""}`;
|
|
58
|
+
return `Background command "${label}" was stopped`;
|
|
59
|
+
}
|
|
39
60
|
const TASK_OUTPUT_DEFAULT_CHARS = 32_000;
|
|
40
61
|
export const TASK_OUTPUT_MAX_CHARS = 160_000;
|
|
41
62
|
const TASK_OUTPUT_MIN_CHARS = 512;
|