@phnx-labs/agents-cli 1.22.59 → 1.22.61
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 +71 -0
- package/dist/cli/command-registry.d.ts +1 -0
- package/dist/cli/command-registry.js +2 -0
- package/dist/commands/browser.js +9 -4
- package/dist/commands/doctor.js +1 -1
- package/dist/commands/exec.js +35 -1
- package/dist/commands/harness-hooks.d.ts +55 -0
- package/dist/commands/harness-hooks.js +104 -0
- package/dist/commands/harness-wizard.d.ts +33 -14
- package/dist/commands/harness-wizard.js +53 -23
- package/dist/commands/harness.d.ts +14 -0
- package/dist/commands/harness.js +86 -5
- package/dist/commands/perf.js +10 -0
- package/dist/commands/reminders.d.ts +9 -0
- package/dist/commands/reminders.js +49 -0
- package/dist/commands/run-account-picker.d.ts +14 -0
- package/dist/commands/run-account-picker.js +13 -0
- package/dist/commands/sessions-picker.d.ts +13 -0
- package/dist/commands/sessions-picker.js +17 -8
- package/dist/commands/sessions.js +13 -11
- package/dist/commands/teams-picker.js +20 -6
- package/dist/commands/teams.d.ts +3 -3
- package/dist/commands/teams.js +86 -24
- package/dist/index.js +9 -0
- package/dist/lib/accounting/rotate.d.ts +63 -0
- package/dist/lib/accounting/rotate.js +240 -16
- package/dist/lib/accounting/usage-sync.d.ts +12 -2
- package/dist/lib/accounting/usage-sync.js +34 -6
- package/dist/lib/browser/drivers/local.d.ts +11 -0
- package/dist/lib/browser/drivers/local.js +26 -0
- package/dist/lib/browser/profiles.js +8 -6
- package/dist/lib/browser/service.d.ts +12 -8
- package/dist/lib/browser/service.js +38 -10
- package/dist/lib/claude-statusline.d.ts +14 -1
- package/dist/lib/claude-statusline.js +27 -2
- package/dist/lib/daemon/runner.js +17 -2
- package/dist/lib/devices/doctor-findings.d.ts +1 -1
- package/dist/lib/devices/doctor-findings.js +22 -4
- package/dist/lib/doctor-diff.d.ts +21 -5
- package/dist/lib/doctor-diff.js +242 -76
- package/dist/lib/feed/events.d.ts +1 -1
- package/dist/lib/feed/events.js +28 -15
- package/dist/lib/github/gh-overload.d.ts +58 -0
- package/dist/lib/github/gh-overload.js +246 -0
- package/dist/lib/github/rest.d.ts +64 -0
- package/dist/lib/github/rest.js +111 -0
- package/dist/lib/harness-connection-test.d.ts +57 -0
- package/dist/lib/harness-connection-test.js +80 -0
- package/dist/lib/heal.js +8 -3
- package/dist/lib/installations/shims.d.ts +22 -0
- package/dist/lib/installations/shims.js +104 -0
- package/dist/lib/linear-project-counts.js +8 -0
- package/dist/lib/linear-rate-limit.d.ts +26 -0
- package/dist/lib/linear-rate-limit.js +163 -0
- package/dist/lib/mcp.d.ts +9 -0
- package/dist/lib/mcp.js +37 -1
- package/dist/lib/open-url.js +5 -3
- package/dist/lib/perf/db.d.ts +1 -1
- package/dist/lib/perf/db.js +53 -2
- package/dist/lib/perf/types.d.ts +14 -0
- package/dist/lib/permissions.d.ts +28 -0
- package/dist/lib/permissions.js +156 -1
- package/dist/lib/refresh.js +9 -1
- package/dist/lib/reminders.d.ts +29 -0
- package/dist/lib/reminders.js +88 -0
- package/dist/lib/resource-content-diff.d.ts +33 -0
- package/dist/lib/resource-content-diff.js +103 -0
- package/dist/lib/rules/compile.d.ts +7 -0
- package/dist/lib/rules/compile.js +7 -1
- package/dist/lib/session/active.d.ts +41 -4
- package/dist/lib/session/active.js +58 -7
- package/dist/lib/session/host-link.d.ts +22 -0
- package/dist/lib/session/host-link.js +40 -4
- package/dist/lib/session/live-metadata.js +3 -3
- package/dist/lib/session/trajectory.d.ts +42 -0
- package/dist/lib/session/trajectory.js +46 -27
- package/dist/lib/ssh-exec.d.ts +30 -0
- package/dist/lib/ssh-exec.js +37 -5
- package/dist/lib/startup/command-registry.js +1 -1
- package/dist/lib/subagents-registry.d.ts +18 -0
- package/dist/lib/subagents-registry.js +79 -0
- package/dist/lib/teams/agents.d.ts +12 -0
- package/dist/lib/teams/agents.js +51 -0
- package/dist/lib/teams/api.d.ts +8 -0
- package/dist/lib/teams/api.js +50 -6
- package/dist/lib/teams/delivery.d.ts +14 -4
- package/dist/lib/teams/delivery.js +15 -5
- package/dist/lib/traces/schema2-build.d.ts +85 -0
- package/dist/lib/traces/schema2-build.js +637 -0
- package/dist/lib/traces/schema2-danger.d.ts +36 -0
- package/dist/lib/traces/schema2-danger.js +185 -0
- package/dist/lib/traces/schema2.d.ts +149 -0
- package/dist/lib/traces/schema2.js +20 -0
- package/dist/lib/traces/sync.d.ts +93 -0
- package/dist/lib/traces/sync.js +75 -22
- package/dist/lib/traces/worker-template.js +5 -0
- package/dist/lib/uninstall.js +10 -1
- package/dist/lib/workflows.d.ts +11 -0
- package/dist/lib/workflows.js +67 -8
- package/package.json +1 -1
|
@@ -101,6 +101,18 @@ export { captureProcessStartTime };
|
|
|
101
101
|
* model_reasoning_effort override). Mode (plan/edit/full) is a separate knob.
|
|
102
102
|
*/
|
|
103
103
|
export type EffortLevel = 'low' | 'medium' | 'high' | 'xhigh' | 'max' | 'auto';
|
|
104
|
+
/**
|
|
105
|
+
* Append {@link TEAMMATE_PR_POLICY} to a teammate prompt for every WRITE-capable
|
|
106
|
+
* mode (all but plan, which is read-only and opens no PR). Exported so the CLOUD
|
|
107
|
+
* dispatch path (`cloudDispatchOptions` in `commands/teams.ts`) applies the SAME
|
|
108
|
+
* boundary this file's `buildRunArgv` applies to LOCAL and REMOTE teammates. A
|
|
109
|
+
* cloud teammate is the case that needs it MOST: it runs in the provider's
|
|
110
|
+
* sandbox, not the shared local version home, so it never inherits the
|
|
111
|
+
* `merge-guard.sh` PreToolUse hook — the prompt policy is then its ONLY
|
|
112
|
+
* self-merge layer. Routing every dispatch surface through one helper keeps that
|
|
113
|
+
* parity from drifting (PHNX-3236).
|
|
114
|
+
*/
|
|
115
|
+
export declare function withTeammatePrPolicy(prompt: string, mode: string): string;
|
|
104
116
|
export declare const VALID_MODES: readonly ["plan", "edit", "auto", "skip", "full"];
|
|
105
117
|
type Mode = 'plan' | 'edit' | 'auto' | 'skip';
|
|
106
118
|
/** Resolve a mode string to a validated Mode, falling back to the given default. */
|
package/dist/lib/teams/agents.js
CHANGED
|
@@ -255,6 +255,49 @@ When you're done, provide a brief summary of:
|
|
|
255
255
|
const CLAUDE_PLAN_MODE_PREFIX = `You are running in HEADLESS PLAN MODE. This mode works like normal plan mode with one exception: you cannot write to ~/.claude/plans/ directory. Instead of writing a plan file, output your complete plan/response as your final message.
|
|
256
256
|
|
|
257
257
|
`;
|
|
258
|
+
// PHNX-3236: the teammate self-merge boundary, injected as a DISPATCH DEFAULT.
|
|
259
|
+
// A write-capable teammate has `gh pr merge` and authenticates as the repo owner,
|
|
260
|
+
// so it can merge its OWN PR past the required non-author-review gate — which is
|
|
261
|
+
// exactly what happened in the RUSH-2988 wave-1 dispatch (PR #1817, #1820). The
|
|
262
|
+
// root cause was that the boundary lived in per-brief wording: one teammate in the
|
|
263
|
+
// batch was told "open the PR, don't merge" and held off; the others weren't and
|
|
264
|
+
// self-merged. Making it a default the runner appends to every non-plan teammate
|
|
265
|
+
// gives one HARNESS-INDEPENDENT layer instead of relying on each dispatch prompt
|
|
266
|
+
// remembering to say it. The HARD enforcement is merge-guard.sh — a PreToolUse hook
|
|
267
|
+
// the teammate inherits from the shared version home, whose self-authored-verdict
|
|
268
|
+
// exclusion was closed in the same ticket (.agents-system #395) so a verdict a
|
|
269
|
+
// teammate posts on its own PR no longer clears the gate. The two layers do NOT
|
|
270
|
+
// overlap everywhere: hook-capable local/remote teammates get both, but cloud
|
|
271
|
+
// teammates (provider sandbox, no inherited hook) and hook-incapable harnesses
|
|
272
|
+
// (Warp/oz — no hook surface, no allowlist) get ONLY this prompt, so for them it is
|
|
273
|
+
// a soft control. That residual is documented in cli/AGENTS.md §6; server-side
|
|
274
|
+
// branch protection is the client-independent way to close it. This is the
|
|
275
|
+
// harness-independent layer plus the operator hand-off contract, not a replacement
|
|
276
|
+
// for the hard block where the hard block can run.
|
|
277
|
+
const TEAMMATE_PR_POLICY = `
|
|
278
|
+
|
|
279
|
+
Teammate PR policy (agents teams): when your work opens a pull request, open it and
|
|
280
|
+
hand it off — do NOT merge your OWN PR unless a NON-AUTHOR review verdict has been
|
|
281
|
+
posted on that same PR. You authenticate as the repo owner and share that one
|
|
282
|
+
GitHub identity with every other teammate, so an APPROVE you post on your own PR
|
|
283
|
+
does not count as a non-author review. \`gh pr merge\` on your own PR is blocked by
|
|
284
|
+
merge-guard until a genuine non-author verdict exists on it; never pass --admin or
|
|
285
|
+
otherwise route around that guard. Report the PR as open and let the orchestrator
|
|
286
|
+
or a separate reviewer take it to merge.`;
|
|
287
|
+
/**
|
|
288
|
+
* Append {@link TEAMMATE_PR_POLICY} to a teammate prompt for every WRITE-capable
|
|
289
|
+
* mode (all but plan, which is read-only and opens no PR). Exported so the CLOUD
|
|
290
|
+
* dispatch path (`cloudDispatchOptions` in `commands/teams.ts`) applies the SAME
|
|
291
|
+
* boundary this file's `buildRunArgv` applies to LOCAL and REMOTE teammates. A
|
|
292
|
+
* cloud teammate is the case that needs it MOST: it runs in the provider's
|
|
293
|
+
* sandbox, not the shared local version home, so it never inherits the
|
|
294
|
+
* `merge-guard.sh` PreToolUse hook — the prompt policy is then its ONLY
|
|
295
|
+
* self-merge layer. Routing every dispatch surface through one helper keeps that
|
|
296
|
+
* parity from drifting (PHNX-3236).
|
|
297
|
+
*/
|
|
298
|
+
export function withTeammatePrPolicy(prompt, mode) {
|
|
299
|
+
return mode === 'plan' ? prompt : prompt + TEAMMATE_PR_POLICY;
|
|
300
|
+
}
|
|
258
301
|
// Canonical modes plus the historical `full` alias (rewritten to `skip` by
|
|
259
302
|
// normalizeModeValue). Keep `full` listed so user-typed CLI flags and stored
|
|
260
303
|
// metadata that pre-date the rename continue to parse.
|
|
@@ -2530,6 +2573,14 @@ export class AgentManager {
|
|
|
2530
2573
|
fullPrompt = CLAUDE_PLAN_MODE_PREFIX + fullPrompt;
|
|
2531
2574
|
}
|
|
2532
2575
|
}
|
|
2576
|
+
// PHNX-3236: append the self-merge boundary to every WRITE-capable teammate,
|
|
2577
|
+
// fresh or resumed. A plan-mode teammate produces no PR (read-only), so it is
|
|
2578
|
+
// skipped to keep its prompt clean; every other mode can open — and could
|
|
2579
|
+
// self-merge — a PR, so the policy rides along regardless of harness. The
|
|
2580
|
+
// hard block is still merge-guard.sh (inherited hook); see TEAMMATE_PR_POLICY.
|
|
2581
|
+
// The cloud dispatch path applies the SAME helper (withTeammatePrPolicy) so
|
|
2582
|
+
// local, remote, and cloud teammates never diverge on this boundary.
|
|
2583
|
+
fullPrompt = withTeammatePrPolicy(fullPrompt, mode);
|
|
2533
2584
|
// Profile target takes precedence — `agents run <profile>` resolves the
|
|
2534
2585
|
// host harness, version pin, and env injection in one place. Plain
|
|
2535
2586
|
// version pins only apply when no profile is selected.
|
package/dist/lib/teams/api.d.ts
CHANGED
|
@@ -62,6 +62,8 @@ export interface AgentStatusDetail {
|
|
|
62
62
|
task_type?: TaskType | null;
|
|
63
63
|
/** Device name the teammate runs on for a distributed (--on) teammate; null for local. */
|
|
64
64
|
host?: string | null;
|
|
65
|
+
/** Absolute path to the teammate's worktree, when known. */
|
|
66
|
+
workspace_dir?: string | null;
|
|
65
67
|
/** Sanitized evidence observed at the lifecycle boundary that failed. */
|
|
66
68
|
failure?: TeammateFailure | null;
|
|
67
69
|
}
|
|
@@ -73,6 +75,7 @@ export interface TaskStatusResult {
|
|
|
73
75
|
pending: number;
|
|
74
76
|
running: number;
|
|
75
77
|
completed: number;
|
|
78
|
+
stranded: number;
|
|
76
79
|
failed: number;
|
|
77
80
|
stopped: number;
|
|
78
81
|
};
|
|
@@ -126,6 +129,8 @@ export interface AgentStatusSummary {
|
|
|
126
129
|
/** Device name for a distributed (--on) teammate; null for local. */
|
|
127
130
|
host: string | null;
|
|
128
131
|
failure: TeammateFailure | null;
|
|
132
|
+
/** Absolute path to the teammate's worktree, when known. */
|
|
133
|
+
workspace_dir?: string | null;
|
|
129
134
|
/** ISO timestamp — feed back via --since for delta polling. */
|
|
130
135
|
cursor: string;
|
|
131
136
|
}
|
|
@@ -137,6 +142,7 @@ export interface TaskStatusSummaryResult {
|
|
|
137
142
|
pending: number;
|
|
138
143
|
running: number;
|
|
139
144
|
completed: number;
|
|
145
|
+
stranded: number;
|
|
140
146
|
failed: number;
|
|
141
147
|
stopped: number;
|
|
142
148
|
};
|
|
@@ -164,6 +170,8 @@ export interface TaskInfo {
|
|
|
164
170
|
pending: number;
|
|
165
171
|
running: number;
|
|
166
172
|
completed: number;
|
|
173
|
+
/** Completed teammates with uncommitted work and no PR (PHNX-2951). */
|
|
174
|
+
stranded: number;
|
|
167
175
|
failed: number;
|
|
168
176
|
stopped: number;
|
|
169
177
|
workspace_dir: string | null;
|
package/dist/lib/teams/api.js
CHANGED
|
@@ -3,6 +3,7 @@ import { getDelta } from './summarizer.js';
|
|
|
3
3
|
import { debug } from './debug.js';
|
|
4
4
|
import { buildClaudeLabelMap } from '../session/discover.js';
|
|
5
5
|
import { resolveTeammateDelivery } from './delivery.js';
|
|
6
|
+
import { hasUncommittedChanges } from './worktree.js';
|
|
6
7
|
/**
|
|
7
8
|
* Truncate a bash command for status output.
|
|
8
9
|
* Handles heredocs specially - shows the redirect target instead of contents.
|
|
@@ -126,6 +127,7 @@ export function toAgentStatusSummary(detail) {
|
|
|
126
127
|
.map((m) => trimMessage(m)),
|
|
127
128
|
host: detail.host ?? null,
|
|
128
129
|
failure: detail.failure ?? null,
|
|
130
|
+
workspace_dir: detail.workspace_dir ?? null,
|
|
129
131
|
cursor: detail.cursor,
|
|
130
132
|
};
|
|
131
133
|
}
|
|
@@ -207,7 +209,7 @@ parentSessionId) {
|
|
|
207
209
|
? allAgents
|
|
208
210
|
: allAgents.filter((a) => a.status === effectiveFilter);
|
|
209
211
|
const agentStatuses = [];
|
|
210
|
-
const counts = { pending: 0, running: 0, completed: 0, failed: 0, stopped: 0 };
|
|
212
|
+
const counts = { pending: 0, running: 0, completed: 0, stranded: 0, failed: 0, stopped: 0 };
|
|
211
213
|
// Count ALL agents for summary (not just filtered)
|
|
212
214
|
for (const agent of allAgents) {
|
|
213
215
|
if (agent.status === AgentStatus.PENDING)
|
|
@@ -221,6 +223,30 @@ parentSessionId) {
|
|
|
221
223
|
else if (agent.status === AgentStatus.STOPPED)
|
|
222
224
|
counts.stopped++;
|
|
223
225
|
}
|
|
226
|
+
// Stranded count is computed over ALL agents, not the filter-narrowed view,
|
|
227
|
+
// so a `--filter running` status still reports teammates that completed dirty.
|
|
228
|
+
// Cache the probe result so we can reuse it when building details for the
|
|
229
|
+
// filtered subset without probing the worktree twice.
|
|
230
|
+
const uncommittedCache = new Map();
|
|
231
|
+
for (const agent of allAgents) {
|
|
232
|
+
if (agent.status !== AgentStatus.COMPLETED)
|
|
233
|
+
continue;
|
|
234
|
+
const shouldProbeWorktree = !agent.prUrl?.trim() &&
|
|
235
|
+
!agent.hostName &&
|
|
236
|
+
Boolean(agent.workspaceDir);
|
|
237
|
+
const hasUncommitted = shouldProbeWorktree
|
|
238
|
+
? await hasUncommittedChanges(agent.workspaceDir)
|
|
239
|
+
: false;
|
|
240
|
+
uncommittedCache.set(agent.agentId, hasUncommitted);
|
|
241
|
+
const delivery = resolveTeammateDelivery({
|
|
242
|
+
status: agent.status,
|
|
243
|
+
prUrl: agent.prUrl,
|
|
244
|
+
hasUncommittedChanges: hasUncommitted,
|
|
245
|
+
});
|
|
246
|
+
if (delivery === 'stranded') {
|
|
247
|
+
counts.stranded++;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
224
250
|
// Build details only for filtered agents
|
|
225
251
|
let maxTimestamp = since || new Date(0).toISOString(); // Track max timestamp for cursor
|
|
226
252
|
const claudeLabels = allAgents.some((agent) => agent.agentType === 'claude')
|
|
@@ -237,6 +263,12 @@ parentSessionId) {
|
|
|
237
263
|
if (agentTimestamp > maxTimestamp) {
|
|
238
264
|
maxTimestamp = agentTimestamp;
|
|
239
265
|
}
|
|
266
|
+
const hasUncommitted = uncommittedCache.get(agent.agentId) ?? false;
|
|
267
|
+
const delivery = resolveTeammateDelivery({
|
|
268
|
+
status: agent.status,
|
|
269
|
+
prUrl: agent.prUrl,
|
|
270
|
+
hasUncommittedChanges: hasUncommitted,
|
|
271
|
+
});
|
|
240
272
|
const detail = {
|
|
241
273
|
agent_id: agent.agentId,
|
|
242
274
|
agent_type: agent.agentType,
|
|
@@ -254,15 +286,13 @@ parentSessionId) {
|
|
|
254
286
|
after: agent.after,
|
|
255
287
|
task_type: agent.taskType,
|
|
256
288
|
host: agent.hostName,
|
|
289
|
+
workspace_dir: agent.workspaceDir,
|
|
257
290
|
failure: agent.failure,
|
|
258
291
|
mode: agent.mode,
|
|
259
292
|
cloud_session_id: agent.cloudSessionId,
|
|
260
293
|
cloud_provider: agent.cloudProvider,
|
|
261
294
|
pr_url: agent.prUrl,
|
|
262
|
-
delivery
|
|
263
|
-
status: agent.status,
|
|
264
|
-
prUrl: agent.prUrl,
|
|
265
|
-
}),
|
|
295
|
+
delivery,
|
|
266
296
|
files_created: delta.new_files_created,
|
|
267
297
|
files_modified: delta.new_files_modified,
|
|
268
298
|
files_read: delta.new_files_read,
|
|
@@ -297,7 +327,7 @@ export async function handleTasks(manager, limit = 10) {
|
|
|
297
327
|
}
|
|
298
328
|
const tasks = [];
|
|
299
329
|
for (const [taskName, agents] of taskMap) {
|
|
300
|
-
let pending = 0, running = 0, completed = 0, failed = 0, stopped = 0;
|
|
330
|
+
let pending = 0, running = 0, completed = 0, stranded = 0, failed = 0, stopped = 0;
|
|
301
331
|
let earliestStart = null;
|
|
302
332
|
let latestActivity = null;
|
|
303
333
|
let workspaceDir = null;
|
|
@@ -313,6 +343,19 @@ export async function handleTasks(manager, limit = 10) {
|
|
|
313
343
|
failed++;
|
|
314
344
|
else if (agent.status === AgentStatus.STOPPED)
|
|
315
345
|
stopped++;
|
|
346
|
+
// Stranded = completed, no PR, local worktree still dirty. Probes the real
|
|
347
|
+
// worktree so `teams tasks` and `teams list --status` don't classify lost
|
|
348
|
+
// work as done (PHNX-2951).
|
|
349
|
+
if (agent.status === AgentStatus.COMPLETED && !agent.prUrl?.trim() && !agent.hostName && agent.workspaceDir) {
|
|
350
|
+
const delivery = resolveTeammateDelivery({
|
|
351
|
+
status: agent.status,
|
|
352
|
+
prUrl: agent.prUrl,
|
|
353
|
+
hasUncommittedChanges: await hasUncommittedChanges(agent.workspaceDir),
|
|
354
|
+
});
|
|
355
|
+
if (delivery === 'stranded') {
|
|
356
|
+
stranded++;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
316
359
|
// Track earliest start (created_at)
|
|
317
360
|
if (!earliestStart || agent.startedAt < earliestStart) {
|
|
318
361
|
earliestStart = agent.startedAt;
|
|
@@ -336,6 +379,7 @@ export async function handleTasks(manager, limit = 10) {
|
|
|
336
379
|
pending,
|
|
337
380
|
running,
|
|
338
381
|
completed,
|
|
382
|
+
stranded,
|
|
339
383
|
failed,
|
|
340
384
|
stopped,
|
|
341
385
|
workspace_dir: workspaceDir,
|
|
@@ -12,13 +12,17 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import { AgentStatus } from './agents.js';
|
|
14
14
|
/** Postcondition-facing delivery of a teammate's work. */
|
|
15
|
-
export type TeammateDelivery = 'pending' | 'in_progress' | 'pr_open' | 'pr_merged' | 'no_pr' | 'failed' | 'stopped';
|
|
15
|
+
export type TeammateDelivery = 'pending' | 'in_progress' | 'pr_open' | 'pr_merged' | 'no_pr' | 'stranded' | 'failed' | 'stopped';
|
|
16
16
|
/**
|
|
17
17
|
* Derive delivery from process status and PR state.
|
|
18
18
|
*
|
|
19
19
|
* When the process completed with a `prUrl` and merge is unknown or false,
|
|
20
20
|
* delivery is `pr_open` — pessimistic: assume open until proven merged so an
|
|
21
21
|
* orchestrator never mistakes "agent stopped" for "work on main".
|
|
22
|
+
*
|
|
23
|
+
* When the process completed with no PR and uncommitted changes remain in the
|
|
24
|
+
* worktree, delivery is `stranded` — the work exists only locally and will be
|
|
25
|
+
* lost if the worktree is cleaned up (PHNX-2951).
|
|
22
26
|
*/
|
|
23
27
|
export declare function resolveTeammateDelivery(opts: {
|
|
24
28
|
status: AgentStatus | string;
|
|
@@ -28,14 +32,20 @@ export declare function resolveTeammateDelivery(opts: {
|
|
|
28
32
|
* `null`/`undefined` = unknown; with a `prUrl` that means `pr_open`.
|
|
29
33
|
*/
|
|
30
34
|
prMerged?: boolean | null;
|
|
35
|
+
/**
|
|
36
|
+
* Whether the teammate's worktree has uncommitted changes. Only consulted
|
|
37
|
+
* when the process completed without a PR URL.
|
|
38
|
+
*/
|
|
39
|
+
hasUncommittedChanges?: boolean | null;
|
|
31
40
|
}): TeammateDelivery;
|
|
32
41
|
/**
|
|
33
42
|
* Human label for `teams status` rows. Replaces bare COMPLETED with PR OPEN
|
|
34
|
-
* when delivery is still pending merge
|
|
43
|
+
* when delivery is still pending merge, and with STRANDED when uncommitted
|
|
44
|
+
* work is stranded in the worktree.
|
|
35
45
|
*/
|
|
36
46
|
export declare function deliveryDisplayLabel(delivery: TeammateDelivery, processStatus: AgentStatus | string): string;
|
|
37
47
|
/**
|
|
38
|
-
* Color key for statusColor-style switches. `pr_open`
|
|
39
|
-
*
|
|
48
|
+
* Color key for statusColor-style switches. `pr_open` and `stranded` get their
|
|
49
|
+
* own keys so the rows are visually distinct from green COMPLETED.
|
|
40
50
|
*/
|
|
41
51
|
export declare function deliveryColorKey(delivery: TeammateDelivery, processStatus: string): string;
|
|
@@ -17,6 +17,10 @@ import { AgentStatus } from './agents.js';
|
|
|
17
17
|
* When the process completed with a `prUrl` and merge is unknown or false,
|
|
18
18
|
* delivery is `pr_open` — pessimistic: assume open until proven merged so an
|
|
19
19
|
* orchestrator never mistakes "agent stopped" for "work on main".
|
|
20
|
+
*
|
|
21
|
+
* When the process completed with no PR and uncommitted changes remain in the
|
|
22
|
+
* worktree, delivery is `stranded` — the work exists only locally and will be
|
|
23
|
+
* lost if the worktree is cleaned up (PHNX-2951).
|
|
20
24
|
*/
|
|
21
25
|
export function resolveTeammateDelivery(opts) {
|
|
22
26
|
const status = String(opts.status);
|
|
@@ -30,8 +34,9 @@ export function resolveTeammateDelivery(opts) {
|
|
|
30
34
|
return 'stopped';
|
|
31
35
|
if (status === AgentStatus.COMPLETED || status === 'completed') {
|
|
32
36
|
const prUrl = opts.prUrl?.trim();
|
|
33
|
-
if (!prUrl)
|
|
34
|
-
return 'no_pr';
|
|
37
|
+
if (!prUrl) {
|
|
38
|
+
return opts.hasUncommittedChanges ? 'stranded' : 'no_pr';
|
|
39
|
+
}
|
|
35
40
|
if (opts.prMerged === true)
|
|
36
41
|
return 'pr_merged';
|
|
37
42
|
return 'pr_open';
|
|
@@ -40,21 +45,26 @@ export function resolveTeammateDelivery(opts) {
|
|
|
40
45
|
}
|
|
41
46
|
/**
|
|
42
47
|
* Human label for `teams status` rows. Replaces bare COMPLETED with PR OPEN
|
|
43
|
-
* when delivery is still pending merge
|
|
48
|
+
* when delivery is still pending merge, and with STRANDED when uncommitted
|
|
49
|
+
* work is stranded in the worktree.
|
|
44
50
|
*/
|
|
45
51
|
export function deliveryDisplayLabel(delivery, processStatus) {
|
|
46
52
|
if (delivery === 'pr_open')
|
|
47
53
|
return 'PR OPEN';
|
|
48
54
|
if (delivery === 'pr_merged')
|
|
49
55
|
return 'COMPLETED';
|
|
56
|
+
if (delivery === 'stranded')
|
|
57
|
+
return 'STRANDED';
|
|
50
58
|
return String(processStatus).toUpperCase();
|
|
51
59
|
}
|
|
52
60
|
/**
|
|
53
|
-
* Color key for statusColor-style switches. `pr_open`
|
|
54
|
-
*
|
|
61
|
+
* Color key for statusColor-style switches. `pr_open` and `stranded` get their
|
|
62
|
+
* own keys so the rows are visually distinct from green COMPLETED.
|
|
55
63
|
*/
|
|
56
64
|
export function deliveryColorKey(delivery, processStatus) {
|
|
57
65
|
if (delivery === 'pr_open')
|
|
58
66
|
return 'pr_open';
|
|
67
|
+
if (delivery === 'stranded')
|
|
68
|
+
return 'stranded';
|
|
59
69
|
return String(processStatus);
|
|
60
70
|
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* schema2-build — the PRODUCER's per-tool mappers + `buildSessionDetailV2`
|
|
3
|
+
* (PHNX-3442 step 2, increments 2-4).
|
|
4
|
+
*
|
|
5
|
+
* Populates the `SessionStepV2` discriminated union (schema2.ts) from the parsed
|
|
6
|
+
* session events, reusing the SAME infrastructure the schema-1 path already uses:
|
|
7
|
+
*
|
|
8
|
+
* - the callId pairing loop (`pairSteps` in session/trajectory.ts) — so a step's
|
|
9
|
+
* (use event, result event) triple is recovered without a duplicate loop;
|
|
10
|
+
* - bash unwrap/tokenize/classify (`session/bash-command.ts`) + the effective
|
|
11
|
+
* program resolver (`effectiveProgram`);
|
|
12
|
+
* - the meta / whereItWentWrong / surfacedToolFailures / active-time helpers
|
|
13
|
+
* factored out of sync.ts (`buildDetailMeta`, `buildWhereItWentWrong`, …).
|
|
14
|
+
*
|
|
15
|
+
* The command/patch/output PARSING lives here; the worker stores the shard
|
|
16
|
+
* opaquely and the console reads the union directly and never reparses (spec §5).
|
|
17
|
+
*
|
|
18
|
+
* category / risk / categoryMetrics are DELIBERATELY omitted from the schema-2
|
|
19
|
+
* detail: the shipped consumer (`decodeSessionDetail` → coerceCategory/Risk/Metrics)
|
|
20
|
+
* backfills them to the same neutral defaults it uses for schema-1, so computing
|
|
21
|
+
* them here would be inventing session-level signal this step does not own.
|
|
22
|
+
*/
|
|
23
|
+
import { type BashCategory as ClassifierCategory } from '../session/bash-command.js';
|
|
24
|
+
import { type SessionTrajectory } from '../session/trajectory.js';
|
|
25
|
+
import type { SessionEvent } from '../session/types.js';
|
|
26
|
+
import type { BashAction, BashCategory, SessionStepV2 } from './schema2.js';
|
|
27
|
+
import { activeMsFromTrajectory, type SessionDetail } from './sync.js';
|
|
28
|
+
/**
|
|
29
|
+
* SessionDetailV2 — the schema-2 shard this producer emits.
|
|
30
|
+
*
|
|
31
|
+
* `category` / `risk` / `categoryMetrics` are DELIBERATELY OMITTED. The consumer's
|
|
32
|
+
* `SessionDetailV2` declares them, but its `decodeSchema2` backfills neutral
|
|
33
|
+
* defaults via `coerceCategory`/`coerceRisk`/`coerceMetrics` (never throws) — the
|
|
34
|
+
* same defaulting it applies to schema-1 shards today. This producer does not yet
|
|
35
|
+
* author those fields (their provenance is the prix/api PHNX-3351 hosted backend,
|
|
36
|
+
* not agents-cli), so emitting them here would fabricate classification. Omission
|
|
37
|
+
* is the honest choice and is asserted as a tested contract in
|
|
38
|
+
* `schema2-fixture.test.ts`. Wire real category/risk here once its source is settled.
|
|
39
|
+
*/
|
|
40
|
+
export interface SessionDetailV2 {
|
|
41
|
+
schema: 2;
|
|
42
|
+
id: string;
|
|
43
|
+
meta: SessionDetail['meta'];
|
|
44
|
+
steps: SessionStepV2[];
|
|
45
|
+
gaps: SessionTrajectory['gaps'];
|
|
46
|
+
truncatedSteps: number;
|
|
47
|
+
whereItWentWrong: string | null;
|
|
48
|
+
surfacedToolFailures: Array<{
|
|
49
|
+
tool?: string;
|
|
50
|
+
label: string;
|
|
51
|
+
detail?: string;
|
|
52
|
+
}>;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* `unwrapCommand` (bash-command.ts) strips VAR=/sudo/cd&&/npx/loops/subshells but
|
|
56
|
+
* NOT an interpreter wrapper like `/bin/zsh -lc "…"`, `bash -lc '…'`, or `sh -c …`
|
|
57
|
+
* — the exact shape the managed runner wraps every command in. Peel that first,
|
|
58
|
+
* then hand the inner payload to the existing unwrapper so all the wrappers it DOES
|
|
59
|
+
* know still apply. One extra rule, at the source, not a fork of unwrapCommand.
|
|
60
|
+
*/
|
|
61
|
+
export declare function unwrapShellExec(command: string): string;
|
|
62
|
+
/**
|
|
63
|
+
* Map a classifier `BashCategory` (the rich vcs|build-test|install|… taxonomy) to
|
|
64
|
+
* the coarse schema-2 `BashCategory` (build|test|git|network|other). `build-test`
|
|
65
|
+
* needs the argv/subcommand to decide build vs test — `bun test` is test, `bun
|
|
66
|
+
* build` is build — so this takes the tokenized argv too.
|
|
67
|
+
*/
|
|
68
|
+
export declare function mapBashCategory(cat: ClassifierCategory, argv: string[]): BashCategory;
|
|
69
|
+
/** Build the per-segment BashAction list for a bash command. */
|
|
70
|
+
export declare function buildBashActions(unwrapped: string): BashAction[];
|
|
71
|
+
export interface BuildDetailV2Options {
|
|
72
|
+
redact?: boolean;
|
|
73
|
+
knownSecrets?: readonly string[];
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Build the schema-2 per-session detail from a pre-built trajectory and its raw
|
|
77
|
+
* events. The trajectory supplies meta/gaps/whereItWentWrong/surfacedToolFailures
|
|
78
|
+
* (via the shared sync.ts helpers) and the truncation count; the raw events supply
|
|
79
|
+
* the per-tool detail the schema-1 flat step could not carry.
|
|
80
|
+
*
|
|
81
|
+
* Re-pairs the events with `pairSteps` (the SAME loop buildTrajectory ran) to
|
|
82
|
+
* recover each step's (use event, result event) triple, then dispatches per tool.
|
|
83
|
+
*/
|
|
84
|
+
export declare function buildSessionDetailV2(traj: SessionTrajectory, events: SessionEvent[], options?: BuildDetailV2Options): SessionDetailV2;
|
|
85
|
+
export { activeMsFromTrajectory };
|