@yefengr/pi-subagents 0.0.0-stage → 0.1.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 +2411 -0
- package/LICENSE +21 -0
- package/README.md +374 -2
- package/UPSTREAM.md +48 -0
- package/dist/public.d.ts +201 -0
- package/dist/settings.d.ts +82 -0
- package/docs/architecture/architecture.md +833 -0
- package/docs/architecture/client-server-opportunities.md +127 -0
- package/docs/architecture/history/phase-1-api-boundary.md +8 -0
- package/docs/architecture/history/phase-10-structural-decomposition.md +141 -0
- package/docs/architecture/history/phase-11-closure-to-class.md +100 -0
- package/docs/architecture/history/phase-12-complexity-test-fixtures.md +55 -0
- package/docs/architecture/history/phase-13-remaining-smells.md +88 -0
- package/docs/architecture/history/phase-14-strip-policy.md +49 -0
- package/docs/architecture/history/phase-15-domain-model-evolution.md +73 -0
- package/docs/architecture/history/phase-16-invert-dependencies.md +144 -0
- package/docs/architecture/history/phase-17-core-consolidation.md +214 -0
- package/docs/architecture/history/phase-18-reconsider-ui.md +166 -0
- package/docs/architecture/history/phase-19-implement-ui-decisions.md +282 -0
- package/docs/architecture/history/phase-2-remove-scheduling.md +9 -0
- package/docs/architecture/history/phase-20-result-delivery.md +245 -0
- package/docs/architecture/history/phase-21-classification-model-boundary.md +107 -0
- package/docs/architecture/history/phase-3-remove-rpc-groupjoin.md +11 -0
- package/docs/architecture/history/phase-4-implement-service.md +8 -0
- package/docs/architecture/history/phase-5-decompose-index.md +42 -0
- package/docs/architecture/history/phase-7-encapsulation.md +173 -0
- package/docs/architecture/history/phase-8-testability.md +103 -0
- package/docs/architecture/history/phase-9-observation-ctx.md +122 -0
- package/docs/comparison-with-upstream.md +79 -0
- package/docs/configuration.md +215 -0
- package/docs/decisions/0001-deferred-patches.md +80 -0
- package/docs/decisions/0002-extensions-on-a-minimal-core.md +114 -0
- package/docs/decisions/0003-publish-bundled-type-declarations.md +71 -0
- package/docs/decisions/0004-reconsider-ui-direction.md +279 -0
- package/index.ts +1 -0
- package/package.json +91 -4
- package/src/config/agent-enablement.ts +132 -0
- package/src/config/agent-types.ts +144 -0
- package/src/config/custom-agents.ts +114 -0
- package/src/config/default-agents.ts +120 -0
- package/src/config/invocation-config.ts +39 -0
- package/src/config/resource-limits.ts +26 -0
- package/src/config/settings-file.ts +174 -0
- package/src/debug.ts +14 -0
- package/src/handlers/index.ts +3 -0
- package/src/handlers/interrupt.ts +58 -0
- package/src/handlers/lifecycle.ts +65 -0
- package/src/handlers/tool-start.ts +32 -0
- package/src/index.ts +257 -0
- package/src/layered-settings.ts +105 -0
- package/src/lifecycle/child-lifecycle.ts +89 -0
- package/src/lifecycle/child-shutdown.ts +105 -0
- package/src/lifecycle/concurrency-limiter.ts +123 -0
- package/src/lifecycle/create-subagent-session.ts +265 -0
- package/src/lifecycle/invocation-control.ts +109 -0
- package/src/lifecycle/lifecycle-record.ts +159 -0
- package/src/lifecycle/parent-snapshot.ts +46 -0
- package/src/lifecycle/run-listeners.ts +41 -0
- package/src/lifecycle/subagent-manager.ts +392 -0
- package/src/lifecycle/subagent-session.ts +287 -0
- package/src/lifecycle/subagent-state.ts +307 -0
- package/src/lifecycle/subagent.ts +854 -0
- package/src/lifecycle/usage.ts +60 -0
- package/src/lifecycle/workspace-bracket.ts +59 -0
- package/src/lifecycle/workspace.ts +47 -0
- package/src/observation/composite-subagent-observer.ts +53 -0
- package/src/observation/notification.ts +233 -0
- package/src/observation/record-observer.ts +78 -0
- package/src/observation/renderer.ts +106 -0
- package/src/observation/subagent-events-observer.ts +75 -0
- package/src/runtime.ts +78 -0
- package/src/service/service-adapter.ts +159 -0
- package/src/service/service.ts +147 -0
- package/src/session/content-items.ts +53 -0
- package/src/session/context.ts +80 -0
- package/src/session/conversation.ts +49 -0
- package/src/session/env.ts +40 -0
- package/src/session/model-resolver.ts +135 -0
- package/src/session/package-exclusions.ts +75 -0
- package/src/session/prompts.ts +145 -0
- package/src/session/provider-inheritance.ts +38 -0
- package/src/session/session-config.ts +177 -0
- package/src/session/session-dir.ts +38 -0
- package/src/settings.ts +436 -0
- package/src/tools/agent-tool.ts +289 -0
- package/src/tools/background-spawner.ts +73 -0
- package/src/tools/foreground-runner.ts +135 -0
- package/src/tools/get-result-report.ts +73 -0
- package/src/tools/get-result-tool.ts +109 -0
- package/src/tools/helpers.ts +134 -0
- package/src/tools/result-renderer.ts +121 -0
- package/src/tools/spawn-config.ts +182 -0
- package/src/tools/steer-tool.ts +120 -0
- package/src/types.ts +145 -0
- package/src/ui/agent-widget.ts +313 -0
- package/src/ui/display.ts +214 -0
- package/src/ui/glyphs.ts +62 -0
- package/src/ui/session-navigation.ts +152 -0
- package/src/ui/session-navigator.ts +257 -0
- package/src/ui/subagents-agents.ts +116 -0
- package/src/ui/subagents-settings.ts +207 -0
- package/src/ui/transcript-content.ts +374 -0
- package/src/ui/widget-renderer.ts +289 -0
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* subagent-state.ts — SubagentState value object: lifecycle phase/outcome, metrics, and live activity.
|
|
3
|
+
*
|
|
4
|
+
* Owns the passive, readable state of a subagent — phase, outcome, result, error,
|
|
5
|
+
* timestamps, lifetime and current-invocation metrics, compactionCount, and
|
|
6
|
+
* live-activity fields (invocation/lifetime turn counts, activeTools, responseText) — together with the transition
|
|
7
|
+
* methods (markRunning, markCompleted, …), accumulation methods
|
|
8
|
+
* (incrementToolUses, addUsage, incrementCompactions), and live-activity
|
|
9
|
+
* transition methods (incrementTurnCount, addActiveTool, removeActiveTool,
|
|
10
|
+
* resetResponseText, appendResponseText) that mutate them.
|
|
11
|
+
*
|
|
12
|
+
* State is encapsulated behind getters; external code reads through them but
|
|
13
|
+
* mutates only via the transition/accumulation methods. The value object owns
|
|
14
|
+
* all of its own mutations — no field is written from outside.
|
|
15
|
+
*
|
|
16
|
+
* Subagent holds one of these privately and delegates its getters and mutation
|
|
17
|
+
* methods to it. Extracting it lets the lifecycle state machine and the
|
|
18
|
+
* session-event observer be unit-tested without constructing an executor.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import type { LifetimeUsage } from "#src/lifecycle/usage";
|
|
22
|
+
import { addUsage } from "#src/lifecycle/usage";
|
|
23
|
+
|
|
24
|
+
export type SubagentPhase = "queued" | "running" | "terminal";
|
|
25
|
+
export type SubagentOutcome =
|
|
26
|
+
| "completed"
|
|
27
|
+
| "turn_limit"
|
|
28
|
+
| "timeout"
|
|
29
|
+
| "queue_timeout"
|
|
30
|
+
| "cancelled"
|
|
31
|
+
| "error";
|
|
32
|
+
|
|
33
|
+
export function isActivePhase(phase: SubagentPhase): boolean {
|
|
34
|
+
return phase === "running" || phase === "queued";
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function isTerminalErrorOutcome(outcome: SubagentOutcome | undefined): boolean {
|
|
38
|
+
return outcome != null && outcome !== "completed";
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function isRunningPhase(phase: SubagentPhase): boolean {
|
|
42
|
+
return phase === "running";
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface SubagentStateInit {
|
|
46
|
+
phase?: SubagentPhase;
|
|
47
|
+
outcome?: SubagentOutcome;
|
|
48
|
+
result?: string;
|
|
49
|
+
error?: string;
|
|
50
|
+
/** Whether the agent was stopped before the limiter ever admitted it. */
|
|
51
|
+
stoppedWhileQueued?: boolean;
|
|
52
|
+
startedAt?: number;
|
|
53
|
+
completedAt?: number;
|
|
54
|
+
/** Time the parent collected the outcome; undefined = obligation still open. */
|
|
55
|
+
consumedAt?: number;
|
|
56
|
+
// Stats — seed a populated value without replaying the accumulation methods
|
|
57
|
+
toolUses?: number;
|
|
58
|
+
lifetimeUsage?: LifetimeUsage;
|
|
59
|
+
invocationStartedAt?: number;
|
|
60
|
+
invocationToolUses?: number;
|
|
61
|
+
invocationUsage?: LifetimeUsage;
|
|
62
|
+
compactionCount?: number;
|
|
63
|
+
// Live activity — activeTools is seeded by name (each entry calls addActiveTool)
|
|
64
|
+
invocationTurnCount?: number;
|
|
65
|
+
lifetimeTurnCount?: number;
|
|
66
|
+
activeTools?: string[];
|
|
67
|
+
responseText?: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export class SubagentState {
|
|
71
|
+
// Execution phase and terminal outcome are orthogonal.
|
|
72
|
+
private _phase: SubagentPhase;
|
|
73
|
+
get phase(): SubagentPhase { return this._phase; }
|
|
74
|
+
|
|
75
|
+
private _outcome?: SubagentOutcome;
|
|
76
|
+
get outcome(): SubagentOutcome | undefined { return this._outcome; }
|
|
77
|
+
|
|
78
|
+
private _result?: string;
|
|
79
|
+
get result(): string | undefined { return this._result; }
|
|
80
|
+
|
|
81
|
+
private _error?: string;
|
|
82
|
+
get error(): string | undefined { return this._error; }
|
|
83
|
+
|
|
84
|
+
// Never-started marker — a queued agent stopped before its slot opened has no
|
|
85
|
+
// result, as distinct from a started agent that produced none.
|
|
86
|
+
private _stoppedWhileQueued: boolean;
|
|
87
|
+
get stoppedWhileQueued(): boolean { return this._stoppedWhileQueued; }
|
|
88
|
+
|
|
89
|
+
private _startedAt: number;
|
|
90
|
+
get startedAt(): number { return this._startedAt; }
|
|
91
|
+
|
|
92
|
+
private _completedAt?: number;
|
|
93
|
+
get completedAt(): number | undefined { return this._completedAt; }
|
|
94
|
+
|
|
95
|
+
// Result delivery — whether the parent has collected the outcome (orthogonal to phase).
|
|
96
|
+
private _consumedAt?: number;
|
|
97
|
+
get consumedAt(): number | undefined { return this._consumedAt; }
|
|
98
|
+
get consumed(): boolean { return this._consumedAt != null; }
|
|
99
|
+
|
|
100
|
+
// Stats — accumulated via mutation methods, readable via getters
|
|
101
|
+
private _toolUses: number;
|
|
102
|
+
get toolUses(): number { return this._toolUses; }
|
|
103
|
+
|
|
104
|
+
private _lifetimeUsage: LifetimeUsage;
|
|
105
|
+
get lifetimeUsage(): Readonly<LifetimeUsage> { return this._lifetimeUsage; }
|
|
106
|
+
|
|
107
|
+
private _invocationStartedAt: number;
|
|
108
|
+
get invocationStartedAt(): number { return this._invocationStartedAt; }
|
|
109
|
+
|
|
110
|
+
private _invocationToolUses: number;
|
|
111
|
+
get invocationToolUses(): number { return this._invocationToolUses; }
|
|
112
|
+
|
|
113
|
+
private _invocationUsage: LifetimeUsage;
|
|
114
|
+
get invocationUsage(): Readonly<LifetimeUsage> { return this._invocationUsage; }
|
|
115
|
+
|
|
116
|
+
private _compactionCount: number;
|
|
117
|
+
get compactionCount(): number { return this._compactionCount; }
|
|
118
|
+
|
|
119
|
+
// Live activity — invocation values reset on resume; lifetime values do not.
|
|
120
|
+
private _invocationTurnCount: number;
|
|
121
|
+
get invocationTurnCount(): number { return this._invocationTurnCount; }
|
|
122
|
+
|
|
123
|
+
private _lifetimeTurnCount: number;
|
|
124
|
+
get lifetimeTurnCount(): number { return this._lifetimeTurnCount; }
|
|
125
|
+
|
|
126
|
+
private _activeTools = new Map<string, string>();
|
|
127
|
+
get activeTools(): ReadonlyMap<string, string> { return this._activeTools; }
|
|
128
|
+
|
|
129
|
+
private _toolKeySeq = 0;
|
|
130
|
+
|
|
131
|
+
private _responseText: string;
|
|
132
|
+
get responseText(): string { return this._responseText; }
|
|
133
|
+
|
|
134
|
+
constructor(init: SubagentStateInit = {}) {
|
|
135
|
+
this._phase = init.phase ?? "queued";
|
|
136
|
+
this._outcome = init.outcome;
|
|
137
|
+
this._result = init.result;
|
|
138
|
+
this._error = init.error;
|
|
139
|
+
this._stoppedWhileQueued = init.stoppedWhileQueued ?? false;
|
|
140
|
+
this._startedAt = init.startedAt ?? Date.now();
|
|
141
|
+
this._completedAt = init.completedAt;
|
|
142
|
+
this._consumedAt = init.consumedAt;
|
|
143
|
+
this._toolUses = init.toolUses ?? 0;
|
|
144
|
+
// Copy so later addUsage() calls cannot mutate either caller-owned object.
|
|
145
|
+
this._lifetimeUsage = init.lifetimeUsage
|
|
146
|
+
? { ...init.lifetimeUsage }
|
|
147
|
+
: { input: 0, output: 0, cacheWrite: 0 };
|
|
148
|
+
this._invocationStartedAt = init.invocationStartedAt ?? this._startedAt;
|
|
149
|
+
this._invocationToolUses = init.invocationToolUses ?? this._toolUses;
|
|
150
|
+
this._invocationUsage = init.invocationUsage
|
|
151
|
+
? { ...init.invocationUsage }
|
|
152
|
+
: { ...this._lifetimeUsage };
|
|
153
|
+
this._compactionCount = init.compactionCount ?? 0;
|
|
154
|
+
this._invocationTurnCount = init.invocationTurnCount ?? 0;
|
|
155
|
+
this._lifetimeTurnCount = init.lifetimeTurnCount ?? this._invocationTurnCount;
|
|
156
|
+
this._responseText = init.responseText ?? "";
|
|
157
|
+
for (const name of init.activeTools ?? []) {
|
|
158
|
+
this.addActiveTool(name);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** Running or queued — still live. */
|
|
163
|
+
isActive(): boolean {
|
|
164
|
+
return isActivePhase(this._phase);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** Terminal outcomes that did not complete normally. */
|
|
168
|
+
isTerminalError(): boolean {
|
|
169
|
+
return isTerminalErrorOutcome(this._outcome);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** Actively running (excludes queued). */
|
|
173
|
+
isRunning(): boolean {
|
|
174
|
+
return isRunningPhase(this._phase);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** Whether a steer message can be delivered — the agent must be running. */
|
|
178
|
+
canBeSteered(): boolean {
|
|
179
|
+
return isRunningPhase(this._phase);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** Increment lifetime and current-invocation tool use counts. */
|
|
183
|
+
incrementToolUses(): void {
|
|
184
|
+
this._toolUses++;
|
|
185
|
+
this._invocationToolUses++;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** Accumulate a usage delta into lifetime and current-invocation windows. */
|
|
189
|
+
addUsage(delta: { input: number; output: number; cacheWrite: number }): void {
|
|
190
|
+
addUsage(this._lifetimeUsage, delta);
|
|
191
|
+
addUsage(this._invocationUsage, delta);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** Increment compaction count. Called by record-observer on compaction_end. */
|
|
195
|
+
incrementCompactions(): void {
|
|
196
|
+
this._compactionCount++;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** Record a completed model turn for both invocation and lifetime metrics. */
|
|
200
|
+
incrementTurnCount(): void {
|
|
201
|
+
this._invocationTurnCount++;
|
|
202
|
+
this._lifetimeTurnCount++;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** Record a tool starting. Called by record-observer on tool_execution_start. */
|
|
206
|
+
addActiveTool(toolName: string): void {
|
|
207
|
+
this._activeTools.set(toolName + "_" + (++this._toolKeySeq), toolName);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** Remove one active tool by name (first match). Called by record-observer on tool_execution_end. */
|
|
211
|
+
removeActiveTool(toolName: string): void {
|
|
212
|
+
for (const [key, name] of this._activeTools) {
|
|
213
|
+
if (name === toolName) {
|
|
214
|
+
this._activeTools.delete(key);
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/** Reset the current response text. Called by record-observer on message_start. */
|
|
221
|
+
resetResponseText(): void {
|
|
222
|
+
this._responseText = "";
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** Append a text delta to the current response text. Called by record-observer on message_update. */
|
|
226
|
+
appendResponseText(delta: string): void {
|
|
227
|
+
this._responseText += delta;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/** Transition to running phase and set the execution-start timestamp. */
|
|
231
|
+
markRunning(startedAt: number): void {
|
|
232
|
+
this._phase = "running";
|
|
233
|
+
this._outcome = undefined;
|
|
234
|
+
this._startedAt = startedAt;
|
|
235
|
+
this._invocationStartedAt = startedAt;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
markCompleted(result: string, completedAt?: number): void {
|
|
239
|
+
this.markResult("completed", result, completedAt);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
markTurnLimited(result: string, completedAt?: number): void {
|
|
243
|
+
this.markResult("turn_limit", result, completedAt);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
markTimedOut(error: unknown, completedAt?: number): void {
|
|
247
|
+
this.markFailure("timeout", error, completedAt);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
markQueueTimedOut(error: unknown, completedAt?: number): void {
|
|
251
|
+
if (this._phase === "queued") this._stoppedWhileQueued = true;
|
|
252
|
+
this.markFailure("queue_timeout", error, completedAt);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
markCancelled(completedAt?: number): void {
|
|
256
|
+
if (this._phase === "queued") this._stoppedWhileQueued = true;
|
|
257
|
+
this.markFailure("cancelled", "Subagent invocation was cancelled", completedAt);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
markError(error: unknown, completedAt?: number): void {
|
|
261
|
+
this.markFailure("error", error, completedAt);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Record the parent collected the outcome. Idempotent — keeps the first
|
|
266
|
+
* collection time (??=), so a re-read does not advance the retention clock.
|
|
267
|
+
*/
|
|
268
|
+
markConsumed(at?: number): void {
|
|
269
|
+
this._consumedAt ??= at ?? Date.now();
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/** Open a new resume invocation while preserving lifetime totals. */
|
|
273
|
+
resetForResume(startedAt: number): void {
|
|
274
|
+
this._phase = "running";
|
|
275
|
+
this._outcome = undefined;
|
|
276
|
+
this._startedAt = startedAt;
|
|
277
|
+
this._invocationStartedAt = startedAt;
|
|
278
|
+
this._invocationToolUses = 0;
|
|
279
|
+
this._invocationUsage = { input: 0, output: 0, cacheWrite: 0 };
|
|
280
|
+
this._invocationTurnCount = 0;
|
|
281
|
+
this._completedAt = undefined;
|
|
282
|
+
this._result = undefined;
|
|
283
|
+
this._error = undefined;
|
|
284
|
+
this._consumedAt = undefined;
|
|
285
|
+
this._stoppedWhileQueued = false;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
private markResult(outcome: "completed" | "turn_limit", result: string, completedAt?: number): void {
|
|
289
|
+
if (!this.isActive()) return;
|
|
290
|
+
this._phase = "terminal";
|
|
291
|
+
this._outcome = outcome;
|
|
292
|
+
this._result = result;
|
|
293
|
+
this._completedAt = completedAt ?? Date.now();
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
private markFailure(
|
|
297
|
+
outcome: "timeout" | "queue_timeout" | "cancelled" | "error",
|
|
298
|
+
error: unknown,
|
|
299
|
+
completedAt?: number,
|
|
300
|
+
): void {
|
|
301
|
+
if (!this.isActive()) return;
|
|
302
|
+
this._phase = "terminal";
|
|
303
|
+
this._outcome = outcome;
|
|
304
|
+
this._error = error instanceof Error ? error.message : String(error);
|
|
305
|
+
this._completedAt = completedAt ?? Date.now();
|
|
306
|
+
}
|
|
307
|
+
}
|