@phnx-labs/agents-cli 1.22.91 → 1.22.93

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.
Files changed (39) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +5 -0
  3. package/dist/commands/exec.js +6 -1
  4. package/dist/lib/claude-account-token.d.ts +13 -0
  5. package/dist/lib/claude-account-token.js +29 -0
  6. package/dist/lib/daemon/attention-notify-service.d.ts +5 -2
  7. package/dist/lib/daemon/attention-notify-service.js +9 -2
  8. package/dist/lib/exec-account-home.d.ts +19 -0
  9. package/dist/lib/exec-account-home.js +35 -1
  10. package/dist/lib/exec.js +11 -0
  11. package/dist/lib/feed/answer.js +9 -0
  12. package/dist/lib/feed/attention.d.ts +18 -2
  13. package/dist/lib/feed/attention.js +0 -0
  14. package/dist/lib/feed/feed.d.ts +6 -2
  15. package/dist/lib/feed/feed.js +10 -4
  16. package/dist/lib/harness/adapter.d.ts +23 -0
  17. package/dist/lib/harness/adapter.js +29 -0
  18. package/dist/lib/harness/adapters/claude.js +5 -3
  19. package/dist/lib/harness/adapters/copilot.js +4 -2
  20. package/dist/lib/harness/adapters/grok.js +4 -2
  21. package/dist/lib/harness/adapters/kimi.js +4 -2
  22. package/dist/lib/harness/adapters/muse.js +4 -3
  23. package/dist/lib/harness/adapters/opencode.js +4 -2
  24. package/dist/lib/helper-download.js +1 -1
  25. package/dist/lib/helper-versions.js +1 -1
  26. package/dist/lib/installations/shims.d.ts +2 -2
  27. package/dist/lib/installations/shims.js +16 -8
  28. package/dist/lib/menubar/download-menubar.js +4 -1
  29. package/dist/lib/menubar/install-menubar.d.ts +10 -9
  30. package/dist/lib/menubar/install-menubar.js +17 -15
  31. package/dist/lib/secrets-policy.d.ts +5 -2
  32. package/dist/lib/secrets-policy.js +18 -4
  33. package/dist/lib/session/active.d.ts +18 -11
  34. package/dist/lib/session/active.js +52 -44
  35. package/dist/lib/session/session-cache.d.ts +1 -1
  36. package/dist/lib/session/session-cache.js +1 -0
  37. package/dist/lib/session/state.d.ts +26 -0
  38. package/dist/lib/session/state.js +43 -27
  39. package/package.json +1 -1
@@ -18,6 +18,16 @@ import type { SessionAttachment, SessionEvent, TodoItem, TodoProgress } from './
18
18
  import { type ProducedArtifact } from './highlights.js';
19
19
  export type { TodoItem, TodoProgress };
20
20
  export type SessionActivity = 'working' | 'waiting_input' | 'idle';
21
+ /**
22
+ * Why a session is `waiting_input`. The state engine produces `question` and
23
+ * `plan_review` from transcript structure. `permission` is NOT inferred from a
24
+ * transcript any more (PHNX-3999): a tool call with no result looks the same
25
+ * whether the tool is still running or a permission dialog is up, and only the
26
+ * harness's own `permission_prompt` hook event (the feed block) can tell them
27
+ * apart. The value stays in the union because a row from a peer running an older
28
+ * CLI can still carry it; the attention reconciler treats such a row as an
29
+ * unverified claim, never as an approvable request.
30
+ */
21
31
  export type AwaitingReason = 'question' | 'plan_review' | 'permission';
22
32
  /** One discrete choice the agent offered the user. */
23
33
  export interface QuestionOption {
@@ -103,6 +113,15 @@ export interface SessionState {
103
113
  /** Last few assistant turns (most-recent last), one line each — panel context. */
104
114
  tail?: string[];
105
115
  lastActivityMs?: number;
116
+ /**
117
+ * Timestamp (ms) of the last meaningful transcript event — a message, tool
118
+ * call, tool result, thinking block, or error — when the harness stamped one
119
+ * (PHNX-3999). This is the transcript's own cursor: unlike the file mtime it does
120
+ * not move when the harness appends a hook-firing record or other bookkeeping
121
+ * line, so it is the evidence that the agent did (or did not) work past a given
122
+ * moment — what the attention reconciler compares a hook-raised prompt against.
123
+ */
124
+ lastEventMs?: number;
106
125
  pr?: DetectedPr;
107
126
  worktree?: DetectedWorktree;
108
127
  ticket?: DetectedTicket;
@@ -122,6 +141,13 @@ export interface StateContext {
122
141
  pidAlive?: boolean;
123
142
  /** Override the running window (defaults to 2 min, matching active.ts). */
124
143
  activeWindowMs?: number;
144
+ /**
145
+ * The clock every elapsed-time signal is measured against (default `Date.now()`).
146
+ * Injected so a cached parse can be re-classified against the CURRENT time — a
147
+ * time-based verdict (the prose-question decay) must expire even when the
148
+ * transcript bytes have not changed (PHNX-3999).
149
+ */
150
+ nowMs?: number;
125
151
  }
126
152
  /**
127
153
  * Derive live plan progress from a checklist tool call's args. Accepts Claude's
@@ -399,10 +399,10 @@ export function structuredQuestionFromAsk(args) {
399
399
  return { text, reason: 'question', options: options.length ? options : undefined };
400
400
  }
401
401
  /**
402
- * Canonical approve/deny choices for an interactive prompt that carries no
403
- * agent-supplied option list: Claude's plan-review and permission dialogs. Approve
404
- * is reliably option 1; deny/keep-planning maps to ESC, which cancels the prompt in
405
- * every variant (2- or 3-option) — safer than guessing a digit that could differ.
402
+ * Canonical approve/send-back choices for Claude's plan-review dialog, which
403
+ * carries no agent-supplied option list. Approve is reliably option 1; keep-planning
404
+ * maps to ESC, which cancels the prompt in every variant (2- or 3-option) — safer
405
+ * than guessing a digit that could differ.
406
406
  */
407
407
  function planReviewQuestion() {
408
408
  return {
@@ -414,15 +414,22 @@ function planReviewQuestion() {
414
414
  ],
415
415
  };
416
416
  }
417
- function permissionQuestion(preview) {
418
- return {
419
- text: preview ? `Permission — ${preview}` : 'Waiting on a permission prompt',
420
- reason: 'permission',
421
- options: [
422
- { label: 'Approve', key: '1' },
423
- { label: 'Deny', key: 'esc' },
424
- ],
425
- };
417
+ /**
418
+ * The harness's own stamp on an event, as ms — or undefined when the event carries
419
+ * none. A stamp LATER than the file's last write cannot have come from the
420
+ * transcript (a parser that finds no stamp fills the field with its own parse
421
+ * time), so it is rejected rather than read as "just happened": the mtime is the
422
+ * physical upper bound on when any transcript line was written.
423
+ */
424
+ function eventStampMs(e, mtimeMs) {
425
+ if (!e?.timestamp)
426
+ return undefined;
427
+ const ms = Date.parse(e.timestamp);
428
+ if (!Number.isFinite(ms))
429
+ return undefined;
430
+ if (mtimeMs != null && ms > mtimeMs)
431
+ return undefined;
432
+ return ms;
426
433
  }
427
434
  /** Does an assistant message read as a question directed at the user? */
428
435
  function looksLikeQuestion(text) {
@@ -461,7 +468,8 @@ function lastOf(events, pred) {
461
468
  */
462
469
  export function inferActivity(events, ctx = {}) {
463
470
  const windowMs = ctx.activeWindowMs ?? ACTIVE_WINDOW_MS;
464
- const fresh = ctx.mtimeMs != null && Date.now() - ctx.mtimeMs < windowMs;
471
+ const nowMs = ctx.nowMs ?? Date.now();
472
+ const fresh = ctx.mtimeMs != null && nowMs - ctx.mtimeMs < windowMs;
465
473
  // A non-live process (pidAlive === false) can never be "working"; the strongest
466
474
  // it gets is "waiting on you" (a dangling question) or "idle".
467
475
  const canWork = ctx.pidAlive !== false && (ctx.pidAlive === true || fresh);
@@ -494,6 +502,7 @@ export function inferActivity(events, ctx = {}) {
494
502
  lastRole: lastMsg?.role,
495
503
  lastEventKind: last?.type,
496
504
  lastActivityMs: ctx.mtimeMs,
505
+ lastEventMs: eventStampMs(last, ctx.mtimeMs),
497
506
  preview: previewSource ? describeEvent(previewSource) : undefined,
498
507
  todos,
499
508
  tail: tail.length ? tail : undefined,
@@ -514,14 +523,17 @@ export function inferActivity(events, ctx = {}) {
514
523
  const question = structuredQuestionFromAsk(lastPlanOrAsk.args) ?? { text: 'Asked you a question', reason: 'question' };
515
524
  return { ...base, activity: 'waiting_input', awaitingReason: 'question', preview: question.text, question };
516
525
  }
517
- // Pending tool call (tool_use with no following tool_result): mid-turn.
526
+ // Pending tool call (tool_use with no following tool_result): the call is in
527
+ // flight for as long as the process is alive. A command that has run for ten
528
+ // minutes and a permission dialog that has sat for ten minutes leave the SAME
529
+ // transcript — a tool_use with no result and a quiet file — so elapsed time is
530
+ // not evidence of a request, and this engine never labels one `permission`
531
+ // (PHNX-3999: the "is the user idle?" reminder and the two-minute mark were both
532
+ // being rendered with Approve/Deny). The harness's own `permission_prompt` hook
533
+ // event, carried by the feed block, is the only evidence a dialog is up; the
534
+ // attention reconciler confirms it against `lastEventMs`.
518
535
  if (last.type === 'tool_use') {
519
- if (canWork && fresh)
520
- return { ...base, activity: 'working' };
521
- // Alive but the file hasn't moved — likely blocked on a permission prompt.
522
- if (ctx.pidAlive)
523
- return { ...base, activity: 'waiting_input', awaitingReason: 'permission', question: permissionQuestion(base.preview) };
524
- return { ...base, activity: 'idle' };
536
+ return { ...base, activity: canWork ? 'working' : 'idle' };
525
537
  }
526
538
  // Thinking or a tool result just landed → agent is mid-turn if recently active.
527
539
  if (last.type === 'thinking' || last.type === 'tool_result' || last.type === 'error') {
@@ -537,12 +549,16 @@ export function inferActivity(events, ctx = {}) {
537
549
  // A prose question takes a free-text reply (no select-list), so no options/keys.
538
550
  // Unlike the structural plan/ask signals above, the prose heuristic DECAYS: a
539
551
  // question nobody answered within PROSE_QUESTION_FRESH_MS is a session that
540
- // ended, not one that needs you (RUSH-1522). A prose "?" is only "waiting on
541
- // you" while the file is DEMONSTRABLY fresh — with no mtime signal at all we
542
- // cannot assert freshness, so the heuristic must not fire (an unknown-age
543
- // prose question is a session that ended, closing RUSH-1522's null-mtime hole
544
- // where a null mtime kept the question forever).
545
- const questionFresh = ctx.mtimeMs != null && Date.now() - ctx.mtimeMs < PROSE_QUESTION_FRESH_MS;
552
+ // ended, not one that needs you (RUSH-1522). The question's age is the
553
+ // assistant message's own stamp when the harness wrote one — the exact moment
554
+ // it was asked — and the file's last write when it did not; the age is measured
555
+ // against `ctx.nowMs`, never a clock frozen at parse time, so the verdict
556
+ // expires on schedule even while the bytes sit still (PHNX-3999). With no age
557
+ // evidence at all the heuristic must not fire (an unknown-age prose question is
558
+ // a session that ended, closing RUSH-1522's null-mtime hole where a null mtime
559
+ // kept the question forever).
560
+ const askedAtMs = eventStampMs(last, ctx.mtimeMs) ?? ctx.mtimeMs;
561
+ const questionFresh = askedAtMs != null && nowMs - askedAtMs < PROSE_QUESTION_FRESH_MS;
546
562
  if (questionFresh && looksLikeQuestion(last.content ?? '')) {
547
563
  const text = oneLine(last.content ?? '');
548
564
  return { ...base, activity: 'waiting_input', awaitingReason: 'question', question: { text, reason: 'question' } };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phnx-labs/agents-cli",
3
- "version": "1.22.91",
3
+ "version": "1.22.93",
4
4
  "description": "One CLI for all your AI coding agents - versions, config, cloud dispatch, sessions, and teams (now with first-class Grok Build CLI support)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",