@jopqior/pi-subagents 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2705 -0
- package/LICENSE +21 -0
- package/README.md +503 -0
- package/dist/public.d.ts +331 -0
- package/dist/settings.d.ts +82 -0
- package/docs/architecture/architecture.md +1566 -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 +77 -0
- package/docs/configuration.md +364 -0
- package/docs/decisions/0001-deferred-patches.md +80 -0
- package/docs/decisions/0002-extensions-on-a-minimal-core.md +125 -0
- package/docs/decisions/0003-publish-bundled-type-declarations.md +71 -0
- package/docs/decisions/0004-reconsider-ui-direction.md +279 -0
- package/docs/decisions/0005-subagent-record-admission-policy.md +106 -0
- package/docs/decisions/0006-inherited-prompt-is-identity-only.md +104 -0
- package/docs/decisions/0007-transcript-viewer-is-not-an-overlay.md +228 -0
- package/docs/decisions/0008-inherited-region-is-shared-parts.md +81 -0
- package/docs/decisions/0009-portable-inheritance-is-provider-scoped.md +116 -0
- package/package.json +91 -0
- package/src/config/agent-types.ts +135 -0
- package/src/config/custom-agents.ts +151 -0
- package/src/config/default-agents.ts +121 -0
- package/src/config/invocation-config.ts +167 -0
- package/src/config/thinking-level.ts +58 -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 +71 -0
- package/src/handlers/widget-events.ts +49 -0
- package/src/index.ts +292 -0
- package/src/layered-settings.ts +105 -0
- package/src/lifecycle/child-lifecycle.ts +115 -0
- package/src/lifecycle/child-shutdown.ts +105 -0
- package/src/lifecycle/concurrency-limiter.ts +55 -0
- package/src/lifecycle/create-subagent-session.ts +335 -0
- package/src/lifecycle/parent-snapshot.ts +119 -0
- package/src/lifecycle/run-listeners.ts +37 -0
- package/src/lifecycle/selection-scope.ts +116 -0
- package/src/lifecycle/spawn-selection.ts +259 -0
- package/src/lifecycle/subagent-manager.ts +546 -0
- package/src/lifecycle/subagent-session.ts +347 -0
- package/src/lifecycle/subagent-state.ts +404 -0
- package/src/lifecycle/subagent.ts +885 -0
- package/src/lifecycle/turn-limits.ts +13 -0
- package/src/lifecycle/usage.ts +60 -0
- package/src/lifecycle/workspace-bracket.ts +76 -0
- package/src/lifecycle/workspace.ts +46 -0
- package/src/observation/composite-subagent-observer.ts +74 -0
- package/src/observation/notification.ts +430 -0
- package/src/observation/outcome-delivery.ts +239 -0
- package/src/observation/record-observer.ts +78 -0
- package/src/observation/renderer.ts +161 -0
- package/src/observation/subagent-events-observer.ts +148 -0
- package/src/runtime.ts +137 -0
- package/src/service/service-adapter.ts +201 -0
- package/src/service/service.ts +246 -0
- package/src/session/ask-parent-tool.ts +69 -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 +126 -0
- package/src/session/notify-parent-tool.ts +83 -0
- package/src/session/package-exclusions.ts +75 -0
- package/src/session/prompts.ts +231 -0
- package/src/session/provider-inheritance.ts +56 -0
- package/src/session/selection-catalogue.ts +143 -0
- package/src/session/session-config.ts +202 -0
- package/src/session/session-dir.ts +38 -0
- package/src/settings.ts +447 -0
- package/src/tools/agent-tool.ts +305 -0
- package/src/tools/background-spawner.ts +83 -0
- package/src/tools/foreground-runner.ts +159 -0
- package/src/tools/get-result-renderer.ts +119 -0
- package/src/tools/get-result-report.ts +84 -0
- package/src/tools/get-result-tool.ts +192 -0
- package/src/tools/helpers.ts +118 -0
- package/src/tools/result-renderer.ts +153 -0
- package/src/tools/spawn-config.ts +192 -0
- package/src/tools/steer-tool.ts +109 -0
- package/src/types.ts +143 -0
- package/src/ui/agent-widget.ts +333 -0
- package/src/ui/bounded-lines.ts +45 -0
- package/src/ui/display.ts +180 -0
- package/src/ui/glyphs.ts +62 -0
- package/src/ui/session-navigation.ts +150 -0
- package/src/ui/session-navigator.ts +255 -0
- package/src/ui/subagents-settings.ts +179 -0
- package/src/ui/transcript-content.ts +374 -0
- package/src/ui/widget-renderer.ts +301 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* turn-limits.ts — Pure turn-limit normalization for subagent execution.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from agent-runner.ts (issue #265) so the turn-counting policy has a
|
|
5
|
+
* focused home independent of session assembly. Consumed by the subagent tool's
|
|
6
|
+
* spawn-config resolution and by the turn loop in SubagentSession.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** Normalize max turns. undefined or 0 = unlimited, otherwise minimum 1. */
|
|
10
|
+
export function normalizeMaxTurns(n: number | undefined): number | undefined {
|
|
11
|
+
if (n == null || n === 0) return undefined;
|
|
12
|
+
return Math.max(1, n);
|
|
13
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/** usage.ts — Token usage: shapes, accumulator operators, session-stats readers. */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Lifetime usage components, accumulated via `message_end` events. Survives
|
|
5
|
+
* compaction (which replaces session.state.messages and would reset any
|
|
6
|
+
* stats-derived sum). cacheRead is excluded because each turn's cacheRead is
|
|
7
|
+
* the cumulative cached prefix re-read on that one call — summing across
|
|
8
|
+
* turns counts the prefix N times. See issue #38.
|
|
9
|
+
*/
|
|
10
|
+
export type LifetimeUsage = { input: number; output: number; cacheWrite: number };
|
|
11
|
+
|
|
12
|
+
/** Sum of lifetime usage components, or 0 if undefined. */
|
|
13
|
+
export function getLifetimeTotal(u?: LifetimeUsage): number {
|
|
14
|
+
return u ? u.input + u.output + u.cacheWrite : 0;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Add a usage delta into a target accumulator (mutates target). */
|
|
18
|
+
export function addUsage(into: LifetimeUsage, delta: LifetimeUsage): void {
|
|
19
|
+
into.input += delta.input;
|
|
20
|
+
into.output += delta.output;
|
|
21
|
+
into.cacheWrite += delta.cacheWrite;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Minimal shape we read from upstream `getSessionStats()`. */
|
|
25
|
+
export type SessionStatsLike = {
|
|
26
|
+
tokens: { input: number; output: number; cacheWrite: number };
|
|
27
|
+
contextUsage?: { percent: number | null };
|
|
28
|
+
};
|
|
29
|
+
export type SessionLike = { getSessionStats(): SessionStatsLike };
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Session-scoped token count: input + output + cacheWrite as reported by
|
|
33
|
+
* upstream `getSessionStats().tokens` for the *current* session window.
|
|
34
|
+
*
|
|
35
|
+
* RESETS at compaction — upstream replaces `session.state.messages` and the
|
|
36
|
+
* stats are derived from that array. For a lifetime total that survives
|
|
37
|
+
* compaction, use `getLifetimeTotal(lifetimeUsage)` instead, which reads
|
|
38
|
+
* from an independent accumulator fed by `message_end` events.
|
|
39
|
+
*
|
|
40
|
+
* Avoids upstream's `tokens.total` field, which sums per-turn `cacheRead`
|
|
41
|
+
* and so counts the cumulative cached prefix N times across N turns
|
|
42
|
+
* (issue #38).
|
|
43
|
+
*/
|
|
44
|
+
export function getSessionTokens(session: SessionLike | undefined): number {
|
|
45
|
+
if (!session) return 0;
|
|
46
|
+
try {
|
|
47
|
+
const t = session.getSessionStats().tokens;
|
|
48
|
+
return t.input + t.output + t.cacheWrite;
|
|
49
|
+
} catch { return 0; }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Context-window utilization (0–100), or null when unavailable
|
|
54
|
+
* (no model contextWindow, or post-compaction before the next response).
|
|
55
|
+
*/
|
|
56
|
+
export function getSessionContextPercent(session: SessionLike | undefined): number | null {
|
|
57
|
+
if (!session) return null;
|
|
58
|
+
try { return session.getSessionStats().contextUsage?.percent ?? null; }
|
|
59
|
+
catch { return null; }
|
|
60
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* workspace-bracket.ts — Owned prepare/dispose lifecycle for a child workspace.
|
|
3
|
+
*
|
|
4
|
+
* Captures the provider resolver (not the provider itself) so provider
|
|
5
|
+
* resolution stays lazy at run-start. The prepared Workspace is held
|
|
6
|
+
* privately; dispose() centralises the guard and addendum-unwrap so callers
|
|
7
|
+
* never reach through to workspace.dispose().resultAddendum directly.
|
|
8
|
+
*
|
|
9
|
+
* dispose() is idempotent — a workspace can outlive the run that prepared it
|
|
10
|
+
* (a child holding it for a resume), so more than one lifecycle edge may reach
|
|
11
|
+
* for it — and it deliberately does NOT catch errors: the best-effort
|
|
12
|
+
* try/catch belongs at the call site, preserving the per-caller semantics.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type {
|
|
16
|
+
Workspace,
|
|
17
|
+
WorkspaceDisposeOutcome,
|
|
18
|
+
WorkspacePrepareContext,
|
|
19
|
+
WorkspaceProvider,
|
|
20
|
+
} from "#src/lifecycle/workspace";
|
|
21
|
+
|
|
22
|
+
/** Owns the child workspace lifecycle: prepare at run-start, dispose at run-end. */
|
|
23
|
+
export class WorkspaceBracket {
|
|
24
|
+
private prepared?: Workspace;
|
|
25
|
+
private disposedWorkspace = false;
|
|
26
|
+
|
|
27
|
+
constructor(private readonly resolveProvider: () => WorkspaceProvider | undefined) {}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* True once a prepared workspace has been torn down — the directory the run
|
|
31
|
+
* used is gone. False for a bracket that never held one, and false while one
|
|
32
|
+
* is still held, so it distinguishes "no workspace" from "workspace removed".
|
|
33
|
+
*/
|
|
34
|
+
wasDisposed(): boolean {
|
|
35
|
+
return this.disposedWorkspace;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Returns true when a workspace provider is currently registered.
|
|
40
|
+
* Use to guard the `await prepare(...)` call and avoid an unnecessary
|
|
41
|
+
* microtask boundary in the no-provider path.
|
|
42
|
+
*/
|
|
43
|
+
hasProvider(): boolean {
|
|
44
|
+
return this.resolveProvider() !== undefined;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Resolve the registered provider and prepare the child workspace.
|
|
49
|
+
* Returns the workspace's cwd, or undefined when no provider is registered
|
|
50
|
+
* or the provider resolves to undefined.
|
|
51
|
+
*/
|
|
52
|
+
async prepare(ctx: WorkspacePrepareContext): Promise<string | undefined> {
|
|
53
|
+
const provider = this.resolveProvider();
|
|
54
|
+
if (!provider) return undefined;
|
|
55
|
+
this.prepared = await provider.prepare(ctx);
|
|
56
|
+
return this.prepared?.cwd;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Dispose the prepared workspace (if any) and return the result addendum
|
|
61
|
+
* verbatim. Returns an empty string when no workspace was prepared, when one
|
|
62
|
+
* was already disposed, or when the workspace returns no addendum.
|
|
63
|
+
*
|
|
64
|
+
* The workspace is released and recorded as disposed before it is torn down,
|
|
65
|
+
* so a provider whose dispose() throws still leaves the bracket reporting a
|
|
66
|
+
* gone workspace — a failed teardown makes reuse no safer than a clean one.
|
|
67
|
+
* The throw itself still propagates.
|
|
68
|
+
*/
|
|
69
|
+
dispose(outcome: WorkspaceDisposeOutcome): string {
|
|
70
|
+
const workspace = this.prepared;
|
|
71
|
+
if (!workspace) return "";
|
|
72
|
+
this.prepared = undefined;
|
|
73
|
+
this.disposedWorkspace = true;
|
|
74
|
+
return workspace.dispose(outcome)?.resultAddendum ?? "";
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* workspace.ts — The single generative extension seam (ADR 0002, Phase 16 Step 2).
|
|
3
|
+
*
|
|
4
|
+
* "Where does a child run, and what brackets the run?" is a strategy (git
|
|
5
|
+
* worktree, container, tmpdir, remote sandbox), not core behavior. The core
|
|
6
|
+
* needs only a working directory plus a disposal hook; the default — the
|
|
7
|
+
* parent's cwd, with no setup/teardown — is always correct.
|
|
8
|
+
*
|
|
9
|
+
* Unlike the observational lifecycle events in child-lifecycle.ts, this is a
|
|
10
|
+
* *generative* seam: a registered provider returns a value the core consumes
|
|
11
|
+
* synchronously at run-start. The core has no knowledge of git or worktrees.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { SubagentStatus } from "#src/lifecycle/subagent";
|
|
15
|
+
import type { SubagentType } from "#src/types";
|
|
16
|
+
|
|
17
|
+
/** Context the core hands a provider when a child run starts. */
|
|
18
|
+
export interface WorkspacePrepareContext {
|
|
19
|
+
agentId: string;
|
|
20
|
+
agentType: SubagentType;
|
|
21
|
+
baseCwd: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Outcome the core reports to a workspace when the run ends. */
|
|
25
|
+
export interface WorkspaceDisposeOutcome {
|
|
26
|
+
status: SubagentStatus;
|
|
27
|
+
description: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** What dispose may hand back for the core to fold into the child result. */
|
|
31
|
+
export interface WorkspaceDisposeResult {
|
|
32
|
+
/** Appended verbatim to the child's result text — the provider owns the wording. */
|
|
33
|
+
resultAddendum?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** A prepared working directory plus its bracketed teardown. Born complete. */
|
|
37
|
+
export interface Workspace {
|
|
38
|
+
/** The working directory — already exists when the workspace is handed back. */
|
|
39
|
+
readonly cwd: string;
|
|
40
|
+
dispose(outcome: WorkspaceDisposeOutcome): WorkspaceDisposeResult | undefined;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** The single generative seam: supplies a child's workspace. */
|
|
44
|
+
export interface WorkspaceProvider {
|
|
45
|
+
prepare(ctx: WorkspacePrepareContext): Promise<Workspace | undefined>;
|
|
46
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { debugLog } from "#src/debug";
|
|
2
|
+
import type { SubagentManagerObserver } from "#src/lifecycle/subagent-manager";
|
|
3
|
+
import type { CompactionInfo, Subagent } from "#src/types";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Fans out SubagentManager lifecycle notifications to multiple observers.
|
|
7
|
+
*
|
|
8
|
+
* Lets the manager keep its single-observer contract while several independent
|
|
9
|
+
* consumers (event/notification dispatch, the reactive widget) subscribe.
|
|
10
|
+
* Each delegate is isolated: a throw in one does not suppress the others.
|
|
11
|
+
*/
|
|
12
|
+
export class CompositeSubagentObserver implements SubagentManagerObserver {
|
|
13
|
+
private readonly delegates: SubagentManagerObserver[];
|
|
14
|
+
|
|
15
|
+
constructor(delegates: SubagentManagerObserver[]) {
|
|
16
|
+
this.delegates = [...delegates];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Register an additional observer (breaks the widget↔manager construction cycle). */
|
|
20
|
+
add(observer: SubagentManagerObserver): void {
|
|
21
|
+
this.delegates.push(observer);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
onSubagentStarted(record: Subagent): void {
|
|
25
|
+
this.dispatch((o) => o.onSubagentStarted(record), "onSubagentStarted");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
onSubagentCreated(record: Subagent): void {
|
|
29
|
+
this.dispatch((o) => o.onSubagentCreated(record), "onSubagentCreated");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
onSubagentCompleted(record: Subagent): void {
|
|
33
|
+
this.dispatch((o) => o.onSubagentCompleted(record), "onSubagentCompleted");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
onSubagentResuming(record: Subagent): void {
|
|
37
|
+
this.dispatch((o) => o.onSubagentResuming(record), "onSubagentResuming");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
onSubagentResumed(record: Subagent): void {
|
|
41
|
+
this.dispatch((o) => o.onSubagentResumed(record), "onSubagentResumed");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
onSubagentUpdate(record: Subagent, message: string): void {
|
|
45
|
+
this.dispatch((o) => o.onSubagentUpdate?.(record, message), "onSubagentUpdate");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Required here even though `SubagentManagerObserver` declares it optional:
|
|
50
|
+
* the manager's observer is always this composite, so a member it does not
|
|
51
|
+
* enumerate is dropped silently — no compiler error, no runtime error, and
|
|
52
|
+
* the announcement simply never fires.
|
|
53
|
+
*/
|
|
54
|
+
onSubagentWorkspaceNotice(record: Subagent, notice: string): void {
|
|
55
|
+
this.dispatch(
|
|
56
|
+
(o) => o.onSubagentWorkspaceNotice?.(record, notice),
|
|
57
|
+
"onSubagentWorkspaceNotice",
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
onSubagentCompacted(record: Subagent, info: CompactionInfo): void {
|
|
62
|
+
this.dispatch((o) => o.onSubagentCompacted(record, info), "onSubagentCompacted");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
private dispatch(call: (o: SubagentManagerObserver) => void, label: string): void {
|
|
66
|
+
for (const o of this.delegates) {
|
|
67
|
+
try {
|
|
68
|
+
call(o);
|
|
69
|
+
} catch (err) {
|
|
70
|
+
debugLog(`CompositeSubagentObserver.${label}`, err);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|