pi-crew 0.11.46 → 0.11.48
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -0
- package/dist/index.mjs +479 -479
- package/package.json +1 -1
- package/src/config/env-vars.ts +1 -1
- package/src/prompt/surface-worker.ts +1 -1
- package/src/runtime/broker/crew-broker-child.ts +6 -3
- package/src/runtime/broker/crew-broker-delegate-request.ts +5 -5
- package/src/runtime/child-pi/child-pi-spawn.ts +21 -13
- package/src/runtime/live-session/live-session-prompt-models.ts +368 -0
- package/src/runtime/live-session/live-session-runtime.ts +19 -356
|
@@ -1,30 +1,15 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
|
-
import type { AgentConfig } from "../../agents/agent-config.ts";
|
|
3
|
-
import { resolveToolPolicy } from "../../agents/agent-config.ts";
|
|
4
|
-
import type { CrewRuntimeConfig } from "../../config/config.ts";
|
|
5
|
-
import { loadConfig } from "../../config/config.ts";
|
|
6
2
|
import { DEFAULT_LIVE_SESSION } from "../../config/defaults.ts";
|
|
7
3
|
import { getCrewEnv } from "../../config/env-vars.ts";
|
|
8
4
|
import { appendEvent, appendEventFireAndForget } from "../../state/event-log/event-log.ts";
|
|
9
|
-
import type {
|
|
5
|
+
import type { UsageState } from "../../state/types.ts";
|
|
10
6
|
import { logInternalError } from "../../utils/internal-error.ts";
|
|
11
7
|
import { redactSecrets } from "../../utils/redaction.ts";
|
|
12
|
-
import type { WorkflowStep } from "../../workflows/workflow-config.ts";
|
|
13
8
|
import { BoundedTail } from "../compaction/compact-stages/bounded-tail.ts";
|
|
14
9
|
import { createIrcTool } from "../custom-tools/irc-tool.ts";
|
|
15
10
|
import { createSubmitResultTool } from "../custom-tools/submit-result-tool.ts";
|
|
16
11
|
import { g2EnforcementDegradationReason, mcpPermittedForRole, shouldFailClosedMcpEnforcement, stripMcpExtensions } from "../mcp-proxy.ts";
|
|
17
|
-
import {
|
|
18
|
-
availableModelInfosFromRegistry,
|
|
19
|
-
buildConfiguredModelRouting,
|
|
20
|
-
type ModelFallbackPolicy,
|
|
21
|
-
modelRefToString,
|
|
22
|
-
providerOfModelRef,
|
|
23
|
-
resolveDefaultSubagentModel,
|
|
24
|
-
resolveModelFallbackPolicy,
|
|
25
|
-
warnOutOfScopeSoft,
|
|
26
|
-
} from "../model/model-fallback.ts";
|
|
27
|
-
import { readEnabledModelsPatterns } from "../model/model-scope.ts";
|
|
12
|
+
import { buildConfiguredModelRouting, modelRefToString, warnOutOfScopeSoft } from "../model/model-fallback.ts";
|
|
28
13
|
import { isLiveSessionRuntimeAvailable } from "../model/runtime-resolver.ts";
|
|
29
14
|
import { awaitRuntimeWarmup } from "../model/runtime-warmup.ts";
|
|
30
15
|
import { liveAgentContext, registerLiveAgentModel, unregisterLiveAgentModel } from "../model/session-model.ts";
|
|
@@ -42,7 +27,6 @@ import {
|
|
|
42
27
|
// block (up to 200 lines) in both the user and system prompts. Keep memory
|
|
43
28
|
// in a single place: the shared user prompt. See G3 fix.
|
|
44
29
|
import { createStreamingOutput, type StreamingOutputHandle } from "../output/streaming-output.ts";
|
|
45
|
-
import { buildSensitivePathConstraint } from "../sensitive-paths.ts";
|
|
46
30
|
import {
|
|
47
31
|
buildYieldReminder,
|
|
48
32
|
DEFAULT_YIELD_CONFIG,
|
|
@@ -64,7 +48,6 @@ import {
|
|
|
64
48
|
enforceEmbeddedBuiltinTools,
|
|
65
49
|
finalizeEmbeddedFaultCheck,
|
|
66
50
|
LiveSessionEmbeddedEnforcementError,
|
|
67
|
-
type LiveSessionEmbeddedMitigations,
|
|
68
51
|
type LiveSessionFault,
|
|
69
52
|
LiveSessionTurnWatchdogError,
|
|
70
53
|
maybeEmitEmbeddedMemoryMetrics,
|
|
@@ -87,17 +70,33 @@ import {
|
|
|
87
70
|
import { hasLiveControlRealtimeListeners, subscribeLiveControlRealtime } from "./live-control-realtime.ts";
|
|
88
71
|
import { buildExtensionBridge } from "./live-extension-bridge.ts";
|
|
89
72
|
import { collectLiveSessionHealth, formatLiveSessionDiagnostics } from "./live-session-health.ts";
|
|
73
|
+
import {
|
|
74
|
+
applyModelProviderOverride,
|
|
75
|
+
compressSessionToolDescriptions,
|
|
76
|
+
filterActiveTools,
|
|
77
|
+
type LiveSessionLike,
|
|
78
|
+
type LiveSessionSpawnInput,
|
|
79
|
+
liveSystemPrompt,
|
|
80
|
+
modelFromRegistry,
|
|
81
|
+
resolveLiveDefaultSubagentModel,
|
|
82
|
+
resolveLiveModelFallbackPolicy,
|
|
83
|
+
resolveParentModelFromRegistry,
|
|
84
|
+
resolveScopedSessionModels,
|
|
85
|
+
resolveScopeModelsPatterns,
|
|
86
|
+
} from "./live-session-prompt-models.ts";
|
|
90
87
|
import {
|
|
91
88
|
asRecord,
|
|
92
89
|
eventText,
|
|
93
90
|
extractToolName,
|
|
94
91
|
faultPathUsage,
|
|
95
92
|
finalAssistantText,
|
|
96
|
-
type SessionStatsLike,
|
|
97
93
|
trackUsageFromMessageEndEvent,
|
|
98
94
|
usageFromSessionStats,
|
|
99
95
|
} from "./usage-bridge.ts";
|
|
100
96
|
|
|
97
|
+
export type { LiveSessionSpawnInput } from "./live-session-prompt-models.ts";
|
|
98
|
+
export { liveSystemPrompt, resolveParentModelFromRegistry, resolveScopedSessionModels } from "./live-session-prompt-models.ts";
|
|
99
|
+
|
|
101
100
|
/**
|
|
102
101
|
* Module-scoped latch for the optional peer dependency import. When N
|
|
103
102
|
* in-process live-session subagents spawn CONCURRENTLY (e.g. several
|
|
@@ -130,49 +129,6 @@ function loadLiveSessionModule(): Promise<LiveSessionModule> {
|
|
|
130
129
|
return liveSessionModulePromise;
|
|
131
130
|
}
|
|
132
131
|
|
|
133
|
-
export interface LiveSessionSpawnInput {
|
|
134
|
-
manifest: TeamRunManifest;
|
|
135
|
-
task: TeamTaskState;
|
|
136
|
-
step: WorkflowStep;
|
|
137
|
-
agent: AgentConfig;
|
|
138
|
-
prompt: string;
|
|
139
|
-
signal?: AbortSignal;
|
|
140
|
-
transcriptPath?: string;
|
|
141
|
-
onEvent?: (event: unknown) => void;
|
|
142
|
-
onOutput?: (text: string) => void;
|
|
143
|
-
runtimeConfig?: CrewRuntimeConfig;
|
|
144
|
-
parentContext?: string;
|
|
145
|
-
parentModel?: unknown;
|
|
146
|
-
modelRegistry?: unknown;
|
|
147
|
-
modelOverride?: string;
|
|
148
|
-
teamRoleModel?: string;
|
|
149
|
-
teamRoleFallbackModels?: string[];
|
|
150
|
-
teamRoleThinking?: string;
|
|
151
|
-
isCurrent?: () => boolean;
|
|
152
|
-
/** Workspace where this run was initiated — used for session-scoped live-agent visibility. */
|
|
153
|
-
workspaceId: string;
|
|
154
|
-
/** Phase 2: Output schema for validating yield data. */
|
|
155
|
-
outputSchema?: unknown;
|
|
156
|
-
/**
|
|
157
|
-
* U3 test seam: optional pi-ai `Provider`-like object registered on the
|
|
158
|
-
* created session's ModelRuntime immediately after session creation, before
|
|
159
|
-
* anything streams. Lets tests run the REAL in-process live-session worker
|
|
160
|
-
* key-free and network-free against `@earendil-works/pi-ai/providers/faux`
|
|
161
|
-
* (tests construct the provider and pass it here; runtime code never
|
|
162
|
-
* imports the faux module). Must stay undefined in production runs.
|
|
163
|
-
*/
|
|
164
|
-
modelProviderOverride?: unknown;
|
|
165
|
-
/**
|
|
166
|
-
* U11 embedded-tier mitigations (DEFAULT-OFF). Present ONLY when the hybrid
|
|
167
|
-
* orchestration layer routed this task to the embedded tier for an
|
|
168
|
-
* opted-in role: builtin-tools-only enforcement, provider timeoutMs,
|
|
169
|
-
* turn-count watchdog, embedded concurrency cap, process-level fault
|
|
170
|
-
* guard, bounded memory metrics and factory/global-state asserts. When
|
|
171
|
-
* undefined this function behaves byte-identically to pre-U11.
|
|
172
|
-
*/
|
|
173
|
-
embeddedMitigations?: LiveSessionEmbeddedMitigations;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
132
|
export interface LiveSessionRunResult {
|
|
177
133
|
available: true;
|
|
178
134
|
exitCode: number | null;
|
|
@@ -237,28 +193,6 @@ type LiveSessionModule = Record<string, unknown> & {
|
|
|
237
193
|
getAgentDir?: () => string;
|
|
238
194
|
};
|
|
239
195
|
|
|
240
|
-
type LiveSessionLike = {
|
|
241
|
-
subscribe?: (listener: (event: unknown) => void) => () => void;
|
|
242
|
-
prompt?: (text: string, options?: Record<string, unknown>) => Promise<void>;
|
|
243
|
-
steer?: (text: string) => Promise<void>;
|
|
244
|
-
abort?: () => Promise<void> | void;
|
|
245
|
-
dispose?: () => void;
|
|
246
|
-
/** U16: SDK AgentSession session-level stats surface (aggregate tokens + cost) — shape: usage-bridge.ts (SessionStatsLike). */
|
|
247
|
-
getSessionStats?: () => SessionStatsLike;
|
|
248
|
-
bindExtensions?: (bindings?: Record<string, unknown>) => Promise<void>;
|
|
249
|
-
getActiveToolNames?: () => string[];
|
|
250
|
-
setActiveToolsByName?: (names: string[]) => void;
|
|
251
|
-
/** U3 test-seam surface: SDK AgentSession exposes its ModelRuntime here. */
|
|
252
|
-
modelRuntime?: {
|
|
253
|
-
registerNativeProvider?: (provider: unknown) => void;
|
|
254
|
-
/** U11 (b): surfaces used to wrap/restore the provider with a default timeoutMs. */
|
|
255
|
-
getProvider?: (providerId: string) => unknown;
|
|
256
|
-
getRegisteredNativeProvider?: (providerId: string) => unknown;
|
|
257
|
-
};
|
|
258
|
-
/** U4 recursion guard (layer 1): public SDK accessor for loaded extension paths. */
|
|
259
|
-
extensionRunner?: { getExtensionPaths?: () => string[] };
|
|
260
|
-
};
|
|
261
|
-
|
|
262
196
|
/**
|
|
263
197
|
* U4 — shared live-session services bundle.
|
|
264
198
|
*
|
|
@@ -452,277 +386,6 @@ function appendTranscript(filePath: string | undefined, event: unknown): void {
|
|
|
452
386
|
* supplied or the session does not expose a ModelRuntime with
|
|
453
387
|
* `registerNativeProvider` (older/foreign SDK shapes).
|
|
454
388
|
*/
|
|
455
|
-
function applyModelProviderOverride(session: LiveSessionLike | undefined, provider: unknown): void {
|
|
456
|
-
if (!session || !provider) return;
|
|
457
|
-
const runtime = asRecord(session.modelRuntime);
|
|
458
|
-
const register = runtime?.registerNativeProvider;
|
|
459
|
-
if (typeof register !== "function") return;
|
|
460
|
-
try {
|
|
461
|
-
register.call(runtime, provider);
|
|
462
|
-
} catch (error) {
|
|
463
|
-
logInternalError("live-session.model-provider-override", error, "registerNativeProvider failed");
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
/**
|
|
468
|
-
* F7: resolve the enabledModels allowlist for the current project, but only
|
|
469
|
-
* if the `reliability.scopeModels` toggle is ON. Returns an empty
|
|
470
|
-
* array when the toggle is off or no allowlist is configured — the routing
|
|
471
|
-
* gate treats empty patterns as "no enforcement" (no-op). Best-effort:
|
|
472
|
-
* any failure to read the toggle or the allowlist silently disables the gate
|
|
473
|
-
* rather than blocking spawn.
|
|
474
|
-
*/
|
|
475
|
-
async function resolveScopeModelsPatterns(cwd: string, agentDir?: string): Promise<string[]> {
|
|
476
|
-
let scopeModels = false;
|
|
477
|
-
try {
|
|
478
|
-
scopeModels = loadConfig(cwd).config.reliability?.scopeModels === true;
|
|
479
|
-
} catch {
|
|
480
|
-
return [];
|
|
481
|
-
}
|
|
482
|
-
if (!scopeModels) return [];
|
|
483
|
-
return readEnabledModelsPatterns(cwd, agentDir);
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
/** Resolve model fallback policy from crew config + env (best-effort). */
|
|
487
|
-
function resolveLiveModelFallbackPolicy(cwd: string): ModelFallbackPolicy | undefined {
|
|
488
|
-
try {
|
|
489
|
-
return resolveModelFallbackPolicy(loadConfig(cwd).config.runtime?.modelFallback);
|
|
490
|
-
} catch {
|
|
491
|
-
return undefined;
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
/** Resolve the default subagent model from crew config + env. */
|
|
496
|
-
function resolveLiveDefaultSubagentModel(cwd: string): string | undefined {
|
|
497
|
-
try {
|
|
498
|
-
return resolveDefaultSubagentModel(loadConfig(cwd).config.runtime?.modelFallback);
|
|
499
|
-
} catch {
|
|
500
|
-
return undefined;
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
function modelFromRegistry(modelRegistry: unknown, modelId: string | undefined): unknown {
|
|
505
|
-
if (!modelId?.includes("/")) return undefined;
|
|
506
|
-
const registry = asRecord(modelRegistry);
|
|
507
|
-
const find = registry?.find;
|
|
508
|
-
if (typeof find !== "function") return undefined;
|
|
509
|
-
const [provider, ...modelParts] = modelId.split("/");
|
|
510
|
-
const id = modelParts.join("/");
|
|
511
|
-
try {
|
|
512
|
-
return find.call(modelRegistry, provider, id);
|
|
513
|
-
} catch {
|
|
514
|
-
return undefined;
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
/**
|
|
519
|
-
* R3-12: resolve pi-crew's fallback chain into pi SDK `scopedModels` entries.
|
|
520
|
-
*
|
|
521
|
-
* SDK semantics (pi-coding-agent docs/sdk.md; dist/core/agent-session.js):
|
|
522
|
-
* `scopedModels` bounds the session's CYCLING scope and what extensions see
|
|
523
|
-
* through `getScopedModels` — it is NOT a hard access gate (`setModel` still
|
|
524
|
-
* accepts any credentialed model), so binding the fallback chain cannot break
|
|
525
|
-
* a legitimate user/model override. Candidates that don't resolve in the
|
|
526
|
-
* registry are skipped; an empty result means the caller passes NO option so
|
|
527
|
-
* pi keeps its default scope (all available models) — the "explicit
|
|
528
|
-
* non-empty" guard from the R3 spec.
|
|
529
|
-
*/
|
|
530
|
-
export function resolveScopedSessionModels(modelRegistry: unknown, candidates: readonly string[]): Array<{ model: unknown }> {
|
|
531
|
-
const scoped: Array<{ model: unknown }> = [];
|
|
532
|
-
for (const candidate of candidates) {
|
|
533
|
-
const model = modelFromRegistry(modelRegistry, candidate);
|
|
534
|
-
if (!model || typeof model !== "object") continue;
|
|
535
|
-
if (scoped.some((entry) => entry.model === model)) continue;
|
|
536
|
-
scoped.push({ model });
|
|
537
|
-
}
|
|
538
|
-
return scoped;
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
/**
|
|
542
|
-
* Round 18: when agent declares `model: false`, the inherited `parentModel`
|
|
543
|
-
* (= `ctx.model` from Pi runtime, set via `team-tool.ts:541/655`) is the
|
|
544
|
-
* session's SAVED model. That saved model can be stale (e.g. a previous
|
|
545
|
-
* session used claude-sonnet-4-5 and saved it as session.model; the new
|
|
546
|
-
* session actually runs on minimax-M3 displayed in the footer). If the
|
|
547
|
-
* saved model has no auth in `modelRegistry`, the worker fails immediately
|
|
548
|
-
* with "No API key found" before reaching any fallback candidate.
|
|
549
|
-
*
|
|
550
|
-
* This helper prefers the saved model when it is in the auth-available
|
|
551
|
-
* registry; otherwise falls back to the first auth-available registry
|
|
552
|
-
* model (e.g. minimax/MiniMax-M3, zai/glm-5.2); otherwise returns the
|
|
553
|
-
* raw `parentModel` unchanged so the caller surfaces E008.
|
|
554
|
-
*/
|
|
555
|
-
export function resolveParentModelFromRegistry(modelRegistry: unknown, rawParentModel: unknown): string | undefined {
|
|
556
|
-
// `ctx.model` is a pi `Model` OBJECT, not a string. The old string-only
|
|
557
|
-
// guard silently discarded it, so every live-session subagent that inherited
|
|
558
|
-
// the parent model landed on `getAvailable()[0]` instead — i.e. the parent
|
|
559
|
-
// model was never actually honoured on this path.
|
|
560
|
-
const raw = modelRefToString(rawParentModel);
|
|
561
|
-
if (raw) {
|
|
562
|
-
const candidate = raw.includes("/")
|
|
563
|
-
? raw
|
|
564
|
-
: (() => {
|
|
565
|
-
const m = modelFromRegistry(modelRegistry, raw);
|
|
566
|
-
if (m && typeof m === "object" && "fullId" in m) {
|
|
567
|
-
return String((m as { fullId?: unknown }).fullId ?? raw);
|
|
568
|
-
}
|
|
569
|
-
return undefined;
|
|
570
|
-
})();
|
|
571
|
-
if (candidate && modelFromRegistry(modelRegistry, candidate)) return candidate;
|
|
572
|
-
}
|
|
573
|
-
const available = availableModelInfosFromRegistry(modelRegistry);
|
|
574
|
-
if (available && available.length > 0) {
|
|
575
|
-
// Staying on the parent's provider keeps the same auth + cost profile;
|
|
576
|
-
// only cross providers when the parent's provider has nothing available.
|
|
577
|
-
const parentProvider = providerOfModelRef(raw);
|
|
578
|
-
const sameProvider = parentProvider ? available.find((entry) => entry.provider === parentProvider) : undefined;
|
|
579
|
-
return (sameProvider ?? available[0]).fullId;
|
|
580
|
-
}
|
|
581
|
-
return raw;
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
/** Communication intensity by role (caveman-inspired token optimization) */
|
|
585
|
-
const ROLE_INTENSITY: Record<string, "lite" | "full" | "ultra"> = {
|
|
586
|
-
explorer: "ultra",
|
|
587
|
-
analyst: "full",
|
|
588
|
-
planner: "full",
|
|
589
|
-
critic: "full",
|
|
590
|
-
executor: "full",
|
|
591
|
-
reviewer: "full",
|
|
592
|
-
"security-reviewer": "full",
|
|
593
|
-
"test-engineer": "full",
|
|
594
|
-
verifier: "full",
|
|
595
|
-
writer: "lite",
|
|
596
|
-
};
|
|
597
|
-
|
|
598
|
-
function buildCommunicationStyle(role: string): string {
|
|
599
|
-
const intensity = ROLE_INTENSITY[role] ?? "full";
|
|
600
|
-
if (intensity === "lite") return "## Communication\nProfessional concise. No filler/hedging. Full sentences OK.";
|
|
601
|
-
if (intensity === "ultra")
|
|
602
|
-
return [
|
|
603
|
-
"## Communication (ultra-compressed)",
|
|
604
|
-
"Drop: articles, filler, hedging, pleasantries. Fragments OK.",
|
|
605
|
-
"Pattern: [thing] [action] [reason].",
|
|
606
|
-
"Code/paths/symbols: exact, never abbreviated. Errors quoted exact.",
|
|
607
|
-
"Abbreviate prose words: DB/auth/config/req/res/fn/impl.",
|
|
608
|
-
"Arrows for causality: X → Y. One word when one word enough.",
|
|
609
|
-
"Security/destructive: write normal English. Resume compressed after.",
|
|
610
|
-
].join("\n");
|
|
611
|
-
return [
|
|
612
|
-
"## Communication (compressed)",
|
|
613
|
-
"Drop: articles (a/an/the), filler (just/really/basically/actually/simply), hedging, pleasantries.",
|
|
614
|
-
"Short synonyms. Fragments OK. Pattern: [thing] [action] [reason]. [next step].",
|
|
615
|
-
"Code/paths/symbols: exact. Errors quoted exact.",
|
|
616
|
-
"Security/destructive: write normal English. Resume compressed after.",
|
|
617
|
-
].join("\n");
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
function buildOutputContract(role: string): string {
|
|
621
|
-
if (role === "explorer")
|
|
622
|
-
return [
|
|
623
|
-
"## Output Contract",
|
|
624
|
-
"<path>:<line> — `<symbol>` — <≤6 word note>",
|
|
625
|
-
"Group: Defs: / Refs: / Callers: / Tests: / Sites:",
|
|
626
|
-
'Zero hits → "No match."',
|
|
627
|
-
"Last line → totals: N defs, M refs.",
|
|
628
|
-
].join("\n");
|
|
629
|
-
if (role === "executor")
|
|
630
|
-
return [
|
|
631
|
-
"## Output Contract",
|
|
632
|
-
"<path>:<line-range> — <change ≤10 words>.",
|
|
633
|
-
"verified: <re-read OK | mismatch @ path:line>.",
|
|
634
|
-
"Refusal tokens: too-big. / needs-confirm. / ambiguous. / regressed.",
|
|
635
|
-
].join("\n");
|
|
636
|
-
if (role === "reviewer" || role === "security-reviewer")
|
|
637
|
-
return [
|
|
638
|
-
"## Output Contract",
|
|
639
|
-
"<path>:<line>: <emoji> <severity>: <problem>. <fix>.",
|
|
640
|
-
"Severity: 🔴 bug, 🟡 risk, 🔵 nit, ❓ question.",
|
|
641
|
-
'Zero findings → "No issues."',
|
|
642
|
-
"Sorted: file order → ascending line numbers.",
|
|
643
|
-
].join("\n");
|
|
644
|
-
if (role === "verifier")
|
|
645
|
-
return [
|
|
646
|
-
"## Output Contract",
|
|
647
|
-
"PASS: <what verified> — <evidence ≤20 words>.",
|
|
648
|
-
"FAIL: <what failed> — <reason>. <expected vs actual>.",
|
|
649
|
-
"Evidence: file paths, test output, or diffs.",
|
|
650
|
-
].join("\n");
|
|
651
|
-
if (role === "writer") return "## Output Contract\nWrite clear documentation. Full sentences. No compression.";
|
|
652
|
-
return ""; // planner, critic, analyst, test-engineer: no strict format
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
/**
|
|
656
|
-
* Phase 3 (caveman): Compress tool descriptions in a live session to reduce
|
|
657
|
-
* input token cost per tool call. MCP tools often have verbose descriptions
|
|
658
|
-
* (e.g. "This tool allows you to search for files in the filesystem..." → "Search files in filesystem.").
|
|
659
|
-
* Compresses only description text, never modifies tool names or parameters.
|
|
660
|
-
*/
|
|
661
|
-
function compressSessionToolDescriptions(session: LiveSessionLike): void {
|
|
662
|
-
if (typeof session.getActiveToolNames !== "function") return;
|
|
663
|
-
// The Pi SDK doesn't expose a setDescription API, but we can attempt
|
|
664
|
-
// to compress via setActiveToolsByName if the session supports it.
|
|
665
|
-
// For now, this is a no-op that documents the intent for future SDK support.
|
|
666
|
-
// When Pi SDK adds tool description mutation, this function will compress.
|
|
667
|
-
// Side benefit: the import of compressToolDescription ensures the module
|
|
668
|
-
// is loaded and tree-shakeable, so adding the actual logic later is trivial.
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
export function liveSystemPrompt(input: LiveSessionSpawnInput): string {
|
|
672
|
-
// Agent MEMORY is intentionally omitted here — it is already injected via
|
|
673
|
-
// renderTaskPrompt().full (the user prompt, shared with the child-pi path).
|
|
674
|
-
// See the import-block note above and the G3 fix.
|
|
675
|
-
const role = input.task.role;
|
|
676
|
-
const styleBlock = buildCommunicationStyle(role);
|
|
677
|
-
const contractBlock = buildOutputContract(role);
|
|
678
|
-
const sensitiveConstraint = buildSensitivePathConstraint();
|
|
679
|
-
return [
|
|
680
|
-
"# pi-crew Live Subagent",
|
|
681
|
-
`Run ID: ${input.manifest.runId}`,
|
|
682
|
-
`Task ID: ${input.task.id}`,
|
|
683
|
-
`Role: ${role}`,
|
|
684
|
-
`Agent: ${input.agent.name}`,
|
|
685
|
-
`Working directory: ${input.task.cwd}`,
|
|
686
|
-
"",
|
|
687
|
-
styleBlock,
|
|
688
|
-
contractBlock,
|
|
689
|
-
sensitiveConstraint,
|
|
690
|
-
"",
|
|
691
|
-
input.agent.systemPrompt || "Follow the user task exactly and report verification evidence.",
|
|
692
|
-
]
|
|
693
|
-
.filter(Boolean)
|
|
694
|
-
.join("\n");
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
function filterActiveTools(session: LiveSessionLike, agent: AgentConfig, role?: string): void {
|
|
698
|
-
if (typeof session.getActiveToolNames !== "function" || typeof session.setActiveToolsByName !== "function") return;
|
|
699
|
-
const recursiveTools = new Set(["team", "Team", "Agent", "get_subagent_result", "steer_subagent"]);
|
|
700
|
-
// U4 hung-tool mitigation (a) "builtin-tools-only": hard floor on the
|
|
701
|
-
// active set — built-in tools (bash honors AbortSignal via killProcessTree),
|
|
702
|
-
// MCP tools for MCP-permitted roles, and pi-crew's own custom tools. Any
|
|
703
|
-
// other tool name (extension drift the loader-level guard missed) is
|
|
704
|
-
// dropped from the ACTIVE set here (layer 2).
|
|
705
|
-
const builtinFloor = new Set(["read", "bash", "edit", "write", "grep", "find", "ls", "powershell", "submit_result", "irc"]);
|
|
706
|
-
const mcpPermitted = mcpPermittedForRole(role);
|
|
707
|
-
// F1 unify (v0.8.0): use the shared resolveToolPolicy so this path agrees
|
|
708
|
-
// with child-pi (pi-args.ts). Before this, live-session used frontmatter
|
|
709
|
-
// only and ignored role-config entirely — so a builtin explorer on the
|
|
710
|
-
// live-session path wasn't bound by the role's read-only security constraint.
|
|
711
|
-
// Now allowlist precedence is source-aware and the denylist is additive.
|
|
712
|
-
const policy = resolveToolPolicy(agent, role);
|
|
713
|
-
const disallowed = policy.excludeTools?.length ? new Set(policy.excludeTools) : undefined;
|
|
714
|
-
const allowed = policy.tools?.length ? new Set(policy.tools) : undefined;
|
|
715
|
-
const active = session
|
|
716
|
-
.getActiveToolNames()
|
|
717
|
-
.filter(
|
|
718
|
-
(name) =>
|
|
719
|
-
!recursiveTools.has(name) &&
|
|
720
|
-
!disallowed?.has(name) &&
|
|
721
|
-
(!allowed || allowed.has(name)) &&
|
|
722
|
-
(builtinFloor.has(name) || (mcpPermitted && name.startsWith("mcp__"))),
|
|
723
|
-
);
|
|
724
|
-
session.setActiveToolsByName(active);
|
|
725
|
-
}
|
|
726
389
|
|
|
727
390
|
// U16 (Fix B): the usageFromStats thin adapter moved to usage-bridge.ts (m5);
|
|
728
391
|
// re-exported here so the historical import surface keeps working.
|