@pi-agents/orchid 0.1.0-beta.0
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 +41 -0
- package/LICENSE +21 -0
- package/README.md +246 -0
- package/agents/AGENTS-MANIFEST.md +42 -0
- package/agents/brain.md +42 -0
- package/agents/context-builder.md +46 -0
- package/agents/delegate.md +12 -0
- package/agents/dev-1.md +42 -0
- package/agents/oracle.md +73 -0
- package/agents/planner.md +55 -0
- package/agents/researcher.md +52 -0
- package/agents/reviewer.md +79 -0
- package/agents/scout.md +50 -0
- package/agents/tester.md +45 -0
- package/agents/worker.md +55 -0
- package/extensions/ralph.ts +1 -0
- package/extensions/reviewer-extension.ts +125 -0
- package/extensions/task-orchestrator.ts +28 -0
- package/package.json +63 -0
- package/prompts/gather-context-and-clarify.md +13 -0
- package/prompts/parallel-cleanup.md +59 -0
- package/prompts/parallel-context-build.md +53 -0
- package/prompts/parallel-handoff-plan.md +59 -0
- package/prompts/parallel-research.md +50 -0
- package/prompts/parallel-review.md +54 -0
- package/prompts/review-loop.md +41 -0
- package/skills/orchid/SKILL.md +214 -0
- package/skills/orchid/orchid-cleanup/SKILL.md +122 -0
- package/skills/orchid/orchid-converge/SKILL.md +124 -0
- package/skills/orchid/orchid-decompose/SKILL.md +201 -0
- package/skills/orchid/orchid-doctor/SKILL.md +162 -0
- package/skills/orchid/orchid-investigate/SKILL.md +102 -0
- package/skills/orchid/orchid-launch/SKILL.md +147 -0
- package/skills/ralph/SKILL.md +73 -0
- package/skills/subagents/pi-subagents/SKILL.md +813 -0
- package/src/index.ts +7 -0
- package/src/orchestrator/abort.ts +534 -0
- package/src/orchestrator/agent-bridge-extension.ts +1020 -0
- package/src/orchestrator/agent-host.ts +954 -0
- package/src/orchestrator/cleanup.ts +776 -0
- package/src/orchestrator/config-loader.ts +1412 -0
- package/src/orchestrator/config-schema.ts +690 -0
- package/src/orchestrator/config.ts +81 -0
- package/src/orchestrator/context-window.ts +66 -0
- package/src/orchestrator/diagnostic-reports.ts +475 -0
- package/src/orchestrator/diagnostics.ts +394 -0
- package/src/orchestrator/discovery.ts +1833 -0
- package/src/orchestrator/engine-worker.ts +415 -0
- package/src/orchestrator/engine.ts +5940 -0
- package/src/orchestrator/execution.ts +3104 -0
- package/src/orchestrator/extension.ts +5934 -0
- package/src/orchestrator/formatting.ts +785 -0
- package/src/orchestrator/git.ts +88 -0
- package/src/orchestrator/index.ts +28 -0
- package/src/orchestrator/lane-runner.ts +1787 -0
- package/src/orchestrator/mailbox.ts +780 -0
- package/src/orchestrator/merge.ts +3414 -0
- package/src/orchestrator/messages.ts +1062 -0
- package/src/orchestrator/migrations.ts +278 -0
- package/src/orchestrator/naming.ts +117 -0
- package/src/orchestrator/path-resolver.ts +275 -0
- package/src/orchestrator/persistence.ts +2625 -0
- package/src/orchestrator/process-registry.ts +452 -0
- package/src/orchestrator/quality-gate.ts +1085 -0
- package/src/orchestrator/resume.ts +3488 -0
- package/src/orchestrator/sessions.ts +57 -0
- package/src/orchestrator/settings-loader.ts +136 -0
- package/src/orchestrator/settings-tui.ts +2208 -0
- package/src/orchestrator/sidecar-telemetry.ts +267 -0
- package/src/orchestrator/supervisor.ts +4548 -0
- package/src/orchestrator/task-executor-core.ts +675 -0
- package/src/orchestrator/tmux-compat.ts +37 -0
- package/src/orchestrator/tool-allowlist-constants.ts +37 -0
- package/src/orchestrator/types.ts +4465 -0
- package/src/orchestrator/verification.ts +547 -0
- package/src/orchestrator/waves.ts +1564 -0
- package/src/orchestrator/workspace.ts +707 -0
- package/src/orchestrator/worktree.ts +2725 -0
- package/src/ralph/index.ts +825 -0
- package/src/subagents/agents/agent-management.ts +648 -0
- package/src/subagents/agents/agent-scope.ts +6 -0
- package/src/subagents/agents/agent-selection.ts +23 -0
- package/src/subagents/agents/agent-serializer.ts +86 -0
- package/src/subagents/agents/agents.ts +832 -0
- package/src/subagents/agents/chain-serializer.ts +137 -0
- package/src/subagents/agents/frontmatter.ts +29 -0
- package/src/subagents/agents/identity.ts +30 -0
- package/src/subagents/agents/skills.ts +632 -0
- package/src/subagents/extension/config.ts +16 -0
- package/src/subagents/extension/control-notices.ts +92 -0
- package/src/subagents/extension/doctor.ts +199 -0
- package/src/subagents/extension/fanout-child.ts +170 -0
- package/src/subagents/extension/index.ts +573 -0
- package/src/subagents/extension/schemas.ts +168 -0
- package/src/subagents/intercom/intercom-bridge.ts +379 -0
- package/src/subagents/intercom/result-intercom.ts +377 -0
- package/src/subagents/runs/background/async-execution.ts +712 -0
- package/src/subagents/runs/background/async-job-tracker.ts +310 -0
- package/src/subagents/runs/background/async-resume.ts +345 -0
- package/src/subagents/runs/background/async-status.ts +325 -0
- package/src/subagents/runs/background/completion-dedupe.ts +63 -0
- package/src/subagents/runs/background/notify.ts +108 -0
- package/src/subagents/runs/background/parallel-groups.ts +45 -0
- package/src/subagents/runs/background/result-watcher.ts +307 -0
- package/src/subagents/runs/background/run-id-resolver.ts +83 -0
- package/src/subagents/runs/background/run-status.ts +269 -0
- package/src/subagents/runs/background/stale-run-reconciler.ts +336 -0
- package/src/subagents/runs/background/subagent-runner.ts +1808 -0
- package/src/subagents/runs/background/top-level-async.ts +13 -0
- package/src/subagents/runs/foreground/chain-clarify.ts +1333 -0
- package/src/subagents/runs/foreground/chain-execution.ts +938 -0
- package/src/subagents/runs/foreground/execution.ts +918 -0
- package/src/subagents/runs/foreground/subagent-executor.ts +2527 -0
- package/src/subagents/runs/shared/completion-guard.ts +147 -0
- package/src/subagents/runs/shared/long-running-guard.ts +175 -0
- package/src/subagents/runs/shared/mcp-direct-tool-allowlist.ts +365 -0
- package/src/subagents/runs/shared/model-fallback.ts +103 -0
- package/src/subagents/runs/shared/nested-events.ts +819 -0
- package/src/subagents/runs/shared/nested-path.ts +52 -0
- package/src/subagents/runs/shared/nested-render.ts +115 -0
- package/src/subagents/runs/shared/parallel-utils.ts +109 -0
- package/src/subagents/runs/shared/pi-args.ts +220 -0
- package/src/subagents/runs/shared/pi-spawn.ts +115 -0
- package/src/subagents/runs/shared/run-history.ts +60 -0
- package/src/subagents/runs/shared/single-output.ts +164 -0
- package/src/subagents/runs/shared/subagent-control.ts +226 -0
- package/src/subagents/runs/shared/subagent-prompt-runtime.ts +170 -0
- package/src/subagents/runs/shared/worktree.ts +577 -0
- package/src/subagents/shared/artifacts.ts +98 -0
- package/src/subagents/shared/atomic-json.ts +16 -0
- package/src/subagents/shared/file-coalescer.ts +40 -0
- package/src/subagents/shared/fork-context.ts +76 -0
- package/src/subagents/shared/formatters.ts +133 -0
- package/src/subagents/shared/jsonl-writer.ts +81 -0
- package/src/subagents/shared/model-info.ts +78 -0
- package/src/subagents/shared/post-exit-stdio-guard.ts +85 -0
- package/src/subagents/shared/session-identity.ts +10 -0
- package/src/subagents/shared/session-tokens.ts +44 -0
- package/src/subagents/shared/settings.ts +397 -0
- package/src/subagents/shared/status-format.ts +49 -0
- package/src/subagents/shared/types.ts +822 -0
- package/src/subagents/shared/utils.ts +450 -0
- package/src/subagents/slash/prompt-template-bridge.ts +397 -0
- package/src/subagents/slash/slash-bridge.ts +174 -0
- package/src/subagents/slash/slash-commands.ts +528 -0
- package/src/subagents/slash/slash-live-state.ts +292 -0
- package/src/subagents/tui/render-helpers.ts +80 -0
- package/src/subagents/tui/render.ts +1358 -0
- package/templates/agents/local/supervisor.md +33 -0
- package/templates/agents/local/task-merger.md +27 -0
- package/templates/agents/local/task-reviewer.md +30 -0
- package/templates/agents/local/task-worker.md +34 -0
- package/templates/agents/supervisor-routing.md +92 -0
- package/templates/agents/supervisor.md +229 -0
- package/templates/agents/task-merger.md +214 -0
- package/templates/agents/task-reviewer.md +260 -0
- package/templates/agents/task-worker-segment.md +44 -0
- package/templates/agents/task-worker.md +557 -0
- package/templates/tasks/CONTEXT.md +30 -0
- package/templates/tasks/EXAMPLE-001-hello-world/PROMPT.md +98 -0
- package/templates/tasks/EXAMPLE-001-hello-world/STATUS.md +73 -0
- package/templates/tasks/EXAMPLE-002-parallel-smoke/PROMPT.md +97 -0
- package/templates/tasks/EXAMPLE-002-parallel-smoke/STATUS.md +73 -0
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exit classification types and logic for task diagnostics.
|
|
3
|
+
*
|
|
4
|
+
* Defines the structured `TaskExitDiagnostic` type that replaces
|
|
5
|
+
* free-text `exitReason` for deterministic retry decisions,
|
|
6
|
+
* cost tracking, and dashboard telemetry.
|
|
7
|
+
*
|
|
8
|
+
* @module orch/diagnostics
|
|
9
|
+
* @see docs/specifications/orchid/resilience-and-diagnostics-roadmap.md §1b
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
// ── Token Counts (Diagnostics) ───────────────────────────────────────
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Token usage breakdown for a single session.
|
|
16
|
+
*
|
|
17
|
+
* Matches the RPC exit-summary `tokens` shape: four count fields only.
|
|
18
|
+
* Cost is tracked separately as a top-level field on `ExitSummary` and
|
|
19
|
+
* `TaskExitDiagnostic`, not embedded in the token counts.
|
|
20
|
+
*
|
|
21
|
+
* This is distinct from `TokenCounts` in `types.ts` (which bundles
|
|
22
|
+
* `costUsd` for batch-history aggregation). Downstream consumers that
|
|
23
|
+
* need to convert can merge `{ ...sessionTokens, costUsd: cost }`.
|
|
24
|
+
*/
|
|
25
|
+
export interface SessionTokenCounts {
|
|
26
|
+
/** Input tokens consumed */
|
|
27
|
+
input: number;
|
|
28
|
+
/** Output tokens generated */
|
|
29
|
+
output: number;
|
|
30
|
+
/** Tokens served from cache (read) */
|
|
31
|
+
cacheRead: number;
|
|
32
|
+
/** Tokens written to cache */
|
|
33
|
+
cacheWrite: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ── Exit Classification ──────────────────────────────────────────────
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* All possible exit classifications for a task session.
|
|
40
|
+
*
|
|
41
|
+
* Each value maps to a specific failure mode that downstream consumers
|
|
42
|
+
* (retry logic, dashboard, cost reports) can branch on deterministically.
|
|
43
|
+
*
|
|
44
|
+
* | Classification | Meaning |
|
|
45
|
+
* |----------------------|------------------------------------------------------|
|
|
46
|
+
* | `completed` | `.DONE` file found — task finished successfully |
|
|
47
|
+
* | `api_error` | API returned error (auth, rate limit, overload) |
|
|
48
|
+
* | `model_access_error` | Model unavailable (401/403/429, model not found) |
|
|
49
|
+
* | `context_overflow` | Hit context window limit (compactions + high ctx %) |
|
|
50
|
+
* | `wall_clock_timeout` | Killed by task-runner's max_worker_minutes timer |
|
|
51
|
+
* | `process_crash` | Non-zero exit code with no API error indicators |
|
|
52
|
+
* | `session_vanished` | Session disappeared without exit summary |
|
|
53
|
+
* | `stall_timeout` | No STATUS.md progress for stall_timeout minutes |
|
|
54
|
+
* | `user_killed` | User manually killed the session (e.g., forced process kill) |
|
|
55
|
+
* | `spawn_failure` | Worker process never spawned (e.g., Pi CLI not findable, worktree provisioning) |
|
|
56
|
+
* | `unknown` | Could not determine cause |
|
|
57
|
+
*
|
|
58
|
+
* Note: `spawn_failure` (TP-190, #561) is set BEFORE any agent process exists —
|
|
59
|
+
* it is produced synchronously when `spawnAgent()` throws (resolvePiCliPath
|
|
60
|
+
* miss, file-system error, etc.). It is intentionally NOT in
|
|
61
|
+
* `TIER0_RETRYABLE_CLASSIFICATIONS` because spawn-stage failures are never
|
|
62
|
+
* transient; retrying without operator intervention only burns budget.
|
|
63
|
+
*/
|
|
64
|
+
export type ExitClassification =
|
|
65
|
+
| "completed"
|
|
66
|
+
| "api_error"
|
|
67
|
+
| "model_access_error"
|
|
68
|
+
| "context_overflow"
|
|
69
|
+
| "wall_clock_timeout"
|
|
70
|
+
| "process_crash"
|
|
71
|
+
| "session_vanished"
|
|
72
|
+
| "stall_timeout"
|
|
73
|
+
| "user_killed"
|
|
74
|
+
| "spawn_failure"
|
|
75
|
+
| "unknown";
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* All classification values as a readonly array, for iteration and validation.
|
|
79
|
+
*/
|
|
80
|
+
export const EXIT_CLASSIFICATIONS: readonly ExitClassification[] = [
|
|
81
|
+
"completed",
|
|
82
|
+
"api_error",
|
|
83
|
+
"model_access_error",
|
|
84
|
+
"context_overflow",
|
|
85
|
+
"wall_clock_timeout",
|
|
86
|
+
"process_crash",
|
|
87
|
+
"session_vanished",
|
|
88
|
+
"stall_timeout",
|
|
89
|
+
"user_killed",
|
|
90
|
+
"spawn_failure",
|
|
91
|
+
"unknown",
|
|
92
|
+
] as const;
|
|
93
|
+
|
|
94
|
+
// ── Retry Record ─────────────────────────────────────────────────────
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* A single API retry event from the RPC wrapper's exit summary.
|
|
98
|
+
*
|
|
99
|
+
* Captured from `auto_retry_start/end` RPC events.
|
|
100
|
+
*/
|
|
101
|
+
export interface RetryRecord {
|
|
102
|
+
/** Retry attempt number (1-indexed) */
|
|
103
|
+
attempt: number;
|
|
104
|
+
/** Error message that triggered the retry */
|
|
105
|
+
error: string;
|
|
106
|
+
/** Delay in milliseconds before retrying */
|
|
107
|
+
delayMs: number;
|
|
108
|
+
/** Whether the retry succeeded */
|
|
109
|
+
succeeded: boolean;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ── Exit Summary ─────────────────────────────────────────────────────
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Exit summary written by rpc-wrapper.mjs on process exit.
|
|
116
|
+
*
|
|
117
|
+
* This is the wrapper's output artifact — a JSON file capturing
|
|
118
|
+
* everything the wrapper observed during the session. The task-runner
|
|
119
|
+
* reads this to build `TaskExitDiagnostic`.
|
|
120
|
+
*
|
|
121
|
+
* **Field optionality rationale:**
|
|
122
|
+
* The wrapper initializes counters (toolCalls, compactions, durationSec,
|
|
123
|
+
* retries) at startup, so they are always present even on crash — these
|
|
124
|
+
* are required. Fields that depend on RPC event accumulation (tokens,
|
|
125
|
+
* cost, lastToolCall, error) are nullable — they may be absent if the
|
|
126
|
+
* process crashes before capturing any events. `exitCode` and
|
|
127
|
+
* `exitSignal` are optional (`?`) because the wrapper may crash before
|
|
128
|
+
* the Node exit handler fires, producing a partial JSON artifact that
|
|
129
|
+
* `JSON.parse()` succeeds on but lacks these fields.
|
|
130
|
+
*
|
|
131
|
+
* Consumers MUST use `typeof` guards on optional/nullable fields before
|
|
132
|
+
* branching (e.g., `typeof exitCode === "number"` rather than `!== null`).
|
|
133
|
+
*/
|
|
134
|
+
export interface ExitSummary {
|
|
135
|
+
/** Process exit code. Optional — may be absent if wrapper crashes before exit handler fires. Null if killed by signal. */
|
|
136
|
+
exitCode?: number | null;
|
|
137
|
+
/** Signal that killed the process (e.g., "SIGTERM"). Optional — may be absent on crash. Null if clean exit. */
|
|
138
|
+
exitSignal?: string | null;
|
|
139
|
+
/** Accumulated token counts across all turns (null if no message_end events received) */
|
|
140
|
+
tokens: SessionTokenCounts | null;
|
|
141
|
+
/** Total cost in USD (null if no cost data received) */
|
|
142
|
+
cost: number | null;
|
|
143
|
+
/** Total tool calls made (initialized to 0 at startup) */
|
|
144
|
+
toolCalls: number;
|
|
145
|
+
/** API retry events observed (initialized to [] at startup) */
|
|
146
|
+
retries: RetryRecord[];
|
|
147
|
+
/** Number of context compactions observed (initialized to 0 at startup) */
|
|
148
|
+
compactions: number;
|
|
149
|
+
/** Wall-clock duration of the session in seconds (always written, even on crash) */
|
|
150
|
+
durationSec: number;
|
|
151
|
+
/** Last tool call description (e.g., "bash: node --test tests/*.test.ts"), null if no tools were called */
|
|
152
|
+
lastToolCall: string | null;
|
|
153
|
+
/** Error message if the session ended with an error, null on clean exit */
|
|
154
|
+
error: string | null;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// ── Classification Input ─────────────────────────────────────────────
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Structured input to `classifyExit()`.
|
|
161
|
+
*
|
|
162
|
+
* Aggregates all signals needed for deterministic classification.
|
|
163
|
+
* Sources:
|
|
164
|
+
* - `exitSummary`: from rpc-wrapper.mjs exit summary JSON (null if file missing)
|
|
165
|
+
* - `doneFileFound`: from .DONE file presence check (task-runner)
|
|
166
|
+
* - `timerKilled`: true if task-runner's max_worker_minutes timer killed the session
|
|
167
|
+
* - `contextKilled`: true if the task-runner explicitly killed the session due to context limit
|
|
168
|
+
* - `stallDetected`: true if monitoring detected no STATUS.md progress
|
|
169
|
+
* - `userKilled`: true if user manually killed the session (e.g., /orch-abort, forced process kill)
|
|
170
|
+
* - `contextPct`: estimated context utilization % (0-100), null if unknown
|
|
171
|
+
*
|
|
172
|
+
* Design: single structured input object (not positional args) for
|
|
173
|
+
* extensibility as new signals are added in future phases.
|
|
174
|
+
*/
|
|
175
|
+
export interface ExitClassificationInput {
|
|
176
|
+
/** Exit summary from rpc-wrapper.mjs. Null if the summary file was not found. */
|
|
177
|
+
exitSummary: ExitSummary | null;
|
|
178
|
+
/** Whether the .DONE file was found in the task folder */
|
|
179
|
+
doneFileFound: boolean;
|
|
180
|
+
/** Whether the task-runner's wall-clock timer killed the session */
|
|
181
|
+
timerKilled: boolean;
|
|
182
|
+
/** Whether the task-runner explicitly killed the session due to context limit (TP-026) */
|
|
183
|
+
contextKilled?: boolean;
|
|
184
|
+
/** Whether monitoring detected a stall (no STATUS.md progress) */
|
|
185
|
+
stallDetected: boolean;
|
|
186
|
+
/** Whether the user manually killed the session */
|
|
187
|
+
userKilled: boolean;
|
|
188
|
+
/** Estimated context utilization percentage (0-100), null if unknown */
|
|
189
|
+
contextPct: number | null;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// ── Task Exit Diagnostic ─────────────────────────────────────────────
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Structured diagnostic for a task session's exit.
|
|
196
|
+
*
|
|
197
|
+
* Sits alongside the legacy `exitReason: string` on `LaneTaskOutcome`
|
|
198
|
+
* during the transition period (Phase 1). Promoted to canonical in
|
|
199
|
+
* schema v3 (Phase 3).
|
|
200
|
+
*
|
|
201
|
+
* Produced by calling `classifyExit()` after the session ends, then
|
|
202
|
+
* enriching with progress/context metadata from STATUS.md and git.
|
|
203
|
+
*/
|
|
204
|
+
export interface TaskExitDiagnostic {
|
|
205
|
+
/** Deterministic exit classification */
|
|
206
|
+
classification: ExitClassification;
|
|
207
|
+
/** Process exit code (null if killed by signal or summary missing) */
|
|
208
|
+
exitCode: number | null;
|
|
209
|
+
/** Human-readable error message (null if clean exit) */
|
|
210
|
+
errorMessage: string | null;
|
|
211
|
+
/** Token usage breakdown (null if no summary available) */
|
|
212
|
+
tokensUsed: SessionTokenCounts | null;
|
|
213
|
+
/** Estimated context utilization percentage (0-100, null if unknown) */
|
|
214
|
+
contextPct: number | null;
|
|
215
|
+
/** Number of commits on the task branch (partial progress indicator) */
|
|
216
|
+
partialProgressCommits: number;
|
|
217
|
+
/** Branch name with partial progress (null if no branch) */
|
|
218
|
+
partialProgressBranch: string | null;
|
|
219
|
+
/** Wall-clock duration of the session in seconds */
|
|
220
|
+
durationSec: number;
|
|
221
|
+
/** Last known step number from STATUS.md (null if unparsed) */
|
|
222
|
+
lastKnownStep: number | null;
|
|
223
|
+
/** Last known checkbox text from STATUS.md (null if unparsed) */
|
|
224
|
+
lastKnownCheckbox: string | null;
|
|
225
|
+
/** Repo identifier ("default" in repo mode, repo key in workspace mode) */
|
|
226
|
+
repoId: string;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// ── Classification Logic ─────────────────────────────────────────────
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Threshold for context utilization percentage to consider "high".
|
|
233
|
+
* Used in the `context_overflow` classification path:
|
|
234
|
+
* compactions > 0 AND contextPct >= this threshold → context_overflow.
|
|
235
|
+
*/
|
|
236
|
+
export const CONTEXT_OVERFLOW_THRESHOLD_PCT = 90;
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Patterns that indicate a model access error (as opposed to a generic API error).
|
|
240
|
+
*
|
|
241
|
+
* These patterns match error messages from API providers when:
|
|
242
|
+
* - The model is not found or deprecated
|
|
243
|
+
* - Authentication/authorization fails (HTTP 401/403)
|
|
244
|
+
* - Rate limits are hit specifically for the model (HTTP 429)
|
|
245
|
+
* - API key is expired or invalid
|
|
246
|
+
*
|
|
247
|
+
* The patterns are case-insensitive and tested against the error string.
|
|
248
|
+
*
|
|
249
|
+
* @since TP-055
|
|
250
|
+
*/
|
|
251
|
+
export const MODEL_ACCESS_ERROR_PATTERNS: readonly RegExp[] = [
|
|
252
|
+
/\b(?:401|403)\b/, // HTTP auth/forbidden status codes
|
|
253
|
+
/\b429\b/, // HTTP rate limit
|
|
254
|
+
/model[_ ]not[_ ]found/i, // Model not found
|
|
255
|
+
/model[_ ](?:is[_ ])?unavailable/i, // Model unavailable
|
|
256
|
+
/model[_ ](?:has[_ ]been[_ ])?deprecated/i, // Model deprecated
|
|
257
|
+
/api[_ ]key[_ ](?:expired|invalid|revoked)/i, // API key issues
|
|
258
|
+
/invalid[_ ]api[_ ]key/i, // Invalid API key (alternate phrasing)
|
|
259
|
+
/authentication[_ ](?:failed|error|required)/i, // Auth failures
|
|
260
|
+
/authorization[_ ](?:failed|error|denied)/i, // Authz failures
|
|
261
|
+
/access[_ ]denied/i, // Generic access denied
|
|
262
|
+
/permission[_ ]denied/i, // Permission denied
|
|
263
|
+
/quota[_ ]exceeded/i, // Quota exceeded
|
|
264
|
+
/rate[_ ]limit/i, // Rate limit (phrase)
|
|
265
|
+
/insufficient[_ ]quota/i, // Insufficient quota
|
|
266
|
+
];
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Test whether an error message indicates a model access error.
|
|
270
|
+
*
|
|
271
|
+
* Used by `classifyExit()` to distinguish model-specific failures from
|
|
272
|
+
* generic API errors, enabling targeted fallback to the session model.
|
|
273
|
+
*
|
|
274
|
+
* @param errorMessage - Error message to test
|
|
275
|
+
* @returns true if the error matches a model access pattern
|
|
276
|
+
* @since TP-055
|
|
277
|
+
*/
|
|
278
|
+
export function isModelAccessError(errorMessage: string): boolean {
|
|
279
|
+
if (!errorMessage) return false;
|
|
280
|
+
return MODEL_ACCESS_ERROR_PATTERNS.some((pattern) => pattern.test(errorMessage));
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Classify a task session's exit into a deterministic category.
|
|
285
|
+
*
|
|
286
|
+
* Uses a strict precedence order — the first matching condition wins.
|
|
287
|
+
* This ensures deterministic results even when multiple signals are
|
|
288
|
+
* present (e.g., a session that was both stalled AND crashed).
|
|
289
|
+
*
|
|
290
|
+
* **Classification precedence (highest → lowest):**
|
|
291
|
+
*
|
|
292
|
+
* | Priority | Condition | Result |
|
|
293
|
+
* |----------|------------------------------------------------------|----------------------|
|
|
294
|
+
* | 1 | `.DONE` file found | `completed` |
|
|
295
|
+
* | 2a | Retries with model-access error pattern | `model_access_error` |
|
|
296
|
+
* | 2b | Retries present with final retry failed | `api_error` |
|
|
297
|
+
* | 2c | Error message has model-access pattern (no retries) | `model_access_error` |
|
|
298
|
+
* | 3 | Compactions > 0 AND contextPct ≥ 90% | `context_overflow` |
|
|
299
|
+
* | 3b | Task-runner explicitly context-killed | `context_overflow` |
|
|
300
|
+
* | 4 | Timer killed the session | `wall_clock_timeout` |
|
|
301
|
+
* | 5 | Non-zero exit code, no API error | `process_crash` |
|
|
302
|
+
* | 6 | No exit summary file (session vanished) | `session_vanished` |
|
|
303
|
+
* | 7 | Stall detected (no STATUS.md progress) | `stall_timeout` |
|
|
304
|
+
* | 8 | User manually killed the session | `user_killed` |
|
|
305
|
+
* | 9 | None of the above | `unknown` |
|
|
306
|
+
*
|
|
307
|
+
* **Tie-break rationale:**
|
|
308
|
+
* - `.DONE` always wins because the task succeeded regardless of how messy
|
|
309
|
+
* the session was (retries, compactions, etc.).
|
|
310
|
+
* - `model_access_error` beats generic `api_error` because it's more specific
|
|
311
|
+
* and enables targeted fallback (retry with session model).
|
|
312
|
+
* - `api_error` beats `context_overflow` because API failures are more
|
|
313
|
+
* actionable (auth fix, rate limit backoff).
|
|
314
|
+
* - `wall_clock_timeout` beats `process_crash` because the timer kill
|
|
315
|
+
* explains the non-zero exit code.
|
|
316
|
+
* - `session_vanished` (no summary) is checked after exit-code-based
|
|
317
|
+
* paths because those require the summary to exist.
|
|
318
|
+
* - `stall_timeout` and `user_killed` are low-priority because they're
|
|
319
|
+
* external signals that may co-occur with other conditions.
|
|
320
|
+
*
|
|
321
|
+
* @param input - Aggregated signals from the session exit
|
|
322
|
+
* @returns The exit classification string
|
|
323
|
+
*/
|
|
324
|
+
export function classifyExit(input: ExitClassificationInput): ExitClassification {
|
|
325
|
+
const { exitSummary, doneFileFound, timerKilled, stallDetected, userKilled, contextPct } = input;
|
|
326
|
+
const contextKilled = input.contextKilled ?? false;
|
|
327
|
+
|
|
328
|
+
// 1. .DONE file found → completed (task succeeded, regardless of session state)
|
|
329
|
+
if (doneFileFound) {
|
|
330
|
+
return "completed";
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// 2a. Retries present with model-access error pattern → model_access_error
|
|
334
|
+
// 2b. Retries present with final retry failed → api_error
|
|
335
|
+
if (exitSummary?.retries && exitSummary.retries.length > 0) {
|
|
336
|
+
const lastRetry = exitSummary.retries[exitSummary.retries.length - 1];
|
|
337
|
+
if (!lastRetry.succeeded) {
|
|
338
|
+
// Check if the retry error indicates a model access issue
|
|
339
|
+
if (isModelAccessError(lastRetry.error)) {
|
|
340
|
+
return "model_access_error";
|
|
341
|
+
}
|
|
342
|
+
return "api_error";
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// 2c. Error message (no retries) indicates model access issue → model_access_error
|
|
347
|
+
if (exitSummary?.error && isModelAccessError(exitSummary.error)) {
|
|
348
|
+
return "model_access_error";
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// 3. Compactions > 0 AND high context utilization → context_overflow
|
|
352
|
+
if (exitSummary && exitSummary.compactions > 0) {
|
|
353
|
+
const effectivePct = contextPct ?? 0;
|
|
354
|
+
if (effectivePct >= CONTEXT_OVERFLOW_THRESHOLD_PCT) {
|
|
355
|
+
return "context_overflow";
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// 3b. Task-runner explicitly killed session due to context limit → context_overflow
|
|
360
|
+
// Catches cases where exit summary is missing (wrapper crashed) or compactions=0
|
|
361
|
+
// but the task-runner's own context guard triggered the kill.
|
|
362
|
+
if (contextKilled) {
|
|
363
|
+
return "context_overflow";
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// 4. Task-runner's wall-clock timer killed the session → wall_clock_timeout
|
|
367
|
+
if (timerKilled) {
|
|
368
|
+
return "wall_clock_timeout";
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// 5. Non-zero exit code, no API error indicators → process_crash
|
|
372
|
+
// Guard with typeof to handle partial summaries where exitCode may be undefined
|
|
373
|
+
if (exitSummary && typeof exitSummary.exitCode === "number" && exitSummary.exitCode !== 0) {
|
|
374
|
+
return "process_crash";
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// 6. No exit summary file found → session_vanished
|
|
378
|
+
if (exitSummary === null) {
|
|
379
|
+
return "session_vanished";
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// 7. Stall detected (no STATUS.md progress) → stall_timeout
|
|
383
|
+
if (stallDetected) {
|
|
384
|
+
return "stall_timeout";
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// 8. User manually killed the session → user_killed
|
|
388
|
+
if (userKilled) {
|
|
389
|
+
return "user_killed";
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// 9. None of the above → unknown
|
|
393
|
+
return "unknown";
|
|
394
|
+
}
|