@herbertgao/pi-subagents 0.18.5 → 0.22.0-local.1
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 +16 -0
- package/README.md +1 -1
- package/docs/packages.md +211 -0
- package/docs/rpc.md +61 -63
- package/docs/workflows.md +24 -16
- package/package.json +6 -6
- package/src/agent-file-toggle.ts +51 -14
- package/src/agent-manager.ts +123 -55
- package/src/agent-runner.ts +144 -13
- package/src/agent-source-badge.ts +78 -0
- package/src/completion-outbox.ts +122 -0
- package/src/custom-agents.ts +171 -79
- package/src/index.ts +598 -297
- package/src/invocation-config.ts +1 -1
- package/src/mention-clone.ts +48 -72
- package/src/nested-tools.ts +1 -1
- package/src/output-file.ts +15 -6
- package/src/package-resources.ts +724 -0
- package/src/schedule.ts +1 -1
- package/src/settings.ts +66 -0
- package/src/types.ts +26 -4
- package/src/ui/agent-widget.ts +2 -0
- package/src/ui/conversation-viewer.ts +354 -32
- package/src/ui/fleet-list.ts +33 -15
- package/src/workflow/collisions.ts +4 -1
- package/src/workflow/host.ts +6 -0
- package/src/workflow/json-schema.ts +9 -8
- package/src/workflow/saved.ts +48 -2
package/src/invocation-config.ts
CHANGED
package/src/mention-clone.ts
CHANGED
|
@@ -12,31 +12,23 @@
|
|
|
12
12
|
* So the turn happens somewhere else. The conversation is cloned into a
|
|
13
13
|
* throwaway in-memory session — same messages, same system prompt, same model —
|
|
14
14
|
* and that copy takes the turn off-screen. A literal clone: the session's own
|
|
15
|
-
* entries,
|
|
16
|
-
*
|
|
15
|
+
* entries, not `inherit_context`'s text rendering of them.
|
|
16
|
+
*
|
|
17
|
+
* Pi builds every request from the session manager, so that is where the
|
|
18
|
+
* clone's history goes: the parent's root-to-leaf entries, handed to
|
|
19
|
+
* `SessionManager.inMemory`. The clone projects them itself, compaction and
|
|
20
|
+
* branch summaries included, so a long conversation clones as what the main
|
|
21
|
+
* model is actually working from. A conversation with nothing in it yet clones
|
|
22
|
+
* to nothing in it yet, which is the correct answer rather than a failure.
|
|
17
23
|
*
|
|
18
24
|
* Cloned from memory rather than from the session file, which cannot be relied
|
|
19
25
|
* on: `SessionManager._persist` withholds every write until the first assistant
|
|
20
26
|
* message lands, so a fork taken before then reads an empty file and throws.
|
|
21
|
-
* `buildSessionContext()` has no such timing, and is compaction-aware — it walks
|
|
22
|
-
* the leaf path and substitutes the summary for entries folded into it, so a
|
|
23
|
-
* long conversation clones as what the main model is actually working from. A
|
|
24
|
-
* conversation with nothing in it yet clones to nothing in it yet, which is the
|
|
25
|
-
* correct answer rather than a failure.
|
|
26
|
-
*
|
|
27
|
-
* It is also the oldest of the equivalent Pi APIs — `buildContextEntries` on
|
|
28
|
-
* ReadonlySessionManager and the `sessionEntryToContextMessages` export both
|
|
29
|
-
* arrived in 0.80.5 — where this one has been exported unchanged from before
|
|
30
|
-
* the declared peer floor, and is the same code path (`byId` is only an index
|
|
31
|
-
* cache, so passing it or not cannot change the result). Keeping the floor
|
|
32
|
-
* honest costs nothing here: see the `compat-floor-pi` job.
|
|
33
27
|
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* field instead lets `createAgentSession` resolve it from settings, which is
|
|
39
|
-
* that real level.
|
|
28
|
+
* The system prompt cannot ride along in those entries. Pi rebuilds it from the
|
|
29
|
+
* session's own options on every request and patches the difference over the
|
|
30
|
+
* transcript, so the parent's prompt would be replaced by the clone's. It goes
|
|
31
|
+
* in through the resource loader's prompt override instead.
|
|
40
32
|
*
|
|
41
33
|
* Three details make the spawn belong to the real session rather than the
|
|
42
34
|
* clone:
|
|
@@ -63,8 +55,6 @@
|
|
|
63
55
|
|
|
64
56
|
import type { Model } from "@earendil-works/pi-ai"
|
|
65
57
|
import {
|
|
66
|
-
buildSessionContext,
|
|
67
|
-
convertToLlm,
|
|
68
58
|
createAgentSession,
|
|
69
59
|
DefaultResourceLoader,
|
|
70
60
|
type ExtensionContext,
|
|
@@ -74,7 +64,7 @@ import {
|
|
|
74
64
|
} from "@earendil-works/pi-coding-agent"
|
|
75
65
|
import { runInChildSessionContext } from "./child-context.js"
|
|
76
66
|
import { agentMentionReminder } from "./mention.js"
|
|
77
|
-
import type { SubagentType
|
|
67
|
+
import type { SubagentType } from "./types.js"
|
|
78
68
|
|
|
79
69
|
export interface MentionCloneOptions {
|
|
80
70
|
/** The MAIN session's context — what the spawn is attributed to, and the
|
|
@@ -156,54 +146,40 @@ export async function runMentionClone(
|
|
|
156
146
|
const parentModelRuntime = (
|
|
157
147
|
ctx.modelRegistry as unknown as { runtime?: unknown }
|
|
158
148
|
).runtime
|
|
159
|
-
//
|
|
160
|
-
//
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
)
|
|
165
|
-
//
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
for (const message of convertToLlm([entry]))
|
|
182
|
-
sessionManager.appendMessage(message)
|
|
183
|
-
} else {
|
|
184
|
-
sessionManager.appendMessage(entry)
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
const systemPrompt = ctx.getSystemPrompt?.()
|
|
188
|
-
const created = await runInChildSessionContext(async () => {
|
|
189
|
-
const resourceLoader = new DefaultResourceLoader({
|
|
190
|
-
cwd: ctx.cwd,
|
|
191
|
-
agentDir: getAgentDir(),
|
|
192
|
-
extensionFactories: [
|
|
193
|
-
(pi) => {
|
|
194
|
-
pi.on("before_agent_start", () =>
|
|
195
|
-
systemPrompt ? { systemPrompt } : undefined,
|
|
196
|
-
)
|
|
197
|
-
},
|
|
198
|
-
],
|
|
199
|
-
})
|
|
200
|
-
await resourceLoader.reload()
|
|
201
|
-
return createAgentSession({
|
|
149
|
+
// Absent when the session has no level of its own; the clone then takes the
|
|
150
|
+
// settings level, which is what a session that never ran `/think` is on.
|
|
151
|
+
const thinkingLevel = ctx.thinkingLevel
|
|
152
|
+
// The live system prompt, not the one the clone would build from cwd and
|
|
153
|
+
// agentDir — extensions contribute to it per turn. Everything the loader
|
|
154
|
+
// would add on top (AGENTS.md, skills, APPEND_SYSTEM.md) is already in the
|
|
155
|
+
// rendered prompt, so it is suppressed, as agent-runner does.
|
|
156
|
+
const systemPrompt = ctx.getSystemPrompt()
|
|
157
|
+
const loader = new DefaultResourceLoader({
|
|
158
|
+
cwd: ctx.cwd,
|
|
159
|
+
agentDir: getAgentDir(),
|
|
160
|
+
noExtensions: true,
|
|
161
|
+
noSkills: true,
|
|
162
|
+
noPromptTemplates: true,
|
|
163
|
+
noThemes: true,
|
|
164
|
+
noContextFiles: true,
|
|
165
|
+
systemPromptOverride: (base: string | undefined) => systemPrompt || base,
|
|
166
|
+
appendSystemPromptOverride: () => [],
|
|
167
|
+
})
|
|
168
|
+
await runInChildSessionContext(() => loader.reload())
|
|
169
|
+
const created = await runInChildSessionContext(() =>
|
|
170
|
+
createAgentSession({
|
|
202
171
|
cwd: ctx.cwd,
|
|
203
|
-
//
|
|
204
|
-
//
|
|
205
|
-
|
|
206
|
-
|
|
172
|
+
// The parent's root-to-leaf path, which the clone projects itself:
|
|
173
|
+
// compaction applied, branch summaries substituted. Not `getEntries()`,
|
|
174
|
+
// whose last entry is not the leaf after `/tree` navigation. In memory,
|
|
175
|
+
// so nothing about the copy is persisted and the real session stays
|
|
176
|
+
// untouched.
|
|
177
|
+
sessionManager: SessionManager.inMemory(
|
|
178
|
+
ctx.cwd,
|
|
179
|
+
undefined,
|
|
180
|
+
ctx.sessionManager.getBranch(),
|
|
181
|
+
),
|
|
182
|
+
resourceLoader: loader,
|
|
207
183
|
model: ctx.model as Model<never> | undefined,
|
|
208
184
|
...(thinkingLevel && { thinkingLevel }),
|
|
209
185
|
modelRegistry: ctx.modelRegistry,
|
|
@@ -220,8 +196,8 @@ export async function runMentionClone(
|
|
|
220
196
|
// agent-runner's `tools: sessionTools` beside its nested `customTools`.
|
|
221
197
|
tools: [cloneAgentTool.name],
|
|
222
198
|
customTools: [cloneAgentTool],
|
|
223
|
-
} as Parameters<typeof createAgentSession>[0])
|
|
224
|
-
|
|
199
|
+
} as Parameters<typeof createAgentSession>[0]),
|
|
200
|
+
)
|
|
225
201
|
session = created.session
|
|
226
202
|
|
|
227
203
|
// User text first, reminder after — the order Claude Code's attachment
|
package/src/nested-tools.ts
CHANGED
package/src/output-file.ts
CHANGED
|
@@ -122,17 +122,27 @@ export function streamToOutputFile(
|
|
|
122
122
|
cwd: string,
|
|
123
123
|
startIndex?: number,
|
|
124
124
|
): () => void {
|
|
125
|
-
// Index of the first message this stream is responsible for. A
|
|
126
|
-
// messages[0] as the initial prompt entry, so it starts at 1. A resume hands
|
|
125
|
+
// Index of the first message this stream is responsible for. A resume hands
|
|
127
126
|
// in the session's length as of just before the run: the session already
|
|
128
127
|
// holds every prior turn, and re-emitting those would duplicate history that
|
|
129
|
-
// is already in the file.
|
|
130
|
-
|
|
128
|
+
// is already in the file. A spawn has already written its prompt as the
|
|
129
|
+
// initial entry, so it starts after the first user message — found on the
|
|
130
|
+
// first flush, because pi puts a system message ahead of it and adds both
|
|
131
|
+
// only once the prompt runs.
|
|
132
|
+
let writtenCount = startIndex
|
|
131
133
|
|
|
132
134
|
const flush = () => {
|
|
133
135
|
const messages = session.messages
|
|
136
|
+
if (writtenCount === undefined) {
|
|
137
|
+
const prompt = messages.findIndex((m) => m.role === "user")
|
|
138
|
+
if (prompt === -1) return
|
|
139
|
+
writtenCount = prompt + 1
|
|
140
|
+
}
|
|
134
141
|
while (writtenCount < messages.length) {
|
|
135
|
-
const msg = messages[writtenCount]
|
|
142
|
+
const msg = messages[writtenCount++]
|
|
143
|
+
// Pi's prompt and tool-loadout records; a Claude Code transcript has no
|
|
144
|
+
// such entry.
|
|
145
|
+
if (msg.role === "system") continue
|
|
136
146
|
const entry = {
|
|
137
147
|
isSidechain: true,
|
|
138
148
|
agentId,
|
|
@@ -151,7 +161,6 @@ export function streamToOutputFile(
|
|
|
151
161
|
} catch {
|
|
152
162
|
/* ignore write errors */
|
|
153
163
|
}
|
|
154
|
-
writtenCount++
|
|
155
164
|
}
|
|
156
165
|
}
|
|
157
166
|
|