pi-maestro-teammate 2.6.0 → 2.6.2
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/package.json +5 -1
- package/src/agents/agents.ts +27 -1
- package/src/extension/index.ts +3 -3
- package/src/extension/teammate-core.ts +2 -76
- package/src/extension/teammate-proxy.ts +7 -3
- package/src/public/v1/foreground-detach.ts +90 -0
- package/src/public/v1/index.ts +1 -0
- package/src/runs/execution-infra.ts +220 -48
- package/src/runs/pi-subprocess-attempt.ts +31 -11
- package/src/shared/routing.ts +10 -0
- package/types/extension/teammate-core.d.ts +3 -4
- package/types/public/v1/foreground-detach.d.ts +4 -0
- package/types/public/v1/index.d.ts +1 -0
- package/types/runs/execution-infra.d.ts +32 -4
- package/types/shared/routing.d.ts +2 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-maestro-teammate",
|
|
3
|
-
"version": "2.6.
|
|
3
|
+
"version": "2.6.2",
|
|
4
4
|
"description": "Pi extension — teammate agent dispatch with DAG task graphs, RPC messaging, and compact TUI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -69,6 +69,10 @@
|
|
|
69
69
|
"types": "./types/public/v1/extension.d.ts",
|
|
70
70
|
"default": "./src/public/v1/extension.ts"
|
|
71
71
|
},
|
|
72
|
+
"./v1/foreground-detach": {
|
|
73
|
+
"types": "./types/public/v1/foreground-detach.d.ts",
|
|
74
|
+
"default": "./src/public/v1/foreground-detach.ts"
|
|
75
|
+
},
|
|
72
76
|
"./v1/model-routing": {
|
|
73
77
|
"types": "./types/public/v1/model-routing.d.ts",
|
|
74
78
|
"default": "./src/public/v1/model-routing.ts"
|
package/src/agents/agents.ts
CHANGED
|
@@ -197,6 +197,23 @@ function isReservedAgentName(name: string): boolean {
|
|
|
197
197
|
return isBuiltinAgentName(name);
|
|
198
198
|
}
|
|
199
199
|
|
|
200
|
+
function agentDefinitionFingerprint(agent: AgentConfig): string {
|
|
201
|
+
return JSON.stringify([
|
|
202
|
+
agent.name,
|
|
203
|
+
agent.description,
|
|
204
|
+
agent.tools ?? null,
|
|
205
|
+
agent.model ?? null,
|
|
206
|
+
agent.fallbackModels ?? null,
|
|
207
|
+
agent.taskType ?? null,
|
|
208
|
+
agent.thinking ?? null,
|
|
209
|
+
agent.systemPromptMode,
|
|
210
|
+
agent.inheritProjectContext,
|
|
211
|
+
agent.inheritSkills,
|
|
212
|
+
agent.defaultContext ?? null,
|
|
213
|
+
agent.systemPrompt,
|
|
214
|
+
]);
|
|
215
|
+
}
|
|
216
|
+
|
|
200
217
|
interface DiscoveryDirs {
|
|
201
218
|
legacyUserAgentsDir: string;
|
|
202
219
|
userPiAgentsDir: string;
|
|
@@ -394,8 +411,17 @@ export function discoverAgents(
|
|
|
394
411
|
}> = [];
|
|
395
412
|
const mergeAgent = (agent: AgentConfig): void => {
|
|
396
413
|
// Builtin names are reserved so custom definitions cannot silently replace
|
|
397
|
-
// the stable general, exploration, and DAG orchestration roles.
|
|
414
|
+
// the stable general, exploration, and DAG orchestration roles. Package
|
|
415
|
+
// catalogs may carry byte-equivalent builtin mirrors for other consumers;
|
|
416
|
+
// those are duplicates, not attempted overrides.
|
|
398
417
|
if (agent.source !== "builtin" && isReservedAgentName(agent.name)) {
|
|
418
|
+
const builtin = agentMap.get(agent.name);
|
|
419
|
+
if (
|
|
420
|
+
builtin?.source === "builtin"
|
|
421
|
+
&& agentDefinitionFingerprint(builtin) === agentDefinitionFingerprint(agent)
|
|
422
|
+
) {
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
399
425
|
rejectedCandidates.push({ name: agent.name, reason: "reserved-builtin", candidate: agent });
|
|
400
426
|
return;
|
|
401
427
|
}
|
package/src/extension/index.ts
CHANGED
|
@@ -487,7 +487,7 @@ import {
|
|
|
487
487
|
type RuntimeReadModelSnapshotV2,
|
|
488
488
|
} from "../runtime-v2/read-model.ts";
|
|
489
489
|
import { RuntimeReadModelBrokerBridge } from "../runtime-v2/broker-read-model.ts";
|
|
490
|
-
import { formatLocalAgentMessage } from "../shared/routing.ts";
|
|
490
|
+
import { formatLocalAgentMessage, formatNoRestorableRuntimeError } from "../shared/routing.ts";
|
|
491
491
|
export * from "./teammate-core.ts";
|
|
492
492
|
import {
|
|
493
493
|
appendTeammateDepthContext,
|
|
@@ -2704,7 +2704,7 @@ export default function registerTeammateExtension(
|
|
|
2704
2704
|
const restarted = agent.status === "sleeping" && agent.restart?.(message, provenance) === true;
|
|
2705
2705
|
if (!restarted) {
|
|
2706
2706
|
restoreDeferredAgentContext(agent, deferredContext);
|
|
2707
|
-
return { delivered: false, error:
|
|
2707
|
+
return { delivered: false, error: formatNoRestorableRuntimeError(targetLabel, agent.status) };
|
|
2708
2708
|
}
|
|
2709
2709
|
const restartDelivery = agent.restartDelivery;
|
|
2710
2710
|
if (restartDelivery) {
|
|
@@ -6201,7 +6201,7 @@ export default function registerTeammateExtension(
|
|
|
6201
6201
|
const agent = cid ? state.activeRuns.get(cid) : undefined;
|
|
6202
6202
|
if (agent && !LIVE_AGENT_STATUSES.has(agent.status)) {
|
|
6203
6203
|
return {
|
|
6204
|
-
content: [{ type: "text", text:
|
|
6204
|
+
content: [{ type: "text", text: formatNoRestorableRuntimeError(params.to, agent.status) }],
|
|
6205
6205
|
isError: true,
|
|
6206
6206
|
details: { delivered: false },
|
|
6207
6207
|
};
|
|
@@ -10,11 +10,11 @@ import { randomUUID } from "node:crypto";
|
|
|
10
10
|
import { logDiagnosticError, logDiagnosticWarn } from "../shared/diagnostic-log.ts";
|
|
11
11
|
|
|
12
12
|
import { altKey } from "pi-maestro-settings-core/v1";
|
|
13
|
+
import { registerForegroundDetach, setPersistentUi } from "../public/v1/foreground-detach.ts";
|
|
13
14
|
import type {
|
|
14
15
|
ExtensionAPI,
|
|
15
16
|
ExtensionCommandContext,
|
|
16
17
|
ExtensionContext,
|
|
17
|
-
ExtensionUIContext,
|
|
18
18
|
ToolDefinition,
|
|
19
19
|
} from "@earendil-works/pi-coding-agent";
|
|
20
20
|
import { createHash } from "node:crypto";
|
|
@@ -896,81 +896,7 @@ export function backgroundWaitGuidance(correlationId: string): string {
|
|
|
896
896
|
*/
|
|
897
897
|
export const FOREGROUND_DETACH_HINT = `${altKey("B")} detaches a foreground call to background.`;
|
|
898
898
|
|
|
899
|
-
|
|
900
|
-
* One session-scoped Alt+B listener dispatches to the oldest active foreground
|
|
901
|
-
* owner. This makes nested calls detach layer by layer from the outermost call
|
|
902
|
-
* instead of relying on TUI listener registration order.
|
|
903
|
-
*/
|
|
904
|
-
type ForegroundDetachOwner = {
|
|
905
|
-
active: boolean;
|
|
906
|
-
detach(): void;
|
|
907
|
-
};
|
|
908
|
-
|
|
909
|
-
let persistentUi: ExtensionUIContext | undefined;
|
|
910
|
-
let persistentUiUnsubscribe: (() => void) | undefined;
|
|
911
|
-
const foregroundDetachOwners: ForegroundDetachOwner[] = [];
|
|
912
|
-
|
|
913
|
-
function uninstallForegroundDetachListener(): void {
|
|
914
|
-
const unsubscribe = persistentUiUnsubscribe;
|
|
915
|
-
persistentUiUnsubscribe = undefined;
|
|
916
|
-
unsubscribe?.();
|
|
917
|
-
}
|
|
918
|
-
|
|
919
|
-
function installForegroundDetachListener(): void {
|
|
920
|
-
if (!persistentUi || persistentUiUnsubscribe || foregroundDetachOwners.length === 0) return;
|
|
921
|
-
persistentUiUnsubscribe = persistentUi.onTerminalInput((data: string) => {
|
|
922
|
-
if (data !== "\x1bb") return undefined;
|
|
923
|
-
const owner = foregroundDetachOwners.shift();
|
|
924
|
-
if (!owner) return undefined;
|
|
925
|
-
owner.active = false;
|
|
926
|
-
if (foregroundDetachOwners.length === 0) uninstallForegroundDetachListener();
|
|
927
|
-
owner.detach();
|
|
928
|
-
return { consume: true };
|
|
929
|
-
});
|
|
930
|
-
}
|
|
931
|
-
|
|
932
|
-
export function setPersistentUi(
|
|
933
|
-
ui: ExtensionUIContext | undefined,
|
|
934
|
-
resetOwners = false,
|
|
935
|
-
): void {
|
|
936
|
-
if (persistentUi !== ui || resetOwners) {
|
|
937
|
-
uninstallForegroundDetachListener();
|
|
938
|
-
persistentUi = ui;
|
|
939
|
-
}
|
|
940
|
-
if (!ui || resetOwners) {
|
|
941
|
-
for (const owner of foregroundDetachOwners) owner.active = false;
|
|
942
|
-
foregroundDetachOwners.length = 0;
|
|
943
|
-
if (!ui) return;
|
|
944
|
-
}
|
|
945
|
-
installForegroundDetachListener();
|
|
946
|
-
}
|
|
947
|
-
|
|
948
|
-
/** Registers one foreground owner; unregister is idempotent on every race path. */
|
|
949
|
-
export function registerForegroundDetach(
|
|
950
|
-
detach: () => void,
|
|
951
|
-
ui?: ExtensionUIContext,
|
|
952
|
-
): () => void {
|
|
953
|
-
if (ui) setPersistentUi(ui);
|
|
954
|
-
const owner: ForegroundDetachOwner = { active: true, detach };
|
|
955
|
-
foregroundDetachOwners.push(owner);
|
|
956
|
-
try {
|
|
957
|
-
installForegroundDetachListener();
|
|
958
|
-
} catch (error) {
|
|
959
|
-
owner.active = false;
|
|
960
|
-
const index = foregroundDetachOwners.indexOf(owner);
|
|
961
|
-
if (index >= 0) foregroundDetachOwners.splice(index, 1);
|
|
962
|
-
if (foregroundDetachOwners.length === 0) uninstallForegroundDetachListener();
|
|
963
|
-
throw error;
|
|
964
|
-
}
|
|
965
|
-
|
|
966
|
-
return () => {
|
|
967
|
-
if (!owner.active) return;
|
|
968
|
-
owner.active = false;
|
|
969
|
-
const index = foregroundDetachOwners.indexOf(owner);
|
|
970
|
-
if (index >= 0) foregroundDetachOwners.splice(index, 1);
|
|
971
|
-
if (foregroundDetachOwners.length === 0) uninstallForegroundDetachListener();
|
|
972
|
-
};
|
|
973
|
-
}
|
|
899
|
+
export { registerForegroundDetach, setPersistentUi };
|
|
974
900
|
|
|
975
901
|
export function foregroundWaitWindowMs(
|
|
976
902
|
tasks: ReadonlyArray<{ timeoutMs?: number }>,
|
|
@@ -20,7 +20,11 @@ import type { AgentToolResult } from "@earendil-works/pi-agent-core";
|
|
|
20
20
|
import { Check } from "typebox/value";
|
|
21
21
|
import { isGuiTeammateToolAllowed, registerGuiTool, unregisterGuiTool } from "../shared/gui-registry.ts";
|
|
22
22
|
import { aggregateAgentRunPhase, projectAgentRuntime } from "../shared/agent-status.ts";
|
|
23
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
formatLocalAgentMessage,
|
|
25
|
+
formatNoRestorableRuntimeError,
|
|
26
|
+
resolveAgentCompletionTarget,
|
|
27
|
+
} from "../shared/routing.ts";
|
|
24
28
|
import { Text, truncateToWidth } from "@earendil-works/pi-tui";
|
|
25
29
|
import { TeammateParams, TeammateSendParams, TeammateListParams, TeammateWatchParams, TeammateWaitParams, TeammateMonitorParams, ObserveParams, LocalObserveParams } from "./schemas.ts";
|
|
26
30
|
import {
|
|
@@ -3113,7 +3117,7 @@ export async function handleProxyRequest(
|
|
|
3113
3117
|
const agent = state.activeRuns.get(cid);
|
|
3114
3118
|
if (agent && !LIVE_AGENT_STATUSES.has(agent.status)) {
|
|
3115
3119
|
reply({ type: "teammate_proxy_result", requestId, result: {
|
|
3116
|
-
content: [{ type: "text", text:
|
|
3120
|
+
content: [{ type: "text", text: formatNoRestorableRuntimeError(to, agent.status) }],
|
|
3117
3121
|
isError: true, details: { delivered: false },
|
|
3118
3122
|
}});
|
|
3119
3123
|
return;
|
|
@@ -3298,7 +3302,7 @@ export async function handleProxyRequest(
|
|
|
3298
3302
|
if (!restarted || !agent) {
|
|
3299
3303
|
if (agent) restoreDeferredAgentContext(agent, deferredContext);
|
|
3300
3304
|
reply({ type: "teammate_proxy_result", requestId, result: {
|
|
3301
|
-
content: [{ type: "text", text:
|
|
3305
|
+
content: [{ type: "text", text: formatNoRestorableRuntimeError(to, agent?.status) }],
|
|
3302
3306
|
isError: true, details: { delivered: false },
|
|
3303
3307
|
}});
|
|
3304
3308
|
return;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { ExtensionUIContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
|
|
3
|
+
/** One session-scoped Alt+B listener shared by every foreground tool owner. */
|
|
4
|
+
type ForegroundDetachOwner = {
|
|
5
|
+
active: boolean;
|
|
6
|
+
detach(): void;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
type ForegroundDetachRegistry = {
|
|
10
|
+
ui?: ExtensionUIContext;
|
|
11
|
+
unsubscribe?: () => void;
|
|
12
|
+
owners: ForegroundDetachOwner[];
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const REGISTRY_KEY = Symbol.for("pi-maestro.foreground-detach.v1");
|
|
16
|
+
const globals = globalThis as typeof globalThis & Record<symbol, unknown>;
|
|
17
|
+
|
|
18
|
+
function registry(): ForegroundDetachRegistry {
|
|
19
|
+
const existing = globals[REGISTRY_KEY];
|
|
20
|
+
if (existing && typeof existing === "object" && Array.isArray((existing as ForegroundDetachRegistry).owners)) {
|
|
21
|
+
return existing as ForegroundDetachRegistry;
|
|
22
|
+
}
|
|
23
|
+
const created: ForegroundDetachRegistry = { owners: [] };
|
|
24
|
+
globals[REGISTRY_KEY] = created;
|
|
25
|
+
return created;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function uninstallForegroundDetachListener(state: ForegroundDetachRegistry): void {
|
|
29
|
+
const unsubscribe = state.unsubscribe;
|
|
30
|
+
state.unsubscribe = undefined;
|
|
31
|
+
unsubscribe?.();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function installForegroundDetachListener(state: ForegroundDetachRegistry): void {
|
|
35
|
+
if (!state.ui || state.unsubscribe || state.owners.length === 0) return;
|
|
36
|
+
state.unsubscribe = state.ui.onTerminalInput((data: string) => {
|
|
37
|
+
if (data !== "\x1bb") return undefined;
|
|
38
|
+
const owner = state.owners.shift();
|
|
39
|
+
if (!owner) return undefined;
|
|
40
|
+
owner.active = false;
|
|
41
|
+
if (state.owners.length === 0) uninstallForegroundDetachListener(state);
|
|
42
|
+
owner.detach();
|
|
43
|
+
return { consume: true };
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function setPersistentUi(
|
|
48
|
+
ui: ExtensionUIContext | undefined,
|
|
49
|
+
resetOwners = false,
|
|
50
|
+
): void {
|
|
51
|
+
const state = registry();
|
|
52
|
+
if (state.ui !== ui || resetOwners) {
|
|
53
|
+
uninstallForegroundDetachListener(state);
|
|
54
|
+
state.ui = ui;
|
|
55
|
+
}
|
|
56
|
+
if (!ui || resetOwners) {
|
|
57
|
+
for (const owner of state.owners) owner.active = false;
|
|
58
|
+
state.owners.length = 0;
|
|
59
|
+
if (!ui) return;
|
|
60
|
+
}
|
|
61
|
+
installForegroundDetachListener(state);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Registers one foreground owner; unregister is idempotent on every race path. */
|
|
65
|
+
export function registerForegroundDetach(
|
|
66
|
+
detach: () => void,
|
|
67
|
+
ui?: ExtensionUIContext,
|
|
68
|
+
): () => void {
|
|
69
|
+
if (ui) setPersistentUi(ui);
|
|
70
|
+
const state = registry();
|
|
71
|
+
const owner: ForegroundDetachOwner = { active: true, detach };
|
|
72
|
+
state.owners.push(owner);
|
|
73
|
+
try {
|
|
74
|
+
installForegroundDetachListener(state);
|
|
75
|
+
} catch (error) {
|
|
76
|
+
owner.active = false;
|
|
77
|
+
const index = state.owners.indexOf(owner);
|
|
78
|
+
if (index >= 0) state.owners.splice(index, 1);
|
|
79
|
+
if (state.owners.length === 0) uninstallForegroundDetachListener(state);
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return () => {
|
|
84
|
+
if (!owner.active) return;
|
|
85
|
+
owner.active = false;
|
|
86
|
+
const index = state.owners.indexOf(owner);
|
|
87
|
+
if (index >= 0) state.owners.splice(index, 1);
|
|
88
|
+
if (state.owners.length === 0) uninstallForegroundDetachListener(state);
|
|
89
|
+
};
|
|
90
|
+
}
|
package/src/public/v1/index.ts
CHANGED
|
@@ -16,6 +16,7 @@ export * from "./completion-durability.ts";
|
|
|
16
16
|
export * from "./events.ts";
|
|
17
17
|
export * from "./execution.ts";
|
|
18
18
|
export * from "./extension.ts";
|
|
19
|
+
export * from "./foreground-detach.ts";
|
|
19
20
|
export * from "./mailbox.ts";
|
|
20
21
|
export * from "./model-routing.ts";
|
|
21
22
|
export * from "./monitor-window-state.ts";
|
|
@@ -1502,75 +1502,247 @@ export function normalizeTeammateParams(
|
|
|
1502
1502
|
}
|
|
1503
1503
|
|
|
1504
1504
|
// ---------------------------------------------------------------------------
|
|
1505
|
-
// AC3:
|
|
1505
|
+
// AC3: shell-free, provenance-bearing Pi launcher resolution
|
|
1506
1506
|
// ---------------------------------------------------------------------------
|
|
1507
1507
|
|
|
1508
|
-
export
|
|
1508
|
+
export type PiLaunchSource =
|
|
1509
|
+
| "override"
|
|
1510
|
+
| "path-native"
|
|
1511
|
+
| "windows-shim"
|
|
1512
|
+
| "host-entry"
|
|
1513
|
+
| "path-fallback";
|
|
1509
1514
|
|
|
1510
|
-
export
|
|
1511
|
-
|
|
1515
|
+
export interface PiLaunchSpec {
|
|
1516
|
+
command: string;
|
|
1517
|
+
argsPrefix: string[];
|
|
1518
|
+
source: PiLaunchSource;
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
export interface PiSpawnCommandOptions {
|
|
1522
|
+
envBinary?: string | null;
|
|
1523
|
+
entryPoint?: string | null;
|
|
1524
|
+
platform?: NodeJS.Platform;
|
|
1525
|
+
pathValue?: string | null;
|
|
1526
|
+
argv?: readonly string[];
|
|
1527
|
+
execPath?: string;
|
|
1528
|
+
appData?: string | null;
|
|
1529
|
+
/** @internal deterministic filesystem seam for focused launcher tests. */
|
|
1530
|
+
isFile?: (candidate: string) => boolean;
|
|
1531
|
+
/** @internal deterministic package-manifest seam for focused launcher tests. */
|
|
1532
|
+
readTextFile?: (candidate: string) => string;
|
|
1533
|
+
}
|
|
1512
1534
|
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
return
|
|
1535
|
+
function regularFileExists(candidate: string): boolean {
|
|
1536
|
+
try {
|
|
1537
|
+
return fs.statSync(candidate).isFile();
|
|
1538
|
+
} catch {
|
|
1539
|
+
return false;
|
|
1518
1540
|
}
|
|
1541
|
+
}
|
|
1519
1542
|
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
if (match) {
|
|
1532
|
-
const entryPoint = path.join(npmDir, match[1]);
|
|
1533
|
-
if (fs.existsSync(entryPoint)) {
|
|
1534
|
-
resolvedPiEntryPoint = entryPoint;
|
|
1535
|
-
return resolvedPiEntryPoint;
|
|
1536
|
-
}
|
|
1537
|
-
}
|
|
1538
|
-
} catch { /* fallback */ }
|
|
1543
|
+
function pathPiCandidate(
|
|
1544
|
+
platform: NodeJS.Platform,
|
|
1545
|
+
pathValue: string | null | undefined,
|
|
1546
|
+
isFile: (candidate: string) => boolean,
|
|
1547
|
+
): Pick<PiLaunchSpec, "command" | "source"> | undefined {
|
|
1548
|
+
if (!pathValue) return undefined;
|
|
1549
|
+
const directories = pathValue.split(platform === "win32" ? ";" : ":").filter(Boolean);
|
|
1550
|
+
if (platform !== "win32") {
|
|
1551
|
+
for (const directory of directories) {
|
|
1552
|
+
const candidate = path.join(directory, "pi");
|
|
1553
|
+
if (isFile(candidate)) return { command: candidate, source: "path-native" };
|
|
1539
1554
|
}
|
|
1555
|
+
return undefined;
|
|
1540
1556
|
}
|
|
1541
1557
|
|
|
1542
|
-
|
|
1543
|
-
|
|
1558
|
+
// Prefer a native executable anywhere on PATH before considering cmd/bat
|
|
1559
|
+
// wrappers. Ignore npm's extensionless POSIX shim: cross-spawn follows its
|
|
1560
|
+
// shebang through sh.exe, so the Pi Node process does not own the IPC channel.
|
|
1561
|
+
for (const extension of [".exe", ".com"] as const) {
|
|
1562
|
+
for (const directory of directories) {
|
|
1563
|
+
const candidate = path.join(directory, `pi${extension}`);
|
|
1564
|
+
if (isFile(candidate)) return { command: candidate, source: "path-native" };
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
for (const extension of [".cmd", ".bat"] as const) {
|
|
1568
|
+
for (const directory of directories) {
|
|
1569
|
+
const candidate = path.join(directory, `pi${extension}`);
|
|
1570
|
+
if (isFile(candidate)) return { command: candidate, source: "windows-shim" };
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
return undefined;
|
|
1544
1574
|
}
|
|
1545
1575
|
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1576
|
+
const WINDOWS_PI_SHIM_ENTRY_PATTERN = /"?%dp0%[\\/]([^"*%\r\n]+?\.(?:js|mjs|cjs))"?/i;
|
|
1577
|
+
|
|
1578
|
+
function resolvedWindowsPiShim(
|
|
1579
|
+
shim: string,
|
|
1580
|
+
isFile: (candidate: string) => boolean,
|
|
1581
|
+
readTextFile: (candidate: string) => string,
|
|
1582
|
+
execPath: string,
|
|
1583
|
+
): PiLaunchSpec | undefined {
|
|
1584
|
+
try {
|
|
1585
|
+
const match = WINDOWS_PI_SHIM_ENTRY_PATTERN.exec(readTextFile(shim));
|
|
1586
|
+
if (!match) return undefined;
|
|
1587
|
+
const entry = path.resolve(path.dirname(shim), match[1]!.replace(/[\\/]/g, path.sep));
|
|
1588
|
+
const verified = verifiedHostPiEntry(entry, isFile, readTextFile);
|
|
1589
|
+
return verified
|
|
1590
|
+
? { command: execPath, argsPrefix: [verified], source: "windows-shim" }
|
|
1591
|
+
: undefined;
|
|
1592
|
+
} catch {
|
|
1593
|
+
return undefined;
|
|
1594
|
+
}
|
|
1550
1595
|
}
|
|
1551
1596
|
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1597
|
+
function verifiedHostPiEntry(
|
|
1598
|
+
entryPoint: string | null | undefined,
|
|
1599
|
+
isFile: (candidate: string) => boolean,
|
|
1600
|
+
readTextFile: (candidate: string) => string,
|
|
1601
|
+
): string | undefined {
|
|
1602
|
+
if (!entryPoint || !/\.(?:js|mjs|cjs)$/i.test(entryPoint) || !isFile(entryPoint)) return undefined;
|
|
1603
|
+
const absoluteEntry = path.resolve(entryPoint);
|
|
1604
|
+
let directory = path.dirname(absoluteEntry);
|
|
1605
|
+
for (let depth = 0; depth < 10; depth += 1) {
|
|
1606
|
+
const manifestPath = path.join(directory, "package.json");
|
|
1607
|
+
if (isFile(manifestPath)) {
|
|
1608
|
+
try {
|
|
1609
|
+
const manifest = JSON.parse(readTextFile(manifestPath)) as {
|
|
1610
|
+
name?: unknown;
|
|
1611
|
+
bin?: unknown;
|
|
1612
|
+
};
|
|
1613
|
+
const acceptedPackage = manifest.name === "@earendil-works/pi-coding-agent"
|
|
1614
|
+
|| manifest.name === "@mariozechner/pi-coding-agent";
|
|
1615
|
+
const piBin = typeof manifest.bin === "object" && manifest.bin !== null
|
|
1616
|
+
? (manifest.bin as Record<string, unknown>).pi
|
|
1617
|
+
: undefined;
|
|
1618
|
+
if (acceptedPackage && typeof piBin === "string"
|
|
1619
|
+
&& path.resolve(directory, piBin) === absoluteEntry) return absoluteEntry;
|
|
1620
|
+
} catch {
|
|
1621
|
+
return undefined;
|
|
1622
|
+
}
|
|
1623
|
+
return undefined;
|
|
1624
|
+
}
|
|
1625
|
+
const parent = path.dirname(directory);
|
|
1626
|
+
if (parent === directory) break;
|
|
1627
|
+
directory = parent;
|
|
1628
|
+
}
|
|
1629
|
+
return undefined;
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
export let resolvedPiEntryPoint: string | null | undefined;
|
|
1633
|
+
|
|
1634
|
+
/**
|
|
1635
|
+
* Resolve process.argv[1] only when its owning package manifest proves it is
|
|
1636
|
+
* Pi's declared CLI. A generic .js/.mjs suffix is never evidence of identity.
|
|
1637
|
+
*/
|
|
1638
|
+
export function resolvePiEntryPoint(): string | null {
|
|
1639
|
+
if (resolvedPiEntryPoint !== undefined) return resolvedPiEntryPoint;
|
|
1640
|
+
resolvedPiEntryPoint = verifiedHostPiEntry(
|
|
1641
|
+
process.argv[1],
|
|
1642
|
+
regularFileExists,
|
|
1643
|
+
(candidate) => fs.readFileSync(candidate, "utf-8"),
|
|
1644
|
+
) ?? null;
|
|
1645
|
+
return resolvedPiEntryPoint;
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
export function resolvePiLaunchSpec(options: PiSpawnCommandOptions = {}): PiLaunchSpec {
|
|
1556
1649
|
const envBinary = options.envBinary === undefined
|
|
1557
1650
|
? process.env.PI_TEAMMATE_PI_BINARY
|
|
1558
1651
|
: options.envBinary;
|
|
1559
|
-
if (envBinary) {
|
|
1560
|
-
return { command: envBinary,
|
|
1652
|
+
if (envBinary?.trim()) {
|
|
1653
|
+
return { command: envBinary, argsPrefix: [], source: "override" };
|
|
1561
1654
|
}
|
|
1562
1655
|
|
|
1563
|
-
const
|
|
1656
|
+
const platform = options.platform ?? process.platform;
|
|
1657
|
+
const isFile = options.isFile ?? regularFileExists;
|
|
1658
|
+
const readTextFile = options.readTextFile ?? ((candidate: string) => fs.readFileSync(candidate, "utf-8"));
|
|
1659
|
+
const execPath = options.execPath ?? process.execPath;
|
|
1660
|
+
const pathCandidate = pathPiCandidate(
|
|
1661
|
+
platform,
|
|
1662
|
+
options.pathValue === undefined ? process.env.PATH : options.pathValue,
|
|
1663
|
+
isFile,
|
|
1664
|
+
);
|
|
1665
|
+
if (pathCandidate?.source === "path-native") return { ...pathCandidate, argsPrefix: [] };
|
|
1666
|
+
if (pathCandidate?.source === "windows-shim") {
|
|
1667
|
+
const resolved = resolvedWindowsPiShim(pathCandidate.command, isFile, readTextFile, execPath);
|
|
1668
|
+
if (resolved) return resolved;
|
|
1669
|
+
}
|
|
1670
|
+
|
|
1671
|
+
// APPDATA/npm is not guaranteed to be present in PATH for service/gateway
|
|
1672
|
+
// processes, so retain an explicit, verified Windows shim fallback.
|
|
1673
|
+
if (platform === "win32") {
|
|
1674
|
+
const appData = options.appData === undefined ? process.env.APPDATA : options.appData;
|
|
1675
|
+
if (appData) {
|
|
1676
|
+
const shim = path.join(appData, "npm", "pi.cmd");
|
|
1677
|
+
if (isFile(shim)) {
|
|
1678
|
+
const resolved = resolvedWindowsPiShim(shim, isFile, readTextFile, execPath);
|
|
1679
|
+
if (resolved) return resolved;
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
const injectedEntry = options.entryPoint !== undefined
|
|
1684
|
+
? verifiedHostPiEntry(options.entryPoint, isFile, readTextFile)
|
|
1685
|
+
: options.argv !== undefined
|
|
1686
|
+
? verifiedHostPiEntry(options.argv[1], isFile, readTextFile)
|
|
1687
|
+
: undefined;
|
|
1688
|
+
const hostEntry = options.entryPoint === undefined && options.argv === undefined
|
|
1564
1689
|
? resolvePiEntryPoint()
|
|
1565
|
-
:
|
|
1566
|
-
if (
|
|
1567
|
-
return {
|
|
1690
|
+
: injectedEntry;
|
|
1691
|
+
if (hostEntry) {
|
|
1692
|
+
return {
|
|
1693
|
+
command: options.execPath ?? process.execPath,
|
|
1694
|
+
argsPrefix: [hostEntry],
|
|
1695
|
+
source: "host-entry",
|
|
1696
|
+
};
|
|
1568
1697
|
}
|
|
1569
1698
|
|
|
1570
|
-
//
|
|
1571
|
-
//
|
|
1572
|
-
|
|
1573
|
-
|
|
1699
|
+
// Preserve the historical ENOENT failure mode when no resolver candidate is
|
|
1700
|
+
// installed, but make that unverified fallback explicit in diagnostics.
|
|
1701
|
+
return { command: "pi", argsPrefix: [], source: "path-fallback" };
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
export function getPiSpawnCommand(
|
|
1705
|
+
args: string[],
|
|
1706
|
+
options: PiSpawnCommandOptions = {},
|
|
1707
|
+
): PiLaunchSpec & { args: string[]; shell: false } {
|
|
1708
|
+
const launch = resolvePiLaunchSpec(options);
|
|
1709
|
+
return {
|
|
1710
|
+
...launch,
|
|
1711
|
+
args: [...launch.argsPrefix, ...args],
|
|
1712
|
+
shell: false,
|
|
1713
|
+
};
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
export interface PiLaunchDiagnostic {
|
|
1717
|
+
type: "teammate_pi_launch_diagnostic";
|
|
1718
|
+
source: PiLaunchSource;
|
|
1719
|
+
phase: "spawn" | "child-error" | "close";
|
|
1720
|
+
exitCode: number | null;
|
|
1721
|
+
signal: NodeJS.Signals | null;
|
|
1722
|
+
stderrTail: string;
|
|
1723
|
+
}
|
|
1724
|
+
|
|
1725
|
+
export function piLaunchDiagnostic(
|
|
1726
|
+
launch: Pick<PiLaunchSpec, "source">,
|
|
1727
|
+
phase: PiLaunchDiagnostic["phase"],
|
|
1728
|
+
exitCode: number | null,
|
|
1729
|
+
signal: NodeJS.Signals | null,
|
|
1730
|
+
stderr: string,
|
|
1731
|
+
): PiLaunchDiagnostic {
|
|
1732
|
+
return {
|
|
1733
|
+
type: "teammate_pi_launch_diagnostic",
|
|
1734
|
+
source: launch.source,
|
|
1735
|
+
phase,
|
|
1736
|
+
exitCode,
|
|
1737
|
+
signal,
|
|
1738
|
+
stderrTail: truncateUtf8Tail(stderr.trim(), EXECUTION_BUFFER_LIMITS.stderrBytes),
|
|
1739
|
+
};
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
export function formatPiLaunchDiagnostic(diagnostic: PiLaunchDiagnostic): string {
|
|
1743
|
+
return `source=${diagnostic.source}, phase=${diagnostic.phase}, exit=${diagnostic.exitCode ?? "null"}, `
|
|
1744
|
+
+ `signal=${diagnostic.signal ?? "none"}`
|
|
1745
|
+
+ (diagnostic.stderrTail ? `\nstderr tail:\n${diagnostic.stderrTail}` : "");
|
|
1574
1746
|
}
|
|
1575
1747
|
|
|
1576
1748
|
export interface InteractiveTerminalLaunchOptions {
|
|
@@ -94,10 +94,12 @@ import {
|
|
|
94
94
|
extractPiEventError,
|
|
95
95
|
extractStructuredOutputCandidate,
|
|
96
96
|
extractTextContent,
|
|
97
|
+
formatPiLaunchDiagnostic,
|
|
97
98
|
getPiSpawnCommand,
|
|
98
99
|
getTeammateDepth,
|
|
99
100
|
getTeammateSessionRoot,
|
|
100
101
|
isPiResultReadyTurn,
|
|
102
|
+
piLaunchDiagnostic,
|
|
101
103
|
readRegularTextFile,
|
|
102
104
|
releasePublishedTurnHistory,
|
|
103
105
|
resetUsage,
|
|
@@ -876,8 +878,8 @@ export async function runSingleAttempt(
|
|
|
876
878
|
);
|
|
877
879
|
|
|
878
880
|
let useIpc = false;
|
|
881
|
+
const spawnSpec = getPiSpawnCommand(piArgs);
|
|
879
882
|
try {
|
|
880
|
-
const spawnSpec = getPiSpawnCommand(piArgs);
|
|
881
883
|
useIpc = !spawnSpec.shell;
|
|
882
884
|
const spawnOpts: Parameters<typeof crossSpawn>[2] = {
|
|
883
885
|
cwd,
|
|
@@ -892,6 +894,8 @@ export async function runSingleAttempt(
|
|
|
892
894
|
if (schemaFile) cleanupFile(schemaFile);
|
|
893
895
|
if (outputFile) cleanupFile(outputFile);
|
|
894
896
|
|
|
897
|
+
const spawnDiagnostic = piLaunchDiagnostic(spawnSpec, "spawn", null, null, "");
|
|
898
|
+
options.onChildEvent?.({ ...spawnDiagnostic, correlationId });
|
|
895
899
|
const result: SingleResult = {
|
|
896
900
|
agent: params.agent,
|
|
897
901
|
name: params.name,
|
|
@@ -901,7 +905,8 @@ export async function runSingleAttempt(
|
|
|
901
905
|
role: "system",
|
|
902
906
|
content:
|
|
903
907
|
`Failed to spawn pi subprocess (agent=${params.agent}, model=${state.resolvedModel || "unknown"}, `
|
|
904
|
-
+ `correlationId=${correlationId},
|
|
908
|
+
+ `correlationId=${correlationId}, ${formatPiLaunchDiagnostic(spawnDiagnostic)}): `
|
|
909
|
+
+ `${error instanceof Error ? error.message : String(error)}`,
|
|
905
910
|
}],
|
|
906
911
|
usage: emptyUsage(),
|
|
907
912
|
model: state.resolvedModel,
|
|
@@ -2869,24 +2874,31 @@ export async function runSingleAttempt(
|
|
|
2869
2874
|
if (finalContent && !messages.some((message) => message.content === finalContent)) {
|
|
2870
2875
|
appendDistinctAssistantMessage(messages, finalContent);
|
|
2871
2876
|
}
|
|
2872
|
-
let stderrAlreadyReported = false;
|
|
2873
2877
|
if (messages.length === 0) {
|
|
2874
2878
|
const content = state.lastContent.trim() || stderrTail || "(no output)";
|
|
2875
|
-
stderrAlreadyReported = stderrTail.length > 0 && content === stderrTail;
|
|
2876
2879
|
appendBoundedTranscriptMessage(messages, { role: "assistant", content });
|
|
2877
2880
|
}
|
|
2878
2881
|
|
|
2879
|
-
//
|
|
2880
|
-
//
|
|
2882
|
+
// Gateway results and Monitor observers consume the same bounded,
|
|
2883
|
+
// provenance-bearing diagnostic projection.
|
|
2881
2884
|
if ((code ?? 1) !== 0) {
|
|
2882
|
-
const
|
|
2885
|
+
const closeDiagnostic = piLaunchDiagnostic(
|
|
2886
|
+
spawnSpec,
|
|
2887
|
+
"close",
|
|
2888
|
+
code,
|
|
2889
|
+
signal,
|
|
2890
|
+
stderrTail,
|
|
2891
|
+
);
|
|
2892
|
+
options.onChildEvent?.({ ...closeDiagnostic, correlationId });
|
|
2893
|
+
const transcriptDiagnostic = stderrTail && messages.some((message) => message.content === stderrTail)
|
|
2894
|
+
? { ...closeDiagnostic, stderrTail: "" }
|
|
2895
|
+
: closeDiagnostic;
|
|
2883
2896
|
appendBoundedTranscriptMessage(messages, {
|
|
2884
2897
|
role: "system",
|
|
2885
2898
|
content:
|
|
2886
2899
|
`Teammate child process exited abnormally (agent=${params.agent}, `
|
|
2887
|
-
+ `correlationId=${correlationId},
|
|
2888
|
-
+ `elapsed=${Date.now() - startTime}ms, tools=${progress.toolCount})
|
|
2889
|
-
+ (detail ? `\nstderr tail:\n${truncateUtf8Tail(detail, EXECUTION_BUFFER_LIMITS.stderrBytes)}` : ""),
|
|
2900
|
+
+ `correlationId=${correlationId}, ${formatPiLaunchDiagnostic(transcriptDiagnostic)}, `
|
|
2901
|
+
+ `elapsed=${Date.now() - startTime}ms, tools=${progress.toolCount}).`,
|
|
2890
2902
|
});
|
|
2891
2903
|
}
|
|
2892
2904
|
|
|
@@ -2989,9 +3001,17 @@ export async function runSingleAttempt(
|
|
|
2989
3001
|
progress.durationMs = Date.now() - startTime;
|
|
2990
3002
|
options.onProgress?.(progress);
|
|
2991
3003
|
|
|
3004
|
+
const childErrorDiagnostic = piLaunchDiagnostic(
|
|
3005
|
+
spawnSpec,
|
|
3006
|
+
"child-error",
|
|
3007
|
+
null,
|
|
3008
|
+
child.signalCode,
|
|
3009
|
+
state.stderrBuffer,
|
|
3010
|
+
);
|
|
3011
|
+
options.onChildEvent?.({ ...childErrorDiagnostic, correlationId });
|
|
2992
3012
|
const processError =
|
|
2993
3013
|
`Teammate child process error (agent=${params.agent}, model=${state.resolvedModel || "unknown"}, `
|
|
2994
|
-
+ `correlationId=${correlationId},
|
|
3014
|
+
+ `correlationId=${correlationId}, ${formatPiLaunchDiagnostic(childErrorDiagnostic)}): ${error.message}`;
|
|
2995
3015
|
if (state.initialResultPublished) {
|
|
2996
3016
|
appendBoundedTranscriptMessage(messages, {
|
|
2997
3017
|
role: "system",
|
package/src/shared/routing.ts
CHANGED
|
@@ -96,3 +96,13 @@ export function formatLocalAgentMessage(input: LocalAgentMessageInput): string {
|
|
|
96
96
|
input.message,
|
|
97
97
|
].join("\n");
|
|
98
98
|
}
|
|
99
|
+
|
|
100
|
+
/** Explain why a settled local agent cannot accept another message and how to continue. */
|
|
101
|
+
export function formatNoRestorableRuntimeError(label: string, status?: string): string {
|
|
102
|
+
const state = status === "completed"
|
|
103
|
+
? "has completed"
|
|
104
|
+
: status
|
|
105
|
+
? `is ${status}`
|
|
106
|
+
: "is unavailable";
|
|
107
|
+
return `Agent "${label}" ${state} and has no restorable runtime. Dispatch a new teammate and pass the previous agent:// publication via tasks[].briefing.`;
|
|
108
|
+
}
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
* TUI: Alt+R mode-aware session list, widget above editor, Alt+B foreground→background detach
|
|
6
6
|
* Mode: RPC subprocess — stdin open for steer/follow_up/abort
|
|
7
7
|
*/
|
|
8
|
-
import
|
|
8
|
+
import { registerForegroundDetach, setPersistentUi } from "../public/v1/foreground-detach.ts";
|
|
9
|
+
import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
9
10
|
import type { WorkspaceSessionScan } from "../transcript/session-transcript.ts";
|
|
10
11
|
import type { RecentToolInfo } from "../shared/types.ts";
|
|
11
12
|
import { type WorkspaceBackgroundJobSnapshot, type WorkspaceOwnerState } from "./workspace-peers.ts";
|
|
@@ -122,9 +123,7 @@ export declare function backgroundWaitGuidance(correlationId: string): string;
|
|
|
122
123
|
* discoverable across the root single, root graph, and nested foreground paths.
|
|
123
124
|
*/
|
|
124
125
|
export declare const FOREGROUND_DETACH_HINT: string;
|
|
125
|
-
export
|
|
126
|
-
/** Registers one foreground owner; unregister is idempotent on every race path. */
|
|
127
|
-
export declare function registerForegroundDetach(detach: () => void, ui?: ExtensionUIContext): () => void;
|
|
126
|
+
export { registerForegroundDetach, setPersistentUi };
|
|
128
127
|
export declare function foregroundWaitWindowMs(tasks: ReadonlyArray<{
|
|
129
128
|
timeoutMs?: number;
|
|
130
129
|
}>, fallbackMs?: number): number;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ExtensionUIContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
export declare function setPersistentUi(ui: ExtensionUIContext | undefined, resetOwners?: boolean): void;
|
|
3
|
+
/** Registers one foreground owner; unregister is idempotent on every race path. */
|
|
4
|
+
export declare function registerForegroundDetach(detach: () => void, ui?: ExtensionUIContext): () => void;
|
|
@@ -16,6 +16,7 @@ export * from "./completion-durability.ts";
|
|
|
16
16
|
export * from "./events.ts";
|
|
17
17
|
export * from "./execution.ts";
|
|
18
18
|
export * from "./extension.ts";
|
|
19
|
+
export * from "./foreground-detach.ts";
|
|
19
20
|
export * from "./mailbox.ts";
|
|
20
21
|
export * from "./model-routing.ts";
|
|
21
22
|
export * from "./monitor-window-state.ts";
|
|
@@ -571,18 +571,46 @@ export declare function buildExpertLeaderPrompt(objective: string): string;
|
|
|
571
571
|
export declare function prepareTeammateMode(params: RunTeammateParams): RunTeammateParams;
|
|
572
572
|
/** Normalize the tasks-only public contract into executable graph tasks. */
|
|
573
573
|
export declare function normalizeTeammateParams(params: RunTeammateParams): NormalizeTeammateResult;
|
|
574
|
-
export
|
|
575
|
-
export
|
|
574
|
+
export type PiLaunchSource = "override" | "path-native" | "windows-shim" | "host-entry" | "path-fallback";
|
|
575
|
+
export interface PiLaunchSpec {
|
|
576
|
+
command: string;
|
|
577
|
+
argsPrefix: string[];
|
|
578
|
+
source: PiLaunchSource;
|
|
579
|
+
}
|
|
576
580
|
export interface PiSpawnCommandOptions {
|
|
577
581
|
envBinary?: string | null;
|
|
578
582
|
entryPoint?: string | null;
|
|
579
583
|
platform?: NodeJS.Platform;
|
|
584
|
+
pathValue?: string | null;
|
|
585
|
+
argv?: readonly string[];
|
|
586
|
+
execPath?: string;
|
|
587
|
+
appData?: string | null;
|
|
588
|
+
/** @internal deterministic filesystem seam for focused launcher tests. */
|
|
589
|
+
isFile?: (candidate: string) => boolean;
|
|
590
|
+
/** @internal deterministic package-manifest seam for focused launcher tests. */
|
|
591
|
+
readTextFile?: (candidate: string) => string;
|
|
580
592
|
}
|
|
581
|
-
export declare
|
|
582
|
-
|
|
593
|
+
export declare let resolvedPiEntryPoint: string | null | undefined;
|
|
594
|
+
/**
|
|
595
|
+
* Resolve process.argv[1] only when its owning package manifest proves it is
|
|
596
|
+
* Pi's declared CLI. A generic .js/.mjs suffix is never evidence of identity.
|
|
597
|
+
*/
|
|
598
|
+
export declare function resolvePiEntryPoint(): string | null;
|
|
599
|
+
export declare function resolvePiLaunchSpec(options?: PiSpawnCommandOptions): PiLaunchSpec;
|
|
600
|
+
export declare function getPiSpawnCommand(args: string[], options?: PiSpawnCommandOptions): PiLaunchSpec & {
|
|
583
601
|
args: string[];
|
|
584
602
|
shell: false;
|
|
585
603
|
};
|
|
604
|
+
export interface PiLaunchDiagnostic {
|
|
605
|
+
type: "teammate_pi_launch_diagnostic";
|
|
606
|
+
source: PiLaunchSource;
|
|
607
|
+
phase: "spawn" | "child-error" | "close";
|
|
608
|
+
exitCode: number | null;
|
|
609
|
+
signal: NodeJS.Signals | null;
|
|
610
|
+
stderrTail: string;
|
|
611
|
+
}
|
|
612
|
+
export declare function piLaunchDiagnostic(launch: Pick<PiLaunchSpec, "source">, phase: PiLaunchDiagnostic["phase"], exitCode: number | null, signal: NodeJS.Signals | null, stderr: string): PiLaunchDiagnostic;
|
|
613
|
+
export declare function formatPiLaunchDiagnostic(diagnostic: PiLaunchDiagnostic): string;
|
|
586
614
|
export interface InteractiveTerminalLaunchOptions {
|
|
587
615
|
platform?: NodeJS.Platform;
|
|
588
616
|
terminalCommand?: string;
|
|
@@ -37,3 +37,5 @@ export interface LocalAgentMessageInput {
|
|
|
37
37
|
}
|
|
38
38
|
/** Canonical model-visible envelope for local agent-to-agent messages. */
|
|
39
39
|
export declare function formatLocalAgentMessage(input: LocalAgentMessageInput): string;
|
|
40
|
+
/** Explain why a settled local agent cannot accept another message and how to continue. */
|
|
41
|
+
export declare function formatNoRestorableRuntimeError(label: string, status?: string): string;
|