@wrongstack/tui 0.301.0 → 0.302.2

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.
@@ -91,9 +91,15 @@ export interface SessionsPanelSidebarProps {
91
91
  liveSessions?: readonly LiveSessionEntry[] | undefined;
92
92
  resumeSessions?: readonly ResumeSessionEntry[] | undefined;
93
93
  currentSessionId?: string | undefined;
94
+ /**
95
+ * Reference timestamp for relative-time labels (e.g. "2d"). Defaults to
96
+ * `Date.now()` at render time. Accepts an injected value so visual snapshots
97
+ * stay deterministic across calendar-day boundaries.
98
+ */
99
+ now?: number | undefined;
94
100
  width: number;
95
101
  }
96
- export declare function SessionsPanelSidebar({ liveSessions, resumeSessions, currentSessionId, width, }: SessionsPanelSidebarProps): React.ReactElement;
102
+ export declare function SessionsPanelSidebar({ liveSessions, resumeSessions, currentSessionId, now, width, }: SessionsPanelSidebarProps): React.ReactElement;
97
103
  export interface CoordinatorPanelSidebarProps {
98
104
  running: boolean;
99
105
  /** Active phase count. */
@@ -31,6 +31,7 @@ export interface AutoProceedCandidate {
31
31
  source: 'todo' | 'suggestion' | 'auto-suggestion';
32
32
  prompt: string;
33
33
  label: string;
34
+ todoId?: string | undefined;
34
35
  }
35
36
  /** Resolve one grounded automatic turn without mutating either suggestion store. */
36
37
  export declare function selectAutoProceedCandidate({ todos, suggestions, autoSuggestions, yolo, autonomyNextPrompt, }: AutoProceedCandidateInput): AutoProceedCandidate | null;
package/dist/index.js CHANGED
@@ -17191,6 +17191,12 @@ function evictParseCache(incomingChars) {
17191
17191
  if (entry) _parseCacheChars -= entry.chars;
17192
17192
  }
17193
17193
  }
17194
+ function isUrlChar(code) {
17195
+ if (code >= 97 && code <= 122 || code >= 65 && code <= 90 || code >= 48 && code <= 57) {
17196
+ return true;
17197
+ }
17198
+ return code === 45 || code === 46 || code === 95 || code === 126 || code === 58 || code === 47 || code === 63 || code === 35 || code === 64 || code === 33 || code === 36 || code === 38 || code === 43 || code === 61 || code === 37 || code === 91 || code === 93 || code === 40 || code === 41 || code === 42 || code === 39 || code === 44 || code === 59;
17199
+ }
17194
17200
  function parseInline(text) {
17195
17201
  const cached = _parseCache.get(text);
17196
17202
  if (cached) {
@@ -17261,14 +17267,8 @@ function parseInline(text) {
17261
17267
  }
17262
17268
  if (text.startsWith("http://", i) || text.startsWith("https://", i)) {
17263
17269
  let urlEnd = i + 8;
17264
- while (urlEnd < text.length) {
17265
- const c = text[urlEnd];
17266
- const urlChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789._~:/?#@!" + String.fromCharCode(36) + String.fromCharCode(38) + "+,;=-%[]()'*";
17267
- if (c !== void 0 && urlChars.includes(c)) {
17268
- urlEnd++;
17269
- } else {
17270
- break;
17271
- }
17270
+ while (urlEnd < text.length && isUrlChar(text.charCodeAt(urlEnd))) {
17271
+ urlEnd++;
17272
17272
  }
17273
17273
  const trailing = text.slice(i, urlEnd);
17274
17274
  const stripped = trailing.replace(/[.,;!?)]+$/, "");
@@ -21558,9 +21558,9 @@ function liveSessionColor2(status) {
21558
21558
  if (status === "closing") return theme.warn;
21559
21559
  return theme.textMuted;
21560
21560
  }
21561
- function fmtRelative2(iso) {
21561
+ function fmtRelative2(iso, now = Date.now()) {
21562
21562
  if (!iso) return "";
21563
- const diff = Date.now() - new Date(iso).getTime();
21563
+ const diff = now - new Date(iso).getTime();
21564
21564
  const min = Math.floor(diff / 6e4);
21565
21565
  if (min < 1) return "now";
21566
21566
  if (min < 60) return `${min}m`;
@@ -22177,9 +22177,11 @@ function SessionsPanelSidebar({
22177
22177
  liveSessions,
22178
22178
  resumeSessions,
22179
22179
  currentSessionId,
22180
+ now,
22180
22181
  width
22181
22182
  }) {
22182
22183
  const inner = Math.max(8, width);
22184
+ const nowRef = now ?? Date.now();
22183
22185
  const live = liveSessions?.slice(0, 3) ?? [];
22184
22186
  const resume = resumeSessions?.slice(0, 3) ?? [];
22185
22187
  const total = (liveSessions?.length ?? 0) + (resumeSessions?.length ?? 0);
@@ -22239,7 +22241,7 @@ function SessionsPanelSidebar({
22239
22241
  ),
22240
22242
  resume.map((rs) => {
22241
22243
  const showRelativeTime = inner >= 24;
22242
- const rel = showRelativeTime ? fmtRelative2(rs.lastActivityAt ?? rs.endedAt) : "";
22244
+ const rel = showRelativeTime ? fmtRelative2(rs.lastActivityAt ?? rs.endedAt, nowRef) : "";
22243
22245
  const title = trunc3(
22244
22246
  rs.title || rs.lastUserMessage || rs.id,
22245
22247
  Math.max(4, inner - 2 - (showRelativeTime ? displayWidth(rel) + 1 : 0))
@@ -31505,10 +31507,10 @@ function parseDate(ts) {
31505
31507
  const d = new Date(ts);
31506
31508
  return Number.isFinite(d.getTime()) ? d : null;
31507
31509
  }
31508
- function daysAgo(ts) {
31510
+ function daysAgo(ts, now = Date.now()) {
31509
31511
  const d = parseDate(ts);
31510
31512
  if (!d) return null;
31511
- return (Date.now() - d.getTime()) / (1e3 * 60 * 60 * 24);
31513
+ return (now - d.getTime()) / (1e3 * 60 * 60 * 24);
31512
31514
  }
31513
31515
  function fmtDate(ts) {
31514
31516
  const d = parseDate(ts);
@@ -31527,7 +31529,7 @@ function recencyLabel(days) {
31527
31529
  // src/memory-slash.ts
31528
31530
  var DEFAULT_MEMORY_LIMIT = 50;
31529
31531
  var MAX_MEMORY_LIMIT = 500;
31530
- function computeStats(entries) {
31532
+ function computeStats(entries, now = Date.now()) {
31531
31533
  const stats = {
31532
31534
  total: entries.length,
31533
31535
  perScope: { "project-agents": 0, "project-memory": 0, "user-memory": 0 },
@@ -31551,7 +31553,7 @@ function computeStats(entries) {
31551
31553
  } else {
31552
31554
  stats.noPriorityCount++;
31553
31555
  }
31554
- const days = daysAgo(e.ts);
31556
+ const days = daysAgo(e.ts, now);
31555
31557
  if (days === null) {
31556
31558
  stats.recency.older++;
31557
31559
  } else if (days < 7) {
@@ -31634,7 +31636,7 @@ function renderSageStats(stats, memories, tagFilter, pathFilter) {
31634
31636
  }
31635
31637
  return lines;
31636
31638
  }
31637
- function renderSageEntries(memories, compact) {
31639
+ function renderSageEntries(memories, compact, now = Date.now()) {
31638
31640
  if (memories.length === 0) return [];
31639
31641
  const lines = [];
31640
31642
  if (compact) {
@@ -31656,7 +31658,7 @@ function renderSageEntries(memories, compact) {
31656
31658
  const kindEmoji = KIND_EMOJI[mem.kind] ?? "\u2022";
31657
31659
  const statusIcon3 = mem.status === "active" ? "\u{1F7E2}" : mem.status === "stale" ? "\u{1F7E1}" : mem.status === "archived" ? "\u{1F535}" : mem.status === "deleted" ? "\u26AB" : "\u26AA";
31658
31660
  const date = fmtDate(mem.createdAt);
31659
- const recency = recencyLabel(daysAgo(mem.createdAt));
31661
+ const recency = recencyLabel(daysAgo(mem.createdAt, now));
31660
31662
  const textPreview = mem.text.length > 100 ? `${mem.text.slice(0, 98)}\u2026` : mem.text;
31661
31663
  const badges = [];
31662
31664
  badges.push(`\`${mem.kind}\``);
@@ -31745,11 +31747,11 @@ function renderLegacySummary(stats) {
31745
31747
  }
31746
31748
  return lines;
31747
31749
  }
31748
- function renderLegacyEntry(e) {
31750
+ function renderLegacyEntry(e, now = Date.now()) {
31749
31751
  const typeEmoji = e.type ? TYPE_EMOJI[e.type] ?? "\u2022" : "\u2022";
31750
31752
  const prioEmoji = e.priority ? PRIORITY_EMOJI[e.priority] ?? "" : "";
31751
31753
  const date = fmtDate(e.ts);
31752
- const recency = recencyLabel(daysAgo(e.ts));
31754
+ const recency = recencyLabel(daysAgo(e.ts, now));
31753
31755
  const textFirstLine = e.text.split("\n")[0] ?? "";
31754
31756
  const preview = textFirstLine.length > 100 ? `${textFirstLine.slice(0, 98)}\u2026` : textFirstLine;
31755
31757
  const parts = [];
@@ -32217,7 +32219,7 @@ function createMemorySlashCommand(deps) {
32217
32219
  const parts2 = [];
32218
32220
  parts2.push(...renderSageStats(stats, allMemories, parsed.tag, parsed.path));
32219
32221
  if (filteredMemories.length > 0) {
32220
- parts2.push(...renderSageEntries(filteredMemories, parsed.compact));
32222
+ parts2.push(...renderSageEntries(filteredMemories, parsed.compact, Date.now()));
32221
32223
  } else {
32222
32224
  parts2.push("*No entries matched the filter.*");
32223
32225
  parts2.push("");
@@ -32263,7 +32265,7 @@ function createMemorySlashCommand(deps) {
32263
32265
  (e) => (e.tags ?? []).some((t) => t.toLowerCase() === tagLower)
32264
32266
  );
32265
32267
  }
32266
- const globalStats = computeStats(filteredEntries);
32268
+ const globalStats = computeStats(filteredEntries, Date.now());
32267
32269
  parts.push(...renderLegacySummary(globalStats));
32268
32270
  parts.push(
32269
32271
  "> \u{1F4A1} **SAGE** available \u2014 enables path anchoring, tags, graph, and structured queries."
@@ -32312,7 +32314,7 @@ function createMemorySlashCommand(deps) {
32312
32314
  const budget = scopeBudgets.get(scope) ?? scopeFiltered.length;
32313
32315
  const capped = scopeFiltered.slice(0, budget);
32314
32316
  if (capped.length === 0) continue;
32315
- parts.push(...renderLegacyScopeSection(scope, capped));
32317
+ parts.push(...renderLegacyScopeSection(scope, capped, Date.now()));
32316
32318
  }
32317
32319
  }
32318
32320
  const scopeSuffix = scopes.length === 1 ? `in **${SCOPE_LABEL[scopes[0]]}**` : "across all scopes";
@@ -32341,7 +32343,7 @@ function createMemorySlashCommand(deps) {
32341
32343
  }
32342
32344
  };
32343
32345
  }
32344
- function renderLegacyScopeSection(scope, entries) {
32346
+ function renderLegacyScopeSection(scope, entries, now = Date.now()) {
32345
32347
  if (entries.length === 0) return [];
32346
32348
  const lines = [];
32347
32349
  lines.push("");
@@ -32350,7 +32352,7 @@ function renderLegacyScopeSection(scope, entries) {
32350
32352
  lines.push(`## ${SCOPE_LABEL[scope]} (${entries.length})`);
32351
32353
  lines.push("");
32352
32354
  for (const e of entries) {
32353
- lines.push(renderLegacyEntry(e));
32355
+ lines.push(renderLegacyEntry(e, now));
32354
32356
  lines.push("");
32355
32357
  }
32356
32358
  return lines;
@@ -34403,7 +34405,7 @@ function selectAutoProceedCandidate({
34403
34405
  }) {
34404
34406
  const todo = resolveContinuation({ todos, suggestions: [] });
34405
34407
  if (todo.source === "todo") {
34406
- return { source: "todo", prompt: todo.text, label: todo.label };
34408
+ return { source: "todo", prompt: todo.text, label: todo.label, todoId: todo.todoId };
34407
34409
  }
34408
34410
  const automatic = yolo ? autoSuggestions.find((item) => item.trim().length > 0)?.trim() : void 0;
34409
34411
  if (automatic) {
@@ -34434,6 +34436,7 @@ function useNextStepsAutoSubmit({
34434
34436
  const [nextStepsAutoSubmitLabel, setNextStepsAutoSubmitLabel] = useState32(null);
34435
34437
  const nextStepsAutoSubmitSuggestionRef = useRef28(null);
34436
34438
  const nextStepsAutoSubmitSourceRef = useRef28(null);
34439
+ const nextStepsAutoSubmitTodoIdRef = useRef28(null);
34437
34440
  const nextStepsAutoSubmitTodosRef = useRef28([]);
34438
34441
  const nextStepsAutoSubmitTimerRef = useRef28(void 0);
34439
34442
  const autoSubmitStreakRef = useRef28(0);
@@ -34456,6 +34459,7 @@ function useNextStepsAutoSubmit({
34456
34459
  setNextStepsAutoSubmitLabel(null);
34457
34460
  nextStepsAutoSubmitSuggestionRef.current = null;
34458
34461
  nextStepsAutoSubmitSourceRef.current = null;
34462
+ nextStepsAutoSubmitTodoIdRef.current = null;
34459
34463
  return;
34460
34464
  }
34461
34465
  if (state.enhance != null || state.enhanceBusy || state.topicCheckBusy || state.refineCountdown != null || state.refineFailure != null || state.continueConfirm != null || state.clearConfirm != null || state.slashConfirm != null || state.escConfirm != null || state.sendModePicker != null || state.confirmQueue.length > 0 || state.shellCommandWarning != null || state.brainPrompt != null) {
@@ -34499,6 +34503,7 @@ function useNextStepsAutoSubmit({
34499
34503
  const delay = cfg?.delayMs ?? 45e3;
34500
34504
  nextStepsAutoSubmitSuggestionRef.current = candidate.prompt;
34501
34505
  nextStepsAutoSubmitSourceRef.current = candidate.source;
34506
+ nextStepsAutoSubmitTodoIdRef.current = candidate.todoId ?? null;
34502
34507
  const start = Date.now();
34503
34508
  setNextStepsAutoSubmitCountdown(Math.ceil(delay / 1e3));
34504
34509
  setNextStepsAutoSubmitLabel(candidate.label);
@@ -34512,13 +34517,29 @@ function useNextStepsAutoSubmit({
34512
34517
  if ((getAutonomy?.() ?? autonomyLive) !== "auto") {
34513
34518
  nextStepsAutoSubmitSuggestionRef.current = null;
34514
34519
  nextStepsAutoSubmitSourceRef.current = null;
34520
+ nextStepsAutoSubmitTodoIdRef.current = null;
34515
34521
  return;
34516
34522
  }
34517
34523
  const suggestion = nextStepsAutoSubmitSuggestionRef.current;
34518
34524
  const source = nextStepsAutoSubmitSourceRef.current;
34525
+ const todoId = nextStepsAutoSubmitTodoIdRef.current;
34519
34526
  nextStepsAutoSubmitSuggestionRef.current = null;
34520
34527
  nextStepsAutoSubmitSourceRef.current = null;
34528
+ nextStepsAutoSubmitTodoIdRef.current = null;
34521
34529
  if (suggestion) {
34530
+ if (source === "todo" && todoId) {
34531
+ if (agent?.ctx?.state) {
34532
+ const liveTodos = agent.ctx.todos;
34533
+ const selected = liveTodos.find((todo) => todo.id === todoId);
34534
+ if (selected?.status === "pending") {
34535
+ agent.ctx.state.replaceTodos(
34536
+ liveTodos.map(
34537
+ (todo) => todo.id === todoId ? { ...todo, status: "in_progress" } : todo.status === "in_progress" ? { ...todo, status: "pending" } : todo
34538
+ )
34539
+ );
34540
+ }
34541
+ }
34542
+ }
34522
34543
  const repetition = source === "todo" ? autoSubmitLoopGuardRef.current.recordGrounded(suggestion) : autoSubmitLoopGuardRef.current.record(suggestion);
34523
34544
  if (repetition.shouldHalt) {
34524
34545
  let stalledMatchFailed = false;
@@ -34538,11 +34559,10 @@ function useNextStepsAutoSubmit({
34538
34559
  (t) => t.id !== stalled.id && !continuationAttemptTrackerRef.current.isSkipped(t.id)
34539
34560
  );
34540
34561
  if (nextTodo) {
34541
- const stalledTodo = todos2.find((t) => t.id === stalled.id);
34542
- if (stalledTodo) {
34543
- stalledTodo.status = "pending";
34544
- }
34545
- const freshTodos = [...todos2];
34562
+ const freshTodos = todos2.map(
34563
+ (todo) => todo.id === stalled.id ? { ...todo, status: "pending" } : todo.id === nextTodo.id ? { ...todo, status: "in_progress" } : todo.status === "in_progress" ? { ...todo, status: "pending" } : todo
34564
+ );
34565
+ agent?.ctx?.state?.replaceTodos(freshTodos);
34546
34566
  nextStepsAutoSubmitTodosRef.current = freshTodos;
34547
34567
  const advancementPrompt = generateAdvancementPrompt(
34548
34568
  { id: stalled.id, content: stalled.content },
@@ -34651,6 +34671,7 @@ ${GROUNDED_NO_PROGRESS_STEER}` : suggestion;
34651
34671
  setNextStepsAutoSubmitLabel(null);
34652
34672
  nextStepsAutoSubmitSuggestionRef.current = null;
34653
34673
  nextStepsAutoSubmitSourceRef.current = null;
34674
+ nextStepsAutoSubmitTodoIdRef.current = null;
34654
34675
  }, []);
34655
34676
  return {
34656
34677
  nextStepsAutoSubmitCountdown,
@@ -39166,6 +39187,16 @@ function createSubmitController(host) {
39166
39187
  todos: agent.ctx.todos,
39167
39188
  suggestions: getSuggestions?.() ?? []
39168
39189
  }) : null;
39190
+ if (continueResolved?.source === "todo" && continueResolved.todoId) {
39191
+ const selected = agent.ctx.todos.find((todo) => todo.id === continueResolved.todoId);
39192
+ if (selected?.status === "pending") {
39193
+ agent.ctx.state.replaceTodos(
39194
+ agent.ctx.todos.map(
39195
+ (todo) => todo.id === continueResolved.todoId ? { ...todo, status: "in_progress" } : todo.status === "in_progress" ? { ...todo, status: "pending" } : todo
39196
+ )
39197
+ );
39198
+ }
39199
+ }
39169
39200
  const sddContext = await getSDDContext?.();
39170
39201
  if (!steering && stateRef.current.status === "idle" && continueResolved === null && !sddContext) {
39171
39202
  const liveState = stateRef.current;
@@ -8,15 +8,6 @@ export interface InlineToken {
8
8
  /** Markdown link target (the URL part of [text](url)). */
9
9
  link?: boolean | undefined;
10
10
  }
11
- /**
12
- * Parse one line of prose into inline-emphasis tokens. Markers are stripped
13
- * (this is display text, not length-preserving). `_..._` is intentionally NOT
14
- * treated as italic so snake_case / file_names aren't mangled. An unterminated
15
- * marker is emitted literally so no text is ever lost.
16
- *
17
- * Results are memoized: repeated calls with the same text return the identical
18
- * cached array, eliminating redundant parsing on every TUI re-render.
19
- */
20
11
  export declare function parseInline(text: string): InlineToken[];
21
12
  /**
22
13
  * Render assistant prose with markdown emphasis + block formatting. Tables are
@@ -4,7 +4,7 @@ export declare const TYPE_EMOJI: Record<MemoryType, string>;
4
4
  export declare const PRIORITY_EMOJI: Record<MemoryPriority, string>;
5
5
  export declare const KIND_EMOJI: Record<string, string>;
6
6
  export declare function parseDate(ts: string): Date | null;
7
- export declare function daysAgo(ts: string): number | null;
7
+ export declare function daysAgo(ts: string, now?: number): number | null;
8
8
  export declare function fmtDate(ts: string): string;
9
9
  export declare function recencyLabel(days: number | null): string;
10
10
  //# sourceMappingURL=memory-slash-format.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wrongstack/tui",
3
- "version": "0.301.0",
3
+ "version": "0.302.2",
4
4
  "license": "MIT",
5
5
  "description": "WrongStack terminal UI: Ink-based interactive renderer with history, pickers, and slash menus.",
6
6
  "repository": {
@@ -29,12 +29,12 @@
29
29
  "ink": "7.1.1",
30
30
  "react": "^19.2.8",
31
31
  "string-width": "8.2.2",
32
- "@wrongstack/core": "0.301.0",
33
- "@wrongstack/kanban": "0.301.0",
34
- "@wrongstack/runtime": "0.301.0",
35
- "@wrongstack/tools": "0.301.0",
36
- "@wrongstack/sdd": "0.301.0",
37
- "@wrongstack/sage": "0.301.0"
32
+ "@wrongstack/core": "0.302.2",
33
+ "@wrongstack/sage": "0.302.2",
34
+ "@wrongstack/sdd": "0.302.2",
35
+ "@wrongstack/kanban": "0.302.2",
36
+ "@wrongstack/tools": "0.302.2",
37
+ "@wrongstack/runtime": "0.302.2"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@testing-library/dom": "^10.4.1",