@gotgenes/pi-subagents 7.0.0 → 7.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -0
- package/docs/architecture/architecture.md +190 -91
- package/docs/architecture/history/phase-10-structural-decomposition.md +141 -0
- package/docs/plans/0192-define-session-context-interface.md +107 -0
- package/docs/plans/0193-runtime-owns-context-queries.md +245 -0
- package/docs/retro/0185-remove-persistent-agent-memory.md +34 -0
- package/docs/retro/0192-define-session-context-interface.md +59 -0
- package/docs/retro/0193-runtime-owns-context-queries.md +43 -0
- package/package.json +1 -1
- package/src/handlers/lifecycle.ts +4 -4
- package/src/index.ts +6 -22
- package/src/lifecycle/parent-snapshot.ts +4 -3
- package/src/runtime.ts +31 -3
- package/src/service/service-adapter.ts +14 -12
- package/src/session/context.ts +20 -6
- package/src/types.ts +21 -0
package/src/runtime.ts
CHANGED
|
@@ -6,6 +6,9 @@
|
|
|
6
6
|
* Follows the same pattern as pi-permission-system's ExtensionRuntime.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
import { buildParentSnapshot, type ParentSnapshot } from "#src/lifecycle/parent-snapshot";
|
|
10
|
+
import type { ModelInfo } from "#src/tools/spawn-config";
|
|
11
|
+
import type { SessionContext } from "#src/types";
|
|
9
12
|
import type { AgentActivityTracker } from "#src/ui/agent-activity-tracker";
|
|
10
13
|
import type { UICtx } from "#src/ui/agent-widget";
|
|
11
14
|
|
|
@@ -39,7 +42,7 @@ export interface RunConfig {
|
|
|
39
42
|
export class SubagentRuntime {
|
|
40
43
|
// ── Session state (was closure-scoped in index.ts) ───────────────────────
|
|
41
44
|
/** Active Pi session context — set on session_start, cleared on session_shutdown. */
|
|
42
|
-
currentCtx:
|
|
45
|
+
currentCtx: SessionContext | undefined = undefined;
|
|
43
46
|
/**
|
|
44
47
|
* Per-agent live activity state shared across the notification system,
|
|
45
48
|
* widget, and tool handlers. The Map itself is never replaced.
|
|
@@ -54,8 +57,8 @@ export class SubagentRuntime {
|
|
|
54
57
|
// ── Session-context methods ──────────────────────────────────────────────
|
|
55
58
|
|
|
56
59
|
/** Store the active Pi session context (called from session_start). */
|
|
57
|
-
setSessionContext(
|
|
58
|
-
this.currentCtx =
|
|
60
|
+
setSessionContext(ctx: SessionContext): void {
|
|
61
|
+
this.currentCtx = ctx;
|
|
59
62
|
}
|
|
60
63
|
|
|
61
64
|
/** Clear the session context (called from session_shutdown). */
|
|
@@ -63,6 +66,31 @@ export class SubagentRuntime {
|
|
|
63
66
|
this.currentCtx = undefined;
|
|
64
67
|
}
|
|
65
68
|
|
|
69
|
+
/**
|
|
70
|
+
* Build a parent snapshot from the current session context.
|
|
71
|
+
* Only valid during an active session (currentCtx is defined).
|
|
72
|
+
*/
|
|
73
|
+
buildSnapshot(inheritContext: boolean): ParentSnapshot {
|
|
74
|
+
|
|
75
|
+
return buildParentSnapshot(this.currentCtx!, inheritContext);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Extract model info from the current session context. */
|
|
79
|
+
getModelInfo(): ModelInfo {
|
|
80
|
+
return {
|
|
81
|
+
parentModel: this.currentCtx?.model as ModelInfo["parentModel"],
|
|
82
|
+
modelRegistry: this.currentCtx?.modelRegistry,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Extract session identity from the current session context. */
|
|
87
|
+
getSessionInfo(): { parentSessionFile: string; parentSessionId: string } {
|
|
88
|
+
return {
|
|
89
|
+
parentSessionFile: this.currentCtx?.sessionManager.getSessionFile() ?? "",
|
|
90
|
+
parentSessionId: this.currentCtx?.sessionManager.getSessionId() ?? "",
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
66
94
|
// ── Widget delegation methods ─────────────────────────────────────────────
|
|
67
95
|
|
|
68
96
|
/** Delegate to widget.setUICtx — no-op when widget is null. */
|
|
@@ -5,12 +5,10 @@
|
|
|
5
5
|
* (stripping non-serializable fields), and session gating.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
9
8
|
import type { ParentSnapshot } from "#src/lifecycle/parent-snapshot";
|
|
10
|
-
import { buildParentSnapshot } from "#src/lifecycle/parent-snapshot";
|
|
11
9
|
import type { SubagentRecord, SubagentsService } from "#src/service/service";
|
|
12
10
|
import type { ModelRegistry } from "#src/session/model-resolver";
|
|
13
|
-
import type { AgentRecord } from "#src/types";
|
|
11
|
+
import type { AgentRecord, SessionContext } from "#src/types";
|
|
14
12
|
|
|
15
13
|
/** Narrow interface for the AgentManager — avoids coupling to the concrete class. */
|
|
16
14
|
export interface AgentManagerLike {
|
|
@@ -23,23 +21,30 @@ export interface AgentManagerLike {
|
|
|
23
21
|
queueSteer(id: string, message: string): boolean;
|
|
24
22
|
}
|
|
25
23
|
|
|
24
|
+
/**
|
|
25
|
+
* Narrow runtime interface consumed by the service adapter.
|
|
26
|
+
* `SubagentRuntime` satisfies this structurally; tests use plain stubs.
|
|
27
|
+
*/
|
|
28
|
+
export interface ServiceRuntimeLike {
|
|
29
|
+
readonly currentCtx: SessionContext | undefined;
|
|
30
|
+
buildSnapshot(inheritContext: boolean): ParentSnapshot;
|
|
31
|
+
}
|
|
32
|
+
|
|
26
33
|
/** Create a SubagentsService backed by the given dependencies. */
|
|
27
34
|
export function createSubagentsService(
|
|
28
35
|
manager: AgentManagerLike,
|
|
29
36
|
resolveModel: (input: string, registry: ModelRegistry) => unknown,
|
|
30
|
-
|
|
31
|
-
getModelRegistry: () => ModelRegistry | undefined,
|
|
37
|
+
runtime: ServiceRuntimeLike,
|
|
32
38
|
): SubagentsService {
|
|
33
39
|
return {
|
|
34
40
|
spawn(type: string, prompt: string, options?) {
|
|
35
|
-
|
|
36
|
-
if (!session) {
|
|
41
|
+
if (!runtime.currentCtx) {
|
|
37
42
|
throw new Error("No active session — cannot spawn agents outside a session.");
|
|
38
43
|
}
|
|
39
44
|
|
|
40
45
|
let model: unknown;
|
|
41
46
|
if (options?.model) {
|
|
42
|
-
const registry =
|
|
47
|
+
const registry = runtime.currentCtx.modelRegistry;
|
|
43
48
|
if (!registry) {
|
|
44
49
|
throw new Error("No model registry available.");
|
|
45
50
|
}
|
|
@@ -53,10 +58,7 @@ export function createSubagentsService(
|
|
|
53
58
|
const description = options?.description ?? prompt.slice(0, 80);
|
|
54
59
|
const isBackground = !(options?.foreground ?? false);
|
|
55
60
|
|
|
56
|
-
const snapshot =
|
|
57
|
-
session.ctx as ExtensionContext,
|
|
58
|
-
options?.inheritContext,
|
|
59
|
-
);
|
|
61
|
+
const snapshot = runtime.buildSnapshot(options?.inheritContext ?? false);
|
|
60
62
|
return manager.spawn(snapshot, type, prompt, {
|
|
61
63
|
description,
|
|
62
64
|
model,
|
package/src/session/context.ts
CHANGED
|
@@ -3,7 +3,19 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import type { TextContent } from "@earendil-works/pi-ai";
|
|
6
|
-
import type {
|
|
6
|
+
import type { SessionContext } from "#src/types";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Minimal structural types for session branch entries consumed by buildParentContext.
|
|
10
|
+
* `getBranch()` returns `unknown[]` in SessionContext (ISP), so we cast to these
|
|
11
|
+
* local shapes instead of coupling to the SDK's SessionEntry type.
|
|
12
|
+
*/
|
|
13
|
+
type MessageEntry = {
|
|
14
|
+
type: "message";
|
|
15
|
+
message: { role: string; content: string | { type: string }[] };
|
|
16
|
+
};
|
|
17
|
+
type CompactionEntry = { type: "compaction"; summary?: string };
|
|
18
|
+
type BranchEntry = MessageEntry | CompactionEntry | { type: string };
|
|
7
19
|
|
|
8
20
|
/** Type predicate: narrow an unknown content block to TextContent. */
|
|
9
21
|
function isTextContent(c: unknown): c is TextContent {
|
|
@@ -23,15 +35,16 @@ export function extractText(content: unknown[]): string {
|
|
|
23
35
|
* Used when inherit_context is true to give the subagent visibility
|
|
24
36
|
* into what has been discussed/done so far.
|
|
25
37
|
*/
|
|
26
|
-
export function buildParentContext(ctx:
|
|
38
|
+
export function buildParentContext(ctx: SessionContext): string {
|
|
27
39
|
const entries = ctx.sessionManager.getBranch();
|
|
28
40
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- getBranch() may return undefined at runtime despite its type
|
|
29
41
|
if (!entries || entries.length === 0) return "";
|
|
30
42
|
|
|
31
43
|
const parts: string[] = [];
|
|
32
44
|
|
|
33
|
-
for (const
|
|
34
|
-
if (
|
|
45
|
+
for (const rawEntry of entries as BranchEntry[]) {
|
|
46
|
+
if (rawEntry.type === "message") {
|
|
47
|
+
const entry = rawEntry as MessageEntry;
|
|
35
48
|
const msg = entry.message;
|
|
36
49
|
if (msg.role === "user") {
|
|
37
50
|
const text = typeof msg.content === "string"
|
|
@@ -39,12 +52,13 @@ export function buildParentContext(ctx: ExtensionContext): string {
|
|
|
39
52
|
: extractText(msg.content);
|
|
40
53
|
if (text.trim()) parts.push(`[User]: ${text.trim()}`);
|
|
41
54
|
} else if (msg.role === "assistant") {
|
|
42
|
-
const text = extractText(msg.content);
|
|
55
|
+
const text = typeof msg.content === "string" ? msg.content : extractText(msg.content);
|
|
43
56
|
if (text.trim()) parts.push(`[Assistant]: ${text.trim()}`);
|
|
44
57
|
}
|
|
45
58
|
// Skip toolResult messages — too verbose for context
|
|
46
|
-
} else if (
|
|
59
|
+
} else if (rawEntry.type === "compaction") {
|
|
47
60
|
// Include compaction summaries — they're already condensed
|
|
61
|
+
const entry = rawEntry as CompactionEntry;
|
|
48
62
|
if (entry.summary) {
|
|
49
63
|
parts.push(`[Summary]: ${entry.summary}`);
|
|
50
64
|
}
|
package/src/types.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import type { ThinkingLevel } from "@earendil-works/pi-ai";
|
|
6
6
|
import type { AgentSessionEvent } from "@earendil-works/pi-coding-agent";
|
|
7
|
+
import type { ModelRegistry } from "#src/session/model-resolver";
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
export { AgentRecord } from "#src/lifecycle/agent-record";
|
|
@@ -77,6 +78,26 @@ export interface AgentInvocation {
|
|
|
77
78
|
isolation?: IsolationMode;
|
|
78
79
|
}
|
|
79
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Narrow shell-exec callback replacing `ExtensionAPI` in `detectEnv()`.
|
|
83
|
+
* Matches the shape of `pi.exec()` without carrying an SDK dependency.
|
|
84
|
+
*/
|
|
85
|
+
/**
|
|
86
|
+
* Narrow interface capturing the ExtensionContext fields SubagentRuntime needs.
|
|
87
|
+
* Avoids coupling runtime to the full SDK ExtensionContext surface (ISP).
|
|
88
|
+
*/
|
|
89
|
+
export interface SessionContext {
|
|
90
|
+
readonly cwd: string;
|
|
91
|
+
readonly model: unknown;
|
|
92
|
+
readonly modelRegistry: ModelRegistry | undefined;
|
|
93
|
+
getSystemPrompt(): string;
|
|
94
|
+
readonly sessionManager: {
|
|
95
|
+
getSessionFile(): string | undefined;
|
|
96
|
+
getSessionId(): string;
|
|
97
|
+
getBranch(): unknown[];
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
80
101
|
/**
|
|
81
102
|
* Narrow shell-exec callback replacing `ExtensionAPI` in `detectEnv()`.
|
|
82
103
|
* Matches the shape of `pi.exec()` without carrying an SDK dependency.
|