aicodeman 1.5.1 → 1.6.1

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 (135) hide show
  1. package/README.md +48 -12
  2. package/README.zh-CN.md +68 -12
  3. package/dist/remote-hosts.d.ts +61 -1
  4. package/dist/remote-hosts.d.ts.map +1 -1
  5. package/dist/remote-hosts.js +142 -0
  6. package/dist/remote-hosts.js.map +1 -1
  7. package/dist/remote-reconnect.d.ts +129 -0
  8. package/dist/remote-reconnect.d.ts.map +1 -0
  9. package/dist/remote-reconnect.js +119 -0
  10. package/dist/remote-reconnect.js.map +1 -0
  11. package/dist/services/unified-session-service.d.ts +13 -1
  12. package/dist/services/unified-session-service.d.ts.map +1 -1
  13. package/dist/services/unified-session-service.js +88 -3
  14. package/dist/services/unified-session-service.js.map +1 -1
  15. package/dist/session-order.d.ts +40 -0
  16. package/dist/session-order.d.ts.map +1 -0
  17. package/dist/session-order.js +67 -0
  18. package/dist/session-order.js.map +1 -0
  19. package/dist/session.d.ts +39 -0
  20. package/dist/session.d.ts.map +1 -1
  21. package/dist/session.js +87 -19
  22. package/dist/session.js.map +1 -1
  23. package/dist/state-store.d.ts +12 -0
  24. package/dist/state-store.d.ts.map +1 -1
  25. package/dist/state-store.js +33 -0
  26. package/dist/state-store.js.map +1 -1
  27. package/dist/tmux-manager.d.ts +66 -0
  28. package/dist/tmux-manager.d.ts.map +1 -1
  29. package/dist/tmux-manager.js +226 -2
  30. package/dist/tmux-manager.js.map +1 -1
  31. package/dist/types/app-state.d.ts +2 -0
  32. package/dist/types/app-state.d.ts.map +1 -1
  33. package/dist/types/app-state.js.map +1 -1
  34. package/dist/types/session.d.ts +42 -0
  35. package/dist/types/session.d.ts.map +1 -1
  36. package/dist/types/session.js.map +1 -1
  37. package/dist/web/public/admin-ui.js +303 -6
  38. package/dist/web/public/admin-ui.js.br +0 -0
  39. package/dist/web/public/admin-ui.js.gz +0 -0
  40. package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
  41. package/dist/web/public/app.9367fd20.js +38 -0
  42. package/dist/web/public/app.9367fd20.js.br +0 -0
  43. package/dist/web/public/app.9367fd20.js.gz +0 -0
  44. package/dist/web/public/{constants.ad3a8031.js → constants.21a0a83e.js} +9 -0
  45. package/dist/web/public/constants.21a0a83e.js.br +0 -0
  46. package/dist/web/public/{constants.ad3a8031.js.gz → constants.21a0a83e.js.gz} +0 -0
  47. package/dist/web/public/cron-ui.js.gz +0 -0
  48. package/dist/web/public/image-input.ee16ad88.js.gz +0 -0
  49. package/dist/web/public/index.html +34 -11
  50. package/dist/web/public/index.html.br +0 -0
  51. package/dist/web/public/index.html.gz +0 -0
  52. package/dist/web/public/input-cjk.63794d0b.js.gz +0 -0
  53. package/dist/web/public/keyboard-accessory.3f91acef.js.gz +0 -0
  54. package/dist/web/public/mobile-handlers.b5a667ef.js.gz +0 -0
  55. package/dist/web/public/mobile.6a53092b.css +1 -0
  56. package/dist/web/public/mobile.6a53092b.css.br +0 -0
  57. package/dist/web/public/mobile.6a53092b.css.gz +0 -0
  58. package/dist/web/public/notification-manager.ca9dc40d.js.gz +0 -0
  59. package/dist/web/public/orchestrator-panel.js.gz +0 -0
  60. package/dist/web/public/{panels-ui.3c9b0465.js → panels-ui.58582d4c.js} +2 -2
  61. package/dist/web/public/panels-ui.58582d4c.js.br +0 -0
  62. package/dist/web/public/panels-ui.58582d4c.js.gz +0 -0
  63. package/dist/web/public/ralph-panel.6de2d0f8.js.gz +0 -0
  64. package/dist/web/public/ralph-wizard.13a1831e.js.gz +0 -0
  65. package/dist/web/public/respawn-ui.2d249da9.js.gz +0 -0
  66. package/dist/web/public/sanitize-html.bc7078d6.js.gz +0 -0
  67. package/dist/web/public/{session-ui.e5d4f9b5.js → session-ui.0cf86dd4.js} +30 -22
  68. package/dist/web/public/session-ui.0cf86dd4.js.br +0 -0
  69. package/dist/web/public/session-ui.0cf86dd4.js.gz +0 -0
  70. package/dist/web/public/{settings-ui.29214cf1.js → settings-ui.bc5f4792.js} +2 -2
  71. package/dist/web/public/settings-ui.bc5f4792.js.br +0 -0
  72. package/dist/web/public/settings-ui.bc5f4792.js.gz +0 -0
  73. package/dist/web/public/styles.e9dc275f.css +1 -0
  74. package/dist/web/public/styles.e9dc275f.css.br +0 -0
  75. package/dist/web/public/styles.e9dc275f.css.gz +0 -0
  76. package/dist/web/public/subagent-windows.69b60c35.js.gz +0 -0
  77. package/dist/web/public/sw.js.gz +0 -0
  78. package/dist/web/public/terminal-ui.9ea677e9.js +3 -0
  79. package/dist/web/public/terminal-ui.9ea677e9.js.br +0 -0
  80. package/dist/web/public/terminal-ui.9ea677e9.js.gz +0 -0
  81. package/dist/web/public/ultracode-panel.js.gz +0 -0
  82. package/dist/web/public/ultracode-windows.js.gz +0 -0
  83. package/dist/web/public/upload.html.gz +0 -0
  84. package/dist/web/public/vendor/dompurify.min.js.gz +0 -0
  85. package/dist/web/public/vendor/marked.min.js.gz +0 -0
  86. package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
  87. package/dist/web/public/vendor/xterm-addon-serialize.min.js.gz +0 -0
  88. package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
  89. package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
  90. package/dist/web/public/vendor/xterm-zerolag-input.137ad9f0.js.gz +0 -0
  91. package/dist/web/public/vendor/xterm.css.gz +0 -0
  92. package/dist/web/public/vendor/xterm.min.js.gz +0 -0
  93. package/dist/web/public/voice-input.085e9e73.js.gz +0 -0
  94. package/dist/web/routes/admin-routes.d.ts +2 -0
  95. package/dist/web/routes/admin-routes.d.ts.map +1 -1
  96. package/dist/web/routes/admin-routes.js +91 -2
  97. package/dist/web/routes/admin-routes.js.map +1 -1
  98. package/dist/web/routes/case-routes.d.ts.map +1 -1
  99. package/dist/web/routes/case-routes.js +19 -1
  100. package/dist/web/routes/case-routes.js.map +1 -1
  101. package/dist/web/routes/session-routes.d.ts.map +1 -1
  102. package/dist/web/routes/session-routes.js +140 -3
  103. package/dist/web/routes/session-routes.js.map +1 -1
  104. package/dist/web/schemas.d.ts +13 -0
  105. package/dist/web/schemas.d.ts.map +1 -1
  106. package/dist/web/schemas.js +31 -0
  107. package/dist/web/schemas.js.map +1 -1
  108. package/dist/web/server.d.ts +9 -0
  109. package/dist/web/server.d.ts.map +1 -1
  110. package/dist/web/server.js +57 -1
  111. package/dist/web/server.js.map +1 -1
  112. package/dist/web/sse-events.d.ts +15 -0
  113. package/dist/web/sse-events.d.ts.map +1 -1
  114. package/dist/web/sse-events.js +18 -0
  115. package/dist/web/sse-events.js.map +1 -1
  116. package/package.json +1 -1
  117. package/dist/web/public/app.1ccca158.js +0 -38
  118. package/dist/web/public/app.1ccca158.js.br +0 -0
  119. package/dist/web/public/app.1ccca158.js.gz +0 -0
  120. package/dist/web/public/constants.ad3a8031.js.br +0 -0
  121. package/dist/web/public/mobile.597136a6.css +0 -1
  122. package/dist/web/public/mobile.597136a6.css.br +0 -0
  123. package/dist/web/public/mobile.597136a6.css.gz +0 -0
  124. package/dist/web/public/panels-ui.3c9b0465.js.br +0 -0
  125. package/dist/web/public/panels-ui.3c9b0465.js.gz +0 -0
  126. package/dist/web/public/session-ui.e5d4f9b5.js.br +0 -0
  127. package/dist/web/public/session-ui.e5d4f9b5.js.gz +0 -0
  128. package/dist/web/public/settings-ui.29214cf1.js.br +0 -0
  129. package/dist/web/public/settings-ui.29214cf1.js.gz +0 -0
  130. package/dist/web/public/styles.9e6cdb9d.css +0 -1
  131. package/dist/web/public/styles.9e6cdb9d.css.br +0 -0
  132. package/dist/web/public/styles.9e6cdb9d.css.gz +0 -0
  133. package/dist/web/public/terminal-ui.efda6083.js +0 -3
  134. package/dist/web/public/terminal-ui.efda6083.js.br +0 -0
  135. package/dist/web/public/terminal-ui.efda6083.js.gz +0 -0
@@ -0,0 +1,129 @@
1
+ /**
2
+ * @fileoverview Pure logic for the remote-session auto-reconnect watcher (COD-108).
3
+ *
4
+ * COD-104 made remote tmux sessions durable + idempotently reattachable, but a
5
+ * reconnect only fired at explicit trigger points. COD-108 adds a continuous
6
+ * watcher (in `TmuxManager`) that detects a dead remote pane and emits
7
+ * `remoteSessionDropped`; `SessionManager`/server then reassembles the respawn
8
+ * options and reattaches (re-running the idempotent remote command).
9
+ *
10
+ * This module holds the SIDE-EFFECT-FREE pieces so they can be unit-tested
11
+ * without real tmux:
12
+ * - the bounded exponential **backoff schedule** (attempt → delay, capped),
13
+ * - the per-session **reconnect state** shape,
14
+ * - the **eligibility decision** (`decideReconnect`) given a session + its
15
+ * reconnect state + the current time + the guard set.
16
+ *
17
+ * The watcher in `tmux-manager.ts` owns the live `isPaneDead` probe and the
18
+ * timers; everything here is pure and deterministic (time is injected).
19
+ *
20
+ * @module remote-reconnect
21
+ */
22
+ /**
23
+ * Bounded exponential backoff delays (ms) between reconnect attempts.
24
+ * Attempt N (1-based) waits `BACKOFF_SCHEDULE_MS[N-1]` from the previous emit
25
+ * before the next emit is eligible. After the last entry the session is
26
+ * considered `reconnect-exhausted` and the watcher stops emitting for it.
27
+ *
28
+ * 5s, 15s, 45s, 2m, 5m, 5m → ~6 attempts spanning ~13 minutes.
29
+ */
30
+ export declare const BACKOFF_SCHEDULE_MS: readonly number[];
31
+ /** Maximum number of reconnect attempts before exhaustion. */
32
+ export declare const MAX_RECONNECT_ATTEMPTS: number;
33
+ /**
34
+ * Delay (ms) to wait AFTER emitting attempt `attempt` (1-based) before the next
35
+ * attempt is eligible. `attempt <= 0` returns the first delay; an attempt at or
36
+ * beyond the cap returns the last delay (callers should check exhaustion via
37
+ * {@link isExhausted} rather than relying on this for the stop decision).
38
+ *
39
+ * Pure — no clock, no I/O.
40
+ */
41
+ export declare function reconnectDelayForAttempt(attempt: number): number;
42
+ /** Whether `attempts` reconnect emits have reached/exceeded the cap. Pure. */
43
+ export declare function isExhausted(attempts: number): boolean;
44
+ /**
45
+ * Per-session reconnect bookkeeping held by the watcher. All time values are
46
+ * epoch ms. `inFlight` guards against stacking respawns when a tick fires while
47
+ * a previous reattach is still running. `exhaustedEmitted` ensures the
48
+ * `remoteReconnectExhausted` event fires at most once per session.
49
+ */
50
+ export interface RemoteReconnectState {
51
+ /** Number of `remoteSessionDropped` emits so far (advances per emit). */
52
+ attempts: number;
53
+ /** Earliest time (epoch ms) the next emit is eligible. 0 = eligible now. */
54
+ nextEligibleAt: number;
55
+ /** A reattach triggered by a prior emit is currently running. */
56
+ inFlight: boolean;
57
+ /** Cap reached — stop auto-retrying for this session. */
58
+ exhausted: boolean;
59
+ /** The `remoteReconnectExhausted` SSE event has already been emitted. */
60
+ exhaustedEmitted: boolean;
61
+ }
62
+ /** A fresh reconnect state (no attempts, immediately eligible). Pure. */
63
+ export declare function freshReconnectState(): RemoteReconnectState;
64
+ /**
65
+ * Advance the backoff after an emit at time `now`. Increments `attempts` and
66
+ * schedules `nextEligibleAt = now + delay`. Returns a NEW state object (does
67
+ * not mutate the input). Pure.
68
+ *
69
+ * NOTE: this does NOT set `exhausted`. Exhaustion is a decision the watcher
70
+ * makes on the FOLLOWING tick (via {@link decideReconnect} → `exhaust`), so the
71
+ * `remoteReconnectExhausted` event fires exactly once after the final attempt's
72
+ * backoff window elapses — not pre-emptively on the last emit.
73
+ */
74
+ export declare function advanceBackoff(state: RemoteReconnectState, now: number): RemoteReconnectState;
75
+ /** Reset after a successful reattach — back to a fresh, eligible state. Pure. */
76
+ export declare function resetReconnectState(): RemoteReconnectState;
77
+ /** Minimal session view the decision needs (avoids importing MuxSession here). */
78
+ export interface ReconnectSessionView {
79
+ sessionId: string;
80
+ /** Truthy when this is a remote (SSH-wrapped) session. */
81
+ isRemote: boolean;
82
+ /** Result of `isPaneDead(muxName)` for this session. */
83
+ paneDead: boolean;
84
+ }
85
+ /**
86
+ * Decision outcomes for a single watcher tick on one session.
87
+ * - `emit` → emit `remoteSessionDropped { sessionId, attempt }`, then
88
+ * advance backoff (attempt = the returned `attempt`).
89
+ * - `exhaust` → cap reached this tick; emit `remoteReconnectExhausted` once.
90
+ * - `skip` → do nothing (not remote / pane alive / guarded / in-flight /
91
+ * not yet due / already exhausted).
92
+ */
93
+ export type ReconnectAction = {
94
+ kind: 'emit';
95
+ attempt: number;
96
+ } | {
97
+ kind: 'exhaust';
98
+ } | {
99
+ kind: 'skip';
100
+ reason: ReconnectSkipReason;
101
+ };
102
+ export type ReconnectSkipReason = 'not-remote' | 'pane-alive' | 'guarded' | 'in-flight' | 'not-due' | 'exhausted' | 'disabled';
103
+ export interface DecideReconnectInput {
104
+ session: ReconnectSessionView;
105
+ state: RemoteReconnectState | undefined;
106
+ /** Session is in the intentional-teardown guard set (killed/detached/stopping). */
107
+ guarded: boolean;
108
+ /** Kill-switch: `remoteAutoReconnect` setting. When false, never reconnect. */
109
+ enabled: boolean;
110
+ now: number;
111
+ }
112
+ /**
113
+ * PURE eligibility decision for one session on one tick. No clock, no I/O — all
114
+ * inputs are passed in. The watcher translates the result into emits + state
115
+ * transitions.
116
+ *
117
+ * Order of guards (most-decisive first):
118
+ * 1. kill-switch off → skip:disabled
119
+ * 2. not a remote session → skip:not-remote
120
+ * 3. pane is alive → skip:pane-alive
121
+ * 4. intentional teardown guard → skip:guarded (NEVER revive a killed tab)
122
+ * 5. a reattach already running → skip:in-flight (no stacked respawns)
123
+ * 6. already exhausted → skip:exhausted (one exhaust emit, then quiet)
124
+ * 7. cap reached this tick → exhaust
125
+ * 8. not yet due (backoff) → skip:not-due
126
+ * 9. otherwise → emit (attempt = attempts + 1)
127
+ */
128
+ export declare function decideReconnect(input: DecideReconnectInput): ReconnectAction;
129
+ //# sourceMappingURL=remote-reconnect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remote-reconnect.d.ts","sourceRoot":"","sources":["../src/remote-reconnect.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB,EAAE,SAAS,MAAM,EAAuD,CAAC;AAEzG,8DAA8D;AAC9D,eAAO,MAAM,sBAAsB,QAA6B,CAAC;AAEjE;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAIhE;AAED,8EAA8E;AAC9E,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAErD;AAED;;;;;GAKG;AACH,MAAM,WAAW,oBAAoB;IACnC,yEAAyE;IACzE,QAAQ,EAAE,MAAM,CAAC;IACjB,4EAA4E;IAC5E,cAAc,EAAE,MAAM,CAAC;IACvB,iEAAiE;IACjE,QAAQ,EAAE,OAAO,CAAC;IAClB,yDAAyD;IACzD,SAAS,EAAE,OAAO,CAAC;IACnB,yEAAyE;IACzE,gBAAgB,EAAE,OAAO,CAAC;CAC3B;AAED,yEAAyE;AACzE,wBAAgB,mBAAmB,IAAI,oBAAoB,CAE1D;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,oBAAoB,EAAE,GAAG,EAAE,MAAM,GAAG,oBAAoB,CAQ7F;AAED,iFAAiF;AACjF,wBAAgB,mBAAmB,IAAI,oBAAoB,CAE1D;AAED,kFAAkF;AAClF,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,0DAA0D;IAC1D,QAAQ,EAAE,OAAO,CAAC;IAClB,wDAAwD;IACxD,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,eAAe,GACvB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACjC;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GACnB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,mBAAmB,CAAA;CAAE,CAAC;AAElD,MAAM,MAAM,mBAAmB,GAC3B,YAAY,GACZ,YAAY,GACZ,SAAS,GACT,WAAW,GACX,SAAS,GACT,WAAW,GACX,UAAU,CAAC;AAEf,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,oBAAoB,CAAC;IAC9B,KAAK,EAAE,oBAAoB,GAAG,SAAS,CAAC;IACxC,mFAAmF;IACnF,OAAO,EAAE,OAAO,CAAC;IACjB,+EAA+E;IAC/E,OAAO,EAAE,OAAO,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,GAAG,eAAe,CAuB5E"}
@@ -0,0 +1,119 @@
1
+ /**
2
+ * @fileoverview Pure logic for the remote-session auto-reconnect watcher (COD-108).
3
+ *
4
+ * COD-104 made remote tmux sessions durable + idempotently reattachable, but a
5
+ * reconnect only fired at explicit trigger points. COD-108 adds a continuous
6
+ * watcher (in `TmuxManager`) that detects a dead remote pane and emits
7
+ * `remoteSessionDropped`; `SessionManager`/server then reassembles the respawn
8
+ * options and reattaches (re-running the idempotent remote command).
9
+ *
10
+ * This module holds the SIDE-EFFECT-FREE pieces so they can be unit-tested
11
+ * without real tmux:
12
+ * - the bounded exponential **backoff schedule** (attempt → delay, capped),
13
+ * - the per-session **reconnect state** shape,
14
+ * - the **eligibility decision** (`decideReconnect`) given a session + its
15
+ * reconnect state + the current time + the guard set.
16
+ *
17
+ * The watcher in `tmux-manager.ts` owns the live `isPaneDead` probe and the
18
+ * timers; everything here is pure and deterministic (time is injected).
19
+ *
20
+ * @module remote-reconnect
21
+ */
22
+ /**
23
+ * Bounded exponential backoff delays (ms) between reconnect attempts.
24
+ * Attempt N (1-based) waits `BACKOFF_SCHEDULE_MS[N-1]` from the previous emit
25
+ * before the next emit is eligible. After the last entry the session is
26
+ * considered `reconnect-exhausted` and the watcher stops emitting for it.
27
+ *
28
+ * 5s, 15s, 45s, 2m, 5m, 5m → ~6 attempts spanning ~13 minutes.
29
+ */
30
+ export const BACKOFF_SCHEDULE_MS = [5_000, 15_000, 45_000, 120_000, 300_000, 300_000];
31
+ /** Maximum number of reconnect attempts before exhaustion. */
32
+ export const MAX_RECONNECT_ATTEMPTS = BACKOFF_SCHEDULE_MS.length;
33
+ /**
34
+ * Delay (ms) to wait AFTER emitting attempt `attempt` (1-based) before the next
35
+ * attempt is eligible. `attempt <= 0` returns the first delay; an attempt at or
36
+ * beyond the cap returns the last delay (callers should check exhaustion via
37
+ * {@link isExhausted} rather than relying on this for the stop decision).
38
+ *
39
+ * Pure — no clock, no I/O.
40
+ */
41
+ export function reconnectDelayForAttempt(attempt) {
42
+ if (!Number.isFinite(attempt) || attempt <= 1)
43
+ return BACKOFF_SCHEDULE_MS[0];
44
+ const idx = Math.min(Math.floor(attempt) - 1, BACKOFF_SCHEDULE_MS.length - 1);
45
+ return BACKOFF_SCHEDULE_MS[idx];
46
+ }
47
+ /** Whether `attempts` reconnect emits have reached/exceeded the cap. Pure. */
48
+ export function isExhausted(attempts) {
49
+ return attempts >= MAX_RECONNECT_ATTEMPTS;
50
+ }
51
+ /** A fresh reconnect state (no attempts, immediately eligible). Pure. */
52
+ export function freshReconnectState() {
53
+ return { attempts: 0, nextEligibleAt: 0, inFlight: false, exhausted: false, exhaustedEmitted: false };
54
+ }
55
+ /**
56
+ * Advance the backoff after an emit at time `now`. Increments `attempts` and
57
+ * schedules `nextEligibleAt = now + delay`. Returns a NEW state object (does
58
+ * not mutate the input). Pure.
59
+ *
60
+ * NOTE: this does NOT set `exhausted`. Exhaustion is a decision the watcher
61
+ * makes on the FOLLOWING tick (via {@link decideReconnect} → `exhaust`), so the
62
+ * `remoteReconnectExhausted` event fires exactly once after the final attempt's
63
+ * backoff window elapses — not pre-emptively on the last emit.
64
+ */
65
+ export function advanceBackoff(state, now) {
66
+ const attempts = state.attempts + 1;
67
+ const delay = reconnectDelayForAttempt(attempts);
68
+ return {
69
+ ...state,
70
+ attempts,
71
+ nextEligibleAt: now + delay,
72
+ };
73
+ }
74
+ /** Reset after a successful reattach — back to a fresh, eligible state. Pure. */
75
+ export function resetReconnectState() {
76
+ return freshReconnectState();
77
+ }
78
+ /**
79
+ * PURE eligibility decision for one session on one tick. No clock, no I/O — all
80
+ * inputs are passed in. The watcher translates the result into emits + state
81
+ * transitions.
82
+ *
83
+ * Order of guards (most-decisive first):
84
+ * 1. kill-switch off → skip:disabled
85
+ * 2. not a remote session → skip:not-remote
86
+ * 3. pane is alive → skip:pane-alive
87
+ * 4. intentional teardown guard → skip:guarded (NEVER revive a killed tab)
88
+ * 5. a reattach already running → skip:in-flight (no stacked respawns)
89
+ * 6. already exhausted → skip:exhausted (one exhaust emit, then quiet)
90
+ * 7. cap reached this tick → exhaust
91
+ * 8. not yet due (backoff) → skip:not-due
92
+ * 9. otherwise → emit (attempt = attempts + 1)
93
+ */
94
+ export function decideReconnect(input) {
95
+ const { session, state, guarded, enabled, now } = input;
96
+ if (!enabled)
97
+ return { kind: 'skip', reason: 'disabled' };
98
+ if (!session.isRemote)
99
+ return { kind: 'skip', reason: 'not-remote' };
100
+ if (!session.paneDead)
101
+ return { kind: 'skip', reason: 'pane-alive' };
102
+ // Intentional kill / detach must NEVER be auto-revived.
103
+ if (guarded)
104
+ return { kind: 'skip', reason: 'guarded' };
105
+ const s = state ?? freshReconnectState();
106
+ // Only one reconnect in flight per session — don't stack respawns.
107
+ if (s.inFlight)
108
+ return { kind: 'skip', reason: 'in-flight' };
109
+ if (s.exhausted)
110
+ return { kind: 'skip', reason: 'exhausted' };
111
+ // Cap reached: surface exhaustion once, then go quiet.
112
+ if (isExhausted(s.attempts))
113
+ return { kind: 'exhaust' };
114
+ // Backoff gate — only emit when due.
115
+ if (now < s.nextEligibleAt)
116
+ return { kind: 'skip', reason: 'not-due' };
117
+ return { kind: 'emit', attempt: s.attempts + 1 };
118
+ }
119
+ //# sourceMappingURL=remote-reconnect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remote-reconnect.js","sourceRoot":"","sources":["../src/remote-reconnect.ts"],"names":[],"mappings":"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"}
@@ -27,9 +27,15 @@ export type UnifiedSessionItem = {
27
27
  lastActivityAt?: number;
28
28
  claudeSessionId?: string;
29
29
  firstPrompt?: string;
30
+ /** Most recent user prompt from the transcript (COD-145), parallel to firstPrompt. */
31
+ lastPrompt?: string;
30
32
  sizeBytes?: number;
31
33
  projectKey?: string;
32
34
  remote?: boolean;
35
+ /** Pinned to the top of the session manager list (COD-139). */
36
+ pinned?: boolean;
37
+ /** When the session was pinned (epoch ms) — orders the pinned group desc. */
38
+ pinnedAt?: number;
33
39
  sources: string[];
34
40
  stats?: {
35
41
  memoryMB: number;
@@ -47,6 +53,8 @@ export type LiveSessionInput = {
47
53
  createdAt?: number;
48
54
  lastActivityAt?: number;
49
55
  claudeSessionId?: string;
56
+ pinned?: boolean;
57
+ pinnedAt?: number;
50
58
  };
51
59
  /** Persisted session view (subset of `SessionState`). */
52
60
  export type PersistedSessionInput = {
@@ -59,6 +67,8 @@ export type PersistedSessionInput = {
59
67
  lastActivityAt?: number;
60
68
  /** Claude conversation ID this session resumes (`SessionState.resumeSessionId`). */
61
69
  claudeSessionId?: string;
70
+ pinned?: boolean;
71
+ pinnedAt?: number;
62
72
  };
63
73
  /** Lifecycle audit-log view. Entries are expected NEWEST-first (the order `SessionLifecycleLog.query()` returns). */
64
74
  export type LifecycleInput = {
@@ -75,6 +85,8 @@ export type HistoryInput = {
75
85
  sizeBytes: number;
76
86
  lastModified: string;
77
87
  firstPrompt?: string;
88
+ /** Most recent user prompt from the transcript (COD-145). */
89
+ lastPrompt?: string;
78
90
  projectKey?: string;
79
91
  };
80
92
  /** Mux process-stat view. */
@@ -101,7 +113,7 @@ export type UnifiedSources = {
101
113
  */
102
114
  export declare function mergeUnifiedSessions(sources: UnifiedSources): UnifiedSessionItem[];
103
115
  /**
104
- * Case-insensitive substring filter (name + firstPrompt + workingDir + sessionId)
116
+ * Case-insensitive substring filter (name + firstPrompt + lastPrompt + workingDir + sessionId)
105
117
  * with offset/limit paging. `total` is the filtered count BEFORE paging.
106
118
  */
107
119
  export declare function filterAndPaginate(items: UnifiedSessionItem[], opts: {
@@ -1 +1 @@
1
- {"version":3,"file":"unified-session-service.d.ts","sourceRoot":"","sources":["../../src/services/unified-session-service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"unified-session-service.d.ts","sourceRoot":"","sources":["../../src/services/unified-session-service.ts"],"names":[],"mappings":"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"}
@@ -63,6 +63,7 @@ export function mergeUnifiedSessions(sources) {
63
63
  overwrite(item, 'workingDir', h.workingDir);
64
64
  overwrite(item, 'sizeBytes', h.sizeBytes);
65
65
  overwrite(item, 'firstPrompt', h.firstPrompt);
66
+ overwrite(item, 'lastPrompt', h.lastPrompt);
66
67
  overwrite(item, 'projectKey', h.projectKey);
67
68
  const ms = Date.parse(h.lastModified);
68
69
  if (!Number.isNaN(ms) && item.lastActivityAt === undefined)
@@ -91,6 +92,8 @@ export function mergeUnifiedSessions(sources) {
91
92
  overwrite(item, 'workingDir', p.workingDir);
92
93
  overwrite(item, 'createdAt', p.createdAt);
93
94
  overwrite(item, 'lastActivityAt', p.lastActivityAt);
95
+ overwrite(item, 'pinned', p.pinned);
96
+ overwrite(item, 'pinnedAt', p.pinnedAt);
94
97
  }
95
98
  // 4) live (highest precedence)
96
99
  for (const v of sources.live ?? []) {
@@ -104,6 +107,8 @@ export function mergeUnifiedSessions(sources) {
104
107
  overwrite(item, 'createdAt', v.createdAt);
105
108
  overwrite(item, 'lastActivityAt', v.lastActivityAt);
106
109
  overwrite(item, 'claudeSessionId', v.claudeSessionId);
110
+ overwrite(item, 'pinned', v.pinned);
111
+ overwrite(item, 'pinnedAt', v.pinnedAt);
107
112
  }
108
113
  // 5) mux stats + remote flag (create item if mux-only)
109
114
  for (const m of sources.mux ?? []) {
@@ -115,6 +120,67 @@ export function mergeUnifiedSessions(sources) {
115
120
  if (m.remote !== undefined)
116
121
  item.remote = m.remote;
117
122
  }
123
+ // firstPrompt backfill (COD-140): the only source that sets firstPrompt is the
124
+ // transcript-history view, keyed by the Claude transcript file's UUID. A live/persisted
125
+ // row keyed by its Codeman id only inherits firstPrompt when that id happens to equal an
126
+ // on-disk transcript UUID. When it doesn't (stale/wrong claudeSessionId, post-/clear new
127
+ // uuid, resumed/attached/worktree session, transcript not yet flushed), the row shows
128
+ // "(no prompt captured)" even though a real transcript for that working dir exists under a
129
+ // different UUID. Backfill from the already-passed history: first try the claudeSessionId
130
+ // join, then the newest transcript in the same workingDir. Never overwrite a non-empty
131
+ // firstPrompt (so rows keyed to their own transcript are untouched).
132
+ const firstPromptByUuid = new Map();
133
+ const firstPromptByWorkingDir = new Map();
134
+ // COD-145: lastPrompt rides the same backfill (build parallel indexes; never overwrite).
135
+ const lastPromptByUuid = new Map();
136
+ const lastPromptByWorkingDir = new Map();
137
+ for (const h of sources.history ?? []) {
138
+ const ms = Date.parse(h.lastModified);
139
+ const ts = Number.isNaN(ms) ? -Infinity : ms;
140
+ if (h.firstPrompt) {
141
+ firstPromptByUuid.set(h.sessionId, h.firstPrompt);
142
+ if (h.workingDir) {
143
+ const existing = firstPromptByWorkingDir.get(h.workingDir);
144
+ if (!existing || ts > existing.ms) {
145
+ firstPromptByWorkingDir.set(h.workingDir, { prompt: h.firstPrompt, ms: ts });
146
+ }
147
+ }
148
+ }
149
+ if (h.lastPrompt) {
150
+ lastPromptByUuid.set(h.sessionId, h.lastPrompt);
151
+ if (h.workingDir) {
152
+ const existing = lastPromptByWorkingDir.get(h.workingDir);
153
+ if (!existing || ts > existing.ms) {
154
+ lastPromptByWorkingDir.set(h.workingDir, { prompt: h.lastPrompt, ms: ts });
155
+ }
156
+ }
157
+ }
158
+ }
159
+ for (const item of map.values()) {
160
+ if (!item.firstPrompt) {
161
+ // never overwrite an existing non-empty prompt
162
+ const byUuid = item.claudeSessionId ? firstPromptByUuid.get(item.claudeSessionId) : undefined;
163
+ if (byUuid) {
164
+ item.firstPrompt = byUuid;
165
+ }
166
+ else if (item.workingDir) {
167
+ const byDir = firstPromptByWorkingDir.get(item.workingDir);
168
+ if (byDir)
169
+ item.firstPrompt = byDir.prompt;
170
+ }
171
+ }
172
+ if (!item.lastPrompt) {
173
+ const byUuid = item.claudeSessionId ? lastPromptByUuid.get(item.claudeSessionId) : undefined;
174
+ if (byUuid) {
175
+ item.lastPrompt = byUuid;
176
+ }
177
+ else if (item.workingDir) {
178
+ const byDir = lastPromptByWorkingDir.get(item.workingDir);
179
+ if (byDir)
180
+ item.lastPrompt = byDir.prompt;
181
+ }
182
+ }
183
+ }
118
184
  // Meaningfulness floor: keep real rows, drop bare lifecycle/mux-only noise.
119
185
  const kept = [];
120
186
  for (const item of map.values()) {
@@ -125,8 +191,27 @@ export function mergeUnifiedSessions(sources) {
125
191
  if (isReal)
126
192
  kept.push(item);
127
193
  }
128
- // Stable sort: lastActivityAt desc (undefined last), createdAt desc, sessionId asc.
194
+ // Stable sort (COD-139): pinned group first (pinnedAt desc, most-recently-pinned
195
+ // first), then unpinned by lastActivityAt desc (undefined last), createdAt desc,
196
+ // sessionId asc.
129
197
  kept.sort((a, b) => {
198
+ const pa = a.pinned === true;
199
+ const pb = b.pinned === true;
200
+ if (pa !== pb)
201
+ return pa ? -1 : 1; // pinned floats above unpinned
202
+ if (pa && pb) {
203
+ // Both pinned: most-recently-pinned first (undefined pinnedAt sorts last).
204
+ const ta = a.pinnedAt;
205
+ const tb = b.pinnedAt;
206
+ if (ta !== tb) {
207
+ if (ta === undefined)
208
+ return 1;
209
+ if (tb === undefined)
210
+ return -1;
211
+ return tb - ta;
212
+ }
213
+ // tie-break falls through to the activity/createdAt/id rules below.
214
+ }
130
215
  const la = a.lastActivityAt;
131
216
  const lb = b.lastActivityAt;
132
217
  if (la !== lb) {
@@ -150,14 +235,14 @@ export function mergeUnifiedSessions(sources) {
150
235
  return kept;
151
236
  }
152
237
  /**
153
- * Case-insensitive substring filter (name + firstPrompt + workingDir + sessionId)
238
+ * Case-insensitive substring filter (name + firstPrompt + lastPrompt + workingDir + sessionId)
154
239
  * with offset/limit paging. `total` is the filtered count BEFORE paging.
155
240
  */
156
241
  export function filterAndPaginate(items, opts) {
157
242
  const q = (opts.q ?? '').trim().toLowerCase();
158
243
  const filtered = q
159
244
  ? items.filter((it) => {
160
- const hay = [it.name, it.firstPrompt, it.workingDir, it.sessionId]
245
+ const hay = [it.name, it.firstPrompt, it.lastPrompt, it.workingDir, it.sessionId]
161
246
  .filter((v) => typeof v === 'string')
162
247
  .join(' ')
163
248
  .toLowerCase();
@@ -1 +1 @@
1
- {"version":3,"file":"unified-session-service.js","sourceRoot":"","sources":["../../src/services/unified-session-service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"unified-session-service.js","sourceRoot":"","sources":["../../src/services/unified-session-service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * @fileoverview Pure helpers for the global session tab-order (COD-131).
3
+ *
4
+ * Tab order (drag-and-drop reorder + Ctrl+Shift+{/}) is persisted server-side
5
+ * so it follows the user across devices. The server is authoritative; the
6
+ * browser's localStorage (`codeman-session-order`) is the offline fallback.
7
+ *
8
+ * These helpers are pure (no IO) so they can be unit-tested in isolation and
9
+ * reused by both the PUT /api/session-order route and the StateStore accessor.
10
+ *
11
+ * - `normalizeSessionOrder` coerces arbitrary input into a clean string[]
12
+ * (non-empty strings only, deduped with first occurrence winning).
13
+ * - `mergeSessionOrder` lets the pushing device's order win, while preserving
14
+ * any server-only ids the pushing device didn't know about — they fall to the
15
+ * END in their existing relative order, never dropped.
16
+ */
17
+ /**
18
+ * Coerce arbitrary input into a clean ordered list of session ids:
19
+ * keep only non-empty strings and dedup (first occurrence wins).
20
+ *
21
+ * @param order - unknown input (expected to be a string[], but defensive)
22
+ * @returns a normalized string[] (empty array for non-array / all-junk input)
23
+ */
24
+ export declare function normalizeSessionOrder(order: unknown): string[];
25
+ /**
26
+ * Merge an incoming order from a pushing device with the existing server order.
27
+ *
28
+ * The incoming order wins; any ids present in `existing` but NOT in `incoming`
29
+ * are appended at the END, preserving their relative order. This is the
30
+ * "server-only ids the pushing device didn't know about fall to the end, never
31
+ * dropped" rule.
32
+ *
33
+ * Both arguments are normalized first, so callers may pass raw input safely.
34
+ *
35
+ * @param incoming - the order the pushing device wants
36
+ * @param existing - the current server-side order
37
+ * @returns the merged, normalized order
38
+ */
39
+ export declare function mergeSessionOrder(incoming: string[], existing: string[]): string[];
40
+ //# sourceMappingURL=session-order.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-order.d.ts","sourceRoot":"","sources":["../src/session-order.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,EAAE,CAiB9D;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAUlF"}
@@ -0,0 +1,67 @@
1
+ /**
2
+ * @fileoverview Pure helpers for the global session tab-order (COD-131).
3
+ *
4
+ * Tab order (drag-and-drop reorder + Ctrl+Shift+{/}) is persisted server-side
5
+ * so it follows the user across devices. The server is authoritative; the
6
+ * browser's localStorage (`codeman-session-order`) is the offline fallback.
7
+ *
8
+ * These helpers are pure (no IO) so they can be unit-tested in isolation and
9
+ * reused by both the PUT /api/session-order route and the StateStore accessor.
10
+ *
11
+ * - `normalizeSessionOrder` coerces arbitrary input into a clean string[]
12
+ * (non-empty strings only, deduped with first occurrence winning).
13
+ * - `mergeSessionOrder` lets the pushing device's order win, while preserving
14
+ * any server-only ids the pushing device didn't know about — they fall to the
15
+ * END in their existing relative order, never dropped.
16
+ */
17
+ /**
18
+ * Coerce arbitrary input into a clean ordered list of session ids:
19
+ * keep only non-empty strings and dedup (first occurrence wins).
20
+ *
21
+ * @param order - unknown input (expected to be a string[], but defensive)
22
+ * @returns a normalized string[] (empty array for non-array / all-junk input)
23
+ */
24
+ export function normalizeSessionOrder(order) {
25
+ if (!Array.isArray(order)) {
26
+ return [];
27
+ }
28
+ const seen = new Set();
29
+ const result = [];
30
+ for (const entry of order) {
31
+ if (typeof entry !== 'string' || entry.length === 0) {
32
+ continue;
33
+ }
34
+ if (seen.has(entry)) {
35
+ continue;
36
+ }
37
+ seen.add(entry);
38
+ result.push(entry);
39
+ }
40
+ return result;
41
+ }
42
+ /**
43
+ * Merge an incoming order from a pushing device with the existing server order.
44
+ *
45
+ * The incoming order wins; any ids present in `existing` but NOT in `incoming`
46
+ * are appended at the END, preserving their relative order. This is the
47
+ * "server-only ids the pushing device didn't know about fall to the end, never
48
+ * dropped" rule.
49
+ *
50
+ * Both arguments are normalized first, so callers may pass raw input safely.
51
+ *
52
+ * @param incoming - the order the pushing device wants
53
+ * @param existing - the current server-side order
54
+ * @returns the merged, normalized order
55
+ */
56
+ export function mergeSessionOrder(incoming, existing) {
57
+ const normalizedIncoming = normalizeSessionOrder(incoming);
58
+ const incomingSet = new Set(normalizedIncoming);
59
+ const merged = [...normalizedIncoming];
60
+ for (const id of normalizeSessionOrder(existing)) {
61
+ if (!incomingSet.has(id)) {
62
+ merged.push(id);
63
+ }
64
+ }
65
+ return merged;
66
+ }
67
+ //# sourceMappingURL=session-order.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-order.js","sourceRoot":"","sources":["../src/session-order.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH;;;;;;GAMG;AACH,MAAM,UAAU,qBAAqB,CAAC,KAAc;IAClD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,KAAK,MAAM,KAAK,IAAI,KAAK,EAAE,CAAC;QAC1B,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACpD,SAAS;QACX,CAAC;QACD,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;YACpB,SAAS;QACX,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAChB,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACrB,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,iBAAiB,CAAC,QAAkB,EAAE,QAAkB;IACtE,MAAM,kBAAkB,GAAG,qBAAqB,CAAC,QAAQ,CAAC,CAAC;IAC3D,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,kBAAkB,CAAC,CAAC;IAChD,MAAM,MAAM,GAAG,CAAC,GAAG,kBAAkB,CAAC,CAAC;IACvC,KAAK,MAAM,EAAE,IAAI,qBAAqB,CAAC,QAAQ,CAAC,EAAE,CAAC;QACjD,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC;YACzB,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAClB,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}