@signalridge/pi-subagents 1.13.0 → 1.15.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 +29 -0
- package/README.md +14 -0
- package/package.json +2 -1
- package/src/agent-manager.ts +102 -51
- package/src/agent-runner.ts +280 -146
- package/src/index.ts +195 -39
- package/src/internal-run.ts +5 -1
- package/src/invocation-config.ts +6 -1
- package/src/nested-tools.ts +7 -2
- package/src/operation-approvals.ts +49 -0
- package/src/run-budget-policy.ts +22 -0
- package/src/ui/fleet-list.ts +4 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.15.0
|
|
4
|
+
### Minor Changes
|
|
5
|
+
|
|
6
|
+
- a4dc1de: Enforce missing per-invocation safety budgets on resumed agents, including foreground/background calls, nested resumes, mentions, and reused managed threads. Share fresh/resumed turn, token, and tool-call enforcement and terminal outcome mapping; preserve configured grace and tool timeouts. Resume inherits the original invocation's turn budget when not overridden, respects authoritative frontmatter/default policy, validates explicit limits like spawn, and does not let zero erase a configured cap. Nested resumes resolve only their branch-local frontmatter through an in-process budget-policy capability; internal callers without it retain the admitted invocation budget rather than consulting the root registry. Authoritative frontmatter zero remains unlimited after a finite earlier invocation. A retained session's finite or unlimited policy survives later default changes; one manager-resolved cap drives enforcement and queued/running UI counters. Activity updates are invocation-owned and lifetime usage is counted only once.
|
|
7
|
+
|
|
8
|
+
Dispose grouped notifications and pending batch/nudge timers synchronously at confirmed shutdown, retire invocation-owned foreground progress/spinner callbacks before any teardown await, fence captured delivery callbacks, and preserve managed lifecycle/usage receipts while cooperative child teardown completes. Refused/failed resume admissions retire their own UI callbacks, while admitted lifetimes own final cleanup; mention notifications are fenced after asynchronous admission. Vetoable navigation remains unchanged.
|
|
9
|
+
|
|
10
|
+
Selective upstream parity references: tintinweb/pi-subagents `d7c9ec0b027b91c7ab9da0fa5f4ae203474a5bdf`, `ca93a5247e72e7053fcd10bea7125d6c4205f949` (unreleased after the 0.22 tag), and `83be963a969eb359af29cbbb4d4af2d01748bfb5` (released in 0.22). The integrated engine is not imported; managed protocol v4 and payloads are unchanged. Event-based budgets cannot forcibly terminate a provider or tool that ignores abort.
|
|
11
|
+
|
|
12
|
+
### Patch Changes
|
|
13
|
+
|
|
14
|
+
- a4dc1de: Distinguish expandable Workflow groups from individual agents with a dedicated `▸` type mark across all run outcomes. Remove the duplicate trailing blank row before the footer while preserving Fleet navigation, exact-owner conversation opening, and the existing Workflow detail UI.
|
|
15
|
+
|
|
16
|
+
## 1.14.0
|
|
17
|
+
### Minor Changes
|
|
18
|
+
|
|
19
|
+
- 0ec64d1: Gate admitted child edit, write, and bash operations through a synchronously captured root human approval service. Preserve tool admission and legacy ask-tools policy, reject canonical shadowing and duplicate child approval resources, retain ownership across resume, and exclude human review time without resetting execution budgets. Managed protocol and journal formats remain v4.
|
|
20
|
+
|
|
21
|
+
### Patch Changes
|
|
22
|
+
|
|
23
|
+
- 74b65a5: Bound edit call rendering without IO or diff work; retain captured service liveness and exact-name admission through post-approval IO; reject live child canonical conflicts even when SDK metadata still wins. Replace delegated bash dispatch with an owned POSIX backend guarded after CWD preparation, preserving the SDK output layer while owning process-group/IPC cleanup. Preserve native editor paste/cursor/Undo state with an admitted non-capturing UI bridge, and hide unfocused reviews so foreign native questions remain visible and answerable.
|
|
24
|
+
|
|
25
|
+
Refuse Bun/compiled application and unknown interpreter capabilities before approval or subprocess startup instead of executing the host application as a supervisor. Use lossless hard wrapping, whole-grapheme and native-normalized Thai/Lao escaping, and explicit compact-preview whitespace/backslashes so spacing marks and distinct commands remain inspectable through actual terminal output.
|
|
26
|
+
- 0925139: Bound synchronous file-review diff work before generation, capture bash execution options and environments, require exact per-name canonical review bindings, and close owned review overlays by identity. Recheck cooperative adapter admission after child tool-call normalizers and carry the same captured approval-service and branch-owner guard through background startup to command dispatch. Preserve existing v1 approval and v4 managed contracts; document review limits and trusted-extension boundaries.
|
|
27
|
+
- Updated dependencies [74b65a5]
|
|
28
|
+
- Updated dependencies [0ec64d1]
|
|
29
|
+
- Updated dependencies [0925139]
|
|
30
|
+
- @signalridge/pi-approval-core@1.3.0
|
|
31
|
+
|
|
3
32
|
## 1.13.0
|
|
4
33
|
### Minor Changes
|
|
5
34
|
|
package/README.md
CHANGED
|
@@ -55,6 +55,8 @@ Derived from [tintinweb/pi-subagents](https://github.com/tintinweb/pi-subagents)
|
|
|
55
55
|
|
|
56
56
|
The container-first FleetView and bounded drill-down patterns are independently adapted from tintinweb/pi-subagents at [`221df02323c6b6350ea8474579e0ee0f6cc84cea`](https://github.com/tintinweb/pi-subagents/tree/221df02323c6b6350ea8474579e0ee0f6cc84cea/src/ui) and [`fd8d81618d80ae83a3faf40cb87dae99cca33264`](https://github.com/tintinweb/pi-subagents/tree/fd8d81618d80ae83a3faf40cb87dae99cca33264/src/ui). Collision-only child-label discriminators are inspired by [nicobailon/pi-subagents `69673c7fbf3351e54a5b0786a7e9d9fff625a749`](https://github.com/nicobailon/pi-subagents/blob/69673c7fbf3351e54a5b0786a7e9d9fff625a749/src/tui/render-helpers.ts). The workflow engine and phase inspector remain owned by `pi-workflows`, not copied into this package. A separate optional, validated UI protocol supplies authoritative run names, statuses and observed phases and opens that owner's inspector. Without that peer, groups retain the observation-only child list. No workflow source imports, inferred DAG state or second queue/model policy are introduced. The managed protocol remains v4.
|
|
57
57
|
|
|
58
|
+
Resume-budget and shutdown parity fixes selectively adapt upstream work at [`d7c9ec0`](https://github.com/tintinweb/pi-subagents/commit/d7c9ec0b027b91c7ab9da0fa5f4ae203474a5bdf), [`ca93a52`](https://github.com/tintinweb/pi-subagents/commit/ca93a5247e72e7053fcd10bea7125d6c4205f949) (unreleased after the 0.22 tag), and [`83be963`](https://github.com/tintinweb/pi-subagents/commit/83be963a969eb359af29cbbb4d4af2d01748bfb5) (released in 0.22). They retain this fork's manager-owned sessions, resource budgets, activity accounting, and protocol v4; the integrated upstream engine is not imported.
|
|
59
|
+
|
|
58
60
|
The opt-in parent clarification broker is locally implemented. It uses the existing main session, not an upstream LLM arbitrator, cloned parent history, private UI automation, or an additional Agent per question. Permission gates remain human-owned.
|
|
59
61
|
|
|
60
62
|
## Lint policy
|
|
@@ -114,6 +116,14 @@ Foreground parent-mode calls are rejected **before spawn**: a main model blocked
|
|
|
114
116
|
|
|
115
117
|
Install the changed package and reload/restart Pi to use this feature; an already running older installed package does not acquire new tools or parameters from checkout edits.
|
|
116
118
|
|
|
119
|
+
### Root-owned operation approvals
|
|
120
|
+
|
|
121
|
+
When a root approval service is present at child creation, admitted canonical `edit`, `write`, and `bash` tools use `@signalridge/pi-approval-core` reviewed SDK definitions. Each operation awaits the root human service with its exact final inputs, file diff or command, working directory, and a distinct `child:<agent-id>` source. Ordinary parent clarification answers never authorize operations. No service means existing behavior is unchanged; malformed or duplicate services refuse before child allocation. A captured service cannot reconnect after retirement or replacement.
|
|
122
|
+
|
|
123
|
+
Tool admission, denylist, `ask_tools`, project trust, and isolation still apply first: approval adds no tools, including to internal zero-tool generation. The gate survives in-memory resume, steering, and nested `ctx.executeTool` calls. Noncanonical command/mutation-shaped tools require exactly one live cooperative adapter matching the effective schema identity. Known command or path-plus-edit/content schema properties are classified before invocation even when optional. Admission is also rechecked after each downstream public `tool_call` handler against the native mutable arguments and the originally captured execution name/schema; a normalizer cannot add a command or rename the event to bypass it. These post-handler checks never repeat `ask_tools` human prompts and retain the same shared execution deadline. Conflicting canonical extension tools are refused rather than silently replacing the reviewed binding. While root review is pending, only human waiting time is excluded from the child's default tool timeout; execution retains its remaining budget and native bash timeouts still apply after approval. Cancellation and divergent root/child branch ownership deny execution.
|
|
124
|
+
|
|
125
|
+
Inherited `pi-approvals` resources are excluded before factory loading when the root service is captured, including package aliases. Explicit selection is rejected with guidance to integrate approvals in the root session. Children do not load another approval extension or forward their UI to impersonate a human; no UI/model mode is enabled automatically. Known-shape admission does not interpret arbitrary extension semantics or unknown schemas; a timed-out hook can still perform its own side effects outside the executor. This is an in-process trusted extension integration, not an OS sandbox for arbitrary extension code. Managed protocol and journal formats remain v4.
|
|
126
|
+
|
|
117
127
|
### Scheduling
|
|
118
128
|
|
|
119
129
|
Add a `schedule` field to register the agent to fire later instead of running now:
|
|
@@ -461,6 +471,10 @@ Instead of hard-aborting at the turn limit, agents get a graceful shutdown:
|
|
|
461
471
|
2. Up to 5 grace turns to finish cleanly
|
|
462
472
|
3. Hard abort only after the grace period
|
|
463
473
|
|
|
474
|
+
Each resumed prompt gets its own counters, not an unlimited continuation or a deduction from prior turns. This applies to foreground/background resumes, nested children, mentions, and reused managed threads. Agent frontmatter remains authoritative; without a per-resume override, the original invocation's turn limit is inherited, falling back to configured defaults. Resume overrides use the same non-negative safe-integer validation as spawning; caller zero cannot disable an inherited/configured cap. An authoritative frontmatter zero remains unlimited, even after an earlier finite invocation. Nested resumes reload the same branch-local definitions as nested spawns, never the root registry; an internal resume without scoped policy retains its admitted invocation budget. A retained session's admitted turn policy, including unlimited, survives later project-default changes; the manager supplies that same effective cap to queued and running progress trackers. The existing token/tool-call caps and tool timeout also apply to resumed prompts. Budgets are event-based: an already-running provider/tool can overshoot within one event or ignore abort; these are not process-isolation guarantees.
|
|
475
|
+
|
|
476
|
+
Confirmed shutdown retires grouped completion, batch-finalization, held-nudge timers, and invocation-owned foreground spinners before awaiting child teardown. Captured delivery callbacks cannot wake the main model or publish foreground progress afterward, even while a provider ignores abort. Refused or failed resume admissions retire their own UI callbacks; an admitted run owns cleanup until settlement. Mention continuations cannot publish notifications after their root/runtime retires. Managed lifecycle/usage receipts remain available during cooperative shutdown; vetoable navigation attempts do not dispose these resources.
|
|
477
|
+
|
|
464
478
|
| Status | Meaning | UI treatment |
|
|
465
479
|
|--------|---------|-------------|
|
|
466
480
|
| `completed` | Finished naturally | `completed` |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@signalridge/pi-subagents",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.15.0",
|
|
4
4
|
"description": "Signalridge's managed subagent runtime with workflow-owned orchestration RPC.",
|
|
5
5
|
"author": "tintinweb and signalridge contributors",
|
|
6
6
|
"license": "MIT",
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
"@sinclair/typebox": "*"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
+
"@signalridge/pi-approval-core": "^1.3.0",
|
|
34
35
|
"croner": "^10.0.1",
|
|
35
36
|
"@signalridge/pi-subagents-protocol": "^1.6.0",
|
|
36
37
|
"@signalridge/pi-ui": "^1.4.0",
|
package/src/agent-manager.ts
CHANGED
|
@@ -35,15 +35,19 @@ import {
|
|
|
35
35
|
import {
|
|
36
36
|
INTERNAL_AGENT_CONFIG_OVERRIDE,
|
|
37
37
|
INTERNAL_PARENT_POLICY_SNAPSHOT,
|
|
38
|
+
INTERNAL_RESUME_BUDGET_POLICY,
|
|
38
39
|
type InternalAgentConfigOverride,
|
|
39
40
|
type ParentPolicySnapshot,
|
|
41
|
+
type ResumeBudgetPolicy,
|
|
40
42
|
} from "./internal-run.js";
|
|
43
|
+
import { resolveMaxTurns } from "./invocation-config.js";
|
|
41
44
|
import { assignHandle, handleBase } from "./mention.js";
|
|
42
45
|
import {
|
|
43
46
|
assertParentModelRuntimeAvailable,
|
|
44
47
|
parentModelSessionOptions,
|
|
45
48
|
} from "./model-runtime-bridge.js";
|
|
46
49
|
import type { ParentQuestionHandler } from "./parent-questions.js";
|
|
50
|
+
import { selectResumeTurnPolicy } from "./run-budget-policy.js";
|
|
47
51
|
import { shutdownAndDisposeSession } from "./session-lifecycle.js";
|
|
48
52
|
import type { TierThinking } from "./settings.js";
|
|
49
53
|
import type {
|
|
@@ -315,6 +319,21 @@ function snapshotCleanupFailure(
|
|
|
315
319
|
});
|
|
316
320
|
}
|
|
317
321
|
|
|
322
|
+
/** Hard budgets take precedence over provider failure, then soft wrap-up. */
|
|
323
|
+
function applyRunOutcome(
|
|
324
|
+
record: AgentRecord,
|
|
325
|
+
outcome: { aborted?: boolean; steered?: boolean; failure?: string },
|
|
326
|
+
): void {
|
|
327
|
+
if (outcome.aborted) {
|
|
328
|
+
record.status = "aborted";
|
|
329
|
+
} else if (outcome.failure) {
|
|
330
|
+
record.status = "error";
|
|
331
|
+
record.error = outcome.failure;
|
|
332
|
+
} else {
|
|
333
|
+
record.status = outcome.steered ? "steered" : "completed";
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
318
337
|
interface SpawnArgs {
|
|
319
338
|
pi: ExtensionAPI;
|
|
320
339
|
ctx: ExtensionContext;
|
|
@@ -1021,10 +1040,27 @@ export type ManagedSpawnCallbacks = Pick<
|
|
|
1021
1040
|
| "onCompaction"
|
|
1022
1041
|
| "onSpawned"
|
|
1023
1042
|
> & {
|
|
1043
|
+
/** Reused thread's effective display budget, before queue admission. */
|
|
1044
|
+
onResumeBudgetResolved?: (maxTurns: number | undefined) => void;
|
|
1024
1045
|
/** Reused thread's actual run boundary, before native steering or prompt. */
|
|
1025
1046
|
onResumeStarting?: (record: AgentRecord, startMessageIndex: number) => void;
|
|
1026
1047
|
};
|
|
1027
1048
|
|
|
1049
|
+
export interface ResumeOptions {
|
|
1050
|
+
readonly [INTERNAL_RESUME_BUDGET_POLICY]?: ResumeBudgetPolicy;
|
|
1051
|
+
isBackground?: boolean;
|
|
1052
|
+
maxTurns?: number;
|
|
1053
|
+
onToolActivity?: (activity: ToolActivity) => void;
|
|
1054
|
+
onTurnEnd?: (count: number) => void;
|
|
1055
|
+
onTextDelta?: (delta: string, fullText: string) => void;
|
|
1056
|
+
onAssistantUsage?: SpawnOptions["onAssistantUsage"];
|
|
1057
|
+
onCompaction?: (info: CompactionInfo) => void;
|
|
1058
|
+
/** Effective display cap, published synchronously before queue admission. */
|
|
1059
|
+
onBudgetResolved?: (maxTurns: number | undefined) => void;
|
|
1060
|
+
/** Called at actual admission, including a queued background resume. */
|
|
1061
|
+
onStarted?: () => void;
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1028
1064
|
interface ResumeTerminalSnapshot {
|
|
1029
1065
|
status: AgentRecord["status"];
|
|
1030
1066
|
startedAt: number;
|
|
@@ -1863,9 +1899,13 @@ export class AgentManager {
|
|
|
1863
1899
|
// only filter excludes only explicit `false`, so undefined agents — which
|
|
1864
1900
|
// have no inline surface — stay visible instead of vanishing.
|
|
1865
1901
|
isBackground: options.isBackground,
|
|
1866
|
-
invocation:
|
|
1867
|
-
|
|
1868
|
-
|
|
1902
|
+
invocation:
|
|
1903
|
+
options.invocation || options.maxTurns !== undefined
|
|
1904
|
+
? cloneFrozenData({
|
|
1905
|
+
...options.invocation,
|
|
1906
|
+
maxTurns: options.maxTurns ?? options.invocation?.maxTurns,
|
|
1907
|
+
})
|
|
1908
|
+
: undefined,
|
|
1869
1909
|
depth: options.depth ?? 1,
|
|
1870
1910
|
parentAgentId: options.parentAgentId,
|
|
1871
1911
|
...(ancestorAgentIds ? { ancestorAgentIds } : {}),
|
|
@@ -2176,9 +2216,13 @@ export class AgentManager {
|
|
|
2176
2216
|
}
|
|
2177
2217
|
void this.resume(threadedId, normalized.prompt, undefined, {
|
|
2178
2218
|
isBackground: true,
|
|
2219
|
+
maxTurns: policy.maxTurns,
|
|
2220
|
+
onBudgetResolved: callbacks?.onResumeBudgetResolved,
|
|
2179
2221
|
onStarted: () => {
|
|
2180
2222
|
void Promise.resolve(callbacks?.onResumeStarting?.(record, record.session!.messages.length)).catch(() => {});
|
|
2181
2223
|
},
|
|
2224
|
+
onTurnEnd: callbacks?.onTurnEnd,
|
|
2225
|
+
onTextDelta: callbacks?.onTextDelta,
|
|
2182
2226
|
onToolActivity: callbacks?.onToolActivity,
|
|
2183
2227
|
onAssistantUsage: callbacks?.onAssistantUsage,
|
|
2184
2228
|
onCompaction: callbacks?.onCompaction
|
|
@@ -2560,14 +2604,7 @@ export class AgentManager {
|
|
|
2560
2604
|
// Precedence: a hard abort keeps "aborted"; then a failed final turn
|
|
2561
2605
|
// (provider error that pi resolved instead of rejecting, #144) is an
|
|
2562
2606
|
// honest "error" — not a completion with an empty or stale result.
|
|
2563
|
-
|
|
2564
|
-
record.status = "aborted";
|
|
2565
|
-
} else if (failure) {
|
|
2566
|
-
record.status = "error";
|
|
2567
|
-
record.error = failure;
|
|
2568
|
-
} else {
|
|
2569
|
-
record.status = steered ? "steered" : "completed";
|
|
2570
|
-
}
|
|
2607
|
+
applyRunOutcome(record, { aborted, steered, failure });
|
|
2571
2608
|
}
|
|
2572
2609
|
record.completedAt ??= Date.now();
|
|
2573
2610
|
detach();
|
|
@@ -3094,21 +3131,7 @@ export class AgentManager {
|
|
|
3094
3131
|
id: string,
|
|
3095
3132
|
prompt: string,
|
|
3096
3133
|
signal?: AbortSignal,
|
|
3097
|
-
options?:
|
|
3098
|
-
isBackground?: boolean;
|
|
3099
|
-
onToolActivity?: (activity: {
|
|
3100
|
-
type: "start" | "end";
|
|
3101
|
-
toolName: string;
|
|
3102
|
-
}) => void;
|
|
3103
|
-
onAssistantUsage?: (usage: {
|
|
3104
|
-
input: number;
|
|
3105
|
-
output: number;
|
|
3106
|
-
cacheWrite: number;
|
|
3107
|
-
}) => void;
|
|
3108
|
-
onCompaction?: (info: unknown) => void;
|
|
3109
|
-
/** Called once a queued background resume has acquired its run slot. */
|
|
3110
|
-
onStarted?: () => void;
|
|
3111
|
-
},
|
|
3134
|
+
options?: ResumeOptions,
|
|
3112
3135
|
): Promise<AgentRecord | undefined> {
|
|
3113
3136
|
if (this.disposed) return undefined;
|
|
3114
3137
|
const record = this.agents.get(id);
|
|
@@ -3124,6 +3147,28 @@ export class AgentManager {
|
|
|
3124
3147
|
record.invocation?.inheritContext === true,
|
|
3125
3148
|
);
|
|
3126
3149
|
|
|
3150
|
+
if (
|
|
3151
|
+
options?.maxTurns !== undefined &&
|
|
3152
|
+
(!Number.isSafeInteger(options.maxTurns) || options.maxTurns < 0)
|
|
3153
|
+
) {
|
|
3154
|
+
throw new Error("maxTurns must be a non-negative safe integer");
|
|
3155
|
+
}
|
|
3156
|
+
// Nested definitions belong to their branch, never the global root registry.
|
|
3157
|
+
// Without the scoped tool's capability, retain the admitted invocation policy.
|
|
3158
|
+
const policy = record.parentAgentId === undefined
|
|
3159
|
+
? getAgentConfig(record.type)
|
|
3160
|
+
: options?.[INTERNAL_RESUME_BUDGET_POLICY] ?? record.invocation;
|
|
3161
|
+
const maxTurns = selectResumeTurnPolicy(record.session, resolveMaxTurns(
|
|
3162
|
+
policy,
|
|
3163
|
+
// A caller's zero cannot erase the original/configured safety policy.
|
|
3164
|
+
options?.maxTurns || record.invocation?.maxTurns,
|
|
3165
|
+
));
|
|
3166
|
+
try {
|
|
3167
|
+
options?.onBudgetResolved?.(maxTurns === 0 ? undefined : maxTurns);
|
|
3168
|
+
} catch {
|
|
3169
|
+
// UI observers cannot alter the authoritative policy or admission.
|
|
3170
|
+
}
|
|
3171
|
+
options = { ...options, maxTurns };
|
|
3127
3172
|
const snapshot = this.resumeSnapshot(record);
|
|
3128
3173
|
if (options?.isBackground) {
|
|
3129
3174
|
// A background resume is one lifecycle, including time spent queued. Keep
|
|
@@ -3177,18 +3222,31 @@ export class AgentManager {
|
|
|
3177
3222
|
record.result = undefined;
|
|
3178
3223
|
record.error = undefined;
|
|
3179
3224
|
try {
|
|
3180
|
-
|
|
3225
|
+
options.onStarted?.();
|
|
3226
|
+
const { text, failure, aborted, steered } = await resumeAgent(record.session, prompt, {
|
|
3227
|
+
maxTurns,
|
|
3228
|
+
onTurnEnd: (count) => {
|
|
3229
|
+
if (!record.detached) options.onTurnEnd?.(count);
|
|
3230
|
+
},
|
|
3231
|
+
onTextDelta: (delta, text) => {
|
|
3232
|
+
if (!record.detached) options.onTextDelta?.(delta, text);
|
|
3233
|
+
},
|
|
3181
3234
|
onToolActivity: (activity) => {
|
|
3182
|
-
if (
|
|
3235
|
+
if (record.detached) return;
|
|
3236
|
+
if (activity.type === "end") record.toolUses++;
|
|
3237
|
+
options.onToolActivity?.(activity);
|
|
3183
3238
|
},
|
|
3184
3239
|
onAssistantUsage: (usage) => {
|
|
3185
|
-
if (
|
|
3240
|
+
if (record.detached) return;
|
|
3241
|
+
addUsage(record.lifetimeUsage, usage);
|
|
3242
|
+
options.onAssistantUsage?.(usage);
|
|
3186
3243
|
},
|
|
3187
3244
|
onCompaction: (info) => {
|
|
3188
3245
|
if (record.detached) return;
|
|
3189
3246
|
record.compactionCount++;
|
|
3190
3247
|
this.syncManagedRecord(record);
|
|
3191
3248
|
this.onCompact?.(record, info);
|
|
3249
|
+
options.onCompaction?.(info);
|
|
3192
3250
|
},
|
|
3193
3251
|
signal: control.controller.signal,
|
|
3194
3252
|
});
|
|
@@ -3197,8 +3255,7 @@ export class AgentManager {
|
|
|
3197
3255
|
!control.controller.signal.aborted &&
|
|
3198
3256
|
(record.status as AgentRecord["status"]) !== "stopped"
|
|
3199
3257
|
) {
|
|
3200
|
-
record
|
|
3201
|
-
if (failure) record.error = failure;
|
|
3258
|
+
applyRunOutcome(record, { aborted, steered, failure });
|
|
3202
3259
|
record.result = text;
|
|
3203
3260
|
record.completedAt = Date.now();
|
|
3204
3261
|
}
|
|
@@ -3242,21 +3299,7 @@ export class AgentManager {
|
|
|
3242
3299
|
record: AgentRecord,
|
|
3243
3300
|
prompt: string,
|
|
3244
3301
|
control: ResumeControl,
|
|
3245
|
-
options?:
|
|
3246
|
-
isBackground?: boolean;
|
|
3247
|
-
onToolActivity?: (activity: {
|
|
3248
|
-
type: "start" | "end";
|
|
3249
|
-
toolName: string;
|
|
3250
|
-
}) => void;
|
|
3251
|
-
onAssistantUsage?: (usage: {
|
|
3252
|
-
input: number;
|
|
3253
|
-
output: number;
|
|
3254
|
-
cacheWrite: number;
|
|
3255
|
-
}) => void;
|
|
3256
|
-
onCompaction?: (info: unknown) => void;
|
|
3257
|
-
/** Called once a queued background resume has acquired its run slot. */
|
|
3258
|
-
onStarted?: () => void;
|
|
3259
|
-
},
|
|
3302
|
+
options?: ResumeOptions,
|
|
3260
3303
|
): Promise<string> {
|
|
3261
3304
|
const snapshot = control.snapshot;
|
|
3262
3305
|
let slotHeld = false;
|
|
@@ -3305,13 +3348,22 @@ export class AgentManager {
|
|
|
3305
3348
|
}
|
|
3306
3349
|
|
|
3307
3350
|
try {
|
|
3308
|
-
const { text, failure } = await resumeAgent(record.session!, prompt, {
|
|
3351
|
+
const { text, failure, aborted, steered } = await resumeAgent(record.session!, prompt, {
|
|
3352
|
+
maxTurns: options?.maxTurns,
|
|
3353
|
+
onTurnEnd: (count) => {
|
|
3354
|
+
if (!record.detached) options?.onTurnEnd?.(count);
|
|
3355
|
+
},
|
|
3356
|
+
onTextDelta: (delta, text) => {
|
|
3357
|
+
if (!record.detached) options?.onTextDelta?.(delta, text);
|
|
3358
|
+
},
|
|
3309
3359
|
onToolActivity: (activity) => {
|
|
3310
|
-
if (
|
|
3360
|
+
if (record.detached) return;
|
|
3361
|
+
if (activity.type === "end") record.toolUses++;
|
|
3311
3362
|
options?.onToolActivity?.(activity);
|
|
3312
3363
|
},
|
|
3313
3364
|
onAssistantUsage: (usage) => {
|
|
3314
|
-
if (
|
|
3365
|
+
if (record.detached) return;
|
|
3366
|
+
addUsage(record.lifetimeUsage, usage);
|
|
3315
3367
|
options?.onAssistantUsage?.(usage);
|
|
3316
3368
|
},
|
|
3317
3369
|
onCompaction: (info) => {
|
|
@@ -3328,8 +3380,7 @@ export class AgentManager {
|
|
|
3328
3380
|
!control.controller.signal.aborted &&
|
|
3329
3381
|
(record.status as AgentRecord["status"]) !== "stopped"
|
|
3330
3382
|
) {
|
|
3331
|
-
record
|
|
3332
|
-
if (failure) record.error = failure;
|
|
3383
|
+
applyRunOutcome(record, { aborted, steered, failure });
|
|
3333
3384
|
record.result = text;
|
|
3334
3385
|
record.completedAt = Date.now();
|
|
3335
3386
|
}
|