@aemonge-dev/pi-stardeck 0.10.0 → 0.12.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.
@@ -1,4 +1,3 @@
1
- import { StringEnum } from '@earendil-works/pi-ai';
2
1
  import type {
3
2
  ExtensionAPI,
4
3
  ExtensionCommandContext,
@@ -6,28 +5,15 @@ import type {
6
5
  } from '@earendil-works/pi-coding-agent';
7
6
  import { promises as fs, watch } from 'node:fs';
8
7
  import { dirname, join, resolve } from 'node:path';
9
- import { Type } from 'typebox';
10
8
  import {
11
9
  formatActiveWorkTitle,
12
- NO_OPEN_SPEC_FOCUS,
13
- NO_SESSION_SUBJECT,
14
- NO_SESSION_WORK_FOCUS,
15
- OPEN_SPEC_FOCUS_ENTRY_TYPE,
16
10
  type OpenSpecFocus,
11
+ parseOpenSpecFocus,
17
12
  restoreOpenSpecFocus,
18
13
  restoreSessionSubject,
19
- restoreSessionWorkFocus,
20
14
  sameOpenSpecFocus,
21
15
  sameSessionSubject,
22
- sameSessionWorkFocus,
23
- sameTaskFocus,
24
- SESSION_SUBJECT_ENTRY_TYPE,
25
- SESSION_WORK_FOCUS_ENTRY_TYPE,
26
16
  type SessionSubject,
27
- type SessionWorkFocus,
28
- sessionSubject,
29
- taskflowRunFocus,
30
- taskFocus,
31
17
  } from './src/active-work.ts';
32
18
  import { collectDiagnostics } from './src/diagnostics.ts';
33
19
  import { loadConfig } from './src/config.ts';
@@ -89,7 +75,6 @@ import type {
89
75
 
90
76
  type SessionContext = ExtensionContext | ExtensionCommandContext;
91
77
  type Domain = RuntimeErrorRecord['domain'];
92
- type OpenSpecFocusAction = 'status' | 'set' | 'replace' | 'validate' | 'clear';
93
78
 
94
79
  type StatusHealthCondition = {
95
80
  id: string;
@@ -186,10 +171,8 @@ class GalacticaStatusRuntime {
186
171
  private openSpecOverview: OpenSpecOverview | undefined;
187
172
  private openSpecProjectDetected: boolean | undefined;
188
173
  private focus: OpenSpecFocus;
189
- private workFocus: SessionWorkFocus;
190
174
  private subject: SessionSubject;
191
175
  private sessionName: string | undefined;
192
- private lastTitle: string | undefined;
193
176
  private diagnosticsPath: string | undefined;
194
177
  private orchestrationPath: string | undefined;
195
178
  private liveLifecycle: HeaderLifecycle;
@@ -217,7 +200,6 @@ class GalacticaStatusRuntime {
217
200
  ctx: SessionContext,
218
201
  publisher: FancyFooterPublisher,
219
202
  focus: OpenSpecFocus,
220
- workFocus: SessionWorkFocus,
221
203
  subject: SessionSubject,
222
204
  sessionName: string | undefined,
223
205
  ) {
@@ -225,12 +207,10 @@ class GalacticaStatusRuntime {
225
207
  this.ctx = ctx;
226
208
  this.publisher = publisher;
227
209
  this.focus = focus;
228
- this.workFocus = workFocus;
229
210
  this.subject = subject;
230
211
  this.sessionName = sessionName;
231
212
  const restoredAwaitingValidation =
232
- (focus.mode === 'task' && focus.awaitingValidation) ||
233
- workFocus.state === 'validation';
213
+ focus.mode === 'task' && focus.awaitingValidation;
234
214
  this.liveLifecycle = restoredAwaitingValidation ? 'waiting' : 'listening';
235
215
  this.liveActivity = restoredAwaitingValidation
236
216
  ? { kind: 'awaiting-validation' }
@@ -242,7 +222,6 @@ class GalacticaStatusRuntime {
242
222
  });
243
223
  const seededAt = Date.now();
244
224
  this.recordEvent({ type: 'focus/openspec', at: seededAt, focus });
245
- this.recordEvent({ type: 'focus/work', at: seededAt, work: workFocus });
246
225
  this.recordEvent({ type: 'focus/subject', at: seededAt, subject });
247
226
  }
248
227
 
@@ -607,28 +586,19 @@ class GalacticaStatusRuntime {
607
586
  // the same object references), so output stays byte-identical while the
608
587
  // ledger becomes the feeding seam.
609
588
  const focus = this.cockpitFacts.openSpec;
610
- const work = this.cockpitFacts.work;
611
589
  const subject = this.cockpitFacts.subject;
612
590
  const title = this.currentTitleState(snapshot);
613
591
  const focusedOpenSpec =
614
592
  focus.mode === 'task' && snapshot.openspec?.changeId === focus.change
615
593
  ? snapshot.openspec
616
594
  : null;
617
- const visibleWorkFocus =
618
- focus.mode === 'task' || (work.state === 'clear' && this.openSpecFeedback)
619
- ? undefined
620
- : work;
621
595
  const widgets = buildWidgetSnapshots(
622
596
  { ...snapshot, openspec: focusedOpenSpec },
623
597
  {
624
598
  focusedTaskId: focus.mode === 'task' ? focus.taskId : undefined,
625
- openSpecFeedback:
626
- focus.mode === 'none' && !visibleWorkFocus
627
- ? this.openSpecFeedback
628
- : undefined,
599
+ openSpecFeedback: focus.mode === 'none' ? this.openSpecFeedback : undefined,
629
600
  openSpecOverview: this.openSpecOverview,
630
601
  openSpecProjectDetected: this.openSpecProjectDetected,
631
- workFocus: visibleWorkFocus,
632
602
  },
633
603
  );
634
604
  this.publisher.sync(widgets);
@@ -637,7 +607,6 @@ class GalacticaStatusRuntime {
637
607
  buildHeaderStatusEvent(widgets, {
638
608
  openSpec: focusedOpenSpec,
639
609
  focusedTaskId: focus.mode === 'task' ? focus.taskId : undefined,
640
- workFocus: visibleWorkFocus,
641
610
  subject,
642
611
  orchestration: snapshot.orchestration,
643
612
  lifecycle: this.liveLifecycle,
@@ -654,20 +623,10 @@ class GalacticaStatusRuntime {
654
623
  diagnostics: snapshot.diagnostics,
655
624
  }),
656
625
  );
657
- this.updateTitle(snapshot);
658
626
  this.publishStatusHealth();
659
627
  }
660
628
 
661
629
  private currentTitleState(snapshot = this.store.get()): TitleState {
662
- return buildTitleState({
663
- subject: this.cockpitFacts.subject,
664
- focus: this.cockpitFacts.openSpec,
665
- openspec: snapshot.openspec,
666
- activity: this.titleActivity.current(),
667
- });
668
- }
669
-
670
- private updateTitle(snapshot = this.store.get()): void {
671
630
  const facts = this.cockpitFacts;
672
631
  const focusedTaskId =
673
632
  facts.openSpec.mode === 'task' ? facts.openSpec.taskId : undefined;
@@ -679,18 +638,23 @@ class GalacticaStatusRuntime {
679
638
  (task) => task.id === focusedTaskId,
680
639
  )?.title
681
640
  : undefined;
682
- const title = formatActiveWorkTitle({
641
+ // The band chip renders the fallback chain — task focus, native session
642
+ // name, legacy subject, directory. The terminal titlebar is pi-title's
643
+ // alone; this package never paints it (pure-renderer-split Slice 2).
644
+ const subjectTitle = formatActiveWorkTitle({
683
645
  focus: facts.openSpec,
684
- workFocus: facts.work,
685
646
  subject: facts.subject,
686
647
  ...(taskTitle ? { taskTitle } : {}),
687
648
  ...(this.sessionName ? { sessionName: this.sessionName } : {}),
688
649
  projectRoot: snapshot.projectRoot,
689
650
  cwd: snapshot.cwd,
690
651
  });
691
- if (title === this.lastTitle) return;
692
- this.lastTitle = title;
693
- this.ctx.ui.setTitle(title);
652
+ return buildTitleState({
653
+ subject: { state: 'set', title: subjectTitle },
654
+ focus: facts.openSpec,
655
+ openspec: snapshot.openspec,
656
+ activity: this.titleActivity.current(),
657
+ });
694
658
  }
695
659
 
696
660
  private recordEvent(event: LedgerEvent): void {
@@ -707,10 +671,6 @@ class GalacticaStatusRuntime {
707
671
  return this.focus;
708
672
  }
709
673
 
710
- currentWorkFocus(): SessionWorkFocus {
711
- return this.workFocus;
712
- }
713
-
714
674
  currentSubject(): SessionSubject {
715
675
  return this.subject;
716
676
  }
@@ -734,27 +694,13 @@ class GalacticaStatusRuntime {
734
694
  this.store.clearOpenSpec();
735
695
  if (focus.mode === 'task' && focus.awaitingValidation) {
736
696
  this.setLiveProjection('waiting', { kind: 'awaiting-validation' });
737
- } else if (focus.mode === 'none' && this.workFocus.state === 'clear') {
697
+ } else if (focus.mode === 'none') {
738
698
  this.setLiveProjection('listening');
739
699
  }
740
700
  this.publish();
741
701
  this.launch(this.refresh(false), 'openspec');
742
702
  }
743
703
 
744
- setWorkFocus(focus: SessionWorkFocus): void {
745
- if (sameSessionWorkFocus(this.workFocus, focus)) return;
746
- this.workFocus = focus;
747
- this.recordEvent({ type: 'focus/work', at: Date.now(), work: focus });
748
- if (focus.state === 'validation') {
749
- this.liveLifecycle = 'waiting';
750
- this.liveActivity = { kind: 'awaiting-validation' };
751
- } else if (focus.state === 'clear' && this.focus.mode === 'none') {
752
- this.liveLifecycle = 'listening';
753
- this.liveActivity = undefined;
754
- }
755
- this.publish();
756
- }
757
-
758
704
  setSubject(subject: SessionSubject): void {
759
705
  if (sameSessionSubject(this.subject, subject)) return;
760
706
  this.subject = subject;
@@ -937,20 +883,16 @@ class GalacticaStatusRuntime {
937
883
  this.publish();
938
884
  return;
939
885
  }
940
- if (this.focus.mode === 'task' || this.workFocus.state === 'validation') {
886
+ if (this.focus.mode === 'task') {
941
887
  this.setLiveProjection('waiting', { kind: 'awaiting-validation' });
942
888
  return;
943
889
  }
944
- if (this.workFocus.state === 'active') {
945
- this.setLiveProjection('waiting', { kind: 'awaiting-input' });
946
- return;
947
- }
948
890
  this.setLiveProjection('listening');
949
891
  }
950
892
 
951
893
  setSessionName(name: string | undefined): void {
952
894
  this.sessionName = name;
953
- this.updateTitle();
895
+ this.publish();
954
896
  }
955
897
 
956
898
  async forceRefresh(): Promise<void> {
@@ -977,11 +919,6 @@ class GalacticaStatusRuntime {
977
919
  ? `OpenSpec focus: ${this.openSpecFeedback}`
978
920
  : 'OpenSpec focus: none',
979
921
  );
980
- lines.push(
981
- this.workFocus.state === 'clear'
982
- ? 'Work: clear'
983
- : `Work: ${this.workFocus.state} · ${this.workFocus.intent}`,
984
- );
985
922
  lines.push(
986
923
  snapshot.diagnostics
987
924
  ? `Diagnostics: ${formatDiagnostics(snapshot.diagnostics)}`
@@ -1003,7 +940,6 @@ class GalacticaStatusRuntime {
1003
940
  configPath: snapshot.configPath,
1004
941
  configWarnings: snapshot.configWarnings,
1005
942
  focus: this.focus,
1006
- workFocus: this.workFocus,
1007
943
  openSpecFeedback: this.openSpecFeedback ?? null,
1008
944
  openSpecOverview: this.openSpecOverview ?? null,
1009
945
  detected: {
@@ -1054,123 +990,20 @@ export default function galacticaStatus(pi: ExtensionAPI): void {
1054
990
  }
1055
991
  });
1056
992
 
1057
- function persistAndApplyWorkFocus(focus: SessionWorkFocus): void {
1058
- pi.appendEntry(SESSION_WORK_FOCUS_ENTRY_TYPE, focus);
1059
- runtime?.setWorkFocus(focus);
1060
- }
1061
-
1062
- function persistAndApplySubject(subject: SessionSubject): void {
1063
- pi.appendEntry(SESSION_SUBJECT_ENTRY_TYPE, subject);
1064
- runtime?.setSubject(subject);
1065
- }
1066
-
1067
- function persistAndApplyFocus(focus: OpenSpecFocus): void {
1068
- if (
1069
- focus.mode === 'task' &&
1070
- (runtime?.currentWorkFocus() ?? NO_SESSION_WORK_FOCUS).state !== 'clear'
1071
- ) {
1072
- persistAndApplyWorkFocus(NO_SESSION_WORK_FOCUS);
1073
- }
1074
- pi.appendEntry(OPEN_SPEC_FOCUS_ENTRY_TYPE, focus);
993
+ // work-state owns focus writes and emits `focus-changed` after each
994
+ // successful one (pi.events); the renderer subscribes for immediate
995
+ // repaint (pure-renderer-split Slice 2).
996
+ const stopFocusRepaint = pi.events.on('focus-changed', (data: unknown) => {
997
+ const focus = parseOpenSpecFocus(data);
998
+ if (!focus) return;
1075
999
  runtime?.setFocus(focus);
1076
- }
1077
-
1078
- function subjectStatus(subject: SessionSubject): string {
1079
- return subject.state === 'clear' ? 'Title: clear' : `Title: ${subject.title}`;
1080
- }
1081
-
1082
- function applyTitleAction(
1083
- action: 'status' | 'set',
1084
- title?: string,
1085
- ): { ok: boolean; message: string; subject: SessionSubject } {
1086
- const current = runtime?.currentSubject() ?? NO_SESSION_SUBJECT;
1087
-
1088
- if (action === 'status') {
1089
- return { ok: true, message: subjectStatus(current), subject: current };
1090
- }
1091
-
1092
- const next = sessionSubject('set', title);
1093
- if (!next || next.state !== 'set') {
1094
- return {
1095
- ok: false,
1096
- message: 'Title requires a non-empty title',
1097
- subject: current,
1098
- };
1099
- }
1100
- if (!sameSessionSubject(current, next)) persistAndApplySubject(next);
1101
- return { ok: true, message: subjectStatus(next), subject: next };
1102
- }
1103
-
1104
- function applyOpenSpecFocusAction(
1105
- action: OpenSpecFocusAction,
1106
- change?: string,
1107
- taskId?: string,
1108
- ): { ok: boolean; message: string; focus: OpenSpecFocus } {
1109
- const current = runtime?.currentFocus() ?? NO_OPEN_SPEC_FOCUS;
1110
- const statusMessage = (focus: OpenSpecFocus): string =>
1111
- focus.mode === 'task'
1112
- ? `OpenSpec focus: ${focus.change} › ${focus.taskId}` +
1113
- (focus.awaitingValidation ? ' — awaiting Human validation' : '')
1114
- : 'OpenSpec focus: none';
1115
-
1116
- if (action === 'status') {
1117
- return { ok: true, message: statusMessage(current), focus: current };
1118
- }
1119
- if (action === 'clear') {
1120
- persistAndApplyFocus(NO_OPEN_SPEC_FOCUS);
1121
- return {
1122
- ok: true,
1123
- message: 'OpenSpec focus cleared',
1124
- focus: NO_OPEN_SPEC_FOCUS,
1125
- };
1126
- }
1127
- if (action === 'validate') {
1128
- if (current.mode !== 'task') {
1129
- return {
1130
- ok: false,
1131
- message: 'OpenSpec focus has no task to validate',
1132
- focus: current,
1133
- };
1134
- }
1135
- const validated = { ...current, awaitingValidation: true };
1136
- if (!sameOpenSpecFocus(current, validated)) persistAndApplyFocus(validated);
1137
- return { ok: true, message: statusMessage(validated), focus: validated };
1138
- }
1139
-
1140
- const requested = taskFocus(change, taskId);
1141
- if (!requested) {
1142
- return {
1143
- ok: false,
1144
- message: 'OpenSpec focus requires non-empty change and task identifiers',
1145
- focus: current,
1146
- };
1147
- }
1148
- if (
1149
- action === 'set' &&
1150
- current.mode === 'task' &&
1151
- !sameTaskFocus(current, requested)
1152
- ) {
1153
- return {
1154
- ok: false,
1155
- message:
1156
- `OpenSpec focus is ${current.change} › ${current.taskId}; ` +
1157
- 'use replace to change it deliberately',
1158
- focus: current,
1159
- };
1160
- }
1161
- if (!sameOpenSpecFocus(current, requested)) persistAndApplyFocus(requested);
1162
- return {
1163
- ok: true,
1164
- message: statusMessage(requested),
1165
- focus: requested,
1166
- };
1167
- }
1000
+ runtime?.settle();
1001
+ });
1168
1002
 
1169
1003
  pi.on('session_start', (_event, ctx) => {
1170
1004
  runtime?.stop();
1171
1005
  const branch = ctx.sessionManager.getBranch();
1172
1006
  const focus = restoreOpenSpecFocus(branch);
1173
- const workFocus = restoreSessionWorkFocus(branch);
1174
1007
  const subject = restoreSessionSubject(branch);
1175
1008
  const sessionName = pi.getSessionName() ?? ctx.sessionManager.getSessionName();
1176
1009
  runtime = new GalacticaStatusRuntime(
@@ -1178,7 +1011,6 @@ export default function galacticaStatus(pi: ExtensionAPI): void {
1178
1011
  ctx,
1179
1012
  publisher,
1180
1013
  focus,
1181
- workFocus,
1182
1014
  subject,
1183
1015
  sessionName,
1184
1016
  );
@@ -1192,7 +1024,6 @@ export default function galacticaStatus(pi: ExtensionAPI): void {
1192
1024
  pi.on('session_tree', (_event, ctx) => {
1193
1025
  const branch = ctx.sessionManager.getBranch();
1194
1026
  runtime?.setFocus(restoreOpenSpecFocus(branch));
1195
- runtime?.setWorkFocus(restoreSessionWorkFocus(branch));
1196
1027
  runtime?.setSubject(restoreSessionSubject(branch));
1197
1028
  runtime?.settle();
1198
1029
  });
@@ -1214,23 +1045,6 @@ export default function galacticaStatus(pi: ExtensionAPI): void {
1214
1045
  );
1215
1046
  return;
1216
1047
  }
1217
- const requested = taskflowRunFocus(event.input);
1218
-
1219
- if (requested) {
1220
- const current = runtime?.currentFocus() ?? NO_OPEN_SPEC_FOCUS;
1221
- if (current.mode === 'task' && !sameOpenSpecFocus(current, requested)) {
1222
- return {
1223
- block: true,
1224
- reason:
1225
- `OpenSpec focus is ${current.change} › ${current.taskId}; ` +
1226
- 'use /openspec-focus clear or /openspec-focus replace <change> <task> before starting a conflicting run',
1227
- };
1228
- }
1229
- if (!sameOpenSpecFocus(current, requested)) {
1230
- persistAndApplyFocus(requested);
1231
- }
1232
- }
1233
-
1234
1048
  const initialPhase = taskflowInitialHeaderPhase(event.input);
1235
1049
  if (initialPhase) {
1236
1050
  runtime?.setTaskflowPhase(initialPhase);
@@ -1296,117 +1110,7 @@ export default function galacticaStatus(pi: ExtensionAPI): void {
1296
1110
  runtime = undefined;
1297
1111
  publisher.removeAll();
1298
1112
  stopReady();
1299
- });
1300
-
1301
- pi.registerTool({
1302
- name: 'title',
1303
- label: 'Session Title',
1304
- description:
1305
- 'Show or set the sanitized session title that keeps the focus row populated beneath OpenSpec and Session Work. Call this at the first conversational turn with a short sanitized title (at most 120 characters, never raw prompt text) and revise it only when the topic clearly shifts; never require Human action for it',
1306
- promptSnippet: 'Manage the automatic session title',
1307
- parameters: Type.Object({
1308
- action: StringEnum(['status', 'set'] as const),
1309
- title: Type.Optional(
1310
- Type.String({
1311
- description: 'Required only for set: the short sanitized subject title',
1312
- }),
1313
- ),
1314
- }),
1315
- async execute(_toolCallId, params) {
1316
- const result = applyTitleAction(params.action, params.title);
1317
- return {
1318
- content: [
1319
- {
1320
- type: 'text',
1321
- text: result.ok ? result.message : `Error: ${result.message}`,
1322
- },
1323
- ],
1324
- details: {
1325
- ok: result.ok,
1326
- subject: result.subject,
1327
- },
1328
- };
1329
- },
1330
- });
1331
-
1332
- pi.registerTool({
1333
- name: 'openspec_focus',
1334
- label: 'OpenSpec Focus',
1335
- description:
1336
- 'Show, safely set, deliberately replace, validate, or clear branch-local OpenSpec Task focus. Use only for explicit Human direction or an exact declared OpenSpec Task handoff; never infer material Plan selection or Human validation',
1337
- promptSnippet:
1338
- 'Manage exact branch-local OpenSpec Task focus with replacement safeguards',
1339
- parameters: Type.Object({
1340
- action: StringEnum(['status', 'set', 'replace', 'validate', 'clear'] as const),
1341
- change: Type.Optional(
1342
- Type.String({
1343
- description: 'Required for set and replace: the exact OpenSpec change ID',
1344
- }),
1345
- ),
1346
- task: Type.Optional(
1347
- Type.String({
1348
- description: 'Required for set and replace: the exact OpenSpec Task ID',
1349
- }),
1350
- ),
1351
- }),
1352
- async execute(_toolCallId, params) {
1353
- const result = applyOpenSpecFocusAction(
1354
- params.action,
1355
- params.change,
1356
- params.task,
1357
- );
1358
- return {
1359
- content: [
1360
- {
1361
- type: 'text',
1362
- text: result.ok ? result.message : `Error: ${result.message}`,
1363
- },
1364
- ],
1365
- details: {
1366
- ok: result.ok,
1367
- focus: result.focus,
1368
- },
1369
- };
1370
- },
1371
- });
1372
-
1373
- pi.registerCommand('openspec-focus', {
1374
- description: "Show, clear, or explicitly set this session's OpenSpec task focus",
1375
- handler: async (args, ctx) => {
1376
- const parts = args.trim().split(/\s+/u).filter(Boolean);
1377
- if (parts.length === 0) {
1378
- ctx.ui.notify(applyOpenSpecFocusAction('status').message, 'info');
1379
- return;
1380
- }
1381
-
1382
- if (parts.length === 1 && parts[0]?.toLowerCase() === 'clear') {
1383
- const result = applyOpenSpecFocusAction('clear');
1384
- ctx.ui.notify(result.message, 'info');
1385
- return;
1386
- }
1387
-
1388
- if (parts.length === 1 && parts[0]?.toLowerCase() === 'validate') {
1389
- const result = applyOpenSpecFocusAction('validate');
1390
- ctx.ui.notify(result.message, result.ok ? 'info' : 'warning');
1391
- return;
1392
- }
1393
-
1394
- const rawAction = parts[0]?.toLowerCase();
1395
- const action: 'set' | 'replace' = ['set', 'replace'].includes(rawAction ?? '')
1396
- ? (rawAction as 'set' | 'replace')
1397
- : 'set';
1398
- const identifiers = rawAction === action ? parts.slice(1) : parts;
1399
- if (identifiers.length !== 2) {
1400
- ctx.ui.notify(
1401
- 'Usage: /openspec-focus [set|replace] <change> <task> | clear',
1402
- 'warning',
1403
- );
1404
- return;
1405
- }
1406
-
1407
- const result = applyOpenSpecFocusAction(action, identifiers[0], identifiers[1]);
1408
- ctx.ui.notify(result.message, result.ok ? 'info' : 'warning');
1409
- },
1113
+ stopFocusRepaint();
1410
1114
  });
1411
1115
 
1412
1116
  pi.registerCommand('galactica-status', {
@@ -1,7 +1,6 @@
1
1
  import { basename } from 'node:path';
2
2
 
3
3
  export const OPEN_SPEC_FOCUS_ENTRY_TYPE = 'galactica-status.openspec-focus.v1';
4
- export const SESSION_WORK_FOCUS_ENTRY_TYPE = 'galactica-status.session-work.v1';
5
4
  export const SESSION_SUBJECT_ENTRY_TYPE = 'galactica-status.session-subject.v1';
6
5
 
7
6
  export type TaskOpenSpecFocus = {
@@ -15,11 +14,6 @@ export type OpenSpecFocus = { mode: 'none' } | TaskOpenSpecFocus;
15
14
 
16
15
  export const NO_OPEN_SPEC_FOCUS: OpenSpecFocus = { mode: 'none' };
17
16
 
18
- export type SessionWorkFocus =
19
- { state: 'clear' } | { state: 'active' | 'validation'; intent: string };
20
-
21
- export const NO_SESSION_WORK_FOCUS: SessionWorkFocus = { state: 'clear' };
22
-
23
17
  export type SessionSubject = { state: 'clear' } | { state: 'set'; title: string };
24
18
 
25
19
  export const NO_SESSION_SUBJECT: SessionSubject = { state: 'clear' };
@@ -27,7 +21,6 @@ export const NO_SESSION_SUBJECT: SessionSubject = { state: 'clear' };
27
21
  const CONTROL_CHARACTERS = /[\u0000-\u001f\u007f-\u009f]/gu;
28
22
  const MAX_TITLE_POINTS = 180;
29
23
  const MAX_TASK_TITLE_POINTS = 72;
30
- const MAX_WORK_INTENT_POINTS = 160;
31
24
  const MAX_SUBJECT_POINTS = 120;
32
25
 
33
26
  function isRecord(value: unknown): value is Record<string, unknown> {
@@ -73,25 +66,6 @@ export function parseOpenSpecFocus(value: unknown): OpenSpecFocus | undefined {
73
66
  return taskFocus(value.change, value.taskId, value.awaitingValidation);
74
67
  }
75
68
 
76
- export function sessionWorkFocus(
77
- state: unknown,
78
- intent?: unknown,
79
- ): SessionWorkFocus | undefined {
80
- if (state === 'clear') return NO_SESSION_WORK_FOCUS;
81
- if (state !== 'active' && state !== 'validation') return undefined;
82
- const normalizedIntent = explicitId(intent);
83
- if (!normalizedIntent) return undefined;
84
- return {
85
- state,
86
- intent: shorten(normalizedIntent, MAX_WORK_INTENT_POINTS),
87
- };
88
- }
89
-
90
- export function parseSessionWorkFocus(value: unknown): SessionWorkFocus | undefined {
91
- if (!isRecord(value)) return undefined;
92
- return sessionWorkFocus(value.state, value.intent);
93
- }
94
-
95
69
  export function sessionSubject(
96
70
  state: unknown,
97
71
  title?: unknown,
@@ -127,22 +101,6 @@ export function restoreOpenSpecFocus(entries: readonly unknown[]): OpenSpecFocus
127
101
  return NO_OPEN_SPEC_FOCUS;
128
102
  }
129
103
 
130
- export function restoreSessionWorkFocus(entries: readonly unknown[]): SessionWorkFocus {
131
- for (let index = entries.length - 1; index >= 0; index -= 1) {
132
- const entry = entries[index];
133
- if (
134
- !isRecord(entry) ||
135
- entry.type !== 'custom' ||
136
- entry.customType !== SESSION_WORK_FOCUS_ENTRY_TYPE
137
- ) {
138
- continue;
139
- }
140
- const focus = parseSessionWorkFocus(entry.data);
141
- if (focus) return focus;
142
- }
143
- return NO_SESSION_WORK_FOCUS;
144
- }
145
-
146
104
  export function restoreSessionSubject(entries: readonly unknown[]): SessionSubject {
147
105
  for (let index = entries.length - 1; index >= 0; index -= 1) {
148
106
  const entry = entries[index];
@@ -192,16 +150,6 @@ export function sameOpenSpecFocus(left: OpenSpecFocus, right: OpenSpecFocus): bo
192
150
  );
193
151
  }
194
152
 
195
- export function sameSessionWorkFocus(
196
- left: SessionWorkFocus,
197
- right: SessionWorkFocus,
198
- ): boolean {
199
- if (left.state !== right.state) return false;
200
- return (
201
- left.state === 'clear' || (right.state !== 'clear' && left.intent === right.intent)
202
- );
203
- }
204
-
205
153
  export function sameSessionSubject(
206
154
  left: SessionSubject,
207
155
  right: SessionSubject,
@@ -214,7 +162,6 @@ export function sameSessionSubject(
214
162
 
215
163
  export function formatActiveWorkTitle(options: {
216
164
  focus: OpenSpecFocus;
217
- workFocus?: SessionWorkFocus;
218
165
  subject?: SessionSubject;
219
166
  taskTitle?: string;
220
167
  sessionName?: string;
@@ -227,11 +174,8 @@ export function formatActiveWorkTitle(options: {
227
174
  ? shorten(normalizeTitlePart(options.taskTitle), MAX_TASK_TITLE_POINTS)
228
175
  : '';
229
176
  detail = `${options.focus.change} › ${options.focus.taskId}${taskTitle ? ` ${taskTitle}` : ''}`;
230
- } else if (options.workFocus && options.workFocus.state !== 'clear') {
231
- detail =
232
- options.workFocus.state === 'validation'
233
- ? `Validate › ${options.workFocus.intent}`
234
- : options.workFocus.intent;
177
+ } else if (normalizeTitlePart(options.sessionName ?? '')) {
178
+ detail = normalizeTitlePart(options.sessionName ?? '');
235
179
  } else if (options.subject && options.subject.state === 'set') {
236
180
  detail = options.subject.title;
237
181
  } else {
@@ -5,7 +5,7 @@ import type {
5
5
  RunSpan,
6
6
  StageValue,
7
7
  } from './types.ts';
8
- import type { OpenSpecFocus, SessionSubject, SessionWorkFocus } from './active-work.ts';
8
+ import type { OpenSpecFocus, SessionSubject } from './active-work.ts';
9
9
 
10
10
  /**
11
11
  * Append-only event ledger for the cockpit status core.
@@ -18,7 +18,6 @@ import type { OpenSpecFocus, SessionSubject, SessionWorkFocus } from './active-w
18
18
 
19
19
  export const LEDGER_EVENT_TYPES = [
20
20
  'focus/openspec',
21
- 'focus/work',
22
21
  'focus/subject',
23
22
  'run/start',
24
23
  'run/stage',
@@ -37,7 +36,6 @@ export type LedgerEvent =
37
36
  at: number;
38
37
  focus: OpenSpecFocus;
39
38
  }
40
- | { type: 'focus/work'; at: number; work: SessionWorkFocus }
41
39
  | { type: 'focus/subject'; at: number; subject: SessionSubject }
42
40
  | { type: 'run/start'; at: number; span: RunSpan }
43
41
  | { type: 'run/stage'; at: number; id: string; stage: StageValue; declared: boolean }