pi-subagents 0.34.0 → 0.35.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 +78 -9
- package/README.md +213 -32
- package/index.ts +1 -0
- package/install.mjs +1 -1
- package/package.json +23 -8
- package/prompts/review-loop.md +3 -1
- package/skills/pi-subagents/SKILL.md +87 -25
- package/src/agents/agent-management.ts +82 -15
- package/src/agents/agent-serializer.ts +19 -0
- package/src/agents/agents.ts +91 -49
- package/src/agents/frontmatter.ts +67 -13
- package/src/agents/skills.ts +25 -12
- package/src/api/background-work.ts +197 -0
- package/src/api/delegation.ts +158 -0
- package/src/extension/chain-validation.ts +165 -0
- package/src/extension/doctor.ts +15 -0
- package/src/extension/fanout-child.ts +3 -1
- package/src/extension/index.ts +65 -124
- package/src/extension/rpc.ts +10 -2
- package/src/extension/schemas.ts +18 -14
- package/src/extension/steering-notices.ts +35 -0
- package/src/extension/tool-description.ts +20 -9
- package/src/intercom/intercom-bridge.ts +3 -2
- package/src/intercom/native-supervisor-channel.ts +9 -1
- package/src/intercom/result-intercom.ts +4 -0
- package/src/runs/background/async-execution.ts +293 -44
- package/src/runs/background/async-job-tracker.ts +56 -9
- package/src/runs/background/async-resume.ts +159 -52
- package/src/runs/background/async-status.ts +25 -18
- package/src/runs/background/auto-drain.ts +67 -0
- package/src/runs/background/chain-root-attachment.ts +16 -8
- package/src/runs/background/control-channel.ts +260 -13
- package/src/runs/background/fleet-view.ts +23 -2
- package/src/runs/background/notify.ts +79 -10
- package/src/runs/background/result-watcher.ts +12 -9
- package/src/runs/background/run-id-resolver.ts +14 -2
- package/src/runs/background/run-status.ts +23 -15
- package/src/runs/background/scheduled-runs.ts +3 -0
- package/src/runs/background/stale-run-reconciler.ts +32 -10
- package/src/runs/background/steering.ts +237 -0
- package/src/runs/background/subagent-runner.ts +898 -236
- package/src/runs/background/subagent-wait.ts +484 -0
- package/src/runs/background/top-level-async.ts +2 -1
- package/src/runs/background/wait-config.ts +36 -0
- package/src/runs/background/wait-tool.ts +26 -0
- package/src/runs/foreground/async-steering-action.ts +230 -0
- package/src/runs/foreground/chain-clarify.ts +22 -6
- package/src/runs/foreground/chain-execution.ts +50 -32
- package/src/runs/foreground/execution.ts +308 -94
- package/src/runs/foreground/subagent-executor.ts +592 -268
- package/src/runs/shared/acceptance.ts +355 -97
- package/src/runs/shared/child-protocol.ts +121 -0
- package/src/runs/shared/completion-guard.ts +8 -127
- package/src/runs/shared/dynamic-fanout.ts +6 -4
- package/src/runs/shared/model-fallback.ts +36 -0
- package/src/runs/shared/nested-events.ts +9 -4
- package/src/runs/shared/nested-render.ts +4 -1
- package/src/runs/shared/parallel-utils.ts +7 -0
- package/src/runs/shared/pi-args.ts +34 -7
- package/src/runs/shared/pi-spawn.ts +18 -12
- package/src/runs/shared/session-lease.ts +279 -0
- package/src/runs/shared/single-output.ts +61 -6
- package/src/runs/shared/spawn-budget.ts +128 -0
- package/src/runs/shared/subagent-control.ts +10 -6
- package/src/runs/shared/subagent-prompt-runtime.ts +127 -26
- package/src/runs/shared/task-intent.ts +176 -0
- package/src/runs/shared/tool-availability.ts +65 -0
- package/src/runs/shared/turn-budget.ts +49 -4
- package/src/shared/atomic-json.ts +4 -1
- package/src/shared/fork-context.ts +28 -3
- package/src/shared/model-info.ts +7 -4
- package/src/shared/status-format.ts +7 -1
- package/src/shared/types.ts +203 -25
- package/src/shared/utils.ts +35 -7
- package/src/slash/delegation-adapters.ts +457 -0
- package/src/slash/delegation-request.ts +103 -0
- package/src/slash/prompt-template-bridge.ts +167 -344
- package/src/slash/slash-commands.ts +239 -6
- package/src/slash/subagents-admin.ts +428 -0
- package/src/slash/subagents-editor.ts +86 -0
- package/src/tui/fleet.ts +405 -0
- package/src/tui/render.ts +90 -16
- package/src/watchdog/change-signature.ts +127 -0
- package/src/watchdog/child-status.ts +205 -0
- package/src/watchdog/emission-guard.ts +123 -0
- package/src/watchdog/lsp-diagnostics.ts +532 -0
- package/src/watchdog/model-selection.ts +167 -0
- package/src/watchdog/register-child.ts +117 -0
- package/src/watchdog/register-main.ts +433 -0
- package/src/watchdog/render.ts +54 -0
- package/src/watchdog/review.ts +293 -0
- package/src/watchdog/runtime.ts +712 -0
- package/src/watchdog/settings.ts +528 -0
- package/src/watchdog/tool-actions.ts +155 -0
- package/src/watchdog/turn-delta.ts +161 -0
- package/src/watchdog/types.ts +188 -0
- package/src/watchdog/warning-format.ts +73 -0
- package/src/runs/background/wait.ts +0 -394
|
@@ -2,6 +2,7 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import { SessionManager } from "@earendil-works/pi-coding-agent";
|
|
5
|
+
import { findModelInfo, type ModelInfo } from "./model-info.ts";
|
|
5
6
|
|
|
6
7
|
type SubagentExecutionContext = "fresh" | "fork";
|
|
7
8
|
|
|
@@ -35,6 +36,9 @@ interface ForkableSessionManager {
|
|
|
35
36
|
|
|
36
37
|
interface ForkContextResolverOptions {
|
|
37
38
|
openSession?: (path: string, sessionDir?: string) => BranchSessionManager;
|
|
39
|
+
/** Decide per child index whether a sanitized transcript must also disable the child's
|
|
40
|
+
* thinking. Defaults to true (the pre-existing conservative behavior) when omitted. */
|
|
41
|
+
forceThinkingOffForIndex?: (index: number) => boolean;
|
|
38
42
|
}
|
|
39
43
|
|
|
40
44
|
interface ForkContextResolution {
|
|
@@ -51,6 +55,20 @@ export function resolveSubagentContext(value: unknown): SubagentExecutionContext
|
|
|
51
55
|
return value === "fork" ? "fork" : "fresh";
|
|
52
56
|
}
|
|
53
57
|
|
|
58
|
+
/** Decide whether a resolved child model uses Anthropic's provider or message API, which
|
|
59
|
+
* requires the sanitized fork to disable thinking. Unknown models stay conservative. */
|
|
60
|
+
export function forkedChildRequiresThinkingOff(
|
|
61
|
+
model: string | undefined,
|
|
62
|
+
availableModels?: ModelInfo[],
|
|
63
|
+
preferredProvider?: string,
|
|
64
|
+
): boolean {
|
|
65
|
+
if (!model) return true;
|
|
66
|
+
const info = findModelInfo(model, availableModels, preferredProvider);
|
|
67
|
+
if (!info) return true;
|
|
68
|
+
return info.provider.toLowerCase() === "anthropic"
|
|
69
|
+
|| info.api?.toLowerCase() === "anthropic-messages";
|
|
70
|
+
}
|
|
71
|
+
|
|
54
72
|
function isUnsafeAnthropicThinkingBlock(message: BranchSessionEntry["message"], block: unknown): boolean {
|
|
55
73
|
if (!message || !block || typeof block !== "object" || !("type" in block)) return false;
|
|
56
74
|
const provider = typeof message.provider === "string" ? message.provider.toLowerCase() : "";
|
|
@@ -94,7 +112,6 @@ function sanitizeUnsafeThinkingBlocks(entries: BranchSessionEntry[]): boolean {
|
|
|
94
112
|
entry.message.content = filtered;
|
|
95
113
|
sanitized = true;
|
|
96
114
|
}
|
|
97
|
-
if (sanitized) appendThinkingOffEntry(entries);
|
|
98
115
|
return sanitized;
|
|
99
116
|
}
|
|
100
117
|
|
|
@@ -150,6 +167,8 @@ export function createForkContextResolver(
|
|
|
150
167
|
if (!sessionFile) {
|
|
151
168
|
throw new Error("Session manager did not return a forked session file.");
|
|
152
169
|
}
|
|
170
|
+
const forceThinkingOff = (sanitized: boolean): boolean =>
|
|
171
|
+
sanitized && (options.forceThinkingOffForIndex?.(index) ?? true);
|
|
153
172
|
let thinkingOverride: "off" | undefined;
|
|
154
173
|
if (!fs.existsSync(sessionFile)) {
|
|
155
174
|
const header = sourceManager.getHeader?.();
|
|
@@ -157,13 +176,19 @@ export function createForkContextResolver(
|
|
|
157
176
|
if (!header || !entries) {
|
|
158
177
|
throw new Error(`Session manager returned a forked session file that does not exist and cannot be persisted by fallback: ${sessionFile}`);
|
|
159
178
|
}
|
|
160
|
-
if (sanitizeUnsafeThinkingBlocks(entries))
|
|
179
|
+
if (forceThinkingOff(sanitizeUnsafeThinkingBlocks(entries))) {
|
|
180
|
+
appendThinkingOffEntry(entries);
|
|
181
|
+
thinkingOverride = "off";
|
|
182
|
+
}
|
|
161
183
|
fs.mkdirSync(path.dirname(sessionFile), { recursive: true });
|
|
162
184
|
fs.writeFileSync(sessionFile, `${[header, ...entries].map((entry) => JSON.stringify(entry)).join("\n")}\n`, "utf-8");
|
|
163
185
|
} else {
|
|
164
186
|
const entries = readSessionEntries(sessionFile);
|
|
165
187
|
if (sanitizeUnsafeThinkingBlocks(entries)) {
|
|
166
|
-
|
|
188
|
+
if (forceThinkingOff(true)) {
|
|
189
|
+
appendThinkingOffEntry(entries);
|
|
190
|
+
thinkingOverride = "off";
|
|
191
|
+
}
|
|
167
192
|
fs.writeFileSync(sessionFile, `${entries.map((entry) => JSON.stringify(entry)).join("\n")}\n`, "utf-8");
|
|
168
193
|
}
|
|
169
194
|
}
|
package/src/shared/model-info.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh"] as const;
|
|
1
|
+
export const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"] as const;
|
|
2
2
|
export type ThinkingLevel = typeof THINKING_LEVELS[number];
|
|
3
3
|
export type ThinkingLevelMap = Partial<Record<ThinkingLevel, string | null>>;
|
|
4
4
|
|
|
@@ -6,6 +6,7 @@ export interface ModelInfo {
|
|
|
6
6
|
provider: string;
|
|
7
7
|
id: string;
|
|
8
8
|
fullId: string;
|
|
9
|
+
api?: string;
|
|
9
10
|
reasoning?: boolean;
|
|
10
11
|
thinkingLevelMap?: ThinkingLevelMap;
|
|
11
12
|
}
|
|
@@ -13,6 +14,7 @@ export interface ModelInfo {
|
|
|
13
14
|
interface RegistryModelLike {
|
|
14
15
|
provider: string;
|
|
15
16
|
id: string;
|
|
17
|
+
api?: string;
|
|
16
18
|
reasoning?: boolean;
|
|
17
19
|
thinkingLevelMap?: ThinkingLevelMap;
|
|
18
20
|
}
|
|
@@ -22,6 +24,7 @@ export function toModelInfo(model: RegistryModelLike): ModelInfo {
|
|
|
22
24
|
provider: model.provider,
|
|
23
25
|
id: model.id,
|
|
24
26
|
fullId: `${model.provider}/${model.id}`,
|
|
27
|
+
api: model.api,
|
|
25
28
|
reasoning: model.reasoning,
|
|
26
29
|
thinkingLevelMap: model.thinkingLevelMap,
|
|
27
30
|
};
|
|
@@ -63,15 +66,15 @@ export function findModelInfo(model: string | undefined, availableModels: ModelI
|
|
|
63
66
|
}
|
|
64
67
|
|
|
65
68
|
export function getSupportedThinkingLevels(model: ModelInfo | undefined): ThinkingLevel[] {
|
|
66
|
-
if (!model) return
|
|
69
|
+
if (!model) return THINKING_LEVELS.filter((level) => level !== "max");
|
|
67
70
|
if (model.reasoning === false) return ["off"];
|
|
68
71
|
|
|
69
|
-
if (!model.thinkingLevelMap) return
|
|
72
|
+
if (!model.thinkingLevelMap) return THINKING_LEVELS.filter((level) => level !== "max");
|
|
70
73
|
|
|
71
74
|
const levels = THINKING_LEVELS.filter((level) => {
|
|
72
75
|
const mapped = model.thinkingLevelMap?.[level];
|
|
73
76
|
if (mapped === null) return false;
|
|
74
|
-
if (level === "xhigh") return mapped !== undefined;
|
|
77
|
+
if (level === "xhigh" || level === "max") return mapped !== undefined;
|
|
75
78
|
return true;
|
|
76
79
|
});
|
|
77
80
|
return levels;
|
|
@@ -16,7 +16,10 @@ export function formatActivityLabel(lastActivityAt: number | undefined, activity
|
|
|
16
16
|
}
|
|
17
17
|
const age = formatActivityAge(Math.max(0, now - lastActivityAt));
|
|
18
18
|
if (activityState === "needs_attention") return `no activity for ${age}`;
|
|
19
|
-
if (activityState === "active_long_running")
|
|
19
|
+
if (activityState === "active_long_running") {
|
|
20
|
+
const activityAge = age === "now" ? "now" : `${age} ago`;
|
|
21
|
+
return `active but long-running · last activity ${activityAge}`;
|
|
22
|
+
}
|
|
20
23
|
return age === "now" ? "active now" : `active ${age} ago`;
|
|
21
24
|
}
|
|
22
25
|
|
|
@@ -27,6 +30,7 @@ function isCompletedStepStatus(status: AsyncJobStep["status"]): boolean {
|
|
|
27
30
|
export function aggregateStepStatus(steps: StepStatusLike[]): AsyncJobStep["status"] {
|
|
28
31
|
if (steps.some((step) => step.status === "running")) return "running";
|
|
29
32
|
if (steps.some((step) => step.status === "failed")) return "failed";
|
|
33
|
+
if (steps.some((step) => step.status === "stopped")) return "stopped";
|
|
30
34
|
if (steps.some((step) => step.status === "paused")) return "paused";
|
|
31
35
|
if (steps.length > 0 && steps.every((step) => isCompletedStepStatus(step.status))) return "complete";
|
|
32
36
|
return "pending";
|
|
@@ -40,10 +44,12 @@ export function formatParallelOutcome(steps: StepStatusLike[], total: number, op
|
|
|
40
44
|
const running = steps.filter((step) => step.status === "running").length;
|
|
41
45
|
const done = steps.filter((step) => isCompletedStepStatus(step.status)).length;
|
|
42
46
|
const failed = steps.filter((step) => step.status === "failed").length;
|
|
47
|
+
const stopped = steps.filter((step) => step.status === "stopped").length;
|
|
43
48
|
const paused = steps.filter((step) => step.status === "paused").length;
|
|
44
49
|
const parts = [`${done}/${total} done`];
|
|
45
50
|
if (options.showRunning !== false && running > 0) parts.unshift(formatAgentRunningLabel(running));
|
|
46
51
|
if (failed > 0) parts.push(`${failed} failed`);
|
|
52
|
+
if (stopped > 0) parts.push(`${stopped} stopped`);
|
|
47
53
|
if (paused > 0) parts.push(`${paused} paused`);
|
|
48
54
|
return parts.join(" · ");
|
|
49
55
|
}
|
package/src/shared/types.ts
CHANGED
|
@@ -20,6 +20,8 @@ export interface MaxOutputConfig {
|
|
|
20
20
|
|
|
21
21
|
export type OutputMode = "inline" | "file-only";
|
|
22
22
|
|
|
23
|
+
export type AcceptanceRole = "read-only" | "writer";
|
|
24
|
+
|
|
23
25
|
export type JsonSchemaObject = Record<string, unknown>;
|
|
24
26
|
|
|
25
27
|
export interface ChainOutputMapEntry {
|
|
@@ -31,7 +33,7 @@ export interface ChainOutputMapEntry {
|
|
|
31
33
|
|
|
32
34
|
export type ChainOutputMap = Record<string, ChainOutputMapEntry>;
|
|
33
35
|
|
|
34
|
-
export type WorkflowNodeStatus = "pending" | "running" | "completed" | "failed" | "paused" | "detached";
|
|
36
|
+
export type WorkflowNodeStatus = "pending" | "running" | "completed" | "failed" | "paused" | "stopped" | "detached";
|
|
35
37
|
|
|
36
38
|
export interface WorkflowGraphNode {
|
|
37
39
|
id: string;
|
|
@@ -121,12 +123,13 @@ export interface ToolBudgetState extends ResolvedToolBudget {
|
|
|
121
123
|
blockedTool?: string;
|
|
122
124
|
}
|
|
123
125
|
|
|
124
|
-
export type TurnBudgetOutcome = "within-budget" | "wrap-up-requested" | "exceeded";
|
|
126
|
+
export type TurnBudgetOutcome = "within-budget" | "wrap-up-requested" | "termination-deferred" | "exceeded";
|
|
125
127
|
|
|
126
128
|
export interface TurnBudgetState extends ResolvedTurnBudget {
|
|
127
129
|
outcome: TurnBudgetOutcome;
|
|
128
130
|
turnCount: number;
|
|
129
131
|
wrapUpRequestedAtTurn?: number;
|
|
132
|
+
terminationDeferredAtTurn?: number;
|
|
130
133
|
exceededAtTurn?: number;
|
|
131
134
|
}
|
|
132
135
|
|
|
@@ -209,14 +212,113 @@ export interface ControlEvent {
|
|
|
209
212
|
recentFailureSummary?: string;
|
|
210
213
|
}
|
|
211
214
|
|
|
212
|
-
export type SubagentResultStatus = "completed" | "failed" | "paused" | "detached";
|
|
215
|
+
export type SubagentResultStatus = "completed" | "failed" | "paused" | "stopped" | "detached";
|
|
213
216
|
export type SubagentRunMode = "single" | "parallel" | "chain";
|
|
214
|
-
export const SUBAGENT_LIFECYCLE_ARTIFACT_VERSION =
|
|
217
|
+
export const SUBAGENT_LIFECYCLE_ARTIFACT_VERSION = 2;
|
|
215
218
|
export type SubagentLifecycleArtifactVersion = typeof SUBAGENT_LIFECYCLE_ARTIFACT_VERSION;
|
|
216
219
|
|
|
220
|
+
export type SteeringActionState = "delivered" | "scheduled" | "pending" | "partial" | "recovered" | "failed";
|
|
221
|
+
export type SteeringTargetState = "scheduled" | "routed" | "delivered" | "late" | "failed" | "recovered";
|
|
222
|
+
|
|
223
|
+
export interface SteeringTargetStatus {
|
|
224
|
+
index: number;
|
|
225
|
+
state: SteeringTargetState;
|
|
226
|
+
routedAt?: number;
|
|
227
|
+
deliveredAt?: number;
|
|
228
|
+
lateDeliveredAt?: number;
|
|
229
|
+
failedAt?: number;
|
|
230
|
+
recoveredAt?: number;
|
|
231
|
+
reason?: string;
|
|
232
|
+
replacementRunId?: string;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export interface SteeringRequestStatus {
|
|
236
|
+
id: string;
|
|
237
|
+
requestedAt: number;
|
|
238
|
+
source?: string;
|
|
239
|
+
messagePreview: string;
|
|
240
|
+
targets: SteeringTargetStatus[];
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export interface SteeringStatus {
|
|
244
|
+
requested: number;
|
|
245
|
+
scheduled: number;
|
|
246
|
+
pending: number;
|
|
247
|
+
delivered: number;
|
|
248
|
+
failed: number;
|
|
249
|
+
recovered: number;
|
|
250
|
+
lastRequestedAt?: number;
|
|
251
|
+
lastDeliveredAt?: number;
|
|
252
|
+
recent: SteeringRequestStatus[];
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export interface SteerActionTarget {
|
|
256
|
+
index: number;
|
|
257
|
+
state: SteeringTargetState;
|
|
258
|
+
deliveredAt?: number;
|
|
259
|
+
lateDeliveredAt?: number;
|
|
260
|
+
reason?: string;
|
|
261
|
+
replacementRunId?: string;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export interface SteerActionResult {
|
|
265
|
+
requestId: string;
|
|
266
|
+
state: SteeringActionState;
|
|
267
|
+
sourceRunId: string;
|
|
268
|
+
replacementRunId?: string;
|
|
269
|
+
targets: SteerActionTarget[];
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export interface SteeringNotice {
|
|
273
|
+
type: "subagent.steering.notice";
|
|
274
|
+
ts: number;
|
|
275
|
+
runId: string;
|
|
276
|
+
requestId: string;
|
|
277
|
+
state: "failed" | "partial" | "recovered";
|
|
278
|
+
message: string;
|
|
279
|
+
currentSessionId?: string;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export interface SteeringRecoveryDescriptor {
|
|
283
|
+
version: 1;
|
|
284
|
+
sourceRunId: string;
|
|
285
|
+
agent: string;
|
|
286
|
+
sessionFile?: string;
|
|
287
|
+
cwd: string;
|
|
288
|
+
model?: string;
|
|
289
|
+
fallbackModels?: string[];
|
|
290
|
+
thinking?: string;
|
|
291
|
+
tools?: string[];
|
|
292
|
+
extensions?: string[];
|
|
293
|
+
subagentOnlyExtensions?: string[];
|
|
294
|
+
mcpDirectTools?: string[];
|
|
295
|
+
systemPrompt?: string;
|
|
296
|
+
systemPromptMode: "append" | "replace";
|
|
297
|
+
inheritProjectContext: boolean;
|
|
298
|
+
inheritSkills: boolean;
|
|
299
|
+
skills?: string[];
|
|
300
|
+
skillPath?: string[];
|
|
301
|
+
agentFilePath?: string;
|
|
302
|
+
completionGuard?: boolean;
|
|
303
|
+
memory?: { scope: "project" | "user"; path: string };
|
|
304
|
+
outputPath?: string;
|
|
305
|
+
outputMode: "inline" | "file-only";
|
|
306
|
+
acceptance?: AcceptanceInput;
|
|
307
|
+
controlConfig?: ResolvedControlConfig;
|
|
308
|
+
absoluteDeadlineAt?: number;
|
|
309
|
+
initialTurnBudget?: ResolvedTurnBudget;
|
|
310
|
+
initialToolBudget?: ResolvedToolBudget;
|
|
311
|
+
maxSubagentDepth: number;
|
|
312
|
+
maxOutput?: MaxOutputConfig;
|
|
313
|
+
share: boolean;
|
|
314
|
+
sessionDir?: string;
|
|
315
|
+
artifactsDir?: string;
|
|
316
|
+
artifactConfig?: ArtifactConfig;
|
|
317
|
+
}
|
|
318
|
+
|
|
217
319
|
export type PublicNestedStepSummary = Pick<
|
|
218
320
|
NestedStepSummary,
|
|
219
|
-
"agent" | "status" | "sessionFile" | "transcriptPath" | "transcriptError" | "activityState" | "lastActivityAt" | "currentTool" | "currentToolStartedAt" | "currentPath" | "turnCount" | "toolCount" | "toolBudget" | "toolBudgetBlocked" | "startedAt" | "endedAt" | "error" | "timedOut"
|
|
321
|
+
"agent" | "status" | "sessionFile" | "transcriptPath" | "transcriptError" | "activityState" | "lastActivityAt" | "currentTool" | "currentToolStartedAt" | "currentPath" | "turnCount" | "toolCount" | "toolBudget" | "toolBudgetBlocked" | "startedAt" | "endedAt" | "error" | "timedOut" | "stopped"
|
|
220
322
|
> & {
|
|
221
323
|
children?: PublicNestedRunSummary[];
|
|
222
324
|
};
|
|
@@ -229,7 +331,7 @@ export type CostSummary = {
|
|
|
229
331
|
|
|
230
332
|
export type PublicNestedRunSummary = Pick<
|
|
231
333
|
NestedRunSummary,
|
|
232
|
-
"id" | "parentRunId" | "parentStepIndex" | "parentAgent" | "depth" | "path" | "asyncDir" | "sessionId" | "sessionFile" | "intercomTarget" | "ownerIntercomTarget" | "leafIntercomTarget" | "ownerState" | "mode" | "state" | "agent" | "agents" | "currentStep" | "chainStepCount" | "parallelGroups" | "activityState" | "lastActivityAt" | "currentTool" | "currentToolStartedAt" | "currentPath" | "turnCount" | "toolCount" | "toolBudget" | "toolBudgetBlocked" | "totalTokens" | "totalCost" | "startedAt" | "endedAt" | "lastUpdate" | "error" | "timeoutMs" | "deadlineAt" | "timedOut" | "turnBudget" | "turnBudgetExceeded" | "wrapUpRequested"
|
|
334
|
+
"id" | "parentRunId" | "parentStepIndex" | "parentAgent" | "depth" | "path" | "asyncDir" | "sessionId" | "sessionFile" | "intercomTarget" | "ownerIntercomTarget" | "leafIntercomTarget" | "ownerState" | "mode" | "state" | "agent" | "agents" | "currentStep" | "chainStepCount" | "parallelGroups" | "activityState" | "lastActivityAt" | "currentTool" | "currentToolStartedAt" | "currentPath" | "turnCount" | "toolCount" | "toolBudget" | "toolBudgetBlocked" | "totalTokens" | "totalCost" | "startedAt" | "endedAt" | "lastUpdate" | "error" | "timeoutMs" | "deadlineAt" | "timedOut" | "stopped" | "turnBudget" | "turnBudgetExceeded" | "wrapUpRequested"
|
|
233
335
|
> & {
|
|
234
336
|
steps?: PublicNestedStepSummary[];
|
|
235
337
|
children?: PublicNestedRunSummary[];
|
|
@@ -269,6 +371,15 @@ export interface SubagentResultIntercomPayload {
|
|
|
269
371
|
// Progress Tracking
|
|
270
372
|
// ============================================================================
|
|
271
373
|
|
|
374
|
+
export interface ChildWatchdogProgress {
|
|
375
|
+
phase: "idle" | "reviewing" | "autofollow" | "settling" | "stale" | "failed";
|
|
376
|
+
seq: number;
|
|
377
|
+
lastUpdate: number;
|
|
378
|
+
followUpPending: boolean;
|
|
379
|
+
reason?: string;
|
|
380
|
+
timedOut?: boolean;
|
|
381
|
+
}
|
|
382
|
+
|
|
272
383
|
export interface AgentProgress {
|
|
273
384
|
index: number;
|
|
274
385
|
agent: string;
|
|
@@ -289,6 +400,7 @@ export interface AgentProgress {
|
|
|
289
400
|
durationMs: number;
|
|
290
401
|
error?: string;
|
|
291
402
|
failedTool?: string;
|
|
403
|
+
watchdog?: ChildWatchdogProgress;
|
|
292
404
|
}
|
|
293
405
|
|
|
294
406
|
export interface ToolCallSummary {
|
|
@@ -359,7 +471,8 @@ export interface AcceptanceConfig {
|
|
|
359
471
|
reason?: string;
|
|
360
472
|
}
|
|
361
473
|
|
|
362
|
-
|
|
474
|
+
/** Bare "none" is not accepted: use { level: "none", reason: "..." }; false remains a deprecated shorthand. */
|
|
475
|
+
export type AcceptanceInput = Exclude<AcceptanceLevel, "none"> | false | AcceptanceConfig;
|
|
363
476
|
|
|
364
477
|
export interface ResolvedAcceptanceGate extends AcceptanceGate {
|
|
365
478
|
id: string;
|
|
@@ -432,6 +545,7 @@ export interface AcceptanceReviewResult {
|
|
|
432
545
|
}
|
|
433
546
|
|
|
434
547
|
export type AcceptanceLedgerStatus =
|
|
548
|
+
| "pending"
|
|
435
549
|
| "not-required"
|
|
436
550
|
| "claimed"
|
|
437
551
|
| "attested"
|
|
@@ -459,6 +573,15 @@ export interface AcceptanceLedger {
|
|
|
459
573
|
};
|
|
460
574
|
}
|
|
461
575
|
|
|
576
|
+
export interface ProtocolOutputLimit {
|
|
577
|
+
code: "protocol_output_limit";
|
|
578
|
+
stream: "stdout" | "stderr";
|
|
579
|
+
limitBytes: number;
|
|
580
|
+
observedBytes: number;
|
|
581
|
+
diagnosticPrefix: string;
|
|
582
|
+
diagnosticTail: string;
|
|
583
|
+
}
|
|
584
|
+
|
|
462
585
|
export interface SingleResult {
|
|
463
586
|
agent: string;
|
|
464
587
|
task: string;
|
|
@@ -467,6 +590,7 @@ export interface SingleResult {
|
|
|
467
590
|
detachedReason?: string;
|
|
468
591
|
interrupted?: boolean;
|
|
469
592
|
timedOut?: boolean;
|
|
593
|
+
stopped?: boolean;
|
|
470
594
|
turnBudget?: TurnBudgetState;
|
|
471
595
|
turnBudgetExceeded?: boolean;
|
|
472
596
|
wrapUpRequested?: boolean;
|
|
@@ -479,6 +603,7 @@ export interface SingleResult {
|
|
|
479
603
|
modelAttempts?: ModelAttempt[];
|
|
480
604
|
controlEvents?: ControlEvent[];
|
|
481
605
|
error?: string;
|
|
606
|
+
protocolError?: ProtocolOutputLimit;
|
|
482
607
|
sessionFile?: string;
|
|
483
608
|
skills?: string[];
|
|
484
609
|
skillsWarning?: string;
|
|
@@ -499,6 +624,25 @@ export interface SingleResult {
|
|
|
499
624
|
transcriptPath?: string;
|
|
500
625
|
transcriptError?: string;
|
|
501
626
|
children?: NestedRunSummary[];
|
|
627
|
+
watchdog?: ChildWatchdogProgress;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
export interface SpawnBudgetGrant {
|
|
631
|
+
sessionId: string;
|
|
632
|
+
amount: number;
|
|
633
|
+
grantedAt: number;
|
|
634
|
+
previousLimit: number;
|
|
635
|
+
limit: number;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
export interface SpawnBudgetSnapshot {
|
|
639
|
+
used: number;
|
|
640
|
+
configuredLimit: number | null;
|
|
641
|
+
granted: number;
|
|
642
|
+
limit: number | null;
|
|
643
|
+
remaining: number | null;
|
|
644
|
+
grantRemaining: number | null;
|
|
645
|
+
grantHistory: SpawnBudgetGrant[];
|
|
502
646
|
}
|
|
503
647
|
|
|
504
648
|
export interface Details {
|
|
@@ -507,11 +651,13 @@ export interface Details {
|
|
|
507
651
|
context?: "fresh" | "fork";
|
|
508
652
|
results: SingleResult[];
|
|
509
653
|
controlEvents?: ControlEvent[];
|
|
654
|
+
steering?: SteerActionResult;
|
|
510
655
|
asyncId?: string;
|
|
511
656
|
asyncDir?: string;
|
|
512
657
|
timeoutMs?: number;
|
|
513
658
|
deadlineAt?: number;
|
|
514
659
|
timedOut?: boolean;
|
|
660
|
+
stopped?: boolean;
|
|
515
661
|
turnBudget?: ResolvedTurnBudget;
|
|
516
662
|
toolBudget?: ResolvedToolBudget;
|
|
517
663
|
progress?: AgentProgress[];
|
|
@@ -536,6 +682,7 @@ export interface Details {
|
|
|
536
682
|
totalChildUsage?: Usage;
|
|
537
683
|
// Aggregated cost across all agents in the run
|
|
538
684
|
totalCost?: CostSummary;
|
|
685
|
+
spawnBudget?: SpawnBudgetSnapshot;
|
|
539
686
|
}
|
|
540
687
|
|
|
541
688
|
// ============================================================================
|
|
@@ -570,7 +717,7 @@ export interface AsyncParallelGroupStatus {
|
|
|
570
717
|
stepIndex: number;
|
|
571
718
|
}
|
|
572
719
|
|
|
573
|
-
export type NestedRunState = "queued" | "running" | "complete" | "failed" | "paused";
|
|
720
|
+
export type NestedRunState = "queued" | "running" | "complete" | "failed" | "paused" | "stopped";
|
|
574
721
|
export type NestedOwnerState = "live" | "gone" | "unknown";
|
|
575
722
|
|
|
576
723
|
export interface NestedRunAddress {
|
|
@@ -584,7 +731,7 @@ export interface NestedRunAddress {
|
|
|
584
731
|
|
|
585
732
|
export interface NestedStepSummary {
|
|
586
733
|
agent: string;
|
|
587
|
-
status: "pending" | "running" | "complete" | "completed" | "failed" | "paused";
|
|
734
|
+
status: "pending" | "running" | "complete" | "completed" | "failed" | "paused" | "stopped";
|
|
588
735
|
sessionFile?: string;
|
|
589
736
|
transcriptPath?: string;
|
|
590
737
|
transcriptError?: string;
|
|
@@ -598,7 +745,9 @@ export interface NestedStepSummary {
|
|
|
598
745
|
startedAt?: number;
|
|
599
746
|
endedAt?: number;
|
|
600
747
|
error?: string;
|
|
748
|
+
watchdog?: ChildWatchdogProgress;
|
|
601
749
|
timedOut?: boolean;
|
|
750
|
+
stopped?: boolean;
|
|
602
751
|
turnBudget?: TurnBudgetState;
|
|
603
752
|
turnBudgetExceeded?: boolean;
|
|
604
753
|
wrapUpRequested?: boolean;
|
|
@@ -642,6 +791,7 @@ export interface NestedRunSummary extends NestedRunAddress {
|
|
|
642
791
|
timeoutMs?: number;
|
|
643
792
|
deadlineAt?: number;
|
|
644
793
|
timedOut?: boolean;
|
|
794
|
+
stopped?: boolean;
|
|
645
795
|
turnBudget?: TurnBudgetState;
|
|
646
796
|
turnBudgetExceeded?: boolean;
|
|
647
797
|
wrapUpRequested?: boolean;
|
|
@@ -666,6 +816,10 @@ export interface AsyncStartedEvent {
|
|
|
666
816
|
mode?: SubagentRunMode;
|
|
667
817
|
agent?: string;
|
|
668
818
|
agents?: string[];
|
|
819
|
+
/** Truncated first child task retained for backwards compatibility. */
|
|
820
|
+
task?: string;
|
|
821
|
+
/** Workflow-level caller task, falling back to the first child task. */
|
|
822
|
+
goal?: string;
|
|
669
823
|
chain?: string[];
|
|
670
824
|
chainStepCount?: number;
|
|
671
825
|
parallelGroups?: AsyncParallelGroupStatus[];
|
|
@@ -681,7 +835,8 @@ export interface AsyncStatus {
|
|
|
681
835
|
runId: string;
|
|
682
836
|
sessionId?: string;
|
|
683
837
|
mode: SubagentRunMode;
|
|
684
|
-
|
|
838
|
+
isNested?: boolean;
|
|
839
|
+
state: "queued" | "running" | "complete" | "failed" | "paused" | "stopped";
|
|
685
840
|
error?: string;
|
|
686
841
|
activityState?: ActivityState;
|
|
687
842
|
lastActivityAt?: number;
|
|
@@ -690,14 +845,14 @@ export interface AsyncStatus {
|
|
|
690
845
|
currentPath?: string;
|
|
691
846
|
turnCount?: number;
|
|
692
847
|
toolCount?: number;
|
|
693
|
-
|
|
694
|
-
lastSteerAt?: number;
|
|
848
|
+
steering?: SteeringStatus;
|
|
695
849
|
startedAt: number;
|
|
696
850
|
endedAt?: number;
|
|
697
851
|
lastUpdate?: number;
|
|
698
852
|
timeoutMs?: number;
|
|
699
853
|
deadlineAt?: number;
|
|
700
854
|
timedOut?: boolean;
|
|
855
|
+
stopped?: boolean;
|
|
701
856
|
turnBudget?: TurnBudgetState;
|
|
702
857
|
turnBudgetExceeded?: boolean;
|
|
703
858
|
wrapUpRequested?: boolean;
|
|
@@ -716,7 +871,7 @@ export interface AsyncStatus {
|
|
|
716
871
|
label?: string;
|
|
717
872
|
outputName?: string;
|
|
718
873
|
structured?: boolean;
|
|
719
|
-
status: "pending" | "running" | "complete" | "completed" | "failed" | "paused";
|
|
874
|
+
status: "pending" | "running" | "complete" | "completed" | "failed" | "paused" | "stopped";
|
|
720
875
|
children?: NestedRunSummary[];
|
|
721
876
|
sessionFile?: string;
|
|
722
877
|
transcriptPath?: string;
|
|
@@ -736,6 +891,7 @@ export interface AsyncStatus {
|
|
|
736
891
|
durationMs?: number;
|
|
737
892
|
exitCode?: number | null;
|
|
738
893
|
timedOut?: boolean;
|
|
894
|
+
stopped?: boolean;
|
|
739
895
|
turnBudget?: TurnBudgetState;
|
|
740
896
|
turnBudgetExceeded?: boolean;
|
|
741
897
|
wrapUpRequested?: boolean;
|
|
@@ -748,13 +904,13 @@ export interface AsyncStatus {
|
|
|
748
904
|
attemptedModels?: string[];
|
|
749
905
|
modelAttempts?: ModelAttempt[];
|
|
750
906
|
totalCost?: CostSummary;
|
|
751
|
-
|
|
752
|
-
lastSteerAt?: number;
|
|
907
|
+
steering?: SteeringStatus;
|
|
753
908
|
error?: string;
|
|
754
909
|
structuredOutput?: unknown;
|
|
755
910
|
structuredOutputPath?: string;
|
|
756
911
|
structuredOutputSchemaPath?: string;
|
|
757
912
|
acceptance?: AcceptanceLedger;
|
|
913
|
+
watchdog?: ChildWatchdogProgress;
|
|
758
914
|
}>;
|
|
759
915
|
sessionDir?: string;
|
|
760
916
|
outputFile?: string;
|
|
@@ -771,7 +927,7 @@ export type AsyncJobStep = NonNullable<AsyncStatus["steps"]>[number] & {
|
|
|
771
927
|
export interface AsyncJobState {
|
|
772
928
|
asyncId: string;
|
|
773
929
|
asyncDir: string;
|
|
774
|
-
status: "queued" | "running" | "complete" | "failed" | "paused";
|
|
930
|
+
status: "queued" | "running" | "complete" | "failed" | "paused" | "stopped";
|
|
775
931
|
pid?: number;
|
|
776
932
|
sessionId?: string;
|
|
777
933
|
activityState?: ActivityState;
|
|
@@ -781,8 +937,7 @@ export interface AsyncJobState {
|
|
|
781
937
|
currentPath?: string;
|
|
782
938
|
turnCount?: number;
|
|
783
939
|
toolCount?: number;
|
|
784
|
-
|
|
785
|
-
lastSteerAt?: number;
|
|
940
|
+
steering?: SteeringStatus;
|
|
786
941
|
mode?: SubagentRunMode;
|
|
787
942
|
agents?: string[];
|
|
788
943
|
currentStep?: number;
|
|
@@ -799,6 +954,7 @@ export interface AsyncJobState {
|
|
|
799
954
|
timeoutMs?: number;
|
|
800
955
|
deadlineAt?: number;
|
|
801
956
|
timedOut?: boolean;
|
|
957
|
+
stopped?: boolean;
|
|
802
958
|
turnBudget?: TurnBudgetState;
|
|
803
959
|
turnBudgetExceeded?: boolean;
|
|
804
960
|
wrapUpRequested?: boolean;
|
|
@@ -819,11 +975,16 @@ export interface ForegroundResumeChild {
|
|
|
819
975
|
sessionFile?: string;
|
|
820
976
|
status: SubagentResultStatus;
|
|
821
977
|
exitCode?: number;
|
|
978
|
+
error?: string;
|
|
822
979
|
finalOutput?: string;
|
|
980
|
+
outputMode?: OutputMode;
|
|
981
|
+
savedOutputPath?: string;
|
|
982
|
+
outputSaveError?: string;
|
|
823
983
|
artifactPaths?: ArtifactPaths;
|
|
824
984
|
transcriptPath?: string;
|
|
825
985
|
transcriptError?: string;
|
|
826
986
|
detachedReason?: string;
|
|
987
|
+
acceptance?: AcceptanceLedger;
|
|
827
988
|
updatedAt?: number;
|
|
828
989
|
}
|
|
829
990
|
|
|
@@ -831,6 +992,8 @@ export interface ForegroundResumeRun {
|
|
|
831
992
|
runId: string;
|
|
832
993
|
mode: SubagentRunMode;
|
|
833
994
|
cwd: string;
|
|
995
|
+
/** Originating parent session. Detached exits can outlive the active session. */
|
|
996
|
+
sessionId?: string;
|
|
834
997
|
updatedAt: number;
|
|
835
998
|
children: ForegroundResumeChild[];
|
|
836
999
|
}
|
|
@@ -839,8 +1002,16 @@ export interface SubagentState {
|
|
|
839
1002
|
baseCwd: string;
|
|
840
1003
|
currentSessionId: string | null;
|
|
841
1004
|
subagentInProgress?: boolean;
|
|
842
|
-
subagentSpawns?: {
|
|
1005
|
+
subagentSpawns?: {
|
|
1006
|
+
sessionId: string | null;
|
|
1007
|
+
count: number;
|
|
1008
|
+
configuredLimit?: number | null;
|
|
1009
|
+
granted?: number;
|
|
1010
|
+
grantHistory?: SpawnBudgetGrant[];
|
|
1011
|
+
};
|
|
843
1012
|
asyncJobs: Map<string, AsyncJobState>;
|
|
1013
|
+
/** Current-session active and recent async runs for the native fleet inspector. */
|
|
1014
|
+
fleetJobs?: Map<string, AsyncJobState>;
|
|
844
1015
|
foregroundRuns?: Map<string, ForegroundResumeRun>;
|
|
845
1016
|
foregroundControls: Map<string, {
|
|
846
1017
|
runId: string;
|
|
@@ -903,8 +1074,10 @@ export const INTERCOM_DETACH_REQUEST_EVENT = "pi-intercom:detach-request";
|
|
|
903
1074
|
export const INTERCOM_DETACH_RESPONSE_EVENT = "pi-intercom:detach-response";
|
|
904
1075
|
export const SUBAGENT_ASYNC_STARTED_EVENT = "subagent:async-started";
|
|
905
1076
|
export const SUBAGENT_ASYNC_COMPLETE_EVENT = "subagent:async-complete";
|
|
1077
|
+
export const SUBAGENT_FOREGROUND_COMPLETE_EVENT = "subagent:foreground-complete";
|
|
906
1078
|
export const SUBAGENT_CONTROL_EVENT = "subagent:control-event";
|
|
907
1079
|
export const SUBAGENT_CONTROL_INTERCOM_EVENT = "subagent:control-intercom";
|
|
1080
|
+
export const SUBAGENT_STEERING_NOTICE_EVENT = "subagent:steering-notice";
|
|
908
1081
|
export const SUBAGENT_RESULT_INTERCOM_EVENT = "subagent:result-intercom";
|
|
909
1082
|
export const SUBAGENT_RESULT_INTERCOM_DELIVERY_EVENT = "subagent:result-intercom-delivery";
|
|
910
1083
|
|
|
@@ -941,6 +1114,8 @@ export interface RunSyncOptions {
|
|
|
941
1114
|
outputPath?: string;
|
|
942
1115
|
outputMode?: OutputMode;
|
|
943
1116
|
maxSubagentDepth?: number;
|
|
1117
|
+
/** Effective parent wait-tool setting propagated to the child runtime. */
|
|
1118
|
+
waitToolEnabled?: boolean;
|
|
944
1119
|
nestedRoute?: NestedRouteInfo;
|
|
945
1120
|
/** Override the agent's default model (format: "provider/id" or just "id") */
|
|
946
1121
|
modelOverride?: string;
|
|
@@ -1003,6 +1178,8 @@ export interface ScheduledRunsConfig {
|
|
|
1003
1178
|
|
|
1004
1179
|
export interface ExtensionConfig {
|
|
1005
1180
|
asyncByDefault?: boolean;
|
|
1181
|
+
/** Show the above-editor async runs widget. Defaults to true. */
|
|
1182
|
+
asyncWidget?: boolean;
|
|
1006
1183
|
/** Tool description variant registered for the parent-facing subagent tool. Defaults to full. */
|
|
1007
1184
|
toolDescriptionMode?: ToolDescriptionMode;
|
|
1008
1185
|
forceTopLevelAsync?: boolean;
|
|
@@ -1010,6 +1187,7 @@ export interface ExtensionConfig {
|
|
|
1010
1187
|
defaultSessionDir?: string;
|
|
1011
1188
|
singleRunOutputBaseDir?: string;
|
|
1012
1189
|
maxSubagentDepth?: number;
|
|
1190
|
+
/** Optional cumulative session cap. Unset or 0 means unlimited. */
|
|
1013
1191
|
maxSubagentSpawnsPerSession?: number;
|
|
1014
1192
|
/** Global cap on simultaneously-running subagent tasks within a single run. Defaults to 20. */
|
|
1015
1193
|
globalConcurrencyLimit?: number;
|
|
@@ -1117,8 +1295,7 @@ export const SLASH_SUBAGENT_CANCEL_EVENT = "subagent:slash:cancel";
|
|
|
1117
1295
|
export const POLL_INTERVAL_MS = 250;
|
|
1118
1296
|
export const MAX_WIDGET_JOBS = 4;
|
|
1119
1297
|
export const DEFAULT_SUBAGENT_MAX_DEPTH = 2;
|
|
1120
|
-
export const
|
|
1121
|
-
export const SUBAGENT_ACTIONS = ["list", "get", "models", "create", "update", "delete", "eject", "disable", "enable", "reset", "status", "interrupt", "resume", "steer", "append-step", "doctor", "schedule", "schedule-list", "schedule-status", "schedule-cancel"] as const;
|
|
1298
|
+
export const SUBAGENT_ACTIONS = ["list", "get", "models", "create", "update", "delete", "eject", "disable", "enable", "reset", "status", "grant-spawn-budget", "interrupt", "resume", "steer", "stop", "append-step", "doctor", "watchdog.status", "watchdog.check", "watchdog.configure", "watchdog.recommend-model", "schedule", "schedule-list", "schedule-status", "schedule-cancel"] as const;
|
|
1122
1299
|
|
|
1123
1300
|
export const DEFAULT_FORK_PREAMBLE =
|
|
1124
1301
|
"You are a delegated subagent running from a fork of the parent session. " +
|
|
@@ -1203,10 +1380,11 @@ export function normalizeMaxSubagentSpawnsPerSession(value: unknown): number | u
|
|
|
1203
1380
|
return normalizeNonNegativeInteger(value);
|
|
1204
1381
|
}
|
|
1205
1382
|
|
|
1206
|
-
export function resolveMaxSubagentSpawnsPerSession(configMaxSpawns?: number): number {
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1383
|
+
export function resolveMaxSubagentSpawnsPerSession(configMaxSpawns?: number): number | undefined {
|
|
1384
|
+
const envMaxSpawns = normalizeMaxSubagentSpawnsPerSession(process.env.PI_SUBAGENT_MAX_SPAWNS_PER_SESSION);
|
|
1385
|
+
if (envMaxSpawns !== undefined) return envMaxSpawns === 0 ? undefined : envMaxSpawns;
|
|
1386
|
+
const configuredMaxSpawns = normalizeMaxSubagentSpawnsPerSession(configMaxSpawns);
|
|
1387
|
+
return configuredMaxSpawns === 0 ? undefined : configuredMaxSpawns;
|
|
1210
1388
|
}
|
|
1211
1389
|
|
|
1212
1390
|
// ============================================================================
|