@agimon-ai/po-team 0.0.0-alpha.2 → 0.0.0-alpha.21
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/LICENSE +166 -5
- package/README.md +11 -12
- package/dist/{agentDiscovery-Whwzc4dP.cjs → agentDiscovery-DTTlM_LT.cjs} +246 -350
- package/dist/agentDiscovery-DTTlM_LT.cjs.map +1 -0
- package/dist/{agentDiscovery-ZQlFkKvB.mjs → agentDiscovery-ZNG8iZXV.mjs} +210 -279
- package/dist/agentDiscovery-ZNG8iZXV.mjs.map +1 -0
- package/dist/apiContracts.cjs +10 -3
- package/dist/apiContracts.cjs.map +1 -1
- package/dist/apiContracts.d.mts.map +1 -1
- package/dist/apiContracts.mjs +10 -3
- package/dist/apiContracts.mjs.map +1 -1
- package/dist/{externalProcessIpc-ojE0pvw0.cjs → atomicJson-DRT03Ql5.cjs} +155 -21
- package/dist/atomicJson-DRT03Ql5.cjs.map +1 -0
- package/dist/{externalProcessIpc-C-15lJrB.mjs → atomicJson-VVM1iAPu.mjs} +140 -12
- package/dist/atomicJson-VVM1iAPu.mjs.map +1 -0
- package/dist/{capabilityCeiling-CF7kAS18.mjs → capabilityCeiling-BaHT9v9S.mjs} +3 -3
- package/dist/capabilityCeiling-BaHT9v9S.mjs.map +1 -0
- package/dist/{capabilityCeiling-C-Y7H8oC.cjs → capabilityCeiling-CO3ZjYmY.cjs} +3 -3
- package/dist/capabilityCeiling-CO3ZjYmY.cjs.map +1 -0
- package/dist/capabilityCeiling.cjs +2 -2
- package/dist/capabilityCeiling.d.mts +2 -2
- package/dist/capabilityCeiling.mjs +2 -2
- package/dist/delegation.cjs +1 -1
- package/dist/delegation.cjs.map +1 -1
- package/dist/delegation.d.mts +1 -1
- package/dist/delegation.mjs +1 -1
- package/dist/delegation.mjs.map +1 -1
- package/dist/delegationApi.cjs +30 -30
- package/dist/delegationApi.d.mts +2 -2
- package/dist/delegationApi.mjs +1 -2
- package/dist/env.cjs +1 -1
- package/dist/env.d.mts +1 -1
- package/dist/env.mjs +1 -2
- package/dist/{environment-DrqhrxG_.mjs → environment-BtvJI2S-.mjs} +3 -3
- package/dist/environment-BtvJI2S-.mjs.map +1 -0
- package/dist/{environment-ma5oQ7uM.d.mts → environment-Bz1OlWIT.d.mts} +2 -2
- package/dist/{environment-ma5oQ7uM.d.mts.map → environment-Bz1OlWIT.d.mts.map} +1 -1
- package/dist/{environment-CaCW3SgT.cjs → environment-Cqs7vHJL.cjs} +3 -3
- package/dist/environment-Cqs7vHJL.cjs.map +1 -0
- package/dist/extensions/native.mjs +34 -12
- package/dist/extensions/native.mjs.map +1 -1
- package/dist/extensions/server.cjs +990 -786
- package/dist/extensions/server.cjs.map +1 -1
- package/dist/extensions/server.d.mts +2 -2
- package/dist/extensions/server.d.mts.map +1 -1
- package/dist/extensions/server.mjs +975 -772
- package/dist/extensions/server.mjs.map +1 -1
- package/dist/extensions/web.mjs +994 -807
- package/dist/extensions/web.mjs.map +1 -1
- package/dist/fablePlan.cjs +3 -3
- package/dist/fablePlan.d.mts +2 -2
- package/dist/fablePlan.mjs +1 -2
- package/dist/{fablePlanApi-CZ3uUcMi.d.mts → fablePlanApi-BTNKjLni.d.mts} +6 -6
- package/dist/{fablePlanApi-CZ3uUcMi.d.mts.map → fablePlanApi-BTNKjLni.d.mts.map} +1 -1
- package/dist/{fablePlanApi-wBu9bRXU.cjs → fablePlanApi-BhI4jnDP.cjs} +12 -12
- package/dist/fablePlanApi-BhI4jnDP.cjs.map +1 -0
- package/dist/{fablePlanApi-BsHhgVFV.mjs → fablePlanApi-DuMg43Hu.mjs} +5 -5
- package/dist/fablePlanApi-DuMg43Hu.mjs.map +1 -0
- package/dist/fablePlanApi.cjs +3 -3
- package/dist/fablePlanApi.d.mts +2 -2
- package/dist/fablePlanApi.mjs +1 -2
- package/dist/index.cjs +17 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +5 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +15 -5
- package/dist/index.mjs.map +1 -1
- package/dist/rolldown-runtime-VH7oDXx4.cjs +28 -0
- package/dist/runs/background/cliRunnerEntry.cjs +26 -25
- package/dist/runs/background/cliRunnerEntry.cjs.map +1 -1
- package/dist/runs/background/cliRunnerEntry.mjs +14 -14
- package/dist/runs/background/cliRunnerEntry.mjs.map +1 -1
- package/dist/runtimeSubagentPolicy.cjs +3 -3
- package/dist/runtimeSubagentPolicy.d.mts +2 -2
- package/dist/runtimeSubagentPolicy.mjs +1 -2
- package/dist/runtimeSubagentTool.cjs +7 -2
- package/dist/runtimeSubagentTool.d.mts +1 -1
- package/dist/runtimeSubagentTool.mjs +1 -2
- package/dist/{subagentPolicy-ALJam4GN.d.mts → subagentPolicy-BE-OZoa-.d.mts} +6 -6
- package/dist/subagentPolicy-BE-OZoa-.d.mts.map +1 -0
- package/dist/{subagentPolicy-DQRyKDH-.cjs → subagentPolicy-DIpaKHO8.cjs} +8 -8
- package/dist/subagentPolicy-DIpaKHO8.cjs.map +1 -0
- package/dist/{subagentPolicy-DxjsKeyj.mjs → subagentPolicy-LfcXuqO5.mjs} +5 -5
- package/dist/subagentPolicy-LfcXuqO5.mjs.map +1 -0
- package/dist/subagentPolicy.cjs +3 -3
- package/dist/subagentPolicy.d.mts +2 -2
- package/dist/subagentPolicy.mjs +1 -2
- package/dist/{subagentTool-Ca2zQXKw.cjs → subagentTool-BueNPGHo.cjs} +5 -16
- package/dist/subagentTool-BueNPGHo.cjs.map +1 -0
- package/dist/{subagentTool-BTssGLw8.d.mts → subagentTool-UdB3s6Mn.d.mts} +2 -11
- package/dist/subagentTool-UdB3s6Mn.d.mts.map +1 -0
- package/dist/{subagentTool-WOYrjxgT.mjs → subagentTool-a2oh2v5B.mjs} +5 -16
- package/dist/subagentTool-a2oh2v5B.mjs.map +1 -0
- package/dist/subagentTool.cjs +7 -2
- package/dist/subagentTool.d.mts +1 -1
- package/dist/subagentTool.mjs +1 -2
- package/dist/teamSnapshot.d.mts +1 -0
- package/dist/teamSnapshot.d.mts.map +1 -1
- package/dist/teamSnapshot.mjs +1 -2
- package/package.json +25 -24
- package/dist/agentDiscovery-Whwzc4dP.cjs.map +0 -1
- package/dist/agentDiscovery-ZQlFkKvB.mjs.map +0 -1
- package/dist/atomicJson-D0346bt5.cjs +0 -178
- package/dist/atomicJson-D0346bt5.cjs.map +0 -1
- package/dist/atomicJson-D2t0CDh9.mjs +0 -137
- package/dist/atomicJson-D2t0CDh9.mjs.map +0 -1
- package/dist/capabilityCeiling-C-Y7H8oC.cjs.map +0 -1
- package/dist/capabilityCeiling-CF7kAS18.mjs.map +0 -1
- package/dist/environment-CaCW3SgT.cjs.map +0 -1
- package/dist/environment-DrqhrxG_.mjs.map +0 -1
- package/dist/externalProcessIpc-C-15lJrB.mjs.map +0 -1
- package/dist/externalProcessIpc-ojE0pvw0.cjs.map +0 -1
- package/dist/fablePlanApi-BsHhgVFV.mjs.map +0 -1
- package/dist/fablePlanApi-wBu9bRXU.cjs.map +0 -1
- package/dist/subagentPolicy-ALJam4GN.d.mts.map +0 -1
- package/dist/subagentPolicy-DQRyKDH-.cjs.map +0 -1
- package/dist/subagentPolicy-DxjsKeyj.mjs.map +0 -1
- package/dist/subagentTool-BTssGLw8.d.mts.map +0 -1
- package/dist/subagentTool-Ca2zQXKw.cjs.map +0 -1
- package/dist/subagentTool-WOYrjxgT.mjs.map +0 -1
|
@@ -1,34 +1,36 @@
|
|
|
1
|
-
import { I as SUBAGENT_RUN_ID_ENV, O as SUBAGENT_PARENT_DEPTH_ENV, W as runtimeBinaryEnvVar } from "../environment-
|
|
2
|
-
import { n as SubagentCapabilityPolicyStore } from "../capabilityCeiling-
|
|
3
|
-
import { a as
|
|
4
|
-
import { a as
|
|
5
|
-
import { t as
|
|
6
|
-
import { n as
|
|
7
|
-
import { a as SubagentToolSchema, n as SUBAGENT_ACTIONS, r as SUBAGENT_ACTION_FIELDS, s as normalizeSubagentToolParams } from "../subagentTool-WOYrjxgT.mjs";
|
|
1
|
+
import { I as SUBAGENT_RUN_ID_ENV, O as SUBAGENT_PARENT_DEPTH_ENV, W as runtimeBinaryEnvVar } from "../environment-BtvJI2S-.mjs";
|
|
2
|
+
import { n as SubagentCapabilityPolicyStore } from "../capabilityCeiling-BaHT9v9S.mjs";
|
|
3
|
+
import { a as FablePlanCancelSchema, d as FablePlanStartSchema, i as FABLE_PLAN_RUNTIME, m as PO_FABLE_PLAN_SERVICE, n as FABLE_PLAN_PROFILE, t as FABLE_PLAN_MODEL } from "../fablePlanApi-DuMg43Hu.mjs";
|
|
4
|
+
import { a as getAgentDir, i as canonicalizeDiscoveryCwd, n as LruCache, o as getConfigDirName, r as agentHasWriteTools, s as getProjectConfigDir, t as AgentDiscoveryService } from "../agentDiscovery-ZNG8iZXV.mjs";
|
|
5
|
+
import { n as SubagentPolicySchema, t as PO_SUBAGENT_POLICY_SERVICE } from "../subagentPolicy-LfcXuqO5.mjs";
|
|
6
|
+
import { a as SubagentToolSchema, n as SUBAGENT_ACTIONS, r as SUBAGENT_ACTION_FIELDS, s as normalizeSubagentToolParams } from "../subagentTool-a2oh2v5B.mjs";
|
|
8
7
|
import { n as readActiveTeamSnapshot, t as formatTeamContextSnapshot } from "../teamSnapshot-DgaANhBs.mjs";
|
|
9
|
-
import { _ as
|
|
8
|
+
import { _ as sessionScopeDir, a as childProcessEndpoint, f as prepareClaudeFableLaunch, g as scopeRunsDir, h as scopeResultsDir, i as ExternalProcessIpc, l as CLAUDE_FABLE_PROFILE, m as createSessionScope, s as parseExternalRunnerMessage, t as writeAtomicJson, u as cleanupClaudeFableLaunch, v as sessionScopeEnvironment } from "../atomicJson-VVM1iAPu.mjs";
|
|
10
9
|
import { Buffer as Buffer$1 } from "node:buffer";
|
|
11
10
|
import { resolveRootSessionId } from "@agimon-ai/po-core/childProcess";
|
|
12
|
-
import {
|
|
11
|
+
import { DelegationCancelSchema, DelegationRequestSchema, PO_DELEGATION_ACCEPTED_EVENT, PO_DELEGATION_CANCELLED_EVENT, PO_DELEGATION_FINISHED_EVENT, PO_DELEGATION_REQUESTED_EVENT, PO_DELEGATION_SERVICE, PO_DELEGATION_STARTED_EVENT, PO_DELEGATION_UPDATED_EVENT } from "@agimon-ai/po-core/delegation";
|
|
13
12
|
import { Type } from "typebox";
|
|
14
|
-
import * as fs from "node:fs";
|
|
15
13
|
import * as path from "node:path";
|
|
14
|
+
import { BACKGROUND_CONTEXT, nodeHost, requirePoFs } from "@agimon-ai/po-core/platform";
|
|
15
|
+
import * as fs from "node:fs";
|
|
16
16
|
import * as os from "node:os";
|
|
17
|
-
import { composeServerProviderOverrides, defineServerPlugin } from "@agimon-ai/po-core/
|
|
18
|
-
import { composeRootHooks, defineChannel, defineResource, defineRoot, defineRoute, defineServerTool } from "@agimon-ai/po-core/extensionFile";
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import { provideBackgroundWorkService,
|
|
25
|
-
import {
|
|
17
|
+
import { composeServerProviderOverrides, defineServerPlugin } from "@agimon-ai/po-core/serverPlugin";
|
|
18
|
+
import { composeRootHooks, composeRootInjects, defineChannel, defineHook, defineResource, defineRoot, defineRoute, defineServerTool, runCleanups } from "@agimon-ai/po-core/extensionFile";
|
|
19
|
+
import { PO_CONFIG_SERVICE } from "@agimon-ai/po-config";
|
|
20
|
+
import { readPoBackgroundWorkService } from "@agimon-ai/po-core/backgroundWork";
|
|
21
|
+
import { PO_CHILD_SESSION_LABELS_SERVICE, readPoChildSessionService } from "@agimon-ai/po-core/childSession";
|
|
22
|
+
import { PO_CONTEXT_CONTRIBUTIONS_SERVICE, requirePoContextContributions } from "@agimon-ai/po-core/contextContributions";
|
|
23
|
+
import { PO_MCP_TOOL_RESOLVER_SERVICE, requirePoMcpToolResolver } from "@agimon-ai/po-core/mcpToolResolver";
|
|
24
|
+
import { provideBackgroundWorkService, readPoSessionDelivery } from "@agimon-ai/po-session";
|
|
25
|
+
import { createPoTelemetry } from "@agimon-ai/po-telemetry";
|
|
26
26
|
import { Check } from "typebox/value";
|
|
27
27
|
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
28
28
|
import { spawn } from "node:child_process";
|
|
29
29
|
import { fileURLToPath } from "node:url";
|
|
30
|
-
import {
|
|
30
|
+
import { toPoHeadlessToolResult } from "@agimon-ai/po-core/headless";
|
|
31
|
+
import { readMajorModesConfig, readPackageConfigurations } from "@agimon-ai/po-config/majorModes";
|
|
31
32
|
import "@agimon-ai/po-core/cordisHost";
|
|
33
|
+
import { HOME_ENTITY_LIMIT } from "@agimon-ai/po-core/hubChannel";
|
|
32
34
|
//#region src/services/config/index.ts
|
|
33
35
|
/**
|
|
34
36
|
* `subagent` tool config file: read/write, and the scoped `ExtensionConfig`
|
|
@@ -170,13 +172,13 @@ var AdmissionGate = class {
|
|
|
170
172
|
}
|
|
171
173
|
if (!waited) {
|
|
172
174
|
waited = true;
|
|
173
|
-
request.report?.("
|
|
175
|
+
request.report?.("po_team.admission_wait", {
|
|
174
176
|
"team.live_runs": live,
|
|
175
177
|
"team.max_live_runs": request.maxLiveRuns
|
|
176
178
|
});
|
|
177
179
|
}
|
|
178
180
|
if (this.now() >= deadline) {
|
|
179
|
-
request.report?.("
|
|
181
|
+
request.report?.("po_team.admission_timeout", {
|
|
180
182
|
"team.live_runs": live,
|
|
181
183
|
"team.max_live_runs": request.maxLiveRuns,
|
|
182
184
|
duration_ms: this.now() - waitStartedAt
|
|
@@ -508,26 +510,6 @@ function claimAgentIdentity(scope, input) {
|
|
|
508
510
|
}
|
|
509
511
|
return degraded;
|
|
510
512
|
}
|
|
511
|
-
/**
|
|
512
|
-
* Point an existing identity at a new run id.
|
|
513
|
-
*
|
|
514
|
-
* Restore mints a fresh run id for what is conceptually the same agent, so the
|
|
515
|
-
* identity has to be carried across rather than derived again. This is a
|
|
516
|
-
* single-writer rewrite of one claim, so the lost-update hazard that rules out
|
|
517
|
-
* a shared registry file does not apply.
|
|
518
|
-
*/
|
|
519
|
-
function adoptAgentIdentity(scope, identity, runId) {
|
|
520
|
-
const file = path.join(agentIdentitiesDir(scope), `${identity}.json`);
|
|
521
|
-
const existing = readClaim(file);
|
|
522
|
-
if (!existing) return;
|
|
523
|
-
try {
|
|
524
|
-
fs.writeFileSync(file, `${JSON.stringify({
|
|
525
|
-
...existing,
|
|
526
|
-
runId,
|
|
527
|
-
claimedAt: Date.now()
|
|
528
|
-
})}\n`, { mode: 384 });
|
|
529
|
-
} catch {}
|
|
530
|
-
}
|
|
531
513
|
/** Render an identity for display, marking the inline case that has no agent file on disk. */
|
|
532
514
|
function formatAgentIdentity(identity, inline) {
|
|
533
515
|
if (!identity) return void 0;
|
|
@@ -1063,7 +1045,7 @@ function preflightSubagentDepth(depth, config) {
|
|
|
1063
1045
|
}
|
|
1064
1046
|
//#endregion
|
|
1065
1047
|
//#region src/services/errors/index.ts
|
|
1066
|
-
var
|
|
1048
|
+
var PoTeamExpectedError = class extends Error {
|
|
1067
1049
|
code;
|
|
1068
1050
|
retryable;
|
|
1069
1051
|
recovery;
|
|
@@ -1072,11 +1054,11 @@ var DoomTeamExpectedError = class extends Error {
|
|
|
1072
1054
|
this.code = code;
|
|
1073
1055
|
this.retryable = retryable;
|
|
1074
1056
|
this.recovery = recovery;
|
|
1075
|
-
this.name = "
|
|
1057
|
+
this.name = "PoTeamExpectedError";
|
|
1076
1058
|
}
|
|
1077
1059
|
};
|
|
1078
1060
|
function invalidRequest(message, recovery) {
|
|
1079
|
-
return new
|
|
1061
|
+
return new PoTeamExpectedError("invalid_request", message, false, recovery);
|
|
1080
1062
|
}
|
|
1081
1063
|
//#endregion
|
|
1082
1064
|
//#region src/services/modelInfo/index.ts
|
|
@@ -1116,6 +1098,12 @@ function toModelInfo(model) {
|
|
|
1116
1098
|
thinkingLevelMap: model.thinkingLevelMap
|
|
1117
1099
|
};
|
|
1118
1100
|
}
|
|
1101
|
+
/** The parent's authenticated models as the host lists them now; only the parent model when it cannot list. */
|
|
1102
|
+
async function availableModelInfos(execution) {
|
|
1103
|
+
const models = await execution.getAvailableModels?.();
|
|
1104
|
+
if (models) return models.map(toModelInfo);
|
|
1105
|
+
return execution.model ? [toModelInfo(execution.model)] : [];
|
|
1106
|
+
}
|
|
1119
1107
|
function splitKnownThinkingSuffix(model) {
|
|
1120
1108
|
const colonIdx = model.lastIndexOf(THINKING_SUFFIX_SEPARATOR);
|
|
1121
1109
|
if (colonIdx === -1) return {
|
|
@@ -1399,6 +1387,205 @@ function selectAvailableModel(primaryModel, fallbackModels, availableModels, pre
|
|
|
1399
1387
|
}
|
|
1400
1388
|
}
|
|
1401
1389
|
//#endregion
|
|
1390
|
+
//#region src/services/nativeRunCoordinator/index.ts
|
|
1391
|
+
/**
|
|
1392
|
+
* The root session's agent. Every Team launcher except the browser's is one of
|
|
1393
|
+
* its tools, so it is who sent a task or steer that arrives through them.
|
|
1394
|
+
*/
|
|
1395
|
+
const MAIN_AGENT_SENDER = {
|
|
1396
|
+
kind: "agent",
|
|
1397
|
+
id: "main",
|
|
1398
|
+
name: "Main agent"
|
|
1399
|
+
};
|
|
1400
|
+
/** The person at the browser, who launches and guides runs from the Agents tab. */
|
|
1401
|
+
const HUMAN_SENDER = {
|
|
1402
|
+
kind: "human",
|
|
1403
|
+
name: "You"
|
|
1404
|
+
};
|
|
1405
|
+
function terminalState(event) {
|
|
1406
|
+
return event.state === "completed" || event.state === "failed" || event.state === "stopped" ? event.state : void 0;
|
|
1407
|
+
}
|
|
1408
|
+
function entryKey(sessionId, runId) {
|
|
1409
|
+
return JSON.stringify([sessionId, runId]);
|
|
1410
|
+
}
|
|
1411
|
+
/** A stored run's identity, as `start` put it in the request's `meta`. */
|
|
1412
|
+
function storedRunIdentity(run) {
|
|
1413
|
+
const meta = run.request.meta;
|
|
1414
|
+
if (typeof meta?.identity !== "string") return void 0;
|
|
1415
|
+
return {
|
|
1416
|
+
identity: meta.identity,
|
|
1417
|
+
inline: meta.inline === true,
|
|
1418
|
+
...typeof meta.agentDescription === "string" ? { agentDescription: meta.agentDescription } : {},
|
|
1419
|
+
...typeof meta.agentFilePath === "string" ? { agentFilePath: meta.agentFilePath } : {}
|
|
1420
|
+
};
|
|
1421
|
+
}
|
|
1422
|
+
var NativeRunCoordinator = class {
|
|
1423
|
+
childSessions;
|
|
1424
|
+
tracker;
|
|
1425
|
+
notifier;
|
|
1426
|
+
projection;
|
|
1427
|
+
entries = /* @__PURE__ */ new Map();
|
|
1428
|
+
closed = false;
|
|
1429
|
+
closing;
|
|
1430
|
+
constructor(childSessions, tracker, notifier, projection) {
|
|
1431
|
+
this.childSessions = childSessions;
|
|
1432
|
+
this.tracker = tracker;
|
|
1433
|
+
this.notifier = notifier;
|
|
1434
|
+
this.projection = projection;
|
|
1435
|
+
}
|
|
1436
|
+
service() {
|
|
1437
|
+
if (this.closed) throw new Error("The native Team run coordinator is closed.");
|
|
1438
|
+
const service = this.childSessions?.get();
|
|
1439
|
+
if (!service) throw new Error("The in-process Pi child-session service is unavailable.");
|
|
1440
|
+
return service;
|
|
1441
|
+
}
|
|
1442
|
+
async start(sessionId, request, identity) {
|
|
1443
|
+
if (!sessionId.trim()) throw new Error("Native child runs require a session identity.");
|
|
1444
|
+
const handle = await this.service().start({
|
|
1445
|
+
...request,
|
|
1446
|
+
...identity ? { meta: {
|
|
1447
|
+
...request.meta,
|
|
1448
|
+
...JSON.parse(JSON.stringify(identity))
|
|
1449
|
+
} } : {}
|
|
1450
|
+
});
|
|
1451
|
+
return this.adopt(sessionId, request.scope, request, handle, identity);
|
|
1452
|
+
}
|
|
1453
|
+
async restore(sessionId, run, intercom) {
|
|
1454
|
+
const service = this.service();
|
|
1455
|
+
if (!service.restore) throw new Error("This child-session service cannot restore runs.");
|
|
1456
|
+
const handle = await service.restore(run.request.runId, intercom ? { intercom } : void 0);
|
|
1457
|
+
return this.adopt(sessionId, run.request.scope, run.request, handle, storedRunIdentity(run));
|
|
1458
|
+
}
|
|
1459
|
+
async adopt(sessionId, scope, request, handle, identity) {
|
|
1460
|
+
if (this.closed) {
|
|
1461
|
+
await handle.dispose();
|
|
1462
|
+
throw new Error("The native Team run coordinator closed during child startup.");
|
|
1463
|
+
}
|
|
1464
|
+
const entry = {
|
|
1465
|
+
sessionId,
|
|
1466
|
+
scope,
|
|
1467
|
+
request,
|
|
1468
|
+
...identity ? { identity } : {},
|
|
1469
|
+
handle,
|
|
1470
|
+
terminal: false
|
|
1471
|
+
};
|
|
1472
|
+
const key = entryKey(sessionId, request.runId);
|
|
1473
|
+
this.entries.set(key, entry);
|
|
1474
|
+
try {
|
|
1475
|
+
const unsubscribe = handle.subscribe((event) => this.onEvent(entry, event));
|
|
1476
|
+
if (entry.terminal) unsubscribe();
|
|
1477
|
+
else entry.unsubscribe = unsubscribe;
|
|
1478
|
+
return handle;
|
|
1479
|
+
} catch (error) {
|
|
1480
|
+
this.entries.delete(key);
|
|
1481
|
+
await handle.dispose().catch(() => void 0);
|
|
1482
|
+
throw error;
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
get(sessionId, runId) {
|
|
1486
|
+
return this.entries.get(entryKey(sessionId, runId))?.handle;
|
|
1487
|
+
}
|
|
1488
|
+
/**
|
|
1489
|
+
* The newest entries of a run this session's child service stored, from its durable conversation, also once the
|
|
1490
|
+
* run finished or the session restarted; undefined for a run it cannot read, such as an external one.
|
|
1491
|
+
*/
|
|
1492
|
+
async readTranscript(sessionId, runId, limit) {
|
|
1493
|
+
const service = this.service();
|
|
1494
|
+
if (!service.readTranscriptPage) return void 0;
|
|
1495
|
+
try {
|
|
1496
|
+
return (await service.readTranscriptPage(runId, { limit })).entries;
|
|
1497
|
+
} catch (error) {
|
|
1498
|
+
if (this.entries.has(entryKey(sessionId, runId))) throw error;
|
|
1499
|
+
return;
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
status(sessionId, runId) {
|
|
1503
|
+
return this.tracker.getNative(sessionId, runId);
|
|
1504
|
+
}
|
|
1505
|
+
async reported(runId) {
|
|
1506
|
+
return this.service().reported?.(runId);
|
|
1507
|
+
}
|
|
1508
|
+
async steer(sessionId, runId, message, signal) {
|
|
1509
|
+
const handle = this.get(sessionId, runId);
|
|
1510
|
+
if (!handle) throw new Error(`No active native run matches '${runId}'.`);
|
|
1511
|
+
await handle.steer(message, signal);
|
|
1512
|
+
}
|
|
1513
|
+
async stop(sessionId, runId, reason) {
|
|
1514
|
+
const handle = this.get(sessionId, runId);
|
|
1515
|
+
if (!handle) throw new Error(`No active native run matches '${runId}'.`);
|
|
1516
|
+
await handle.stop(reason);
|
|
1517
|
+
}
|
|
1518
|
+
async close() {
|
|
1519
|
+
this.closed = true;
|
|
1520
|
+
this.closing ??= (async () => {
|
|
1521
|
+
const active = [...this.entries.values()];
|
|
1522
|
+
for (const entry of active) {
|
|
1523
|
+
entry.unsubscribe?.();
|
|
1524
|
+
this.entries.delete(entryKey(entry.sessionId, entry.request.runId));
|
|
1525
|
+
}
|
|
1526
|
+
const failures = (await Promise.allSettled(active.map((entry) => entry.handle.dispose()))).filter((outcome) => outcome.status === "rejected").map((outcome) => outcome.reason);
|
|
1527
|
+
if (failures.length) throw new AggregateError(failures, "Native Team run shutdown failed.");
|
|
1528
|
+
})();
|
|
1529
|
+
return this.closing;
|
|
1530
|
+
}
|
|
1531
|
+
onEvent(entry, event) {
|
|
1532
|
+
entry.startedAt ??= event.timestamp;
|
|
1533
|
+
const status = terminalState(event);
|
|
1534
|
+
const projection = {
|
|
1535
|
+
runId: event.runId,
|
|
1536
|
+
agent: entry.request.agent,
|
|
1537
|
+
...entry.identity ? {
|
|
1538
|
+
identity: entry.identity.identity,
|
|
1539
|
+
inline: entry.identity.inline
|
|
1540
|
+
} : {},
|
|
1541
|
+
...entry.identity?.agentDescription ? { agentDescription: entry.identity.agentDescription } : {},
|
|
1542
|
+
...entry.identity?.agentFilePath ? { agentFilePath: entry.identity.agentFilePath } : {},
|
|
1543
|
+
task: entry.request.task,
|
|
1544
|
+
cwd: entry.request.cwd,
|
|
1545
|
+
runtime: "pi",
|
|
1546
|
+
status: event.state === "starting" ? "queued" : event.state === "running" ? "running" : event.state,
|
|
1547
|
+
startedAt: entry.startedAt,
|
|
1548
|
+
updatedAt: event.timestamp,
|
|
1549
|
+
...event.cost === void 0 ? {} : { cost: event.cost },
|
|
1550
|
+
...event.tokens === void 0 ? {} : { tokens: event.tokens },
|
|
1551
|
+
...event.toolCount === void 0 ? {} : { toolCount: event.toolCount },
|
|
1552
|
+
...event.model === void 0 ? {} : { model: event.model },
|
|
1553
|
+
...event.thinking === void 0 ? {} : { thinking: event.thinking },
|
|
1554
|
+
...event.message ? {
|
|
1555
|
+
error: status === "failed" || status === "stopped" ? event.message : void 0,
|
|
1556
|
+
summary: event.message
|
|
1557
|
+
} : {}
|
|
1558
|
+
};
|
|
1559
|
+
this.tracker.upsertNative(entry.sessionId, entry.scope, projection);
|
|
1560
|
+
this.projection?.publish(entry.sessionId, projection);
|
|
1561
|
+
if (!status || entry.terminal) return;
|
|
1562
|
+
entry.terminal = true;
|
|
1563
|
+
entry.unsubscribe?.();
|
|
1564
|
+
this.entries.delete(entryKey(entry.sessionId, entry.request.runId));
|
|
1565
|
+
const result = {
|
|
1566
|
+
runId: entry.request.runId,
|
|
1567
|
+
agent: entry.request.agent,
|
|
1568
|
+
...entry.identity ? {
|
|
1569
|
+
identity: entry.identity.identity,
|
|
1570
|
+
inline: entry.identity.inline
|
|
1571
|
+
} : {},
|
|
1572
|
+
task: entry.request.task,
|
|
1573
|
+
cwd: entry.request.cwd,
|
|
1574
|
+
runtime: "pi",
|
|
1575
|
+
state: status,
|
|
1576
|
+
success: status === "completed",
|
|
1577
|
+
summary: event.message ?? (status === "completed" ? "Native child run completed." : `Native child run ${status}.`),
|
|
1578
|
+
durationMs: Math.max(0, event.timestamp - (this.tracker.getNative(entry.sessionId, entry.request.runId)?.startedAt ?? event.timestamp)),
|
|
1579
|
+
sessionId: entry.sessionId
|
|
1580
|
+
};
|
|
1581
|
+
if (this.notifier) this.notifier.deliver(result).then(async (delivered) => {
|
|
1582
|
+
if (!delivered) return;
|
|
1583
|
+
this.tracker.acknowledgeHandoff(entry.sessionId, entry.request.runId);
|
|
1584
|
+
await this.childSessions?.get()?.markReported?.(entry.request.runId);
|
|
1585
|
+
}).catch(() => void 0);
|
|
1586
|
+
}
|
|
1587
|
+
};
|
|
1588
|
+
//#endregion
|
|
1402
1589
|
//#region src/services/runtimeRegistry/index.ts
|
|
1403
1590
|
/**
|
|
1404
1591
|
* Which agent runtime executes a run, and how to invoke it.
|
|
@@ -1515,12 +1702,12 @@ async function runWithConcurrency(factories, concurrency, report) {
|
|
|
1515
1702
|
}
|
|
1516
1703
|
}
|
|
1517
1704
|
}
|
|
1518
|
-
report?.("
|
|
1705
|
+
report?.("po_team.concurrency_started", {
|
|
1519
1706
|
"team.task_count": factories.length,
|
|
1520
1707
|
"team.concurrency": effectiveConcurrency
|
|
1521
1708
|
});
|
|
1522
1709
|
await Promise.all(Array.from({ length: effectiveConcurrency }, () => worker()));
|
|
1523
|
-
report?.("
|
|
1710
|
+
report?.("po_team.concurrency_finished", {
|
|
1524
1711
|
"team.task_count": factories.length,
|
|
1525
1712
|
"team.concurrency": effectiveConcurrency,
|
|
1526
1713
|
"team.failure_count": results.filter((result) => result?.status === "rejected").length,
|
|
@@ -1567,8 +1754,8 @@ async function runWithConcurrency(factories, concurrency, report) {
|
|
|
1567
1754
|
* be worse than not checking at all (false confidence). Wiring this needs
|
|
1568
1755
|
* a real session-scoped store owned by a lifecycle-bound service - a
|
|
1569
1756
|
* follow-up, not guessed here.
|
|
1570
|
-
* - Fork context is resolved here and requires
|
|
1571
|
-
* unavailable
|
|
1757
|
+
* - Fork context is resolved here and requires the parent session's branch;
|
|
1758
|
+
* unavailable fork requests fail closed rather than becoming fresh
|
|
1572
1759
|
* launches.
|
|
1573
1760
|
* - Per-task overrides with no direct `BuildPiArgsInput` field confirmed yet
|
|
1574
1761
|
* (`skill`, `toolBudget`, `turnBudget`, `outputSchema`/structured output,
|
|
@@ -1604,9 +1791,8 @@ function describeForkFailure(reason) {
|
|
|
1604
1791
|
/**
|
|
1605
1792
|
* Map a captured parent branch onto the spawn request's fork fields.
|
|
1606
1793
|
*
|
|
1607
|
-
*
|
|
1608
|
-
*
|
|
1609
|
-
* those fields would describe it in the wrong vocabulary and lose the branch.
|
|
1794
|
+
* A headless host's branch forks a native child inside its own Harness. A
|
|
1795
|
+
* terminal Pi capture describes a branch for an external runtime only.
|
|
1610
1796
|
*/
|
|
1611
1797
|
function forkRequestFields(captured) {
|
|
1612
1798
|
if (!captured) return { parentForkFailure: "no-capture-installed" };
|
|
@@ -1614,7 +1800,6 @@ function forkRequestFields(captured) {
|
|
|
1614
1800
|
if (!captured.ok) return { parentForkFailure: captured.reason };
|
|
1615
1801
|
const { source } = captured;
|
|
1616
1802
|
return {
|
|
1617
|
-
parentForkSource: source.terminalSource,
|
|
1618
1803
|
...source.sessionFile ? { parentSessionFile: source.sessionFile } : {},
|
|
1619
1804
|
parentLeafId: source.leafId
|
|
1620
1805
|
};
|
|
@@ -1636,34 +1821,25 @@ function captureSessionForkSource(manager, mode) {
|
|
|
1636
1821
|
ok: false,
|
|
1637
1822
|
reason: "unsupported-version"
|
|
1638
1823
|
};
|
|
1639
|
-
|
|
1640
|
-
if (branch.at(-1)?.id !== leafId) return {
|
|
1824
|
+
if (manager.getBranch(leafId).at(-1)?.id !== leafId) return {
|
|
1641
1825
|
ok: false,
|
|
1642
1826
|
reason: "branch-mismatch"
|
|
1643
1827
|
};
|
|
1644
|
-
|
|
1645
|
-
if (!sourceSessionId.trim()) return {
|
|
1828
|
+
if (!manager.getSessionId().trim()) return {
|
|
1646
1829
|
ok: false,
|
|
1647
1830
|
reason: "no-session-id"
|
|
1648
1831
|
};
|
|
1649
|
-
const terminalSource = Object.freeze({
|
|
1650
|
-
kind: "terminal-pi-fork",
|
|
1651
|
-
sourceSessionId,
|
|
1652
|
-
sourceLeafId: leafId,
|
|
1653
|
-
snapshotJsonl: `${[header, ...branch].map((record) => JSON.stringify(record)).join("\n")}\n`
|
|
1654
|
-
});
|
|
1655
1832
|
const sessionFile = manager.getSessionFile();
|
|
1656
1833
|
return {
|
|
1657
1834
|
ok: true,
|
|
1658
1835
|
source: {
|
|
1659
1836
|
leafId,
|
|
1660
|
-
terminalSource,
|
|
1661
1837
|
...readableSessionFile(sessionFile) ? { sessionFile } : {}
|
|
1662
1838
|
}
|
|
1663
1839
|
};
|
|
1664
1840
|
}
|
|
1665
1841
|
function resolveEffectiveContext(taskInput, agent) {
|
|
1666
|
-
return taskInput.
|
|
1842
|
+
return taskInput.context ?? agent.defaultContext ?? CONTEXT_FRESH;
|
|
1667
1843
|
}
|
|
1668
1844
|
const DEFAULT_PARALLEL_CONCURRENCY = 4;
|
|
1669
1845
|
/**
|
|
@@ -1673,14 +1849,13 @@ const DEFAULT_PARALLEL_CONCURRENCY = 4;
|
|
|
1673
1849
|
* soon as its child confirms it started, and the child then runs detached. So
|
|
1674
1850
|
* `runWithConcurrency` throttles how fast children are STARTED, not how many
|
|
1675
1851
|
* are running - `{tasks: [8], concurrency: 4}` ends up with eight live model
|
|
1676
|
-
* processes, not four.
|
|
1677
|
-
* `PI_TEAM_MATE_MAX_PARALLEL`.
|
|
1852
|
+
* processes, not four. 30 lets one call fan a whole swarm out.
|
|
1678
1853
|
*
|
|
1679
1854
|
* This is a per-call declaration limit and nothing more. What actually bounds
|
|
1680
1855
|
* live children across concurrent calls is `AdmissionGate`
|
|
1681
1856
|
* (`runs/shared/admissionGate.ts`), which every spawn passes through.
|
|
1682
1857
|
*/
|
|
1683
|
-
const DEFAULT_PARALLEL_MAX_TASKS =
|
|
1858
|
+
const DEFAULT_PARALLEL_MAX_TASKS = 30;
|
|
1684
1859
|
/**
|
|
1685
1860
|
* Process-wide ceiling on children ALIVE at once.
|
|
1686
1861
|
*
|
|
@@ -1711,14 +1886,14 @@ function bestEffortRuntimeWarnings(runtime, agentConfig, excludeTools) {
|
|
|
1711
1886
|
...agentConfig.skills?.length || agentConfig.skillPath?.length ? ["skills"] : [],
|
|
1712
1887
|
...agentConfig.extensions !== void 0 || agentConfig.subagentOnlyExtensions?.length ? ["extensions"] : []
|
|
1713
1888
|
];
|
|
1714
|
-
if (unsupportedResources.length > 0) warnings.push(`
|
|
1889
|
+
if (unsupportedResources.length > 0) warnings.push(`Po Team cannot project or enforce configured Pi ${unsupportedResources.join(", ")} on runtime '${runtime}'.`);
|
|
1715
1890
|
if (agentConfig.skills?.length) warnings.push(`Configured skills were not injected: ${agentConfig.skills.join(", ")}.`);
|
|
1716
1891
|
if (excludeTools?.length) warnings.push(`Runtime '${runtime}' cannot enforce Team package tool exclusions; launched best effort for: ${excludeTools.join(", ")}.`);
|
|
1717
1892
|
return warnings;
|
|
1718
1893
|
}
|
|
1719
1894
|
function readWorkflowPreferences(payload) {
|
|
1720
1895
|
if (payload === void 0) return {};
|
|
1721
|
-
const invalid = (detail) => new
|
|
1896
|
+
const invalid = (detail) => new PoTeamExpectedError(ERROR_CODE_INVALID_REQUEST, `WORKFLOW_RUN_CONFIG ${detail}`, false, "Provide a JSON object with optional nonblank subagentModel and recognized subagentThinking.");
|
|
1722
1897
|
let value;
|
|
1723
1898
|
try {
|
|
1724
1899
|
value = JSON.parse(payload);
|
|
@@ -1736,29 +1911,40 @@ function readWorkflowPreferences(payload) {
|
|
|
1736
1911
|
if (preferences.subagentThinking && !THINKING_LEVELS.some((level) => level === preferences.subagentThinking)) throw invalid(`subagentThinking must be one of: ${THINKING_LEVELS.join(", ")}.`);
|
|
1737
1912
|
return preferences;
|
|
1738
1913
|
}
|
|
1739
|
-
function resolvedModelSelection(taskInput, agentConfig, runtime, parentModel, availableModels, teamPackageModels, preferences) {
|
|
1914
|
+
function resolvedModelSelection(taskInput, agentConfig, runtime, parentModel, availableModels, teamPackageModels, preferences, planning = {}, teamThinking) {
|
|
1740
1915
|
const parentModelId = isPiRuntime(runtime) && parentModel ? `${parentModel.provider}/${parentModel.id}` : void 0;
|
|
1741
1916
|
const agentModels = [...agentConfig.modelSource?.scope === "package" ? [] : [agentConfig.model], ...agentConfig.fallbackModels ?? []];
|
|
1742
1917
|
const candidates = (isPiRuntime(runtime) ? [
|
|
1743
1918
|
taskInput.model,
|
|
1744
1919
|
preferences.subagentModel,
|
|
1920
|
+
planning.subagentModel,
|
|
1745
1921
|
...agentModels,
|
|
1746
1922
|
...teamPackageModels ?? [],
|
|
1747
1923
|
parentModelId
|
|
1748
1924
|
] : [
|
|
1749
1925
|
taskInput.model,
|
|
1750
1926
|
preferences.subagentModel,
|
|
1927
|
+
planning.subagentModel,
|
|
1751
1928
|
...agentModels,
|
|
1752
1929
|
...teamPackageModels ?? []
|
|
1753
1930
|
]).filter((candidate) => Boolean(candidate?.trim()));
|
|
1754
1931
|
const [primaryModel, ...fallbackModels] = candidates;
|
|
1755
1932
|
const agentThinking = typeof agentConfig.thinking === "string" ? agentConfig.thinking : void 0;
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
thinking: agentThinking
|
|
1933
|
+
const teamThinkingFor = (model) => {
|
|
1934
|
+
if (agentConfig.thinking === false || !teamThinking) return void 0;
|
|
1935
|
+
const info = findModelInfo(model, availableModels);
|
|
1936
|
+
return info && !getSupportedThinkingLevels(info).some((level) => level === teamThinking) ? void 0 : teamThinking;
|
|
1761
1937
|
};
|
|
1938
|
+
if (!preferences.subagentModel && !preferences.subagentThinking && !planning.subagentModel && !planning.subagentThinking) {
|
|
1939
|
+
const model = selectAvailableModel(primaryModel, fallbackModels, availableModels);
|
|
1940
|
+
const selectedSuffix = model ? splitKnownThinkingSuffix(model).thinkingSuffix.slice(1) : void 0;
|
|
1941
|
+
return {
|
|
1942
|
+
primaryModel,
|
|
1943
|
+
fallbackModels,
|
|
1944
|
+
model,
|
|
1945
|
+
thinking: agentThinking ?? (selectedSuffix || void 0) ?? teamThinkingFor(model)
|
|
1946
|
+
};
|
|
1947
|
+
}
|
|
1762
1948
|
let model;
|
|
1763
1949
|
let selectedCandidate;
|
|
1764
1950
|
for (const candidate of candidates) {
|
|
@@ -1770,11 +1956,13 @@ function resolvedModelSelection(taskInput, agentConfig, runtime, parentModel, av
|
|
|
1770
1956
|
}
|
|
1771
1957
|
const selectedSuffix = model ? splitKnownThinkingSuffix(model).thinkingSuffix.slice(1) : void 0;
|
|
1772
1958
|
const explicitThinking = selectedCandidate === taskInput.model ? selectedSuffix : void 0;
|
|
1773
|
-
const
|
|
1774
|
-
const
|
|
1959
|
+
const planningThinking = !planning.subagentModel || selectedCandidate === planning.subagentModel ? planning.subagentThinking : void 0;
|
|
1960
|
+
const preferredThinking = (!preferences.subagentModel || selectedCandidate === preferences.subagentModel || selectedCandidate === taskInput.model ? preferences.subagentThinking : void 0) || planningThinking;
|
|
1961
|
+
const thinking = explicitThinking || preferredThinking || selectedSuffix || agentThinking || teamThinkingFor(model);
|
|
1962
|
+
const workflowThinking = !explicitThinking && (preferredThinking || selectedCandidate === preferences.subagentModel && selectedSuffix);
|
|
1775
1963
|
const modelInfo = findModelInfo(model, availableModels);
|
|
1776
|
-
if (isPiRuntime(runtime) && workflowThinking && modelInfo && !getSupportedThinkingLevels(modelInfo).some((level) => level === thinking)) throw new
|
|
1777
|
-
if (model && selectedSuffix && !explicitThinking &&
|
|
1964
|
+
if (isPiRuntime(runtime) && workflowThinking && modelInfo && !getSupportedThinkingLevels(modelInfo).some((level) => level === thinking)) throw new PoTeamExpectedError(ERROR_CODE_INVALID_REQUEST, `Subagent thinking '${thinking}' is unsupported by model '${modelInfo.fullId}'.`, false, `Choose a supported thinking level: ${getSupportedThinkingLevels(modelInfo).join(", ")}.`);
|
|
1965
|
+
if (model && selectedSuffix && !explicitThinking && preferredThinking) model = splitKnownThinkingSuffix(model).baseModel;
|
|
1778
1966
|
return {
|
|
1779
1967
|
primaryModel,
|
|
1780
1968
|
fallbackModels,
|
|
@@ -1800,7 +1988,8 @@ var SpawnPlanner = class {
|
|
|
1800
1988
|
nativeRuns;
|
|
1801
1989
|
teamChannel;
|
|
1802
1990
|
environment;
|
|
1803
|
-
|
|
1991
|
+
agentConfig;
|
|
1992
|
+
constructor(agents, spawner, policies = new SubagentCapabilityPolicyStore(), skills, reportConcurrencyEvent, _mcpToolResolver, admission, childSessions, nativeRuns, teamChannel, environment = {}, agentConfig) {
|
|
1804
1993
|
this.agents = agents;
|
|
1805
1994
|
this.spawner = spawner;
|
|
1806
1995
|
this.policies = policies;
|
|
@@ -1811,6 +2000,7 @@ var SpawnPlanner = class {
|
|
|
1811
2000
|
this.nativeRuns = nativeRuns;
|
|
1812
2001
|
this.teamChannel = teamChannel;
|
|
1813
2002
|
this.environment = environment;
|
|
2003
|
+
this.agentConfig = agentConfig;
|
|
1814
2004
|
}
|
|
1815
2005
|
generateRunId() {
|
|
1816
2006
|
return crypto.randomUUID();
|
|
@@ -1820,15 +2010,9 @@ var SpawnPlanner = class {
|
|
|
1820
2010
|
try {
|
|
1821
2011
|
stat = fs.statSync(cwd);
|
|
1822
2012
|
} catch {
|
|
1823
|
-
throw new
|
|
2013
|
+
throw new PoTeamExpectedError(ERROR_CODE_INVALID_REQUEST, `Working directory '${cwd}' does not exist.`, false, "Retry with an existing directory.");
|
|
1824
2014
|
}
|
|
1825
|
-
if (!stat.isDirectory()) throw new
|
|
1826
|
-
}
|
|
1827
|
-
resolveTeamPackageExcludeTools() {
|
|
1828
|
-
return resolveActiveTeamPackageConfig()?.config.excludeTools;
|
|
1829
|
-
}
|
|
1830
|
-
resolveTeamPackageModels() {
|
|
1831
|
-
return resolveActiveTeamModelSpecs();
|
|
2015
|
+
if (!stat.isDirectory()) throw new PoTeamExpectedError(ERROR_CODE_INVALID_REQUEST, `Working directory '${cwd}' is not a directory.`, false, "Retry with an existing directory.");
|
|
1832
2016
|
}
|
|
1833
2017
|
executableAvailable(command) {
|
|
1834
2018
|
if (path.isAbsolute(command) || command.includes(path.sep)) try {
|
|
@@ -1848,17 +2032,17 @@ var SpawnPlanner = class {
|
|
|
1848
2032
|
}
|
|
1849
2033
|
resolveTaskList(request) {
|
|
1850
2034
|
const hasSingle = request.single !== void 0;
|
|
1851
|
-
if (hasSingle === (request.tasks !== void 0)) throw new
|
|
2035
|
+
if (hasSingle === (request.tasks !== void 0)) throw new PoTeamExpectedError(ERROR_CODE_INVALID_REQUEST, "A run requires exactly one of internal single or tasks representations.", false, "Submit a non-empty requests array.");
|
|
1852
2036
|
if (hasSingle) return [request.single];
|
|
1853
|
-
if (!request.tasks || request.tasks.length === 0) throw new
|
|
2037
|
+
if (!request.tasks || request.tasks.length === 0) throw new PoTeamExpectedError(ERROR_CODE_INVALID_REQUEST, "A run requires at least one entry in its requests collection.", false, "Submit a non-empty requests array.");
|
|
1854
2038
|
return request.tasks;
|
|
1855
2039
|
}
|
|
1856
|
-
resolveAgentOrThrow(name, cwd, scope) {
|
|
1857
|
-
const resolved = this.agents.find(cwd, scope, name);
|
|
1858
|
-
if (!resolved) throw new
|
|
2040
|
+
async resolveAgentOrThrow(name, cwd, scope) {
|
|
2041
|
+
const resolved = await this.agents.find(cwd, scope, name);
|
|
2042
|
+
if (!resolved) throw new PoTeamExpectedError(ERROR_CODE_AGENT_NOT_FOUND, `Unknown agent '${name}'.`, false, "Call subagent({\"action\":\"agents\"}) and retry with an exact name.");
|
|
1859
2043
|
return resolved;
|
|
1860
2044
|
}
|
|
1861
|
-
resolveExecutableAgent(taskInput, cwd, scope) {
|
|
2045
|
+
async resolveExecutableAgent(taskInput, cwd, scope) {
|
|
1862
2046
|
if (!taskInput.inlineAgent) return this.resolveAgentOrThrow(taskInput.agent, cwd, scope);
|
|
1863
2047
|
return {
|
|
1864
2048
|
name: taskInput.agent.trim(),
|
|
@@ -1952,12 +2136,12 @@ var SpawnPlanner = class {
|
|
|
1952
2136
|
* the module header.
|
|
1953
2137
|
*/
|
|
1954
2138
|
async spawnOneChild(input) {
|
|
1955
|
-
const { runId, operationId, agentConfig, identity, capabilityCeiling, excludeTools, teamPackageModels, workflowPreferences, taskInput, childIndex, fanout, fallbackCwd, parentSessionId, parentForkSource, sessionScope, environment, parentSessionFile, maxLiveRuns, admissionTimeoutMs, handshakeTimeoutMs, artifacts, artifactDir, availableModels, parentModel, runtime, runtimes, skillProjectionCache } = input;
|
|
2139
|
+
const { runId, operationId, agentConfig, identity, capabilityCeiling, excludeTools, teamPackageModels, teamThinking, workflowPreferences, planningPreferences, taskInput, childIndex, fanout, fallbackCwd, parentSessionId, parentForkSource, sessionScope, environment, parentSessionFile, maxLiveRuns, admissionTimeoutMs, handshakeTimeoutMs, artifacts, artifactDir, availableModels, parentModel, runtime, runtimes, skillProjectionCache, sender } = input;
|
|
1956
2140
|
const cwd = taskInput.cwd ?? fallbackCwd;
|
|
1957
2141
|
const task = taskInput.task ?? "";
|
|
1958
2142
|
const effectiveContext = resolveEffectiveContext(taskInput, agentConfig);
|
|
1959
2143
|
const effectiveRuntime = taskInput.runtime ?? runtime ?? agentConfig.runtime ?? "pi";
|
|
1960
|
-
const modelSelection = resolvedModelSelection(taskInput, agentConfig, effectiveRuntime, parentModel, availableModels, teamPackageModels, workflowPreferences);
|
|
2144
|
+
const modelSelection = resolvedModelSelection(taskInput, agentConfig, effectiveRuntime, parentModel, availableModels, teamPackageModels, workflowPreferences, planningPreferences, teamThinking);
|
|
1961
2145
|
if (modelSelection.primaryModel && availableModels !== void 0 && !modelSelection.model) return {
|
|
1962
2146
|
agent: agentConfig.name,
|
|
1963
2147
|
identity: identity.identity,
|
|
@@ -1979,7 +2163,11 @@ var SpawnPlanner = class {
|
|
|
1979
2163
|
error: error instanceof Error ? error.message : String(error)
|
|
1980
2164
|
};
|
|
1981
2165
|
}
|
|
1982
|
-
const warnings = [
|
|
2166
|
+
const warnings = [
|
|
2167
|
+
...modelSelection.primaryModel && modelSelection.model && !selectAvailableModel(modelSelection.primaryModel, [], availableModels) ? [`[fallback] ${modelSelection.primaryModel} is unavailable. Using ${modelSelection.model}.`] : [],
|
|
2168
|
+
...skillProjection.warnings,
|
|
2169
|
+
...bestEffortRuntimeWarnings(effectiveRuntime, agentConfig, excludeTools)
|
|
2170
|
+
];
|
|
1983
2171
|
const warning = warnings.length > 0 ? warnings.join(" ") : void 0;
|
|
1984
2172
|
const spawnInput = {
|
|
1985
2173
|
runId,
|
|
@@ -1994,7 +2182,6 @@ var SpawnPlanner = class {
|
|
|
1994
2182
|
sessionScope,
|
|
1995
2183
|
...parentSessionFile ? { parentSessionFile } : {},
|
|
1996
2184
|
piArgs: {
|
|
1997
|
-
...taskInput.sessionFile ? { sessionFile: taskInput.sessionFile } : {},
|
|
1998
2185
|
...modelSelection.model ? { model: modelSelection.model } : {},
|
|
1999
2186
|
...capabilityCeiling ? { capabilityCeiling } : {},
|
|
2000
2187
|
...effectiveContext === CONTEXT_FORK && parentSessionId ? { parentSessionId } : {}
|
|
@@ -2006,7 +2193,7 @@ var SpawnPlanner = class {
|
|
|
2006
2193
|
runtimes
|
|
2007
2194
|
};
|
|
2008
2195
|
const admission = this.admission;
|
|
2009
|
-
if (!admission) throw new Error("
|
|
2196
|
+
if (!admission) throw new Error("Po Team spawn admission is not configured.");
|
|
2010
2197
|
let ticket;
|
|
2011
2198
|
try {
|
|
2012
2199
|
ticket = await admission.admit({
|
|
@@ -2028,11 +2215,8 @@ var SpawnPlanner = class {
|
|
|
2028
2215
|
if (isPiRuntime(effectiveRuntime)) {
|
|
2029
2216
|
if (!this.nativeRuns || !this.childSessions?.get()) throw new Error("The native Team run coordinator is unavailable.");
|
|
2030
2217
|
const scope = sessionScope;
|
|
2031
|
-
const source =
|
|
2032
|
-
|
|
2033
|
-
sessionFile: taskInput.sessionFile
|
|
2034
|
-
} : effectiveContext === CONTEXT_FORK ? parentForkSource ?? (() => {
|
|
2035
|
-
throw new Error("Native fork input requires an immutable terminal Pi snapshot.");
|
|
2218
|
+
const source = effectiveContext === CONTEXT_FORK ? parentForkSource ?? (() => {
|
|
2219
|
+
throw new Error("Native fork input requires the parent session's branch.");
|
|
2036
2220
|
})() : { kind: "fresh" };
|
|
2037
2221
|
const intercom = this.teamChannel?.createNativeChildIntercom({
|
|
2038
2222
|
rootSessionId: scope.rootSessionId,
|
|
@@ -2055,6 +2239,8 @@ var SpawnPlanner = class {
|
|
|
2055
2239
|
source,
|
|
2056
2240
|
agent: agentConfig.name,
|
|
2057
2241
|
task,
|
|
2242
|
+
...warning ? { meta: { warning } } : {},
|
|
2243
|
+
sender: sender ?? MAIN_AGENT_SENDER,
|
|
2058
2244
|
cwd,
|
|
2059
2245
|
...modelSelection.model ? { model: modelSelection.model } : {},
|
|
2060
2246
|
...typeof modelSelection.thinking === "string" ? { thinking: modelSelection.thinking } : {},
|
|
@@ -2071,7 +2257,9 @@ var SpawnPlanner = class {
|
|
|
2071
2257
|
environment
|
|
2072
2258
|
}, {
|
|
2073
2259
|
identity: identity.identity,
|
|
2074
|
-
inline: identity.inline
|
|
2260
|
+
inline: identity.inline,
|
|
2261
|
+
...agentConfig.description ? { agentDescription: agentConfig.description } : {},
|
|
2262
|
+
...agentConfig.filePath ? { agentFilePath: agentConfig.filePath } : {}
|
|
2075
2263
|
});
|
|
2076
2264
|
} catch (error) {
|
|
2077
2265
|
intercom?.dispose?.();
|
|
@@ -2113,71 +2301,67 @@ var SpawnPlanner = class {
|
|
|
2113
2301
|
}
|
|
2114
2302
|
}
|
|
2115
2303
|
async spawn(request, config) {
|
|
2304
|
+
if (!this.agentConfig) throw new PoTeamExpectedError("runtime_unavailable", "Team agent configuration is not loaded yet.", true, "Retry after the session configuration is ready.");
|
|
2305
|
+
const team = await this.agentConfig.current();
|
|
2116
2306
|
const tasks = this.resolveTaskList(request);
|
|
2117
2307
|
const workflowPreferences = readWorkflowPreferences(request.environment?.WORKFLOW_RUN_CONFIG ?? this.environment.WORKFLOW_RUN_CONFIG);
|
|
2308
|
+
const planningConfig = request.parentSelection?.state?.["minor-mode"]?.includes("plan") ? team.planningSubagents : void 0;
|
|
2309
|
+
const planningPreferences = {
|
|
2310
|
+
subagentModel: planningConfig?.model,
|
|
2311
|
+
subagentThinking: planningConfig?.thinking
|
|
2312
|
+
};
|
|
2118
2313
|
const fanout = tasks.length > 1;
|
|
2119
|
-
const teamPackageExcludeTools =
|
|
2120
|
-
const teamPackageModels =
|
|
2314
|
+
const teamPackageExcludeTools = team.excludeTools ? [...team.excludeTools] : void 0;
|
|
2315
|
+
const teamPackageModels = team.models;
|
|
2121
2316
|
const capabilityCeiling = this.policies.resolve();
|
|
2122
2317
|
const depthCheck = preflightSubagentDepth(request.currentDepth ?? resolveCurrentSubagentDepth(), config);
|
|
2123
|
-
if (depthCheck.error) throw new
|
|
2318
|
+
if (depthCheck.error) throw new PoTeamExpectedError(ERROR_CODE_UNSUPPORTED_OPERATION, depthCheck.error, false, "Give each child a self-contained task within the Team package policy.");
|
|
2124
2319
|
const maxTasks = config.parallel?.maxTasks ?? DEFAULT_PARALLEL_MAX_TASKS;
|
|
2125
|
-
if (tasks.length > maxTasks) throw new
|
|
2320
|
+
if (tasks.length > maxTasks) throw new PoTeamExpectedError(ERROR_CODE_INVALID_REQUEST, `Run requested ${tasks.length} tasks, but at most ${maxTasks} may be declared in one call. Concurrency throttles how fast they start, not how many run.`, false, "Send at most that many requests in this call and wait for them, or raise parallel.maxTasks in the subagent config. Splitting the same width across extra calls does not raise the live-child ceiling.");
|
|
2126
2321
|
for (const taskInput of tasks) {
|
|
2127
|
-
if (!taskInput.task?.trim()) throw new
|
|
2128
|
-
if (taskInput.inlineAgent && !taskInput.inlineAgent.systemPrompt.trim()) throw new
|
|
2322
|
+
if (!taskInput.task?.trim()) throw new PoTeamExpectedError(ERROR_CODE_INVALID_REQUEST, `Run request for '${taskInput.agent}' requires a nonblank task.`, false, "Provide a self-contained nonblank task.");
|
|
2323
|
+
if (taskInput.inlineAgent && !taskInput.inlineAgent.systemPrompt.trim()) throw new PoTeamExpectedError(ERROR_CODE_INVALID_REQUEST, `Inline agent '${taskInput.agent}' requires a nonblank system prompt.`, false, "Provide a focused read-only exploration role.");
|
|
2129
2324
|
}
|
|
2130
2325
|
const resolvedAgentCache = /* @__PURE__ */ new Map();
|
|
2131
|
-
const resolvedAgents =
|
|
2132
|
-
|
|
2326
|
+
const resolvedAgents = [];
|
|
2327
|
+
for (const taskInput of tasks) {
|
|
2133
2328
|
const cacheKey = [
|
|
2134
2329
|
request.agentScope,
|
|
2135
2330
|
canonicalizeDiscoveryCwd(taskInput.cwd ?? request.cwd),
|
|
2136
2331
|
taskInput.agent
|
|
2137
2332
|
].join("\0");
|
|
2138
|
-
|
|
2139
|
-
if (
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2333
|
+
let resolved = taskInput.inlineAgent ? void 0 : resolvedAgentCache.get(cacheKey);
|
|
2334
|
+
if (!resolved) {
|
|
2335
|
+
resolved = await this.resolveExecutableAgent(taskInput, taskInput.cwd ?? request.cwd, request.agentScope);
|
|
2336
|
+
if (!taskInput.inlineAgent) resolvedAgentCache.set(cacheKey, resolved);
|
|
2337
|
+
}
|
|
2338
|
+
resolvedAgents.push(resolved);
|
|
2339
|
+
}
|
|
2144
2340
|
for (const [index, taskInput] of tasks.entries()) {
|
|
2145
2341
|
const effectiveContext = resolveEffectiveContext(taskInput, resolvedAgents[index]);
|
|
2146
2342
|
const runtime = taskInput.runtime ?? request.runtime ?? resolvedAgents[index].runtime ?? "pi";
|
|
2147
2343
|
if (effectiveContext === CONTEXT_FORK) {
|
|
2148
|
-
if (!isPiRuntime(runtime)) throw new
|
|
2149
|
-
if (!request.parentForkSource) throw new
|
|
2344
|
+
if (!isPiRuntime(runtime)) throw new PoTeamExpectedError(ERROR_CODE_UNSUPPORTED_CONTEXT, `Fork context requires ${PI_RUNTIME_REQUIREMENT} for '${taskInput.agent}'.`, false, "Use a Pi agent for fork context or explicitly request a fresh run.");
|
|
2345
|
+
if (!request.parentForkSource) throw new PoTeamExpectedError(ERROR_CODE_UNSUPPORTED_CONTEXT, `Fork context is unavailable for '${taskInput.agent}': ${describeForkFailure(request.parentForkFailure)}.`, false, "Use an active Pi session with a completed parent turn or explicitly request a fresh run.");
|
|
2150
2346
|
}
|
|
2151
2347
|
}
|
|
2152
|
-
if (request.preallocatedRunIds && request.preallocatedRunIds.length !== tasks.length) throw new
|
|
2348
|
+
if (request.preallocatedRunIds && request.preallocatedRunIds.length !== tasks.length) throw new PoTeamExpectedError(ERROR_CODE_OPERATION_CONFLICT, "The operation journal run-id count does not match the request count.", false, "Submit the run as a new tool call.");
|
|
2153
2349
|
const runIds = request.preallocatedRunIds ?? tasks.map(() => this.generateRunId());
|
|
2154
|
-
const identities = tasks.map((taskInput, index) => {
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
runId: runIds[index]
|
|
2160
|
-
});
|
|
2161
|
-
adoptAgentIdentity(request.sessionScope, taskInput.identity, runIds[index]);
|
|
2162
|
-
return {
|
|
2163
|
-
identity: taskInput.identity,
|
|
2164
|
-
name: "",
|
|
2165
|
-
role: roleFromAgent(resolvedAgents[index].name),
|
|
2166
|
-
number: 0,
|
|
2167
|
-
inline,
|
|
2168
|
-
persisted: true
|
|
2169
|
-
};
|
|
2170
|
-
});
|
|
2350
|
+
const identities = tasks.map((taskInput, index) => claimAgentIdentity(request.sessionScope, {
|
|
2351
|
+
agent: resolvedAgents[index].name,
|
|
2352
|
+
inline: taskInput.inlineAgent !== void 0,
|
|
2353
|
+
runId: runIds[index]
|
|
2354
|
+
}));
|
|
2171
2355
|
const runtimes = resolveRuntimeTable(config.runtimes);
|
|
2172
2356
|
for (const [index, taskInput] of tasks.entries()) {
|
|
2173
2357
|
const agent = resolvedAgents[index];
|
|
2174
2358
|
const cwd = taskInput.cwd ?? request.cwd;
|
|
2175
2359
|
this.validateCwd(cwd);
|
|
2176
2360
|
const runtime = taskInput.runtime ?? request.runtime ?? agent.runtime ?? "pi";
|
|
2177
|
-
const modelSelection = resolvedModelSelection(taskInput, agent, runtime, request.parentModel, request.availableModels, teamPackageModels, workflowPreferences);
|
|
2178
|
-
if (modelSelection.primaryModel && request.availableModels !== void 0 && !modelSelection.model) throw new
|
|
2179
|
-
if (taskInput.inlineAgent && !isPiRuntime(runtime)) throw new
|
|
2180
|
-
if (capabilityCeiling && !isPiRuntime(runtime)) throw new
|
|
2361
|
+
const modelSelection = resolvedModelSelection(taskInput, agent, runtime, request.parentModel, request.availableModels, teamPackageModels, workflowPreferences, planningPreferences, team.thinking);
|
|
2362
|
+
if (modelSelection.primaryModel && request.availableModels !== void 0 && !modelSelection.model) throw new PoTeamExpectedError(ERROR_CODE_MODEL_UNAVAILABLE, `No authenticated model is available for '${agent.name}'.`, false, "Authenticate the requested model or choose an available model.");
|
|
2363
|
+
if (taskInput.inlineAgent && !isPiRuntime(runtime)) throw new PoTeamExpectedError(ERROR_CODE_UNSUPPORTED_OPERATION, `Inline agent '${taskInput.agent}' requires ${PI_RUNTIME_REQUIREMENT} to enforce its read-only tools.`, false, "Remove the runtime override or use a discovered external-runtime agent without inlineAgent.");
|
|
2364
|
+
if (capabilityCeiling && !isPiRuntime(runtime)) throw new PoTeamExpectedError(ERROR_CODE_UNSUPPORTED_OPERATION, `Runtime '${runtime}' cannot enforce the active capability ceiling.`, false, `Use ${PI_RUNTIME_REQUIREMENT} while plan mode or another capability ceiling is active.`);
|
|
2181
2365
|
if (!isPiRuntime(runtime)) {
|
|
2182
2366
|
let command;
|
|
2183
2367
|
try {
|
|
@@ -2186,9 +2370,9 @@ var SpawnPlanner = class {
|
|
|
2186
2370
|
cwd
|
|
2187
2371
|
}).command;
|
|
2188
2372
|
} catch (error) {
|
|
2189
|
-
throw new
|
|
2373
|
+
throw new PoTeamExpectedError(ERROR_CODE_RUNTIME_UNAVAILABLE, error instanceof Error ? error.message : String(error), false, `Configure a valid external runtime or use ${PI_RUNTIME_REQUIREMENT}.`);
|
|
2190
2374
|
}
|
|
2191
|
-
if (!this.executableAvailable(command)) throw new
|
|
2375
|
+
if (!this.executableAvailable(command)) throw new PoTeamExpectedError(ERROR_CODE_RUNTIME_UNAVAILABLE, `Executable '${command}' for runtime '${runtime}' is unavailable.`, false, `Install the executable, configure its absolute path, or use ${PI_RUNTIME_REQUIREMENT}.`);
|
|
2192
2376
|
}
|
|
2193
2377
|
}
|
|
2194
2378
|
const concurrency = fanout ? request.concurrency ?? config.parallel?.concurrency ?? DEFAULT_PARALLEL_CONCURRENCY : 1;
|
|
@@ -2202,7 +2386,9 @@ var SpawnPlanner = class {
|
|
|
2202
2386
|
identity: identities[childIndex],
|
|
2203
2387
|
excludeTools: teamPackageExcludeTools,
|
|
2204
2388
|
teamPackageModels,
|
|
2389
|
+
teamThinking: team.thinking,
|
|
2205
2390
|
workflowPreferences,
|
|
2391
|
+
planningPreferences,
|
|
2206
2392
|
capabilityCeiling,
|
|
2207
2393
|
taskInput,
|
|
2208
2394
|
childIndex,
|
|
@@ -2211,6 +2397,7 @@ var SpawnPlanner = class {
|
|
|
2211
2397
|
parentSessionId: request.parentSessionId,
|
|
2212
2398
|
parentForkSource: request.parentForkSource,
|
|
2213
2399
|
sessionScope: request.sessionScope,
|
|
2400
|
+
...request.sender ? { sender: request.sender } : {},
|
|
2214
2401
|
environment: request.environment ?? {},
|
|
2215
2402
|
parentSessionFile: request.parentSessionFile,
|
|
2216
2403
|
maxLiveRuns,
|
|
@@ -2236,7 +2423,7 @@ var SpawnPlanner = class {
|
|
|
2236
2423
|
//#endregion
|
|
2237
2424
|
//#region src/services/delegationBridge/index.ts
|
|
2238
2425
|
/**
|
|
2239
|
-
* Serves `
|
|
2426
|
+
* Serves `po-task` through Team's session-bound delegation service: turn a
|
|
2240
2427
|
* request into a spawned run, report its progress, and settle it exactly once.
|
|
2241
2428
|
*
|
|
2242
2429
|
* WHY THIS IS NOT IN `pi.ts`:
|
|
@@ -2266,7 +2453,7 @@ const DELEGATION_PROGRESS_INTERVAL_MS = 250;
|
|
|
2266
2453
|
const SETTLED_DELEGATION_WINDOW = 256;
|
|
2267
2454
|
/**
|
|
2268
2455
|
* Terminal states, named because they are an external contract with
|
|
2269
|
-
* `
|
|
2456
|
+
* `po-task`'s `DelegationManager`, which branches on the exact strings.
|
|
2270
2457
|
*/
|
|
2271
2458
|
const STATUS_COMPLETED = "completed";
|
|
2272
2459
|
const STATUS_CANCELLED = "cancelled";
|
|
@@ -2288,7 +2475,7 @@ function createDelegationBridge(deps) {
|
|
|
2288
2475
|
entry.disposeProgress?.();
|
|
2289
2476
|
active.delete(entry.requestId);
|
|
2290
2477
|
rememberSettled(entry.requestId);
|
|
2291
|
-
ctx.emit(
|
|
2478
|
+
ctx.emit(PO_DELEGATION_FINISHED_EVENT, result);
|
|
2292
2479
|
};
|
|
2293
2480
|
/** Ask the run to stop. Returns the failure message when the request itself failed. */
|
|
2294
2481
|
const stop = (entry, reason) => {
|
|
@@ -2319,7 +2506,13 @@ function createDelegationBridge(deps) {
|
|
|
2319
2506
|
timeoutWarningSent = true;
|
|
2320
2507
|
const secondsRemaining = Math.max(1, Math.ceil(warningLeadMs / 1e3));
|
|
2321
2508
|
const secondsLabel = `${secondsRemaining} second${secondsRemaining === 1 ? "" : "s"}`;
|
|
2322
|
-
deps.management.steer(runId,
|
|
2509
|
+
deps.management.steer(runId, {
|
|
2510
|
+
text: `This delegation will time out in about ${secondsLabel}. Stop exploring now, complete the highest-value remaining work, and return a concise result before the deadline. If you cannot finish, return verified findings and blockers immediately.`,
|
|
2511
|
+
sender: {
|
|
2512
|
+
kind: "system",
|
|
2513
|
+
name: "Po"
|
|
2514
|
+
}
|
|
2515
|
+
}).catch(() => void 0);
|
|
2323
2516
|
worked = true;
|
|
2324
2517
|
}
|
|
2325
2518
|
const progressKey = [
|
|
@@ -2332,7 +2525,7 @@ function createDelegationBridge(deps) {
|
|
|
2332
2525
|
].map((value) => String(value)).join(":");
|
|
2333
2526
|
if (progressKey === lastProgress) return worked;
|
|
2334
2527
|
lastProgress = progressKey;
|
|
2335
|
-
ctx.emit(
|
|
2528
|
+
ctx.emit(PO_DELEGATION_UPDATED_EVENT, {
|
|
2336
2529
|
requestId: entry.requestId,
|
|
2337
2530
|
runId,
|
|
2338
2531
|
...job.status !== void 0 ? { status: job.status } : {},
|
|
@@ -2407,7 +2600,7 @@ function createDelegationBridge(deps) {
|
|
|
2407
2600
|
settled: false
|
|
2408
2601
|
};
|
|
2409
2602
|
active.set(request.requestId, entry);
|
|
2410
|
-
ctx.emit(
|
|
2603
|
+
ctx.emit(PO_DELEGATION_ACCEPTED_EVENT, { requestId: request.requestId });
|
|
2411
2604
|
try {
|
|
2412
2605
|
const forkSource = await session.captureForkSource?.();
|
|
2413
2606
|
const outcome = (await deps.planner.spawn({
|
|
@@ -2422,8 +2615,10 @@ function createDelegationBridge(deps) {
|
|
|
2422
2615
|
agentScope: "both",
|
|
2423
2616
|
sessionScope: session.sessionScope,
|
|
2424
2617
|
parentSessionId: session.sessionId,
|
|
2618
|
+
parentSelection: session.parentSelection,
|
|
2619
|
+
environment: session.environment,
|
|
2425
2620
|
...forkRequestFields(forkSource),
|
|
2426
|
-
availableModels: session.availableModels,
|
|
2621
|
+
availableModels: await session.availableModels(),
|
|
2427
2622
|
...session.parentModel ? { parentModel: session.parentModel } : {}
|
|
2428
2623
|
}, deps.loadConfig())).outcomes[0];
|
|
2429
2624
|
if (!outcome?.runId) {
|
|
@@ -2446,10 +2641,15 @@ function createDelegationBridge(deps) {
|
|
|
2446
2641
|
return;
|
|
2447
2642
|
}
|
|
2448
2643
|
entry.runtimeStartedAt = Date.now();
|
|
2449
|
-
ctx.emit(
|
|
2644
|
+
ctx.emit(PO_DELEGATION_STARTED_EVENT, {
|
|
2450
2645
|
requestId: request.requestId,
|
|
2451
2646
|
runId: outcome.runId
|
|
2452
2647
|
});
|
|
2648
|
+
if (outcome.warning) try {
|
|
2649
|
+
await session.reportWarning?.(outcome.warning);
|
|
2650
|
+
} catch (error) {
|
|
2651
|
+
process.emitWarning(`Delegation warning could not be delivered: ${error instanceof Error ? error.message : String(error)}`);
|
|
2652
|
+
}
|
|
2453
2653
|
await monitor(entry, request, ctx, jobs, session.sessionId, session.sessionScope);
|
|
2454
2654
|
} catch (error) {
|
|
2455
2655
|
finish(entry, {
|
|
@@ -2477,15 +2677,15 @@ function createDelegationBridge(deps) {
|
|
|
2477
2677
|
createService(ctx, session) {
|
|
2478
2678
|
const service = {
|
|
2479
2679
|
sessionId: session.sessionId,
|
|
2480
|
-
generation: `
|
|
2680
|
+
generation: `po-delegation:${crypto.randomUUID()}`,
|
|
2481
2681
|
async request(request) {
|
|
2482
2682
|
if (!Check(DelegationRequestSchema, request)) throw new TypeError("Invalid delegation request.");
|
|
2483
|
-
ctx.emit(
|
|
2683
|
+
ctx.emit(PO_DELEGATION_REQUESTED_EVENT, request);
|
|
2484
2684
|
await onRequested(request, ctx, session);
|
|
2485
2685
|
},
|
|
2486
2686
|
cancel(request) {
|
|
2487
2687
|
if (!Check(DelegationCancelSchema, request)) throw new TypeError("Invalid delegation cancel request.");
|
|
2488
|
-
ctx.emit(
|
|
2688
|
+
ctx.emit(PO_DELEGATION_CANCELLED_EVENT, request);
|
|
2489
2689
|
onCancelled(request.requestId, request.reason, ctx);
|
|
2490
2690
|
}
|
|
2491
2691
|
};
|
|
@@ -2588,10 +2788,10 @@ function resolveRunnerEntry(builtRelativePath, sourceRelativePath, stem) {
|
|
|
2588
2788
|
if (fs.existsSync(builtCandidate)) return builtCandidate;
|
|
2589
2789
|
const sourceCandidate = path.join(directory, "src", `${sourceRelativePath}.ts`);
|
|
2590
2790
|
if (fs.existsSync(sourceCandidate)) return sourceCandidate;
|
|
2591
|
-
throw new Error(`
|
|
2791
|
+
throw new Error(`Po Team runner '${stem}' is unavailable at '${builtCandidate}' or '${sourceCandidate}'.`);
|
|
2592
2792
|
}
|
|
2593
2793
|
const parent = path.dirname(directory);
|
|
2594
|
-
if (parent === directory) throw new Error(`Could not locate the
|
|
2794
|
+
if (parent === directory) throw new Error(`Could not locate the Po Team package root for runner '${stem}'.`);
|
|
2595
2795
|
directory = parent;
|
|
2596
2796
|
}
|
|
2597
2797
|
}
|
|
@@ -2788,8 +2988,8 @@ const FABLE_STAGE = {
|
|
|
2788
2988
|
interrupted: "interrupted"
|
|
2789
2989
|
};
|
|
2790
2990
|
const FABLE_EVENT = {
|
|
2791
|
-
stageStarted: "
|
|
2792
|
-
finished: "
|
|
2991
|
+
stageStarted: "po_team.fable_stage_started",
|
|
2992
|
+
finished: "po_team.fable_finished"
|
|
2793
2993
|
};
|
|
2794
2994
|
const SESSION_ENDED_REASON = "Fable bridge session ended.";
|
|
2795
2995
|
const FABLE_ERROR = {
|
|
@@ -2979,7 +3179,7 @@ function createFablePlanBridge(deps) {
|
|
|
2979
3179
|
return Object.freeze({
|
|
2980
3180
|
...provider,
|
|
2981
3181
|
sessionId: session.sessionId,
|
|
2982
|
-
generation: `
|
|
3182
|
+
generation: `po-fable-plan:${crypto.randomUUID()}`
|
|
2983
3183
|
});
|
|
2984
3184
|
},
|
|
2985
3185
|
abandonAll() {
|
|
@@ -3167,7 +3367,7 @@ function parseTeamToolParams(raw) {
|
|
|
3167
3367
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) throw invalidRequest("Intercom params must be an object.", "Use one documented intercom action shape.");
|
|
3168
3368
|
const params = raw;
|
|
3169
3369
|
const action = TEAM_ACTIONS.find((candidate) => candidate === params.action);
|
|
3170
|
-
if (!action) throw new
|
|
3370
|
+
if (!action) throw new PoTeamExpectedError("unsupported_operation", `Unsupported intercom action: ${String(params.action)}.`, false, "Use members, send, ask, pending, or reply.");
|
|
3171
3371
|
if (!isOptionalString(params.to) || !isOptionalString(params.message) || !isOptionalString(params.requestId)) throw invalidRequest("Intercom to, message and requestId values must be strings.", "Correct the action fields and retry.");
|
|
3172
3372
|
const allowed = new Set(action === "members" || action === "pending" ? ["action"] : action === "reply" ? [
|
|
3173
3373
|
"action",
|
|
@@ -3280,7 +3480,14 @@ var NativeTeamDirectChannel = class {
|
|
|
3280
3480
|
bindRuntime: (runtime) => {
|
|
3281
3481
|
const member = this.members.get(memberId);
|
|
3282
3482
|
if (!member || disposed) throw new Error(`Native team member '${memberId}' is no longer active.`);
|
|
3283
|
-
member.deliver = (
|
|
3483
|
+
member.deliver = ({ content, details: { from } }) => runtime.steer({
|
|
3484
|
+
text: content,
|
|
3485
|
+
sender: from.role === "main" ? MAIN_AGENT_SENDER : {
|
|
3486
|
+
kind: "agent",
|
|
3487
|
+
id: from.runId ?? from.name,
|
|
3488
|
+
name: from.name
|
|
3489
|
+
}
|
|
3490
|
+
});
|
|
3284
3491
|
return {
|
|
3285
3492
|
name: NATIVE_TEAM_TOOL_NAME,
|
|
3286
3493
|
description: "Communicate with active native agents in this root session.",
|
|
@@ -3378,7 +3585,7 @@ var NativeTeamDirectChannel = class {
|
|
|
3378
3585
|
if (params.action === "reply") {
|
|
3379
3586
|
const request = this.asks.get(params.requestId ?? "");
|
|
3380
3587
|
const asker = request && this.members.get(request.fromMemberId);
|
|
3381
|
-
if (!request || request.toMemberId !== memberId || !asker) throw new
|
|
3588
|
+
if (!request || request.toMemberId !== memberId || !asker) throw new PoTeamExpectedError("recipient_not_found", `No open intercom ask matches requestId '${params.requestId}'. It may already be answered, or its asker has finished.`, false, "Call intercom({\"action\":\"pending\"}) and retry with an exact requestId, or continue without replying.");
|
|
3382
3589
|
await this.deliver(asker, formatDirectMessage(member.context, "reply", request.id, message));
|
|
3383
3590
|
this.closeAsk(request);
|
|
3384
3591
|
return {
|
|
@@ -3393,7 +3600,7 @@ var NativeTeamDirectChannel = class {
|
|
|
3393
3600
|
};
|
|
3394
3601
|
}
|
|
3395
3602
|
const target = this.findMember(params.to);
|
|
3396
|
-
if (!target) throw new
|
|
3603
|
+
if (!target) throw new PoTeamExpectedError("recipient_not_found", `Active intercom member '${params.to}' was not found.`, true, "Call intercom({\"action\":\"members\"}) and retry with an active member id.");
|
|
3397
3604
|
const messageId = operationMessageId(memberId, operationId);
|
|
3398
3605
|
const isAsk = params.action === "ask";
|
|
3399
3606
|
const result = () => ({
|
|
@@ -3463,15 +3670,15 @@ var NativeTeamDirectChannel = class {
|
|
|
3463
3670
|
}
|
|
3464
3671
|
async deliver(target, message) {
|
|
3465
3672
|
const id = target.context.memberId;
|
|
3466
|
-
if (this.members.get(id) !== target) throw new
|
|
3467
|
-
if (!target.deliver) throw new
|
|
3673
|
+
if (this.members.get(id) !== target) throw new PoTeamExpectedError("communication_unavailable", `Intercom member '${id}' has finished.`, false, "Call intercom({\"action\":\"members\"}) to see who is active.");
|
|
3674
|
+
if (!target.deliver) throw new PoTeamExpectedError("communication_unavailable", `Intercom member '${id}' is not ready.`, true, "Retry after the child session starts.");
|
|
3468
3675
|
target.inFlight += 1;
|
|
3469
3676
|
try {
|
|
3470
3677
|
await target.deliver(message);
|
|
3471
3678
|
target.delivered = true;
|
|
3472
3679
|
} catch (error) {
|
|
3473
|
-
if (error instanceof
|
|
3474
|
-
throw new
|
|
3680
|
+
if (error instanceof PoTeamExpectedError) throw error;
|
|
3681
|
+
throw new PoTeamExpectedError("communication_unavailable", `Intercom member '${id}' could not receive the message: ${error instanceof Error ? error.message : String(error)}`, true, "Retry shortly, or continue without it.");
|
|
3475
3682
|
} finally {
|
|
3476
3683
|
target.inFlight -= 1;
|
|
3477
3684
|
target.release?.(true);
|
|
@@ -3498,7 +3705,7 @@ var TeamChannelRuntime = class {
|
|
|
3498
3705
|
}
|
|
3499
3706
|
execute(operationId, rawParams) {
|
|
3500
3707
|
const context = this.context;
|
|
3501
|
-
if (!context || !this.directChannel) throw new
|
|
3708
|
+
if (!context || !this.directChannel) throw new PoTeamExpectedError("communication_unavailable", "Intercom is not active for this session.", true, "Wait for session startup or reload Po Team.");
|
|
3502
3709
|
return this.directChannel.execute(context.memberId, operationId, rawParams);
|
|
3503
3710
|
}
|
|
3504
3711
|
bindMainSession(rootSessionId) {
|
|
@@ -3555,51 +3762,80 @@ var NativeTeamChannelService = class {
|
|
|
3555
3762
|
};
|
|
3556
3763
|
//#endregion
|
|
3557
3764
|
//#region src/services/headlessIntercomTool/index.ts
|
|
3765
|
+
/**
|
|
3766
|
+
* Finds the peer a `to` value names: its session ID first, then its session
|
|
3767
|
+
* name, ignoring case, so an agent can use the name a person typed. A name two
|
|
3768
|
+
* peers share is refused with their IDs rather than guessed.
|
|
3769
|
+
*/
|
|
3770
|
+
function resolvePeer(peers, to) {
|
|
3771
|
+
const byId = peers.find((peer) => peer.sessionId === to);
|
|
3772
|
+
if (byId !== void 0) return byId;
|
|
3773
|
+
const wanted = to.toLowerCase();
|
|
3774
|
+
const named = peers.filter((peer) => peer.name.trim().toLowerCase() === wanted);
|
|
3775
|
+
if (named.length > 1) throw invalidRequest(`${String(named.length)} sessions are named "${to}".`, `Send to one session ID: ${named.map((peer) => peer.sessionId).join(", ")}.`);
|
|
3776
|
+
return named[0];
|
|
3777
|
+
}
|
|
3778
|
+
function describePeerLine(peer) {
|
|
3779
|
+
const details = [
|
|
3780
|
+
`session ${peer.sessionId}`,
|
|
3781
|
+
...peer.profile === void 0 ? [] : [`profile ${peer.profile}`],
|
|
3782
|
+
...peer.sharedGroups.length === 0 ? [] : [`shared groups: ${peer.sharedGroups.join(", ")}`]
|
|
3783
|
+
];
|
|
3784
|
+
return `- ${peer.name.trim() === "" ? peer.sessionId : peer.name} (${details.join("; ")}): use send; it can reply to you the same way`;
|
|
3785
|
+
}
|
|
3558
3786
|
function createHeadlessIntercomTool(channel, sessions) {
|
|
3559
3787
|
return {
|
|
3560
3788
|
name: "intercom",
|
|
3561
3789
|
label: "Intercom",
|
|
3562
3790
|
description: "Communicate with active agents and related live sessions.",
|
|
3563
3791
|
parameters: TeamToolParamsSchema,
|
|
3564
|
-
promptGuidelines: ["Send only new progress, blockers, or decisions. Do not repeat task briefs or delegation instructions.", "Use members to find related sessions
|
|
3792
|
+
promptGuidelines: ["Send only new progress, blockers, or decisions. Do not repeat task briefs or delegation instructions.", "Use members to find related sessions by the names people give them, then send to that name or its session ID. Session peers use send, not ask/reply, and can send back."],
|
|
3565
3793
|
executionMode: "serial",
|
|
3566
3794
|
async execute(toolCallId, parameters, signal, onUpdate) {
|
|
3567
3795
|
const params = parseTeamToolParams(parameters);
|
|
3568
3796
|
const peers = channel.current() === void 0 ? [] : sessions?.peers() ?? [];
|
|
3569
3797
|
const to = params.to?.trim();
|
|
3570
|
-
|
|
3571
|
-
|
|
3798
|
+
const knownId = channel.current() !== void 0 && to !== void 0 && sessions?.canCommunicate(to) === true;
|
|
3799
|
+
const teamMember = to !== void 0 && channel.snapshot().members.some((member) => member.name === to) && !knownId && !peers.some((peer) => peer.sessionId === to);
|
|
3800
|
+
const peer = sessions === void 0 || to === void 0 || teamMember ? void 0 : knownId ? peers.find((candidate) => candidate.sessionId === to) : resolvePeer(peers, to);
|
|
3801
|
+
const recipientId = knownId ? to : peer?.sessionId;
|
|
3802
|
+
if (sessions !== void 0 && recipientId !== void 0) {
|
|
3803
|
+
if (params.action !== "send") throw invalidRequest("Session peers support send only.", "Use send with the session name or ID.");
|
|
3572
3804
|
const message = validateMessage(params.message);
|
|
3573
3805
|
const delivery = sessions.delivery();
|
|
3574
3806
|
if (delivery === void 0) throw new Error("Session delivery is unavailable.");
|
|
3807
|
+
const senderName = sessions.name?.()?.trim() || sessions.sessionId;
|
|
3808
|
+
const toName = peer?.name.trim() || recipientId;
|
|
3575
3809
|
const receipt = await delivery.deliver({
|
|
3576
3810
|
deliveryId: `intercom:${sessions.sessionId}:${toolCallId}`,
|
|
3577
|
-
recipientKey:
|
|
3811
|
+
recipientKey: recipientId,
|
|
3578
3812
|
kind: "intercom_message",
|
|
3579
|
-
prompt: `Message from session ${
|
|
3813
|
+
prompt: `Message from session ${senderName} via intercom.\n\n${message}\n\nReply using intercom({ action: "send", to: ${JSON.stringify(senderName)}, message: "..." }).`,
|
|
3580
3814
|
delivery: "steer"
|
|
3581
3815
|
});
|
|
3582
3816
|
return {
|
|
3583
3817
|
content: [{
|
|
3584
3818
|
type: "text",
|
|
3585
|
-
text: `Message queued for session ${
|
|
3819
|
+
text: `Message queued for session ${toName}.`
|
|
3586
3820
|
}],
|
|
3587
3821
|
details: {
|
|
3588
3822
|
...receipt,
|
|
3589
|
-
to
|
|
3823
|
+
to: recipientId,
|
|
3824
|
+
toName
|
|
3590
3825
|
}
|
|
3591
3826
|
};
|
|
3592
3827
|
}
|
|
3593
|
-
const result =
|
|
3828
|
+
const result = toPoHeadlessToolResult(await channel.execute(toolCallId, parameters, signal, onUpdate && ((partial) => onUpdate(toPoHeadlessToolResult(partial)))));
|
|
3594
3829
|
if (params.action !== "members" || peers.length === 0) return result;
|
|
3595
3830
|
return {
|
|
3596
3831
|
...result,
|
|
3597
3832
|
content: [...result.content, {
|
|
3598
3833
|
type: "text",
|
|
3599
|
-
text: peers.map(
|
|
3834
|
+
text: peers.map(describePeerLine).join("\n")
|
|
3600
3835
|
}],
|
|
3601
|
-
details: { members: [...channel.snapshot().members, ...peers.map((
|
|
3602
|
-
name:
|
|
3836
|
+
details: { members: [...channel.snapshot().members, ...peers.map((peer) => ({
|
|
3837
|
+
name: peer.name || peer.sessionId,
|
|
3838
|
+
sessionId: peer.sessionId,
|
|
3603
3839
|
role: "session"
|
|
3604
3840
|
}))] }
|
|
3605
3841
|
};
|
|
@@ -3721,6 +3957,7 @@ var AsyncJobTracker = class {
|
|
|
3721
3957
|
startedAt: projection.startedAt,
|
|
3722
3958
|
updatedAt: projection.updatedAt,
|
|
3723
3959
|
...projection.error === void 0 ? {} : { error: projection.error },
|
|
3960
|
+
...projection.promptWithheld ? { promptWithheld: true } : {},
|
|
3724
3961
|
...projection.summary === void 0 ? {} : { summary: projection.summary },
|
|
3725
3962
|
...projection.tokens === void 0 ? {} : { tokens: projection.tokens },
|
|
3726
3963
|
...projection.cost === void 0 ? {} : { cost: projection.cost },
|
|
@@ -3932,7 +4169,7 @@ function formatDuration(ms) {
|
|
|
3932
4169
|
*/
|
|
3933
4170
|
const MAX_RETAINED_EVENTS = 1e3;
|
|
3934
4171
|
const NEWLINE_BYTE = 10;
|
|
3935
|
-
function isRecord$
|
|
4172
|
+
function isRecord$2(value) {
|
|
3936
4173
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3937
4174
|
}
|
|
3938
4175
|
function parseLine(line) {
|
|
@@ -3940,7 +4177,7 @@ function parseLine(line) {
|
|
|
3940
4177
|
if (!trimmed) return void 0;
|
|
3941
4178
|
try {
|
|
3942
4179
|
const parsed = JSON.parse(trimmed);
|
|
3943
|
-
return isRecord$
|
|
4180
|
+
return isRecord$2(parsed) ? parsed : void 0;
|
|
3944
4181
|
} catch {
|
|
3945
4182
|
return;
|
|
3946
4183
|
}
|
|
@@ -3975,7 +4212,7 @@ function parseArgsPayload(payload) {
|
|
|
3975
4212
|
if (!payload) return void 0;
|
|
3976
4213
|
try {
|
|
3977
4214
|
const parsed = JSON.parse(payload);
|
|
3978
|
-
return isRecord$
|
|
4215
|
+
return isRecord$2(parsed) ? parsed : void 0;
|
|
3979
4216
|
} catch {
|
|
3980
4217
|
return;
|
|
3981
4218
|
}
|
|
@@ -4170,125 +4407,63 @@ function readFleetTranscript(transcriptPath) {
|
|
|
4170
4407
|
warning: tail.warning
|
|
4171
4408
|
} : { events: tail.events };
|
|
4172
4409
|
}
|
|
4173
|
-
|
|
4174
|
-
|
|
4410
|
+
function contentText(content) {
|
|
4411
|
+
if (typeof content === "string") return content;
|
|
4412
|
+
if (!Array.isArray(content)) return "";
|
|
4413
|
+
return content.flatMap((part) => isRecord$2(part) && part.type === "text" && typeof part.text === "string" ? [part.text] : []).join("\n");
|
|
4414
|
+
}
|
|
4175
4415
|
/**
|
|
4176
|
-
*
|
|
4177
|
-
*
|
|
4178
|
-
* WHY EVERY SHUTDOWN SUSPENDS, WITH NO SPECIAL CASE FOR `reload`:
|
|
4179
|
-
* Pi emits `session_shutdown` for `quit`, `reload`, `new`, `resume` and `fork`.
|
|
4180
|
-
* An earlier design kept children alive across `reload` alone, on the grounds
|
|
4181
|
-
* that `/domains`, `/major-mode` and `/profile` reload the extension rather than
|
|
4182
|
-
* change the session. That is true, and it still makes `reload` the one case
|
|
4183
|
-
* behaving differently from the other four - which is a rule someone has to
|
|
4184
|
-
* remember, and a branch that silently does the wrong thing if Pi ever adds a
|
|
4185
|
-
* sixth reason. Suspending uniformly costs a respawn on reload and removes the
|
|
4186
|
-
* special case; restore is cheap because the child continues its own transcript
|
|
4187
|
-
* rather than starting over.
|
|
4188
|
-
*
|
|
4189
|
-
* WHY RESTORE IS OFFERED AND NEVER AUTOMATIC:
|
|
4190
|
-
* Reopening a session would otherwise spawn N processes and start spending
|
|
4191
|
-
* tokens on tasks that may be days stale. `session_start` reports what is
|
|
4192
|
-
* suspended; something explicit has to ask for it back.
|
|
4193
|
-
*
|
|
4194
|
-
* AVOID:
|
|
4195
|
-
* - Auto-restoring from any lifecycle event
|
|
4196
|
-
* - Recording a suspended run without its `sessionFile`; without it a restore
|
|
4197
|
-
* restarts the work instead of continuing it
|
|
4416
|
+
* Events of a native run's durable transcript page, so a native run renders through the same tail as a file
|
|
4417
|
+
* transcript. Entries are projected messages: `{ type: 'message', timestamp, message }` with Pi message roles.
|
|
4198
4418
|
*/
|
|
4199
|
-
|
|
4200
|
-
const
|
|
4201
|
-
|
|
4202
|
-
return path.join(scopeSuspendedDir(scope), `${runId}${JSON_SUFFIX}`);
|
|
4203
|
-
}
|
|
4204
|
-
function suspendRun(scope, record) {
|
|
4205
|
-
writeAtomicJson(suspendedPath(scope, record.runId), {
|
|
4206
|
-
version: SUSPENDED_RUN_VERSION,
|
|
4207
|
-
...record
|
|
4208
|
-
});
|
|
4209
|
-
}
|
|
4210
|
-
function parseSuspendedRun(raw) {
|
|
4211
|
-
let parsed;
|
|
4212
|
-
try {
|
|
4213
|
-
parsed = JSON.parse(raw);
|
|
4214
|
-
} catch {
|
|
4215
|
-
return;
|
|
4216
|
-
}
|
|
4217
|
-
const result = parseVersioned(parsed, [SUSPENDED_RUN_VERSION]);
|
|
4218
|
-
return result.ok && typeof result.value.runId === "string" ? result.value : void 0;
|
|
4219
|
-
}
|
|
4220
|
-
function listSuspendedRuns(scope) {
|
|
4221
|
-
let entries;
|
|
4222
|
-
try {
|
|
4223
|
-
entries = fs.readdirSync(scopeSuspendedDir(scope));
|
|
4224
|
-
} catch {
|
|
4225
|
-
return [];
|
|
4226
|
-
}
|
|
4227
|
-
const runs = [];
|
|
4419
|
+
function fleetEventsFromEntries(entries) {
|
|
4420
|
+
const events = [];
|
|
4421
|
+
const calls = /* @__PURE__ */ new Map();
|
|
4228
4422
|
for (const entry of entries) {
|
|
4229
|
-
if (!entry.
|
|
4230
|
-
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
|
|
4234
|
-
|
|
4423
|
+
if (!isRecord$2(entry) || entry.type !== "message" || !isRecord$2(entry.message)) continue;
|
|
4424
|
+
const message = entry.message;
|
|
4425
|
+
const at = typeof entry.timestamp === "number" ? entry.timestamp : 0;
|
|
4426
|
+
if (message.role === "user") {
|
|
4427
|
+
const text = contentText(message.content);
|
|
4428
|
+
if (text.trim()) events.push({
|
|
4429
|
+
kind: "user",
|
|
4430
|
+
at,
|
|
4431
|
+
text
|
|
4432
|
+
});
|
|
4433
|
+
} else if (message.role === "assistant") {
|
|
4434
|
+
const parts = splitAssistantContent(message.content);
|
|
4435
|
+
for (const thinking of parts?.thinking ?? []) events.push({
|
|
4436
|
+
kind: "thinking",
|
|
4437
|
+
at,
|
|
4438
|
+
text: thinking
|
|
4439
|
+
});
|
|
4440
|
+
if (parts?.text.trim()) events.push({
|
|
4441
|
+
kind: "assistant",
|
|
4442
|
+
at,
|
|
4443
|
+
text: parts.text,
|
|
4444
|
+
...typeof message.model === "string" ? { model: message.model } : {}
|
|
4445
|
+
});
|
|
4446
|
+
for (const part of Array.isArray(message.content) ? message.content : []) {
|
|
4447
|
+
if (!isRecord$2(part) || part.type !== "toolCall" || typeof part.id !== "string") continue;
|
|
4448
|
+
const event = {
|
|
4449
|
+
kind: "tool",
|
|
4450
|
+
at,
|
|
4451
|
+
text: "",
|
|
4452
|
+
...typeof part.name === "string" ? { name: part.name } : {},
|
|
4453
|
+
status: "running",
|
|
4454
|
+
...isRecord$2(part.arguments) ? { args: part.arguments } : {}
|
|
4455
|
+
};
|
|
4456
|
+
calls.set(part.id, event);
|
|
4457
|
+
events.push(event);
|
|
4458
|
+
}
|
|
4459
|
+
} else if (message.role === "toolResult" && typeof message.toolCallId === "string") {
|
|
4460
|
+
const call = calls.get(message.toolCallId);
|
|
4461
|
+
if (!call) continue;
|
|
4462
|
+
call.result = contentText(message.content);
|
|
4463
|
+
call.status = message.isError === true ? "error" : "ok";
|
|
4235
4464
|
}
|
|
4236
|
-
if (run) runs.push(run);
|
|
4237
|
-
}
|
|
4238
|
-
return runs.sort((left, right) => left.suspendedAt - right.suspendedAt);
|
|
4239
|
-
}
|
|
4240
|
-
async function listSuspendedRunsAsync(scope) {
|
|
4241
|
-
let entries;
|
|
4242
|
-
try {
|
|
4243
|
-
entries = await fs.promises.readdir(scopeSuspendedDir(scope));
|
|
4244
|
-
} catch {
|
|
4245
|
-
return [];
|
|
4246
|
-
}
|
|
4247
|
-
const runs = [];
|
|
4248
|
-
for (const entry of entries) {
|
|
4249
|
-
if (!entry.endsWith(JSON_SUFFIX)) continue;
|
|
4250
|
-
try {
|
|
4251
|
-
const run = parseSuspendedRun(await fs.promises.readFile(path.join(scopeSuspendedDir(scope), entry), "utf-8"));
|
|
4252
|
-
if (run) runs.push(run);
|
|
4253
|
-
} catch {}
|
|
4254
|
-
}
|
|
4255
|
-
return runs.sort((left, right) => left.suspendedAt - right.suspendedAt);
|
|
4256
|
-
}
|
|
4257
|
-
/** Drop a record once its run has been restored, or explicitly discarded. */
|
|
4258
|
-
function clearSuspendedRun(scope, runId) {
|
|
4259
|
-
try {
|
|
4260
|
-
fs.rmSync(suspendedPath(scope, runId), { force: true });
|
|
4261
|
-
} catch {}
|
|
4262
|
-
}
|
|
4263
|
-
/** Whether an explicit restore can safely continue this exact run. */
|
|
4264
|
-
function isSuspendedRunResumable(run) {
|
|
4265
|
-
if (run.runtime !== "pi" || !run.agent.trim() || !run.task.trim() || !run.cwd.trim() || !run.sessionFile) return false;
|
|
4266
|
-
try {
|
|
4267
|
-
fs.accessSync(run.sessionFile, fs.constants.R_OK);
|
|
4268
|
-
return true;
|
|
4269
|
-
} catch {
|
|
4270
|
-
return false;
|
|
4271
4465
|
}
|
|
4272
|
-
|
|
4273
|
-
/** The identity prefix for a suspended line, empty for a record written before identities existed. */
|
|
4274
|
-
function suspendedLabel(run) {
|
|
4275
|
-
const label = formatAgentIdentity(run.identity, run.inline);
|
|
4276
|
-
return label ? `${label}, ` : "";
|
|
4277
|
-
}
|
|
4278
|
-
/** One line per suspended run, for the report `session_start` prints. */
|
|
4279
|
-
function formatSuspendedRuns(runs) {
|
|
4280
|
-
if (runs.length === 0) return "";
|
|
4281
|
-
const lines = runs.map((run) => {
|
|
4282
|
-
const continues = isSuspendedRunResumable(run) ? "resumable" : "not resumable";
|
|
4283
|
-
return `- ${run.runId} (${suspendedLabel(run)}${run.agent}, ${run.runtime}, ${continues}): ${run.task.split("\n")[0]}`;
|
|
4284
|
-
});
|
|
4285
|
-
const noun = runs.length === 1 ? "subagent" : "subagents";
|
|
4286
|
-
return [
|
|
4287
|
-
`${runs.length} suspended ${noun} from this session:`,
|
|
4288
|
-
...lines,
|
|
4289
|
-
"",
|
|
4290
|
-
"They are stopped. Restore one with subagent {\"action\":\"restore\",\"id\":\"<run-id>\"}, or leave them."
|
|
4291
|
-
].join("\n");
|
|
4466
|
+
return events;
|
|
4292
4467
|
}
|
|
4293
4468
|
function age(job, now) {
|
|
4294
4469
|
return job.startedAt ? formatDuration(Math.max(0, now - job.startedAt)) : "unknown";
|
|
@@ -4299,8 +4474,8 @@ function age(job, now) {
|
|
|
4299
4474
|
* Takes `now` rather than calling `Date.now()` so a test asserting rendered
|
|
4300
4475
|
* ages does not have to freeze global time.
|
|
4301
4476
|
*/
|
|
4302
|
-
function formatFleetView(jobs, now = Date.now()
|
|
4303
|
-
if (jobs.length === 0
|
|
4477
|
+
function formatFleetView(jobs, now = Date.now()) {
|
|
4478
|
+
if (jobs.length === 0) return "No runs tracked in this session. A run appears here once it has been launched from this session.";
|
|
4304
4479
|
const lines = [`${jobs.length} active run${jobs.length === 1 ? "" : "s"}:`];
|
|
4305
4480
|
for (const job of jobs) {
|
|
4306
4481
|
const parts = [`- ${job.runId}`];
|
|
@@ -4311,18 +4486,8 @@ function formatFleetView(jobs, now = Date.now(), suspended = []) {
|
|
|
4311
4486
|
if (job.error) parts.push(`error: ${job.error}`);
|
|
4312
4487
|
lines.push(parts.join(" · "));
|
|
4313
4488
|
}
|
|
4314
|
-
|
|
4315
|
-
|
|
4316
|
-
for (const run of suspended) {
|
|
4317
|
-
const recovery = isSuspendedRunResumable(run) ? `restore with { action: "restore", id: "${run.runId}" }` : "not resumable; submit a new explicit run";
|
|
4318
|
-
const label = formatAgentIdentity(run.identity, run.inline);
|
|
4319
|
-
lines.push(`- ${run.runId} (${label ? `${label}, ` : ""}${run.agent}, ${run.runtime}): ${recovery}`);
|
|
4320
|
-
}
|
|
4321
|
-
}
|
|
4322
|
-
if (jobs.length > 0) {
|
|
4323
|
-
lines.push("");
|
|
4324
|
-
lines.push(`Inspect one with { action: "status", id: "<run id>", transcriptLines: 80 }.`);
|
|
4325
|
-
}
|
|
4489
|
+
lines.push("");
|
|
4490
|
+
lines.push(`Inspect one with { action: "status", id: "<run id>", transcriptLines: 80 }.`);
|
|
4326
4491
|
return lines.join("\n");
|
|
4327
4492
|
}
|
|
4328
4493
|
/** Tool output shown to the model, bounded so one long result cannot crowd out the rest of the tail. */
|
|
@@ -4355,11 +4520,25 @@ function formatEvent(event) {
|
|
|
4355
4520
|
*/
|
|
4356
4521
|
function formatRunTranscript(status, maxLines) {
|
|
4357
4522
|
const transcriptPath = status?.transcriptPath;
|
|
4358
|
-
if (!transcriptPath) return status ? "This run has no transcript
|
|
4523
|
+
if (!transcriptPath) return status ? "This run has no transcript to read." : "No status found for this run, so there is no transcript to read.";
|
|
4359
4524
|
const transcript = readFleetTranscript(transcriptPath);
|
|
4360
4525
|
if (transcript.warning) return `Transcript at '${transcriptPath}' could not be read: ${transcript.warning}`;
|
|
4361
4526
|
if (transcript.events.length === 0) return `Transcript at '${transcriptPath}' is empty. The run has not written anything yet.`;
|
|
4362
|
-
|
|
4527
|
+
return formatTail(transcript.events, maxLines);
|
|
4528
|
+
}
|
|
4529
|
+
/**
|
|
4530
|
+
* A native run's transcript tail, read from its durable conversation rather than a file.
|
|
4531
|
+
*
|
|
4532
|
+
* ponytail: `entries` is one page of the newest entries, so the "of N lines" count covers that page, not the
|
|
4533
|
+
* whole run; read older pages first if a caller needs the full count.
|
|
4534
|
+
*/
|
|
4535
|
+
function formatNativeRunTranscript(entries, maxLines) {
|
|
4536
|
+
const events = fleetEventsFromEntries(entries);
|
|
4537
|
+
if (events.length === 0) return "The run has no transcript entries yet.";
|
|
4538
|
+
return formatTail(events, maxLines);
|
|
4539
|
+
}
|
|
4540
|
+
function formatTail(events, maxLines) {
|
|
4541
|
+
const rendered = events.flatMap((event) => formatEvent(event).split("\n"));
|
|
4363
4542
|
const kept = rendered.slice(-maxLines);
|
|
4364
4543
|
const dropped = rendered.length - kept.length;
|
|
4365
4544
|
return [
|
|
@@ -4407,7 +4586,7 @@ function startOperation(scope, operationId, args, runIds) {
|
|
|
4407
4586
|
} catch (error) {
|
|
4408
4587
|
if (!(error instanceof Error && "code" in error && error.code === "EEXIST")) throw error;
|
|
4409
4588
|
const existing = readRecord(file);
|
|
4410
|
-
if (existing.argumentHash !== argumentHash) throw new
|
|
4589
|
+
if (existing.argumentHash !== argumentHash) throw new PoTeamExpectedError("operation_conflict", `Operation '${operationId}' was already used with different arguments.`, false, "Submit the operation as a new tool call.");
|
|
4411
4590
|
return {
|
|
4412
4591
|
kind: "replay",
|
|
4413
4592
|
record: existing
|
|
@@ -4444,7 +4623,6 @@ function progressMessage(params) {
|
|
|
4444
4623
|
case SUBAGENT_ACTIONS.run: return `Starting ${params.requests.length} subagent${params.requests.length === 1 ? "" : "s"}...`;
|
|
4445
4624
|
case SUBAGENT_ACTIONS.steer: return `Steering subagent ${params.id}...`;
|
|
4446
4625
|
case SUBAGENT_ACTIONS.stop: return `Requesting stop for subagent ${params.id}...`;
|
|
4447
|
-
case SUBAGENT_ACTIONS.restore: return `Restoring subagent ${params.id}...`;
|
|
4448
4626
|
default: return;
|
|
4449
4627
|
}
|
|
4450
4628
|
}
|
|
@@ -4472,12 +4650,6 @@ function publicAgent(agent) {
|
|
|
4472
4650
|
...tools.length ? { tools } : {}
|
|
4473
4651
|
};
|
|
4474
4652
|
}
|
|
4475
|
-
function publicSuspendedRun(run) {
|
|
4476
|
-
return {
|
|
4477
|
-
...run,
|
|
4478
|
-
resumable: isSuspendedRunResumable(run)
|
|
4479
|
-
};
|
|
4480
|
-
}
|
|
4481
4653
|
/** One agent in full, as the model reads it. Shared with the headless tool. */
|
|
4482
4654
|
function formatAgentDetail(agent) {
|
|
4483
4655
|
const detail = publicAgent(agent);
|
|
@@ -4507,7 +4679,7 @@ function validateParams(input) {
|
|
|
4507
4679
|
requireNonblank(record, "id", action);
|
|
4508
4680
|
requireNonblank(record, "message", action);
|
|
4509
4681
|
}
|
|
4510
|
-
if (action === SUBAGENT_ACTIONS.stop
|
|
4682
|
+
if (action === SUBAGENT_ACTIONS.stop) requireNonblank(record, "id", action);
|
|
4511
4683
|
return raw;
|
|
4512
4684
|
}
|
|
4513
4685
|
var SubagentToolService = class {
|
|
@@ -4572,7 +4744,7 @@ var SubagentToolService = class {
|
|
|
4572
4744
|
const operation = startOperation(scope, operationId, params, params.requests.map(() => randomUUID()));
|
|
4573
4745
|
if (operation.kind === "replay") {
|
|
4574
4746
|
if (operation.record.state === "completed" && operation.record.result) return operation.record.result;
|
|
4575
|
-
throw new
|
|
4747
|
+
throw new PoTeamExpectedError("delivery_unconfirmed", `Operation '${operationId}' is already pending for runs: ${operation.record.runIds.join(", ")}.`, true, "Call subagent({\"action\":\"status\"}) and do not submit the run again.");
|
|
4576
4748
|
}
|
|
4577
4749
|
const result = this.trackOutcomes(await this.planner.spawn(this.buildSpawnRequest(operationId, params, ctx, operation.record.runIds), loadConfig().config), ctx);
|
|
4578
4750
|
const started = result.outcomes.filter((outcome) => outcome.runId);
|
|
@@ -4580,7 +4752,7 @@ var SubagentToolService = class {
|
|
|
4580
4752
|
const outcomeLines = result.outcomes.map((outcome) => {
|
|
4581
4753
|
return `- ${formatAgentIdentity(outcome.identity, outcome.inline) ?? outcome.agent}: ${outcome.runId ? outcome.runId : `failed: ${outcome.error ?? "unknown error"}`}${outcome.warning ? `\n Warning: ${outcome.warning}` : ""}`;
|
|
4582
4754
|
});
|
|
4583
|
-
if (started.length === 0 && failed.length > 0) throw new
|
|
4755
|
+
if (started.length === 0 && failed.length > 0) throw new PoTeamExpectedError("runtime_unavailable", `No requested subagent started.\n${outcomeLines.join("\n")}`, true, "Correct the reported launch failures. If an agent name is uncertain, call subagent({\"action\":\"agents\"}) before retrying only corrected requests.");
|
|
4584
4756
|
const subagentLabel = result.outcomes.length === 1 ? "subagent" : "subagents";
|
|
4585
4757
|
const summary = failed.length === 0 ? `Started ${started.length} ${subagentLabel}:` : `Started ${started.length}/${result.outcomes.length} ${subagentLabel}; ${failed.length} failed:`;
|
|
4586
4758
|
const next = failed.length === 0 ? "Completion will arrive asynchronously. Continue only non-overlapping work, or end your turn." : "Completion will arrive asynchronously for started runs. Do not resubmit them; retry only corrected failed entries.";
|
|
@@ -4597,44 +4769,35 @@ var SubagentToolService = class {
|
|
|
4597
4769
|
completeOperation(scope, operationId, operation.record, response);
|
|
4598
4770
|
return response;
|
|
4599
4771
|
}
|
|
4600
|
-
executeAgents(params, ctx) {
|
|
4772
|
+
async executeAgents(params, ctx) {
|
|
4601
4773
|
const cwd = params.cwd ?? ctx.cwd;
|
|
4602
4774
|
const scope = scopeOrDefault(params.scope);
|
|
4603
4775
|
if (!params.name) {
|
|
4604
|
-
const result = this.discovery.discover(cwd, scope);
|
|
4776
|
+
const result = await this.discovery.discover(cwd, scope);
|
|
4605
4777
|
return textResult(formatAgentList(result.agents), { agents: result.agents.map(publicAgent) });
|
|
4606
4778
|
}
|
|
4607
|
-
const agent = this.discovery.find(cwd, scope, params.name);
|
|
4779
|
+
const agent = await this.discovery.find(cwd, scope, params.name);
|
|
4608
4780
|
if (!agent) {
|
|
4609
|
-
const candidates = this.discovery.discover(cwd, scope).agents.map((candidate) => candidate.name).toSorted();
|
|
4610
|
-
throw new
|
|
4781
|
+
const candidates = (await this.discovery.discover(cwd, scope)).agents.map((candidate) => candidate.name).toSorted();
|
|
4782
|
+
throw new PoTeamExpectedError("agent_not_found", `No executable agent matches '${params.name}'.${candidates.length ? ` Available: ${candidates.join(", ")}.` : ""}`, false, "Call subagent({\"action\":\"agents\"}) and retry with an exact name.");
|
|
4611
4783
|
}
|
|
4612
4784
|
return textResult(formatAgentDetail(agent), { agent: publicAgent(agent) });
|
|
4613
4785
|
}
|
|
4614
|
-
async executeManagement(params, ctx, signal
|
|
4786
|
+
async executeManagement(params, ctx, signal) {
|
|
4615
4787
|
const scope = this.sessionScope(ctx);
|
|
4616
4788
|
this.management.bindSessionScope(scope);
|
|
4617
4789
|
const jobs = this.tracker.forSession(ctx.sessionManager.getSessionId(), scope);
|
|
4618
4790
|
switch (params.action) {
|
|
4619
4791
|
case SUBAGENT_ACTIONS.status: {
|
|
4620
|
-
const suspendedRuns = listSuspendedRuns(scope);
|
|
4621
4792
|
if (!("id" in params)) {
|
|
4622
4793
|
if ("transcriptLines" in params && params.transcriptLines !== void 0) throw invalidRequest("transcriptLines requires a run id.", "Add id or omit transcriptLines for fleet status.");
|
|
4623
4794
|
const runs = jobs.list();
|
|
4624
|
-
return textResult(formatFleetView(runs, Date.now()
|
|
4625
|
-
fleet: runs,
|
|
4626
|
-
suspended: suspendedRuns.map(publicSuspendedRun)
|
|
4627
|
-
});
|
|
4795
|
+
return textResult(formatFleetView(runs, Date.now()), { fleet: runs });
|
|
4628
4796
|
}
|
|
4629
4797
|
const runId = params.id;
|
|
4630
4798
|
if (!runId) throw invalidRequest("transcriptLines requires a run id.", "Add id or omit transcriptLines for fleet status.");
|
|
4631
|
-
const suspended = suspendedRuns.find((run) => run.runId === runId);
|
|
4632
|
-
if (suspended && params.transcriptLines === void 0) {
|
|
4633
|
-
const recovery = isSuspendedRunResumable(suspended) ? `Restore it with { action: "restore", id: "${suspended.runId}" }.` : "It is not resumable; submit a new explicit run.";
|
|
4634
|
-
return textResult(`Run '${suspended.runId}' is suspended. ${recovery}`, { suspended: publicSuspendedRun(suspended) });
|
|
4635
|
-
}
|
|
4636
4799
|
const result = this.management.status(runId);
|
|
4637
|
-
if (!result.status && !result.runDir && !result.resultPath
|
|
4800
|
+
if (!result.status && !result.runDir && !result.resultPath) throw new PoTeamExpectedError("run_not_found", `No run matches '${runId}'.`, false, "Call subagent({\"action\":\"status\"}) and retry with an exact id.");
|
|
4638
4801
|
if (params.transcriptLines !== void 0) return textResult(formatRunTranscript(result.status, params.transcriptLines ?? 80), { transcript: { runId: result.runId } });
|
|
4639
4802
|
return textResult(result.status ? `Run '${result.runId}': ${result.status.state}` : `Run '${result.runId}' has no status yet.`, { status: result });
|
|
4640
4803
|
}
|
|
@@ -4646,44 +4809,13 @@ var SubagentToolService = class {
|
|
|
4646
4809
|
case SUBAGENT_ACTIONS.steer: {
|
|
4647
4810
|
const id = resolveTrackedRunId(jobs, params.id);
|
|
4648
4811
|
const runtime = jobs.get(id)?.runtime;
|
|
4649
|
-
if (runtime && !isPiRuntime(runtime)) throw new
|
|
4650
|
-
const result = await this.management.steer(id,
|
|
4812
|
+
if (runtime && !isPiRuntime(runtime)) throw new PoTeamExpectedError("unsupported_operation", `Run '${id}' uses '${runtime}', which does not support steering.`, false, `Use subagent({"action":"stop","id":"${id}"}) to terminate it.`);
|
|
4813
|
+
const result = await this.management.steer(id, {
|
|
4814
|
+
text: params.message,
|
|
4815
|
+
sender: MAIN_AGENT_SENDER
|
|
4816
|
+
}, void 0, signal);
|
|
4651
4817
|
return textResult(`Steer request '${result.requestId}' for '${id}' is ${result.state}: ${result.message}`, { steer: result });
|
|
4652
4818
|
}
|
|
4653
|
-
case SUBAGENT_ACTIONS.suspended: {
|
|
4654
|
-
const runs = listSuspendedRuns(scope);
|
|
4655
|
-
return textResult(runs.length ? formatSuspendedRuns(runs) : "No suspended subagents in this session.", { suspended: runs.map(publicSuspendedRun) });
|
|
4656
|
-
}
|
|
4657
|
-
case SUBAGENT_ACTIONS.restore: {
|
|
4658
|
-
const record = listSuspendedRuns(scope).find((run) => run.runId === params.id);
|
|
4659
|
-
if (!record) throw new DoomTeamExpectedError("run_not_found", `No suspended run matches '${params.id}'.`, false, "Call subagent({\"action\":\"suspended\"}) and retry with an exact id.");
|
|
4660
|
-
if (!isSuspendedRunResumable(record)) throw new DoomTeamExpectedError("not_resumable", `Suspended run '${params.id}' does not have complete, readable Pi recovery state.`, false, "Submit a new explicit run request instead of restoring this record.");
|
|
4661
|
-
const outcome = this.trackOutcomes(await this.planner.spawn({
|
|
4662
|
-
single: {
|
|
4663
|
-
agent: record.agent,
|
|
4664
|
-
...record.inlineAgent ? { inlineAgent: record.inlineAgent } : {},
|
|
4665
|
-
task: record.task,
|
|
4666
|
-
cwd: record.cwd,
|
|
4667
|
-
...record.model ? { model: record.model } : {},
|
|
4668
|
-
sessionFile: record.sessionFile,
|
|
4669
|
-
...record.identity ? { identity: record.identity } : {}
|
|
4670
|
-
},
|
|
4671
|
-
cwd: record.cwd,
|
|
4672
|
-
agentScope: "both",
|
|
4673
|
-
sessionScope: scope,
|
|
4674
|
-
parentSessionId: ctx.sessionManager.getSessionId(),
|
|
4675
|
-
availableModels: authenticatedModelInfos(ctx.modelRegistry),
|
|
4676
|
-
...operationId ? { operationId } : {},
|
|
4677
|
-
...preallocatedRunIds ? { preallocatedRunIds } : {}
|
|
4678
|
-
}, loadConfig().config), ctx).outcomes[0];
|
|
4679
|
-
if (!outcome?.runId) throw new DoomTeamExpectedError("not_resumable", `Could not restore '${params.id}': ${outcome?.error ?? "the spawn produced no run."}`, true, "Inspect suspended state, then submit a new explicit run if recovery is not possible.");
|
|
4680
|
-
clearSuspendedRun(scope, params.id);
|
|
4681
|
-
const restoredAs = formatAgentIdentity(outcome.identity, outcome.inline) ?? outcome.runId;
|
|
4682
|
-
return textResult(`Restored '${params.id}' as '${restoredAs}' (${outcome.runId}), continuing its transcript.`, { restore: {
|
|
4683
|
-
...outcome,
|
|
4684
|
-
restoredFrom: params.id
|
|
4685
|
-
} });
|
|
4686
|
-
}
|
|
4687
4819
|
}
|
|
4688
4820
|
}
|
|
4689
4821
|
async execute(id, rawParams, signal, onUpdate, ctx) {
|
|
@@ -4692,22 +4824,22 @@ var SubagentToolService = class {
|
|
|
4692
4824
|
if (progress) onUpdate?.(progressResult(progress, params.action));
|
|
4693
4825
|
if (params.action === SUBAGENT_ACTIONS.run) return this.executeRun(id, params, ctx);
|
|
4694
4826
|
if (params.action === SUBAGENT_ACTIONS.agents) return this.executeAgents(params, ctx);
|
|
4695
|
-
if (!(params.action === SUBAGENT_ACTIONS.steer || params.action === SUBAGENT_ACTIONS.stop
|
|
4827
|
+
if (!(params.action === SUBAGENT_ACTIONS.steer || params.action === SUBAGENT_ACTIONS.stop)) return this.executeManagement(params, ctx, signal);
|
|
4696
4828
|
const scope = this.sessionScope(ctx);
|
|
4697
4829
|
this.management.bindSessionScope(scope);
|
|
4698
|
-
const operation = startOperation(scope, id, params,
|
|
4830
|
+
const operation = startOperation(scope, id, params, []);
|
|
4699
4831
|
if (operation.kind === "replay") {
|
|
4700
4832
|
if (operation.record.state === "completed" && operation.record.result) return operation.record.result;
|
|
4701
|
-
throw new
|
|
4833
|
+
throw new PoTeamExpectedError("delivery_unconfirmed", `Operation '${id}' is already pending.`, true, "Inspect run status before retrying this control operation.");
|
|
4702
4834
|
}
|
|
4703
|
-
const response = await this.executeManagement(params, ctx, signal
|
|
4835
|
+
const response = await this.executeManagement(params, ctx, signal);
|
|
4704
4836
|
completeOperation(scope, id, operation.record, response);
|
|
4705
4837
|
return response;
|
|
4706
4838
|
}
|
|
4707
4839
|
};
|
|
4708
4840
|
//#endregion
|
|
4709
4841
|
//#region src/services/headlessSubagentTool/index.ts
|
|
4710
|
-
const SUBAGENT_DESCRIPTION = "Discover agents, start and manage persistent background subagent runs
|
|
4842
|
+
const SUBAGENT_DESCRIPTION = "Discover agents, start and manage persistent background subagent runs.";
|
|
4711
4843
|
/**
|
|
4712
4844
|
* Every result carries the text the MODEL reads and the structured details the
|
|
4713
4845
|
* cockpit card reads. The text is always given explicitly: a serialized detail
|
|
@@ -4750,18 +4882,17 @@ function createHeadlessSubagentTool(runtime, initialExecution) {
|
|
|
4750
4882
|
label: "Subagent",
|
|
4751
4883
|
description: SUBAGENT_DESCRIPTION,
|
|
4752
4884
|
parameters: SubagentToolSchema,
|
|
4753
|
-
promptSnippet: "Discover, run, inspect, steer,
|
|
4754
|
-
promptGuidelines: ["Runs are persistent background work. Do not resubmit a run that already returned an id.", "Use status
|
|
4885
|
+
promptSnippet: "Discover, run, inspect, steer, and stop persistent subagents",
|
|
4886
|
+
promptGuidelines: ["Runs are persistent background work. Do not resubmit a run that already returned an id.", "Use status before retrying a failed or interrupted operation."],
|
|
4755
4887
|
executionMode: "serial",
|
|
4756
4888
|
async execute(_toolCallId, parameters, signal, onUpdate, execution = initialExecution) {
|
|
4757
4889
|
try {
|
|
4758
4890
|
const params = validateParams(parameters);
|
|
4759
|
-
const availableModels = execution.getAvailableModels?.().map(toModelInfo) ?? (execution.model ? [toModelInfo(execution.model)] : []);
|
|
4760
4891
|
if (params.action === SUBAGENT_ACTIONS.agents) {
|
|
4761
4892
|
const scope = scopeOf(params.scope);
|
|
4762
|
-
const agents = runtime.discovery.discover(params.cwd ?? execution.cwd, scope).agents;
|
|
4763
|
-
const selected = params.name ? runtime.discovery.find(params.cwd ?? execution.cwd, scope, params.name) : void 0;
|
|
4764
|
-
if (params.name && !selected) throw new
|
|
4893
|
+
const agents = (await runtime.discovery.discover(params.cwd ?? execution.cwd, scope)).agents;
|
|
4894
|
+
const selected = params.name ? await runtime.discovery.find(params.cwd ?? execution.cwd, scope, params.name) : void 0;
|
|
4895
|
+
if (params.name && !selected) throw new PoTeamExpectedError("agent_not_found", `No executable agent matches '${params.name}'.`, false, "Call subagent({\"action\":\"agents\"}) and retry with an exact agent name.");
|
|
4765
4896
|
if (selected) return result(formatAgentDetail(selected), { agents: [publicAgent(selected)] });
|
|
4766
4897
|
return result(formatAgentList(agents), { agents: agents.map((agent) => ({
|
|
4767
4898
|
name: agent.name,
|
|
@@ -4790,9 +4921,10 @@ function createHeadlessSubagentTool(runtime, initialExecution) {
|
|
|
4790
4921
|
sessionScope: createSessionScope(execution.sessionId),
|
|
4791
4922
|
environment: execution.environment,
|
|
4792
4923
|
parentSessionId: execution.sessionId,
|
|
4924
|
+
parentSelection: execution.selection,
|
|
4793
4925
|
...params.concurrency === void 0 ? {} : { concurrency: params.concurrency },
|
|
4794
4926
|
...params.artifacts === void 0 ? {} : { artifacts: params.artifacts },
|
|
4795
|
-
availableModels,
|
|
4927
|
+
availableModels: await availableModelInfos(execution),
|
|
4796
4928
|
...execution.model ? { parentModel: execution.model } : {},
|
|
4797
4929
|
...parentForkSource ? { parentForkSource } : {}
|
|
4798
4930
|
}, loadConfig().config);
|
|
@@ -4808,19 +4940,13 @@ function createHeadlessSubagentTool(runtime, initialExecution) {
|
|
|
4808
4940
|
return result(formatFleetView(runs), { runs });
|
|
4809
4941
|
}
|
|
4810
4942
|
const status = runtime.management.statusAsync ? await runtime.management.statusAsync(params.id) : runtime.management.status(params.id);
|
|
4811
|
-
|
|
4943
|
+
const statusText = status.status ? [`Run '${status.runId}': ${status.status.state}`, status.status.summary ? `Summary: ${status.status.summary}` : void 0].filter((line) => line !== void 0).join("\n") : `Run '${status.runId}' has no status yet.`;
|
|
4944
|
+
const nativeEntries = params.transcriptLines === void 0 || status.status?.transcriptPath !== void 0 ? void 0 : await runtime.nativeRuns.readTranscript(execution.sessionId, status.runId, params.transcriptLines);
|
|
4945
|
+
return result([statusText, params.transcriptLines === void 0 ? void 0 : nativeEntries !== void 0 ? formatNativeRunTranscript(nativeEntries, params.transcriptLines) : formatRunTranscript(status.status, params.transcriptLines)].filter((text) => text !== void 0).join("\n\n"), {
|
|
4812
4946
|
runId: status.runId,
|
|
4813
4947
|
status: status.status
|
|
4814
4948
|
});
|
|
4815
4949
|
}
|
|
4816
|
-
if (params.action === SUBAGENT_ACTIONS.suspended) {
|
|
4817
|
-
const suspended = listSuspendedRuns(createSessionScope(execution.sessionId));
|
|
4818
|
-
const text = suspended.length ? formatSuspendedRuns(suspended) : "No suspended subagents in this session.";
|
|
4819
|
-
return result(text, {
|
|
4820
|
-
suspended,
|
|
4821
|
-
text
|
|
4822
|
-
});
|
|
4823
|
-
}
|
|
4824
4950
|
const id = resolveTrackedRunId(jobs, params.id);
|
|
4825
4951
|
if (params.action === SUBAGENT_ACTIONS.stop) {
|
|
4826
4952
|
const control = await runtime.management.stop(id, params.reason);
|
|
@@ -4829,40 +4955,14 @@ function createHeadlessSubagentTool(runtime, initialExecution) {
|
|
|
4829
4955
|
control
|
|
4830
4956
|
});
|
|
4831
4957
|
}
|
|
4832
|
-
|
|
4833
|
-
|
|
4834
|
-
|
|
4835
|
-
|
|
4836
|
-
|
|
4837
|
-
|
|
4838
|
-
|
|
4839
|
-
|
|
4840
|
-
if (!suspended) throw new Error(`No suspended run matches '${params.id}'.`);
|
|
4841
|
-
if (!isSuspendedRunResumable(suspended)) throw new Error(`Suspended run '${params.id}' is not resumable.`);
|
|
4842
|
-
const outcome = (await runtime.spawnPlanner.spawn({
|
|
4843
|
-
single: {
|
|
4844
|
-
agent: suspended.agent,
|
|
4845
|
-
...suspended.inlineAgent ? { inlineAgent: suspended.inlineAgent } : {},
|
|
4846
|
-
task: suspended.task,
|
|
4847
|
-
cwd: suspended.cwd,
|
|
4848
|
-
...suspended.model ? { model: suspended.model } : {},
|
|
4849
|
-
sessionFile: suspended.sessionFile,
|
|
4850
|
-
...suspended.identity ? { identity: suspended.identity } : {}
|
|
4851
|
-
},
|
|
4852
|
-
cwd: suspended.cwd,
|
|
4853
|
-
agentScope: "both",
|
|
4854
|
-
sessionScope: createSessionScope(execution.sessionId),
|
|
4855
|
-
environment: execution.environment,
|
|
4856
|
-
runtime: "pi",
|
|
4857
|
-
parentSessionId: execution.sessionId,
|
|
4858
|
-
availableModels
|
|
4859
|
-
}, loadConfig().config)).outcomes[0];
|
|
4860
|
-
if (!outcome?.runId) throw new Error(`Could not restore '${params.id}': ${outcome?.error ?? "the spawn produced no run."}`);
|
|
4861
|
-
clearSuspendedRun(createSessionScope(execution.sessionId), params.id);
|
|
4862
|
-
return result(`Restored '${params.id}' as '${outcome.runId}', continuing its transcript.`, { restore: {
|
|
4863
|
-
...outcome,
|
|
4864
|
-
restoredFrom: params.id
|
|
4865
|
-
} });
|
|
4958
|
+
const steer = await runtime.management.steer(id, {
|
|
4959
|
+
text: params.message,
|
|
4960
|
+
sender: MAIN_AGENT_SENDER
|
|
4961
|
+
}, void 0, signal);
|
|
4962
|
+
return result(`Steer request '${steer.requestId}' for '${id}' is ${steer.state}: ${steer.message}`, {
|
|
4963
|
+
runId: id,
|
|
4964
|
+
steer
|
|
4965
|
+
});
|
|
4866
4966
|
} catch (error) {
|
|
4867
4967
|
const message = error instanceof Error ? error.message : String(error);
|
|
4868
4968
|
return result(message, message, true);
|
|
@@ -4872,7 +4972,7 @@ function createHeadlessSubagentTool(runtime, initialExecution) {
|
|
|
4872
4972
|
}
|
|
4873
4973
|
//#endregion
|
|
4874
4974
|
//#region src/services/mcpTools/index.ts
|
|
4875
|
-
const TEAM_MCP_TOOLS_SERVICE = "
|
|
4975
|
+
const TEAM_MCP_TOOLS_SERVICE = "po/team-mcp-tools";
|
|
4876
4976
|
function mountMcpTools(tools) {
|
|
4877
4977
|
return (context) => {
|
|
4878
4978
|
context.plugin((providerContext) => {
|
|
@@ -4922,7 +5022,7 @@ function subscribeNativeRunProjection(sessionId, listener) {
|
|
|
4922
5022
|
if (closed) return;
|
|
4923
5023
|
closed = true;
|
|
4924
5024
|
projection.listeners.delete(listener);
|
|
4925
|
-
if (projection.listeners.size === 0 && projection.runs.size === 0) sessions.delete(sessionId);
|
|
5025
|
+
if (projection.listeners.size === 0 && projection.runs.size === 0 && sessions.get(sessionId) === projection) sessions.delete(sessionId);
|
|
4926
5026
|
};
|
|
4927
5027
|
}
|
|
4928
5028
|
//#endregion
|
|
@@ -5138,7 +5238,7 @@ function formatGroupedCompletion(details) {
|
|
|
5138
5238
|
*
|
|
5139
5239
|
* Separate from `formatSingleCompletion` because the two have different
|
|
5140
5240
|
* readers. That one is written for the model and embeds the run's full result
|
|
5141
|
-
* preview; this one goes to `
|
|
5241
|
+
* preview; this one goes to `PoHeadlessClient.notify`, which collapses every
|
|
5142
5242
|
* newline to a space and truncates at 4096 characters
|
|
5143
5243
|
* (`packages/core/po-core/src/schemas/notification.ts`), and on the web
|
|
5144
5244
|
* additionally drives a browser notification. Feeding the model-facing text to
|
|
@@ -5211,7 +5311,7 @@ function buildCompletionDetails(result) {
|
|
|
5211
5311
|
* The custom-message type the parent renders completions under. The literal is
|
|
5212
5312
|
* shared by the producer here and the TUI renderer registered for it
|
|
5213
5313
|
* (`(frontend)/message/_lib/subagent-notify.cli.ts`, which draws through
|
|
5214
|
-
* `completionNotice.ts`), and by the
|
|
5314
|
+
* `completionNotice.ts`), and by the server plugin, which persists the same
|
|
5215
5315
|
* type as a session entry so a cockpit transcript records what was delivered.
|
|
5216
5316
|
*/
|
|
5217
5317
|
const SUBAGENT_NOTIFY_MESSAGE_TYPE = "subagent-notify";
|
|
@@ -5337,41 +5437,149 @@ function active(job) {
|
|
|
5337
5437
|
"stopped"
|
|
5338
5438
|
])).has(job.status);
|
|
5339
5439
|
}
|
|
5340
|
-
|
|
5440
|
+
/**
|
|
5441
|
+
* Stops the live external runs of a session that ends. A native run is a conversation of the session's own
|
|
5442
|
+
* Harness and resumes with it, so it is left alone.
|
|
5443
|
+
*/
|
|
5444
|
+
async function stopScopeRuns(options) {
|
|
5445
|
+
const jobs = options.jobs.filter((job) => !job.native && active(job));
|
|
5446
|
+
const outcomes = await Promise.allSettled(jobs.map((job) => options.stop(job.runId, options.reason)));
|
|
5341
5447
|
return {
|
|
5342
|
-
|
|
5343
|
-
|
|
5344
|
-
...job.identity === void 0 ? {} : { identity: job.identity },
|
|
5345
|
-
...job.inline === void 0 ? {} : { inline: job.inline },
|
|
5346
|
-
runtime: job.runtime ?? (job.native ? "pi" : "external"),
|
|
5347
|
-
task: job.task ?? "",
|
|
5348
|
-
cwd: job.cwd ?? "",
|
|
5349
|
-
...job.status === void 0 ? {} : { lastStatus: job.status },
|
|
5350
|
-
...job.sessionFile === void 0 ? {} : { sessionFile: job.sessionFile },
|
|
5351
|
-
suspendedAt,
|
|
5352
|
-
reason
|
|
5448
|
+
stopped: jobs.map((job) => job.runId),
|
|
5449
|
+
unstoppable: jobs.filter((_, index) => outcomes[index]?.status === "rejected").map((job) => job.runId)
|
|
5353
5450
|
};
|
|
5354
5451
|
}
|
|
5355
|
-
|
|
5356
|
-
|
|
5357
|
-
|
|
5358
|
-
|
|
5359
|
-
|
|
5360
|
-
|
|
5452
|
+
//#endregion
|
|
5453
|
+
//#region src/schemas/team/packageConfig.ts
|
|
5454
|
+
const PO_TEAM_PACKAGE = "@agimon-ai/po-team";
|
|
5455
|
+
const TEAM_CONFIG_KEYS = ["models", "excludeTools"];
|
|
5456
|
+
const TEAM_MODEL_KEYS = ["model", "thinking"];
|
|
5457
|
+
const THINKING_VALUES = /* @__PURE__ */ new Set([
|
|
5458
|
+
"off",
|
|
5459
|
+
"minimal",
|
|
5460
|
+
"low",
|
|
5461
|
+
"medium",
|
|
5462
|
+
"high",
|
|
5463
|
+
"xhigh",
|
|
5464
|
+
"max"
|
|
5465
|
+
]);
|
|
5466
|
+
function isRecord$1(value) {
|
|
5467
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5468
|
+
}
|
|
5469
|
+
function assertKnownKeys(value, keys, location) {
|
|
5470
|
+
const unsupported = Object.keys(value).filter((key) => !keys.includes(key));
|
|
5471
|
+
if (unsupported.length > 0) throw new Error(`${location} has unsupported field(s): ${unsupported.join(", ")}`);
|
|
5472
|
+
}
|
|
5473
|
+
/** Validate and normalize one Team-owned package configuration mapping. */
|
|
5474
|
+
function parseTeamPackageConfig(value, location) {
|
|
5475
|
+
if (!isRecord$1(value)) throw new Error(`${location} must be a mapping`);
|
|
5476
|
+
assertKnownKeys(value, TEAM_CONFIG_KEYS, location);
|
|
5477
|
+
const hasModels = Object.hasOwn(value, "models");
|
|
5478
|
+
const hasExcludeTools = Object.hasOwn(value, "excludeTools");
|
|
5479
|
+
if (!hasModels && !hasExcludeTools) throw new Error(`${location} must define models or excludeTools`);
|
|
5480
|
+
let models;
|
|
5481
|
+
if (hasModels) {
|
|
5482
|
+
if (!Array.isArray(value.models) || value.models.length === 0) throw new Error(`${location}.models must be a non-empty array`);
|
|
5483
|
+
models = value.models.map((entry, index) => {
|
|
5484
|
+
const entryLocation = `${location}.models[${index}]`;
|
|
5485
|
+
if (!isRecord$1(entry)) throw new Error(`${entryLocation} must be a mapping`);
|
|
5486
|
+
assertKnownKeys(entry, TEAM_MODEL_KEYS, entryLocation);
|
|
5487
|
+
if (typeof entry.model !== "string" || !entry.model.trim()) throw new Error(`${entryLocation}.model must be a non-empty string`);
|
|
5488
|
+
if (entry.thinking !== void 0 && (typeof entry.thinking !== "string" || !THINKING_VALUES.has(entry.thinking))) throw new Error(`${entryLocation}.thinking must be one of: ${[...THINKING_VALUES].join(", ")}`);
|
|
5489
|
+
return {
|
|
5490
|
+
model: entry.model.trim(),
|
|
5491
|
+
...entry.thinking ? { thinking: entry.thinking } : {}
|
|
5492
|
+
};
|
|
5493
|
+
});
|
|
5494
|
+
}
|
|
5495
|
+
let excludeTools;
|
|
5496
|
+
if (hasExcludeTools) {
|
|
5497
|
+
if (!Array.isArray(value.excludeTools) || value.excludeTools.length === 0) throw new Error(`${location}.excludeTools must be a non-empty array`);
|
|
5498
|
+
excludeTools = [...new Set(value.excludeTools.map((entry, index) => {
|
|
5499
|
+
if (typeof entry !== "string" || !entry.trim()) throw new Error(`${location}.excludeTools[${index}] must be a non-empty string`);
|
|
5500
|
+
return entry.trim();
|
|
5501
|
+
}))];
|
|
5502
|
+
}
|
|
5361
5503
|
return {
|
|
5362
|
-
|
|
5363
|
-
|
|
5504
|
+
...models ? { models } : {},
|
|
5505
|
+
...excludeTools ? { excludeTools } : {}
|
|
5364
5506
|
};
|
|
5365
5507
|
}
|
|
5366
|
-
/**
|
|
5367
|
-
|
|
5508
|
+
/** Merge Team configurations contributed by selected layers in selection order. */
|
|
5509
|
+
function mergeTeamPackageConfigurations(entries) {
|
|
5510
|
+
let models;
|
|
5511
|
+
const excludeTools = /* @__PURE__ */ new Set();
|
|
5512
|
+
let hasExcludeTools = false;
|
|
5513
|
+
for (const entry of entries) {
|
|
5514
|
+
const location = `Package "${entry.specifier}" config in layer "${entry.layer}" of .po/modes.yaml`;
|
|
5515
|
+
const next = parseTeamPackageConfig(entry.config, location);
|
|
5516
|
+
if (next.models) models = next.models.map((model) => ({ ...model }));
|
|
5517
|
+
if (next.excludeTools) {
|
|
5518
|
+
hasExcludeTools = true;
|
|
5519
|
+
for (const tool of next.excludeTools) excludeTools.add(tool);
|
|
5520
|
+
}
|
|
5521
|
+
}
|
|
5522
|
+
if (!models && !hasExcludeTools) return void 0;
|
|
5368
5523
|
return {
|
|
5369
|
-
|
|
5370
|
-
|
|
5371
|
-
suspended: await listSuspendedRunsAsync(scope)
|
|
5524
|
+
...models ? { models } : {},
|
|
5525
|
+
...hasExcludeTools ? { excludeTools: [...excludeTools] } : {}
|
|
5372
5526
|
};
|
|
5373
5527
|
}
|
|
5374
5528
|
//#endregion
|
|
5529
|
+
//#region src/services/teamAgentConfig/index.ts
|
|
5530
|
+
const PO_TEAM_AGENT_CONFIG_SERVICE = "po/team-agent-config";
|
|
5531
|
+
/** Normalize Team's ordered models without duplicating a known thinking suffix. */
|
|
5532
|
+
function teamModelSpecs(config) {
|
|
5533
|
+
return config?.models?.map(({ model, thinking }) => thinking ? `${model.replace(/:(?:off|minimal|low|medium|high|xhigh|max)$/, "")}:${thinking}` : model);
|
|
5534
|
+
}
|
|
5535
|
+
/** Session-local Team policy, loaded once for each injected config snapshot. */
|
|
5536
|
+
var TeamAgentConfigService = class {
|
|
5537
|
+
io;
|
|
5538
|
+
snapshot;
|
|
5539
|
+
loads = /* @__PURE__ */ new WeakMap();
|
|
5540
|
+
constructor(io) {
|
|
5541
|
+
this.io = io;
|
|
5542
|
+
}
|
|
5543
|
+
bind(snapshot) {
|
|
5544
|
+
this.snapshot = snapshot;
|
|
5545
|
+
return () => {
|
|
5546
|
+
if (this.snapshot === snapshot) this.snapshot = void 0;
|
|
5547
|
+
};
|
|
5548
|
+
}
|
|
5549
|
+
current() {
|
|
5550
|
+
if (!this.snapshot) return Promise.reject(new PoTeamExpectedError("runtime_unavailable", "Team agent configuration is not loaded yet.", true, "Retry after the session configuration is ready."));
|
|
5551
|
+
const snapshot = this.snapshot();
|
|
5552
|
+
const cached = this.loads.get(snapshot);
|
|
5553
|
+
if (cached) return cached;
|
|
5554
|
+
const load = this.load(snapshot).catch((error) => {
|
|
5555
|
+
this.loads.delete(snapshot);
|
|
5556
|
+
throw error;
|
|
5557
|
+
});
|
|
5558
|
+
this.loads.set(snapshot, load);
|
|
5559
|
+
return load;
|
|
5560
|
+
}
|
|
5561
|
+
currentOrUndefined() {
|
|
5562
|
+
return this.snapshot ? this.current() : Promise.resolve(void 0);
|
|
5563
|
+
}
|
|
5564
|
+
async load(snapshot) {
|
|
5565
|
+
const { fs, homeDirectory, environment } = this.io;
|
|
5566
|
+
if (!snapshot.harness.root) return { planningSubagents: snapshot.settings.modes?.planning?.subagents };
|
|
5567
|
+
const modes = await readMajorModesConfig(fs, snapshot.harness.root, BACKGROUND_CONTEXT, {
|
|
5568
|
+
homeDirectory,
|
|
5569
|
+
environment
|
|
5570
|
+
});
|
|
5571
|
+
const configurations = await readPackageConfigurations(fs, modes, [...snapshot.harness.layers], PO_TEAM_PACKAGE, BACKGROUND_CONTEXT);
|
|
5572
|
+
const config = mergeTeamPackageConfigurations(configurations);
|
|
5573
|
+
return {
|
|
5574
|
+
models: teamModelSpecs(config),
|
|
5575
|
+
thinking: config?.models?.[0]?.thinking,
|
|
5576
|
+
excludeTools: config?.excludeTools,
|
|
5577
|
+
planningSubagents: snapshot.settings.modes?.planning?.subagents,
|
|
5578
|
+
...config ? { path: configurations.at(-1)?.filePath ?? path.join(snapshot.harness.root, ".po", "modes.yaml") } : {}
|
|
5579
|
+
};
|
|
5580
|
+
}
|
|
5581
|
+
};
|
|
5582
|
+
//#endregion
|
|
5375
5583
|
//#region src/services/directRunBackgroundWork/index.ts
|
|
5376
5584
|
const PROVIDER_NAME = "team-direct-runs";
|
|
5377
5585
|
/** Publish direct Team runs owned by exactly one Pi session. */
|
|
@@ -5399,7 +5607,7 @@ function assertPolicy(policy) {
|
|
|
5399
5607
|
/** Publishes Team's capability intersection store through a Cordis service. */
|
|
5400
5608
|
function createSubagentPolicyService(store) {
|
|
5401
5609
|
const service = {
|
|
5402
|
-
generation: `
|
|
5610
|
+
generation: `po-subagent-policy:${crypto.randomUUID()}`,
|
|
5403
5611
|
register(initialPolicy) {
|
|
5404
5612
|
assertPolicy(initialPolicy);
|
|
5405
5613
|
const owner = initialPolicy.owner;
|
|
@@ -5431,7 +5639,7 @@ function createSubagentPolicyService(store) {
|
|
|
5431
5639
|
//#region src/services/teamCollaboration/index.ts
|
|
5432
5640
|
/** Mount every Team-owned collaboration capability in one session fiber. */
|
|
5433
5641
|
function teamCollaborationPlugin(ctx, config) {
|
|
5434
|
-
const backgroundWork =
|
|
5642
|
+
const backgroundWork = readPoBackgroundWorkService(ctx);
|
|
5435
5643
|
if (backgroundWork === void 0) throw new Error("Team requires the Po Session foundation.");
|
|
5436
5644
|
const subagentPolicy = createSubagentPolicyService(config.policies);
|
|
5437
5645
|
const delegation = config.delegation.createService(ctx, config.session);
|
|
@@ -5441,28 +5649,28 @@ function teamCollaborationPlugin(ctx, config) {
|
|
|
5441
5649
|
cwd: config.session.cwd,
|
|
5442
5650
|
environment: config.session.environment
|
|
5443
5651
|
});
|
|
5444
|
-
ctx.provide(
|
|
5445
|
-
ctx.provide(
|
|
5446
|
-
ctx.provide(
|
|
5652
|
+
ctx.provide(PO_SUBAGENT_POLICY_SERVICE, subagentPolicy);
|
|
5653
|
+
ctx.provide(PO_DELEGATION_SERVICE, delegation);
|
|
5654
|
+
ctx.provide(PO_FABLE_PLAN_SERVICE, fablePlan);
|
|
5447
5655
|
registerDirectRunBackgroundWork(ctx, backgroundWork, config.session.sessionId, config.directRunTracker);
|
|
5448
5656
|
if (config.observeDelegation) {
|
|
5449
|
-
ctx.on(
|
|
5657
|
+
ctx.on(PO_DELEGATION_REQUESTED_EVENT, (event) => config.observeDelegation?.({
|
|
5450
5658
|
kind: "requested",
|
|
5451
5659
|
event
|
|
5452
5660
|
}));
|
|
5453
|
-
ctx.on(
|
|
5661
|
+
ctx.on(PO_DELEGATION_STARTED_EVENT, (event) => config.observeDelegation?.({
|
|
5454
5662
|
kind: "started",
|
|
5455
5663
|
event
|
|
5456
5664
|
}));
|
|
5457
|
-
ctx.on(
|
|
5665
|
+
ctx.on(PO_DELEGATION_UPDATED_EVENT, (event) => config.observeDelegation?.({
|
|
5458
5666
|
kind: "updated",
|
|
5459
5667
|
event
|
|
5460
5668
|
}));
|
|
5461
|
-
ctx.on(
|
|
5669
|
+
ctx.on(PO_DELEGATION_FINISHED_EVENT, (event) => config.observeDelegation?.({
|
|
5462
5670
|
kind: "finished",
|
|
5463
5671
|
event
|
|
5464
5672
|
}));
|
|
5465
|
-
ctx.on(
|
|
5673
|
+
ctx.on(PO_DELEGATION_CANCELLED_EVENT, (event) => config.observeDelegation?.({
|
|
5466
5674
|
kind: "cancelled",
|
|
5467
5675
|
event
|
|
5468
5676
|
}));
|
|
@@ -5533,11 +5741,11 @@ var ManagementActions = class {
|
|
|
5533
5741
|
if (exact) return exact;
|
|
5534
5742
|
const matches = runs.filter((run) => run.runId.startsWith(id));
|
|
5535
5743
|
if (matches.length === 1) return matches[0];
|
|
5536
|
-
if (matches.length > 1) throw new
|
|
5744
|
+
if (matches.length > 1) throw new PoTeamExpectedError("run_not_found", `Multiple current-session runs match '${id}'.`, false, "Retry with a longer run id.");
|
|
5537
5745
|
}
|
|
5538
5746
|
requireActiveJob(id) {
|
|
5539
5747
|
const job = this.resolveJob(id);
|
|
5540
|
-
if (!job) throw new
|
|
5748
|
+
if (!job) throw new PoTeamExpectedError("run_not_found", `No active run matches '${id}'.`, false, "Call subagent({\"action\":\"status\"}) and retry with an exact run id.");
|
|
5541
5749
|
return job;
|
|
5542
5750
|
}
|
|
5543
5751
|
status(id) {
|
|
@@ -5551,7 +5759,20 @@ var ManagementActions = class {
|
|
|
5551
5759
|
};
|
|
5552
5760
|
}
|
|
5553
5761
|
async statusAsync(id) {
|
|
5554
|
-
|
|
5762
|
+
const live = this.status(id);
|
|
5763
|
+
if (live.status || !this.nativeRuns?.reported) return live;
|
|
5764
|
+
const run = await this.nativeRuns.reported(id);
|
|
5765
|
+
if (!run?.state) return live;
|
|
5766
|
+
return {
|
|
5767
|
+
...live,
|
|
5768
|
+
status: statusProjection({
|
|
5769
|
+
runId: id,
|
|
5770
|
+
agent: run.agent,
|
|
5771
|
+
task: run.task,
|
|
5772
|
+
status: run.state,
|
|
5773
|
+
native: true
|
|
5774
|
+
})
|
|
5775
|
+
};
|
|
5555
5776
|
}
|
|
5556
5777
|
list() {
|
|
5557
5778
|
return { runs: this.sessionJobs().list() };
|
|
@@ -5567,11 +5788,11 @@ var ManagementActions = class {
|
|
|
5567
5788
|
const job = this.requireActiveJob(id);
|
|
5568
5789
|
const requestId = randomUUID();
|
|
5569
5790
|
if (job.native) {
|
|
5570
|
-
if (!this.nativeRuns?.get(scope.rootSessionId, job.runId)) throw new
|
|
5791
|
+
if (!this.nativeRuns?.get(scope.rootSessionId, job.runId)) throw new PoTeamExpectedError("run_not_found", `Native run '${job.runId}' is no longer active.`, false, "List active runs and retry with a current run id.");
|
|
5571
5792
|
await this.nativeRuns.stop(scope.rootSessionId, job.runId, reason);
|
|
5572
5793
|
return { requestId };
|
|
5573
5794
|
}
|
|
5574
|
-
if (!this.externalProcesses?.has(scope, job.runId)) throw new
|
|
5795
|
+
if (!this.externalProcesses?.has(scope, job.runId)) throw new PoTeamExpectedError("run_not_found", `External run '${job.runId}' is no longer active.`, false, "List active runs and retry with a current run id.");
|
|
5575
5796
|
await this.externalProcesses.control(scope, job.runId, {
|
|
5576
5797
|
command,
|
|
5577
5798
|
requestId,
|
|
@@ -5585,7 +5806,7 @@ var ManagementActions = class {
|
|
|
5585
5806
|
const requestId = randomUUID();
|
|
5586
5807
|
const index = targetIndex ?? 0;
|
|
5587
5808
|
if (job.native) {
|
|
5588
|
-
if (!this.nativeRuns?.get(scope.rootSessionId, job.runId)) throw new
|
|
5809
|
+
if (!this.nativeRuns?.get(scope.rootSessionId, job.runId)) throw new PoTeamExpectedError("run_not_found", `Native run '${job.runId}' is no longer active.`, false, "List active runs and retry with a current run id.");
|
|
5589
5810
|
await this.nativeRuns.steer(scope.rootSessionId, job.runId, message, signal);
|
|
5590
5811
|
return {
|
|
5591
5812
|
requestId,
|
|
@@ -5594,11 +5815,11 @@ var ManagementActions = class {
|
|
|
5594
5815
|
message: "Native child accepted the steer request."
|
|
5595
5816
|
};
|
|
5596
5817
|
}
|
|
5597
|
-
if (!this.externalProcesses?.has(scope, job.runId)) throw new
|
|
5818
|
+
if (!this.externalProcesses?.has(scope, job.runId)) throw new PoTeamExpectedError("run_not_found", `External run '${job.runId}' is no longer active.`, false, "List active runs and retry with a current run id.");
|
|
5598
5819
|
const acknowledgement = await this.externalProcesses.control(scope, job.runId, {
|
|
5599
5820
|
command: "steer",
|
|
5600
5821
|
requestId,
|
|
5601
|
-
message,
|
|
5822
|
+
message: typeof message === "string" ? message : message.text,
|
|
5602
5823
|
targetIndex: index
|
|
5603
5824
|
});
|
|
5604
5825
|
if (!acknowledgement) return {
|
|
@@ -5645,140 +5866,6 @@ var McpDirectToolResolverBinding = class {
|
|
|
5645
5866
|
}
|
|
5646
5867
|
};
|
|
5647
5868
|
//#endregion
|
|
5648
|
-
//#region src/services/nativeRunCoordinator/index.ts
|
|
5649
|
-
function terminalState(event) {
|
|
5650
|
-
return event.state === "completed" || event.state === "failed" || event.state === "stopped" ? event.state : void 0;
|
|
5651
|
-
}
|
|
5652
|
-
function entryKey(sessionId, runId) {
|
|
5653
|
-
return JSON.stringify([sessionId, runId]);
|
|
5654
|
-
}
|
|
5655
|
-
var NativeRunCoordinator = class {
|
|
5656
|
-
childSessions;
|
|
5657
|
-
tracker;
|
|
5658
|
-
notifier;
|
|
5659
|
-
projection;
|
|
5660
|
-
entries = /* @__PURE__ */ new Map();
|
|
5661
|
-
closed = false;
|
|
5662
|
-
closing;
|
|
5663
|
-
constructor(childSessions, tracker, notifier, projection) {
|
|
5664
|
-
this.childSessions = childSessions;
|
|
5665
|
-
this.tracker = tracker;
|
|
5666
|
-
this.notifier = notifier;
|
|
5667
|
-
this.projection = projection;
|
|
5668
|
-
}
|
|
5669
|
-
async start(sessionId, request, identity) {
|
|
5670
|
-
if (this.closed) throw new Error("The native Team run coordinator is closed.");
|
|
5671
|
-
if (!sessionId.trim()) throw new Error("Native child runs require a session identity.");
|
|
5672
|
-
const service = this.childSessions?.get();
|
|
5673
|
-
if (!service) throw new Error("The in-process Pi child-session service is unavailable.");
|
|
5674
|
-
const handle = await service.start(request);
|
|
5675
|
-
if (this.closed) {
|
|
5676
|
-
await handle.dispose();
|
|
5677
|
-
throw new Error("The native Team run coordinator closed during child startup.");
|
|
5678
|
-
}
|
|
5679
|
-
const entry = {
|
|
5680
|
-
sessionId,
|
|
5681
|
-
scope: request.scope,
|
|
5682
|
-
request,
|
|
5683
|
-
...identity ? { identity } : {},
|
|
5684
|
-
handle,
|
|
5685
|
-
terminal: false
|
|
5686
|
-
};
|
|
5687
|
-
const key = entryKey(sessionId, request.runId);
|
|
5688
|
-
this.entries.set(key, entry);
|
|
5689
|
-
try {
|
|
5690
|
-
const unsubscribe = handle.subscribe((event) => this.onEvent(entry, event));
|
|
5691
|
-
if (entry.terminal) unsubscribe();
|
|
5692
|
-
else entry.unsubscribe = unsubscribe;
|
|
5693
|
-
return handle;
|
|
5694
|
-
} catch (error) {
|
|
5695
|
-
this.entries.delete(key);
|
|
5696
|
-
await handle.dispose().catch(() => void 0);
|
|
5697
|
-
throw error;
|
|
5698
|
-
}
|
|
5699
|
-
}
|
|
5700
|
-
get(sessionId, runId) {
|
|
5701
|
-
return this.entries.get(entryKey(sessionId, runId))?.handle;
|
|
5702
|
-
}
|
|
5703
|
-
status(sessionId, runId) {
|
|
5704
|
-
return this.tracker.getNative(sessionId, runId);
|
|
5705
|
-
}
|
|
5706
|
-
async steer(sessionId, runId, message, signal) {
|
|
5707
|
-
const handle = this.get(sessionId, runId);
|
|
5708
|
-
if (!handle) throw new Error(`No active native run matches '${runId}'.`);
|
|
5709
|
-
await handle.steer(message, signal);
|
|
5710
|
-
}
|
|
5711
|
-
async stop(sessionId, runId, reason) {
|
|
5712
|
-
const handle = this.get(sessionId, runId);
|
|
5713
|
-
if (!handle) throw new Error(`No active native run matches '${runId}'.`);
|
|
5714
|
-
await handle.stop(reason);
|
|
5715
|
-
}
|
|
5716
|
-
async close() {
|
|
5717
|
-
this.closed = true;
|
|
5718
|
-
this.closing ??= (async () => {
|
|
5719
|
-
const active = [...this.entries.values()];
|
|
5720
|
-
const outcomes = await Promise.allSettled(active.map((entry) => entry.handle.dispose()));
|
|
5721
|
-
for (const entry of active) {
|
|
5722
|
-
entry.unsubscribe?.();
|
|
5723
|
-
this.entries.delete(entryKey(entry.sessionId, entry.request.runId));
|
|
5724
|
-
}
|
|
5725
|
-
const failures = outcomes.filter((outcome) => outcome.status === "rejected").map((outcome) => outcome.reason);
|
|
5726
|
-
if (failures.length) throw new AggregateError(failures, "Native Team run shutdown failed.");
|
|
5727
|
-
})();
|
|
5728
|
-
return this.closing;
|
|
5729
|
-
}
|
|
5730
|
-
onEvent(entry, event) {
|
|
5731
|
-
entry.startedAt ??= event.timestamp;
|
|
5732
|
-
const status = terminalState(event);
|
|
5733
|
-
const projection = {
|
|
5734
|
-
runId: event.runId,
|
|
5735
|
-
agent: entry.request.agent,
|
|
5736
|
-
...entry.identity ? {
|
|
5737
|
-
identity: entry.identity.identity,
|
|
5738
|
-
inline: entry.identity.inline
|
|
5739
|
-
} : {},
|
|
5740
|
-
task: entry.request.task,
|
|
5741
|
-
cwd: entry.request.cwd,
|
|
5742
|
-
runtime: "pi",
|
|
5743
|
-
status: event.state === "starting" ? "queued" : event.state === "running" ? "running" : event.state,
|
|
5744
|
-
startedAt: entry.startedAt,
|
|
5745
|
-
updatedAt: event.timestamp,
|
|
5746
|
-
...event.cost === void 0 ? {} : { cost: event.cost },
|
|
5747
|
-
...event.sessionFile ? { sessionFile: event.sessionFile } : {},
|
|
5748
|
-
...event.message ? {
|
|
5749
|
-
error: status === "failed" || status === "stopped" ? event.message : void 0,
|
|
5750
|
-
summary: event.message
|
|
5751
|
-
} : {}
|
|
5752
|
-
};
|
|
5753
|
-
this.tracker.upsertNative(entry.sessionId, entry.scope, projection);
|
|
5754
|
-
this.projection?.publish(entry.sessionId, projection);
|
|
5755
|
-
if (!status || entry.terminal) return;
|
|
5756
|
-
entry.terminal = true;
|
|
5757
|
-
entry.unsubscribe?.();
|
|
5758
|
-
this.entries.delete(entryKey(entry.sessionId, entry.request.runId));
|
|
5759
|
-
const result = {
|
|
5760
|
-
runId: entry.request.runId,
|
|
5761
|
-
agent: entry.request.agent,
|
|
5762
|
-
...entry.identity ? {
|
|
5763
|
-
identity: entry.identity.identity,
|
|
5764
|
-
inline: entry.identity.inline
|
|
5765
|
-
} : {},
|
|
5766
|
-
task: entry.request.task,
|
|
5767
|
-
cwd: entry.request.cwd,
|
|
5768
|
-
runtime: "pi",
|
|
5769
|
-
state: status,
|
|
5770
|
-
success: status === "completed",
|
|
5771
|
-
summary: event.message ?? (status === "completed" ? "Native child run completed." : `Native child run ${status}.`),
|
|
5772
|
-
durationMs: Math.max(0, event.timestamp - (this.tracker.getNative(entry.sessionId, entry.request.runId)?.startedAt ?? event.timestamp)),
|
|
5773
|
-
sessionId: entry.sessionId,
|
|
5774
|
-
...event.sessionFile ? { sessionFile: event.sessionFile } : {}
|
|
5775
|
-
};
|
|
5776
|
-
if (this.notifier) this.notifier.deliver(result).then((delivered) => {
|
|
5777
|
-
if (delivered) this.tracker.acknowledgeHandoff(entry.sessionId, entry.request.runId);
|
|
5778
|
-
}).catch(() => void 0);
|
|
5779
|
-
}
|
|
5780
|
-
};
|
|
5781
|
-
//#endregion
|
|
5782
5869
|
//#region src/services/pollScheduler/index.ts
|
|
5783
5870
|
/**
|
|
5784
5871
|
* One master tick that drives every background subscriber in the runner
|
|
@@ -6094,14 +6181,14 @@ function createTeamExtensionRuntime(reportConcurrencyEvent, options) {
|
|
|
6094
6181
|
const subagentWaiter = new SubagentWaiter(asyncJobTracker);
|
|
6095
6182
|
const externalProcesses = new ExternalProcessIpc();
|
|
6096
6183
|
const asyncSubagentSpawner = new AsyncSubagentSpawner(externalProcesses);
|
|
6097
|
-
const discovery = new AgentDiscoveryService(options.
|
|
6184
|
+
const discovery = new AgentDiscoveryService(options.discoveryIo, options.agentConfig);
|
|
6098
6185
|
const skills = new SkillDiscoveryService();
|
|
6099
6186
|
const capabilityPolicies = new SubagentCapabilityPolicyStore();
|
|
6100
6187
|
const mcpToolResolver = new McpDirectToolResolverBinding();
|
|
6101
6188
|
const completionNotifier = new CompletionNotifier();
|
|
6102
6189
|
const nativeRuns = new NativeRunCoordinator(options.childSessions, asyncJobTracker, completionNotifier, options.nativeRunProjection);
|
|
6103
6190
|
const teamChannel = new NativeTeamChannelService();
|
|
6104
|
-
const spawnPlanner = new SpawnPlanner(discovery, asyncSubagentSpawner, capabilityPolicies, skills, report, mcpToolResolver, admission, options.childSessions, nativeRuns, teamChannel, options.environment);
|
|
6191
|
+
const spawnPlanner = new SpawnPlanner(discovery, asyncSubagentSpawner, capabilityPolicies, skills, report, mcpToolResolver, admission, options.childSessions, nativeRuns, teamChannel, options.environment, options.agentConfig);
|
|
6105
6192
|
const management = new ManagementActions(asyncJobTracker, nativeRuns, externalProcesses);
|
|
6106
6193
|
const onExternalEvent = (event) => {
|
|
6107
6194
|
const sessionId = event.scope.rootSessionId;
|
|
@@ -6158,6 +6245,7 @@ function createTeamExtensionRuntime(reportConcurrencyEvent, options) {
|
|
|
6158
6245
|
subagentWaiter,
|
|
6159
6246
|
asyncSubagentSpawner,
|
|
6160
6247
|
discovery,
|
|
6248
|
+
agentConfig: options.agentConfig,
|
|
6161
6249
|
skills,
|
|
6162
6250
|
spawnPlanner,
|
|
6163
6251
|
completionNotifier,
|
|
@@ -6239,12 +6327,16 @@ function presentTrackedRun(run) {
|
|
|
6239
6327
|
state: runState(run.status),
|
|
6240
6328
|
rawState: run.status,
|
|
6241
6329
|
task: run.task ?? "",
|
|
6330
|
+
...run.promptWithheld ? { promptWithheld: true } : {},
|
|
6242
6331
|
cwd: run.cwd ?? "",
|
|
6243
6332
|
startedAt: run.startedAt,
|
|
6244
6333
|
...TERMINAL_RUN_STATES.has(run.status) ? { endedAt: lastUpdate } : {},
|
|
6245
6334
|
lastUpdate,
|
|
6246
6335
|
tail: [],
|
|
6247
|
-
|
|
6336
|
+
canSteer: false,
|
|
6337
|
+
...run.tokens === void 0 ? {} : { tokens: run.tokens },
|
|
6338
|
+
...run.toolCount === void 0 ? {} : { toolCount: run.toolCount },
|
|
6339
|
+
...run.cost === void 0 ? {} : { cost: run.cost },
|
|
6248
6340
|
...run.summary === void 0 ? {} : { summary: run.summary },
|
|
6249
6341
|
...run.error === void 0 ? {} : { error: run.error }
|
|
6250
6342
|
};
|
|
@@ -6255,6 +6347,8 @@ function presentNativeRun(run) {
|
|
|
6255
6347
|
agent: run.agent,
|
|
6256
6348
|
...run.identity === void 0 ? {} : { identity: run.identity },
|
|
6257
6349
|
...run.inline === void 0 ? {} : { inline: run.inline },
|
|
6350
|
+
...run.agentDescription === void 0 ? {} : { agentDescription: run.agentDescription },
|
|
6351
|
+
...run.agentFilePath === void 0 ? {} : { agentFilePath: run.agentFilePath },
|
|
6258
6352
|
state: runState(run.status),
|
|
6259
6353
|
rawState: run.status,
|
|
6260
6354
|
task: run.task,
|
|
@@ -6263,7 +6357,14 @@ function presentNativeRun(run) {
|
|
|
6263
6357
|
...TERMINAL_RUN_STATES.has(run.status) ? { endedAt: run.updatedAt } : {},
|
|
6264
6358
|
lastUpdate: run.updatedAt,
|
|
6265
6359
|
tail: [],
|
|
6266
|
-
...run.
|
|
6360
|
+
...run.model === void 0 ? {} : {
|
|
6361
|
+
model: run.model,
|
|
6362
|
+
modelSource: "runtime"
|
|
6363
|
+
},
|
|
6364
|
+
...run.thinking === void 0 ? {} : { thinking: run.thinking },
|
|
6365
|
+
...run.tokens === void 0 ? {} : { tokens: run.tokens },
|
|
6366
|
+
...run.toolCount === void 0 ? {} : { toolCount: run.toolCount },
|
|
6367
|
+
...run.cost === void 0 ? {} : { cost: run.cost },
|
|
6267
6368
|
...run.summary === void 0 ? {} : { summary: run.summary },
|
|
6268
6369
|
...run.error === void 0 ? {} : { error: run.error }
|
|
6269
6370
|
};
|
|
@@ -6273,7 +6374,7 @@ function publishRuns(directEvents, sessionId, jobs, nativeRuns) {
|
|
|
6273
6374
|
runs.push(...[...nativeRuns.values()].map(presentNativeRun));
|
|
6274
6375
|
directEvents.publish(SUBAGENT_RUNS_TYPE, sessionId, { runs });
|
|
6275
6376
|
}
|
|
6276
|
-
const TEAM_COST_STATUS = "
|
|
6377
|
+
const TEAM_COST_STATUS = "po-team-cost";
|
|
6277
6378
|
const COST_DECIMALS = 6;
|
|
6278
6379
|
function updateTeamCost(client, jobs, nativeRuns, costs, previousText, attached, force = false) {
|
|
6279
6380
|
const trackedCosts = /* @__PURE__ */ new Map();
|
|
@@ -6293,20 +6394,20 @@ function updateTeamCost(client, jobs, nativeRuns, costs, previousText, attached,
|
|
|
6293
6394
|
function recordDelegationObservation(telemetry, observation) {
|
|
6294
6395
|
if (observation.kind === "requested") {
|
|
6295
6396
|
const { event } = observation;
|
|
6296
|
-
telemetry.recordEvent("
|
|
6397
|
+
telemetry.recordEvent("po_team.delegation_requested", {
|
|
6297
6398
|
"agent.name": event.agent,
|
|
6298
6399
|
"task.id": event.taskId,
|
|
6299
6400
|
mode: event.runMode ?? "foreground"
|
|
6300
6401
|
});
|
|
6301
6402
|
} else if (observation.kind === "started") {
|
|
6302
6403
|
const { event } = observation;
|
|
6303
|
-
telemetry.recordEvent("
|
|
6404
|
+
telemetry.recordEvent("po_team.child_started", {
|
|
6304
6405
|
"run.id": event.runId,
|
|
6305
6406
|
outcome: "started"
|
|
6306
6407
|
});
|
|
6307
6408
|
} else if (observation.kind === "updated") {
|
|
6308
6409
|
const { event } = observation;
|
|
6309
|
-
telemetry.recordEvent("
|
|
6410
|
+
telemetry.recordEvent("po_team.delegation_updated", {
|
|
6310
6411
|
"run.id": event.runId,
|
|
6311
6412
|
"team.tool_count": event.toolCount ?? 0,
|
|
6312
6413
|
"team.duration_ms": event.durationMs ?? 0,
|
|
@@ -6315,40 +6416,58 @@ function recordDelegationObservation(telemetry, observation) {
|
|
|
6315
6416
|
});
|
|
6316
6417
|
} else if (observation.kind === "finished") {
|
|
6317
6418
|
const { event } = observation;
|
|
6318
|
-
telemetry.recordEvent("
|
|
6419
|
+
telemetry.recordEvent("po_team.delegation_finished", {
|
|
6319
6420
|
"run.id": event.runId,
|
|
6320
6421
|
"team.tool_count": event.toolCount ?? 0,
|
|
6321
6422
|
"team.duration_ms": event.durationMs ?? 0,
|
|
6322
6423
|
outcome: event.status
|
|
6323
6424
|
});
|
|
6324
|
-
} else telemetry.recordEvent("
|
|
6425
|
+
} else telemetry.recordEvent("po_team.delegation_cancelled", { outcome: "cancelled" });
|
|
6325
6426
|
}
|
|
6326
6427
|
const root = defineRoot(({ context, host: serverHost, agent: host }) => {
|
|
6327
6428
|
if (!host) throw new Error("Team session requires the headless host.");
|
|
6328
|
-
if (serverHost.context.environment === void 0) throw new Error("Team
|
|
6429
|
+
if (serverHost.context.environment === void 0) throw new Error("Team server plugin requires an admitted session environment.");
|
|
6329
6430
|
const directEvents = serverHost.context.directEvents;
|
|
6330
|
-
if (directEvents === void 0) throw new Error("Team
|
|
6431
|
+
if (directEvents === void 0) throw new Error("Team server plugin requires host-owned direct events.");
|
|
6331
6432
|
const execution = {
|
|
6332
6433
|
...host.context,
|
|
6333
6434
|
get model() {
|
|
6334
6435
|
return host.context.model;
|
|
6436
|
+
},
|
|
6437
|
+
get selection() {
|
|
6438
|
+
return host.context.selection;
|
|
6335
6439
|
}
|
|
6336
6440
|
};
|
|
6337
6441
|
const sessionPeers = /* @__PURE__ */ new Set();
|
|
6338
6442
|
const stopPeerDiscovery = serverHost.context.sessionCommunication?.onPeerReady((id) => sessionPeers.add(id));
|
|
6339
|
-
const telemetry =
|
|
6340
|
-
serviceName: "
|
|
6443
|
+
const telemetry = createPoTelemetry({
|
|
6444
|
+
serviceName: "po-team",
|
|
6341
6445
|
packageName: "@agimon-ai/po-team",
|
|
6342
6446
|
cwd: execution.cwd,
|
|
6343
6447
|
env: serverHost.context.environment,
|
|
6344
6448
|
enableLogs: true,
|
|
6345
6449
|
enableTraces: true
|
|
6346
6450
|
});
|
|
6451
|
+
const environment = serverHost.context.environment;
|
|
6452
|
+
const fs = requirePoFs(context);
|
|
6453
|
+
const homeDirectory = environment.HOME ?? nodeHost.homeDirectory();
|
|
6347
6454
|
const runtime = createTeamExtensionRuntime((event, attributes) => {
|
|
6348
6455
|
telemetry.recordEvent(event, attributes);
|
|
6349
6456
|
}, {
|
|
6350
|
-
environment
|
|
6351
|
-
|
|
6457
|
+
environment,
|
|
6458
|
+
discoveryIo: {
|
|
6459
|
+
fs,
|
|
6460
|
+
context: BACKGROUND_CONTEXT,
|
|
6461
|
+
environment,
|
|
6462
|
+
homeDirectory,
|
|
6463
|
+
configDirName: getConfigDirName(environment)
|
|
6464
|
+
},
|
|
6465
|
+
agentConfig: new TeamAgentConfigService({
|
|
6466
|
+
fs,
|
|
6467
|
+
environment,
|
|
6468
|
+
homeDirectory
|
|
6469
|
+
}),
|
|
6470
|
+
childSessions: { get: () => readPoChildSessionService(context) },
|
|
6352
6471
|
nativeRunProjection
|
|
6353
6472
|
});
|
|
6354
6473
|
const scope = createSessionScope(execution.sessionId);
|
|
@@ -6371,9 +6490,6 @@ const root = defineRoot(({ context, host: serverHost, agent: host }) => {
|
|
|
6371
6490
|
telemetry.recordEvent(event, attributes);
|
|
6372
6491
|
}
|
|
6373
6492
|
});
|
|
6374
|
-
let ownedReadiness;
|
|
6375
|
-
let ready;
|
|
6376
|
-
const startupAbort = new AbortController();
|
|
6377
6493
|
const nativeRuns = /* @__PURE__ */ new Map();
|
|
6378
6494
|
const childCosts = /* @__PURE__ */ new Map();
|
|
6379
6495
|
let costText;
|
|
@@ -6411,7 +6527,8 @@ const root = defineRoot(({ context, host: serverHost, agent: host }) => {
|
|
|
6411
6527
|
});
|
|
6412
6528
|
if (options?.triggerTurn === false) return;
|
|
6413
6529
|
if (!execution.session.admitPrompt) throw new Error("The session cannot admit a completion prompt.");
|
|
6414
|
-
|
|
6530
|
+
const runIds = details.map((detail) => detail.runId).toSorted();
|
|
6531
|
+
await execution.session.admitPrompt(message.content, "steer", void 0, runIds.length ? `subagent-report:${runIds.join(",")}` : void 0);
|
|
6415
6532
|
});
|
|
6416
6533
|
deliveries = delivery.catch(() => void 0);
|
|
6417
6534
|
return delivery;
|
|
@@ -6424,16 +6541,26 @@ const root = defineRoot(({ context, host: serverHost, agent: host }) => {
|
|
|
6424
6541
|
});
|
|
6425
6542
|
if (options?.triggerTurn === false) return;
|
|
6426
6543
|
const delivery = deliveries.then(async () => {
|
|
6427
|
-
if (!execution.session.
|
|
6428
|
-
await execution.session.
|
|
6544
|
+
if (!execution.session.admitMessage) throw new Error("The session cannot admit an intercom message.");
|
|
6545
|
+
await execution.session.admitMessage({
|
|
6546
|
+
type: message.customType,
|
|
6547
|
+
text,
|
|
6548
|
+
display: message.display,
|
|
6549
|
+
details: message.details
|
|
6550
|
+
}, "steer");
|
|
6429
6551
|
});
|
|
6430
6552
|
deliveries = delivery.catch(() => void 0);
|
|
6431
6553
|
return delivery;
|
|
6432
6554
|
} });
|
|
6433
6555
|
const intercom = createHeadlessIntercomTool(channel, {
|
|
6434
6556
|
sessionId: execution.sessionId,
|
|
6435
|
-
|
|
6436
|
-
|
|
6557
|
+
name: () => serverHost.context.sessionCommunication?.sessionName,
|
|
6558
|
+
peers: () => [...sessionPeers].filter((id) => serverHost.context.sessionService?.canCommunicate?.(execution.sessionId, id) === true).flatMap((id) => {
|
|
6559
|
+
const peer = serverHost.context.sessionCommunication?.describePeer?.(id);
|
|
6560
|
+
return peer === void 0 ? [] : [peer];
|
|
6561
|
+
}),
|
|
6562
|
+
canCommunicate: (id) => serverHost.context.sessionService?.canCommunicate?.(execution.sessionId, id) === true,
|
|
6563
|
+
delivery: () => readPoSessionDelivery(context)
|
|
6437
6564
|
});
|
|
6438
6565
|
const serverService = (ctx) => {
|
|
6439
6566
|
ctx.plugin((providerContext) => {
|
|
@@ -6443,12 +6570,18 @@ const root = defineRoot(({ context, host: serverHost, agent: host }) => {
|
|
|
6443
6570
|
sessionScope: scope,
|
|
6444
6571
|
cwd: execution.cwd,
|
|
6445
6572
|
environment: serverHost.context.environment,
|
|
6446
|
-
|
|
6447
|
-
|
|
6573
|
+
availableModels: () => availableModelInfos(execution),
|
|
6574
|
+
repoRoot: execution.repoRoot,
|
|
6575
|
+
get parentSelection() {
|
|
6576
|
+
return execution.selection;
|
|
6448
6577
|
},
|
|
6449
6578
|
get parentModel() {
|
|
6450
6579
|
return execution.model;
|
|
6451
6580
|
},
|
|
6581
|
+
reportWarning: (body) => execution.client.notify({
|
|
6582
|
+
body,
|
|
6583
|
+
level: "warning"
|
|
6584
|
+
}),
|
|
6452
6585
|
captureForkSource: () => execution.session.forkSource?.()
|
|
6453
6586
|
},
|
|
6454
6587
|
directRunTracker: runtime.asyncJobTracker,
|
|
@@ -6457,9 +6590,16 @@ const root = defineRoot(({ context, host: serverHost, agent: host }) => {
|
|
|
6457
6590
|
policies: runtime.capabilityPolicies,
|
|
6458
6591
|
observeDelegation: (observation) => recordDelegationObservation(telemetry, observation)
|
|
6459
6592
|
});
|
|
6460
|
-
providerContext.inject([
|
|
6461
|
-
|
|
6462
|
-
|
|
6593
|
+
providerContext.inject([PO_CONFIG_SERVICE], (configContext) => {
|
|
6594
|
+
const config = configContext.get(PO_CONFIG_SERVICE);
|
|
6595
|
+
if (!config) throw new Error("Team requires the session Po config service.");
|
|
6596
|
+
const unbind = runtime.agentConfig.bind(() => config.getSnapshot());
|
|
6597
|
+
publishCatalog();
|
|
6598
|
+
return unbind;
|
|
6599
|
+
});
|
|
6600
|
+
providerContext.inject([PO_MCP_TOOL_RESOLVER_SERVICE], (resolverContext) => runtime.mcpToolResolver.bind(requirePoMcpToolResolver(resolverContext)));
|
|
6601
|
+
providerContext.inject([PO_CONTEXT_CONTRIBUTIONS_SERVICE], (contributionContext) => {
|
|
6602
|
+
const registration = requirePoContextContributions(contributionContext).register({
|
|
6463
6603
|
source: "@agimon-ai/po-team",
|
|
6464
6604
|
id: "runtime",
|
|
6465
6605
|
label: "Team",
|
|
@@ -6468,30 +6608,96 @@ const root = defineRoot(({ context, host: serverHost, agent: host }) => {
|
|
|
6468
6608
|
});
|
|
6469
6609
|
return () => registration.dispose();
|
|
6470
6610
|
});
|
|
6471
|
-
|
|
6472
|
-
|
|
6473
|
-
|
|
6474
|
-
|
|
6475
|
-
|
|
6476
|
-
|
|
6477
|
-
|
|
6478
|
-
|
|
6479
|
-
|
|
6480
|
-
|
|
6481
|
-
|
|
6482
|
-
|
|
6483
|
-
});
|
|
6484
|
-
telemetry.recordEvent("doom_team.runs_restored", {
|
|
6485
|
-
"team.suspended_count": opened.suspended.length,
|
|
6486
|
-
"team.pruned_count": opened.pruned.length,
|
|
6487
|
-
"team.reaped_count": opened.reaped,
|
|
6488
|
-
outcome: "reported"
|
|
6489
|
-
});
|
|
6490
|
-
return { value: void 0 };
|
|
6611
|
+
});
|
|
6612
|
+
};
|
|
6613
|
+
const publishCatalog = async () => {
|
|
6614
|
+
if (!activityAttached) return;
|
|
6615
|
+
try {
|
|
6616
|
+
const discovered = (await runtime.discovery.discover(execution.cwd, "both")).agents;
|
|
6617
|
+
const models = (await runtime.agentConfig.currentOrUndefined())?.models ?? [];
|
|
6618
|
+
if (!activityAttached) return;
|
|
6619
|
+
directEvents.publish(SUBAGENT_CATALOG_TYPE, execution.sessionId, {
|
|
6620
|
+
cwd: execution.cwd,
|
|
6621
|
+
agents: presentCatalog(discovered),
|
|
6622
|
+
models: catalogModels(discovered, [...models])
|
|
6491
6623
|
});
|
|
6624
|
+
} catch (error) {
|
|
6625
|
+
if (!activityAttached) return;
|
|
6626
|
+
directEvents.publish(SUBAGENT_CATALOG_TYPE, execution.sessionId, {
|
|
6627
|
+
cwd: execution.cwd,
|
|
6628
|
+
agents: [],
|
|
6629
|
+
models: [],
|
|
6630
|
+
warning: error instanceof Error ? error.message : String(error)
|
|
6631
|
+
});
|
|
6632
|
+
}
|
|
6633
|
+
};
|
|
6634
|
+
const activity = {
|
|
6635
|
+
name: "@agimon-ai/po-team",
|
|
6636
|
+
async start(activityContext) {
|
|
6637
|
+
activityAttached = true;
|
|
6638
|
+
publishCost(true);
|
|
6639
|
+
publishRunSnapshot();
|
|
6640
|
+
await publishCatalog();
|
|
6641
|
+
try {
|
|
6642
|
+
channel.bindMainSession(activityContext.sessionId);
|
|
6643
|
+
return () => {
|
|
6644
|
+
if (!activityAttached) return;
|
|
6645
|
+
activityAttached = false;
|
|
6646
|
+
channel.dispose();
|
|
6647
|
+
execution.client.setStatus(TEAM_COST_STATUS, void 0);
|
|
6648
|
+
};
|
|
6649
|
+
} catch (error) {
|
|
6650
|
+
activityAttached = false;
|
|
6651
|
+
execution.client.setStatus(TEAM_COST_STATUS, void 0);
|
|
6652
|
+
throw error;
|
|
6653
|
+
}
|
|
6654
|
+
}
|
|
6655
|
+
};
|
|
6656
|
+
/**
|
|
6657
|
+
* Re-attaches the runs this session stored before a restart. Called from `session_start`, once the session can
|
|
6658
|
+
* take a completion and before its Harness resumes: a running child gets its lane and intercom back first, and
|
|
6659
|
+
* a finished one reports its completion now.
|
|
6660
|
+
*/
|
|
6661
|
+
const restoreRuns = async () => {
|
|
6662
|
+
const report = (subject, error) => execution.client.notify({
|
|
6663
|
+
body: `${subject}: ${error instanceof Error ? error.message : String(error)}`,
|
|
6664
|
+
level: "warning"
|
|
6665
|
+
});
|
|
6666
|
+
let stored = [];
|
|
6667
|
+
try {
|
|
6668
|
+
stored = await readPoChildSessionService(context)?.stored?.() ?? [];
|
|
6669
|
+
} catch (error) {
|
|
6670
|
+
await report("Stored subagent runs could not be read", error);
|
|
6671
|
+
}
|
|
6672
|
+
let restored = 0;
|
|
6673
|
+
for (const run of stored) {
|
|
6674
|
+
const identity = storedRunIdentity(run);
|
|
6675
|
+
const intercom = run.settled ? void 0 : runtime.teamChannel.createNativeChildIntercom({
|
|
6676
|
+
rootSessionId: run.request.scope.rootSessionId,
|
|
6677
|
+
agent: run.request.agent,
|
|
6678
|
+
...identity ? {
|
|
6679
|
+
identity: identity.identity,
|
|
6680
|
+
inline: identity.inline
|
|
6681
|
+
} : {},
|
|
6682
|
+
runId: run.request.runId,
|
|
6683
|
+
task: {
|
|
6684
|
+
id: run.request.runId,
|
|
6685
|
+
subject: run.request.task
|
|
6686
|
+
}
|
|
6687
|
+
});
|
|
6688
|
+
try {
|
|
6689
|
+
await runtime.nativeRuns.restore(run.request.parentSessionId, run, intercom);
|
|
6690
|
+
restored += 1;
|
|
6691
|
+
} catch (error) {
|
|
6692
|
+
intercom?.dispose?.();
|
|
6693
|
+
await report(`Subagent run ${run.request.runId} could not resume`, error);
|
|
6694
|
+
}
|
|
6695
|
+
}
|
|
6696
|
+
telemetry.recordEvent("po_team.runs_restored", {
|
|
6697
|
+
"team.restored_count": restored,
|
|
6698
|
+
outcome: "restored"
|
|
6492
6699
|
});
|
|
6493
6700
|
};
|
|
6494
|
-
const environment = serverHost.context.environment;
|
|
6495
6701
|
return {
|
|
6496
6702
|
value: {
|
|
6497
6703
|
runtime,
|
|
@@ -6502,96 +6708,63 @@ const root = defineRoot(({ context, host: serverHost, agent: host }) => {
|
|
|
6502
6708
|
channel,
|
|
6503
6709
|
intercom,
|
|
6504
6710
|
directEvents,
|
|
6505
|
-
environment: serverHost.context.environment
|
|
6711
|
+
environment: serverHost.context.environment,
|
|
6712
|
+
restoreRuns
|
|
6506
6713
|
},
|
|
6507
6714
|
services: [
|
|
6508
6715
|
(context) => {
|
|
6509
|
-
if (
|
|
6716
|
+
if (readPoBackgroundWorkService(context) === void 0) provideBackgroundWorkService(context);
|
|
6510
6717
|
},
|
|
6511
6718
|
serverService,
|
|
6719
|
+
(context) => context.provide(PO_TEAM_AGENT_CONFIG_SERVICE, runtime.agentConfig),
|
|
6720
|
+
(context) => context.provide(PO_CHILD_SESSION_LABELS_SERVICE, { labelFor: (runId) => nativeRuns.get(runId)?.identity }),
|
|
6512
6721
|
mountMcpTools([intercom, createHeadlessSubagentTool(runtime, execution)])
|
|
6513
6722
|
],
|
|
6514
|
-
activities: [
|
|
6515
|
-
name: "@agimon-ai/po-team",
|
|
6516
|
-
async start(activityContext) {
|
|
6517
|
-
activityAttached = true;
|
|
6518
|
-
publishCost(true);
|
|
6519
|
-
publishRunSnapshot();
|
|
6520
|
-
try {
|
|
6521
|
-
const discovered = runtime.discovery.discover(execution.cwd, "both").agents;
|
|
6522
|
-
directEvents.publish(SUBAGENT_CATALOG_TYPE, execution.sessionId, {
|
|
6523
|
-
cwd: execution.cwd,
|
|
6524
|
-
agents: presentCatalog(discovered),
|
|
6525
|
-
models: catalogModels(discovered, resolveActiveTeamModelSpecs(environment) ?? [])
|
|
6526
|
-
});
|
|
6527
|
-
} catch (error) {
|
|
6528
|
-
directEvents.publish(SUBAGENT_CATALOG_TYPE, execution.sessionId, {
|
|
6529
|
-
cwd: execution.cwd,
|
|
6530
|
-
agents: [],
|
|
6531
|
-
models: [],
|
|
6532
|
-
warning: error instanceof Error ? error.message : String(error)
|
|
6533
|
-
});
|
|
6534
|
-
}
|
|
6535
|
-
try {
|
|
6536
|
-
channel.bindMainSession(activityContext.sessionId);
|
|
6537
|
-
return () => {
|
|
6538
|
-
if (!activityAttached) return;
|
|
6539
|
-
activityAttached = false;
|
|
6540
|
-
channel.dispose();
|
|
6541
|
-
execution.client.setStatus(TEAM_COST_STATUS, void 0);
|
|
6542
|
-
};
|
|
6543
|
-
} catch (error) {
|
|
6544
|
-
activityAttached = false;
|
|
6545
|
-
execution.client.setStatus(TEAM_COST_STATUS, void 0);
|
|
6546
|
-
throw error;
|
|
6547
|
-
}
|
|
6548
|
-
}
|
|
6549
|
-
}],
|
|
6723
|
+
activities: [activity],
|
|
6550
6724
|
async onStart() {
|
|
6551
6725
|
runtime.pollScheduler.start();
|
|
6552
|
-
telemetry.recordEvent("
|
|
6553
|
-
if (!ready) throw new Error("Team readiness was not installed.");
|
|
6554
|
-
await Promise.allSettled([ready.wait()]);
|
|
6726
|
+
telemetry.recordEvent("po_team.session_started", { "team.root_session": resolveRootSessionId(execution.sessionId, environment) === execution.sessionId });
|
|
6555
6727
|
},
|
|
6556
6728
|
async onDispose() {
|
|
6557
6729
|
disposing = true;
|
|
6558
|
-
startupAbort.abort(/* @__PURE__ */ new Error("Team session ended."));
|
|
6559
|
-
await Promise.allSettled([...ready ? [ready.wait()] : [], ...ownedReadiness ? [ownedReadiness.dispose()] : []]);
|
|
6560
6730
|
activityAttached = false;
|
|
6561
|
-
|
|
6562
|
-
|
|
6563
|
-
|
|
6564
|
-
|
|
6565
|
-
|
|
6566
|
-
|
|
6567
|
-
|
|
6568
|
-
|
|
6569
|
-
|
|
6570
|
-
|
|
6571
|
-
|
|
6572
|
-
|
|
6573
|
-
|
|
6574
|
-
|
|
6575
|
-
|
|
6576
|
-
|
|
6577
|
-
|
|
6578
|
-
|
|
6579
|
-
|
|
6580
|
-
|
|
6581
|
-
|
|
6582
|
-
|
|
6583
|
-
|
|
6584
|
-
|
|
6585
|
-
|
|
6586
|
-
|
|
6587
|
-
|
|
6588
|
-
|
|
6589
|
-
|
|
6590
|
-
|
|
6591
|
-
|
|
6592
|
-
|
|
6593
|
-
|
|
6594
|
-
|
|
6731
|
+
let stoppedRuns;
|
|
6732
|
+
await runCleanups([
|
|
6733
|
+
() => stopPeerDiscovery?.(),
|
|
6734
|
+
() => sessionPeers.clear(),
|
|
6735
|
+
() => execution.client.setStatus(TEAM_COST_STATUS, void 0),
|
|
6736
|
+
() => channel.dispose(),
|
|
6737
|
+
() => runtime.completionNotifier.dispose(),
|
|
6738
|
+
async () => {
|
|
6739
|
+
stoppedRuns = await stopScopeRuns({
|
|
6740
|
+
reason: "headless session ended",
|
|
6741
|
+
jobs: jobs.list(),
|
|
6742
|
+
stop: (runId, reason) => runtime.management.stop(runId, reason)
|
|
6743
|
+
});
|
|
6744
|
+
},
|
|
6745
|
+
() => runCleanups([() => runtime.nativeRuns.close(), () => nativeRunProjection.dispose(execution.sessionId)], "Native Team runs cleanup failed."),
|
|
6746
|
+
unsubscribeCostTracker,
|
|
6747
|
+
unsubscribeCostNative,
|
|
6748
|
+
() => nativeRuns.clear(),
|
|
6749
|
+
() => childCosts.clear(),
|
|
6750
|
+
() => {
|
|
6751
|
+
costText = void 0;
|
|
6752
|
+
},
|
|
6753
|
+
() => bridge.abandonAll(),
|
|
6754
|
+
() => fablePlan.abandonAll(),
|
|
6755
|
+
() => runtime.capabilityPolicies.clear(),
|
|
6756
|
+
() => runtime.management.releaseSessionScope(scope),
|
|
6757
|
+
() => runtime.dispose(),
|
|
6758
|
+
() => runtime.asyncJobTracker.stop(),
|
|
6759
|
+
() => runtime.pollScheduler.stop(),
|
|
6760
|
+
() => telemetry.recordEvent("po_team.session_finished", {
|
|
6761
|
+
reason: "headless session ended",
|
|
6762
|
+
"team.stopped_count": stoppedRuns?.stopped.length ?? 0,
|
|
6763
|
+
"team.unstoppable_count": stoppedRuns?.unstoppable.length ?? 0,
|
|
6764
|
+
outcome: stoppedRuns && !stoppedRuns.unstoppable.length ? "stopped" : "degraded"
|
|
6765
|
+
}),
|
|
6766
|
+
() => telemetry.shutdown()
|
|
6767
|
+
], "Team cleanup failed.");
|
|
6595
6768
|
}
|
|
6596
6769
|
};
|
|
6597
6770
|
});
|
|
@@ -6657,13 +6830,9 @@ var subagent_catalog_default = defineChannel(createSubagentCatalogChannel);
|
|
|
6657
6830
|
var subagent_catalog_server_default = defineChannel(subagent_catalog_default);
|
|
6658
6831
|
//#endregion
|
|
6659
6832
|
//#region src/extensions/(backend)/channel/_lib/subagent-runs.ts
|
|
6660
|
-
const RUN_ID_PATTERN = /^[\w.-]+$/;
|
|
6661
6833
|
function isSubagentRunsPayload(value) {
|
|
6662
6834
|
return typeof value === "object" && value !== null && Array.isArray(value.runs);
|
|
6663
6835
|
}
|
|
6664
|
-
function publicPayload(payload) {
|
|
6665
|
-
return { runs: payload.runs.map(({ sessionFile: _sessionFile, ...run }) => run) };
|
|
6666
|
-
}
|
|
6667
6836
|
/**
|
|
6668
6837
|
* The built-in subagents data channel consumes current state from the
|
|
6669
6838
|
* host-owned direct event bus. Its bounded latest replay seeds late subscribers.
|
|
@@ -6678,16 +6847,34 @@ function createSubagentsChannel() {
|
|
|
6678
6847
|
const publish = (scope, payload) => {
|
|
6679
6848
|
if (scopes.get(scope.sessionId) !== scope) return;
|
|
6680
6849
|
latest.set(scope.sessionId, payload);
|
|
6681
|
-
host.publish(scope.sessionId,
|
|
6850
|
+
host.publish(scope.sessionId, payload);
|
|
6682
6851
|
};
|
|
6683
6852
|
return {
|
|
6684
|
-
|
|
6685
|
-
const payload =
|
|
6686
|
-
|
|
6853
|
+
homeFor(scope) {
|
|
6854
|
+
const payload = latest.get(scope.sessionId);
|
|
6855
|
+
if (!payload) return {
|
|
6856
|
+
coverage: "unavailable",
|
|
6857
|
+
entities: []
|
|
6858
|
+
};
|
|
6859
|
+
return {
|
|
6860
|
+
coverage: payload.runs.length > HOME_ENTITY_LIMIT ? "partial" : "ready",
|
|
6861
|
+
entities: payload.runs.slice(0, HOME_ENTITY_LIMIT).map((run) => ({
|
|
6862
|
+
id: createHash("sha256").update(JSON.stringify([
|
|
6863
|
+
scope.workspaceId ?? scope.cwd,
|
|
6864
|
+
scope.sessionId,
|
|
6865
|
+
run.runId,
|
|
6866
|
+
run.startedAt
|
|
6867
|
+
])).digest("hex"),
|
|
6868
|
+
kind: run.state === "failed" ? "failure" : run.state === "done" ? "completed" : run.state === "stopped" ? "paused" : run.state,
|
|
6869
|
+
title: (run.taskRef || run.task || run.agent).slice(0, 160),
|
|
6870
|
+
...(run.error || run.currentTool)?.trim() ? { detail: (run.error || run.currentTool).slice(0, 320) } : {},
|
|
6871
|
+
updatedAt: new Date(run.lastUpdate).toISOString(),
|
|
6872
|
+
sessionId: scope.sessionId
|
|
6873
|
+
}))
|
|
6874
|
+
};
|
|
6687
6875
|
},
|
|
6688
|
-
|
|
6689
|
-
|
|
6690
|
-
return (latest.get(scope.sessionId)?.runs.find((run) => run.runId === runId))?.sessionFile;
|
|
6876
|
+
payloadFor(scope) {
|
|
6877
|
+
return scopes.has(scope.sessionId) ? latest.get(scope.sessionId) : void 0;
|
|
6691
6878
|
},
|
|
6692
6879
|
sessionAdded(scope) {
|
|
6693
6880
|
subscriptions.get(scope.sessionId)?.();
|
|
@@ -6743,16 +6930,11 @@ function isStopRequest(value) {
|
|
|
6743
6930
|
}
|
|
6744
6931
|
/** The typed error's text carries a recovery hint written for the model, so a missing run gets a sentence for people. */
|
|
6745
6932
|
function controlFailure(error) {
|
|
6746
|
-
if (error instanceof
|
|
6933
|
+
if (error instanceof PoTeamExpectedError && error.code === "run_not_found") return Response.json({ error: "This run is not active." }, { status: 404 });
|
|
6747
6934
|
return Response.json({ error: error instanceof Error ? error.message : String(error) }, { status: 409 });
|
|
6748
6935
|
}
|
|
6749
6936
|
/** Serves catalog discovery, run launch, and run steer and stop from the selected session process, never through a parent prompt. */
|
|
6750
6937
|
function createTeamApiHandler(options) {
|
|
6751
|
-
const discovery = new AgentDiscoveryService(options.environment);
|
|
6752
|
-
const read = options.read ?? ((cwd) => ({
|
|
6753
|
-
agents: discovery.discover(cwd, "both").agents,
|
|
6754
|
-
models: resolveActiveTeamModelSpecs(options.environment) ?? []
|
|
6755
|
-
}));
|
|
6756
6938
|
return {
|
|
6757
6939
|
async fetch(request) {
|
|
6758
6940
|
const url = new URL(request.url);
|
|
@@ -6803,8 +6985,8 @@ function createTeamApiHandler(options) {
|
|
|
6803
6985
|
}
|
|
6804
6986
|
if (request.method !== "GET" || url.pathname !== "/catalog") return Response.json({ error: "Not found." }, { status: 404 });
|
|
6805
6987
|
try {
|
|
6806
|
-
|
|
6807
|
-
const snapshot = read(options.cwd);
|
|
6988
|
+
options.invalidate?.();
|
|
6989
|
+
const snapshot = await options.read(options.cwd);
|
|
6808
6990
|
return Response.json({
|
|
6809
6991
|
cwd: options.cwd,
|
|
6810
6992
|
agents: presentCatalog(snapshot.agents),
|
|
@@ -6837,9 +7019,11 @@ function createTeamSessionApi(runtime, execution) {
|
|
|
6837
7019
|
sessionScope: scope,
|
|
6838
7020
|
environment: execution.environment,
|
|
6839
7021
|
parentSessionId: execution.sessionId,
|
|
7022
|
+
parentSelection: execution.selection,
|
|
6840
7023
|
...parentForkSource ? { parentForkSource } : {},
|
|
6841
|
-
availableModels:
|
|
6842
|
-
...execution.model ? { parentModel: execution.model } : {}
|
|
7024
|
+
availableModels: await availableModelInfos(execution),
|
|
7025
|
+
...execution.model ? { parentModel: execution.model } : {},
|
|
7026
|
+
sender: HUMAN_SENDER
|
|
6843
7027
|
}, loadConfig().config)).outcomes[0];
|
|
6844
7028
|
if (!outcome?.runId) throw new Error(outcome?.error ?? "The agent did not start.");
|
|
6845
7029
|
jobs.track(outcome.runId, outcome.identity ? {
|
|
@@ -6852,9 +7036,16 @@ function createTeamSessionApi(runtime, execution) {
|
|
|
6852
7036
|
basePath: TEAM_API_BASE_PATH,
|
|
6853
7037
|
start: () => createTeamApiHandler({
|
|
6854
7038
|
cwd: execution.cwd,
|
|
6855
|
-
|
|
7039
|
+
read: async (cwd) => ({
|
|
7040
|
+
agents: (await runtime.discovery.discover(cwd, "both")).agents,
|
|
7041
|
+
models: [...(await runtime.agentConfig.currentOrUndefined())?.models ?? []]
|
|
7042
|
+
}),
|
|
7043
|
+
invalidate: () => runtime.discovery.invalidate(),
|
|
6856
7044
|
launch,
|
|
6857
|
-
steer: (request, signal) => runtime.management.steer(request.runId,
|
|
7045
|
+
steer: (request, signal) => runtime.management.steer(request.runId, {
|
|
7046
|
+
text: request.message,
|
|
7047
|
+
sender: HUMAN_SENDER
|
|
7048
|
+
}, void 0, signal),
|
|
6858
7049
|
stop: (request) => runtime.management.stop(request.runId)
|
|
6859
7050
|
})
|
|
6860
7051
|
};
|
|
@@ -6864,6 +7055,12 @@ var route_server_default$1 = (context) => createTeamSessionApi(context.root.runt
|
|
|
6864
7055
|
//#region src/extensions/workspaces/sessions/(backend)/api/route.server.ts
|
|
6865
7056
|
var route_server_default = defineRoute(route_server_default$1);
|
|
6866
7057
|
//#endregion
|
|
7058
|
+
//#region src/extensions/workspaces/sessions/(backend)/hook/sessionStart.server.ts
|
|
7059
|
+
var sessionStart_server_default = defineHook(({ root }) => ({
|
|
7060
|
+
event: "session_start",
|
|
7061
|
+
handle: () => root.restoreRuns()
|
|
7062
|
+
}));
|
|
7063
|
+
//#endregion
|
|
6867
7064
|
//#region src/extensions/workspaces/sessions/(backend)/resource/_lib/team.server.ts
|
|
6868
7065
|
/**
|
|
6869
7066
|
* The roster the model is allowed to see.
|
|
@@ -6872,7 +7069,7 @@ var route_server_default = defineRoute(route_server_default$1);
|
|
|
6872
7069
|
* `TeamMemberContext`, whose `token` is a session capability documented as never
|
|
6873
7070
|
* leaving the direct context. A context resource is pasted into the system prompt,
|
|
6874
7071
|
* sent to the provider and persisted, so the raw struct must not be serialized here.
|
|
6875
|
-
* `formatTeamContextSnapshot` is the same projection the Pi
|
|
7072
|
+
* `formatTeamContextSnapshot` is the same projection the Pi extension renders.
|
|
6876
7073
|
*/
|
|
6877
7074
|
var team_server_default$1 = (context) => ({
|
|
6878
7075
|
name: "po/team",
|
|
@@ -6894,21 +7091,24 @@ var subagent_server_default = defineServerTool((context) => createHeadlessSubage
|
|
|
6894
7091
|
//#endregion
|
|
6895
7092
|
//#region generated/server.ts
|
|
6896
7093
|
const at = (value, context) => typeof value === "function" ? value(context) : value;
|
|
7094
|
+
/* v8 ignore next -- generated normalization branch */
|
|
7095
|
+
const defined = (values) => values.filter((value) => value !== void 0);
|
|
6897
7096
|
const via = (identity, value) => value === void 0 ? void 0 : {
|
|
6898
7097
|
...identity,
|
|
6899
7098
|
...value
|
|
6900
7099
|
};
|
|
6901
|
-
const
|
|
7100
|
+
const serverPlugin = defineServerPlugin({
|
|
6902
7101
|
name: "@agimon-ai/po-team",
|
|
6903
7102
|
providerOverrides: composeServerProviderOverrides(root_server_default),
|
|
7103
|
+
injects: composeRootInjects({ session: [root_server_default] }),
|
|
6904
7104
|
global: () => ({ get channels() {
|
|
6905
7105
|
return [() => via({ frameType: "subagent_catalog" }, subagent_catalog_server_default()), () => via({ frameType: "subagent_runs" }, subagent_runs_server_default())];
|
|
6906
7106
|
} }),
|
|
6907
|
-
workspace: () => ({ get channels() {
|
|
6908
|
-
return [() => via({ frameType: "subagent_catalog" }, subagent_catalog_server_default()), () => via({ frameType: "subagent_runs" }, subagent_runs_server_default())];
|
|
7107
|
+
workspace: (context) => ({ get channels() {
|
|
7108
|
+
return defined([context.hosted("global") ? void 0 : () => via({ frameType: "subagent_catalog" }, subagent_catalog_server_default()), context.hosted("global") ? void 0 : () => via({ frameType: "subagent_runs" }, subagent_runs_server_default())]);
|
|
6909
7109
|
} }),
|
|
6910
7110
|
session: async (context) => {
|
|
6911
|
-
const scopeSession = await root_server_default
|
|
7111
|
+
const scopeSession = await context.scopeRoot("session", root_server_default, context);
|
|
6912
7112
|
const contextSession = {
|
|
6913
7113
|
...context,
|
|
6914
7114
|
root: scopeSession.value
|
|
@@ -6919,7 +7119,10 @@ const facet = defineServerPlugin({
|
|
|
6919
7119
|
return [at(route_server_default, contextSession)];
|
|
6920
7120
|
},
|
|
6921
7121
|
get channels() {
|
|
6922
|
-
return [() => via({ frameType: "subagent_catalog" }, subagent_catalog_server_default()), () => via({ frameType: "subagent_runs" }, subagent_runs_server_default())];
|
|
7122
|
+
return defined([context.hosted("global") ? void 0 : () => via({ frameType: "subagent_catalog" }, subagent_catalog_server_default()), context.hosted("global") ? void 0 : () => via({ frameType: "subagent_runs" }, subagent_runs_server_default())]);
|
|
7123
|
+
},
|
|
7124
|
+
get hooks() {
|
|
7125
|
+
return [via({ event: "session_start" }, at(sessionStart_server_default, contextSession))];
|
|
6923
7126
|
},
|
|
6924
7127
|
get resources() {
|
|
6925
7128
|
return [at(team_server_default, contextSession)];
|
|
@@ -6935,6 +7138,6 @@ const facet = defineServerPlugin({
|
|
|
6935
7138
|
}
|
|
6936
7139
|
});
|
|
6937
7140
|
//#endregion
|
|
6938
|
-
export {
|
|
7141
|
+
export { serverPlugin as default, serverPlugin };
|
|
6939
7142
|
|
|
6940
7143
|
//# sourceMappingURL=server.mjs.map
|