@sema-agent/core 7.40.0 → 7.41.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 +218 -0
- package/dist/agents/agent-transcript-tool.d.ts +10 -2
- package/dist/agents/agent-transcript-tool.js +52 -26
- package/dist/agents/list-agents-tool.d.ts +17 -2
- package/dist/agents/list-agents-tool.js +28 -16
- package/dist/agents/peer-session-drain.d.ts +3 -1
- package/dist/agents/peer-session-drain.js +1 -0
- package/dist/agents/subagent-steps.d.ts +12 -2
- package/dist/agents/subagent-steps.js +8 -4
- package/dist/agents/subagent.d.ts +1 -17
- package/dist/agents/subagent.js +12 -24
- package/dist/core/agent-definition.d.ts +19 -40
- package/dist/core/agent-state.d.ts +91 -0
- package/dist/core/agent-state.js +79 -0
- package/dist/core/ask-origin.d.ts +28 -6
- package/dist/core/ask-origin.js +9 -5
- package/dist/core/ask-unresolvable-notice.d.ts +27 -4
- package/dist/core/ask-unresolvable-notice.js +8 -0
- package/dist/core/auto-promote.d.ts +14 -27
- package/dist/core/auto-promote.js +5 -5
- package/dist/core/checkpoint-store.d.ts +19 -20
- package/dist/core/decision-receipt.d.ts +5 -2
- package/dist/core/decision-receipt.js +1 -1
- package/dist/core/engine-notice.d.ts +7 -1
- package/dist/core/gate-crash-trace.d.ts +1 -1
- package/dist/core/gate-fold.js +7 -3
- package/dist/core/gate-lanes.js +22 -3
- package/dist/core/governance-codes.d.ts +3 -1
- package/dist/core/governance-codes.js +3 -0
- package/dist/core/hooks.d.ts +20 -6
- package/dist/core/hooks.js +28 -28
- package/dist/core/mcp-tool-meta.d.ts +24 -0
- package/dist/core/mcp-tool-meta.js +17 -0
- package/dist/core/mcp.d.ts +1 -6
- package/dist/core/mcp.js +6 -7
- package/dist/core/mode-policy.d.ts +25 -0
- package/dist/core/mode-policy.js +8 -0
- package/dist/core/protocol-table.d.ts +3 -0
- package/dist/core/protocol-table.js +3 -0
- package/dist/core/reminder-mint.d.ts +21 -1
- package/dist/core/reminder-mint.js +20 -0
- package/dist/core/retired-keys.d.ts +2 -1
- package/dist/core/retired-keys.js +13 -2
- package/dist/core/rule-denial-sentence.d.ts +2 -2
- package/dist/core/runner/call-hook-stations.d.ts +106 -0
- package/dist/core/runner/call-hook-stations.js +118 -0
- package/dist/core/runner/contracts.d.ts +35 -25
- package/dist/core/runner/gate-exit.d.ts +5 -1
- package/dist/core/runner/gate-exit.js +10 -6
- package/dist/core/runner/prepare-delegation-surface.d.ts +1 -0
- package/dist/core/runner/prepare-delegation-surface.js +4 -4
- package/dist/core/runner/prepare-gate-stations.d.ts +8 -3
- package/dist/core/runner/prepare-gate-stations.js +13 -70
- package/dist/core/runner/prepare-inherited-gate.d.ts +6 -3
- package/dist/core/runner/prepare-inherited-gate.js +5 -3
- package/dist/core/runner/prepare-park-ask.d.ts +4 -0
- package/dist/core/runner/prepare-park-ask.js +4 -1
- package/dist/core/runner/prepare-policy-chain.d.ts +6 -5
- package/dist/core/runner/prepare-policy-chain.js +14 -25
- package/dist/core/runner/prepare-project-context.d.ts +15 -16
- package/dist/core/runner/prepare-project-context.js +15 -11
- package/dist/core/runner/prepare-run-refs.d.ts +8 -8
- package/dist/core/runner/prepare-run-refs.js +3 -3
- package/dist/core/runner/prepare-safety-scan.js +3 -3
- package/dist/core/runner/prepare-suspend-saga.d.ts +2 -1
- package/dist/core/runner/prepare-suspend-saga.js +2 -1
- package/dist/core/runner/prepare-task.js +14 -12
- package/dist/core/runner/prepare-turn-wiring.d.ts +3 -1
- package/dist/core/runner/prepare-turn-wiring.js +16 -13
- package/dist/core/runner/resume-admission.js +2 -1
- package/dist/core/runner/resume-apply.js +15 -26
- package/dist/core/runner/resume-checkpoint-screen.js +3 -0
- package/dist/core/runner/resume-preflight.js +3 -3
- package/dist/core/runner/run-leg.js +61 -47
- package/dist/core/runner/runtask.d.ts +1 -1
- package/dist/core/runner/session-rule-policy.d.ts +7 -19
- package/dist/core/runner/session-rule-policy.js +1 -2
- package/dist/core/runner/skill-base-dir.d.ts +10 -0
- package/dist/core/runner/skill-base-dir.js +4 -0
- package/dist/core/runner/synthetic-tools.d.ts +11 -17
- package/dist/core/runner/synthetic-tools.js +7 -8
- package/dist/core/runner-deps.d.ts +3 -1
- package/dist/core/safety-axis-vocab.d.ts +2 -2
- package/dist/core/safety-axis-vocab.js +2 -0
- package/dist/core/session-policy-store.d.ts +63 -11
- package/dist/core/session-policy-store.js +52 -6
- package/dist/core/skill-grant.d.ts +239 -0
- package/dist/core/skill-grant.js +313 -0
- package/dist/core/skills-directory.d.ts +5 -34
- package/dist/core/skills-directory.js +9 -58
- package/dist/core/task-registry-agent.d.ts +14 -2
- package/dist/core/task-registry-agent.js +23 -8
- package/dist/core/task-registry-shared.d.ts +6 -0
- package/dist/core/task-registry.d.ts +16 -0
- package/dist/core/task-registry.js +19 -6
- package/dist/core/task-spec.d.ts +10 -0
- package/dist/core/tool-face.d.ts +3 -2
- package/dist/core/tool-name-aliases.d.ts +38 -12
- package/dist/core/tool-name-aliases.js +16 -0
- package/dist/core/tool-name-readings.d.ts +6 -2
- package/dist/core/tool-name-readings.js +6 -4
- package/dist/core/tool-policy.d.ts +2 -2
- package/dist/core/tool-registry.d.ts +1 -4
- package/dist/core/tool-registry.js +0 -3
- package/dist/core/tool-spec.d.ts +11 -13
- package/dist/core/tools.d.ts +1 -1
- package/dist/core/trace.d.ts +15 -0
- package/dist/core/types.d.ts +1 -1
- package/dist/engine/harness/agent-harness.d.ts +12 -1
- package/dist/engine/harness/agent-harness.js +17 -13
- package/dist/engine/loop/agent-loop.d.ts +16 -1
- package/dist/engine/loop/agent-loop.js +9 -15
- package/dist/engine/loop/types.d.ts +2 -0
- package/dist/index.d.ts +9 -4
- package/dist/index.js +4 -2
- package/dist/stores/file/adoption/adopt.js +23 -16
- package/dist/stores/file/index.d.ts +8 -2
- package/dist/stores/file/index.js +2 -2
- package/dist/stores/file/mailbox-store.js +2 -2
- package/dist/stores/file/permission-rule-adopt.js +10 -1
- package/dist/stores/file/permission-rule-store.d.ts +6 -4
- package/dist/stores/file/permission-rule-store.js +127 -59
- package/dist/stores/file/session-policy-store.d.ts +86 -14
- package/dist/stores/file/session-policy-store.js +135 -118
- package/dist/stores/file/task-list-store.d.ts +5 -0
- package/dist/stores/file/task-list-store.js +18 -4
- package/dist/stores/file/usage-window-store.d.ts +9 -1
- package/dist/stores/file/usage-window-store.js +47 -33
- package/dist/tools/fs/safety.d.ts +1 -1
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +45 -5
- package/wire-changes/core@7.40.1.json +39 -0
- package/wire-changes/core@7.41.0.json +1865 -0
- package/dist/core/runner/active-skill-scope.d.ts +0 -58
- package/dist/core/runner/active-skill-scope.js +0 -133
- package/dist/core/skill-tool-specifier.d.ts +0 -80
- package/dist/core/skill-tool-specifier.js +0 -57
|
@@ -3,6 +3,8 @@ import type { TaskAccess } from "../core/task-registry.js";
|
|
|
3
3
|
import { type RosterEntry } from "./roster-store.js";
|
|
4
4
|
import { type PeerDirectory, type PeerPresence, type PeerSessionRecord } from "./peer-directory.js";
|
|
5
5
|
import type { SubagentTranscriptTier } from "../core/wiring-manifest.js";
|
|
6
|
+
import { formatAgo, type AgentState, type AgentStateSource } from "../core/agent-state.js";
|
|
7
|
+
import type { CheckpointSeat } from "../core/checkpoint-seat.js";
|
|
6
8
|
export declare const LIST_AGENTS_TOOL_NAME = "ListAgents";
|
|
7
9
|
export declare const LIST_AGENTS_TOOL_ALIAS = "ListPeers";
|
|
8
10
|
/** CC `maxResultSizeChars` for this tool. */
|
|
@@ -16,8 +18,8 @@ export declare const PEER_LISTING_FRESH_WINDOW_MS: number;
|
|
|
16
18
|
* addressable by name. Dead and stale rows are what `includeAll` adds.
|
|
17
19
|
*/
|
|
18
20
|
export declare function isFreshPeerRow(r: Pick<PeerSessionRecord, "liveness" | "tempo" | "updatedAt">, now: number, windowMs?: number): boolean;
|
|
19
|
-
/** `Ns` / `Nm` / `Nh` / `Nd` — a bounded age word for a listing row
|
|
20
|
-
export
|
|
21
|
+
/** `Ns` / `Nm` / `Nh` / `Nd` — a bounded age word for a listing row (one definition, shared with the agent-state clauses). */
|
|
22
|
+
export { formatAgo };
|
|
21
23
|
/** The last two path segments of a working directory (what a person recognizes a project by). */
|
|
22
24
|
export declare function cwdTail(cwd: string): string;
|
|
23
25
|
export interface ListAgentsToolOptions {
|
|
@@ -42,6 +44,9 @@ export interface ListAgentsToolOptions {
|
|
|
42
44
|
name?: string;
|
|
43
45
|
status: string;
|
|
44
46
|
} | undefined;
|
|
47
|
+
/** The live background-agent row's state facts ({@link import("../core/task-registry.js").TaskRegistry.agentStateSource}).
|
|
48
|
+
* Absent on a registry that does not offer it ⇒ agent rows carry no `agentState` and no state columns. */
|
|
49
|
+
agentStateSource?(id: string, access: TaskAccess): AgentStateSource | undefined;
|
|
45
50
|
};
|
|
46
51
|
access?: TaskAccess;
|
|
47
52
|
/** design/147 S3a parity — a delegated child's TRUSTED parent view (owner = the parent's task id,
|
|
@@ -62,9 +67,14 @@ export interface ListAgentsToolOptions {
|
|
|
62
67
|
* the conversation does not; `none` = only the recorded name. Absent (a direct mount that did not say) ⇒
|
|
63
68
|
* the `none` wording — a label never promises more than the mount can show. */
|
|
64
69
|
continuationTier?: SubagentTranscriptTier;
|
|
70
|
+
/** The run's checkpoint seat — where a PARKED agent row's approval card is read from (the same seat the
|
|
71
|
+
* Agent tool parks children into). Absent ⇒ the card reads `unreadable`. */
|
|
72
|
+
checkpointSeat?: CheckpointSeat;
|
|
65
73
|
/** The clock (tests). */
|
|
66
74
|
now?: () => number;
|
|
67
75
|
}
|
|
76
|
+
/** The store seats the listing reads agent rows' facts through, as one bundle a mount helper passes on unchanged. */
|
|
77
|
+
export type ListAgentsReadSeats = Pick<ListAgentsToolOptions, "checkpointSeat">;
|
|
68
78
|
/** One rendered row. `kind` names the lane; `address` is what SendMessage accepts. */
|
|
69
79
|
export interface ListAgentsRow {
|
|
70
80
|
kind: "peer_session" | "background_agent" | "roster";
|
|
@@ -88,6 +98,11 @@ export interface ListAgentsRow {
|
|
|
88
98
|
* — on the peer lane the common case, since no host writes the activity word. `finished` means
|
|
89
99
|
* COMPLETED and nothing else: a model applies the continuable rule to that word. */
|
|
90
100
|
state: "busy" | "idle" | "offline" | "running" | "parked" | "finished" | "failed" | "killed" | "cancelled" | "unknown";
|
|
101
|
+
/** Agent lanes with a live registry row: the agent's current state
|
|
102
|
+
* ({@link import("../core/agent-state.js").readAgentState}, the one reading AgentTranscript and TaskOutput
|
|
103
|
+
* render too); {@link state} is this status word through the lane table. Absent on peer rows and on a
|
|
104
|
+
* roster row with no live row. */
|
|
105
|
+
agentState?: AgentState;
|
|
91
106
|
}
|
|
92
107
|
export declare function createListAgentsTool(opts: ListAgentsToolOptions): import("../internal/harness-types.js").AgentTool<Type.TObject<{
|
|
93
108
|
includeAll: Type.TOptional<Type.TBoolean>;
|
|
@@ -8,6 +8,7 @@ import { PEER_LANE_DEFAULTS } from "./peer-admission.js";
|
|
|
8
8
|
import { toolFace } from "../core/tool-catalog-entries.js";
|
|
9
9
|
import { thrownText } from "../core/thrown-value.js";
|
|
10
10
|
import { assertNoRetiredKeys } from "../core/retired-keys.js";
|
|
11
|
+
import { agentActivityClause, agentApprovalClause, formatAgo, readAgentState } from "../core/agent-state.js";
|
|
11
12
|
export const LIST_AGENTS_TOOL_NAME = "ListAgents";
|
|
12
13
|
export const LIST_AGENTS_TOOL_ALIAS = "ListPeers";
|
|
13
14
|
export const LIST_AGENTS_MAX_RESULT_CHARS = 10_000;
|
|
@@ -19,18 +20,7 @@ export function isFreshPeerRow(r, now, windowMs = PEER_LISTING_FRESH_WINDOW_MS)
|
|
|
19
20
|
return true;
|
|
20
21
|
return now - r.updatedAt <= windowMs;
|
|
21
22
|
}
|
|
22
|
-
export
|
|
23
|
-
const sec = Math.max(0, Math.floor(ms / 1000));
|
|
24
|
-
if (sec < 60)
|
|
25
|
-
return `${sec}s`;
|
|
26
|
-
const min = Math.floor(sec / 60);
|
|
27
|
-
if (min < 60)
|
|
28
|
-
return `${min}m`;
|
|
29
|
-
const h = Math.floor(min / 60);
|
|
30
|
-
if (h < 48)
|
|
31
|
-
return `${h}h`;
|
|
32
|
-
return `${Math.floor(h / 24)}d`;
|
|
33
|
-
}
|
|
23
|
+
export { formatAgo };
|
|
34
24
|
export function cwdTail(cwd) {
|
|
35
25
|
const parts = cwd.split(/[\\/]+/).filter((p) => p !== "");
|
|
36
26
|
return parts.slice(-2).join("/");
|
|
@@ -80,7 +70,7 @@ export function createListAgentsTool(opts) {
|
|
|
80
70
|
`pass includeAll: true to also list stale and offline ones. A peer that is offline or unlisted still accepts messages — they are parked ` +
|
|
81
71
|
`durably in its session box and delivered when it next runs. `
|
|
82
72
|
: `Peer sessions of this engine are not addressable on this mount (no session directory). `) +
|
|
83
|
-
`Background agents you spawned are listed with their task_id. ` +
|
|
73
|
+
`Background agents you spawned are listed with their task_id, their status, when they last did anything, and — for one parked on a durable approval — that approval's card id, state and deadline. ` +
|
|
84
74
|
`A ref is only valid against the listing (or disambiguation error) you read it from — do not invent one.`,
|
|
85
75
|
parameters: Type.Object({
|
|
86
76
|
includeAll: Type.Optional(Type.Boolean({ description: "Also list stale (no activity within the freshness window) and offline peer sessions. Default false." })),
|
|
@@ -120,6 +110,10 @@ export function createListAgentsTool(opts) {
|
|
|
120
110
|
});
|
|
121
111
|
}
|
|
122
112
|
let registryCapped = false;
|
|
113
|
+
const agentStateOf = (id, view) => {
|
|
114
|
+
const src = opts.registry?.agentStateSource?.(id, view);
|
|
115
|
+
return src === undefined ? undefined : readAgentState(src, opts.checkpointSeat, opts.now?.() ?? Date.now());
|
|
116
|
+
};
|
|
123
117
|
const views = [opts.access, opts.parentAccess].filter((a) => a !== undefined);
|
|
124
118
|
const seenIds = new Set();
|
|
125
119
|
if (opts.registry !== undefined) {
|
|
@@ -131,7 +125,15 @@ export function createListAgentsTool(opts) {
|
|
|
131
125
|
continue;
|
|
132
126
|
seenIds.add(t.task_id);
|
|
133
127
|
const full = opts.registry.getAccessibleTask(t.task_id, view);
|
|
134
|
-
|
|
128
|
+
const stateRead = agentStateOf(t.task_id, view);
|
|
129
|
+
const agentState = stateRead instanceof Promise ? await stateRead : stateRead;
|
|
130
|
+
rows.push({
|
|
131
|
+
kind: "background_agent",
|
|
132
|
+
name: full?.name ?? t.description ?? t.task_id,
|
|
133
|
+
address: t.task_id,
|
|
134
|
+
state: agentRowState(agentState?.status ?? t.status),
|
|
135
|
+
...(agentState !== undefined ? { agentState } : {}),
|
|
136
|
+
});
|
|
135
137
|
}
|
|
136
138
|
}
|
|
137
139
|
}
|
|
@@ -143,7 +145,15 @@ export function createListAgentsTool(opts) {
|
|
|
143
145
|
continue;
|
|
144
146
|
seenIds.add(e.agentId);
|
|
145
147
|
const live = opts.registry?.getAccessibleTask(e.agentId, view);
|
|
146
|
-
|
|
148
|
+
const stateRead = live === undefined ? undefined : agentStateOf(e.agentId, view);
|
|
149
|
+
const agentState = stateRead instanceof Promise ? await stateRead : stateRead;
|
|
150
|
+
rows.push({
|
|
151
|
+
kind: "roster",
|
|
152
|
+
name: e.name,
|
|
153
|
+
address: e.agentId,
|
|
154
|
+
state: live === undefined ? "unknown" : agentRowState(agentState?.status ?? live.status),
|
|
155
|
+
...(agentState !== undefined ? { agentState } : {}),
|
|
156
|
+
});
|
|
147
157
|
}
|
|
148
158
|
}
|
|
149
159
|
catch {
|
|
@@ -159,7 +169,9 @@ export function createListAgentsTool(opts) {
|
|
|
159
169
|
const lines = rows.map((r) => {
|
|
160
170
|
const label = r.kind === "peer_session" ? formatPeerNameRef(inlineUntrusted(r.name, 64), r.ref) : inlineUntrusted(r.name, 64);
|
|
161
171
|
const age = r.kind !== "peer_session" ? undefined : r.lastActiveAt !== undefined ? `active ${formatAgo(now - r.lastActiveAt)} ago` : r.offlineAt !== undefined ? `offline ${formatAgo(now - r.offlineAt)} ago` : "offline";
|
|
162
|
-
const
|
|
172
|
+
const activity = r.agentState !== undefined ? agentActivityClause(r.agentState) : undefined;
|
|
173
|
+
const approval = r.agentState?.approval !== undefined ? agentApprovalClause(r.agentState.approval, r.agentState.readAt) : undefined;
|
|
174
|
+
const cols = [label, r.state, age, activity, approval, r.cwd !== undefined ? inlineUntrusted(r.cwd, 64) : undefined, r.presence !== undefined ? renderPresence(r.presence) : undefined, r.kind === "peer_session" ? "peer session" : r.kind === "roster" ? rosterLabel : "agent", r.address];
|
|
163
175
|
return cols.filter((c) => c !== undefined).join(" · ");
|
|
164
176
|
});
|
|
165
177
|
const finishedCount = rows.filter((r) => r.state === "finished").length;
|
|
@@ -6,7 +6,7 @@ import { type PermissionModeClass } from "./cross-session-envelope.js";
|
|
|
6
6
|
import { type CrossSessionInboundSettingLayers } from "./cross-session-judge.js";
|
|
7
7
|
import { type PeerLaneConfig } from "./peer-admission.js";
|
|
8
8
|
import { type PeerDirectory } from "./peer-directory.js";
|
|
9
|
-
import { createListAgentsTool } from "./list-agents-tool.js";
|
|
9
|
+
import { createListAgentsTool, type ListAgentsReadSeats } from "./list-agents-tool.js";
|
|
10
10
|
import { foldPermissionModeClass } from "./cross-session-judge.js";
|
|
11
11
|
import type { AskEffective } from "../core/wiring-manifest.js";
|
|
12
12
|
import type { SendMessageToolOptions } from "./send-message-tool.js";
|
|
@@ -248,6 +248,8 @@ export declare function mountListAgents(args: {
|
|
|
248
248
|
aliases?: readonly string[];
|
|
249
249
|
}>;
|
|
250
250
|
toolEffects: Map<string, "read" | "write" | "idempotent">;
|
|
251
|
+
/** The read faces' store seats, handed to the tool unchanged (see {@link ListAgentsReadSeats}). */
|
|
252
|
+
readSeats?: ListAgentsReadSeats;
|
|
251
253
|
}): ReturnType<typeof createListAgentsTool> | undefined;
|
|
252
254
|
type ListAgentsToolRegistry = NonNullable<Parameters<typeof createListAgentsTool>[0]["registry"]>;
|
|
253
255
|
/** The harness surface the binding needs (structural — the machine itself never sees the harness). */
|
|
@@ -702,6 +702,7 @@ export function mountListAgents(args) {
|
|
|
702
702
|
...(args.parentTaskId !== undefined ? { parentAccess: { owner: args.parentTaskId, scope: args.scope, ...(args.parentSessionId !== undefined ? { sessionId: args.parentSessionId } : {}) } } : {}),
|
|
703
703
|
...(args.roster !== undefined ? { roster: args.roster } : {}),
|
|
704
704
|
...(args.continuationTier !== undefined ? { continuationTier: args.continuationTier } : {}),
|
|
705
|
+
...(args.readSeats ?? {}),
|
|
705
706
|
});
|
|
706
707
|
}
|
|
707
708
|
export function bindPeerSessionDrain(harness, opts) {
|
|
@@ -31,7 +31,8 @@ export interface SubagentStep {
|
|
|
31
31
|
tool: string;
|
|
32
32
|
/** The step's primary argument, one line: file path / command / pattern. May be "". */
|
|
33
33
|
target: string;
|
|
34
|
-
/** Result first line — or `error: …` when the tool failed. May be ""
|
|
34
|
+
/** Result first line — or `error: …` when the tool failed. May be "" (a result that was only an engine
|
|
35
|
+
* reminder block has no line of its own). The engine's marked reminder blocks are not part of a result. */
|
|
35
36
|
outcome: string;
|
|
36
37
|
}
|
|
37
38
|
/** A file the child mutated, with how many times (Write/Edit/MultiEdit/NotebookEdit). */
|
|
@@ -104,10 +105,19 @@ export declare class SubagentStepRecorder {
|
|
|
104
105
|
target?: string;
|
|
105
106
|
} | undefined;
|
|
106
107
|
}
|
|
108
|
+
/** One step read back from a session's persisted messages: a {@link SubagentStep} plus WHEN. */
|
|
109
|
+
export interface TranscriptStep extends SubagentStep {
|
|
110
|
+
/** Epoch ms: the result message's timestamp when the call has a result, the calling message's when it
|
|
111
|
+
* has none. Absent when that message carries no finite timestamp. */
|
|
112
|
+
at?: number;
|
|
113
|
+
/** Present (`true`) exactly when the transcript holds the call but no result for it — the call is still
|
|
114
|
+
* in flight, or the run stopped before its result was recorded. */
|
|
115
|
+
inFlight?: true;
|
|
116
|
+
}
|
|
107
117
|
/**
|
|
108
118
|
* Residual lane C — derive the last `lastN` tool steps from a session's PERSISTED messages (the pull-model
|
|
109
119
|
* `AgentTranscript` read face). Mirrors {@link SubagentStepRecorder} but sources from committed history
|
|
110
120
|
* instead of the live event stream, so a parent can retrospectively read a child's tail on demand. Walks
|
|
111
121
|
* assistant `toolCall`s paired with the following `toolResult`, oldest→newest, then keeps the last N.
|
|
112
122
|
*/
|
|
113
|
-
export declare function stepsFromMessages(messages: readonly unknown[], lastN: number):
|
|
123
|
+
export declare function stepsFromMessages(messages: readonly unknown[], lastN: number): TranscriptStep[];
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { stripMarkedReminderBlocks } from "../core/reminder-mint.js";
|
|
1
2
|
import { redactSecrets } from "../core/untrusted-egress.js";
|
|
2
3
|
import { isProtectedWrite } from "../core/tool-registry.js";
|
|
3
4
|
import { writeTargetPath } from "../tools/fs/safety.js";
|
|
@@ -32,7 +33,7 @@ export function redactThenCut(s, max) {
|
|
|
32
33
|
return cutAt(redactSecrets(s), max);
|
|
33
34
|
}
|
|
34
35
|
function clip(s) {
|
|
35
|
-
return redactThenCut(firstLine(s).trim(), FIELD_MAX);
|
|
36
|
+
return redactThenCut(firstLine(stripMarkedReminderBlocks(s)).trim(), FIELD_MAX);
|
|
36
37
|
}
|
|
37
38
|
export function extractTarget(args) {
|
|
38
39
|
if (args === null || typeof args !== "object")
|
|
@@ -51,7 +52,7 @@ export function extractTarget(args) {
|
|
|
51
52
|
if (primary !== undefined)
|
|
52
53
|
return clip(primary);
|
|
53
54
|
try {
|
|
54
|
-
return clip(JSON.stringify(a));
|
|
55
|
+
return clip(JSON.stringify(a, (_key, v) => (typeof v === "string" ? stripMarkedReminderBlocks(v) : v)));
|
|
55
56
|
}
|
|
56
57
|
catch {
|
|
57
58
|
return "";
|
|
@@ -162,12 +163,13 @@ export class SubagentStepRecorder {
|
|
|
162
163
|
return this.actionTool === undefined ? undefined : { toolName: this.actionTool, ...(this.actionTarget !== undefined ? { target: this.actionTarget } : {}) };
|
|
163
164
|
}
|
|
164
165
|
}
|
|
166
|
+
const stampOf = (m) => (typeof m.timestamp === "number" && Number.isFinite(m.timestamp) ? m.timestamp : undefined);
|
|
165
167
|
export function stepsFromMessages(messages, lastN) {
|
|
166
168
|
const results = new Map();
|
|
167
169
|
for (const m of messages) {
|
|
168
170
|
const msg = m;
|
|
169
171
|
if (msg.role === "toolResult" && typeof msg.toolCallId === "string") {
|
|
170
|
-
results.set(msg.toolCallId, { isError: msg.isError === true, body: outputFirstLineFromContent(msg.content) });
|
|
172
|
+
results.set(msg.toolCallId, { isError: msg.isError === true, body: outputFirstLineFromContent(msg.content), at: stampOf(msg) });
|
|
171
173
|
}
|
|
172
174
|
}
|
|
173
175
|
const steps = [];
|
|
@@ -175,13 +177,15 @@ export function stepsFromMessages(messages, lastN) {
|
|
|
175
177
|
const msg = m;
|
|
176
178
|
if (msg.role !== "assistant" || !Array.isArray(msg.content))
|
|
177
179
|
continue;
|
|
180
|
+
const calledAt = stampOf(msg);
|
|
178
181
|
for (const block of msg.content) {
|
|
179
182
|
const b = block;
|
|
180
183
|
if (b.type !== "toolCall" || typeof b.name !== "string")
|
|
181
184
|
continue;
|
|
182
185
|
const res = typeof b.id === "string" ? results.get(b.id) : undefined;
|
|
183
186
|
const outcome = res ? (res.isError ? cutAt(`error: ${res.body}`, FIELD_MAX) : res.body) : "";
|
|
184
|
-
|
|
187
|
+
const at = res ? res.at : calledAt;
|
|
188
|
+
steps.push({ tool: b.name, target: extractTarget(b.arguments), outcome, ...(at !== undefined ? { at } : {}), ...(res === undefined ? { inFlight: true } : {}) });
|
|
185
189
|
}
|
|
186
190
|
}
|
|
187
191
|
const cap = Math.max(0, Math.min(lastN, steps.length));
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { RunnerSelfSeat } from "../core/runner/contracts.js";
|
|
2
2
|
import type { AgentDefinition, Model, ModelRef, TaskLimits, TerminalCause, ToolSpec } from "../core/types.js";
|
|
3
3
|
export { childThinkingSeat, deploymentSubagentThinking, deploymentSubagentTierModel } from "./child-model-seat.js";
|
|
4
4
|
import { type ExecutionEnv } from "../internal/harness.js";
|
|
@@ -74,22 +74,6 @@ interface ReviewSample {
|
|
|
74
74
|
/** Layered head/middle/tail sample of `text` under `budget`, with each skipped span marked in band so the
|
|
75
75
|
* reader can see that the material is discontinuous. Exported for tests (window determinism). */
|
|
76
76
|
export declare function layeredReviewSample(text: string, budget?: number, windows?: number): ReviewSample;
|
|
77
|
-
/**
|
|
78
|
-
* 🔴 design/77 §3 / §7 — skill→subagent manifest-scope PROPAGATION (fail-closed). Given the parent task's
|
|
79
|
-
* live skill-manifest snapshot (from `ctx.activeSkillScope()`), build the inherited-scope frames to thread
|
|
80
|
-
* into a spawned child via the trusted {@link RunInternals.inheritedManifestScope} channel, so the child's
|
|
81
|
-
* tool gate inherits the parent skill's deny-narrowing (a child of a manifested skill is at most as capable
|
|
82
|
-
* as the manifest — MONOTONIC).
|
|
83
|
-
*
|
|
84
|
-
* - Parent has NO active manifest (empty/absent snapshot) → return `undefined` (no inheritance; the child
|
|
85
|
-
* behaves exactly as today, backward-compatible).
|
|
86
|
-
* - Parent HAS active manifest frames → return them verbatim (the child seeds them as the LIFO base; its
|
|
87
|
-
* own skill loads can only intersect/narrow further).
|
|
88
|
-
* - Fail-closed: the snapshot is non-empty but its shape is not a recognizable frame array (a future/older
|
|
89
|
-
* Runner, a tampered accessor) → return a SINGLE DENY-ALL `unresolved` frame, so the child runs under a
|
|
90
|
-
* deny-all inherited scope rather than UNMANIFESTED. The safe path is the default.
|
|
91
|
-
*/
|
|
92
|
-
export declare function inheritedManifestScopeFor(snapshot: readonly unknown[] | undefined): RunInternals["inheritedManifestScope"];
|
|
93
77
|
/** Default model-facing name of the delegation tool (CC 2.1.187 `Agent`; legacy alias `Task`). */
|
|
94
78
|
export declare const DEFAULT_SUBAGENT_TOOL_NAME = "Agent";
|
|
95
79
|
/**
|
package/dist/agents/subagent.js
CHANGED
|
@@ -31,6 +31,7 @@ import { shellQuote } from "../tools/fs/search.js";
|
|
|
31
31
|
import { BG_AGENT_REAP_STOP_ERROR } from "../core/task-registry.js";
|
|
32
32
|
import { readDurableOrgAdmission } from "../core/memory-admission.js";
|
|
33
33
|
import { governanceRetryClass } from "../core/governance-codes.js";
|
|
34
|
+
import { readSkillGrantCapture } from "../core/skill-grant.js";
|
|
34
35
|
import { terminalFrameFacets } from "../core/task-notification.js";
|
|
35
36
|
import { RETAIN_DEFAULT_TTL_MS, RETAIN_DEFAULT_MAX, RUNNING_AGENT_OBSERVE_EVERY_BEATS, DELEGATION_MAX_CONCURRENT_DEFAULT, DELEGATION_MAX_PER_SESSION_DEFAULT } from "../config/defaults.js";
|
|
36
37
|
function rollupDelegatedCost(stats, nested) {
|
|
@@ -187,28 +188,6 @@ function withHandbackWarning(warning, result) {
|
|
|
187
188
|
return result;
|
|
188
189
|
return result ? `${warning}\n\n${result}` : warning;
|
|
189
190
|
}
|
|
190
|
-
export function inheritedManifestScopeFor(snapshot) {
|
|
191
|
-
if (!snapshot || snapshot.length === 0)
|
|
192
|
-
return undefined;
|
|
193
|
-
const frames = [];
|
|
194
|
-
for (const f of snapshot) {
|
|
195
|
-
if (f && typeof f === "object" && "kind" in f) {
|
|
196
|
-
const kind = f.kind;
|
|
197
|
-
if (kind === "manifest" || kind === "unresolved") {
|
|
198
|
-
frames.push(f);
|
|
199
|
-
continue;
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
return [
|
|
203
|
-
{
|
|
204
|
-
kind: "unresolved",
|
|
205
|
-
lineageId: "inherited-parent-manifest",
|
|
206
|
-
reason: "parent skill manifest was active at spawn but its scope could not be propagated to the subagent — fail-closed deny-all",
|
|
207
|
-
},
|
|
208
|
-
];
|
|
209
|
-
}
|
|
210
|
-
return frames;
|
|
211
|
-
}
|
|
212
191
|
export const DEFAULT_SUBAGENT_TOOL_NAME = "Agent";
|
|
213
192
|
export const EXTRA_TOOLS_MAX_FACTORY_CALLS_PER_TREE = 64;
|
|
214
193
|
export const DEFAULT_SUBAGENT_MAX_DEPTH = 3;
|
|
@@ -1724,6 +1703,14 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
1724
1703
|
})()
|
|
1725
1704
|
: seatModel?.id;
|
|
1726
1705
|
const rowModelKey = perCallModel !== undefined ? requestedModel ?? perCallModel.id : typeof seatModel === "string" ? seatModel : seatModel?.id;
|
|
1706
|
+
const inheritedSkillGrants = readSkillGrantCapture(ctx.activeSkillGrants?.());
|
|
1707
|
+
if (inheritedSkillGrants === undefined) {
|
|
1708
|
+
return {
|
|
1709
|
+
isError: true,
|
|
1710
|
+
content: "Sub-agent not started: the parent's skill grants could not be read for the child (an unrecognizable capture); a child started without them would lose the parent's disallowed-tools refusals.",
|
|
1711
|
+
details: { error: "skill_grants.unreadable" },
|
|
1712
|
+
};
|
|
1713
|
+
}
|
|
1727
1714
|
let worktreeDir;
|
|
1728
1715
|
if (reviveClaim === undefined && (a.isolation ?? def?.isolation) === "worktree") {
|
|
1729
1716
|
const iso = ctx.worktreeIsolation;
|
|
@@ -1844,7 +1831,6 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
1844
1831
|
if (depth + 1 < maxDepth && defAllowsDelegation) {
|
|
1845
1832
|
childTools.push(createSubagentToolNode(opts, depth + 1, nextExcluded, treeBudget));
|
|
1846
1833
|
}
|
|
1847
|
-
const inheritedManifestScope = inheritedManifestScopeFor(ctx.activeSkillScope?.());
|
|
1848
1834
|
const childAgentName = (typeof a.description === "string" && a.description.trim() ? a.description.trim() : undefined) ?? def?.name;
|
|
1849
1835
|
const observerArm = ctx.observersAllowed === true && def !== undefined
|
|
1850
1836
|
? resolveObserverDeclaration({
|
|
@@ -2100,7 +2086,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2100
2086
|
: undefined;
|
|
2101
2087
|
const childInternals = {
|
|
2102
2088
|
...(childPick.modelRef !== undefined ? { inheritedModelRef: childPick.modelRef } : {}),
|
|
2103
|
-
...(
|
|
2089
|
+
...(inheritedSkillGrants.length > 0 ? { inheritedSkillGrants } : {}),
|
|
2104
2090
|
...(childProvenanceRef !== undefined && provenanceRequest !== undefined ? { delegationProvenance: { ref: childProvenanceRef, contentSafety: provenanceRequest } } : {}),
|
|
2105
2091
|
...(treeScope !== undefined ? { registryScope: treeScope } : {}),
|
|
2106
2092
|
...(ctx.inheritedGateForChildren ? { inheritedGate: ctx.inheritedGateForChildren() } : {}),
|
|
@@ -2668,6 +2654,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2668
2654
|
}
|
|
2669
2655
|
catch {
|
|
2670
2656
|
}
|
|
2657
|
+
bgForkActivityBeat(e);
|
|
2671
2658
|
},
|
|
2672
2659
|
};
|
|
2673
2660
|
void opts.runner
|
|
@@ -3305,6 +3292,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
3305
3292
|
}
|
|
3306
3293
|
catch {
|
|
3307
3294
|
}
|
|
3295
|
+
bgActivityBeat(e);
|
|
3308
3296
|
},
|
|
3309
3297
|
};
|
|
3310
3298
|
const driveParkedResume = async () => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* A delegated AGENT's definition and the skills it may mount: `AgentDefinition` (the agent a subagent
|
|
3
|
-
* tool spawns)
|
|
4
|
-
*
|
|
3
|
+
* tool spawns) and `SkillSpec` (the wired form the runner mounts). Layer 0 vocabulary; `types.ts`
|
|
4
|
+
* re-exports the two, so no consumer's import changes.
|
|
5
5
|
*/
|
|
6
6
|
import type { ThinkingLevel } from "../internal/harness.js";
|
|
7
7
|
import type { ModelRef } from "./model-seat.js";
|
|
@@ -101,49 +101,28 @@ export interface AgentDefinition {
|
|
|
101
101
|
*/
|
|
102
102
|
observerMessage?: string;
|
|
103
103
|
}
|
|
104
|
-
/**
|
|
105
|
-
* A per-skill runtime capability manifest (design/77 §3 Gate-3). When present, loading the skill
|
|
106
|
-
* SCOPES the rest of the invocation to ONLY these tools/paths via runtime deny-narrowing: a tool call
|
|
107
|
-
* not in `allowTools` (intersected across nested skill frames) is DENIED, and a write whose canonical
|
|
108
|
-
* target falls outside `allowPaths` is DENIED — regardless of how permissive the task policy is.
|
|
109
|
-
*
|
|
110
|
-
* 🔴 **Safe-by-construction:** a manifest can only SUBTRACT capability. The narrowing composes via the
|
|
111
|
-
* `deny > ask > allow` fold (`combinePolicies`) where `deny` short-circuits, so a manifest can never
|
|
112
|
-
* *grant* a tool the task policy withheld — whoever sets a manifest can only restrict further.
|
|
113
|
-
* Worst case (the model never pops the frame) = no narrowing, never widening — still ⊆ task policy.
|
|
114
|
-
*
|
|
115
|
-
* The manifest is CALLER-provided here (same trust level as the rest of `SkillSpec`); deriving it
|
|
116
|
-
* automatically (the auto-promote pipeline) is a separate, deferred concern (design/77 §5).
|
|
117
|
-
*/
|
|
118
|
-
export interface SkillManifest {
|
|
119
|
-
/** The ONLY tool names the invocation may call while this skill's frame is active. */
|
|
120
|
-
allowTools: string[];
|
|
121
|
-
/**
|
|
122
|
-
* Optional path allowlist for WRITE tools (`write_file`/`edit_file`): a write whose canonicalized
|
|
123
|
-
* target is not inside one of these (path-segment-aware) prefixes is DENIED. Omit = no path narrowing
|
|
124
|
-
* (only `allowTools` applies). Reads are never path-narrowed (this gate restricts capability, not visibility).
|
|
125
|
-
*
|
|
126
|
-
* 🔴 **Fail-closed (MAJOR-2):** `allowPaths` can only confine tools whose write target is a parseable `path`
|
|
127
|
-
* argument (`write_file`/`edit_file`). While an `allowPaths` constraint is active, any OTHER mutating
|
|
128
|
-
* (effect:write/idempotent) allowlisted tool — notably `bash`, whose command can write anywhere — is DENIED
|
|
129
|
-
* outright, because its target cannot be path-confined. So a manifest that lists `bash` together with
|
|
130
|
-
* `allowPaths` does NOT let bash write/egress anywhere: bash is denied while the path constraint is active.
|
|
131
|
-
* Confine writes to specific paths via `write_file`/`edit_file` only.
|
|
132
|
-
*/
|
|
133
|
-
allowPaths?: string[];
|
|
134
|
-
/** Reserved (design/77 §3): whether egress is permitted under this manifest. Carried through; v1 does
|
|
135
|
-
* not act on it beyond the allowTools intersection (egress tools must also be in `allowTools`). */
|
|
136
|
-
egress?: boolean;
|
|
137
|
-
/** Stable lineage id of the artifact this manifest came from — used in deny reasons + as the scope key. */
|
|
138
|
-
lineageId: string;
|
|
139
|
-
}
|
|
140
104
|
/** A skill made available to this task (passed as an object; not loaded from disk). */
|
|
141
105
|
export interface SkillSpec {
|
|
142
106
|
name: string;
|
|
143
107
|
description: string;
|
|
144
108
|
content: string;
|
|
145
|
-
/**
|
|
146
|
-
|
|
109
|
+
/**
|
|
110
|
+
* The tools this skill lets run WITHOUT ASKING for the rest of the run it is activated in — the frontmatter
|
|
111
|
+
* `allowed-tools` list, spelled as a permission rule is spelled: a tool name (`Read`, `mcp__srv__get_issue`), a
|
|
112
|
+
* protocol namespace (`mcp__srv` / `mcp__srv__*` / `mcp__srv__get_*`), a shell command rule (`Bash(git:*)`) or the Read
|
|
113
|
+
* directory form (`Read(//abs/dir/**)`). The list is an allow-rule source, not a fence: an unlisted tool is judged as
|
|
114
|
+
* it always is, and a listed one is released only from the questions an allow rule may clear (a deny or ask rule, the
|
|
115
|
+
* plan mode, the organization ceiling and every mandated question still stand). Activating a skill that declares
|
|
116
|
+
* either list asks once (`Execute skill: <name>`, the card listing what it releases); a skill the person invoked by
|
|
117
|
+
* name ({@link import("./task-spec.js").TaskSpec.userInvokedSkills}) grants without asking. The grant holds for that
|
|
118
|
+
* run (a steer it takes in does not end it) and the next runTask starts without it. An entry this grammar reads as nothing — `*` (it voids an allow list; on a deny list it is inert and the rest of the
|
|
119
|
+
* list still refuses), `mcp__*`, a write path, a domain — grants nothing and is announced at the start of the run (`skills.allowed_tool_not_granted`).
|
|
120
|
+
*/
|
|
121
|
+
allowedTools?: string[];
|
|
122
|
+
/** The tools refused for the rest of the run this skill is activated in — the frontmatter `disallowed-tools` list,
|
|
123
|
+
* in the {@link allowedTools} grammar (a path tool also takes a path pattern here); read wherever a person's deny
|
|
124
|
+
* rows are read, ahead of every allow. */
|
|
125
|
+
disallowedTools?: string[];
|
|
147
126
|
/**
|
|
148
127
|
* Optional attachments bundled with the skill (additive) — the CC bundled-skill shape (a SKILL.md whose
|
|
149
128
|
* body references sibling files by relative path) translated to sema's brain-outside posture, where the
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A background agent's CURRENT state — the one reading every read face renders.
|
|
3
|
+
*
|
|
4
|
+
* A parent model answers "how far along is it?" from what its read faces show. Three faces read a
|
|
5
|
+
* background agent: AgentTranscript (its recent steps), ListAgents (the roster) and TaskOutput (the poll).
|
|
6
|
+
* Each one projects {@link readAgentState}'s result and renders it with {@link agentStateSummary} /
|
|
7
|
+
* {@link agentActivityClause} / {@link agentApprovalClause}; none derives status, activity time or approval
|
|
8
|
+
* facts on its own, so the three cannot disagree about one agent at one instant.
|
|
9
|
+
*
|
|
10
|
+
* Sources:
|
|
11
|
+
* - the registry row — the live handle this engine process runs ({@link agentStateSourceOfHandle}), or the
|
|
12
|
+
* durable record when no live handle answers ({@link agentStateSourceOfRecord});
|
|
13
|
+
* - for a row whose status is `parked` only, the approval checkpoint the row names, read through the run's
|
|
14
|
+
* checkpoint seat (the same seat the Agent tool parks children into). The checkpoint token is the resume
|
|
15
|
+
* capability and never leaves this module: a face renders the checkpoint's non-secret `checkpointId`.
|
|
16
|
+
*
|
|
17
|
+
* Absence is stated, never left silent: a missing activity time renders as "last activity unknown (…why)",
|
|
18
|
+
* and a parked row whose card cannot be read carries `approval.state` `missing` / `unreadable`. A face that
|
|
19
|
+
* goes quiet on a fact invites the reader to fill the gap with a guess.
|
|
20
|
+
*/
|
|
21
|
+
import type { SemaTaskStatus } from "./task-registry-shared.js";
|
|
22
|
+
import { type CheckpointSeat } from "./checkpoint-seat.js";
|
|
23
|
+
/** The approval card's state: the checkpoint row's own `status`, plus `missing` (the row names no card, or
|
|
24
|
+
* the store holds none under its token) and `unreadable` (no checkpoint seat on this face, or the store
|
|
25
|
+
* threw). The same five answers the parked-row reconciliation sweep gives the same question. */
|
|
26
|
+
export type AgentApprovalState = "pending" | "resolved" | "expired" | "missing" | "unreadable";
|
|
27
|
+
/** The approval checkpoint a parked agent waits on. */
|
|
28
|
+
export interface AgentApproval {
|
|
29
|
+
state: AgentApprovalState;
|
|
30
|
+
/** The checkpoint's non-secret identity (the card id a person sees in the approval inbox). Present when
|
|
31
|
+
* the card was read and carries one. */
|
|
32
|
+
checkpointId?: string;
|
|
33
|
+
/** The card's SLA / abandonment deadline, epoch ms. Present when the card was read and carries one. */
|
|
34
|
+
deadline?: number;
|
|
35
|
+
}
|
|
36
|
+
/** Where the row was read from: the live handle this engine process runs, or the durable record (the
|
|
37
|
+
* process that wrote it may be another one, or gone). */
|
|
38
|
+
export type AgentStateRow = "live_row" | "durable_row";
|
|
39
|
+
/** One agent's state at {@link readAt}. Times are epoch ms; relative ages appear only in rendered text. */
|
|
40
|
+
export interface AgentState {
|
|
41
|
+
/** The registry row's own status word. A word outside {@link SemaTaskStatus} reads `unknown`. */
|
|
42
|
+
status: SemaTaskStatus | "unknown";
|
|
43
|
+
/** The instant this state was read (epoch ms) — the base every rendered age is computed from. */
|
|
44
|
+
readAt: number;
|
|
45
|
+
row: AgentStateRow;
|
|
46
|
+
/** The row's last recorded change (epoch ms). Live row: every state transition and every tool start /
|
|
47
|
+
* end of the direct child moves it. Durable row: when it parked (`parked`) or settled (terminal).
|
|
48
|
+
* ABSENT when the source holds no such time — a durable row still marked running (activity in another
|
|
49
|
+
* process is not observable here), or a row that never recorded one. */
|
|
50
|
+
lastActivityAt?: number;
|
|
51
|
+
/** Present exactly when {@link status} is `parked`. */
|
|
52
|
+
approval?: AgentApproval;
|
|
53
|
+
}
|
|
54
|
+
/** The row facts {@link readAgentState} reads. Built by the two projections below — never by a face. */
|
|
55
|
+
export interface AgentStateSource {
|
|
56
|
+
status: string;
|
|
57
|
+
row: AgentStateRow;
|
|
58
|
+
lastActivityAt?: number;
|
|
59
|
+
parkedCheckpointToken?: string;
|
|
60
|
+
}
|
|
61
|
+
/** The live handle's facts: `updatedAt` is moved by every state transition and by each direct-child tool
|
|
62
|
+
* lifecycle beat, so it is the row's last activity. */
|
|
63
|
+
export declare function agentStateSourceOfHandle(h: {
|
|
64
|
+
status: string;
|
|
65
|
+
updatedAt: number;
|
|
66
|
+
parkedCheckpointToken?: string;
|
|
67
|
+
}): AgentStateSource;
|
|
68
|
+
/** The durable record's facts. Its `updatedAt` is the writer's liveness lease renewal, not activity, so it
|
|
69
|
+
* is never read as such: a parked record answers with `parkedAt`, a terminal one with `settledAt`, and a
|
|
70
|
+
* record still marked running answers nothing. */
|
|
71
|
+
export declare function agentStateSourceOfRecord(r: {
|
|
72
|
+
status: string;
|
|
73
|
+
parkedAt?: number;
|
|
74
|
+
settledAt?: number;
|
|
75
|
+
parkedCheckpointToken?: string;
|
|
76
|
+
}): AgentStateSource;
|
|
77
|
+
/**
|
|
78
|
+
* THE state of one background agent at `now`. Synchronous unless the row is parked on a card the seat can
|
|
79
|
+
* read — only that row takes an async frame (the store read); every other row is answered in place.
|
|
80
|
+
* The caller must already have passed the row through its face's access check: this reads the card the
|
|
81
|
+
* row names and authorizes nothing.
|
|
82
|
+
*/
|
|
83
|
+
export declare function readAgentState(src: AgentStateSource, seat: CheckpointSeat | undefined, now: number): AgentState | Promise<AgentState>;
|
|
84
|
+
/** `Ns` / `Nm` / `Nh` / `Nd` — a bounded age word; a negative span reads as `0s`. */
|
|
85
|
+
export declare function formatAgo(ms: number): string;
|
|
86
|
+
/** `last activity <age> ago`, or the reason the time is unknown. */
|
|
87
|
+
export declare function agentActivityClause(s: AgentState): string;
|
|
88
|
+
/** `approval <card id> <state>[ (deadline in <age> | deadline passed <age> ago)]` — facts only. */
|
|
89
|
+
export declare function agentApprovalClause(a: AgentApproval, readAt: number): string;
|
|
90
|
+
/** The one-line state sentence: `<status> · <activity clause>[ · <approval clause>]`. */
|
|
91
|
+
export declare function agentStateSummary(s: AgentState): string;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { checkpointStoreOfSeat } from "./checkpoint-seat.js";
|
|
2
|
+
import { inlineUntrusted } from "./untrusted-text.js";
|
|
3
|
+
const STATUS_WORDS = { pending: true, running: true, parked: true, completed: true, failed: true, killed: true, cancelled: true };
|
|
4
|
+
export function agentStateSourceOfHandle(h) {
|
|
5
|
+
return {
|
|
6
|
+
status: h.status,
|
|
7
|
+
row: "live_row",
|
|
8
|
+
lastActivityAt: h.updatedAt,
|
|
9
|
+
...(h.parkedCheckpointToken !== undefined ? { parkedCheckpointToken: h.parkedCheckpointToken } : {}),
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export function agentStateSourceOfRecord(r) {
|
|
13
|
+
const at = r.status === "parked" ? r.parkedAt : r.status === "running" ? undefined : r.settledAt;
|
|
14
|
+
return {
|
|
15
|
+
status: r.status,
|
|
16
|
+
row: "durable_row",
|
|
17
|
+
...(at !== undefined ? { lastActivityAt: at } : {}),
|
|
18
|
+
...(r.parkedCheckpointToken !== undefined ? { parkedCheckpointToken: r.parkedCheckpointToken } : {}),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
const finite = (n) => typeof n === "number" && Number.isFinite(n);
|
|
22
|
+
async function readApprovalCard(store, token) {
|
|
23
|
+
let cp;
|
|
24
|
+
try {
|
|
25
|
+
cp = (await store.get(token));
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return { state: "unreadable" };
|
|
29
|
+
}
|
|
30
|
+
if (cp === null || cp === undefined)
|
|
31
|
+
return { state: "missing" };
|
|
32
|
+
const state = cp.status === "pending" || cp.status === "resolved" || cp.status === "expired" ? cp.status : "unreadable";
|
|
33
|
+
return {
|
|
34
|
+
state,
|
|
35
|
+
...(typeof cp.checkpointId === "string" && cp.checkpointId !== "" ? { checkpointId: cp.checkpointId } : {}),
|
|
36
|
+
...(finite(cp.deadline) ? { deadline: cp.deadline } : {}),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
export function readAgentState(src, seat, now) {
|
|
40
|
+
const base = {
|
|
41
|
+
status: Object.hasOwn(STATUS_WORDS, src.status) ? src.status : "unknown",
|
|
42
|
+
readAt: now,
|
|
43
|
+
row: src.row,
|
|
44
|
+
...(finite(src.lastActivityAt) ? { lastActivityAt: src.lastActivityAt } : {}),
|
|
45
|
+
};
|
|
46
|
+
if (base.status !== "parked")
|
|
47
|
+
return base;
|
|
48
|
+
if (src.parkedCheckpointToken === undefined)
|
|
49
|
+
return { ...base, approval: { state: "missing" } };
|
|
50
|
+
const store = checkpointStoreOfSeat(seat);
|
|
51
|
+
if (store === undefined)
|
|
52
|
+
return { ...base, approval: { state: "unreadable" } };
|
|
53
|
+
return readApprovalCard(store, src.parkedCheckpointToken).then((approval) => ({ ...base, approval }));
|
|
54
|
+
}
|
|
55
|
+
export function formatAgo(ms) {
|
|
56
|
+
const sec = Math.max(0, Math.floor(ms / 1000));
|
|
57
|
+
if (sec < 60)
|
|
58
|
+
return `${sec}s`;
|
|
59
|
+
const min = Math.floor(sec / 60);
|
|
60
|
+
if (min < 60)
|
|
61
|
+
return `${min}m`;
|
|
62
|
+
const h = Math.floor(min / 60);
|
|
63
|
+
if (h < 48)
|
|
64
|
+
return `${h}h`;
|
|
65
|
+
return `${Math.floor(h / 24)}d`;
|
|
66
|
+
}
|
|
67
|
+
export function agentActivityClause(s) {
|
|
68
|
+
if (s.lastActivityAt !== undefined)
|
|
69
|
+
return `last activity ${formatAgo(s.readAt - s.lastActivityAt)} ago`;
|
|
70
|
+
return s.row === "durable_row" && s.status === "running" ? "last activity unknown (recorded as running by another engine process)" : "last activity unknown (not recorded)";
|
|
71
|
+
}
|
|
72
|
+
export function agentApprovalClause(a, readAt) {
|
|
73
|
+
const id = a.checkpointId !== undefined ? ` ${inlineUntrusted(a.checkpointId, 64)}` : "";
|
|
74
|
+
const deadline = a.deadline === undefined ? "" : a.deadline > readAt ? ` (deadline in ${formatAgo(a.deadline - readAt)})` : ` (deadline passed ${formatAgo(readAt - a.deadline)} ago)`;
|
|
75
|
+
return `approval${id} ${a.state}${deadline}`;
|
|
76
|
+
}
|
|
77
|
+
export function agentStateSummary(s) {
|
|
78
|
+
return [s.status, agentActivityClause(s), ...(s.approval !== undefined ? [agentApprovalClause(s.approval, s.readAt)] : [])].join(" · ");
|
|
79
|
+
}
|