pi-squire 0.14.0 → 0.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 +116 -1
- package/README.md +179 -70
- package/package.json +3 -3
- package/src/agents.ts +361 -111
- package/src/child.ts +72 -21
- package/src/index.ts +242 -66
- package/src/navigator.ts +1 -1
- package/src/new-delegate.ts +17 -8
- package/src/result-sessions.ts +39 -0
- package/src/results.ts +2 -0
- package/src/resume-checks.ts +45 -12
- package/src/resume.ts +7 -2
package/src/results.ts
CHANGED
|
@@ -35,6 +35,8 @@ const INBOUND = "message:inbound";
|
|
|
35
35
|
export type Delegation = {
|
|
36
36
|
id: string;
|
|
37
37
|
agent: string;
|
|
38
|
+
/** The agent's `display_name` at launch, shown instead of `agent`; absent when it had none. */
|
|
39
|
+
displayName?: string;
|
|
38
40
|
model: string;
|
|
39
41
|
thinking: string;
|
|
40
42
|
/** The delegate's session and window name; absent in records from before labels, where it was `<agent>-<8 id characters>`. */
|
package/src/resume-checks.ts
CHANGED
|
@@ -8,8 +8,8 @@ import { isAbsolute } from "node:path";
|
|
|
8
8
|
|
|
9
9
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
10
10
|
|
|
11
|
-
import type
|
|
12
|
-
import { sessionHeaderCwd, sessionIdOf } from "./result-sessions.ts";
|
|
11
|
+
import { type Agent, disabledProblem, isThinking, withoutLocked } from "./agents.ts";
|
|
12
|
+
import { sessionHeaderCwd, sessionIdOf, sessionThinking } from "./result-sessions.ts";
|
|
13
13
|
import { deriveState, finishedModel, locateSession, type StartedRecorded } from "./results.ts";
|
|
14
14
|
import type { ResumeDeps, ResumeRequest } from "./resume.ts";
|
|
15
15
|
import { requireTmuxVersion, type TmuxWindow } from "./tmux.ts";
|
|
@@ -79,10 +79,35 @@ export function sessionFor(record: StartedRecorded, ctx: ExtensionContext): Sess
|
|
|
79
79
|
return { path, cwd, dir };
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
/**
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
82
|
+
/**
|
|
83
|
+
* What a resume runs with when the call passes nothing. A field the agent
|
|
84
|
+
* locks takes the agent file's current value, or the parent's current one when
|
|
85
|
+
* the file sets none, even when the delegate last ran on another (a fallback
|
|
86
|
+
* after a usage limit, or a value from before the file changed). A field it
|
|
87
|
+
* does not lock keeps what the delegate last ran on: the model of its last
|
|
88
|
+
* answer, and the thinking level its session last changed to, or the
|
|
89
|
+
* recorded ones when the session does not say. The models are in order, the
|
|
90
|
+
* agent's fallbacks after the first.
|
|
91
|
+
*/
|
|
92
|
+
export function resumeDefaults(
|
|
93
|
+
record: StartedRecorded,
|
|
94
|
+
agent: Agent,
|
|
95
|
+
sessionPath: string,
|
|
96
|
+
parent: { model?: string; thinking: string },
|
|
97
|
+
): { models: string[]; thinking: string } {
|
|
98
|
+
const first = agent.locked.includes("model")
|
|
99
|
+
? (agent.model ?? parent.model)
|
|
100
|
+
: (finishedModel(sessionPath) ?? record.result?.finishedModel ?? record.model);
|
|
101
|
+
const thinking = agent.locked.includes("thinking")
|
|
102
|
+
? (agent.thinking ?? parent.thinking)
|
|
103
|
+
: lastThinking(record, sessionPath);
|
|
104
|
+
return { models: [...new Set([...(first === undefined ? [] : [first]), ...agent.fallback])], thinking };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** The thinking level the delegate last ran on: its session's last change, else the level recorded at its start. */
|
|
108
|
+
function lastThinking(record: StartedRecorded, sessionPath: string): string {
|
|
109
|
+
const level = sessionThinking(sessionPath);
|
|
110
|
+
return level !== undefined && isThinking(level) ? level : record.thinking;
|
|
86
111
|
}
|
|
87
112
|
|
|
88
113
|
/** Steps 5 and 6: the session file and the agent, and no resume of the delegation already under way. */
|
|
@@ -92,7 +117,10 @@ export function recordCheck(
|
|
|
92
117
|
ctx: ExtensionContext,
|
|
93
118
|
): { session: SessionFile; agent: Agent } {
|
|
94
119
|
const session = sessionFor(record, ctx);
|
|
95
|
-
const
|
|
120
|
+
const roster = deps.roster(ctx);
|
|
121
|
+
const disabled = disabledProblem(roster, record.agent);
|
|
122
|
+
if (disabled !== undefined) throw new Error(`${disabled}, so delegation ${record.id} is not resumed`);
|
|
123
|
+
const agent = roster.agents.find((item) => item.name === record.agent);
|
|
96
124
|
if (agent === undefined) {
|
|
97
125
|
throw new Error(
|
|
98
126
|
`agent ${JSON.stringify(record.agent)} is no longer in the roster, so delegation ${record.id} is not resumed`,
|
|
@@ -113,12 +141,17 @@ export async function settleModel(
|
|
|
113
141
|
},
|
|
114
142
|
ctx: ExtensionContext,
|
|
115
143
|
): Promise<{ thinking: string; chosen: Awaited<ReturnType<ResumeDeps["chooseModel"]>> }> {
|
|
116
|
-
const { record, agent, session
|
|
117
|
-
const
|
|
144
|
+
const { record, agent, session } = input;
|
|
145
|
+
const defaults = resumeDefaults(record, agent, session.path, deps.parentDefaults(ctx));
|
|
146
|
+
const { request, notice } = withoutLocked(agent, input.request, {
|
|
147
|
+
model: defaults.models[0],
|
|
148
|
+
thinking: defaults.thinking,
|
|
149
|
+
});
|
|
150
|
+
const thinking = request.thinking ?? defaults.thinking;
|
|
118
151
|
const badThinking = deps.thinkingProblem(thinking);
|
|
119
152
|
if (badThinking !== undefined) throw new Error(badThinking);
|
|
120
|
-
const
|
|
121
|
-
return { thinking, chosen:
|
|
153
|
+
const chosen = await deps.chooseModel(request.model, agent, ctx.modelRegistry, defaults.models);
|
|
154
|
+
return { thinking, chosen: notice === undefined ? chosen : { ...chosen, notices: [notice, ...chosen.notices] } };
|
|
122
155
|
}
|
|
123
156
|
|
|
124
157
|
/** The call-wide prerequisites of a resume: inside tmux, a new enough tmux, a valid placement setting (read once). */
|
|
@@ -143,7 +176,7 @@ export async function passes(
|
|
|
143
176
|
windowCheck(record, windows);
|
|
144
177
|
const { session, agent } = recordCheck(deps, record, ctx);
|
|
145
178
|
// Model choice depends on the agent and where the delegate ended, so records alike share one answer.
|
|
146
|
-
const key = JSON.stringify([
|
|
179
|
+
const key = JSON.stringify([agent.name, resumeDefaults(record, agent, session.path, deps.parentDefaults(ctx))]);
|
|
147
180
|
let ok = models.get(key);
|
|
148
181
|
if (ok === undefined) {
|
|
149
182
|
ok = await settleModel(deps, { record, agent, session, request: {} }, ctx).then(
|
package/src/resume.ts
CHANGED
|
@@ -52,6 +52,8 @@ export type ResumeDeps = {
|
|
|
52
52
|
/** The window placement for this call; throws on an invalid setting. */
|
|
53
53
|
placement(): WindowPlacement;
|
|
54
54
|
roster(ctx: ExtensionContext): Roster;
|
|
55
|
+
/** The parent's current model and thinking level, which a field the agent locks but does not set keeps. */
|
|
56
|
+
parentDefaults(ctx: ExtensionContext): { model?: string; thinking: string };
|
|
55
57
|
chooseModel(
|
|
56
58
|
explicit: string | undefined,
|
|
57
59
|
agent: Agent,
|
|
@@ -243,6 +245,7 @@ async function startResume(
|
|
|
243
245
|
{
|
|
244
246
|
id,
|
|
245
247
|
agent: start.agent.name,
|
|
248
|
+
...(start.agent.displayName === undefined ? {} : { displayName: start.agent.displayName }),
|
|
246
249
|
model: start.model,
|
|
247
250
|
thinking: start.thinking,
|
|
248
251
|
name,
|
|
@@ -348,11 +351,13 @@ function resumeTool(deps: ResumeDeps): ToolDef {
|
|
|
348
351
|
model: Type.Optional(
|
|
349
352
|
Type.String({
|
|
350
353
|
description:
|
|
351
|
-
"provider/id to resume on; used as given. Omit to continue on the model the delegate finished on, or its agent's next fallback when that one is unavailable.",
|
|
354
|
+
"provider/id to resume on; used as given. Omit to continue on the model the delegate finished on, or its agent's next fallback when that one is unavailable. Not used when the agent locks model: the agent's current model applies.",
|
|
352
355
|
}),
|
|
353
356
|
),
|
|
354
357
|
thinking: Type.Optional(
|
|
355
|
-
Type.String({
|
|
358
|
+
Type.String({
|
|
359
|
+
description: `Thinking level (${THINKING_LEVELS.join(", ")}); defaults to the level the delegate last ran on, from its session. Not used when the agent locks thinking: the agent's current level applies.`,
|
|
360
|
+
}),
|
|
356
361
|
),
|
|
357
362
|
auto_exit: Type.Optional(
|
|
358
363
|
Type.Boolean({ description: "Close the window once it finishes; defaults to the delegation's setting." }),
|