@tea-agent/loop-agent 0.42.0-next.14 → 0.42.0-next.15
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 +4 -0
- package/dist/build-stamp.json +3 -3
- package/dist/executors/dag-pi-executor.js +617 -94
- package/dist/executors/shell-executor.js +4 -0
- package/dist/worker/console/chat/pi-runtime.js +45 -2
- package/dist/worker/console/chat/resource-loader.js +4 -1
- package/dist/worker/console/chat/routes.js +8 -0
- package/dist/worker/console/chat/subagents/agent-tool.js +62 -0
- package/dist/worker/console/chat/subagents/explore-agent.js +169 -0
- package/dist/worker/console/chat/subagents/index.js +5 -0
- package/dist/worker/console/chat/subagents/orchestrator.js +273 -0
- package/dist/worker/console/chat/subagents/tool-policy.js +53 -0
- package/dist/worker/console/chat/subagents/types.js +13 -0
- package/dist/worker/console/chat/tool-preview.js +10 -0
- package/dist/worker/console/chat/tools.js +11 -1
- package/dist/worker/console/interview/tools.js +1 -0
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-BGMbAdAG.js → abnfDiagram-N423BO3Z-CgXb0EVO.js} +1 -1
- package/dist/worker/console/static/assets/{arc-CJu1LND8.js → arc-DN59MZqN.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-BmJh9gMQ.js → architectureDiagram-T3A2C74G-BUk3sWpn.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-BWjaX3ZF.js → blockDiagram-VBNYF7ZC-IH-cPBFE.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-CLEOAxLB.js → c4Diagram-5PPSVZJV-BJWf1nGo.js} +1 -1
- package/dist/worker/console/static/assets/channel-i1DjpDIw.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-CydnyQUX.js → chunk-2GRJ4B5K-BCPb5H0y.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-tjJ1IoZ1.js → chunk-2Q5K7J3B-Cfs3jeW2.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DR358Phd.js → chunk-5RXB4S5H-BhQI1B_T.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-B-LuWFZD.js → chunk-5VM5RSS4-Cc_m4gyV.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-FG53fA6v.js → chunk-6Q2QTUOP-JPqDS3IV.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-Bkt-w8SZ.js → chunk-GF5L2VYU-AeGX6EAg.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-WxHo0z2I.js → chunk-JWPE2WC7-pEyTjekV.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-CqHrBJhp.js → chunk-KBJHAD2P-Bi5UopKd.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-D4MVae_f.js → chunk-RYQCIY6F-FkGFJxgQ.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-CamBh0Ll.js → chunk-XXDRQBXY-D-7LGrV2.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DgDRdE1V.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DgDRdE1V.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-C3qheCuW.js → cose-bilkent-JH36ORCC-CCEDNDaX.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-DQlWfkS7.js → cynefin-VYW2F7L2-DDd-t7fm.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-BAxa9rFb.js → cynefinDiagram-MW4NZA55-LWe4Ikzr.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-YM09nLJw.js → dagre-VZM6K2ZE-BBCZScc-.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-I4W9EJj2.js → diagram-7IWD3JNH-B6liRVig.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-DH8eJ8Ff.js → diagram-B4RE2ZJO-DO_hIrpW.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-D0D0ntDW.js → diagram-LBJQPF4R-dmCy93uR.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-BIISKq1s.js → diagram-Q27KOJAE-D6mFTIxP.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3--xyAWj0w.js → diagram-UB23O5K3-DCteVUfY.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-B3sYXLJ4.js → ebnfDiagram-BXEA7PRR-DTv530VK.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-zRSYWhJP.js → erDiagram-JOGREHBK-DCXDMxCr.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-BUYlie03.js → flowDiagram-UKHOOZJN-BwEBLOJh.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-DP4kzzrM.js → ganttDiagram-PKOTCBZU-Ck-Vymjm.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-CAPoUxTF.js → gitGraphDiagram-DS77QQ5N-Hqs6X_3L.js} +1 -1
- package/dist/worker/console/static/assets/{index-CWhTyxvc.js → index-BmMi-Bve.js} +71 -71
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-BXPfjekV.js → infoDiagram-6WML65LV-Cjc6M9eg.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-aOI3pV6t.js → ishikawaDiagram-WSZJBQD7-CPchYZMl.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DAPZuvi4.js → journeyDiagram-NVQOT4AX-BABdJNNC.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-atwDzL3C.js → kanban-definition-27J2QSJJ-1oXhbM4j.js} +1 -1
- package/dist/worker/console/static/assets/{linear-D5E09qji.js → linear-PTmQ9LkV.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-CE4idY-s.js → mermaid.core-B6Lxil0V.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-CbZxtPca.js → mindmap-definition-FAOFIHXS-BUqjNGEw.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-De-FYEte.js → pegDiagram-VL7TDLO6-8D1N-orJ.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-Bs0USobz.js → pieDiagram-7S7Q4E2Y-BlPMN9d9.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-Bq3ZrGCJ.js → quadrantDiagram-CIZ2JOQS-BIL5YDes.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-Bk8ojyIj.js → railroadDiagram-AXF67PYL-Dqmil3ie.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-CGhoyws1.js → requirementDiagram-LRYGKXZP-C_-Dp4mH.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-DmVSQs3b.js → sankeyDiagram-W5VNT64P-C0s5-bQS.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-C80h1u0Q.js → sequenceDiagram-SI44F4Z6-DiVCHPfi.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-CcbksQVZ.js → sizeCapture-X5ZJPWSS-CxQ9WVvx.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-CFTTTQKj.js → stateDiagram-OKZ733FA-D9mFHA-Y.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BqXQxfFC.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-Cw7Jr7Tg.js → swimlanes-SLNWSIFB-DDnC5l-M.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-CXwWNRJl.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CVcdTVa5.js → timeline-definition-Z64GVDOM-C8QySPC-.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-BpLbmWp9.js → vennDiagram-T6HMQDX7-BeYdDiz6.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-6PXfbwj-.js → wardleyDiagram-T6FBY63Y-D8hjFWLH.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-Bjkcz4ih.js → xychartDiagram-ELKLHX3M-ClqG_JGK.js} +1 -1
- package/dist/worker/console/static/index.html +1 -1
- package/dist/worker/console/static-src/operator-chat/tools-catalog.js +21 -2
- package/dist/workflows/dag/frontend-contract-facts.js +130 -0
- package/dist/workflows/dag/frontend-design-policy.js +99 -14
- package/dist/workflows/dag/frontend-implementation-contract.js +56 -8
- package/dist/workflows/dag/frontend-plan-render.js +13 -2
- package/dist/workflows/dag/frontend-shadow-dual-write.js +1 -0
- package/dist/workflows/dag/frontend-typed-event-store.js +3 -0
- package/dist/workflows/dag/init-hybrid.js +7 -3
- package/dist/workflows/dag/node-execution.js +54 -16
- package/docs/operations/README.md +1 -0
- package/docs/templates/frontend-implementation-contract.schema.json +34 -2
- package/package.json +2 -3
- package/skills/frontend-plan/SKILL.md +8 -0
- package/skills/frontend-plan/references/decision-contract.md +26 -3
- package/dist/worker/console/static/assets/channel-DOsaUk1k.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-D4-D49vY.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-D4-D49vY.js +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-C4YCesmi.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-lN2U_DLl.js +0 -8
|
@@ -2603,6 +2603,10 @@ async function executeFrontendDesignPolicy(input, meta) {
|
|
|
2603
2603
|
[],
|
|
2604
2604
|
allowedDependencies: config.allowedDependencies,
|
|
2605
2605
|
writeSetPatterns: config.implementationWriteSet ?? [],
|
|
2606
|
+
// Fresh-existence evidence at gate time (Scout pathEvidence.fresh
|
|
2607
|
+
// semantics): a reuse-existing evidencePath must name a file that
|
|
2608
|
+
// exists NOW; greenfield paths are decision=new only.
|
|
2609
|
+
hasFreshPathEvidence: (candidate) => existsSync(path.resolve(input.cwd, candidate)),
|
|
2606
2610
|
});
|
|
2607
2611
|
// 8. Write canonical contract + policy result.
|
|
2608
2612
|
const contractArtifact = await writeFrontendImplementationContractArtifact({
|
|
@@ -32,7 +32,8 @@ import { extractAssistantContentBlocks, extractAssistantText, isAssistantSdkMess
|
|
|
32
32
|
import { parseProviderError, } from "./provider-error.js";
|
|
33
33
|
import { projectCompactSnapshot, } from "./chat-event-store.js";
|
|
34
34
|
import { extractUsageSample } from "./usage.js";
|
|
35
|
-
import { OPERATOR_CHAT_ALLOWED_TOOLS, authorizeOperatorChatTool, assertNoWriteToolInList, } from "./tools.js";
|
|
35
|
+
import { OPERATOR_CHAT_ALLOWED_TOOLS, OPERATOR_CHAT_BUILTIN_CUSTOM_TOOLS, authorizeOperatorChatTool, assertNoWriteToolInList, } from "./tools.js";
|
|
36
|
+
import { buildAgentCustomTool, ExploreOrchestrator, } from "./subagents/index.js";
|
|
36
37
|
import { createOperatorChatResourceLoader, } from "./resource-loader.js";
|
|
37
38
|
import { applySkillAutoInvocationPreferences, isAutoInvocationEnabled, loadResourcePreferences, skillResourceId, } from "./resource-preferences-store.js";
|
|
38
39
|
import { buildModelCallableToolSchemas } from "./tool-adapter.js";
|
|
@@ -115,6 +116,7 @@ export function OPERATOR_CHAT_ACTIVE_TOOL_NAMES() {
|
|
|
115
116
|
...OPERATOR_CHAT_ALLOWED_TOOLS,
|
|
116
117
|
...OPERATOR_CHAT_BUILTIN_EXPLORE_TOOLS,
|
|
117
118
|
...OPERATOR_CHAT_SAFE_EXPLORE_TOOL_IDS,
|
|
119
|
+
...OPERATOR_CHAT_BUILTIN_CUSTOM_TOOLS,
|
|
118
120
|
];
|
|
119
121
|
}
|
|
120
122
|
/**
|
|
@@ -1024,14 +1026,33 @@ export class ConsolePiRuntime {
|
|
|
1024
1026
|
compactionControllers = new Map();
|
|
1025
1027
|
siftStates = new Map();
|
|
1026
1028
|
/** Session-less tool definition caches (no AgentSession state). */
|
|
1027
|
-
/** Session-less tool definition caches (no AgentSession state). */
|
|
1028
1029
|
operatorCustomToolsPromise;
|
|
1029
1030
|
exploreCustomToolsPromise;
|
|
1031
|
+
exploreOrchestrator;
|
|
1030
1032
|
bindings;
|
|
1031
1033
|
constructor(options) {
|
|
1032
1034
|
this.options = options;
|
|
1033
1035
|
this.loader = createOperatorChatResourceLoader();
|
|
1034
1036
|
this.bindings = options.bindings ?? createDefaultPiSdkBindings();
|
|
1037
|
+
this.exploreOrchestrator = new ExploreOrchestrator({
|
|
1038
|
+
cwd: this.options.cwd,
|
|
1039
|
+
bindings: this.bindings,
|
|
1040
|
+
loadSafeExploreCustomTools: () => this.loadExploreCustomTools(),
|
|
1041
|
+
// Fallback when the parent session's model is unknown at Agent-call
|
|
1042
|
+
// time (e.g. turn started before materialization finished): resolve
|
|
1043
|
+
// the default Chat model from the child services' modelRuntime.
|
|
1044
|
+
resolveModel: async (modelRuntime) => {
|
|
1045
|
+
const available = this.bindings.listAvailableModels({ modelRuntime });
|
|
1046
|
+
const descriptor = await resolveDefaultChatModel(this.options.cwd, available);
|
|
1047
|
+
if (!descriptor)
|
|
1048
|
+
return undefined;
|
|
1049
|
+
return this.bindings.resolveModel({
|
|
1050
|
+
modelRuntime,
|
|
1051
|
+
provider: descriptor.provider,
|
|
1052
|
+
modelId: descriptor.modelId,
|
|
1053
|
+
});
|
|
1054
|
+
},
|
|
1055
|
+
});
|
|
1035
1056
|
}
|
|
1036
1057
|
/** Absolute path for repo-scoped resource preferences (may be undefined in unit tests). */
|
|
1037
1058
|
get resourcePreferencesPath() {
|
|
@@ -1526,12 +1547,31 @@ export class ConsolePiRuntime {
|
|
|
1526
1547
|
const goalTools = this.options.goalRegistry
|
|
1527
1548
|
? await buildGoalTools({ sessionId, registry: this.options.goalRegistry })
|
|
1528
1549
|
: [];
|
|
1550
|
+
const agentTool = await buildAgentCustomTool({
|
|
1551
|
+
parentSessionId: sessionId,
|
|
1552
|
+
orchestrator: this.exploreOrchestrator,
|
|
1553
|
+
resolveModel: () => {
|
|
1554
|
+
const model = this.sessionModels.get(sessionId);
|
|
1555
|
+
const modelRuntime = this.modelRuntimes.get(sessionId);
|
|
1556
|
+
if (!model || !modelRuntime)
|
|
1557
|
+
return undefined;
|
|
1558
|
+
return this.bindings.resolveModel({
|
|
1559
|
+
modelRuntime,
|
|
1560
|
+
provider: model.provider,
|
|
1561
|
+
modelId: model.modelId,
|
|
1562
|
+
});
|
|
1563
|
+
},
|
|
1564
|
+
}).catch((error) => {
|
|
1565
|
+
process.stderr.write(`[console] chat Agent tool build failed: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
1566
|
+
return undefined;
|
|
1567
|
+
});
|
|
1529
1568
|
return [
|
|
1530
1569
|
...operatorCustomTools,
|
|
1531
1570
|
...exploreCustomTools,
|
|
1532
1571
|
...(questionTool ? [questionTool] : []),
|
|
1533
1572
|
...(todoTool ? [todoTool] : []),
|
|
1534
1573
|
...goalTools,
|
|
1574
|
+
...(agentTool ? [agentTool] : []),
|
|
1535
1575
|
];
|
|
1536
1576
|
}
|
|
1537
1577
|
loadOperatorCustomTools() {
|
|
@@ -2595,6 +2635,7 @@ export class ConsolePiRuntime {
|
|
|
2595
2635
|
catch {
|
|
2596
2636
|
// ignore
|
|
2597
2637
|
}
|
|
2638
|
+
void this.exploreOrchestrator.abortByParent(sessionId);
|
|
2598
2639
|
};
|
|
2599
2640
|
try {
|
|
2600
2641
|
if (options?.turnId && this.options.questionRegistry) {
|
|
@@ -2970,10 +3011,12 @@ export class ConsolePiRuntime {
|
|
|
2970
3011
|
this.thinkingSelections.delete(sessionId);
|
|
2971
3012
|
this.reloadInFlight.delete(sessionId);
|
|
2972
3013
|
this.pendingSteering.delete(sessionId);
|
|
3014
|
+
void this.exploreOrchestrator.abortByParent(sessionId);
|
|
2973
3015
|
}
|
|
2974
3016
|
disposeAll() {
|
|
2975
3017
|
for (const id of [...this.sessions.keys()])
|
|
2976
3018
|
this.dispose(id);
|
|
3019
|
+
void this.exploreOrchestrator.abortAll();
|
|
2977
3020
|
}
|
|
2978
3021
|
}
|
|
2979
3022
|
function mapAssistantMessageEvent(type, sessionId, message) {
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* The three-gate authorization (authorizeOperatorChatTool) is the actual
|
|
16
16
|
* enforcement; this adapter describes what that gate admits.
|
|
17
17
|
*/
|
|
18
|
-
import { OPERATOR_CHAT_ALLOWED_TOOLS, OPERATOR_CHAT_DENIED_TOOLS, OPERATOR_CHAT_DENIED_OPERATOR_ACTIONS, OPERATOR_CHAT_PI_BUILTIN_TOOLS, authorizeOperatorChatTool, filterOperatorChatTools, assertNoWriteToolInList, } from "./tools.js";
|
|
18
|
+
import { OPERATOR_CHAT_ALLOWED_TOOLS, OPERATOR_CHAT_DENIED_TOOLS, OPERATOR_CHAT_DENIED_OPERATOR_ACTIONS, OPERATOR_CHAT_PI_BUILTIN_TOOLS, OPERATOR_CHAT_BUILTIN_CUSTOM_TOOLS, authorizeOperatorChatTool, filterOperatorChatTools, assertNoWriteToolInList, } from "./tools.js";
|
|
19
19
|
/** Built-in Pi tool names allowed in every Chat session (ADR 0011). */
|
|
20
20
|
export const OPERATOR_CHAT_BUILTIN_EXPLORE_TOOL_IDS = Object.freeze([
|
|
21
21
|
...OPERATOR_CHAT_PI_BUILTIN_TOOLS,
|
|
@@ -42,6 +42,9 @@ export function createOperatorChatResourceLoader() {
|
|
|
42
42
|
if (explore) {
|
|
43
43
|
return { ok: true, toolId };
|
|
44
44
|
}
|
|
45
|
+
if (OPERATOR_CHAT_BUILTIN_CUSTOM_TOOLS.includes(toolId)) {
|
|
46
|
+
return { ok: true, toolId };
|
|
47
|
+
}
|
|
45
48
|
const decision = authorizeOperatorChatTool(toolId);
|
|
46
49
|
if (decision.ok)
|
|
47
50
|
active.add(decision.toolId);
|
|
@@ -517,6 +517,14 @@ export async function handleChatCapabilities(_req, res, deps) {
|
|
|
517
517
|
deniedTools: OPERATOR_CHAT_DENIED_TOOLS,
|
|
518
518
|
deniedOperatorActions: OPERATOR_CHAT_DENIED_OPERATOR_ACTIONS,
|
|
519
519
|
tools: toolSchemas,
|
|
520
|
+
builtinSubagents: [
|
|
521
|
+
{
|
|
522
|
+
toolId: "Agent",
|
|
523
|
+
agent: "Explore",
|
|
524
|
+
kind: "subagent",
|
|
525
|
+
description: "Start the built-in read-only Explore subagent for codebase search, symbol lookup, and call-chain tracing.",
|
|
526
|
+
},
|
|
527
|
+
],
|
|
520
528
|
// ADR 0011: full Pi tools including bash are default-active.
|
|
521
529
|
hasBash: true,
|
|
522
530
|
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parent-session Agent custom tool. First slice: Explore only, one-shot.
|
|
3
|
+
*/
|
|
4
|
+
import { AGENT_TOOL_DESCRIPTION, AGENT_TOOL_PROMPT_GUIDELINES, AGENT_TOOL_PROMPT_SNIPPET, parseExploreAgentParams, } from "./explore-agent.js";
|
|
5
|
+
import { OPERATOR_CHAT_AGENT_TOOL_NAME } from "./types.js";
|
|
6
|
+
function textResult(payload, isError = false) {
|
|
7
|
+
return {
|
|
8
|
+
content: [{ type: "text", text: JSON.stringify(payload) }],
|
|
9
|
+
details: payload,
|
|
10
|
+
...(isError ? { isError: true } : {}),
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export async function buildAgentCustomTool(options) {
|
|
14
|
+
const [{ Type }, { defineTool }] = await Promise.all([
|
|
15
|
+
import("typebox"),
|
|
16
|
+
import("@earendil-works/pi-coding-agent"),
|
|
17
|
+
]);
|
|
18
|
+
return defineTool({
|
|
19
|
+
name: OPERATOR_CHAT_AGENT_TOOL_NAME,
|
|
20
|
+
label: "Explore",
|
|
21
|
+
description: AGENT_TOOL_DESCRIPTION,
|
|
22
|
+
promptSnippet: AGENT_TOOL_PROMPT_SNIPPET,
|
|
23
|
+
promptGuidelines: [...AGENT_TOOL_PROMPT_GUIDELINES],
|
|
24
|
+
executionMode: "parallel",
|
|
25
|
+
parameters: Type.Object({
|
|
26
|
+
description: Type.String({
|
|
27
|
+
description: "Short 3-5 word summary shown on the tool card",
|
|
28
|
+
}),
|
|
29
|
+
prompt: Type.String({
|
|
30
|
+
description: "Complete Explore brief. Include goal, known clues, exclusions, success criteria, and thoroughness (quick | medium | very thorough).",
|
|
31
|
+
}),
|
|
32
|
+
subagent_type: Type.Optional(Type.String({
|
|
33
|
+
description: "Built-in agent name. Omit or set Explore.",
|
|
34
|
+
})),
|
|
35
|
+
}, { additionalProperties: false }),
|
|
36
|
+
async execute(_toolCallId, params, signal) {
|
|
37
|
+
const parsed = parseExploreAgentParams(params);
|
|
38
|
+
if (!parsed.ok) {
|
|
39
|
+
const failure = {
|
|
40
|
+
schemaVersion: 1,
|
|
41
|
+
kind: "subagent",
|
|
42
|
+
agent: "Explore",
|
|
43
|
+
status: "failed",
|
|
44
|
+
description: typeof params.description === "string" ? params.description : "",
|
|
45
|
+
error: parsed.message,
|
|
46
|
+
runId: "",
|
|
47
|
+
};
|
|
48
|
+
return textResult({
|
|
49
|
+
...failure,
|
|
50
|
+
code: parsed.code,
|
|
51
|
+
}, true);
|
|
52
|
+
}
|
|
53
|
+
const result = await options.orchestrator.launch({
|
|
54
|
+
parentSessionId: options.parentSessionId,
|
|
55
|
+
request: parsed.request,
|
|
56
|
+
signal,
|
|
57
|
+
model: options.resolveModel?.(),
|
|
58
|
+
});
|
|
59
|
+
return textResult(result, result.status !== "completed");
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in Explore role: parameter validation + Chinese system prompt.
|
|
3
|
+
* Frontmatter `tools: "*"` / `shellPolicy: unrestricted` are role metadata
|
|
4
|
+
* only — runtime tool args come from tool-policy.ts.
|
|
5
|
+
*/
|
|
6
|
+
import { EXPLORE_AGENT_NAME, EXPLORE_RESULT_KIND, EXPLORE_RESULT_MAX_BYTES, EXPLORE_RESULT_MAX_LINES, EXPLORE_RESULT_SCHEMA_VERSION, } from "./types.js";
|
|
7
|
+
const FORBIDDEN_OPTION_KEYS = [
|
|
8
|
+
"tasks",
|
|
9
|
+
"model",
|
|
10
|
+
"run_in_background",
|
|
11
|
+
"isolation",
|
|
12
|
+
"cwd",
|
|
13
|
+
"name",
|
|
14
|
+
];
|
|
15
|
+
export const EXPLORE_SYSTEM_PROMPT = [
|
|
16
|
+
"你是运行在 Pi 中的文件搜索和代码库探索专家。请按照用户要求的探索深度,彻底浏览仓库,并尽快返回基于证据的答案。",
|
|
17
|
+
"",
|
|
18
|
+
"=== 关键要求:只读模式,不得修改任何内容 ===",
|
|
19
|
+
"",
|
|
20
|
+
"这是一个只读探索任务。你严格禁止:",
|
|
21
|
+
"",
|
|
22
|
+
"- 创建、编辑、删除、移动或复制文件;",
|
|
23
|
+
"- 创建临时文件;",
|
|
24
|
+
"- 使用 shell 重定向、命令替换,或任何会改变仓库或系统状态的命令;",
|
|
25
|
+
"- 安装依赖或执行 Git 写操作。",
|
|
26
|
+
"",
|
|
27
|
+
"Pi 工具在技术上可能对你可见,但这不代表你获得了修改权限。只读行为是严格的角色要求。Console runtime 不会向你提供 `edit`、`write` 或 `bash`;即使出现可变工具,你也必须保持非修改行为。你的职责仅限于搜索、阅读和分析现有代码。",
|
|
28
|
+
"",
|
|
29
|
+
"你的优势包括:",
|
|
30
|
+
"",
|
|
31
|
+
"- 使用 find 和 ls 查找文件;",
|
|
32
|
+
"- 使用 safe-grep 搜索代码和文本;",
|
|
33
|
+
"- 使用 safe-read 阅读并分析相关文件;",
|
|
34
|
+
"- 使用 git-status / git-diff 做只读仓库检查;",
|
|
35
|
+
"- 追踪符号、调用者、数据流、配置、测试和集成点。",
|
|
36
|
+
"",
|
|
37
|
+
"请根据用户要求的探索深度调整工作量:",
|
|
38
|
+
"",
|
|
39
|
+
"- **quick**:定位最可能的文件、符号或定义,并直接回答;",
|
|
40
|
+
"- **medium**:检查主要实现、重要调用方和附近测试;",
|
|
41
|
+
"- **very thorough**:搜索多种命名约定和目录位置,追踪数据流和集成关系,检查测试与配置,并协调相互冲突的证据。",
|
|
42
|
+
"",
|
|
43
|
+
"工作指南:",
|
|
44
|
+
"",
|
|
45
|
+
"- 当位置不确定时,先从宽范围开始,再直接读取最相关的文件;",
|
|
46
|
+
"- 知道具体路径时使用 safe-read;",
|
|
47
|
+
"- 只做只读检查;git 工具是固定 argv,不是任意 shell;",
|
|
48
|
+
"- 根据请求的探索深度调整搜索策略;第一次查询不足时切换方法;",
|
|
49
|
+
"- 高效使用彼此独立的 grep、find、ls 和 read 操作;可能时并行执行独立搜索;",
|
|
50
|
+
"- 在 medium 或 very thorough 探索中,每完成一个有意义的阶段,以及每使用约 8–12 轮工具后,输出一到两句简短阶段说明。说明当前已经确认的事实、下一步准备检查的内容以及是否存在阻塞,然后继续工作。不要等到最终报告时才暴露所有进展;",
|
|
51
|
+
"- 在最终回答中汇报发现,不要创建报告文件。",
|
|
52
|
+
"",
|
|
53
|
+
"返回清晰的发现结果,并提供相关绝对路径和行号范围。将已确认事实与推断分开,并明确指出尚未解决的问题。",
|
|
54
|
+
].join("\n");
|
|
55
|
+
export const AGENT_TOOL_DESCRIPTION = "Start the built-in read-only Explore subagent for codebase search, symbol lookup, and call-chain tracing. One call launches one Fresh one-shot Explore session. Do not pass tasks, model, cwd, isolation, or run_in_background.";
|
|
56
|
+
export const AGENT_TOOL_PROMPT_SNIPPET = "Agent: launch built-in read-only Explore (Fresh, one-shot). Not for known single-file reads.";
|
|
57
|
+
export const AGENT_TOOL_PROMPT_GUIDELINES = Object.freeze([
|
|
58
|
+
"Delegate codebase exploration, cross-directory search, call-chain tracing, and path-uncertain repo questions to Agent (Explore).",
|
|
59
|
+
"Brief must include the goal, known clues, exclusions, success criteria, and thoroughness (quick | medium | very thorough).",
|
|
60
|
+
"Do not delegate a known single-file read or single-symbol lookup; use read/grep/find directly.",
|
|
61
|
+
"Call Agent multiple times in one turn for non-overlapping questions; do not pack unrelated questions into one prompt. Do not pass tasks.",
|
|
62
|
+
"Treat Explore output as advisory evidence; synthesize before answering. Never delegate DAG writes, Human Gate, or operator mutations.",
|
|
63
|
+
]);
|
|
64
|
+
function asRecord(value) {
|
|
65
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
66
|
+
return undefined;
|
|
67
|
+
return value;
|
|
68
|
+
}
|
|
69
|
+
function readTrimmedString(value) {
|
|
70
|
+
if (typeof value !== "string")
|
|
71
|
+
return undefined;
|
|
72
|
+
const trimmed = value.trim();
|
|
73
|
+
return trimmed ? trimmed : undefined;
|
|
74
|
+
}
|
|
75
|
+
function detectThoroughness(prompt) {
|
|
76
|
+
const lower = prompt.toLowerCase();
|
|
77
|
+
if (/\bvery\s+thorough\b/.test(lower) || lower.includes("非常彻底")) {
|
|
78
|
+
return "very thorough";
|
|
79
|
+
}
|
|
80
|
+
if (/\bmedium\b/.test(lower) || lower.includes("中等"))
|
|
81
|
+
return "medium";
|
|
82
|
+
if (/\bquick\b/.test(lower) || lower.includes("快速"))
|
|
83
|
+
return "quick";
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
export function parseExploreAgentParams(raw) {
|
|
87
|
+
const record = asRecord(raw);
|
|
88
|
+
if (!record) {
|
|
89
|
+
return {
|
|
90
|
+
ok: false,
|
|
91
|
+
code: "invalid-params",
|
|
92
|
+
message: "Agent requires an object with description and prompt",
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
for (const key of FORBIDDEN_OPTION_KEYS) {
|
|
96
|
+
if (!Object.prototype.hasOwnProperty.call(record, key))
|
|
97
|
+
continue;
|
|
98
|
+
const value = record[key];
|
|
99
|
+
if (value === undefined || value === null)
|
|
100
|
+
continue;
|
|
101
|
+
if (key === "tasks") {
|
|
102
|
+
return {
|
|
103
|
+
ok: false,
|
|
104
|
+
code: "tasks-not-supported",
|
|
105
|
+
message: "Agent tasks[] fan-out is not supported in this slice; call Agent once per independent Explore",
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
ok: false,
|
|
110
|
+
code: "unsupported-option",
|
|
111
|
+
message: `Agent option "${key}" is not supported for Console Explore`,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
const description = readTrimmedString(record.description);
|
|
115
|
+
const prompt = readTrimmedString(record.prompt);
|
|
116
|
+
if (!description || !prompt) {
|
|
117
|
+
return {
|
|
118
|
+
ok: false,
|
|
119
|
+
code: "invalid-params",
|
|
120
|
+
message: "Agent requires non-empty description and prompt",
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
const subagentRaw = record.subagent_type ?? record.subagentType;
|
|
124
|
+
const subagentType = readTrimmedString(subagentRaw) ?? EXPLORE_AGENT_NAME;
|
|
125
|
+
if (subagentType !== EXPLORE_AGENT_NAME) {
|
|
126
|
+
return {
|
|
127
|
+
ok: false,
|
|
128
|
+
code: "unknown-subagent-type",
|
|
129
|
+
message: `unknown subagent_type "${subagentType}"; only Explore is built in`,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
const thoroughness = detectThoroughness(prompt);
|
|
133
|
+
const request = {
|
|
134
|
+
description,
|
|
135
|
+
prompt,
|
|
136
|
+
subagentType: EXPLORE_AGENT_NAME,
|
|
137
|
+
...(thoroughness ? { thoroughness } : {}),
|
|
138
|
+
};
|
|
139
|
+
return { ok: true, request };
|
|
140
|
+
}
|
|
141
|
+
export function boundExploreText(text) {
|
|
142
|
+
const lines = text.split(/\r?\n/);
|
|
143
|
+
const clipped = lines.length > EXPLORE_RESULT_MAX_LINES
|
|
144
|
+
? `${lines.slice(0, EXPLORE_RESULT_MAX_LINES).join("\n")}\n…[truncated ${lines.length - EXPLORE_RESULT_MAX_LINES} lines]`
|
|
145
|
+
: text;
|
|
146
|
+
if (Buffer.byteLength(clipped, "utf8") <= EXPLORE_RESULT_MAX_BYTES) {
|
|
147
|
+
return clipped;
|
|
148
|
+
}
|
|
149
|
+
let out = clipped;
|
|
150
|
+
while (Buffer.byteLength(out, "utf8") > EXPLORE_RESULT_MAX_BYTES && out.length > 0) {
|
|
151
|
+
out = out.slice(0, Math.max(0, Math.floor(out.length * 0.9)));
|
|
152
|
+
}
|
|
153
|
+
return `${out}\n…[truncated to ${EXPLORE_RESULT_MAX_BYTES} bytes]`;
|
|
154
|
+
}
|
|
155
|
+
export function buildExploreResult(input) {
|
|
156
|
+
return {
|
|
157
|
+
schemaVersion: EXPLORE_RESULT_SCHEMA_VERSION,
|
|
158
|
+
kind: EXPLORE_RESULT_KIND,
|
|
159
|
+
agent: EXPLORE_AGENT_NAME,
|
|
160
|
+
status: input.status,
|
|
161
|
+
description: input.description,
|
|
162
|
+
runId: input.runId,
|
|
163
|
+
...(input.result !== undefined
|
|
164
|
+
? { result: boundExploreText(input.result) }
|
|
165
|
+
: {}),
|
|
166
|
+
...(input.error !== undefined ? { error: input.error } : {}),
|
|
167
|
+
...(input.usage ? { usage: input.usage } : {}),
|
|
168
|
+
};
|
|
169
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { AGENT_TOOL_DESCRIPTION, AGENT_TOOL_PROMPT_GUIDELINES, AGENT_TOOL_PROMPT_SNIPPET, EXPLORE_SYSTEM_PROMPT, boundExploreText, buildExploreResult, parseExploreAgentParams, } from "./explore-agent.js";
|
|
2
|
+
export { buildAgentCustomTool } from "./agent-tool.js";
|
|
3
|
+
export { ExploreOrchestrator } from "./orchestrator.js";
|
|
4
|
+
export { EXPLORE_ALLOWED_TOOLS, EXPLORE_CHILD_RESOURCE_LOADER_OPTIONS, EXPLORE_EXCLUDE_TOOLS, extrasOutsideExploreAllowlist, filterExploreCustomTools, isExploreAllowedTool, pinExploreToolNames, } from "./tool-policy.js";
|
|
5
|
+
export { EXPLORE_AGENT_NAME, OPERATOR_CHAT_AGENT_TOOL_NAME, } from "./types.js";
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fresh one-shot Explore child sessions for Operator Chat.
|
|
3
|
+
*
|
|
4
|
+
* Registry is a Map (never a singleton currentExplore) so two Agent calls in
|
|
5
|
+
* the same parent turn stay independently abortable. Child sessions use an
|
|
6
|
+
* in-memory SessionManager and a closed resource surface: no extensions,
|
|
7
|
+
* no operator tools, no write/bash.
|
|
8
|
+
*/
|
|
9
|
+
import { randomBytes } from "node:crypto";
|
|
10
|
+
import { extractAssistantText } from "../assistant-content.js";
|
|
11
|
+
import { buildExploreResult, EXPLORE_SYSTEM_PROMPT, } from "./explore-agent.js";
|
|
12
|
+
import { EXPLORE_ALLOWED_TOOLS, EXPLORE_CHILD_RESOURCE_LOADER_OPTIONS, EXPLORE_EXCLUDE_TOOLS, extrasOutsideExploreAllowlist, filterExploreCustomTools, pinExploreToolNames, } from "./tool-policy.js";
|
|
13
|
+
const EXPLORE_IDLE_TIMEOUT_MS = 180_000;
|
|
14
|
+
function newExploreRunId() {
|
|
15
|
+
return `explore-${randomBytes(8).toString("hex")}`;
|
|
16
|
+
}
|
|
17
|
+
async function waitForChildIdle(session, signal) {
|
|
18
|
+
const start = Date.now();
|
|
19
|
+
while (true) {
|
|
20
|
+
if (signal?.aborted)
|
|
21
|
+
return { timedOut: false };
|
|
22
|
+
if (session.isIdle === true)
|
|
23
|
+
return { timedOut: false };
|
|
24
|
+
if (session.isStreaming === false)
|
|
25
|
+
return { timedOut: false };
|
|
26
|
+
if (Date.now() - start > EXPLORE_IDLE_TIMEOUT_MS)
|
|
27
|
+
return { timedOut: true };
|
|
28
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export class ExploreOrchestrator {
|
|
32
|
+
options;
|
|
33
|
+
runs = new Map();
|
|
34
|
+
constructor(options) {
|
|
35
|
+
this.options = options;
|
|
36
|
+
}
|
|
37
|
+
liveCount() {
|
|
38
|
+
return this.runs.size;
|
|
39
|
+
}
|
|
40
|
+
liveRunIds() {
|
|
41
|
+
return [...this.runs.keys()];
|
|
42
|
+
}
|
|
43
|
+
async abortAll() {
|
|
44
|
+
const live = [...this.runs.values()];
|
|
45
|
+
await Promise.all(live.map((run) => run.abort().catch(() => undefined)));
|
|
46
|
+
}
|
|
47
|
+
/** Abort only the runs launched by one parent session (Stop must never
|
|
48
|
+
* kill another session's live Explore — exec-plan acceptance #7). */
|
|
49
|
+
async abortByParent(parentSessionId) {
|
|
50
|
+
const mine = [...this.runs.values()].filter((run) => run.parentSessionId === parentSessionId);
|
|
51
|
+
await Promise.all(mine.map((run) => run.abort().catch(() => undefined)));
|
|
52
|
+
}
|
|
53
|
+
async launch(input) {
|
|
54
|
+
if (input.signal?.aborted) {
|
|
55
|
+
return buildExploreResult({
|
|
56
|
+
status: "aborted",
|
|
57
|
+
description: input.request.description,
|
|
58
|
+
runId: newExploreRunId(),
|
|
59
|
+
error: "Explore aborted before start",
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
const runId = newExploreRunId();
|
|
63
|
+
const { cwd, bindings } = this.options;
|
|
64
|
+
let session;
|
|
65
|
+
let unsub;
|
|
66
|
+
let output = "";
|
|
67
|
+
let turns = 0;
|
|
68
|
+
let toolCalls = 0;
|
|
69
|
+
let promptError;
|
|
70
|
+
let abortPromise;
|
|
71
|
+
let settled = false;
|
|
72
|
+
let idleTimedOut = false;
|
|
73
|
+
/** True once abortChild ran — an abort without a parent signal (dispose
|
|
74
|
+
* path) must still converge to status=aborted, never a hollow
|
|
75
|
+
* "completed" with whatever partial text the SDK flushed. */
|
|
76
|
+
let childAborted = false;
|
|
77
|
+
const disposeChild = async () => {
|
|
78
|
+
unsub?.();
|
|
79
|
+
unsub = undefined;
|
|
80
|
+
try {
|
|
81
|
+
await session?.abort?.();
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
// ignore
|
|
85
|
+
}
|
|
86
|
+
try {
|
|
87
|
+
session?.dispose();
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
// ignore
|
|
91
|
+
}
|
|
92
|
+
session = undefined;
|
|
93
|
+
this.runs.delete(runId);
|
|
94
|
+
};
|
|
95
|
+
const abortChild = async () => {
|
|
96
|
+
if (abortPromise) {
|
|
97
|
+
await abortPromise;
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
childAborted = true;
|
|
101
|
+
abortPromise = (async () => {
|
|
102
|
+
try {
|
|
103
|
+
await session?.abort?.();
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
// ignore
|
|
107
|
+
}
|
|
108
|
+
})();
|
|
109
|
+
await abortPromise;
|
|
110
|
+
};
|
|
111
|
+
this.runs.set(runId, {
|
|
112
|
+
runId,
|
|
113
|
+
parentSessionId: input.parentSessionId,
|
|
114
|
+
description: input.request.description,
|
|
115
|
+
status: "starting",
|
|
116
|
+
startedAt: Date.now(),
|
|
117
|
+
abort: abortChild,
|
|
118
|
+
});
|
|
119
|
+
const onParentAbort = () => {
|
|
120
|
+
void abortChild();
|
|
121
|
+
};
|
|
122
|
+
input.signal?.addEventListener("abort", onParentAbort);
|
|
123
|
+
try {
|
|
124
|
+
if (!bindings.createInMemorySessionManager) {
|
|
125
|
+
return buildExploreResult({
|
|
126
|
+
status: "failed",
|
|
127
|
+
description: input.request.description,
|
|
128
|
+
runId,
|
|
129
|
+
error: "Explore requires an in-memory SessionManager",
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
const agentDir = await bindings.getAgentDir();
|
|
133
|
+
const { services } = await bindings.createServices({
|
|
134
|
+
cwd,
|
|
135
|
+
agentDir,
|
|
136
|
+
resourceLoaderOptions: {
|
|
137
|
+
...EXPLORE_CHILD_RESOURCE_LOADER_OPTIONS,
|
|
138
|
+
systemPrompt: EXPLORE_SYSTEM_PROMPT,
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
const sessionManager = await bindings.createInMemorySessionManager({
|
|
142
|
+
cwd,
|
|
143
|
+
});
|
|
144
|
+
const safeTools = await this.options.loadSafeExploreCustomTools();
|
|
145
|
+
const customTools = filterExploreCustomTools(safeTools);
|
|
146
|
+
const resolvedModel = input.model ??
|
|
147
|
+
((await this.options.resolveModel?.(services.modelRuntime)) ??
|
|
148
|
+
undefined);
|
|
149
|
+
const created = await bindings.createSessionFromServices({
|
|
150
|
+
services,
|
|
151
|
+
sessionManager,
|
|
152
|
+
tools: [...EXPLORE_ALLOWED_TOOLS],
|
|
153
|
+
excludeTools: [...EXPLORE_EXCLUDE_TOOLS],
|
|
154
|
+
customTools,
|
|
155
|
+
...(resolvedModel ? { model: resolvedModel } : {}),
|
|
156
|
+
});
|
|
157
|
+
session = created.session;
|
|
158
|
+
session.setActiveToolsByName(pinExploreToolNames());
|
|
159
|
+
const leaked = extrasOutsideExploreAllowlist(session.getActiveToolNames());
|
|
160
|
+
if (leaked.length > 0) {
|
|
161
|
+
return buildExploreResult({
|
|
162
|
+
status: "failed",
|
|
163
|
+
description: input.request.description,
|
|
164
|
+
runId,
|
|
165
|
+
error: `Explore child received forbidden tools: ${leaked.join(", ")}`,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
const live = this.runs.get(runId);
|
|
169
|
+
if (live)
|
|
170
|
+
live.status = "working";
|
|
171
|
+
unsub = session.subscribe((event) => {
|
|
172
|
+
if (event.type === "agent_start")
|
|
173
|
+
turns += 1;
|
|
174
|
+
if (event.type === "tool_execution_start")
|
|
175
|
+
toolCalls += 1;
|
|
176
|
+
if (event.type === "message_end") {
|
|
177
|
+
output = extractAssistantText(event.message);
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
if (input.signal?.aborted) {
|
|
181
|
+
await abortChild();
|
|
182
|
+
return buildExploreResult({
|
|
183
|
+
status: "aborted",
|
|
184
|
+
description: input.request.description,
|
|
185
|
+
runId,
|
|
186
|
+
error: "Explore aborted",
|
|
187
|
+
usage: { turns, toolCalls },
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
try {
|
|
191
|
+
await session.prompt(input.request.prompt, {
|
|
192
|
+
signal: input.signal,
|
|
193
|
+
});
|
|
194
|
+
const idle = await waitForChildIdle(session, input.signal);
|
|
195
|
+
idleTimedOut = idle.timedOut;
|
|
196
|
+
}
|
|
197
|
+
catch (error) {
|
|
198
|
+
if (input.signal?.aborted) {
|
|
199
|
+
promptError = "Explore aborted";
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
promptError = error instanceof Error ? error.message : String(error);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
if (abortPromise)
|
|
206
|
+
await abortPromise.catch(() => undefined);
|
|
207
|
+
settled = true;
|
|
208
|
+
if (childAborted || input.signal?.aborted) {
|
|
209
|
+
return buildExploreResult({
|
|
210
|
+
status: "aborted",
|
|
211
|
+
description: input.request.description,
|
|
212
|
+
runId,
|
|
213
|
+
error: "Explore aborted",
|
|
214
|
+
usage: { turns, toolCalls },
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
if (promptError) {
|
|
218
|
+
return buildExploreResult({
|
|
219
|
+
status: "failed",
|
|
220
|
+
description: input.request.description,
|
|
221
|
+
runId,
|
|
222
|
+
error: promptError,
|
|
223
|
+
usage: { turns, toolCalls },
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
if (idleTimedOut) {
|
|
227
|
+
// The turn itself finished but a continuation (auto-compaction /
|
|
228
|
+
// follow-up) never settled within the idle budget. Never report a
|
|
229
|
+
// partial transcript as a clean completion.
|
|
230
|
+
return buildExploreResult({
|
|
231
|
+
status: "failed",
|
|
232
|
+
description: input.request.description,
|
|
233
|
+
runId,
|
|
234
|
+
error: `Explore idle timeout after ${EXPLORE_IDLE_TIMEOUT_MS / 1000}s`,
|
|
235
|
+
usage: { turns, toolCalls },
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
return buildExploreResult({
|
|
239
|
+
status: "completed",
|
|
240
|
+
description: input.request.description,
|
|
241
|
+
runId,
|
|
242
|
+
result: output,
|
|
243
|
+
usage: { turns, toolCalls },
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
catch (error) {
|
|
247
|
+
const aborted = childAborted || Boolean(input.signal?.aborted);
|
|
248
|
+
let message;
|
|
249
|
+
if (aborted) {
|
|
250
|
+
message = "Explore aborted";
|
|
251
|
+
}
|
|
252
|
+
else if (error instanceof Error) {
|
|
253
|
+
message = error.message;
|
|
254
|
+
}
|
|
255
|
+
else {
|
|
256
|
+
message = String(error);
|
|
257
|
+
}
|
|
258
|
+
return buildExploreResult({
|
|
259
|
+
status: aborted ? "aborted" : "failed",
|
|
260
|
+
description: input.request.description,
|
|
261
|
+
runId,
|
|
262
|
+
error: message,
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
finally {
|
|
266
|
+
input.signal?.removeEventListener("abort", onParentAbort);
|
|
267
|
+
if (!settled && abortPromise) {
|
|
268
|
+
await abortPromise.catch(() => undefined);
|
|
269
|
+
}
|
|
270
|
+
await disposeChild();
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|