@fitchmultz/pi-subagents 0.0.0-stage → 0.44.5
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 +1654 -0
- package/LICENSE +21 -0
- package/README.md +1533 -2
- package/agents/context-builder.md +97 -0
- package/agents/debugger.md +51 -0
- package/agents/delegate.md +13 -0
- package/agents/fixer.md +37 -0
- package/agents/oracle.md +71 -0
- package/agents/planner.md +72 -0
- package/agents/researcher.md +51 -0
- package/agents/reviewer-claude.md +69 -0
- package/agents/reviewer-gpt.md +68 -0
- package/agents/reviewer-ponytail.md +82 -0
- package/agents/reviewer-security.md +81 -0
- package/agents/reviewer.md +69 -0
- package/agents/scout.md +66 -0
- package/agents/ui-designer.md +51 -0
- package/agents/watcher.md +26 -0
- package/agents/worker.md +46 -0
- package/agents/writer.md +36 -0
- package/dist/agents/agent-management.js +923 -0
- package/dist/agents/agent-scope.js +7 -0
- package/dist/agents/agent-selection.js +20 -0
- package/dist/agents/agent-serializer.js +100 -0
- package/dist/agents/agents.js +752 -0
- package/dist/agents/chain-serializer.js +367 -0
- package/dist/agents/frontmatter.js +21 -0
- package/dist/agents/identity.js +29 -0
- package/dist/agents/skills.js +570 -0
- package/dist/extension/build-info.js +1 -0
- package/dist/extension/compact-tools.js +112 -0
- package/dist/extension/config.js +22 -0
- package/dist/extension/control-notices.js +29 -0
- package/dist/extension/doctor.js +143 -0
- package/dist/extension/fanout-child.js +326 -0
- package/dist/extension/index.js +577 -0
- package/dist/extension/schemas.js +416 -0
- package/dist/extension/tool-input.js +20 -0
- package/dist/extension/tool-result.js +14 -0
- package/dist/history/canonical-result.js +81 -0
- package/dist/history/database.js +72 -0
- package/dist/history/index.js +199 -0
- package/dist/history/ingest.js +468 -0
- package/dist/history/queries.js +350 -0
- package/dist/history/store.js +216 -0
- package/dist/history/types.js +4 -0
- package/dist/history/worker.js +466 -0
- package/dist/intercom/intercom-bridge.js +112 -0
- package/dist/intercom/live-intercom.js +94 -0
- package/dist/intercom/result-intercom.js +373 -0
- package/dist/pi-intercom/agent-dir.js +10 -0
- package/dist/pi-intercom/bridge.js +750 -0
- package/dist/pi-intercom/broker/broker.js +426 -0
- package/dist/pi-intercom/broker/client.js +492 -0
- package/dist/pi-intercom/broker/framing.js +65 -0
- package/dist/pi-intercom/broker/paths.js +42 -0
- package/dist/pi-intercom/broker/pid.js +66 -0
- package/dist/pi-intercom/broker/spawn.js +221 -0
- package/dist/pi-intercom/config.js +102 -0
- package/dist/pi-intercom/index.js +2942 -0
- package/dist/pi-intercom/reply-tracker.js +182 -0
- package/dist/pi-intercom/session-targets.js +128 -0
- package/dist/pi-intercom/subagent-live-events.js +132 -0
- package/dist/pi-intercom/topics.js +157 -0
- package/dist/pi-intercom/types.js +163 -0
- package/dist/pi-intercom/ui/compose.js +269 -0
- package/dist/pi-intercom/ui/inline-message.js +80 -0
- package/dist/pi-intercom/ui/session-list.js +124 -0
- package/dist/runs/background/async-control.js +95 -0
- package/dist/runs/background/async-execution.js +676 -0
- package/dist/runs/background/async-job-tracker.js +383 -0
- package/dist/runs/background/async-result-file.js +58 -0
- package/dist/runs/background/async-resume.js +305 -0
- package/dist/runs/background/async-status.js +442 -0
- package/dist/runs/background/completion-dedupe.js +69 -0
- package/dist/runs/background/completion-delivery.js +245 -0
- package/dist/runs/background/notify.js +71 -0
- package/dist/runs/background/parallel-groups.js +55 -0
- package/dist/runs/background/result-watcher.js +426 -0
- package/dist/runs/background/run-id-resolver.js +64 -0
- package/dist/runs/background/run-status.js +382 -0
- package/dist/runs/background/stale-run-reconciler.js +335 -0
- package/dist/runs/background/subagent-runner-launcher.js +55 -0
- package/dist/runs/background/subagent-runner.js +1872 -0
- package/dist/runs/background/top-level-async.js +5 -0
- package/dist/runs/foreground/chain-clarify.js +1152 -0
- package/dist/runs/foreground/clarify-invocation.js +72 -0
- package/dist/runs/foreground/execution-input.js +423 -0
- package/dist/runs/foreground/foreground-control.js +817 -0
- package/dist/runs/foreground/question-control.js +88 -0
- package/dist/runs/foreground/run-async-path.js +205 -0
- package/dist/runs/foreground/subagent-executor.js +442 -0
- package/dist/runs/foreground/subagent-params.js +165 -0
- package/dist/runs/foreground/wait-run.js +141 -0
- package/dist/runs/shared/acceptance-contract.js +253 -0
- package/dist/runs/shared/acceptance-evaluation.js +256 -0
- package/dist/runs/shared/acceptance-finalization.js +280 -0
- package/dist/runs/shared/acceptance-reports.js +147 -0
- package/dist/runs/shared/acceptance.js +4 -0
- package/dist/runs/shared/chain-outputs.js +110 -0
- package/dist/runs/shared/child-attempt.js +693 -0
- package/dist/runs/shared/child-execution-cwd.js +96 -0
- package/dist/runs/shared/child-observations.js +114 -0
- package/dist/runs/shared/claude-code.js +199 -0
- package/dist/runs/shared/completion-guard.js +31 -0
- package/dist/runs/shared/dynamic-fanout.js +274 -0
- package/dist/runs/shared/history-index.js +72 -0
- package/dist/runs/shared/mcp-direct-tool-allowlist.js +334 -0
- package/dist/runs/shared/model-fallback.js +185 -0
- package/dist/runs/shared/mutating-tool-guard.js +170 -0
- package/dist/runs/shared/native-finalization.js +101 -0
- package/dist/runs/shared/native-usage.js +153 -0
- package/dist/runs/shared/nested-events.js +700 -0
- package/dist/runs/shared/nested-path.js +48 -0
- package/dist/runs/shared/nested-render.js +108 -0
- package/dist/runs/shared/parallel-utils.js +61 -0
- package/dist/runs/shared/parent-receipts.js +68 -0
- package/dist/runs/shared/parent-usage.js +121 -0
- package/dist/runs/shared/pi-args.js +310 -0
- package/dist/runs/shared/pi-spawn.js +52 -0
- package/dist/runs/shared/run-history-queries.js +45 -0
- package/dist/runs/shared/run-history.js +110 -0
- package/dist/runs/shared/run-id-ambiguity.js +4 -0
- package/dist/runs/shared/run-records.js +679 -0
- package/dist/runs/shared/session-cwd-preload.js +33 -0
- package/dist/runs/shared/single-output.js +222 -0
- package/dist/runs/shared/streaming-text.js +15 -0
- package/dist/runs/shared/structured-output.js +55 -0
- package/dist/runs/shared/subagent-control.js +179 -0
- package/dist/runs/shared/subagent-prompt-runtime.js +197 -0
- package/dist/runs/shared/subagent-tool-loop-guard.js +66 -0
- package/dist/runs/shared/supervisor-questions.js +350 -0
- package/dist/runs/shared/workflow-graph.js +214 -0
- package/dist/runs/shared/workflow-policy.js +84 -0
- package/dist/runs/shared/worktree.js +525 -0
- package/dist/shared/agent-context-policy.js +126 -0
- package/dist/shared/artifacts.js +96 -0
- package/dist/shared/atomic-json.js +13 -0
- package/dist/shared/cooperative.js +20 -0
- package/dist/shared/execution-cwd.js +14 -0
- package/dist/shared/file-coalescer.js +21 -0
- package/dist/shared/fork-context.js +49 -0
- package/dist/shared/formatters.js +85 -0
- package/dist/shared/journal-reader.js +407 -0
- package/dist/shared/lazy-tools.js +32 -0
- package/dist/shared/model-info.js +68 -0
- package/dist/shared/native-session.js +15 -0
- package/dist/shared/native-tui.js +18 -0
- package/dist/shared/native-typebox.js +20 -0
- package/dist/shared/post-exit-stdio-guard.js +197 -0
- package/dist/shared/prompt-sections.js +8 -0
- package/dist/shared/session-entries.js +52 -0
- package/dist/shared/session-identity.js +14 -0
- package/dist/shared/settings.js +254 -0
- package/dist/shared/status-format.js +111 -0
- package/dist/shared/temp-root.js +114 -0
- package/dist/shared/types.js +207 -0
- package/dist/shared/ui-mode.js +3 -0
- package/dist/shared/utils.js +334 -0
- package/dist/slash/prompt-template-bridge.js +298 -0
- package/dist/slash/slash-bridge.js +116 -0
- package/dist/slash/slash-commands.js +607 -0
- package/dist/slash/slash-live-state.js +246 -0
- package/dist/tui/action-hints.js +75 -0
- package/dist/tui/agent-history.js +197 -0
- package/dist/tui/agent-view.js +1656 -0
- package/dist/tui/render.js +1515 -0
- package/docs/intercom-bridge.md +212 -0
- package/docs/intercom.md +581 -0
- package/docs/performance.md +121 -0
- package/package.json +102 -4
- package/prompts/gather-context-and-clarify.md +14 -0
- package/prompts/parallel-cleanup.md +62 -0
- package/prompts/parallel-context-build.md +56 -0
- package/prompts/parallel-handoff-plan.md +62 -0
- package/prompts/parallel-research.md +51 -0
- package/prompts/parallel-review.md +59 -0
- package/prompts/review-loop.md +42 -0
- package/scripts/build.mjs +134 -0
- package/scripts/check-compat.mjs +48 -0
- package/scripts/compat-host.mjs +19 -0
- package/scripts/intercom-bridge-credentials.mjs +143 -0
- package/scripts/intercom-remote.mjs +103 -0
- package/scripts/linux-smoke.sh +51 -0
- package/scripts/local-install-smoke.mjs +66 -0
- package/scripts/native-package-smoke.mjs +82 -0
- package/scripts/package-smoke.mjs +209 -0
- package/scripts/prepare.mjs +67 -0
- package/scripts/real-pi-smoke.mjs +415 -0
- package/scripts/run-tests.mjs +149 -0
- package/skills/pi-intercom/SKILL.md +113 -0
- package/skills/pi-intercom/evals/evals.json +29 -0
- package/skills/pi-intercom/evals/trigger-evals.json +47 -0
- package/skills/pi-intercom/references/peer-sessions.md +69 -0
- package/skills/pi-subagents/SKILL.md +76 -0
- package/skills/pi-subagents/evals/evals.json +29 -0
- package/skills/pi-subagents/evals/trigger-evals.json +42 -0
- package/skills/pi-subagents/references/full-orchestration-guide.md +863 -0
- package/src/agents/agent-management.ts +813 -0
- package/src/agents/agent-scope.ts +7 -0
- package/src/agents/agent-selection.ts +23 -0
- package/src/agents/agent-serializer.ts +99 -0
- package/src/agents/agents.ts +916 -0
- package/src/agents/chain-serializer.ts +329 -0
- package/src/agents/frontmatter.ts +21 -0
- package/src/agents/identity.ts +30 -0
- package/src/agents/skills.ts +637 -0
- package/src/extension/build-info.ts +2 -0
- package/src/extension/compact-tools.ts +114 -0
- package/src/extension/config.ts +23 -0
- package/src/extension/control-notices.ts +47 -0
- package/src/extension/doctor.ts +171 -0
- package/src/extension/fanout-child.ts +312 -0
- package/src/extension/index.ts +649 -0
- package/src/extension/schemas.ts +444 -0
- package/src/extension/tool-input.ts +19 -0
- package/src/extension/tool-result.ts +16 -0
- package/src/history/canonical-result.ts +62 -0
- package/src/history/database.ts +59 -0
- package/src/history/index.ts +156 -0
- package/src/history/ingest.ts +349 -0
- package/src/history/queries.ts +241 -0
- package/src/history/store.ts +203 -0
- package/src/history/types.ts +166 -0
- package/src/history/worker.ts +321 -0
- package/src/intercom/intercom-bridge.ts +123 -0
- package/src/intercom/live-intercom.ts +104 -0
- package/src/intercom/result-intercom.ts +429 -0
- package/src/pi-intercom/agent-dir.ts +9 -0
- package/src/pi-intercom/bridge.ts +668 -0
- package/src/pi-intercom/broker/broker.ts +463 -0
- package/src/pi-intercom/broker/client.ts +558 -0
- package/src/pi-intercom/broker/framing.ts +77 -0
- package/src/pi-intercom/broker/paths.ts +47 -0
- package/src/pi-intercom/broker/pid.ts +59 -0
- package/src/pi-intercom/broker/spawn.ts +248 -0
- package/src/pi-intercom/config.ts +142 -0
- package/src/pi-intercom/index.ts +2929 -0
- package/src/pi-intercom/reply-tracker.ts +211 -0
- package/src/pi-intercom/session-targets.ts +172 -0
- package/src/pi-intercom/subagent-live-events.ts +133 -0
- package/src/pi-intercom/topics.ts +125 -0
- package/src/pi-intercom/types.ts +271 -0
- package/src/pi-intercom/ui/compose.ts +289 -0
- package/src/pi-intercom/ui/inline-message.ts +92 -0
- package/src/pi-intercom/ui/session-list.ts +139 -0
- package/src/runs/background/async-control.ts +89 -0
- package/src/runs/background/async-execution.ts +850 -0
- package/src/runs/background/async-job-tracker.ts +385 -0
- package/src/runs/background/async-result-file.ts +62 -0
- package/src/runs/background/async-resume.ts +333 -0
- package/src/runs/background/async-status.ts +481 -0
- package/src/runs/background/completion-dedupe.ts +79 -0
- package/src/runs/background/completion-delivery.ts +195 -0
- package/src/runs/background/notify.ts +123 -0
- package/src/runs/background/parallel-groups.ts +53 -0
- package/src/runs/background/result-watcher.ts +423 -0
- package/src/runs/background/run-id-resolver.ts +69 -0
- package/src/runs/background/run-status.ts +384 -0
- package/src/runs/background/stale-run-reconciler.ts +369 -0
- package/src/runs/background/subagent-runner-launcher.ts +56 -0
- package/src/runs/background/subagent-runner.ts +2053 -0
- package/src/runs/background/top-level-async.ts +13 -0
- package/src/runs/foreground/chain-clarify.ts +1340 -0
- package/src/runs/foreground/clarify-invocation.ts +77 -0
- package/src/runs/foreground/execution-input.ts +467 -0
- package/src/runs/foreground/foreground-control.ts +821 -0
- package/src/runs/foreground/question-control.ts +82 -0
- package/src/runs/foreground/run-async-path.ts +243 -0
- package/src/runs/foreground/subagent-executor.ts +501 -0
- package/src/runs/foreground/subagent-params.ts +320 -0
- package/src/runs/foreground/wait-run.ts +124 -0
- package/src/runs/shared/acceptance-contract.ts +257 -0
- package/src/runs/shared/acceptance-evaluation.ts +275 -0
- package/src/runs/shared/acceptance-finalization.ts +353 -0
- package/src/runs/shared/acceptance-reports.ts +152 -0
- package/src/runs/shared/acceptance.ts +33 -0
- package/src/runs/shared/chain-outputs.ts +114 -0
- package/src/runs/shared/child-attempt.ts +623 -0
- package/src/runs/shared/child-execution-cwd.ts +91 -0
- package/src/runs/shared/child-observations.ts +99 -0
- package/src/runs/shared/claude-code.ts +243 -0
- package/src/runs/shared/completion-guard.ts +35 -0
- package/src/runs/shared/dynamic-fanout.ts +295 -0
- package/src/runs/shared/history-index.ts +69 -0
- package/src/runs/shared/mcp-direct-tool-allowlist.ts +376 -0
- package/src/runs/shared/model-fallback.ts +219 -0
- package/src/runs/shared/mutating-tool-guard.ts +194 -0
- package/src/runs/shared/native-finalization.ts +128 -0
- package/src/runs/shared/native-usage.ts +113 -0
- package/src/runs/shared/nested-events.ts +752 -0
- package/src/runs/shared/nested-path.ts +52 -0
- package/src/runs/shared/nested-render.ts +119 -0
- package/src/runs/shared/parallel-utils.ts +153 -0
- package/src/runs/shared/parent-receipts.ts +59 -0
- package/src/runs/shared/parent-usage.ts +121 -0
- package/src/runs/shared/pi-args.ts +350 -0
- package/src/runs/shared/pi-spawn.ts +52 -0
- package/src/runs/shared/run-history-queries.ts +44 -0
- package/src/runs/shared/run-history.ts +100 -0
- package/src/runs/shared/run-id-ambiguity.ts +4 -0
- package/src/runs/shared/run-records.ts +607 -0
- package/src/runs/shared/session-cwd-preload.ts +27 -0
- package/src/runs/shared/single-output.ts +267 -0
- package/src/runs/shared/streaming-text.ts +18 -0
- package/src/runs/shared/structured-output.ts +68 -0
- package/src/runs/shared/subagent-control.ts +219 -0
- package/src/runs/shared/subagent-prompt-runtime.ts +201 -0
- package/src/runs/shared/subagent-tool-loop-guard.ts +89 -0
- package/src/runs/shared/supervisor-questions.ts +415 -0
- package/src/runs/shared/workflow-graph.ts +220 -0
- package/src/runs/shared/workflow-policy.ts +125 -0
- package/src/runs/shared/worktree.ts +635 -0
- package/src/shared/agent-context-policy.ts +184 -0
- package/src/shared/artifacts.ts +95 -0
- package/src/shared/atomic-json.ts +16 -0
- package/src/shared/cooperative.ts +21 -0
- package/src/shared/execution-cwd.ts +14 -0
- package/src/shared/file-coalescer.ts +29 -0
- package/src/shared/fork-context.ts +68 -0
- package/src/shared/formatters.ts +84 -0
- package/src/shared/journal-reader.ts +333 -0
- package/src/shared/lazy-tools.ts +36 -0
- package/src/shared/model-info.ts +81 -0
- package/src/shared/native-session.ts +8 -0
- package/src/shared/native-tui.ts +10 -0
- package/src/shared/native-typebox.ts +14 -0
- package/src/shared/post-exit-stdio-guard.ts +204 -0
- package/src/shared/prompt-sections.ts +10 -0
- package/src/shared/session-entries.ts +49 -0
- package/src/shared/session-identity.ts +18 -0
- package/src/shared/settings.ts +384 -0
- package/src/shared/status-format.ts +123 -0
- package/src/shared/temp-root.ts +107 -0
- package/src/shared/types.ts +1260 -0
- package/src/shared/ui-mode.ts +5 -0
- package/src/shared/utils.ts +342 -0
- package/src/slash/prompt-template-bridge.ts +408 -0
- package/src/slash/slash-bridge.ts +178 -0
- package/src/slash/slash-commands.ts +617 -0
- package/src/slash/slash-live-state.ts +292 -0
- package/src/tui/action-hints.ts +69 -0
- package/src/tui/agent-history.ts +210 -0
- package/src/tui/agent-view.ts +1323 -0
- package/src/tui/render.ts +1550 -0
- package/tsconfig.build.json +12 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as os from "node:os";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { encodeNestedPathEnv, parseNestedPathEnv } from "./nested-path.js";
|
|
6
|
+
import { resolveMcpDirectToolNames } from "./mcp-direct-tool-allowlist.js";
|
|
7
|
+
import { STRUCTURED_OUTPUT_CAPTURE_ENV, STRUCTURED_OUTPUT_SCHEMA_ENV } from "./structured-output.js";
|
|
8
|
+
import { splitKnownThinkingSuffix } from "../../shared/model-info.js";
|
|
9
|
+
import { prepareChildExecutionCwd } from "./child-execution-cwd.js";
|
|
10
|
+
import { loadConfig } from "../../extension/config.js";
|
|
11
|
+
// Managed macOS environments can SIGKILL Node when one argv entry reaches ~930 UTF-8 bytes.
|
|
12
|
+
// Measure the full entry (including the `Task: ` prefix), not just the task body.
|
|
13
|
+
const TASK_ARG_LIMIT_BYTES = 900;
|
|
14
|
+
// Resolve sibling extensions in both layouts: TypeScript sources (tests, jiti) and compiled dist output.
|
|
15
|
+
const MODULE_EXTENSION = import.meta.url.endsWith(".ts") ? ".ts" : ".js";
|
|
16
|
+
const SESSION_CWD_PRELOAD_URL = new URL(`session-cwd-preload${MODULE_EXTENSION}`, import.meta.url).href;
|
|
17
|
+
const PROMPT_RUNTIME_EXTENSION_PATH = path.join(path.dirname(fileURLToPath(import.meta.url)), `subagent-prompt-runtime${MODULE_EXTENSION}`);
|
|
18
|
+
const FANOUT_CHILD_EXTENSION_PATH = path.join(path.dirname(fileURLToPath(import.meta.url)), "..", "..", "extension", `fanout-child${MODULE_EXTENSION}`);
|
|
19
|
+
export const SUBAGENT_CHILD_ENV = "PI_SUBAGENT_CHILD";
|
|
20
|
+
export const SUBAGENT_ORCHESTRATOR_TARGET_ENV = "PI_SUBAGENT_ORCHESTRATOR_TARGET";
|
|
21
|
+
export const SUBAGENT_RUN_ID_ENV = "PI_SUBAGENT_RUN_ID";
|
|
22
|
+
export const SUBAGENT_CHILD_AGENT_ENV = "PI_SUBAGENT_CHILD_AGENT";
|
|
23
|
+
export const SUBAGENT_CHILD_INDEX_ENV = "PI_SUBAGENT_CHILD_INDEX";
|
|
24
|
+
export const SUBAGENT_FANOUT_CHILD_ENV = "PI_SUBAGENT_FANOUT_CHILD";
|
|
25
|
+
export const SUBAGENT_EAGER_TOOL_ENV = "PI_SUBAGENT_EAGER_TOOL";
|
|
26
|
+
export const SUBAGENT_PARENT_EVENT_SINK_ENV = "PI_SUBAGENT_PARENT_EVENT_SINK";
|
|
27
|
+
export const SUBAGENT_PARENT_CONTROL_INBOX_ENV = "PI_SUBAGENT_PARENT_CONTROL_INBOX";
|
|
28
|
+
export const SUBAGENT_PARENT_ROOT_RUN_ID_ENV = "PI_SUBAGENT_PARENT_ROOT_RUN_ID";
|
|
29
|
+
export const SUBAGENT_PARENT_RUN_ID_ENV = "PI_SUBAGENT_PARENT_RUN_ID";
|
|
30
|
+
export const SUBAGENT_PARENT_CHILD_INDEX_ENV = "PI_SUBAGENT_PARENT_CHILD_INDEX";
|
|
31
|
+
export const SUBAGENT_PARENT_DEPTH_ENV = "PI_SUBAGENT_PARENT_DEPTH";
|
|
32
|
+
export const SUBAGENT_PARENT_PATH_ENV = "PI_SUBAGENT_PARENT_PATH";
|
|
33
|
+
export const SUBAGENT_PARENT_CAPABILITY_TOKEN_ENV = "PI_SUBAGENT_PARENT_CAPABILITY_TOKEN";
|
|
34
|
+
export const SUBAGENT_INHERITED_EXTENSIONS_JSON_ENV = "PI_SUBAGENT_INHERITED_EXTENSIONS_JSON";
|
|
35
|
+
export function applyThinkingSuffix(model, thinking) {
|
|
36
|
+
if (!model || !thinking)
|
|
37
|
+
return model;
|
|
38
|
+
if (splitKnownThinkingSuffix(model).thinkingSuffix)
|
|
39
|
+
return model;
|
|
40
|
+
return `${model}:${thinking}`;
|
|
41
|
+
}
|
|
42
|
+
export function normalizeChildProjectTrustPolicy(input) {
|
|
43
|
+
if (input === "approve" || input === "no-approve" || input === "inherit")
|
|
44
|
+
return input;
|
|
45
|
+
if (typeof input === "object" && input !== null && "childRuns" in input) {
|
|
46
|
+
return normalizeChildProjectTrustPolicy(input.childRuns);
|
|
47
|
+
}
|
|
48
|
+
return input === undefined ? "inherit" : "no-approve";
|
|
49
|
+
}
|
|
50
|
+
export function resolveConfiguredChildProjectTrustPolicy(input, argv = process.argv) {
|
|
51
|
+
const inherited = findInheritedProjectTrustFlag(argv);
|
|
52
|
+
const explicitInherit = input === "inherit" || (typeof input === "object" && input !== null && input.childRuns === "inherit");
|
|
53
|
+
if (explicitInherit)
|
|
54
|
+
return inherited ?? "inherit";
|
|
55
|
+
if (inherited === "no-approve")
|
|
56
|
+
return "no-approve";
|
|
57
|
+
if (input === undefined || (typeof input === "object" && input !== null && !("childRuns" in input)))
|
|
58
|
+
return "approve";
|
|
59
|
+
const normalized = normalizeChildProjectTrustPolicy(input);
|
|
60
|
+
return normalized === "inherit" ? "approve" : normalized;
|
|
61
|
+
}
|
|
62
|
+
function findInheritedProjectTrustFlag(argv) {
|
|
63
|
+
let inherited;
|
|
64
|
+
for (const arg of argv) {
|
|
65
|
+
if (arg === "--approve" || arg === "-a")
|
|
66
|
+
inherited = "approve";
|
|
67
|
+
if (arg === "--no-approve" || arg === "-na")
|
|
68
|
+
inherited = "no-approve";
|
|
69
|
+
}
|
|
70
|
+
return inherited;
|
|
71
|
+
}
|
|
72
|
+
export function resolveChildProjectTrustArgs(policy = "inherit", argv = process.argv) {
|
|
73
|
+
const inherited = findInheritedProjectTrustFlag(argv);
|
|
74
|
+
if (policy === "approve")
|
|
75
|
+
return inherited === "no-approve" ? ["--no-approve"] : ["--approve"];
|
|
76
|
+
if (policy === "no-approve")
|
|
77
|
+
return ["--no-approve"];
|
|
78
|
+
return inherited === "approve" ? ["--approve"] : inherited === "no-approve" ? ["--no-approve"] : [];
|
|
79
|
+
}
|
|
80
|
+
function inheritedRuntimeExtensionPaths(env = process.env) {
|
|
81
|
+
const raw = env[SUBAGENT_INHERITED_EXTENSIONS_JSON_ENV]?.trim();
|
|
82
|
+
if (!raw)
|
|
83
|
+
return [];
|
|
84
|
+
try {
|
|
85
|
+
const parsed = JSON.parse(raw);
|
|
86
|
+
if (!Array.isArray(parsed))
|
|
87
|
+
return [];
|
|
88
|
+
return parsed.filter((entry) => typeof entry === "string" && entry.trim().length > 0);
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
return [];
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// Read only through the header, not the potentially large transcript. Decode after
|
|
95
|
+
// joining chunks so a UTF-8 character split across reads is preserved.
|
|
96
|
+
function readSessionHeaderLine(file) {
|
|
97
|
+
let fd;
|
|
98
|
+
try {
|
|
99
|
+
fd = fs.openSync(file, "r");
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
if (error.code === "ENOENT" && !fs.lstatSync(file, { throwIfNoEntry: false }))
|
|
103
|
+
return undefined;
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
try {
|
|
107
|
+
const chunks = [];
|
|
108
|
+
const buffer = Buffer.alloc(4096);
|
|
109
|
+
while (true) {
|
|
110
|
+
const length = fs.readSync(fd, buffer, 0, buffer.length, null);
|
|
111
|
+
if (!length)
|
|
112
|
+
break;
|
|
113
|
+
const chunk = buffer.subarray(0, length);
|
|
114
|
+
const newline = chunk.indexOf(10);
|
|
115
|
+
chunks.push(Buffer.from(newline < 0 ? chunk : chunk.subarray(0, newline)));
|
|
116
|
+
if (newline >= 0)
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
120
|
+
}
|
|
121
|
+
finally {
|
|
122
|
+
fs.closeSync(fd);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
export function buildPiArgs(input) {
|
|
126
|
+
const args = [...input.baseArgs];
|
|
127
|
+
const env = {};
|
|
128
|
+
args.push(...resolveChildProjectTrustArgs(input.projectTrust));
|
|
129
|
+
if (input.sessionFile) {
|
|
130
|
+
prepareChildExecutionCwd(input.sessionFile, input.cwd);
|
|
131
|
+
fs.mkdirSync(path.dirname(input.sessionFile), { recursive: true });
|
|
132
|
+
args.push("--session", input.sessionFile);
|
|
133
|
+
if (input.cwd) {
|
|
134
|
+
let needsOverride = true;
|
|
135
|
+
try {
|
|
136
|
+
const line = readSessionHeaderLine(input.sessionFile);
|
|
137
|
+
if (line === undefined) {
|
|
138
|
+
// Preassigned new sessions (including acceptance) inherit the actual spawn cwd.
|
|
139
|
+
needsOverride = false;
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
const header = JSON.parse(line);
|
|
143
|
+
if (header && typeof header === "object" && "type" in header && header.type === "session"
|
|
144
|
+
&& "cwd" in header && typeof header.cwd === "string" && path.isAbsolute(header.cwd)) {
|
|
145
|
+
needsOverride = fs.realpathSync.native(header.cwd) !== fs.realpathSync.native(input.cwd);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
catch { /* Unknown headers or unavailable directories still need the native override. */ }
|
|
150
|
+
// Pi's SDK supports cwdOverride; its CLI has no --session-cwd flag.
|
|
151
|
+
// Keep normal resumes untouched and supply only a needed override before startup.
|
|
152
|
+
if (needsOverride) {
|
|
153
|
+
env.PI_SUBAGENT_SESSION_CWD = JSON.stringify({
|
|
154
|
+
sessionFile: path.resolve(input.cwd, input.sessionFile), cwd: input.cwd, nodeOptions: process.env.NODE_OPTIONS,
|
|
155
|
+
});
|
|
156
|
+
env.NODE_OPTIONS = `${process.env.NODE_OPTIONS ?? ""} --import=${SESSION_CWD_PRELOAD_URL}`.trim();
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
if (!input.sessionEnabled) {
|
|
162
|
+
args.push("--no-session");
|
|
163
|
+
}
|
|
164
|
+
if (input.sessionDir) {
|
|
165
|
+
fs.mkdirSync(input.sessionDir, { recursive: true });
|
|
166
|
+
args.push("--session-dir", input.sessionDir);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
const modelArg = applyThinkingSuffix(input.model, input.thinking);
|
|
170
|
+
if (modelArg) {
|
|
171
|
+
args.push("--model", modelArg);
|
|
172
|
+
}
|
|
173
|
+
const declaredBuiltinTools = input.tools?.filter((tool) => !(tool.includes("/") || tool.endsWith(".ts") || tool.endsWith(".js"))) ?? [];
|
|
174
|
+
const fanoutAuthorized = input.allowSubagents === true || declaredBuiltinTools.includes("subagent");
|
|
175
|
+
const compactChildTools = fanoutAuthorized && loadConfig().compactChildTools !== false;
|
|
176
|
+
// Preserve an explicit advanced-tool policy, but never inherit it into another profile.
|
|
177
|
+
env[SUBAGENT_EAGER_TOOL_ENV] = declaredBuiltinTools.includes("subagent") ? "1" : undefined;
|
|
178
|
+
const toolExtensionPaths = [];
|
|
179
|
+
if (input.tools?.length) {
|
|
180
|
+
const builtinTools = [...declaredBuiltinTools];
|
|
181
|
+
if (fanoutAuthorized && declaredBuiltinTools.length > 0) {
|
|
182
|
+
for (const tool of compactChildTools ? ["subagent", "delegate", "agent_runs", "load_subagent"] : ["subagent"]) {
|
|
183
|
+
if (!builtinTools.includes(tool))
|
|
184
|
+
builtinTools.push(tool);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
if (input.structuredOutput && builtinTools.length > 0 && !builtinTools.includes("structured_output"))
|
|
188
|
+
builtinTools.push("structured_output");
|
|
189
|
+
for (const tool of input.tools) {
|
|
190
|
+
if (!declaredBuiltinTools.includes(tool) && (tool.includes("/") || tool.endsWith(".ts") || tool.endsWith(".js"))) {
|
|
191
|
+
toolExtensionPaths.push(tool);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (builtinTools.length > 0) {
|
|
195
|
+
if (input.mcpDirectTools?.length) {
|
|
196
|
+
builtinTools.push(...resolveMcpDirectToolNames(input.mcpDirectTools, input.cwd));
|
|
197
|
+
}
|
|
198
|
+
args.push("--tools", builtinTools.join(","));
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
const runtimeExtensions = fanoutAuthorized
|
|
202
|
+
? [PROMPT_RUNTIME_EXTENSION_PATH, FANOUT_CHILD_EXTENSION_PATH, ...inheritedRuntimeExtensionPaths()]
|
|
203
|
+
: [PROMPT_RUNTIME_EXTENSION_PATH, ...inheritedRuntimeExtensionPaths()];
|
|
204
|
+
if (input.extensions !== undefined) {
|
|
205
|
+
args.push("--no-extensions");
|
|
206
|
+
for (const extPath of [...new Set([...runtimeExtensions, ...toolExtensionPaths, ...input.extensions])]) {
|
|
207
|
+
args.push("--extension", extPath);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
for (const extPath of [...new Set([...runtimeExtensions, ...toolExtensionPaths])]) {
|
|
212
|
+
args.push("--extension", extPath);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
if (!input.inheritSkills) {
|
|
216
|
+
args.push("--no-skills");
|
|
217
|
+
}
|
|
218
|
+
if (!input.inheritProjectContext) {
|
|
219
|
+
args.push("--no-context-files");
|
|
220
|
+
}
|
|
221
|
+
let tempDir;
|
|
222
|
+
if (input.systemPrompt !== undefined && input.systemPrompt !== null) {
|
|
223
|
+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagent-"));
|
|
224
|
+
const promptPath = path.join(tempDir, "system.md");
|
|
225
|
+
fs.writeFileSync(promptPath, input.systemPrompt, { mode: 0o600 });
|
|
226
|
+
args.push(input.systemPromptMode === "replace" ? "--system-prompt" : "--append-system-prompt", promptPath);
|
|
227
|
+
}
|
|
228
|
+
const taskArg = `Task: ${input.task}`;
|
|
229
|
+
if (Buffer.byteLength(taskArg) > TASK_ARG_LIMIT_BYTES) {
|
|
230
|
+
if (!tempDir) {
|
|
231
|
+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagent-"));
|
|
232
|
+
}
|
|
233
|
+
const taskFilePath = path.join(tempDir, "task.md");
|
|
234
|
+
fs.writeFileSync(taskFilePath, taskArg, { mode: 0o600 });
|
|
235
|
+
args.push(`@${taskFilePath}`);
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
args.push(taskArg);
|
|
239
|
+
}
|
|
240
|
+
env[SUBAGENT_CHILD_ENV] = "1";
|
|
241
|
+
env.PI_SUBAGENT_ROOT_SESSION_ID = input.rootSessionId ?? (process.env[SUBAGENT_CHILD_ENV] === "1" ? process.env.PI_SUBAGENT_ROOT_SESSION_ID : undefined);
|
|
242
|
+
env[SUBAGENT_FANOUT_CHILD_ENV] = fanoutAuthorized ? "1" : "0";
|
|
243
|
+
const inheritedNestedRoute = Boolean(process.env[SUBAGENT_PARENT_EVENT_SINK_ENV] && process.env[SUBAGENT_PARENT_ROOT_RUN_ID_ENV] && process.env[SUBAGENT_PARENT_CAPABILITY_TOKEN_ENV]);
|
|
244
|
+
const parentRunId = input.parentRunId ?? input.runId ?? (inheritedNestedRoute ? process.env[SUBAGENT_RUN_ID_ENV] : undefined) ?? process.env[SUBAGENT_PARENT_RUN_ID_ENV] ?? "";
|
|
245
|
+
const parentChildIndex = input.parentChildIndex !== undefined
|
|
246
|
+
? String(input.parentChildIndex)
|
|
247
|
+
: input.childIndex !== undefined
|
|
248
|
+
? String(input.childIndex)
|
|
249
|
+
: process.env[SUBAGENT_PARENT_CHILD_INDEX_ENV] ?? "";
|
|
250
|
+
const inheritedDepth = Number(process.env[SUBAGENT_PARENT_DEPTH_ENV]);
|
|
251
|
+
const parentDepth = input.parentDepth ?? (inheritedNestedRoute && Number.isFinite(inheritedDepth) ? inheritedDepth + 1 : 1);
|
|
252
|
+
const parentPath = input.parentPath ?? [
|
|
253
|
+
...parseNestedPathEnv(process.env[SUBAGENT_PARENT_PATH_ENV]),
|
|
254
|
+
...(parentRunId ? [{
|
|
255
|
+
runId: parentRunId,
|
|
256
|
+
...(parentChildIndex && /^\d+$/.test(parentChildIndex) ? { stepIndex: Number(parentChildIndex) } : {}),
|
|
257
|
+
...(input.childAgentName ? { agent: input.childAgentName } : {}),
|
|
258
|
+
}] : []),
|
|
259
|
+
];
|
|
260
|
+
env[SUBAGENT_PARENT_EVENT_SINK_ENV] = fanoutAuthorized
|
|
261
|
+
? input.parentEventSink ?? process.env[SUBAGENT_PARENT_EVENT_SINK_ENV] ?? ""
|
|
262
|
+
: "";
|
|
263
|
+
env[SUBAGENT_PARENT_CONTROL_INBOX_ENV] = fanoutAuthorized
|
|
264
|
+
? input.parentControlInbox ?? process.env[SUBAGENT_PARENT_CONTROL_INBOX_ENV] ?? ""
|
|
265
|
+
: "";
|
|
266
|
+
env[SUBAGENT_PARENT_ROOT_RUN_ID_ENV] = fanoutAuthorized
|
|
267
|
+
? input.parentRootRunId ?? process.env[SUBAGENT_PARENT_ROOT_RUN_ID_ENV] ?? input.runId ?? ""
|
|
268
|
+
: "";
|
|
269
|
+
env[SUBAGENT_PARENT_RUN_ID_ENV] = fanoutAuthorized ? parentRunId : "";
|
|
270
|
+
env[SUBAGENT_PARENT_CHILD_INDEX_ENV] = fanoutAuthorized ? parentChildIndex : "";
|
|
271
|
+
env[SUBAGENT_PARENT_DEPTH_ENV] = fanoutAuthorized ? String(parentDepth) : "";
|
|
272
|
+
env[SUBAGENT_PARENT_PATH_ENV] = fanoutAuthorized ? encodeNestedPathEnv(parentPath) : "";
|
|
273
|
+
env[SUBAGENT_PARENT_CAPABILITY_TOKEN_ENV] = fanoutAuthorized
|
|
274
|
+
? input.parentCapabilityToken ?? process.env[SUBAGENT_PARENT_CAPABILITY_TOKEN_ENV] ?? ""
|
|
275
|
+
: "";
|
|
276
|
+
env.PI_SUBAGENT_INHERIT_PROJECT_CONTEXT = input.inheritProjectContext ? "1" : "0";
|
|
277
|
+
env.PI_SUBAGENT_INHERIT_SKILLS = input.inheritSkills ? "1" : "0";
|
|
278
|
+
if (input.intercomSessionName) {
|
|
279
|
+
env.PI_SUBAGENT_INTERCOM_SESSION_NAME = input.intercomSessionName;
|
|
280
|
+
}
|
|
281
|
+
if (input.orchestratorIntercomTarget) {
|
|
282
|
+
env[SUBAGENT_ORCHESTRATOR_TARGET_ENV] = input.orchestratorIntercomTarget;
|
|
283
|
+
}
|
|
284
|
+
if (input.runId) {
|
|
285
|
+
env[SUBAGENT_RUN_ID_ENV] = input.runId;
|
|
286
|
+
}
|
|
287
|
+
if (input.childAgentName) {
|
|
288
|
+
env[SUBAGENT_CHILD_AGENT_ENV] = input.childAgentName;
|
|
289
|
+
}
|
|
290
|
+
if (input.childIndex !== undefined) {
|
|
291
|
+
env[SUBAGENT_CHILD_INDEX_ENV] = String(input.childIndex);
|
|
292
|
+
}
|
|
293
|
+
if (input.mcpDirectTools?.length) {
|
|
294
|
+
env.MCP_DIRECT_TOOLS = input.mcpDirectTools.join(",");
|
|
295
|
+
}
|
|
296
|
+
// A nested helper must never submit into its parent's capture.
|
|
297
|
+
env[STRUCTURED_OUTPUT_CAPTURE_ENV] = input.structuredOutput?.outputPath ?? "";
|
|
298
|
+
env[STRUCTURED_OUTPUT_SCHEMA_ENV] = input.structuredOutput?.schemaPath ?? "";
|
|
299
|
+
return { args, env, tempDir };
|
|
300
|
+
}
|
|
301
|
+
export function cleanupTempDir(tempDir) {
|
|
302
|
+
if (!tempDir)
|
|
303
|
+
return;
|
|
304
|
+
try {
|
|
305
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
306
|
+
}
|
|
307
|
+
catch {
|
|
308
|
+
// Temp cleanup is best effort.
|
|
309
|
+
}
|
|
310
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
export const PI_CODING_AGENT_PACKAGE = "@earendil-works/pi-coding-agent";
|
|
5
|
+
export function findPiPackageRootFromEntry(entryPoint) {
|
|
6
|
+
let dir = path.dirname(entryPoint);
|
|
7
|
+
while (dir !== path.dirname(dir)) {
|
|
8
|
+
const packageJsonPath = path.join(dir, "package.json");
|
|
9
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
10
|
+
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
11
|
+
if (pkg.name === PI_CODING_AGENT_PACKAGE)
|
|
12
|
+
return dir;
|
|
13
|
+
}
|
|
14
|
+
dir = path.dirname(dir);
|
|
15
|
+
}
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
export function resolveInstalledPiPackageRoot() {
|
|
19
|
+
try {
|
|
20
|
+
return findPiPackageRootFromEntry(fileURLToPath(import.meta.resolve(PI_CODING_AGENT_PACKAGE)));
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
for (const dir of (process.env.PATH ?? "").split(path.delimiter)) {
|
|
24
|
+
try {
|
|
25
|
+
const root = findPiPackageRootFromEntry(fs.realpathSync(path.join(dir, "pi")));
|
|
26
|
+
if (root)
|
|
27
|
+
return root;
|
|
28
|
+
}
|
|
29
|
+
catch { /* PATH entries can disappear or be wrappers rather than Pi. */ }
|
|
30
|
+
}
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export function resolvePiPackageRoot() {
|
|
35
|
+
try {
|
|
36
|
+
const entry = process.argv[1];
|
|
37
|
+
return entry ? findPiPackageRootFromEntry(fs.realpathSync(entry)) : undefined;
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
// process.argv[1] probing is best-effort; callers can fall back to PATH/package resolution.
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
export function requirePiPackageRoot() {
|
|
45
|
+
const root = process.env.PI_PACKAGE_DIR || resolvePiPackageRoot() || resolveInstalledPiPackageRoot();
|
|
46
|
+
if (!root)
|
|
47
|
+
throw new Error("Could not locate Pi runtime APIs; Pi must be installed and available on PATH.");
|
|
48
|
+
return root;
|
|
49
|
+
}
|
|
50
|
+
export function getPiSpawnCommand(args) {
|
|
51
|
+
return { command: "pi", args };
|
|
52
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { runHistoryIndex } from "./history-index.js";
|
|
2
|
+
import { ownedRunView, resolveOwnedRun } from "./run-records.js";
|
|
3
|
+
/** Browse observations never confer ownership or replace canonical control/receipt checks. */
|
|
4
|
+
export async function ownedHistoryQuery(state, params, signal) {
|
|
5
|
+
const requested = params.id ?? params.runId;
|
|
6
|
+
const run = requested ? resolveOwnedRun(state, requested) : undefined;
|
|
7
|
+
if (requested && !run)
|
|
8
|
+
throw new Error("Run not found in this owning session.");
|
|
9
|
+
const index = await runHistoryIndex(state, true);
|
|
10
|
+
if (params.action === "history") {
|
|
11
|
+
if (!run)
|
|
12
|
+
throw new Error("History requires an owned run ID.");
|
|
13
|
+
const view = ownedRunView(run, state, { includeContinuations: false, readConfiguration: false, reconcile: false });
|
|
14
|
+
const child = params.index === undefined && view.children.length === 1 ? view.children[0] : view.children.find((child) => child.index === params.index);
|
|
15
|
+
if (!child)
|
|
16
|
+
throw new Error("Choose index for a run with multiple children.");
|
|
17
|
+
const input = { runId: run.runId, index: child.index, limit: params.limit, before: params.before, cursor: params.cursor, signal,
|
|
18
|
+
...(child.state !== "live" ? { terminalEntryId: child.result?.terminalEntryId, endedAt: child.result?.terminalEntryId ? undefined : view.updatedAt } : {}) };
|
|
19
|
+
const page = await index.historyPage(input);
|
|
20
|
+
const earlier = page.previousCursor ? { action: "history", id: run.runId, index: child.index, limit: params.limit ?? 100, cursor: page.previousCursor } : undefined;
|
|
21
|
+
return { content: [{ type: "text", text: [
|
|
22
|
+
`History: ${run.runId}, child ${child.index} (${child.agent}); ${page.entries.length} of ${page.count} indexed native entries.`,
|
|
23
|
+
`Browse index: ${page.freshness.state}. These are bounded previews, not completion or delivery receipts.`,
|
|
24
|
+
...(page.unavailable ? [page.unavailable] : []),
|
|
25
|
+
...page.entries.map((entry) => `- ${entry.id} · position ${entry.sequence}\n${JSON.stringify(entry.entry)}`),
|
|
26
|
+
...(earlier ? [`Earlier: agent_runs(${JSON.stringify(earlier)})`] : []),
|
|
27
|
+
...(child.sessionFile ? [`Native source: ${child.sessionFile}. Open a selected record in Agents for validated full details.`] : []),
|
|
28
|
+
].join("\n") }], details: { mode: "management", results: [], runId: run.runId, history: page } };
|
|
29
|
+
}
|
|
30
|
+
if (params.action !== "search" || !params.query)
|
|
31
|
+
throw new Error("Search requires query.");
|
|
32
|
+
if (params.sort && !["relevance", "newest"].includes(params.sort))
|
|
33
|
+
throw new Error("Search sort must be relevance or newest.");
|
|
34
|
+
const input = { query: params.query, runId: run?.runId, index: params.index, limit: params.limit, cursor: params.cursor,
|
|
35
|
+
sort: params.sort, agent: params.agent, signal };
|
|
36
|
+
const page = await index.search(input);
|
|
37
|
+
const next = page.nextCursor ? { action: "search", query: params.query, ...(run ? { id: run.runId } : {}), ...(params.index !== undefined ? { index: params.index } : {}),
|
|
38
|
+
...(params.agent ? { agent: params.agent } : {}), ...(params.sort ? { sort: params.sort } : {}), limit: params.limit ?? 20, cursor: page.nextCursor } : undefined;
|
|
39
|
+
return { content: [{ type: "text", text: [
|
|
40
|
+
`Saved-text search: ${page.matches.length} matches in this owning session; index ${page.freshness.state}.`,
|
|
41
|
+
"Indexed excerpts may be stale. They do not establish completion, ownership, or delivery; full selected records are validated separately.",
|
|
42
|
+
...page.matches.map((match) => `- ${match.runId}:${match.index} (${match.agent}) · entry ${match.entryId}\n${match.preview}\n${match.sessionFile}:${match.ref.start}`),
|
|
43
|
+
...(next ? [`Next: agent_runs(${JSON.stringify(next)})`] : []),
|
|
44
|
+
].join("\n") }], details: { mode: "management", results: [], historySearch: page } };
|
|
45
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { historyDirectory, historyTransaction, openHistoryDatabase } from "../../history/database.js";
|
|
4
|
+
import { getAgentDir } from "../../shared/utils.js";
|
|
5
|
+
const MAX_AGENT_SAMPLES = 1000;
|
|
6
|
+
const MAX_LEGACY_BYTES = 1024 * 1024;
|
|
7
|
+
const TIMING_DATABASE = "run-timing.sqlite";
|
|
8
|
+
function validEntry(value) {
|
|
9
|
+
if (!value || typeof value !== "object")
|
|
10
|
+
return false;
|
|
11
|
+
const entry = value;
|
|
12
|
+
return typeof entry.agent === "string" && entry.agent.length > 0 && entry.agent.length <= 256
|
|
13
|
+
&& typeof entry.task === "string" && Number.isSafeInteger(entry.ts) && entry.ts >= 0
|
|
14
|
+
&& (entry.status === "ok" || entry.status === "error") && Number.isFinite(entry.duration) && entry.duration >= 0
|
|
15
|
+
&& (entry.exit === undefined || Number.isSafeInteger(entry.exit));
|
|
16
|
+
}
|
|
17
|
+
/** Legacy samples are read-only, complete LF records from a bounded tail. */
|
|
18
|
+
function legacySamples(agentDir, agent) {
|
|
19
|
+
let fd;
|
|
20
|
+
try {
|
|
21
|
+
fd = fs.openSync(path.join(agentDir, "run-history.jsonl"), "r");
|
|
22
|
+
const size = fs.fstatSync(fd).size, start = Math.max(0, size - MAX_LEGACY_BYTES);
|
|
23
|
+
const bytes = Buffer.alloc(size - start);
|
|
24
|
+
let count = 0;
|
|
25
|
+
while (count < bytes.length) {
|
|
26
|
+
const read = fs.readSync(fd, bytes, count, bytes.length - count, start + count);
|
|
27
|
+
if (!read)
|
|
28
|
+
break;
|
|
29
|
+
count += read;
|
|
30
|
+
}
|
|
31
|
+
const complete = bytes.subarray(0, count), first = start ? complete.indexOf(10) + 1 : 0, last = complete.lastIndexOf(10);
|
|
32
|
+
if (last < first || start && !first)
|
|
33
|
+
return [];
|
|
34
|
+
const samples = [], retained = new Map();
|
|
35
|
+
for (const line of complete.subarray(first, last).toString("utf8").split("\n").reverse()) {
|
|
36
|
+
try {
|
|
37
|
+
const entry = JSON.parse(line);
|
|
38
|
+
if (!validEntry(entry) || agent !== undefined && entry.agent !== agent || (retained.get(entry.agent) ?? 0) >= MAX_AGENT_SAMPLES)
|
|
39
|
+
continue;
|
|
40
|
+
samples.push({ ...entry, task: entry.task.slice(0, 200) });
|
|
41
|
+
retained.set(entry.agent, (retained.get(entry.agent) ?? 0) + 1);
|
|
42
|
+
if (agent !== undefined && samples.length === MAX_AGENT_SAMPLES)
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
catch { /* Incomplete or invalid timing samples are not evidence. */ }
|
|
46
|
+
}
|
|
47
|
+
return samples;
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return [];
|
|
51
|
+
}
|
|
52
|
+
finally {
|
|
53
|
+
if (fd !== undefined)
|
|
54
|
+
fs.closeSync(fd);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
export function recordRun(agent, task, exitCode, durationMs) {
|
|
58
|
+
const entry = { agent, task: task.slice(0, 200), ts: Math.floor(Date.now() / 1000), status: exitCode === 0 ? "ok" : "error", duration: durationMs, ...(exitCode !== 0 ? { exit: exitCode } : {}) };
|
|
59
|
+
if (!validEntry(entry))
|
|
60
|
+
return;
|
|
61
|
+
let db;
|
|
62
|
+
try {
|
|
63
|
+
const agentDir = getAgentDir();
|
|
64
|
+
fs.mkdirSync(agentDir, { recursive: true });
|
|
65
|
+
db = openHistoryDatabase(path.join(historyDirectory(agentDir), TIMING_DATABASE));
|
|
66
|
+
const connection = db;
|
|
67
|
+
const imported = connection.prepare("SELECT 1 FROM sqlite_schema WHERE type='table' AND name='timing_meta'").get()
|
|
68
|
+
&& connection.prepare("SELECT key FROM timing_meta WHERE key='legacy-imported'").get();
|
|
69
|
+
// Filesystem reads and JSON parsing must not consume another writer's admission budget.
|
|
70
|
+
const legacy = imported ? [] : legacySamples(agentDir).reverse();
|
|
71
|
+
// Allow brief native writer overlap; three attempts still bound best-effort admission.
|
|
72
|
+
connection.exec("PRAGMA busy_timeout=1000");
|
|
73
|
+
historyTransaction(connection, () => {
|
|
74
|
+
connection.exec(`CREATE TABLE IF NOT EXISTS timing_meta(key TEXT PRIMARY KEY);
|
|
75
|
+
CREATE TABLE IF NOT EXISTS samples(sequence INTEGER PRIMARY KEY,agent TEXT NOT NULL,task TEXT NOT NULL,ts INTEGER NOT NULL,status TEXT NOT NULL,duration REAL NOT NULL,exit INTEGER);
|
|
76
|
+
CREATE INDEX IF NOT EXISTS samples_agent ON samples(agent,sequence DESC);`);
|
|
77
|
+
const insert = connection.prepare("INSERT INTO samples(agent,task,ts,status,duration,exit) VALUES (?,?,?,?,?,?)");
|
|
78
|
+
const save = (sample) => insert.run(sample.agent, sample.task, sample.ts, sample.status, sample.duration, sample.exit ?? null);
|
|
79
|
+
if (!connection.prepare("SELECT key FROM timing_meta WHERE key='legacy-imported'").get()) {
|
|
80
|
+
// Another writer may have imported while this snapshot was being read.
|
|
81
|
+
for (const sample of legacy)
|
|
82
|
+
save(sample);
|
|
83
|
+
connection.exec("INSERT INTO timing_meta VALUES ('legacy-imported')");
|
|
84
|
+
}
|
|
85
|
+
save(entry);
|
|
86
|
+
connection.prepare("DELETE FROM samples WHERE agent=? AND sequence NOT IN (SELECT sequence FROM samples WHERE agent=? ORDER BY sequence DESC LIMIT ?)").run(agent, agent, MAX_AGENT_SAMPLES);
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
catch { /* Best-effort timing metadata must never interrupt a run. */ }
|
|
90
|
+
finally {
|
|
91
|
+
db?.close();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
export function loadRunsForAgent(agent) {
|
|
95
|
+
const agentDir = getAgentDir(), file = path.join(agentDir, "history-index", TIMING_DATABASE);
|
|
96
|
+
if (!fs.existsSync(file))
|
|
97
|
+
return legacySamples(agentDir, agent);
|
|
98
|
+
let db;
|
|
99
|
+
try {
|
|
100
|
+
db = openHistoryDatabase(path.join(historyDirectory(agentDir, false), TIMING_DATABASE), true);
|
|
101
|
+
return db.prepare("SELECT agent,task,ts,status,duration,exit FROM samples WHERE agent=? ORDER BY sequence DESC LIMIT ?").all(agent, MAX_AGENT_SAMPLES)
|
|
102
|
+
.map((row) => ({ agent: String(row.agent), task: String(row.task), ts: Number(row.ts), status: row.status, duration: Number(row.duration), ...(row.exit === null ? {} : { exit: Number(row.exit) }) }));
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
return [];
|
|
106
|
+
}
|
|
107
|
+
finally {
|
|
108
|
+
db?.close();
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export function formatRunIdAmbiguity(kind, prefix, candidates) {
|
|
2
|
+
const preview = candidates.slice(0, 5);
|
|
3
|
+
return `Ambiguous ${kind} run id prefix '${prefix}' matched: ${preview.join(", ")}. ${candidates.length} matches (showing ${preview.length}). Provide a longer prefix or full run id.`;
|
|
4
|
+
}
|