@selesai/code 0.13.12 → 0.13.14
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 +9 -0
- package/dist/extensions/pi-subagents/CHANGELOG.md +77 -1
- package/dist/extensions/pi-subagents/VISION.md +12 -1
- package/dist/extensions/pi-subagents/docs/agents.md +13 -6
- package/dist/extensions/pi-subagents/docs/configuration.md +35 -9
- package/dist/extensions/pi-subagents/docs/extension-api.md +1 -1
- package/dist/extensions/pi-subagents/docs/missions.md +1 -1
- package/dist/extensions/pi-subagents/docs/models.md +6 -6
- package/dist/extensions/pi-subagents/docs/observability.md +6 -3
- package/dist/extensions/pi-subagents/docs/tool-reference.md +3 -3
- package/dist/extensions/pi-subagents/docs/watchdog.md +92 -114
- package/dist/extensions/pi-subagents/docs/workflows.md +1 -1
- package/dist/extensions/pi-subagents/install.mjs +1 -2
- package/dist/extensions/pi-subagents/package.json +1 -1
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/constraints-and-recipes.md +1 -1
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +6 -6
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/management-authoring-rpc.md +2 -2
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/multi-lane-orchestration.md +1 -1
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/prompting-and-roles.md +4 -4
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/review-and-validation.md +1 -1
- package/dist/extensions/pi-subagents/src/agents/agent-management.ts +41 -4
- package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +3 -0
- package/dist/extensions/pi-subagents/src/agents/agents.ts +120 -124
- package/dist/extensions/pi-subagents/src/agents/runtime-agent-registry.ts +5 -1
- package/dist/extensions/pi-subagents/src/api/preflight.ts +4 -0
- package/dist/extensions/pi-subagents/src/api/shared-types.ts +3 -0
- package/dist/extensions/pi-subagents/src/extension/config.ts +20 -0
- package/dist/extensions/pi-subagents/src/extension/public-execution.ts +1 -0
- package/dist/extensions/pi-subagents/src/extension/schemas.ts +6 -2
- package/dist/extensions/pi-subagents/src/extension/tool-description.ts +1 -1
- package/dist/extensions/pi-subagents/src/inspectors/herdr/inspector-runner.ts +19 -13
- package/dist/extensions/pi-subagents/src/runs/background/active-async-capacity.ts +26 -8
- package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +103 -23
- package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +6 -2
- package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +18 -2
- package/dist/extensions/pi-subagents/src/runs/background/notify.ts +54 -3
- package/dist/extensions/pi-subagents/src/runs/background/process-terminal.ts +16 -0
- package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +22 -2
- package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +63 -6
- package/dist/extensions/pi-subagents/src/runs/background/steering.ts +4 -1
- package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +69 -12
- package/dist/extensions/pi-subagents/src/runs/background/wait-completions.ts +13 -0
- package/dist/extensions/pi-subagents/src/runs/background/wait-tool.ts +3 -1
- package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +21 -8
- package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +59 -6
- package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +95 -18
- package/dist/extensions/pi-subagents/src/runs/shared/async-status-projection.ts +11 -43
- package/dist/extensions/pi-subagents/src/runs/shared/capability-ceiling.ts +1 -0
- package/dist/extensions/pi-subagents/src/runs/shared/dynamic-fanout.ts +1 -1
- package/dist/extensions/pi-subagents/src/runs/shared/lane-metadata.ts +24 -3
- package/dist/extensions/pi-subagents/src/runs/shared/parallel-handoff.ts +4 -0
- package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +2 -6
- package/dist/extensions/pi-subagents/src/runs/shared/permissions.ts +1 -0
- package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +33 -18
- package/dist/extensions/pi-subagents/src/runs/shared/pi-spawn.ts +73 -37
- package/dist/extensions/pi-subagents/src/runs/shared/structured-output.ts +33 -6
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-control.ts +4 -2
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +20 -3
- package/dist/extensions/pi-subagents/src/runs/shared/task-intent.ts +21 -7
- package/dist/extensions/pi-subagents/src/runs/shared/tool-timeout.ts +1 -1
- package/dist/extensions/pi-subagents/src/runs/shared/worktree.ts +467 -63
- package/dist/extensions/pi-subagents/src/shared/atomic-json.ts +3 -1
- package/dist/extensions/pi-subagents/src/shared/fork-context.ts +0 -12
- package/dist/extensions/pi-subagents/src/shared/fork-session-cwd.ts +27 -0
- package/dist/extensions/pi-subagents/src/shared/launch-contract.ts +3 -0
- package/dist/extensions/pi-subagents/src/shared/types.ts +42 -4
- package/dist/extensions/pi-subagents/src/shared/utils.ts +18 -7
- package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +1 -1
- package/dist/extensions/pi-subagents/src/slash/subagents-admin.ts +26 -12
- package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +61 -2
- package/dist/extensions/pi-subagents/src/tui/fleet.ts +12 -7
- package/dist/extensions/pi-subagents/src/tui/render.ts +227 -14
- package/dist/extensions/pi-subagents/src/watchdog/child-status.ts +56 -34
- package/dist/extensions/pi-subagents/src/watchdog/diff-tool.ts +77 -0
- package/dist/extensions/pi-subagents/src/watchdog/emission-guard.ts +5 -3
- package/dist/extensions/pi-subagents/src/watchdog/guidance.ts +20 -0
- package/dist/extensions/pi-subagents/src/watchdog/register-child.ts +16 -13
- package/dist/extensions/pi-subagents/src/watchdog/register-main.ts +10 -9
- package/dist/extensions/pi-subagents/src/watchdog/render.ts +4 -5
- package/dist/extensions/pi-subagents/src/watchdog/review.ts +15 -4
- package/dist/extensions/pi-subagents/src/watchdog/rules.ts +70 -0
- package/dist/extensions/pi-subagents/src/watchdog/runtime.ts +75 -92
- package/dist/extensions/pi-subagents/src/watchdog/scope.ts +2 -1
- package/dist/extensions/pi-subagents/src/watchdog/settings.ts +48 -104
- package/dist/extensions/pi-subagents/src/watchdog/types.ts +18 -32
- package/dist/extensions/pi-subagents/src/watchdog/warning-format.ts +0 -1
- package/dist/extensions/pi-subagents/src/workflows/chat-progress.ts +3 -2
- package/dist/extensions/pi-subagents/src/workflows/workflow-checklist.ts +439 -0
- package/dist/extensions/pi-subagents/src/workflows/workflow-preflight.ts +28 -1
- package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +335 -21
- package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +4 -1
- package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +22 -18
- package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +0 -1
- package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +21 -18
- package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +307 -22
- package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +1 -0
- package/dist/extensions/pi-subagents/test/support/helpers.ts +25 -0
- package/dist/extensions/pi-subagents/test/support/isolated-temp-root.mjs +15 -4
- package/dist/extensions/pi-subagents/test/support/mock-pi-script.mjs +17 -2
- package/dist/extensions/pi-subagents/test/unit/acceptance.test.ts +96 -15
- package/dist/extensions/pi-subagents/test/unit/active-async-capacity.test.ts +53 -5
- package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +20 -0
- package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +221 -8
- package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +45 -24
- package/dist/extensions/pi-subagents/test/unit/agent-scan-dirs.test.ts +133 -0
- package/dist/extensions/pi-subagents/test/unit/async-execution.test.ts +28 -2
- package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +2 -0
- package/dist/extensions/pi-subagents/test/unit/async-retention.test.ts +4 -1
- package/dist/extensions/pi-subagents/test/unit/async-status-projection.test.ts +18 -4
- package/dist/extensions/pi-subagents/test/unit/atomic-json.test.ts +16 -0
- package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +38 -0
- package/dist/extensions/pi-subagents/test/unit/default-extensions.test.ts +2 -2
- package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +21 -0
- package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +19 -6
- package/dist/extensions/pi-subagents/test/unit/get-final-output.test.ts +26 -0
- package/dist/extensions/pi-subagents/test/unit/herdr-inspector.test.ts +22 -1
- package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +2 -6
- package/dist/extensions/pi-subagents/test/unit/notify.test.ts +66 -1
- package/dist/extensions/pi-subagents/test/unit/pi-args-permission-system.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +116 -10
- package/dist/extensions/pi-subagents/test/unit/pi-spawn.test.ts +136 -18
- package/dist/extensions/pi-subagents/test/unit/preflight.test.ts +20 -0
- package/dist/extensions/pi-subagents/test/unit/process-terminal.test.ts +22 -0
- package/dist/extensions/pi-subagents/test/unit/profiles.test.ts +1 -0
- package/dist/extensions/pi-subagents/test/unit/project-panes-public-api.test.ts +8 -0
- package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +127 -48
- package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +68 -0
- package/dist/extensions/pi-subagents/test/unit/runtime-agent-registration.test.ts +17 -0
- package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +88 -0
- package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +4 -2
- package/dist/extensions/pi-subagents/test/unit/scripted-workflow.test.ts +9 -9
- package/dist/extensions/pi-subagents/test/unit/steering-action.test.ts +2 -2
- package/dist/extensions/pi-subagents/test/unit/steering.test.ts +9 -3
- package/dist/extensions/pi-subagents/test/unit/subagent-control.test.ts +22 -9
- package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +91 -8
- package/dist/extensions/pi-subagents/test/unit/task-intent.test.ts +27 -0
- package/dist/extensions/pi-subagents/test/unit/temp-paths.test.ts +50 -0
- package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +0 -1
- package/dist/extensions/pi-subagents/test/unit/tool-timeout.test.ts +2 -2
- package/dist/extensions/pi-subagents/test/unit/wait-completions.test.ts +34 -0
- package/dist/extensions/pi-subagents/test/unit/wait-subscriptions.test.ts +1 -2
- package/dist/extensions/pi-subagents/test/unit/watchdog-child-status.test.ts +73 -13
- package/dist/extensions/pi-subagents/test/unit/watchdog-diff-tool.test.ts +79 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-permission-arbiter.test.ts +2 -4
- package/dist/extensions/pi-subagents/test/unit/watchdog-render.test.ts +7 -8
- package/dist/extensions/pi-subagents/test/unit/watchdog-review.test.ts +48 -3
- package/dist/extensions/pi-subagents/test/unit/watchdog-rules.test.ts +37 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-runtime.test.ts +36 -118
- package/dist/extensions/pi-subagents/test/unit/watchdog-scope.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/watchdog-settings.test.ts +37 -14
- package/dist/extensions/pi-subagents/test/unit/workflow-chat-progress.test.ts +45 -26
- package/dist/extensions/pi-subagents/test/unit/workflow-checklist.test.ts +223 -0
- package/dist/extensions/pi-subagents/test/unit/workflow-launch-params.test.ts +50 -1
- package/dist/extensions/pi-subagents/test/unit/workflow-preflight.test.ts +22 -0
- package/dist/extensions/pi-subagents/test/unit/worktree.test.ts +136 -4
- package/dist/extensions/pi-zentui/README.md +19 -5
- package/dist/extensions/pi-zentui/docs/configuration.md +37 -9
- package/dist/extensions/pi-zentui/extensions/zentui/config.ts +29 -0
- package/dist/extensions/pi-zentui/extensions/zentui/index.ts +31 -2
- package/dist/extensions/pi-zentui/extensions/zentui/prototype-patch-registry.ts +55 -11
- package/dist/extensions/pi-zentui/extensions/zentui/settings-command.ts +115 -3
- package/dist/extensions/pi-zentui/extensions/zentui/settings-previews.ts +119 -2
- package/dist/extensions/pi-zentui/extensions/zentui/thinking-experimental.ts +1768 -0
- package/dist/extensions/pi-zentui/extensions/zentui/thinking-status.ts +69 -0
- package/dist/extensions/pi-zentui/extensions/zentui/thinking-steps.ts +155 -0
- package/dist/extensions/pi-zentui/extensions/zentui/ui.ts +2 -1
- package/dist/extensions/pi-zentui/extensions/zentui/user-message-osc.ts +16 -2
- package/dist/extensions/pi-zentui/extensions/zentui/user-message-styles.ts +1 -1
- package/dist/extensions/pi-zentui/test/config-load-lifecycle.test.ts +124 -0
- package/dist/extensions/pi-zentui/test/config.test.ts +82 -2
- package/dist/extensions/pi-zentui/test/editor-viewport-indicators.test.ts +19 -4
- package/dist/extensions/pi-zentui/test/extension-compliance.test.ts +58 -9
- package/dist/extensions/pi-zentui/test/package-contents.mjs +33 -0
- package/dist/extensions/pi-zentui/test/prototype-patch-registry.test.ts +131 -0
- package/dist/extensions/pi-zentui/test/responsive-dependencies.test.ts +4 -4
- package/dist/extensions/pi-zentui/test/settings-command.test.ts +289 -7
- package/dist/extensions/pi-zentui/test/settings-previews.test.ts +106 -0
- package/dist/extensions/pi-zentui/test/thinking-experimental-docs.test.ts +51 -0
- package/dist/extensions/pi-zentui/test/thinking-experimental-loader-diagnostics.mjs +54 -0
- package/dist/extensions/pi-zentui/test/thinking-experimental-missing-export.test.ts +44 -0
- package/dist/extensions/pi-zentui/test/thinking-experimental-rows.test.ts +297 -0
- package/dist/extensions/pi-zentui/test/thinking-experimental-tui.mjs +2329 -0
- package/dist/extensions/pi-zentui/test/thinking-experimental.test.ts +1668 -0
- package/dist/extensions/pi-zentui/test/thinking-status.test.ts +85 -0
- package/dist/extensions/pi-zentui/test/thinking-steps.test.ts +122 -0
- package/dist/extensions/pi-zentui/test/user-message-osc.test.ts +58 -0
- package/dist/skills/brandkit/SKILL.md +0 -2
- package/dist/skills/{industrial-brutalist-ui → brutalist}/SKILL.md +1 -3
- package/dist/skills/gpt-taste/SKILL.md +0 -2
- package/dist/skills/image-to-code/SKILL.md +0 -2
- package/dist/skills/imagegen-frontend-mobile/SKILL.md +0 -2
- package/dist/skills/imagegen-frontend-web/SKILL.md +0 -2
- package/dist/skills/{minimalist-ui → minimalist}/SKILL.md +1 -3
- package/dist/skills/{full-output-enforcement → output}/SKILL.md +1 -3
- package/dist/skills/{redesign-existing-projects → redesign}/SKILL.md +1 -3
- package/dist/skills/{high-end-visual-design → soft}/SKILL.md +1 -3
- package/dist/skills/stitch/DESIGN.md +121 -0
- package/dist/skills/{stitch-design-taste → stitch}/SKILL.md +1 -3
- package/dist/skills/{design-taste-frontend → taste}/SKILL.md +1 -3
- package/dist/skills/{design-taste-frontend-v1 → taste-v1}/SKILL.md +2 -4
- package/package.json +1 -1
|
@@ -9,11 +9,13 @@
|
|
|
9
9
|
|
|
10
10
|
import { after, afterEach, before, beforeEach, describe, it } from "node:test";
|
|
11
11
|
import assert from "node:assert/strict";
|
|
12
|
+
import fsDefault from "node:fs";
|
|
12
13
|
import { spawnSync } from "node:child_process";
|
|
13
14
|
import * as fs from "node:fs";
|
|
15
|
+
import { syncBuiltinESMExports } from "node:module";
|
|
14
16
|
import * as os from "node:os";
|
|
15
17
|
import * as path from "node:path";
|
|
16
|
-
import { createEventBus, createMockPi, createTempDir, events, makeAgent, makeMinimalCtx, removeTempDir, tryImport } from "../support/helpers.ts";
|
|
18
|
+
import { createEventBus, createMockPi, createTempDir, events, makeAgent, makeMinimalCtx, removeTempDir, resolveMockPiCallArgs, tryImport } from "../support/helpers.ts";
|
|
17
19
|
import type { MockPi } from "../support/helpers.ts";
|
|
18
20
|
import { deliverInterruptRequest, deliverStopRequest, deliverTimeoutRequest } from "../../src/runs/background/control-channel.ts";
|
|
19
21
|
import { waitForSubagents } from "../../src/runs/background/subagent-wait.ts";
|
|
@@ -25,8 +27,10 @@ import { registerSubagentCapabilityCeiling } from "../../src/api/capability-ceil
|
|
|
25
27
|
import { resolveSubagentLaunchContract } from "../../src/api/preflight.ts";
|
|
26
28
|
import { discoverAgents } from "../../src/agents/agents.ts";
|
|
27
29
|
import { runSync } from "../../src/runs/foreground/execution.ts";
|
|
28
|
-
import { ACTIVE_ASYNC_CAPACITY_DIR, acquireActiveAsyncCapacity, activeAsyncCapacitySessionKey } from "../../src/runs/background/active-async-capacity.ts";
|
|
30
|
+
import { ACTIVE_ASYNC_CAPACITY_DIR, acquireActiveAsyncCapacity, activeAsyncCapacitySessionKey, getActiveAsyncCapacitySnapshot } from "../../src/runs/background/active-async-capacity.ts";
|
|
29
31
|
import { deriveForkPromptCacheKey, SUBAGENT_FORK_CACHE_KEY_ENV } from "../../src/runs/shared/pi-args.ts";
|
|
32
|
+
import { resolveInstalledPiPackageRoot, resolvePiPackageRoot } from "../../src/runs/shared/pi-spawn.ts";
|
|
33
|
+
import { SELESAI_CODING_AGENT_PACKAGE_ROOT_ENV } from "../../src/shared/utils.ts";
|
|
30
34
|
import { clearExclusions, recordModelFailure } from "../../src/runs/shared/model-exclusions.ts";
|
|
31
35
|
|
|
32
36
|
interface LaunchResolvedExtensions {
|
|
@@ -82,7 +86,7 @@ interface AsyncResultPayload {
|
|
|
82
86
|
totalTokens?: { input: number; output: number; total: number };
|
|
83
87
|
totalCost?: { inputTokens: number; outputTokens: number; costUsd: number };
|
|
84
88
|
usageBudget?: UsageBudgetState;
|
|
85
|
-
results: Array<{ agent?: string; sessionName?: string; launchContractDigest?: string; launchResolvedExtensions?: LaunchResolvedExtensions; runtimeAcknowledgedExtensions?: RuntimeAcknowledgedExtensions; output?: string; outputState?: "present" | "absent" | "unknown"; success?: boolean; error?: string; protocolError?: { code?: string; stream?: string; limitBytes?: number; observedBytes?: number }; timedOut?: boolean; timeoutRecovery?: { changedFiles?: string[]; message?: string; warning?: string; recoveryNeeded?: boolean; reason?: string; reportStatus?: string }; stopped?: boolean; turnBudget?: { maxTurns: number; graceTurns: number; outcome: string; turnCount: number; wrapUpRequestedAtTurn?: number; terminationDeferredAtTurn?: number; exceededAtTurn?: number }; turnBudgetExceeded?: boolean; wrapUpRequested?: boolean; model?: string; attemptedModels?: string[]; modelAttempts?: Array<{ success?: boolean; error?: string }>; totalCost?: { inputTokens: number; outputTokens: number; costUsd: number }; usage?: { input: number; output: number; cacheRead: number; cacheWrite: number; cost: number; turns: number }; structuredOutput?: unknown; agentContract?: { version: 1 }; execution?: { status?: string; success?: boolean; exitCode?: number }; effects?: { fileMutation?: { status?: string; expected?: boolean; attempted?: boolean; message?: string }; settlementDiagnostic?: { finalTextPresent?: boolean; mutation?: { expected?: boolean; attempted?: boolean; observed?: boolean }; requiredOutput?: { kind?: string; path?: string; missing?: boolean }; afterCompactionSettlement?: boolean } }; intercomTarget?: string; acceptance?: { status?: string; effectiveAcceptance?: { level?: string }; childReport?: unknown; runtimeChecks?: Array<{ id?: string; status?: string; message?: string }> }; artifactPaths?: { outputPath?: string; inputPath?: string; metadataPath?: string; transcriptPath?: string }; outputSaveError?: string; metadataSaveError?: string; capabilityCeiling?: { version?: number; allowedTools?: string[]; denyExtensions?: boolean; sources?: string[] }; capabilityAudit?: { effectiveTools?: string[]; removedTools?: string[]; extensionsDenied?: boolean } }>;
|
|
89
|
+
results: Array<{ agent?: string; sessionName?: string; launchContractDigest?: string; launchResolvedExtensions?: LaunchResolvedExtensions; runtimeAcknowledgedExtensions?: RuntimeAcknowledgedExtensions; output?: string; outputState?: "present" | "absent" | "unknown"; success?: boolean; error?: string; protocolError?: { code?: string; stream?: string; limitBytes?: number; observedBytes?: number }; timedOut?: boolean; timeoutRecovery?: { changedFiles?: string[]; message?: string; warning?: string; recoveryNeeded?: boolean; reason?: string; reportStatus?: string }; stopped?: boolean; turnBudget?: { maxTurns: number; graceTurns: number; outcome: string; turnCount: number; wrapUpRequestedAtTurn?: number; terminationDeferredAtTurn?: number; exceededAtTurn?: number }; turnBudgetExceeded?: boolean; wrapUpRequested?: boolean; model?: string; thinking?: string; attemptedModels?: string[]; modelAttempts?: Array<{ success?: boolean; error?: string }>; totalCost?: { inputTokens: number; outputTokens: number; costUsd: number }; usage?: { input: number; output: number; cacheRead: number; cacheWrite: number; cost: number; turns: number }; structuredOutput?: unknown; agentContract?: { version: 1 }; execution?: { status?: string; success?: boolean; exitCode?: number }; effects?: { fileMutation?: { status?: string; expected?: boolean; attempted?: boolean; message?: string }; settlementDiagnostic?: { finalTextPresent?: boolean; mutation?: { expected?: boolean; attempted?: boolean; observed?: boolean }; requiredOutput?: { kind?: string; path?: string; missing?: boolean }; afterCompactionSettlement?: boolean } }; intercomTarget?: string; acceptance?: { status?: string; effectiveAcceptance?: { level?: string }; childReport?: unknown; runtimeChecks?: Array<{ id?: string; status?: string; message?: string }> }; artifactPaths?: { outputPath?: string; inputPath?: string; metadataPath?: string; transcriptPath?: string }; outputSaveError?: string; metadataSaveError?: string; capabilityCeiling?: { version?: number; allowedTools?: string[]; denyExtensions?: boolean; sources?: string[] }; capabilityAudit?: { effectiveTools?: string[]; removedTools?: string[]; extensionsDenied?: boolean } }>;
|
|
86
90
|
outputs?: Record<string, { text?: string; structured?: unknown }>;
|
|
87
91
|
workflowGraph?: { nodes?: Array<{ kind?: string; label?: string; phase?: string; status?: string; acceptanceStatus?: string; error?: string; outputName?: string; structured?: boolean; children?: Array<{ label?: string; outputName?: string; itemKey?: string; status?: string; acceptanceStatus?: string; error?: string }> }> };
|
|
88
92
|
parallelHandoff?: { version?: number; path?: string; groupCount?: number; childCount?: number; changedPatches?: number; cleanupState?: string };
|
|
@@ -154,6 +158,7 @@ interface AsyncStatusPayload {
|
|
|
154
158
|
|
|
155
159
|
interface MockPiCallRecord {
|
|
156
160
|
args?: string[];
|
|
161
|
+
effectiveArgs?: string[];
|
|
157
162
|
systemPrompts?: Array<{ mode?: string; path?: string; text?: string; error?: string }>;
|
|
158
163
|
requiredChildTools?: string[];
|
|
159
164
|
}
|
|
@@ -194,7 +199,7 @@ async function withIsolatedWatchdogSettings<T>(projectDir: string, run: () => Pr
|
|
|
194
199
|
}
|
|
195
200
|
}
|
|
196
201
|
|
|
197
|
-
function childWatchdogStatus(runId: string, phase: "idle" | "reviewing" | "
|
|
202
|
+
function childWatchdogStatus(runId: string, phase: "idle" | "reviewing" | "stale" | "failed", seq: number) {
|
|
198
203
|
return {
|
|
199
204
|
type: CHILD_WATCHDOG_STATUS_EVENT,
|
|
200
205
|
runId,
|
|
@@ -204,7 +209,6 @@ function childWatchdogStatus(runId: string, phase: "idle" | "reviewing" | "autof
|
|
|
204
209
|
seq,
|
|
205
210
|
phase,
|
|
206
211
|
ts: Date.now() + seq,
|
|
207
|
-
followUpPending,
|
|
208
212
|
};
|
|
209
213
|
}
|
|
210
214
|
|
|
@@ -379,7 +383,7 @@ async function waitForMockPiCall(mockPi: MockPi, index: number, timeoutMs = 30_0
|
|
|
379
383
|
if (callFile) {
|
|
380
384
|
const payload = JSON.parse(fs.readFileSync(path.join(mockPi.dir, callFile), "utf-8")) as MockPiCallRecord;
|
|
381
385
|
assert.ok(Array.isArray(payload.args), "expected recorded args");
|
|
382
|
-
return { args: payload
|
|
386
|
+
return { args: resolveMockPiCallArgs(payload), systemPrompts: payload.systemPrompts ?? [] };
|
|
383
387
|
}
|
|
384
388
|
if (Date.now() > deadline) assert.fail(`Timed out waiting for recorded mock pi call ${index}`);
|
|
385
389
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
@@ -398,7 +402,7 @@ function readLastMockPiArgs(mockPi: MockPi): string[] {
|
|
|
398
402
|
assert.ok(callFile, "expected a recorded mock pi call");
|
|
399
403
|
const payload = JSON.parse(fs.readFileSync(path.join(mockPi.dir, callFile), "utf-8")) as MockPiCallRecord;
|
|
400
404
|
assert.ok(Array.isArray(payload.args), "expected recorded args");
|
|
401
|
-
return payload
|
|
405
|
+
return resolveMockPiCallArgs(payload);
|
|
402
406
|
}
|
|
403
407
|
|
|
404
408
|
function readMockPiArgs(mockPi: MockPi, index: number): string[] {
|
|
@@ -409,7 +413,7 @@ function readMockPiArgs(mockPi: MockPi, index: number): string[] {
|
|
|
409
413
|
assert.ok(callFile, `expected recorded call ${index}`);
|
|
410
414
|
const payload = JSON.parse(fs.readFileSync(path.join(mockPi.dir, callFile), "utf-8")) as MockPiCallRecord;
|
|
411
415
|
assert.ok(Array.isArray(payload.args), "expected recorded args");
|
|
412
|
-
return payload
|
|
416
|
+
return resolveMockPiCallArgs(payload);
|
|
413
417
|
}
|
|
414
418
|
|
|
415
419
|
function readMockPiRequiredTools(mockPi: MockPi, index: number): string[] {
|
|
@@ -428,9 +432,10 @@ function readMockPiArgsMatching(mockPi: MockPi, text: string): string[] {
|
|
|
428
432
|
.filter((name) => name.startsWith("call-") && name.endsWith(".json"))
|
|
429
433
|
.sort();
|
|
430
434
|
for (const callFile of callFiles) {
|
|
431
|
-
const payload = JSON.parse(fs.readFileSync(path.join(mockPi.dir, callFile), "utf-8")) as
|
|
435
|
+
const payload = JSON.parse(fs.readFileSync(path.join(mockPi.dir, callFile), "utf-8")) as MockPiCallRecord;
|
|
432
436
|
assert.ok(Array.isArray(payload.args), "expected recorded args");
|
|
433
|
-
|
|
437
|
+
const args = resolveMockPiCallArgs(payload);
|
|
438
|
+
if (args.join("\n").includes(text)) return args;
|
|
434
439
|
}
|
|
435
440
|
assert.fail(`expected recorded call containing ${text}`);
|
|
436
441
|
}
|
|
@@ -567,6 +572,56 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
567
572
|
assert.equal(status.endedAt !== undefined, true);
|
|
568
573
|
});
|
|
569
574
|
|
|
575
|
+
it("preserves effective thinking in the completed async result artifact", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
|
|
576
|
+
mockPi.onCall({ output: "completed with thinking" });
|
|
577
|
+
const id = `async-result-thinking-${Date.now().toString(36)}`;
|
|
578
|
+
const launch = executeAsyncSingle(id, {
|
|
579
|
+
agent: "worker",
|
|
580
|
+
task: "Report the configured reasoning level.",
|
|
581
|
+
agentConfig: makeAgent("worker", { model: "mock/test-model", thinking: "high", completionGuard: false }),
|
|
582
|
+
availableModels: [{ provider: "mock", id: "test-model", fullId: "mock/test-model" }],
|
|
583
|
+
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-result-thinking" },
|
|
584
|
+
artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
|
|
585
|
+
shareEnabled: false,
|
|
586
|
+
maxSubagentDepth: 2,
|
|
587
|
+
acceptance: false,
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
assert.equal(launch.isError, undefined, launch.content[0]?.text ?? "launch failed");
|
|
591
|
+
const payload = await readAsyncPayload(id);
|
|
592
|
+
const status = JSON.parse(fs.readFileSync(path.join(ASYNC_DIR, id, "status.json"), "utf-8")) as AsyncStatusPayload;
|
|
593
|
+
assert.equal(payload.success, true);
|
|
594
|
+
assert.equal(payload.results[0]?.thinking, "high");
|
|
595
|
+
assert.equal(status.steps?.[0]?.thinking, "high");
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
it("forwards the installed Pi package root through the async runner environment", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
|
|
599
|
+
// The child resolves piPackageRoot as resolvePiPackageRoot() ??
|
|
600
|
+
// resolveInstalledPiPackageRoot() at module load. In this fork the host
|
|
601
|
+
// repo root is itself named @selesai/code, so the argv1 walk finds it
|
|
602
|
+
// before the shim; mirror the child's expression for the expectation.
|
|
603
|
+
const packageRoot = resolvePiPackageRoot() ?? resolveInstalledPiPackageRoot();
|
|
604
|
+
assert.ok(packageRoot, "expected the test Pi package to be installed");
|
|
605
|
+
mockPi.onCall({ echoEnv: [SELESAI_CODING_AGENT_PACKAGE_ROOT_ENV] });
|
|
606
|
+
const id = `async-package-root-env-${Date.now().toString(36)}`;
|
|
607
|
+
executeAsyncSingle(id, {
|
|
608
|
+
agent: "worker",
|
|
609
|
+
task: "Report the forwarded package root.",
|
|
610
|
+
agentConfig: makeAgent("worker", { completionGuard: false }),
|
|
611
|
+
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-package-root-env" },
|
|
612
|
+
artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
|
|
613
|
+
shareEnabled: false,
|
|
614
|
+
maxSubagentDepth: 2,
|
|
615
|
+
acceptance: false,
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
const payload = await readAsyncPayload(id);
|
|
619
|
+
assert.equal(payload.success, true);
|
|
620
|
+
assert.deepEqual(JSON.parse(payload.results[0]?.output ?? "{}"), {
|
|
621
|
+
[SELESAI_CODING_AGENT_PACKAGE_ROOT_ENV]: packageRoot,
|
|
622
|
+
});
|
|
623
|
+
});
|
|
624
|
+
|
|
570
625
|
it("persists the bounded usage projection in async results and metadata", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
|
|
571
626
|
mockPi.onCall({ output: "accounted output" });
|
|
572
627
|
const id = `async-usage-artifact-${Date.now().toString(36)}`;
|
|
@@ -990,6 +1045,124 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
990
1045
|
assert.equal(mockPi.callCount(), 0);
|
|
991
1046
|
});
|
|
992
1047
|
|
|
1048
|
+
it("establishes a lifecycle sidecar before an external CLI can run", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
|
|
1049
|
+
const id = `async-external-lifecycle-${Date.now().toString(36)}`;
|
|
1050
|
+
const startedRunners: string[] = [];
|
|
1051
|
+
const launch = executeAsyncSingle(id, {
|
|
1052
|
+
agent: "external",
|
|
1053
|
+
task: "Run external",
|
|
1054
|
+
agentConfig: makeAgent("external", {
|
|
1055
|
+
runner: { type: "external-cli", command: process.execPath, args: ["-e", "setTimeout(() => process.stdout.write('external lifecycle'), 500)"] },
|
|
1056
|
+
} as never),
|
|
1057
|
+
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
1058
|
+
artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
|
|
1059
|
+
shareEnabled: false,
|
|
1060
|
+
activeAsyncCapacity: {
|
|
1061
|
+
owner: {},
|
|
1062
|
+
markStarted: (runnerProcessInstanceId: string) => { startedRunners.push(runnerProcessInstanceId); },
|
|
1063
|
+
markWorkflowStarted() {},
|
|
1064
|
+
rollback: () => false,
|
|
1065
|
+
rollbackBeforeRunnerProceed: () => false,
|
|
1066
|
+
reconcile: () => ({ used: 1, limit: 1 }),
|
|
1067
|
+
} as never,
|
|
1068
|
+
});
|
|
1069
|
+
|
|
1070
|
+
assert.equal(launch.isError, undefined, launch.content[0]?.text ?? "launch failed");
|
|
1071
|
+
const proof = JSON.parse(fs.readFileSync(path.join(launch.details.asyncDir ?? "", "process-terminal.json"), "utf-8")) as { state?: string; runId?: string; runnerProcessInstanceId?: string };
|
|
1072
|
+
assert.equal(proof.state, "pending");
|
|
1073
|
+
assert.equal(proof.runId, id);
|
|
1074
|
+
assert.deepEqual(startedRunners, [proof.runnerProcessInstanceId]);
|
|
1075
|
+
const payload = await readAsyncPayload(id);
|
|
1076
|
+
assert.equal(payload.success, true);
|
|
1077
|
+
assert.match(payload.results[0]?.output ?? "", /external lifecycle/);
|
|
1078
|
+
});
|
|
1079
|
+
|
|
1080
|
+
it("continues startup when proceed authorization is published but temp cleanup fails", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async (t) => {
|
|
1081
|
+
const id = `async-external-proceed-cleanup-${Date.now().toString(36)}`;
|
|
1082
|
+
const originalRmSync = fsDefault.rmSync;
|
|
1083
|
+
let proceedCleanupFailures = 0;
|
|
1084
|
+
let rollbackCount = 0;
|
|
1085
|
+
t.mock.method(fsDefault, "rmSync", ((target: fs.PathLike, options?: fs.RmOptions) => {
|
|
1086
|
+
const targetPath = String(target);
|
|
1087
|
+
if (targetPath.includes(".runner-startup-proceed.json.") && targetPath.endsWith(".tmp") && proceedCleanupFailures === 0) {
|
|
1088
|
+
proceedCleanupFailures += 1;
|
|
1089
|
+
throw new Error("simulated proceed cleanup failure");
|
|
1090
|
+
}
|
|
1091
|
+
return originalRmSync(target, options);
|
|
1092
|
+
}) as typeof fsDefault.rmSync);
|
|
1093
|
+
syncBuiltinESMExports();
|
|
1094
|
+
t.after(() => syncBuiltinESMExports());
|
|
1095
|
+
|
|
1096
|
+
const launch = executeAsyncSingle(id, {
|
|
1097
|
+
agent: "external",
|
|
1098
|
+
task: "Run external",
|
|
1099
|
+
agentConfig: makeAgent("external", {
|
|
1100
|
+
runner: { type: "external-cli", command: process.execPath, args: ["-e", "process.stdout.write('external proceed cleanup')"] },
|
|
1101
|
+
} as never),
|
|
1102
|
+
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
1103
|
+
artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
|
|
1104
|
+
shareEnabled: false,
|
|
1105
|
+
activeAsyncCapacity: {
|
|
1106
|
+
owner: {},
|
|
1107
|
+
markStarted() {},
|
|
1108
|
+
markWorkflowStarted() {},
|
|
1109
|
+
rollback() { rollbackCount += 1; return false; },
|
|
1110
|
+
rollbackBeforeRunnerProceed() { rollbackCount += 1; return false; },
|
|
1111
|
+
reconcile: () => ({ used: 1, limit: 1 }),
|
|
1112
|
+
} as never,
|
|
1113
|
+
});
|
|
1114
|
+
|
|
1115
|
+
assert.equal(launch.isError, undefined, launch.content[0]?.text ?? "launch failed");
|
|
1116
|
+
assert.equal(proceedCleanupFailures, 1);
|
|
1117
|
+
assert.equal(rollbackCount, 0);
|
|
1118
|
+
const payload = await readAsyncPayload(id);
|
|
1119
|
+
assert.equal(payload.success, true);
|
|
1120
|
+
assert.match(payload.results[0]?.output ?? "", /external proceed cleanup/);
|
|
1121
|
+
});
|
|
1122
|
+
|
|
1123
|
+
it("fails pre-proceed capacity bind without rebinding the killed runner", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async (t) => {
|
|
1124
|
+
const parentSessionId = `session-capacity-bind-${Date.now().toString(36)}`;
|
|
1125
|
+
const id = `async-capacity-bind-failure-${Date.now().toString(36)}`;
|
|
1126
|
+
const asyncDir = path.join(ASYNC_DIR, id);
|
|
1127
|
+
let ownerWrites = 0;
|
|
1128
|
+
fs.rmSync(path.join(ACTIVE_ASYNC_CAPACITY_DIR, activeAsyncCapacitySessionKey(parentSessionId)), { recursive: true, force: true });
|
|
1129
|
+
const activeAsyncCapacity = acquireActiveAsyncCapacity({ sessionId: parentSessionId, limit: 1, runId: id, kind: "runner", asyncDir }, {
|
|
1130
|
+
writeOwner(filePath, owner) {
|
|
1131
|
+
ownerWrites += 1;
|
|
1132
|
+
if (ownerWrites === 1) throw new Error("simulated durable bind failure");
|
|
1133
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
1134
|
+
fs.writeFileSync(filePath, JSON.stringify(owner, null, 2));
|
|
1135
|
+
},
|
|
1136
|
+
});
|
|
1137
|
+
assert.ok(activeAsyncCapacity);
|
|
1138
|
+
const originalKill = process.kill;
|
|
1139
|
+
t.mock.method(process, "kill", ((pid: number, signal?: NodeJS.Signals | 0) => {
|
|
1140
|
+
if (signal === 0) return true;
|
|
1141
|
+
return originalKill(pid, signal);
|
|
1142
|
+
}) as typeof process.kill);
|
|
1143
|
+
|
|
1144
|
+
const launch = executeAsyncSingle(id, {
|
|
1145
|
+
agent: "external",
|
|
1146
|
+
task: "Run external",
|
|
1147
|
+
agentConfig: makeAgent("external", {
|
|
1148
|
+
runner: { type: "external-cli", command: process.execPath, args: ["-e", "setTimeout(() => {}, 30000)"] },
|
|
1149
|
+
} as never),
|
|
1150
|
+
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: parentSessionId },
|
|
1151
|
+
artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
|
|
1152
|
+
shareEnabled: false,
|
|
1153
|
+
activeAsyncCapacity,
|
|
1154
|
+
});
|
|
1155
|
+
|
|
1156
|
+
assert.equal(launch.isError, true);
|
|
1157
|
+
assert.match(launch.content[0]?.text ?? "", /Failed to establish async runner capacity ownership/);
|
|
1158
|
+
assert.equal(ownerWrites, 1);
|
|
1159
|
+
const status = JSON.parse(fs.readFileSync(path.join(asyncDir, "status.json"), "utf-8")) as AsyncStatusPayload;
|
|
1160
|
+
assert.equal(status.state, "failed");
|
|
1161
|
+
assert.match(status.error ?? "", /Failed to establish async runner capacity ownership/);
|
|
1162
|
+
assert.equal(status.processTerminal?.state, "not-started");
|
|
1163
|
+
assert.deepEqual(getActiveAsyncCapacitySnapshot(parentSessionId, 1), { used: 0, limit: 1 });
|
|
1164
|
+
});
|
|
1165
|
+
|
|
993
1166
|
it("lets explicit fast false opt out async external chains from inherited fast mode", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
|
|
994
1167
|
const agent = makeAgent("external", {
|
|
995
1168
|
fast: true,
|
|
@@ -1234,8 +1407,8 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
1234
1407
|
await waitForMockPiCall(mockPi, 0);
|
|
1235
1408
|
const callFile = fs.readdirSync(mockPi.dir).find((name) => name.endsWith(".json"));
|
|
1236
1409
|
assert.ok(callFile);
|
|
1237
|
-
const call = JSON.parse(fs.readFileSync(path.join(mockPi.dir, callFile), "utf-8")) as
|
|
1238
|
-
assert.match(call.
|
|
1410
|
+
const call = JSON.parse(fs.readFileSync(path.join(mockPi.dir, callFile), "utf-8")) as MockPiCallRecord;
|
|
1411
|
+
assert.match(resolveMockPiCallArgs(call).join("\n"), new RegExp(sentinel));
|
|
1239
1412
|
|
|
1240
1413
|
const payload = await readAsyncPayload(id);
|
|
1241
1414
|
const artifactPaths = payload.results[0]?.artifactPaths;
|
|
@@ -1642,7 +1815,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
1642
1815
|
fs.writeFileSync(path.join(repo, "input.md"), "parallel partial child change\n", "utf-8");
|
|
1643
1816
|
const resultPath = await waitForAsyncResultFile(id, 8_000);
|
|
1644
1817
|
const payload = JSON.parse(fs.readFileSync(resultPath, "utf-8")) as AsyncResultPayload;
|
|
1645
|
-
const status = await waitForAsyncState(id, (candidate) => candidate.state === "failed");
|
|
1818
|
+
const status = await waitForAsyncState(id, (candidate) => candidate.state === "failed", 30_000);
|
|
1646
1819
|
assert.equal(payload.state, "failed");
|
|
1647
1820
|
assert.equal(payload.success, false);
|
|
1648
1821
|
assert.equal(payload.exitCode, 1);
|
|
@@ -2152,7 +2325,6 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
2152
2325
|
assert.match(interactiveResult.content[0]?.text ?? "", /does not need a wait call/);
|
|
2153
2326
|
assert.match(interactiveResult.content[0]?.text ?? "", /native completion notification/);
|
|
2154
2327
|
assert.doesNotMatch(interactiveResult.content[0]?.text ?? "", /bg_wait\(\{ id:/);
|
|
2155
|
-
assert.doesNotMatch(interactiveResult.content[0]?.text ?? "", /subagent_wait/);
|
|
2156
2328
|
assert.doesNotMatch(interactiveResult.content[0]?.text ?? "", /auto-drain/);
|
|
2157
2329
|
await waitForAsyncResultFile(interactiveId, 30_000);
|
|
2158
2330
|
|
|
@@ -2168,7 +2340,6 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
2168
2340
|
assert.match(parallelResult.content[0]?.text ?? "", /Async parallel:/);
|
|
2169
2341
|
assert.match(parallelResult.content[0]?.text ?? "", /Do not run sleep timers or polling loops/);
|
|
2170
2342
|
assert.match(parallelResult.content[0]?.text ?? "", /Use bg_wait only/i);
|
|
2171
|
-
assert.doesNotMatch(parallelResult.content[0]?.text ?? "", /subagent_wait/);
|
|
2172
2343
|
assert.equal(startedEvent(parallelId).goal, "[prompt redacted]");
|
|
2173
2344
|
const parallelResultPath = await waitForAsyncResultFile(parallelId, 10_000);
|
|
2174
2345
|
const parallelPayload = JSON.parse(fs.readFileSync(parallelResultPath, "utf-8")) as { agent?: string; mode?: string };
|
|
@@ -2260,7 +2431,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
2260
2431
|
const asyncId = result.details?.asyncId;
|
|
2261
2432
|
assert.ok(asyncId, "expected asyncId");
|
|
2262
2433
|
const payload = await readAsyncPayload(asyncId);
|
|
2263
|
-
assert.equal(payload.results[0]?.acceptance?.effectiveAcceptance.level, "
|
|
2434
|
+
assert.equal(payload.results[0]?.acceptance?.effectiveAcceptance.level, "none");
|
|
2264
2435
|
});
|
|
2265
2436
|
|
|
2266
2437
|
|
|
@@ -2293,7 +2464,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
2293
2464
|
maxSubagentDepth: 2,
|
|
2294
2465
|
});
|
|
2295
2466
|
const reviewPayload = await readAsyncPayload(reviewId);
|
|
2296
|
-
assert.equal(reviewPayload.results[0]?.acceptance?.effectiveAcceptance?.level, "
|
|
2467
|
+
assert.equal(reviewPayload.results[0]?.acceptance?.effectiveAcceptance?.level, "none");
|
|
2297
2468
|
});
|
|
2298
2469
|
|
|
2299
2470
|
|
|
@@ -2334,7 +2505,10 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
2334
2505
|
assert.equal(fs.readFileSync(artifactPaths[0], "utf-8"), "chain first report");
|
|
2335
2506
|
assert.equal(fs.readFileSync(artifactPaths[1], "utf-8"), "chain second report");
|
|
2336
2507
|
const calls = fs.readdirSync(mockPi.dir).filter((name) => name.startsWith("call-")).sort();
|
|
2337
|
-
const taskArgs = calls.map((name) =>
|
|
2508
|
+
const taskArgs = calls.map((name) => {
|
|
2509
|
+
const call = JSON.parse(fs.readFileSync(path.join(mockPi.dir, name), "utf-8")) as MockPiCallRecord;
|
|
2510
|
+
return resolveMockPiCallArgs(call).at(-1) ?? "";
|
|
2511
|
+
});
|
|
2338
2512
|
assert.ok(taskArgs.find((task) => task.includes("Write first"))?.includes(path.join("parallel-0", "0-worker", "context.md")));
|
|
2339
2513
|
assert.ok(taskArgs.find((task) => task.includes("Write second"))?.includes(path.join("parallel-0", "1-worker", "context.md")));
|
|
2340
2514
|
});
|
|
@@ -2773,10 +2947,10 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
2773
2947
|
assert.ok(!result.isError);
|
|
2774
2948
|
const payload = await readAsyncPayload(id);
|
|
2775
2949
|
const explorerResults = payload.results.filter((child) => child.agent === "explorer");
|
|
2776
|
-
assert.deepEqual(explorerResults.map((child) => child.acceptance?.effectiveAcceptance?.level), ["
|
|
2950
|
+
assert.deepEqual(explorerResults.map((child) => child.acceptance?.effectiveAcceptance?.level), ["none", "none"]);
|
|
2777
2951
|
const dynamicNode = payload.workflowGraph?.nodes?.[1];
|
|
2778
|
-
assert.equal(dynamicNode?.acceptanceStatus, "
|
|
2779
|
-
assert.deepEqual(dynamicNode?.children?.map((child) => child.acceptanceStatus), ["
|
|
2952
|
+
assert.equal(dynamicNode?.acceptanceStatus, "not-required");
|
|
2953
|
+
assert.deepEqual(dynamicNode?.children?.map((child) => child.acceptanceStatus), ["not-required", "not-required"]);
|
|
2780
2954
|
});
|
|
2781
2955
|
|
|
2782
2956
|
it("infers async dynamic acceptance after materializing item templates", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
|
|
@@ -4010,6 +4184,40 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
4010
4184
|
assert.equal(fs.readFileSync(outputPath, "utf-8"), "async full output\nwith details");
|
|
4011
4185
|
});
|
|
4012
4186
|
|
|
4187
|
+
it("removes Pi turn-timing telemetry from runtime-persisted background output", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
|
|
4188
|
+
const report = "## Review\n\nVERDICT: FINDINGS";
|
|
4189
|
+
const timingFooter = "\x1b[38;2;136;136;136m✻ Turn took 5m 54s (Total time 5m 54s · 2 turns)\x1b[0m";
|
|
4190
|
+
mockPi.onCall({ output: `${report}\n\n${timingFooter}` });
|
|
4191
|
+
const id = `async-timing-footer-${Date.now().toString(36)}`;
|
|
4192
|
+
const outputPath = path.join(tempDir, "async-review.md");
|
|
4193
|
+
executeAsyncSingle(id, {
|
|
4194
|
+
agent: "reviewer",
|
|
4195
|
+
task: "Review without modifying files",
|
|
4196
|
+
agentConfig: makeAgent("reviewer", { tools: ["read", "grep", "find", "ls"] }),
|
|
4197
|
+
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
4198
|
+
artifactConfig: {
|
|
4199
|
+
enabled: false,
|
|
4200
|
+
includeInput: false,
|
|
4201
|
+
includeOutput: false,
|
|
4202
|
+
includeJsonl: false,
|
|
4203
|
+
includeMetadata: false,
|
|
4204
|
+
cleanupDays: 7,
|
|
4205
|
+
},
|
|
4206
|
+
shareEnabled: false,
|
|
4207
|
+
sessionRoot: path.join(tempDir, "sessions"),
|
|
4208
|
+
output: outputPath,
|
|
4209
|
+
acceptance: false,
|
|
4210
|
+
maxSubagentDepth: 2,
|
|
4211
|
+
});
|
|
4212
|
+
|
|
4213
|
+
const resultPath = await waitForAsyncResultFile(id);
|
|
4214
|
+
const payload = JSON.parse(fs.readFileSync(resultPath, "utf-8")) as AsyncResultPayload;
|
|
4215
|
+
assert.equal(payload.success, true);
|
|
4216
|
+
assert.equal(fs.readFileSync(outputPath, "utf-8"), report);
|
|
4217
|
+
assert.doesNotMatch(payload.summary ?? "", /Turn took/);
|
|
4218
|
+
assert.doesNotMatch(payload.results[0]?.output ?? "", /Turn took/);
|
|
4219
|
+
});
|
|
4220
|
+
|
|
4013
4221
|
it("background single runs route relative outputs to outputBaseDir", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
|
|
4014
4222
|
mockPi.onCall({ output: "async configured report" });
|
|
4015
4223
|
const id = `async-configured-output-base-${Date.now().toString(36)}`;
|
|
@@ -4384,6 +4592,29 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
4384
4592
|
assert.deepEqual(payload.results[0]?.structuredOutput, { ok: true, note: "async" });
|
|
4385
4593
|
});
|
|
4386
4594
|
|
|
4595
|
+
it("background outputSchema runs fail closed when required acceptanceReport is missing", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
|
|
4596
|
+
mockPi.onCall({ output: "structured", structuredOutput: { ok: true } });
|
|
4597
|
+
const id = `async-schema-missing-acceptance-${Date.now().toString(36)}`;
|
|
4598
|
+
|
|
4599
|
+
executeAsyncSingle(id, {
|
|
4600
|
+
agent: "worker",
|
|
4601
|
+
task: "Return structured data",
|
|
4602
|
+
agentConfig: makeAgent("worker", { completionGuard: false }),
|
|
4603
|
+
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
4604
|
+
artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
|
|
4605
|
+
shareEnabled: false,
|
|
4606
|
+
sessionRoot: path.join(tempDir, "sessions"),
|
|
4607
|
+
maxSubagentDepth: 2,
|
|
4608
|
+
acceptance: { level: "checked", report: "on" },
|
|
4609
|
+
structuredOutputSchema: { type: "object", required: ["ok"], properties: { ok: { type: "boolean" } } },
|
|
4610
|
+
});
|
|
4611
|
+
|
|
4612
|
+
const payload = JSON.parse(fs.readFileSync(await waitForAsyncResultFile(id, 10_000), "utf-8")) as AsyncResultPayload;
|
|
4613
|
+
assert.equal(payload.success, false);
|
|
4614
|
+
assert.match(payload.results[0]?.error ?? "", /Missing acceptanceReport/);
|
|
4615
|
+
assert.equal(payload.results[0]?.acceptance?.status, "rejected");
|
|
4616
|
+
});
|
|
4617
|
+
|
|
4387
4618
|
it("background bash-enabled non-implementation agents can opt out of the completion guard", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
|
|
4388
4619
|
mockPi.onCall({ output: "cold start test after patch" });
|
|
4389
4620
|
|
|
@@ -4829,6 +5060,59 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
4829
5060
|
assert.equal(args[args.indexOf("--model") + 1], "mock/fallback");
|
|
4830
5061
|
});
|
|
4831
5062
|
|
|
5063
|
+
it("aligns initial and resumed background forked sessions with an explicit child cwd", { skip: !isAsyncAvailable() || !createSubagentExecutor ? "jiti or executor not available" : undefined }, async () => {
|
|
5064
|
+
mockPi.onCall({ output: "Forked async work" });
|
|
5065
|
+
const parentCwd = fs.realpathSync(tempDir);
|
|
5066
|
+
const childCwd = path.join(tempDir, "child-cwd");
|
|
5067
|
+
fs.mkdirSync(childCwd);
|
|
5068
|
+
const parentSessionFile = path.join(tempDir, "parent-cross-cwd.jsonl");
|
|
5069
|
+
const forkedSessionFile = path.join(tempDir, "forked-cross-cwd.jsonl");
|
|
5070
|
+
const parentHeader = { type: "session", version: 1, id: "parent", cwd: parentCwd };
|
|
5071
|
+
const childHeader = { type: "session", version: 1, id: "child", cwd: parentCwd, parentSession: parentSessionFile };
|
|
5072
|
+
fs.writeFileSync(parentSessionFile, `${JSON.stringify(parentHeader)}\n`, "utf-8");
|
|
5073
|
+
fs.writeFileSync(forkedSessionFile, `${JSON.stringify(childHeader)}\n`, "utf-8");
|
|
5074
|
+
const ctx = {
|
|
5075
|
+
...makeMinimalCtx(parentCwd),
|
|
5076
|
+
sessionManager: {
|
|
5077
|
+
getSessionId: () => "session-cross-cwd",
|
|
5078
|
+
getSessionFile: () => parentSessionFile,
|
|
5079
|
+
getLeafId: () => "leaf-current",
|
|
5080
|
+
openSession: () => ({ createBranchedSession: () => forkedSessionFile }),
|
|
5081
|
+
},
|
|
5082
|
+
};
|
|
5083
|
+
|
|
5084
|
+
const executor = makeAsyncExecutor([makeAgent("worker")]);
|
|
5085
|
+
const launch = await executor.execute(
|
|
5086
|
+
"forked-cross-cwd",
|
|
5087
|
+
{ agent: "worker", task: "Do work", async: true, context: "fork", cwd: childCwd },
|
|
5088
|
+
new AbortController().signal,
|
|
5089
|
+
undefined,
|
|
5090
|
+
ctx,
|
|
5091
|
+
) as AsyncExecutionResult;
|
|
5092
|
+
assert.ok(!launch.isError, launch.content[0]?.text);
|
|
5093
|
+
assert.ok(launch.details.asyncId);
|
|
5094
|
+
await readAsyncPayload(launch.details.asyncId);
|
|
5095
|
+
|
|
5096
|
+
const sessionHeader = JSON.parse(fs.readFileSync(forkedSessionFile, "utf-8").split("\n", 1)[0]!) as { cwd?: string };
|
|
5097
|
+
assert.equal(sessionHeader.cwd, fs.realpathSync.native(childCwd));
|
|
5098
|
+
|
|
5099
|
+
fs.writeFileSync(forkedSessionFile, `${JSON.stringify(childHeader)}\n`, "utf-8");
|
|
5100
|
+
mockPi.onCall({ output: "Resumed async work" });
|
|
5101
|
+
const resumed = await executor.execute(
|
|
5102
|
+
"resume-cross-cwd",
|
|
5103
|
+
{ action: "resume", id: launch.details.asyncId, message: "Continue" },
|
|
5104
|
+
new AbortController().signal,
|
|
5105
|
+
undefined,
|
|
5106
|
+
ctx,
|
|
5107
|
+
) as AsyncExecutionResult;
|
|
5108
|
+
assert.ok(!resumed.isError, resumed.content[0]?.text);
|
|
5109
|
+
assert.ok(resumed.details.asyncId);
|
|
5110
|
+
await readAsyncPayload(resumed.details.asyncId);
|
|
5111
|
+
|
|
5112
|
+
const resumedHeader = JSON.parse(fs.readFileSync(forkedSessionFile, "utf-8").split("\n", 1)[0]!) as { cwd?: string };
|
|
5113
|
+
assert.equal(resumedHeader.cwd, fs.realpathSync.native(childCwd));
|
|
5114
|
+
});
|
|
5115
|
+
|
|
4832
5116
|
it("background forked runs inherit a parent model outside the registry", { skip: !isAsyncAvailable() || !createSubagentExecutor ? "jiti or executor not available" : undefined }, async () => {
|
|
4833
5117
|
mockPi.onCall({ output: "Forked async work" });
|
|
4834
5118
|
const parentSessionFile = path.join(tempDir, "parent.jsonl");
|
|
@@ -5666,6 +5950,35 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
5666
5950
|
});
|
|
5667
5951
|
});
|
|
5668
5952
|
|
|
5953
|
+
it("background runs carry unaddressed child watchdog blockers into the result payload and acceptance", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
|
|
5954
|
+
await withIsolatedWatchdogSettings(tempDir, async () => {
|
|
5955
|
+
writeWatchdogSettings(tempDir);
|
|
5956
|
+
const id = `async-watchdog-blocker-${Date.now().toString(36)}`;
|
|
5957
|
+
mockPi.onCall({ jsonl: [events.acceptanceReport(), events.watchdogWarning("blocker", "Claims tests passed without running them")] });
|
|
5958
|
+
|
|
5959
|
+
executeAsyncSingle(id, {
|
|
5960
|
+
agent: "worker",
|
|
5961
|
+
task: "Do work",
|
|
5962
|
+
agentConfig: makeAgent("worker"),
|
|
5963
|
+
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
5964
|
+
artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
|
|
5965
|
+
shareEnabled: false,
|
|
5966
|
+
sessionRoot: path.join(tempDir, "sessions"),
|
|
5967
|
+
maxSubagentDepth: 2,
|
|
5968
|
+
acceptance: { level: "checked", criteria: ["Ship it"] },
|
|
5969
|
+
});
|
|
5970
|
+
|
|
5971
|
+
const resultPath = await waitForAsyncResultFile(id, 10_000);
|
|
5972
|
+
const payload = JSON.parse(fs.readFileSync(resultPath, "utf-8")) as AsyncResultPayload;
|
|
5973
|
+
assert.equal(payload.success, false);
|
|
5974
|
+
const child = payload.results[0] as AsyncResultPayload["results"][number] & { watchdog?: { warnings?: Array<{ severity: string; addressed: boolean; summary: string }> } };
|
|
5975
|
+
assert.deepEqual(child.watchdog?.warnings?.map((warning) => [warning.severity, warning.addressed]), [["blocker", false]]);
|
|
5976
|
+
const check = child.acceptance?.runtimeChecks?.find((entry) => entry.id === "watchdog-blocker");
|
|
5977
|
+
assert.equal(check?.status, "failed");
|
|
5978
|
+
assert.match(check?.message ?? "", /Unresolved watchdog blocker/);
|
|
5979
|
+
});
|
|
5980
|
+
});
|
|
5981
|
+
|
|
5669
5982
|
it("background forced drain after final assistant output is cleanup success", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
|
|
5670
5983
|
mockPi.onCall({
|
|
5671
5984
|
jsonl: [events.assistantMessage("async-done-before-drain")],
|
|
@@ -5727,6 +6040,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
5727
6040
|
agent: "scout",
|
|
5728
6041
|
task: "Inspect something",
|
|
5729
6042
|
agentConfig: makeAgent("scout"),
|
|
6043
|
+
acceptance: { level: "attested", criteria: ["Finish cleanly"] },
|
|
5730
6044
|
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
5731
6045
|
artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
|
|
5732
6046
|
shareEnabled: false,
|
|
@@ -343,7 +343,7 @@ describe("async job tracker", { skip: !available ? "pi packages not available" :
|
|
|
343
343
|
parallelGroups: [{ start: 1, count: 2, stepIndex: 1 }],
|
|
344
344
|
steps: [
|
|
345
345
|
{ agent: "scout", status: "complete" },
|
|
346
|
-
{ agent: "reviewer", status: "running", currentTool: "read", transcriptPath },
|
|
346
|
+
{ agent: "reviewer", status: "running", currentTool: "read", transcriptPath, model: "mock/test-model", thinking: "high" },
|
|
347
347
|
{ agent: "worker", status: "running" },
|
|
348
348
|
{ agent: "writer", status: "pending" },
|
|
349
349
|
],
|
|
@@ -379,6 +379,7 @@ describe("async job tracker", { skip: !available ? "pi packages not available" :
|
|
|
379
379
|
assert.equal(job.cwd, customCwd);
|
|
380
380
|
assert.deepEqual(job.agents, ["reviewer", "worker"]);
|
|
381
381
|
assert.deepEqual(job.steps?.map((step: { index?: number }) => step.index), [1, 2]);
|
|
382
|
+
assert.equal(job.steps?.[0]?.thinking, "high");
|
|
382
383
|
assert.equal(job.stepsTotal, 2);
|
|
383
384
|
assert.equal(job.runningSteps, 2);
|
|
384
385
|
assert.equal(job.completedSteps, 0);
|
|
@@ -1100,8 +1101,10 @@ describe("async job tracker", { skip: !available ? "pi packages not available" :
|
|
|
1100
1101
|
await waitForCondition(() => state.asyncJobs.get("run-unchanged")?.toolCount === 1, "changed status load");
|
|
1101
1102
|
await waitForCondition(() => ui.widgets.length > widgetsAfterStatusLoaded, "changed status widget replacement");
|
|
1102
1103
|
|
|
1104
|
+
const requestsBeforeTerminal = ui.renderRequests;
|
|
1103
1105
|
writeStatus(4000, 1, "complete");
|
|
1104
1106
|
await waitForCondition(() => state.asyncJobs.get("run-unchanged")?.status === "complete", "terminal status load");
|
|
1107
|
+
await waitForCondition(() => ui.renderRequests > requestsBeforeTerminal, "terminal status widget redraw");
|
|
1105
1108
|
const widgetsAfterTerminal = ui.widgets.length;
|
|
1106
1109
|
const requestsAfterTerminal = ui.renderRequests;
|
|
1107
1110
|
await new Promise((resolve) => setTimeout(resolve, 35));
|
|
@@ -73,7 +73,7 @@ describe("async status helpers", () => {
|
|
|
73
73
|
outputFile,
|
|
74
74
|
steps: [
|
|
75
75
|
{ agent: "scout", status: "complete", durationMs: 10, description: "Inspect auth only" },
|
|
76
|
-
{ agent: "worker", status: "running", durationMs: 20, description: "Patch billing only", contextLimit: 128_000, toolBudgetBlocked: true, watchdog: { phase: "stale", seq: 2, lastUpdate: 200
|
|
76
|
+
{ agent: "worker", status: "running", durationMs: 20, description: "Patch billing only", contextLimit: 128_000, toolBudgetBlocked: true, watchdog: { phase: "stale", seq: 2, lastUpdate: 200 } },
|
|
77
77
|
],
|
|
78
78
|
});
|
|
79
79
|
const descriptor = createRunFanoutBudget("run-a", 64);
|