@aemonge-dev/pi-stardeck 0.9.0 → 0.9.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.
@@ -1,5 +1,4 @@
1
1
  import type { SessionSubject, SessionWorkFocus } from './active-work.ts';
2
- import type { GoalHeaderState } from './goal.ts';
3
2
  import type { ActiveRunSpanView, ActiveRuntimeRuns } from './runtime-runs.ts';
4
3
  import type { TitleState } from './title-state.ts';
5
4
  import type { TurnCostSnapshot } from './turn-cost.ts';
@@ -26,7 +25,6 @@ const OPEN_SPEC_WIDGET_ID = 'galactica.openspec';
26
25
  const OPEN_SPEC_SCOPE_SEPARATOR_WIDGET_ID = 'galactica.openspec-scope-separator';
27
26
  const OPEN_SPEC_PROGRESS_WIDGET_ID = 'galactica.openspec-progress';
28
27
  const SESSION_WORK_WIDGET_ID = 'galactica.work';
29
- const GOAL_WIDGET_ID = 'galactica.goal';
30
28
  const DIAGNOSTICS_WIDGET_ID = 'galactica.diagnostics';
31
29
  const ORCHESTRATION_WIDGET_ID = 'galactica.orchestration';
32
30
 
@@ -212,7 +210,6 @@ export function buildWidgetSnapshots(
212
210
  openSpecOverview?: OpenSpecOverview;
213
211
  openSpecProjectDetected?: boolean;
214
212
  workFocus?: SessionWorkFocus;
215
- goal?: GoalHeaderState | null;
216
213
  } = {},
217
214
  ): FancyFooterWidget[] {
218
215
  const compact = options.compact ?? false;
@@ -221,8 +218,7 @@ export function buildWidgetSnapshots(
221
218
 
222
219
  const hasActiveFallback =
223
220
  !options.openSpecOverview &&
224
- (Boolean(options.goal) ||
225
- Boolean(options.workFocus && options.workFocus.state !== 'clear'));
221
+ Boolean(options.workFocus && options.workFocus.state !== 'clear');
226
222
 
227
223
  if (
228
224
  options.openSpecOverview ||
@@ -269,25 +265,7 @@ export function buildWidgetSnapshots(
269
265
  // Frozen contract: without an OpenSpec focus there is no progress surface —
270
266
  // no aggregate overview, no 'no OpenSpec' narration.
271
267
 
272
- if (!snapshot.openspec && !options.openSpecOverview && options.goal) {
273
- widgets.push({
274
- id: GOAL_WIDGET_ID,
275
- label: 'Goal',
276
- description: 'Active Goal scope; its detailed objective remains in the header',
277
- content: { type: 'text', text: 'Goal' },
278
- icon: {
279
- glyphs: { nerd: '󰊕', emoji: '🎯', unicode: '◎', ascii: 'G' },
280
- color: goalHeaderColor(options.goal),
281
- },
282
- style: { textColor: goalHeaderColor(options.goal) },
283
- layout: {
284
- row: 0,
285
- position: 0,
286
- align: 'right',
287
- fill: 'none',
288
- },
289
- });
290
- } else if (
268
+ if (
291
269
  !snapshot.openspec &&
292
270
  !options.openSpecOverview &&
293
271
  options.workFocus &&
@@ -321,12 +299,7 @@ export function buildWidgetSnapshots(
321
299
  });
322
300
  }
323
301
 
324
- if (
325
- !snapshot.openspec &&
326
- !options.workFocus &&
327
- !options.goal &&
328
- options.openSpecFeedback
329
- ) {
302
+ if (!snapshot.openspec && !options.workFocus && options.openSpecFeedback) {
330
303
  const unavailable = options.openSpecFeedback === 'unavailable';
331
304
  const color: WidgetColor = unavailable ? 'warning' : 'dim';
332
305
  const stateText =
@@ -458,6 +431,7 @@ export const HEADER_ACTIVITY_KINDS = [
458
431
  'regression',
459
432
  'verification',
460
433
  'result-review',
434
+ 'answering',
461
435
  'recovery',
462
436
  'awaiting-validation',
463
437
  'awaiting-input',
@@ -494,26 +468,14 @@ export type HeaderLifecycle = (typeof HEADER_LIFECYCLES)[number];
494
468
 
495
469
  export const HEADER_SELECTION_SOURCES = [
496
470
  'openspec',
497
- 'goal',
498
471
  'session-work',
499
472
  'subject',
500
473
  ] as const;
501
474
 
502
- export type HeaderSelectionGoalDetail = {
503
- status: string;
504
- elapsedSeconds?: number;
505
- tasksDone?: number;
506
- tasksActive?: number;
507
- tasksTotal?: number;
508
- tokensUsed?: number;
509
- tokenBudget?: number;
510
- };
511
-
512
475
  export type HeaderSelection = {
513
476
  source: (typeof HEADER_SELECTION_SOURCES)[number];
514
477
  titles: string[];
515
478
  color: WidgetColor;
516
- goal?: HeaderSelectionGoalDetail;
517
479
  };
518
480
 
519
481
  export const HEADER_SUGGESTIONS = [
@@ -548,7 +510,6 @@ export type HeaderStatusEvent = {
548
510
  backgroundActivity: boolean;
549
511
  approvalRequired: boolean;
550
512
  blocked: boolean;
551
- openGoals?: number;
552
513
  activeRunSpans?: ActiveRunSpanView[];
553
514
  /** Versioned deck-title-state contract for the deck's upper half. */
554
515
  title?: TitleState;
@@ -586,7 +547,6 @@ function selectedOpenSpecTitles(
586
547
  export function headerSelection(options: {
587
548
  openSpec?: OpenSpecState | null;
588
549
  focusedTaskId?: string;
589
- goal?: GoalHeaderState | null;
590
550
  workFocus?: SessionWorkFocus;
591
551
  subject?: SessionSubject;
592
552
  }): HeaderSelection | null {
@@ -597,26 +557,6 @@ export function headerSelection(options: {
597
557
  color: openSpecColor(options.openSpec),
598
558
  };
599
559
  }
600
- if (options.goal) {
601
- const goal = options.goal;
602
- const detail = {
603
- status: goal.status,
604
- ...(goal.elapsedSeconds !== undefined
605
- ? { elapsedSeconds: goal.elapsedSeconds }
606
- : {}),
607
- ...(goal.tasksDone !== undefined ? { tasksDone: goal.tasksDone } : {}),
608
- ...(goal.tasksActive !== undefined ? { tasksActive: goal.tasksActive } : {}),
609
- ...(goal.tasksTotal !== undefined ? { tasksTotal: goal.tasksTotal } : {}),
610
- ...(goal.tokensUsed !== undefined ? { tokensUsed: goal.tokensUsed } : {}),
611
- ...(goal.tokenBudget !== undefined ? { tokenBudget: goal.tokenBudget } : {}),
612
- };
613
- return {
614
- source: 'goal',
615
- titles: [goal.objective ? normalizeInline(goal.objective) : 'Goal'],
616
- color: goalHeaderColor(goal),
617
- goal: detail,
618
- };
619
- }
620
560
  if (options.workFocus && options.workFocus.state !== 'clear') {
621
561
  return {
622
562
  source: 'session-work',
@@ -713,86 +653,6 @@ export function headerLifecycleForActivity(
713
653
  return 'working';
714
654
  }
715
655
 
716
- export function goalHeaderLifecycle(
717
- goal: GoalHeaderState,
718
- activeFallback: HeaderLifecycle = 'working',
719
- ): HeaderLifecycle {
720
- if (goal.status === 'blocked') return 'blocked';
721
- if (goal.status === 'active' && !goal.waiting) return activeFallback;
722
- return 'waiting';
723
- }
724
-
725
- export function goalHeaderActivityPath(
726
- goal: GoalHeaderState,
727
- automaticTurnLimit?: number,
728
- ): HeaderActivitySegment[] {
729
- if (goal.status === 'complete') {
730
- return [
731
- {
732
- id: 'awaiting-human-validation',
733
- label: 'awaiting Human validation',
734
- compact: 'validation',
735
- },
736
- ];
737
- }
738
- if (goal.status === 'blocked') {
739
- return [{ id: 'goal-blocked', label: 'Goal blocked', compact: 'blocked' }];
740
- }
741
- if (goal.status === 'paused') {
742
- return [{ id: 'goal-paused', label: 'Goal paused', compact: 'paused' }];
743
- }
744
- if (goal.status === 'usage_limited') {
745
- return [
746
- {
747
- id: 'goal-usage-limited',
748
- label: 'Goal usage limited',
749
- compact: 'usage limit',
750
- },
751
- ];
752
- }
753
- if (goal.status === 'budget_limited') {
754
- return [
755
- {
756
- id: 'goal-budget-limited',
757
- label: 'Goal budget limited',
758
- compact: 'budget limit',
759
- },
760
- ];
761
- }
762
- if (goal.status === 'queued') {
763
- return [{ id: 'goal-queued', label: 'Goal queued', compact: 'queued' }];
764
- }
765
- if (goal.waiting) {
766
- return [
767
- {
768
- id: 'goal-waiting',
769
- label: 'waiting for external event',
770
- compact: 'waiting',
771
- },
772
- ];
773
- }
774
- return [
775
- {
776
- id: 'goal-work',
777
- label: 'completing Goal',
778
- compact: 'Goal',
779
- ...(automaticTurnLimit === undefined
780
- ? {}
781
- : {
782
- current: Math.min(goal.automaticModelTurns, automaticTurnLimit),
783
- total: automaticTurnLimit,
784
- }),
785
- },
786
- ];
787
- }
788
-
789
- function goalHeaderColor(goal: GoalHeaderState): WidgetColor {
790
- if (goal.status === 'blocked') return 'error';
791
- if (goal.status === 'active' && !goal.waiting) return 'accent';
792
- if (goal.status === 'complete') return 'warning';
793
- return 'dim';
794
- }
795
-
796
656
  export function orchestrationHeaderLifecycle(
797
657
  state: OrchestrationState | null | undefined,
798
658
  ): HeaderLifecycle | undefined {
@@ -1032,9 +892,6 @@ export function buildHeaderStatusEvent(
1032
892
  activityPath?: HeaderActivitySegment[];
1033
893
  taskflowPhase?: string;
1034
894
  fallbackTitles?: string[];
1035
- goal?: GoalHeaderState | null;
1036
- openGoals?: number;
1037
- goalAutomaticTurnLimit?: number;
1038
895
  activeRuns?: ActiveRuntimeRuns;
1039
896
  activeRunSpans?: ActiveRunSpanView[];
1040
897
  idleMs?: number;
@@ -1060,7 +917,6 @@ export function buildHeaderStatusEvent(
1060
917
  const orchestration = options.orchestration;
1061
918
  const blocked =
1062
919
  orchestration?.state === 'blocked' ||
1063
- options.goal?.status === 'blocked' ||
1064
920
  Boolean(
1065
921
  options.openSpec &&
1066
922
  ['blocked', 'error', 'failed'].includes(options.openSpec.phase),
@@ -1071,33 +927,22 @@ export function buildHeaderStatusEvent(
1071
927
  const taskflowPhase = boundedTaskflowPhase(options.taskflowPhase);
1072
928
  const activityPath =
1073
929
  options.activityPath ??
1074
- (taskflowPhase ? taskflowPhaseHeaderActivityPath(taskflowPhase) : undefined) ??
1075
- (options.goal
1076
- ? goalHeaderActivityPath(options.goal, options.goalAutomaticTurnLimit)
1077
- : undefined);
930
+ (taskflowPhase ? taskflowPhaseHeaderActivityPath(taskflowPhase) : undefined);
1078
931
  const ordinaryLifecycle =
1079
932
  orchestrationLifecycle ??
1080
933
  options.lifecycle ??
1081
934
  headerLifecycleForActivity(activity) ??
1082
- (options.goal?.status === 'active' && !options.goal.waiting
1083
- ? 'working'
1084
- : options.workFocus?.state === 'validation'
1085
- ? 'waiting'
1086
- : 'listening');
935
+ (options.workFocus?.state === 'validation' ? 'waiting' : 'listening');
1087
936
  const lifecycle: HeaderLifecycle = blocked
1088
937
  ? 'blocked'
1089
938
  : options.lifecycle === 'aborted'
1090
939
  ? 'aborted'
1091
940
  : ((taskflowPhase ? taskflowPhaseHeaderLifecycle(taskflowPhase) : undefined) ??
1092
- (options.goal
1093
- ? goalHeaderLifecycle(options.goal, ordinaryLifecycle)
1094
- : undefined) ??
1095
941
  ordinaryLifecycle);
1096
942
  const projectedActivity =
1097
943
  activityPath !== undefined ? { activityPath } : activity ? { activity } : {};
1098
944
  const selection = headerSelection(options);
1099
- const approvalRequired =
1100
- options.workFocus?.state === 'validation' || options.goal?.status === 'complete';
945
+ const approvalRequired = options.workFocus?.state === 'validation';
1101
946
  const suggestion = headerSuggestion({
1102
947
  lifecycle,
1103
948
  activity,
@@ -1115,13 +960,6 @@ export function buildHeaderStatusEvent(
1115
960
  color: openSpecColor(state),
1116
961
  ...projectedActivity,
1117
962
  };
1118
- } else if (options.goal) {
1119
- work = {
1120
- lifecycle,
1121
- titles: ['Goal'],
1122
- color: goalHeaderColor(options.goal),
1123
- ...projectedActivity,
1124
- };
1125
963
  } else if (options.workFocus && options.workFocus.state !== 'clear') {
1126
964
  work = {
1127
965
  lifecycle,
@@ -1162,7 +1000,6 @@ export function buildHeaderStatusEvent(
1162
1000
 
1163
1001
  return {
1164
1002
  protocol: 1,
1165
- ...(options.openGoals ? { openGoals: options.openGoals } : {}),
1166
1003
  ...(options.activeRunSpans ? { activeRunSpans: options.activeRunSpans } : {}),
1167
1004
  ...(options.idleMs ? { idleMs: options.idleMs } : {}),
1168
1005
  ...(options.title ? { title: options.title } : {}),
@@ -1188,9 +1025,7 @@ export function buildHeaderStatusEvent(
1188
1025
  }
1189
1026
  : null,
1190
1027
  backgroundActivity: Boolean(
1191
- orchestration?.state === 'running' ||
1192
- (orchestration?.activeWorkers ?? 0) > 0 ||
1193
- (options.goal?.status === 'active' && !options.goal.waiting),
1028
+ orchestration?.state === 'running' || (orchestration?.activeWorkers ?? 0) > 0,
1194
1029
  ),
1195
1030
  approvalRequired,
1196
1031
  blocked,
@@ -1,5 +1,4 @@
1
1
  import type { LedgerEvent } from './ledger.ts';
2
- import type { GoalHeaderState } from './goal.ts';
3
2
  import type { OpenSpecFocus, SessionSubject, SessionWorkFocus } from './active-work.ts';
4
3
  import type {
5
4
  CockpitSnapshot,
@@ -20,7 +19,6 @@ import type {
20
19
 
21
20
  export type ReducerFacts = {
22
21
  openSpec: OpenSpecFocus;
23
- goal: GoalHeaderState | null;
24
22
  work: SessionWorkFocus;
25
23
  subject: SessionSubject;
26
24
  runs: ReadonlyMap<string, RunSpan>;
@@ -32,7 +30,6 @@ export const NO_SELECTION: SelectionState = { kind: 'none' };
32
30
  export function emptyFacts(): ReducerFacts {
33
31
  return {
34
32
  openSpec: { mode: 'none' },
35
- goal: null,
36
33
  work: { state: 'clear' },
37
34
  subject: { state: 'clear' },
38
35
  runs: new Map(),
@@ -43,7 +40,6 @@ export function emptyFacts(): ReducerFacts {
43
40
  /** The single selection resolver. Priority order is contract-frozen. */
44
41
  export function resolveSelection(facts: {
45
42
  openSpec: OpenSpecFocus;
46
- goal: GoalHeaderState | null;
47
43
  work: SessionWorkFocus;
48
44
  subject: SessionSubject;
49
45
  }): SelectionState {
@@ -54,13 +50,6 @@ export function resolveSelection(facts: {
54
50
  task: facts.openSpec.taskId,
55
51
  };
56
52
  }
57
- if (facts.goal) {
58
- return {
59
- kind: 'goal',
60
- status: facts.goal.status,
61
- waiting: facts.goal.waiting,
62
- };
63
- }
64
53
  if (facts.work.state !== 'clear') {
65
54
  return {
66
55
  kind: 'session-work',
@@ -84,8 +73,6 @@ export function applyEvent(facts: ReducerFacts, event: LedgerEvent): ReducerFact
84
73
  switch (event.type) {
85
74
  case 'focus/openspec':
86
75
  return { ...facts, openSpec: event.focus };
87
- case 'focus/goal':
88
- return { ...facts, goal: event.goal };
89
76
  case 'focus/work':
90
77
  return { ...facts, work: event.work };
91
78
  case 'focus/subject':
@@ -166,14 +153,6 @@ export function deriveTitle(snapshot: CockpitSnapshot): string {
166
153
  case 'openspec-task':
167
154
  detail = `${selection.change} › ${selection.task}`;
168
155
  break;
169
- case 'goal':
170
- detail =
171
- selection.status === 'complete'
172
- ? 'Validate › Goal'
173
- : selection.waiting
174
- ? 'Goal › waiting'
175
- : 'Goal';
176
- break;
177
156
  case 'session-work':
178
157
  detail =
179
158
  selection.phase === 'validation'
@@ -138,6 +138,7 @@ function sameActivity(
138
138
 
139
139
  export class TitleActivityFeed {
140
140
  private newest: TitleActivityState | null = null;
141
+ private ownerCallId: string | null = null;
141
142
 
142
143
  /** The newest observed event, or null when idle. */
143
144
  current(): TitleActivityState | null {
@@ -148,6 +149,17 @@ export class TitleActivityFeed {
148
149
  reset(): boolean {
149
150
  if (this.newest === null) return false;
150
151
  this.newest = null;
152
+ this.ownerCallId = null;
153
+ return true;
154
+ }
155
+
156
+ /** Observe a tool-execution end: the current activity clears when this
157
+ * call authored it — a finished tool never lingers through the gaps
158
+ * that follow (sticky-label fix, quiet-activity-row amendment). */
159
+ finish(toolCallId: string): boolean {
160
+ if (this.newest === null || this.ownerCallId !== toolCallId) return false;
161
+ this.newest = null;
162
+ this.ownerCallId = null;
151
163
  return true;
152
164
  }
153
165
 
@@ -180,7 +192,9 @@ export class TitleActivityFeed {
180
192
  span: turnSpanId ?? toolCallId,
181
193
  parent: null,
182
194
  };
183
- return this.set(event);
195
+ const changed = this.set(event);
196
+ if (changed) this.ownerCallId = toolCallId;
197
+ return changed;
184
198
  }
185
199
 
186
200
  /**
@@ -204,12 +218,14 @@ export class TitleActivityFeed {
204
218
  shortTarget(result.agent) ??
205
219
  shortTarget(callsAgentName(args, index)) ??
206
220
  FALLBACK_SUBAGENT_TARGET;
207
- return this.set({
221
+ const changed = this.set({
208
222
  class: 'subagent',
209
223
  target,
210
224
  span: `${toolCallId}:${index}`,
211
225
  parent: toolCallId,
212
226
  });
227
+ if (changed) this.ownerCallId = toolCallId;
228
+ return changed;
213
229
  }
214
230
  return false;
215
231
  }
@@ -1,5 +1,3 @@
1
- import type { GoalHeaderState } from './goal.ts';
2
-
3
1
  export type CheckState = 'pass' | 'fail' | 'running' | 'unknown';
4
2
  export type WorkflowState = 'running' | 'blocked' | 'complete' | 'idle' | 'unknown';
5
3
  export type WidgetColor =
@@ -193,11 +191,6 @@ export type StageValue =
193
191
 
194
192
  export type SelectionState =
195
193
  | { kind: 'openspec-task'; change: string; task: string }
196
- | {
197
- kind: 'goal';
198
- status: GoalHeaderState['status'];
199
- waiting: boolean;
200
- }
201
194
  | { kind: 'session-work'; intent: string; phase: 'active' | 'validation' }
202
195
  | { kind: 'subject'; title: string }
203
196
  | { kind: 'none' };