@ian-pascoe/pi-minimal-subagents 0.1.1 → 0.2.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/README.md +58 -5
- package/package.json +1 -1
- package/src/minimal-subagents-capabilities.ts +8 -12
- package/src/minimal-subagents-config.ts +169 -63
- package/src/minimal-subagents-context.ts +7 -1
- package/src/minimal-subagents-coordinator.ts +932 -130
- package/src/minimal-subagents-delivery-ledger.ts +521 -0
- package/src/minimal-subagents-extension.ts +382 -64
- package/src/minimal-subagents-fork-lifecycle.ts +22 -12
- package/src/minimal-subagents-message-envelope.ts +20 -0
- package/src/minimal-subagents-registry-wire.ts +269 -0
- package/src/minimal-subagents-registry.ts +1487 -137
- package/src/minimal-subagents-render-contract.ts +279 -0
- package/src/minimal-subagents-rendering.ts +445 -399
- package/src/minimal-subagents-session-wire.ts +42 -0
- package/src/minimal-subagents-sessions.ts +428 -120
- package/src/minimal-subagents-shutdown.ts +1 -1
- package/src/minimal-subagents-tool-schemas.ts +21 -7
- package/src/minimal-subagents-tools.ts +46 -18
- package/src/minimal-subagents-types.ts +67 -27
- package/src/minimal-subagents-ui.ts +8 -5
package/README.md
CHANGED
|
@@ -130,11 +130,64 @@ children: `subagent`, `agent_message`, `subagent_wait`, `subagent_status`,
|
|
|
130
130
|
only the three adjacent-coordination tools: `agent_message`, `subagent_wait`,
|
|
131
131
|
and `subagent_status`.
|
|
132
132
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
133
|
+
The `subagent` `tools` argument distinguishes capability presets from exact
|
|
134
|
+
lists: `"read"` grants `read`, `grep`, `find`, and `ls`; `"modify"` adds
|
|
135
|
+
`bash`, `edit`, and `write`; an array such as `["read"]` grants exactly the
|
|
136
|
+
named ordinary tool and does not expand a preset. Coordinator tools are
|
|
137
|
+
injected separately according to delegation and must not appear in `tools`;
|
|
138
|
+
misuse returns an actionable error. Use the string preset when a child needs
|
|
139
|
+
the complete read-only discovery bundle.
|
|
140
|
+
|
|
141
|
+
`agent_message` reports whether a message was delivered through an active
|
|
142
|
+
parent wait, queued for the recipient, or failed. `subagent_wait` can return an
|
|
143
|
+
intermediate Wait Event containing a Coordination Message before the child turn
|
|
144
|
+
settles; call it again for the terminal turn result. Pass optional `turn_id` to
|
|
145
|
+
address an older retained turn exactly. Without it, waits select the oldest
|
|
146
|
+
observable claimed or pending turn before the active/latest turn. A caller may
|
|
147
|
+
have only one outstanding wait for the same source turn; a concurrent duplicate
|
|
148
|
+
is rejected instead of competing for one Wait Event.
|
|
149
|
+
|
|
150
|
+
The persisted Delivery Ledger records Coordination Messages, terminal results,
|
|
151
|
+
globally increasing sequence, and wait ownership before delivery. Existing
|
|
152
|
+
items retain their sequence; gaps from skipped malformed records are valid.
|
|
153
|
+
Claims can name only active, latest, or retained turns. Once a wait returns an
|
|
154
|
+
intermediate message, that wait path owns the rest of the source turn across
|
|
155
|
+
reloads, forks, and newer turns. Automatic fallback retains its ordered queue
|
|
156
|
+
reservation, treats idle notifications as advisory, and rechecks actual
|
|
157
|
+
recipient idleness before injecting a message. Destination-session Delivery
|
|
158
|
+
Evidence settles and compacts ledger items, preventing duplicate delivery and
|
|
159
|
+
unbounded checkpoint growth. The pure Delivery Ledger state machine retains at
|
|
160
|
+
most 20 pending wait-only terminal results per source agent; Coordination
|
|
161
|
+
Messages are not removed by that terminal-retention limit. Delivered messages
|
|
162
|
+
include stable delivery, source-agent, and source-turn identities in persisted
|
|
163
|
+
details.
|
|
164
|
+
|
|
165
|
+
Deleting a child first verifies its session header and persistent identity,
|
|
166
|
+
then uses the optional `trash` command when available and falls back to
|
|
167
|
+
unlinking its session file. Deletion prunes pending delivery state and retained
|
|
168
|
+
recent-message projections sourced from the complete deleted subtree. Restore
|
|
169
|
+
and clone perform the same ownership check and reopen the recorded child-session
|
|
170
|
+
leaf.
|
|
171
|
+
|
|
172
|
+
Registry replay and Delivery Evidence are scoped to the Root Agent's active
|
|
173
|
+
session-tree branch. Registry writes use V2 records with complete field,
|
|
174
|
+
identity, sequence, hierarchy, adjacency, destination, ordinary-tool ceiling,
|
|
175
|
+
and coordinator-tool exclusion validation. Every available V2 agent has a
|
|
176
|
+
selected leaf; only unavailable recovery placeholders may omit it. Valid V1
|
|
177
|
+
records and checkpoints migrate during replay; invalid owned records are
|
|
178
|
+
skipped with semantic diagnostic codes rather than disabling the extension.
|
|
179
|
+
Persisted message activity carries an explicit `recorded_at` from the
|
|
180
|
+
coordinator clock.
|
|
181
|
+
|
|
182
|
+
`/tree` abandons old process-local work and restores the selected branch. Fork
|
|
183
|
+
preparation is read-only; only confirmed fork shutdown interrupts work and
|
|
184
|
+
clones the selected branch, so another extension can cancel a fork without
|
|
185
|
+
freezing coordinator tools. Each clone records a new generation-specific
|
|
186
|
+
identity/provenance pair, and the destination appends ownership for that clone's
|
|
187
|
+
current session ID rather than reusing inherited ownership. If a
|
|
188
|
+
process-local fork handoff is lost, recovery reads only the destination's
|
|
189
|
+
selected branch and proceeds only when its canonical `parentSession` proves the
|
|
190
|
+
source file; it never substitutes the source session's newer head.
|
|
138
191
|
|
|
139
192
|
## Status and TUI
|
|
140
193
|
|
package/package.json
CHANGED
|
@@ -36,18 +36,7 @@ export function buildEligibleModelIds(input: {
|
|
|
36
36
|
const source = scopeConfigured
|
|
37
37
|
? input.scopedModels.map((entry) => entry.model)
|
|
38
38
|
: input.availableModels;
|
|
39
|
-
|
|
40
|
-
const result: string[] = [];
|
|
41
|
-
|
|
42
|
-
for (const model of source) {
|
|
43
|
-
const canonicalId = `${model.provider}/${model.id}`;
|
|
44
|
-
if (!seen.has(canonicalId)) {
|
|
45
|
-
seen.add(canonicalId);
|
|
46
|
-
result.push(canonicalId);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
return result;
|
|
39
|
+
return [...new Set(source.map(({ provider, id }) => `${provider}/${id}`))];
|
|
51
40
|
}
|
|
52
41
|
|
|
53
42
|
/** Supplies inherited tools, the ancestor ceiling, and runtime availability for exact tool resolution. */
|
|
@@ -73,6 +62,13 @@ export function resolveOrdinaryToolSelection(
|
|
|
73
62
|
? MODIFY_TOOL_BUNDLE
|
|
74
63
|
: selection;
|
|
75
64
|
const uniqueRequested = [...new Set(requested)];
|
|
65
|
+
const coordinatorTools = new Set<string>(COORDINATOR_TOOL_NAMES);
|
|
66
|
+
const requestedCoordinatorTools = uniqueRequested.filter((name) => coordinatorTools.has(name));
|
|
67
|
+
if (requestedCoordinatorTools.length > 0) {
|
|
68
|
+
throw new Error(
|
|
69
|
+
`Minimal subagents ordinary tool selection: coordinator tools are injected separately and must not appear in tools: ${requestedCoordinatorTools.join(", ")}`,
|
|
70
|
+
);
|
|
71
|
+
}
|
|
76
72
|
const available = new Set(context.availableTools);
|
|
77
73
|
const ceiling = new Set(context.capabilityCeiling);
|
|
78
74
|
const missing = uniqueRequested.filter((name) => !available.has(name));
|
|
@@ -1,8 +1,34 @@
|
|
|
1
|
+
import type { JsonValue } from "@earendil-works/pi-ai";
|
|
2
|
+
import type { SettingsManager } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import { type Static, Type } from "typebox";
|
|
4
|
+
import { Value } from "typebox/value";
|
|
1
5
|
import { DEFAULT_MAX_SUBAGENT_DEPTH, THINKING_LEVELS } from "./minimal-subagents-capabilities.js";
|
|
2
6
|
|
|
3
7
|
const MODEL_ROLE_NAME_MAX_LENGTH = 64;
|
|
4
8
|
const MODEL_ROLE_HINT_MAX_LENGTH = 500;
|
|
5
9
|
|
|
10
|
+
const JsonValueSchema = Type.Unsafe<JsonValue>({});
|
|
11
|
+
const SettingsDocumentSchema = Type.Object({
|
|
12
|
+
minimalSubagents: Type.Optional(JsonValueSchema),
|
|
13
|
+
});
|
|
14
|
+
const MinimalSubagentsSettingsSchema = Type.Object({
|
|
15
|
+
maxSubagentDepth: Type.Optional(JsonValueSchema),
|
|
16
|
+
modelRoles: Type.Optional(JsonValueSchema),
|
|
17
|
+
});
|
|
18
|
+
const JsonObjectSchema = Type.Record(Type.String(), JsonValueSchema);
|
|
19
|
+
const PositiveSafeIntegerSchema = Type.Integer({ minimum: 1, maximum: Number.MAX_SAFE_INTEGER });
|
|
20
|
+
const MaxSubagentDepthSettingSchema = Type.Union([PositiveSafeIntegerSchema, Type.Null()]);
|
|
21
|
+
const ShorthandModelRoleSchema = Type.String();
|
|
22
|
+
const ExpandedModelRoleSchema = Type.Object(
|
|
23
|
+
{
|
|
24
|
+
model: Type.String(),
|
|
25
|
+
hint: Type.Optional(Type.String()),
|
|
26
|
+
},
|
|
27
|
+
{ additionalProperties: false },
|
|
28
|
+
);
|
|
29
|
+
const ModelRoleEntriesSchema = Type.Record(Type.String(), JsonValueSchema);
|
|
30
|
+
const ModelRolesSettingSchema = Type.Union([ModelRoleEntriesSchema, Type.Null()]);
|
|
31
|
+
|
|
6
32
|
type ModelRoleThinkingLevel = (typeof THINKING_LEVELS)[number];
|
|
7
33
|
|
|
8
34
|
/** Describes one user-authored advisory model role shown to subagent callers. */
|
|
@@ -20,71 +46,142 @@ export interface ResolvedMinimalSubagentsConfig {
|
|
|
20
46
|
warnings: string[];
|
|
21
47
|
}
|
|
22
48
|
|
|
49
|
+
interface MinimalSubagentsSettingsDocument {
|
|
50
|
+
minimalSubagents?: JsonValue;
|
|
51
|
+
}
|
|
52
|
+
|
|
23
53
|
interface MinimalSubagentsConfigInput {
|
|
24
|
-
globalSettings:
|
|
25
|
-
projectSettings:
|
|
54
|
+
globalSettings: MinimalSubagentsSettingsDocumentInput;
|
|
55
|
+
projectSettings: MinimalSubagentsSettingsDocumentInput;
|
|
26
56
|
eligibleModelIds: readonly string[];
|
|
27
57
|
}
|
|
28
58
|
|
|
59
|
+
type PiSettingsDocument = ReturnType<SettingsManager["getGlobalSettings"]>;
|
|
60
|
+
type MinimalSubagentsSettingsDocumentInput = PiSettingsDocument | MinimalSubagentsSettingsDocument;
|
|
61
|
+
|
|
29
62
|
interface MinimalSubagentsSettingsReader {
|
|
30
|
-
getGlobalSettings():
|
|
31
|
-
getProjectSettings():
|
|
63
|
+
getGlobalSettings(): MinimalSubagentsSettingsDocumentInput;
|
|
64
|
+
getProjectSettings(): MinimalSubagentsSettingsDocumentInput;
|
|
32
65
|
}
|
|
33
66
|
|
|
34
67
|
type SettingsScope = "global" | "project";
|
|
35
68
|
|
|
36
69
|
interface ScopedSettingValue {
|
|
37
70
|
scope: SettingsScope;
|
|
38
|
-
value:
|
|
71
|
+
value: ModelRoleWireValue;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
interface ParsedMinimalSubagentsSettings {
|
|
75
|
+
maxSubagentDepth?: MaxSubagentDepthWireValue;
|
|
76
|
+
modelRoles?: ModelRolesWireValue;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
type MaxSubagentDepthWireValue =
|
|
80
|
+
| { kind: "depth"; value: number }
|
|
81
|
+
| { kind: "reset" }
|
|
82
|
+
| { kind: "invalid" };
|
|
83
|
+
|
|
84
|
+
type ModelRoleWireValue =
|
|
85
|
+
| { kind: "delete" }
|
|
86
|
+
| { kind: "shorthand"; model: string }
|
|
87
|
+
| { kind: "expanded"; fields: Static<typeof ExpandedModelRoleSchema> }
|
|
88
|
+
| { kind: "malformed-expanded"; fields: Record<string, JsonValue> }
|
|
89
|
+
| { kind: "invalid" };
|
|
90
|
+
|
|
91
|
+
type ModelRolesWireValue =
|
|
92
|
+
| { kind: "reset" }
|
|
93
|
+
| { kind: "entries"; entries: ReadonlyMap<string, ModelRoleWireValue> }
|
|
94
|
+
| { kind: "invalid" };
|
|
95
|
+
|
|
96
|
+
function parseMaxSubagentDepthWireValue(value: JsonValue): MaxSubagentDepthWireValue {
|
|
97
|
+
if (!Value.Check(MaxSubagentDepthSettingSchema, value)) return { kind: "invalid" };
|
|
98
|
+
return value === null ? { kind: "reset" } : { kind: "depth", value };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function parseModelRoleWireValue(value: JsonValue): ModelRoleWireValue {
|
|
102
|
+
if (value === null) return { kind: "delete" };
|
|
103
|
+
if (Value.Check(ShorthandModelRoleSchema, value)) return { kind: "shorthand", model: value };
|
|
104
|
+
if (Value.Check(JsonObjectSchema, value)) {
|
|
105
|
+
return Value.Check(ExpandedModelRoleSchema, value)
|
|
106
|
+
? { kind: "expanded", fields: value }
|
|
107
|
+
: { kind: "malformed-expanded", fields: value };
|
|
108
|
+
}
|
|
109
|
+
return { kind: "invalid" };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function isExpandedModelRoleWireValue(
|
|
113
|
+
value: ModelRoleWireValue,
|
|
114
|
+
): value is Extract<ModelRoleWireValue, { kind: "expanded" | "malformed-expanded" }> {
|
|
115
|
+
return value.kind === "expanded" || value.kind === "malformed-expanded";
|
|
39
116
|
}
|
|
40
117
|
|
|
41
|
-
function
|
|
42
|
-
|
|
118
|
+
function parseModelRolesWireValue(value: JsonValue): ModelRolesWireValue {
|
|
119
|
+
if (!Value.Check(ModelRolesSettingSchema, value)) return { kind: "invalid" };
|
|
120
|
+
if (value === null) return { kind: "reset" };
|
|
121
|
+
return {
|
|
122
|
+
kind: "entries",
|
|
123
|
+
entries: new Map(
|
|
124
|
+
Object.entries(value).map(([name, roleValue]) => [name, parseModelRoleWireValue(roleValue)]),
|
|
125
|
+
),
|
|
126
|
+
};
|
|
43
127
|
}
|
|
44
128
|
|
|
45
129
|
function readMinimalSubagentsSettings(
|
|
46
|
-
settings:
|
|
130
|
+
settings: MinimalSubagentsSettingsDocumentInput,
|
|
47
131
|
scope: SettingsScope,
|
|
48
132
|
warnings: string[],
|
|
49
|
-
):
|
|
50
|
-
if (!
|
|
51
|
-
|
|
133
|
+
): ParsedMinimalSubagentsSettings {
|
|
134
|
+
if (!Value.Check(SettingsDocumentSchema, settings)) return {};
|
|
135
|
+
const minimalSubagents = settings.minimalSubagents;
|
|
136
|
+
if (minimalSubagents === undefined) return {};
|
|
137
|
+
if (Value.Check(MinimalSubagentsSettingsSchema, minimalSubagents)) {
|
|
138
|
+
const parsed: ParsedMinimalSubagentsSettings = {};
|
|
139
|
+
if (minimalSubagents.maxSubagentDepth !== undefined) {
|
|
140
|
+
parsed.maxSubagentDepth = parseMaxSubagentDepthWireValue(minimalSubagents.maxSubagentDepth);
|
|
141
|
+
}
|
|
142
|
+
if (minimalSubagents.modelRoles !== undefined) {
|
|
143
|
+
parsed.modelRoles = parseModelRolesWireValue(minimalSubagents.modelRoles);
|
|
144
|
+
}
|
|
145
|
+
return parsed;
|
|
146
|
+
}
|
|
52
147
|
warnings.push(`${scope} minimalSubagents: expected an object`);
|
|
53
148
|
return {};
|
|
54
149
|
}
|
|
55
150
|
|
|
56
151
|
function mergeModelRoleEntries(
|
|
57
|
-
globalValue:
|
|
58
|
-
projectValue:
|
|
152
|
+
globalValue: ModelRolesWireValue | undefined,
|
|
153
|
+
projectValue: ModelRolesWireValue | undefined,
|
|
59
154
|
warnings: string[],
|
|
60
155
|
): Map<string, ScopedSettingValue> {
|
|
61
156
|
const entries = new Map<string, ScopedSettingValue>();
|
|
62
|
-
if (globalValue
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
entries.set(name, { scope: "global", value });
|
|
66
|
-
}
|
|
67
|
-
} else if (globalValue !== null) {
|
|
68
|
-
warnings.push("global minimalSubagents.modelRoles: expected an object or null");
|
|
157
|
+
if (globalValue?.kind === "entries") {
|
|
158
|
+
for (const [name, value] of globalValue.entries) {
|
|
159
|
+
entries.set(name, { scope: "global", value });
|
|
69
160
|
}
|
|
161
|
+
} else if (globalValue?.kind === "invalid") {
|
|
162
|
+
warnings.push("global minimalSubagents.modelRoles: expected an object or null");
|
|
70
163
|
}
|
|
71
|
-
if (projectValue ===
|
|
164
|
+
if (projectValue?.kind === "reset") return new Map();
|
|
72
165
|
if (projectValue === undefined) return entries;
|
|
73
|
-
if (
|
|
166
|
+
if (projectValue.kind === "invalid") {
|
|
74
167
|
warnings.push("project minimalSubagents.modelRoles: expected an object or null");
|
|
75
168
|
return entries;
|
|
76
169
|
}
|
|
170
|
+
if (projectValue.kind !== "entries") return entries;
|
|
77
171
|
|
|
78
|
-
for (const [name, value] of
|
|
79
|
-
if (value ===
|
|
172
|
+
for (const [name, value] of projectValue.entries) {
|
|
173
|
+
if (value.kind === "delete") {
|
|
80
174
|
entries.delete(name);
|
|
81
175
|
continue;
|
|
82
176
|
}
|
|
83
177
|
const inherited = entries.get(name)?.value;
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
178
|
+
const mergedValue =
|
|
179
|
+
inherited !== undefined &&
|
|
180
|
+
isExpandedModelRoleWireValue(inherited) &&
|
|
181
|
+
isExpandedModelRoleWireValue(value)
|
|
182
|
+
? parseModelRoleWireValue({ ...inherited.fields, ...value.fields })
|
|
183
|
+
: value;
|
|
184
|
+
entries.set(name, { scope: "project", value: mergedValue });
|
|
88
185
|
}
|
|
89
186
|
return entries;
|
|
90
187
|
}
|
|
@@ -142,69 +239,78 @@ function parseModelRoles(
|
|
|
142
239
|
}
|
|
143
240
|
|
|
144
241
|
const value = entry.value;
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
242
|
+
const expandedRoleObject = isExpandedModelRoleWireValue(value) ? value.fields : undefined;
|
|
243
|
+
if (expandedRoleObject !== undefined) {
|
|
244
|
+
const invalidFields = Object.keys(expandedRoleObject).filter(
|
|
245
|
+
(key) => key !== "model" && key !== "hint",
|
|
246
|
+
);
|
|
247
|
+
if (invalidFields.length > 0) {
|
|
248
|
+
warnings.push(`${path}: unknown field: ${invalidFields.join(", ")}`);
|
|
149
249
|
continue;
|
|
150
250
|
}
|
|
151
|
-
} else if (
|
|
251
|
+
} else if (value.kind !== "shorthand") {
|
|
152
252
|
warnings.push(`${path}: expected a model string or expanded role object`);
|
|
153
253
|
continue;
|
|
154
254
|
}
|
|
155
255
|
|
|
156
|
-
const
|
|
157
|
-
|
|
256
|
+
const modelValue =
|
|
257
|
+
expandedRoleObject === undefined && value.kind === "shorthand"
|
|
258
|
+
? value.model
|
|
259
|
+
: expandedRoleObject?.model;
|
|
260
|
+
if (
|
|
261
|
+
!Value.Check(ShorthandModelRoleSchema, modelValue) ||
|
|
262
|
+
modelValue.length === 0 ||
|
|
263
|
+
modelValue !== modelValue.trim()
|
|
264
|
+
) {
|
|
158
265
|
warnings.push(`${path}: model must be a non-empty trimmed string`);
|
|
159
266
|
continue;
|
|
160
267
|
}
|
|
161
|
-
const resolvedModel = resolveModelRoleReference(
|
|
268
|
+
const resolvedModel = resolveModelRoleReference(modelValue, eligibleModels, path, warnings);
|
|
162
269
|
if (resolvedModel === undefined) continue;
|
|
163
270
|
|
|
164
|
-
const
|
|
271
|
+
const hintValue = expandedRoleObject?.hint;
|
|
165
272
|
if (
|
|
166
|
-
|
|
167
|
-
(
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
/[\r\n]/.test(
|
|
171
|
-
|
|
273
|
+
hintValue !== undefined &&
|
|
274
|
+
(!Value.Check(ShorthandModelRoleSchema, hintValue) ||
|
|
275
|
+
hintValue.length === 0 ||
|
|
276
|
+
hintValue !== hintValue.trim() ||
|
|
277
|
+
/[\r\n]/.test(hintValue) ||
|
|
278
|
+
hintValue.length > MODEL_ROLE_HINT_MAX_LENGTH)
|
|
172
279
|
) {
|
|
173
280
|
warnings.push(`${path}.hint: expected trimmed single-line text up to 500 characters`);
|
|
174
281
|
continue;
|
|
175
282
|
}
|
|
176
|
-
|
|
283
|
+
const role: MinimalSubagentsModelRole = {
|
|
177
284
|
name,
|
|
178
285
|
model: resolvedModel.model,
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
286
|
+
};
|
|
287
|
+
if (resolvedModel.thinkingLevel !== undefined) {
|
|
288
|
+
role.thinkingLevel = resolvedModel.thinkingLevel;
|
|
289
|
+
}
|
|
290
|
+
if (hintValue !== undefined && Value.Check(ShorthandModelRoleSchema, hintValue)) {
|
|
291
|
+
role.hint = hintValue;
|
|
292
|
+
}
|
|
293
|
+
roles.push(role);
|
|
184
294
|
}
|
|
185
295
|
return roles;
|
|
186
296
|
}
|
|
187
297
|
|
|
188
298
|
function resolveMaxSubagentDepth(
|
|
189
|
-
globalValue:
|
|
190
|
-
projectValue:
|
|
299
|
+
globalValue: MaxSubagentDepthWireValue | undefined,
|
|
300
|
+
projectValue: MaxSubagentDepthWireValue | undefined,
|
|
191
301
|
warnings: string[],
|
|
192
302
|
): number {
|
|
193
303
|
let resolvedDepth = DEFAULT_MAX_SUBAGENT_DEPTH;
|
|
194
|
-
if (globalValue
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
);
|
|
201
|
-
}
|
|
304
|
+
if (globalValue?.kind === "depth") {
|
|
305
|
+
resolvedDepth = globalValue.value;
|
|
306
|
+
} else if (globalValue?.kind === "invalid") {
|
|
307
|
+
warnings.push(
|
|
308
|
+
"global minimalSubagents.maxSubagentDepth: expected a positive safe integer or null",
|
|
309
|
+
);
|
|
202
310
|
}
|
|
203
311
|
if (projectValue === undefined) return resolvedDepth;
|
|
204
|
-
if (projectValue ===
|
|
205
|
-
if (
|
|
206
|
-
return Number(projectValue);
|
|
207
|
-
}
|
|
312
|
+
if (projectValue.kind === "reset") return DEFAULT_MAX_SUBAGENT_DEPTH;
|
|
313
|
+
if (projectValue.kind === "depth") return projectValue.value;
|
|
208
314
|
warnings.push(
|
|
209
315
|
"project minimalSubagents.maxSubagentDepth: expected a positive safe integer or null",
|
|
210
316
|
);
|
|
@@ -11,11 +11,17 @@ export function snapshotCommittedContext(
|
|
|
11
11
|
return structuredClone(committed);
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
/** Carries the selected caller messages and whether child preparation should compact them. */
|
|
15
|
+
export interface ImportedSubagentContext {
|
|
16
|
+
messages: AgentMessage[];
|
|
17
|
+
compact: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
14
20
|
/** Select the imported message snapshot and defer expensive compact preparation to the child turn. */
|
|
15
21
|
export function assembleImportedContext(
|
|
16
22
|
mode: SessionContextMode,
|
|
17
23
|
committedMessages: AgentMessage[],
|
|
18
|
-
):
|
|
24
|
+
): ImportedSubagentContext {
|
|
19
25
|
if (mode === "omit") return { messages: [], compact: false };
|
|
20
26
|
return { messages: committedMessages, compact: mode === "compact" };
|
|
21
27
|
}
|