@luckydraw/cumulus 1.0.43 → 1.0.45

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 (43) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +1 -0
  3. package/dist/gateway/adapters/webchat.d.ts.map +1 -1
  4. package/dist/gateway/adapters/webchat.js +22 -1
  5. package/dist/gateway/adapters/webchat.js.map +1 -1
  6. package/dist/gateway/config.d.ts +8 -6
  7. package/dist/gateway/config.d.ts.map +1 -1
  8. package/dist/gateway/config.js +2 -2
  9. package/dist/gateway/config.js.map +1 -1
  10. package/dist/gateway/jobs.d.ts +6 -0
  11. package/dist/gateway/jobs.d.ts.map +1 -1
  12. package/dist/gateway/jobs.js +12 -0
  13. package/dist/gateway/jobs.js.map +1 -1
  14. package/dist/gateway/one-shot-model.d.ts +23 -8
  15. package/dist/gateway/one-shot-model.d.ts.map +1 -1
  16. package/dist/gateway/one-shot-model.js +104 -26
  17. package/dist/gateway/one-shot-model.js.map +1 -1
  18. package/dist/gateway/server.d.ts +2 -0
  19. package/dist/gateway/server.d.ts.map +1 -1
  20. package/dist/gateway/server.js +108 -0
  21. package/dist/gateway/server.js.map +1 -1
  22. package/dist/gateway/stall-detection.d.ts +9 -3
  23. package/dist/gateway/stall-detection.d.ts.map +1 -1
  24. package/dist/gateway/stall-detection.js +9 -3
  25. package/dist/gateway/stall-detection.js.map +1 -1
  26. package/dist/gateway/static/widget.js +233 -25
  27. package/dist/gateway/thread-rename.d.ts +108 -0
  28. package/dist/gateway/thread-rename.d.ts.map +1 -0
  29. package/dist/gateway/thread-rename.js +208 -0
  30. package/dist/gateway/thread-rename.js.map +1 -0
  31. package/dist/gateway/worker.d.ts +20 -3
  32. package/dist/gateway/worker.d.ts.map +1 -1
  33. package/dist/gateway/worker.js +0 -0
  34. package/dist/gateway/worker.js.map +1 -1
  35. package/dist/lib/content-store.d.ts +11 -0
  36. package/dist/lib/content-store.d.ts.map +1 -1
  37. package/dist/lib/content-store.js +34 -0
  38. package/dist/lib/content-store.js.map +1 -1
  39. package/dist/lib/worktree.d.ts +18 -0
  40. package/dist/lib/worktree.d.ts.map +1 -1
  41. package/dist/lib/worktree.js +32 -0
  42. package/dist/lib/worktree.js.map +1 -1
  43. package/package.json +1 -1
@@ -139,13 +139,12 @@
139
139
  // and re-adding it does not quietly clear the voice setting. The gateway's
140
140
  // resolveVoiceModel treats an unmatched id as "no override" anyway.
141
141
  voiceModel: typeof config.voiceModel === 'string' ? config.voiceModel : '',
142
- // Task 164. Same verbatim rule as voiceModel: an id naming nothing in claudeModels[]
143
- // is preserved on round-trip; the gateway's resolveStallClassifierModel falls back to
144
- // "haiku" for an unmatched id anyway.
142
+ // Task 164. Same verbatim rule as voiceModel: an id naming nothing in the catalog is
143
+ // preserved on round-trip; the gateway reads an unmatched id as NOT CONFIGURED
144
+ // (task 184), which is what the dropdown shows for it too.
145
145
  stallClassifierModel:
146
146
  typeof config.stallClassifierModel === 'string' ? config.stallClassifierModel : '',
147
- // Task 179. Same verbatim rule again; the gateway's resolveGatewayModel falls
148
- // back to "haiku" for an unmatched id.
147
+ // Task 179. Same verbatim rule again, same not-configured reading.
149
148
  workerModel: typeof config.workerModel === 'string' ? config.workerModel : '',
150
149
  };
151
150
  }
@@ -208,11 +207,11 @@
208
207
  // Task 152: '' means "same as thread" and must reach the server as an explicit
209
208
  // null (clear), not as absent — absent would leave the stored value untouched.
210
209
  voiceModel: state.voiceModel ? state.voiceModel : null,
211
- // Task 164: '' means "haiku" and must reach the server as an explicit null (clear),
212
- // not as absent — absent would leave a previously-set classifier model untouched.
210
+ // Task 164: '' means "not configured" and must reach the server as an explicit null
211
+ // (clear), not as absent — absent would leave a previously-set classifier model untouched.
213
212
  stallClassifierModel: state.stallClassifierModel ? state.stallClassifierModel : null,
214
- // Task 179: same explicit-null rule — '' means "haiku", and absent would
215
- // leave a previously-set worker model untouched.
213
+ // Task 179: same explicit-null rule — '' means "not configured", and absent
214
+ // would leave a previously-set worker model untouched.
216
215
  workerModel: state.workerModel ? state.workerModel : null,
217
216
  // Registry travels without credentials — addCredentialPatch fills those
218
217
  // in, so an untouched key round-trips as absent and the server preserves it.
@@ -5653,6 +5652,16 @@
5653
5652
  menu.appendChild(coItem);
5654
5653
  }
5655
5654
 
5655
+ var renameItem = document.createElement('button');
5656
+ renameItem.className = 'cumulus-context-menu-item';
5657
+ renameItem.textContent = 'Rename thread…';
5658
+ renameItem.setAttribute('data-testid', 'webchat-thread-rename');
5659
+ renameItem.addEventListener('click', function () {
5660
+ dismissContextMenu();
5661
+ showThreadRenameDialog(threadName, master);
5662
+ });
5663
+ menu.appendChild(renameItem);
5664
+
5656
5665
  var deleteItem = document.createElement('button');
5657
5666
  deleteItem.className = 'cumulus-context-menu-item danger';
5658
5667
  deleteItem.textContent = 'Delete thread';
@@ -5836,6 +5845,190 @@
5836
5845
  xhr.send(JSON.stringify({ label: label }));
5837
5846
  }
5838
5847
 
5848
+ // ── Thread rename (task 186) ──
5849
+
5850
+ /**
5851
+ * Re-key every piece of client state that names a thread. One function so
5852
+ * a `thread_renamed` from the server and a rename this tab just made take
5853
+ * the same path; a list missed here is a sidebar row that 404s on click.
5854
+ */
5855
+ function applyThreadRename(from, to) {
5856
+ for (var i = 0; i < allThreads.length; i++) {
5857
+ if (allThreads[i].name === from) allThreads[i].name = to;
5858
+ if (allThreads[i].master === from) allThreads[i].master = to;
5859
+ }
5860
+ if (threadStates[from]) {
5861
+ threadStates[to] = threadStates[from];
5862
+ delete threadStates[from];
5863
+ }
5864
+ var vi = visibleThreads.indexOf(from);
5865
+ if (vi !== -1) visibleThreads[vi] = to;
5866
+ if (busyThreads[from]) {
5867
+ busyThreads[to] = true;
5868
+ delete busyThreads[from];
5869
+ }
5870
+ if (unreadThreads[from]) {
5871
+ unreadThreads[to] = unreadThreads[from];
5872
+ delete unreadThreads[from];
5873
+ }
5874
+ if (collapsedMasters[from]) {
5875
+ collapsedMasters[to] = true;
5876
+ delete collapsedMasters[from];
5877
+ try {
5878
+ localStorage.setItem('cumulus-collapsed-masters', JSON.stringify(collapsedMasters));
5879
+ } catch {
5880
+ /* storage unavailable — the collapse state is cosmetic */
5881
+ }
5882
+ }
5883
+ }
5884
+
5885
+ function renameThread(from, to, cb) {
5886
+ var xhr = new XMLHttpRequest();
5887
+ xhr.open('POST', '/api/thread/' + encodeURIComponent(from) + '/rename');
5888
+ xhr.setRequestHeader('Content-Type', 'application/json');
5889
+ xhr.setRequestHeader('X-API-Key', activeApiKey);
5890
+ xhr.onload = function () {
5891
+ var body = {};
5892
+ try {
5893
+ body = JSON.parse(xhr.responseText || '{}');
5894
+ } catch {
5895
+ /* fall through to the status-based message below */
5896
+ }
5897
+ if (xhr.status === 200) {
5898
+ cb(null, body);
5899
+ return;
5900
+ }
5901
+ cb(body.error || 'Could not rename the thread (' + xhr.status + ').');
5902
+ };
5903
+ xhr.onerror = function () {
5904
+ cb('Could not reach the gateway.');
5905
+ };
5906
+ xhr.send(JSON.stringify({ name: to }));
5907
+ }
5908
+
5909
+ function showThreadRenameDialog(threadName, master) {
5910
+ var existing = document.querySelector('.cumulus-confirm-dialog[data-context="rename"]');
5911
+ if (existing) existing.remove();
5912
+
5913
+ // A co-thread keeps its master's prefix (the server enforces it); the
5914
+ // dialog shows the prefix as fixed text and edits only the label.
5915
+ var prefix = master ? master + '-' : '';
5916
+ var current = threadName.slice(prefix.length);
5917
+
5918
+ var dialog = document.createElement('div');
5919
+ dialog.className = 'cumulus-confirm-dialog';
5920
+ dialog.setAttribute('data-context', 'rename');
5921
+ dialog.style.cssText =
5922
+ 'position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.6); z-index: 200; display: flex; align-items: center; justify-content: center;';
5923
+
5924
+ var box = document.createElement('div');
5925
+ box.className = 'cumulus-confirm-dialog-box';
5926
+
5927
+ var title = document.createElement('div');
5928
+ title.className = 'cumulus-confirm-dialog-title';
5929
+ title.textContent = master ? 'Rename co-thread' : 'Rename thread';
5930
+ box.appendChild(title);
5931
+
5932
+ var text = document.createElement('div');
5933
+ text.className = 'cumulus-confirm-dialog-text';
5934
+ text.textContent = master
5935
+ ? 'Keeps "' + master + '-" so it stays a co-thread of ' + master + '.'
5936
+ : 'Everything the thread has — history, memory, settings, jobs, schedules — moves with it.';
5937
+ box.appendChild(text);
5938
+
5939
+ var input = document.createElement('input');
5940
+ input.type = 'text';
5941
+ input.className = 'cumulus-co-thread-input';
5942
+ input.value = current;
5943
+ input.setAttribute('data-testid', 'webchat-thread-rename-input');
5944
+ box.appendChild(input);
5945
+
5946
+ var preview = document.createElement('div');
5947
+ preview.className = 'cumulus-confirm-dialog-warn';
5948
+ preview.setAttribute('data-testid', 'webchat-thread-rename-preview');
5949
+ box.appendChild(preview);
5950
+
5951
+ // Mirrors THREAD_NAME_RE / CO_THREAD_LABEL_RE in thread-rename.ts. The
5952
+ // server is authoritative; this only turns a 400 into a message written
5953
+ // before the request is sent.
5954
+ var NAME_OK = master ? /^[a-zA-Z0-9_]+$/ : /^[a-zA-Z0-9_-]+$/;
5955
+ function refreshPreview() {
5956
+ var value = input.value.trim();
5957
+ if (!value) {
5958
+ preview.textContent = master
5959
+ ? 'Letters, numbers and underscores.'
5960
+ : 'Letters, numbers, hyphens and underscores.';
5961
+ return false;
5962
+ }
5963
+ if (!NAME_OK.test(value)) {
5964
+ preview.textContent = master
5965
+ ? 'Letters, numbers and underscores only — no spaces or hyphens.'
5966
+ : 'Letters, numbers, hyphens and underscores only — no spaces.';
5967
+ return false;
5968
+ }
5969
+ if (prefix + value === threadName) {
5970
+ preview.textContent = 'That is the current name.';
5971
+ return false;
5972
+ }
5973
+ preview.textContent = 'Renames to: ' + prefix + value;
5974
+ return true;
5975
+ }
5976
+ refreshPreview();
5977
+ input.addEventListener('input', refreshPreview);
5978
+
5979
+ var actions = document.createElement('div');
5980
+ actions.className = 'cumulus-confirm-dialog-actions';
5981
+
5982
+ var cancelBtn = document.createElement('button');
5983
+ cancelBtn.className = 'cumulus-confirm-dialog-cancel';
5984
+ cancelBtn.textContent = 'Cancel';
5985
+ cancelBtn.addEventListener('click', function () {
5986
+ dialog.remove();
5987
+ });
5988
+ actions.appendChild(cancelBtn);
5989
+
5990
+ var renameBtn = document.createElement('button');
5991
+ renameBtn.className = 'cumulus-confirm-dialog-confirm';
5992
+ renameBtn.setAttribute('data-testid', 'webchat-thread-rename-confirm');
5993
+ renameBtn.textContent = 'Rename';
5994
+ function submit() {
5995
+ if (!refreshPreview()) return;
5996
+ renameBtn.disabled = true;
5997
+ renameBtn.textContent = 'Renaming…';
5998
+ var to = prefix + input.value.trim();
5999
+ renameThread(threadName, to, function (err) {
6000
+ if (err) {
6001
+ renameBtn.disabled = false;
6002
+ renameBtn.textContent = 'Rename';
6003
+ preview.textContent = err;
6004
+ return;
6005
+ }
6006
+ dialog.remove();
6007
+ // The server also broadcasts thread_renamed to every tab, this one
6008
+ // included; applying it here too means no flash of the old name.
6009
+ applyThreadRename(threadName, to);
6010
+ renderSidebar();
6011
+ renderContentArea();
6012
+ if (connection) connection.send({ type: 'threads' });
6013
+ });
6014
+ }
6015
+ renameBtn.addEventListener('click', submit);
6016
+ input.addEventListener('keydown', function (e) {
6017
+ if (e.key === 'Enter') submit();
6018
+ if (e.key === 'Escape') dialog.remove();
6019
+ });
6020
+ actions.appendChild(renameBtn);
6021
+
6022
+ box.appendChild(actions);
6023
+ dialog.appendChild(box);
6024
+ dialog.addEventListener('click', function (e) {
6025
+ if (e.target === dialog) dialog.remove();
6026
+ });
6027
+ document.body.appendChild(dialog);
6028
+ input.focus();
6029
+ input.select();
6030
+ }
6031
+
5839
6032
  // ── Thread delete confirmation ──
5840
6033
  function showThreadDeleteConfirm(threadName) {
5841
6034
  // Remove any existing confirm dialogs
@@ -6679,10 +6872,10 @@
6679
6872
  voiceCard.appendChild(voiceSelect);
6680
6873
  body.appendChild(voiceCard);
6681
6874
 
6682
- // Task 164: the stall-detection classifier is a one-off `claude --print --model <id>`
6683
- // call, so it can only run on a Claude CLI sub-model this dropdown lists ONLY the
6684
- // Anthropic sub-models (not the full catalog), with "Haiku (default)" clearing the
6685
- // setting. Same card shape as the voice model above.
6875
+ // Task 164/165: the stall-detection classifier runs on any catalog model, so this
6876
+ // dropdown lists the full catalog with a leading "Not configured" option that clears
6877
+ // the setting and clearing it really does turn the feature off (task 184). Same
6878
+ // card shape as the voice model above.
6686
6879
  var stallCard = document.createElement('div');
6687
6880
  stallCard.className = 'cumulus-settings-default-card';
6688
6881
  var stallCardLabel = document.createElement('label');
@@ -7341,27 +7534,31 @@
7341
7534
 
7342
7535
  // Task 165: the classifier runs on ANY catalog model (a Claude CLI sub-model or a
7343
7536
  // direct-provider model), so this lists the FULL catalog grouped by provider — same
7344
- // shape as the voice selector — plus a leading "Haiku (default)" clear option. A
7345
- // stored id naming nothing in either catalog falls back to "Haiku (default)" in the
7346
- // UI which is what the gateway does with it too (resolveStallClassifierModel "haiku").
7537
+ // shape as the voice selector — plus a leading "Not configured" clear option.
7538
+ //
7539
+ // Task 184: that option used to read "Haiku (default)", which was a claim about a
7540
+ // model in no catalog that the operator never chose and that a fresh install has no
7541
+ // way to run. Empty now means empty, on the screen and in the gateway alike, so the
7542
+ // label says what the feature actually does when nothing is picked. A stored id
7543
+ // naming nothing in either catalog lands here too — the gateway reads it the same way.
7347
7544
  function renderStallSelect() {
7348
7545
  stallSelect.innerHTML = '';
7349
- var haiku = document.createElement('option');
7350
- haiku.value = '';
7351
- haiku.textContent = 'Haiku (default)';
7352
- stallSelect.appendChild(haiku);
7546
+ var unset = document.createElement('option');
7547
+ unset.value = '';
7548
+ unset.textContent = 'Not configured — no stall detection';
7549
+ stallSelect.appendChild(unset);
7353
7550
  fillModelOptions(stallSelect);
7354
7551
  stallSelect.value = gwState.stallClassifierModel || '';
7355
7552
  if (stallSelect.value !== (gwState.stallClassifierModel || '')) stallSelect.value = '';
7356
7553
  }
7357
7554
 
7358
- // Task 179 — same rule, same fallback, its own setting.
7555
+ // Task 179 — same rule, same not-configured reading, its own setting.
7359
7556
  function renderWorkerSelect() {
7360
7557
  workerSelect.innerHTML = '';
7361
- var workerHaiku = document.createElement('option');
7362
- workerHaiku.value = '';
7363
- workerHaiku.textContent = 'Haiku (default)';
7364
- workerSelect.appendChild(workerHaiku);
7558
+ var workerUnset = document.createElement('option');
7559
+ workerUnset.value = '';
7560
+ workerUnset.textContent = 'Not configured — Refresh unavailable';
7561
+ workerSelect.appendChild(workerUnset);
7365
7562
  fillModelOptions(workerSelect);
7366
7563
  workerSelect.value = gwState.workerModel || '';
7367
7564
  if (workerSelect.value !== (gwState.workerModel || '')) workerSelect.value = '';
@@ -10592,6 +10789,17 @@
10592
10789
  }
10593
10790
  break;
10594
10791
 
10792
+ case 'thread_renamed':
10793
+ // Task 186 — re-key this tab's state, then ask for the listing, which
10794
+ // is what carries `master` and the per-thread counts.
10795
+ if (data.from && data.to) {
10796
+ applyThreadRename(data.from, data.to);
10797
+ renderSidebar();
10798
+ renderContentArea();
10799
+ if (connection) connection.send({ type: 'threads' });
10800
+ }
10801
+ break;
10802
+
10595
10803
  case 'thread_deleted':
10596
10804
  // Server confirms thread deleted; remove from sidebar and panels
10597
10805
  if (data.threadName) {
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Task 186 — rename a thread or a co-thread.
3
+ *
4
+ * The thread's NAME is the key of every artefact the gateway keeps about it
5
+ * (task 185 lists them; the task doc for 186 repeats the list). So a rename is
6
+ * not a field update: it is one migration that moves every name-keyed row at
7
+ * once, and this module is the only place that migration is written. A second
8
+ * caller — an MCP tool, the CLI — calls `executeThreadRename`; there is no
9
+ * other path, because a path that moved most of the rows would leave a thread
10
+ * that half-exists under two names (Rule #8).
11
+ *
12
+ * Two phases, deliberately separate:
13
+ *
14
+ * 1. `planThreadRename` — validates and computes every move, including the
15
+ * cascade when a master is renamed. Pure apart from the context callbacks,
16
+ * so the six rules it enforces are testable without a filesystem, and so
17
+ * nothing moves until the WHOLE plan is legal.
18
+ * 2. `executeThreadRename` — moves files, rewrites the master's `coThreads`,
19
+ * moves worktrees, appends corrected attribution lines. The caller (server)
20
+ * then re-keys its own in-memory maps and tells the sockets.
21
+ */
22
+ import type { NamespaceConfig } from './config.js';
23
+ /** The alphabet `create_thread` sanitises to. A rename refuses instead of sanitising. */
24
+ export declare const THREAD_NAME_RE: RegExp;
25
+ /** Mirrors CO_THREAD_LABEL in server.ts: the part of a co-thread's name after `<master>-`. */
26
+ export declare const CO_THREAD_LABEL_RE: RegExp;
27
+ /**
28
+ * Every per-thread file or directory under the threads dir, by extension. The
29
+ * same list `handleDeleteThread` walks, plus the streaming scratch file. A
30
+ * rename that missed one would leave an orphan that reattaches to nothing.
31
+ */
32
+ export declare const THREAD_FILE_EXTENSIONS: readonly [".jsonl", ".embeddings.json", ".embeddings.bin", ".segments.json", ".adaptive.json", ".config.json", ".sessions", ".content", ".prompts", ".streaming.tmp"];
33
+ /** A refused plan. `status` is the HTTP status the endpoint answers with. */
34
+ export declare class ThreadRenameError extends Error {
35
+ readonly status: number;
36
+ constructor(status: number, message: string);
37
+ }
38
+ export interface RenameMove {
39
+ from: string;
40
+ to: string;
41
+ }
42
+ export interface ThreadRenamePlan {
43
+ from: string;
44
+ to: string;
45
+ /** Set when the renamed thread is a co-thread: whose list to rewrite. */
46
+ coThreadMaster?: string;
47
+ /** Cascaded co-thread renames when a master is renamed (empty otherwise). */
48
+ coThreads: RenameMove[];
49
+ /** Every move, the thread itself first. */
50
+ moves: RenameMove[];
51
+ }
52
+ export interface PlanContext {
53
+ namespaces: NamespaceConfig[];
54
+ /** Any artefact under any extension exists for this name. */
55
+ threadExists(name: string): boolean;
56
+ /** The thread is mid-turn right now. */
57
+ threadBusy(name: string): boolean;
58
+ /** Master of a registered co-thread, else undefined. */
59
+ coThreadMasterOf(name: string): string | undefined;
60
+ /** The master's registered co-thread list (its EXACT config). */
61
+ coThreadsOf(master: string): Promise<string[]>;
62
+ }
63
+ /**
64
+ * Validate a rename and compute every move it implies.
65
+ *
66
+ * Rules, in the order they are checked (each one's failure names it):
67
+ * 1. `from` exists.
68
+ * 2. `to` has the thread-name shape and differs from `from`.
69
+ * 3. Neither name is a namespace base, and both fall in the same namespace.
70
+ * 4. A co-thread keeps `<master>-` and its new label has the label shape.
71
+ * 5. A master's co-threads cascade: `<from>-x` → `<to>-x`.
72
+ * 6. No target name exists; no source is mid-turn. Checked for the WHOLE
73
+ * cascade before anything is reported legal.
74
+ */
75
+ export declare function planThreadRename(from: string, to: string, ctx: PlanContext): Promise<ThreadRenamePlan>;
76
+ export interface ExecuteContext {
77
+ threadsDir: string;
78
+ /** The project directory a master's worktrees hang off. */
79
+ projectDirFor(master: string): Promise<string | undefined>;
80
+ }
81
+ export interface ThreadRenameResult {
82
+ from: string;
83
+ to: string;
84
+ coThreads: RenameMove[];
85
+ /** Extensions actually moved, per thread. */
86
+ moved: Record<string, string[]>;
87
+ /** Attribution entries rewritten in the affected content store. */
88
+ attributionUpdated: number;
89
+ /** Worktree outcomes for every co-thread move. */
90
+ worktrees: Array<RenameMove & {
91
+ moved: boolean;
92
+ detail?: string;
93
+ }>;
94
+ }
95
+ /** Move every existing artefact of `from` to `to`. Returns the extensions moved. */
96
+ export declare function renameThreadFiles(threadsDir: string, from: string, to: string): string[];
97
+ /**
98
+ * Carry out a legal plan. Order matters:
99
+ *
100
+ * - The project dir is read BEFORE the files move: for a master it lives in
101
+ * the config file that is about to change name.
102
+ * - Files move before the `coThreads` rewrite, because for a master the list
103
+ * is written into the config under its NEW name.
104
+ * - Worktrees move from the master's checkout, which does not move.
105
+ * - Attribution is appended last, into the store under its new path.
106
+ */
107
+ export declare function executeThreadRename(plan: ThreadRenamePlan, ctx: ExecuteContext): Promise<ThreadRenameResult>;
108
+ //# sourceMappingURL=thread-rename.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"thread-rename.d.ts","sourceRoot":"","sources":["../../src/gateway/thread-rename.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AASH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAGnD,yFAAyF;AACzF,eAAO,MAAM,cAAc,QAAqB,CAAC;AACjD,8FAA8F;AAC9F,eAAO,MAAM,kBAAkB,QAAoB,CAAC;AAEpD;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,uKAWzB,CAAC;AAEX,6EAA6E;AAC7E,qBAAa,iBAAkB,SAAQ,KAAK;aAExB,MAAM,EAAE,MAAM;gBAAd,MAAM,EAAE,MAAM,EAC9B,OAAO,EAAE,MAAM;CAKlB;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,yEAAyE;IACzE,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,6EAA6E;IAC7E,SAAS,EAAE,UAAU,EAAE,CAAC;IACxB,2CAA2C;IAC3C,KAAK,EAAE,UAAU,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,eAAe,EAAE,CAAC;IAC9B,6DAA6D;IAC7D,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IACpC,wCAAwC;IACxC,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAClC,wDAAwD;IACxD,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IACnD,iEAAiE;IACjE,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;CAChD;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,EACV,GAAG,EAAE,WAAW,GACf,OAAO,CAAC,gBAAgB,CAAC,CAuB3B;AA4ED,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,2DAA2D;IAC3D,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;CAC5D;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,UAAU,EAAE,CAAC;IACxB,6CAA6C;IAC7C,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAChC,mEAAmE;IACnE,kBAAkB,EAAE,MAAM,CAAC;IAC3B,kDAAkD;IAClD,SAAS,EAAE,KAAK,CAAC,UAAU,GAAG;QAAE,KAAK,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACpE;AAED,oFAAoF;AACpF,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,EAAE,CASxF;AAED;;;;;;;;;GASG;AACH,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,gBAAgB,EACtB,GAAG,EAAE,cAAc,GAClB,OAAO,CAAC,kBAAkB,CAAC,CA4C7B"}
@@ -0,0 +1,208 @@
1
+ /**
2
+ * Task 186 — rename a thread or a co-thread.
3
+ *
4
+ * The thread's NAME is the key of every artefact the gateway keeps about it
5
+ * (task 185 lists them; the task doc for 186 repeats the list). So a rename is
6
+ * not a field update: it is one migration that moves every name-keyed row at
7
+ * once, and this module is the only place that migration is written. A second
8
+ * caller — an MCP tool, the CLI — calls `executeThreadRename`; there is no
9
+ * other path, because a path that moved most of the rows would leave a thread
10
+ * that half-exists under two names (Rule #8).
11
+ *
12
+ * Two phases, deliberately separate:
13
+ *
14
+ * 1. `planThreadRename` — validates and computes every move, including the
15
+ * cascade when a master is renamed. Pure apart from the context callbacks,
16
+ * so the six rules it enforces are testable without a filesystem, and so
17
+ * nothing moves until the WHOLE plan is legal.
18
+ * 2. `executeThreadRename` — moves files, rewrites the master's `coThreads`,
19
+ * moves worktrees, appends corrected attribution lines. The caller (server)
20
+ * then re-keys its own in-memory maps and tells the sockets.
21
+ */
22
+ import * as fs from 'fs';
23
+ import * as path from 'path';
24
+ import { loadThreadConfigExact, saveThreadConfig } from '../lib/config.js';
25
+ import { ContentStore } from '../lib/content-store.js';
26
+ import { moveCoThreadWorktree } from '../lib/worktree.js';
27
+ import { namespaceForThread } from './namespaces.js';
28
+ /** The alphabet `create_thread` sanitises to. A rename refuses instead of sanitising. */
29
+ export const THREAD_NAME_RE = /^[a-zA-Z0-9_-]+$/;
30
+ /** Mirrors CO_THREAD_LABEL in server.ts: the part of a co-thread's name after `<master>-`. */
31
+ export const CO_THREAD_LABEL_RE = /^[a-zA-Z0-9_]+$/;
32
+ /**
33
+ * Every per-thread file or directory under the threads dir, by extension. The
34
+ * same list `handleDeleteThread` walks, plus the streaming scratch file. A
35
+ * rename that missed one would leave an orphan that reattaches to nothing.
36
+ */
37
+ export const THREAD_FILE_EXTENSIONS = [
38
+ '.jsonl',
39
+ '.embeddings.json',
40
+ '.embeddings.bin',
41
+ '.segments.json',
42
+ '.adaptive.json',
43
+ '.config.json',
44
+ '.sessions',
45
+ '.content',
46
+ '.prompts',
47
+ '.streaming.tmp',
48
+ ];
49
+ /** A refused plan. `status` is the HTTP status the endpoint answers with. */
50
+ export class ThreadRenameError extends Error {
51
+ status;
52
+ constructor(status, message) {
53
+ super(message);
54
+ this.status = status;
55
+ this.name = 'ThreadRenameError';
56
+ }
57
+ }
58
+ /**
59
+ * Validate a rename and compute every move it implies.
60
+ *
61
+ * Rules, in the order they are checked (each one's failure names it):
62
+ * 1. `from` exists.
63
+ * 2. `to` has the thread-name shape and differs from `from`.
64
+ * 3. Neither name is a namespace base, and both fall in the same namespace.
65
+ * 4. A co-thread keeps `<master>-` and its new label has the label shape.
66
+ * 5. A master's co-threads cascade: `<from>-x` → `<to>-x`.
67
+ * 6. No target name exists; no source is mid-turn. Checked for the WHOLE
68
+ * cascade before anything is reported legal.
69
+ */
70
+ export async function planThreadRename(from, to, ctx) {
71
+ if (!ctx.threadExists(from)) {
72
+ throw new ThreadRenameError(404, `Thread "${from}" does not exist.`);
73
+ }
74
+ if (!THREAD_NAME_RE.test(to)) {
75
+ throw new ThreadRenameError(400, 'A thread name is letters, numbers, hyphens and underscores only (no spaces or slashes).');
76
+ }
77
+ if (to === from) {
78
+ throw new ThreadRenameError(400, `"${from}" is already this thread's name.`);
79
+ }
80
+ assertNamespacePreserved(from, to, ctx.namespaces);
81
+ const coThreadMaster = ctx.coThreadMasterOf(from);
82
+ if (coThreadMaster)
83
+ assertCoThreadShape(from, to, coThreadMaster);
84
+ const coThreads = coThreadMaster ? [] : cascadeMoves(from, to, await ctx.coThreadsOf(from));
85
+ const moves = [{ from, to }, ...coThreads];
86
+ assertMovesLegal(from, moves, ctx);
87
+ return { from, to, ...(coThreadMaster ? { coThreadMaster } : {}), coThreads, moves };
88
+ }
89
+ /**
90
+ * Rule 3 — a namespace is a thread-name prefix and a capability boundary
91
+ * (task 097): a rename must not move a thread across one, and the base
92
+ * thread's name IS the namespace.
93
+ */
94
+ function assertNamespacePreserved(from, to, namespaces) {
95
+ const nsBases = new Set(namespaces.map(ns => ns.name));
96
+ if (nsBases.has(from)) {
97
+ throw new ThreadRenameError(400, `"${from}" is the base thread of a namespace; its name is the namespace and cannot change.`);
98
+ }
99
+ if (nsBases.has(to)) {
100
+ throw new ThreadRenameError(400, `"${to}" is the name of a namespace.`);
101
+ }
102
+ const nsFrom = namespaceForThread(from, namespaces);
103
+ const nsTo = namespaceForThread(to, namespaces);
104
+ if (nsFrom !== nsTo) {
105
+ throw new ThreadRenameError(400, nsFrom
106
+ ? `"${from}" belongs to the "${nsFrom}" namespace, so its new name must keep the "${nsFrom}-" prefix.`
107
+ : `"${to}" would move the thread into the "${nsTo}" namespace.`);
108
+ }
109
+ }
110
+ /**
111
+ * Rule 4 — a co-thread resolves to its master by prefix chain plus
112
+ * registration. A name that stops starting with `<master>-` loses the shared
113
+ * store and the worktree in one stroke.
114
+ */
115
+ function assertCoThreadShape(from, to, master) {
116
+ const prefix = `${master}-`;
117
+ const label = to.startsWith(prefix) ? to.slice(prefix.length) : '';
118
+ if (!label || !CO_THREAD_LABEL_RE.test(label)) {
119
+ throw new ThreadRenameError(400, `"${from}" is a co-thread of "${master}", so its new name must be "${prefix}<label>" with a label of letters, numbers or underscores.`);
120
+ }
121
+ }
122
+ /** Rule 5 — a master's registered co-threads follow it: `<from>-x` → `<to>-x`. */
123
+ function cascadeMoves(from, to, registered) {
124
+ return registered.map(name => {
125
+ if (!name.startsWith(`${from}-`)) {
126
+ throw new ThreadRenameError(409, `Registered co-thread "${name}" does not carry the "${from}-" prefix; fix the registration before renaming.`);
127
+ }
128
+ return { from: name, to: `${to}${name.slice(from.length)}` };
129
+ });
130
+ }
131
+ /** Rule 6 — over the WHOLE plan, before anything is reported legal. */
132
+ function assertMovesLegal(from, moves, ctx) {
133
+ for (const move of moves) {
134
+ if (ctx.threadExists(move.to)) {
135
+ throw new ThreadRenameError(409, `A thread named "${move.to}" already exists.`);
136
+ }
137
+ if (ctx.threadBusy(move.from)) {
138
+ throw new ThreadRenameError(409, move.from === from
139
+ ? `"${from}" is running a turn; rename it once it is idle.`
140
+ : `Co-thread "${move.from}" is running a turn; rename once it is idle.`);
141
+ }
142
+ }
143
+ }
144
+ /** Move every existing artefact of `from` to `to`. Returns the extensions moved. */
145
+ export function renameThreadFiles(threadsDir, from, to) {
146
+ const moved = [];
147
+ for (const ext of THREAD_FILE_EXTENSIONS) {
148
+ const source = path.join(threadsDir, `${from}${ext}`);
149
+ if (!fs.existsSync(source))
150
+ continue;
151
+ fs.renameSync(source, path.join(threadsDir, `${to}${ext}`));
152
+ moved.push(ext);
153
+ }
154
+ return moved;
155
+ }
156
+ /**
157
+ * Carry out a legal plan. Order matters:
158
+ *
159
+ * - The project dir is read BEFORE the files move: for a master it lives in
160
+ * the config file that is about to change name.
161
+ * - Files move before the `coThreads` rewrite, because for a master the list
162
+ * is written into the config under its NEW name.
163
+ * - Worktrees move from the master's checkout, which does not move.
164
+ * - Attribution is appended last, into the store under its new path.
165
+ */
166
+ export async function executeThreadRename(plan, ctx) {
167
+ const master = plan.coThreadMaster ?? plan.from;
168
+ const masterDir = await ctx.projectDirFor(master);
169
+ const moved = {};
170
+ for (const move of plan.moves) {
171
+ moved[move.from] = renameThreadFiles(ctx.threadsDir, move.from, move.to);
172
+ }
173
+ if (plan.coThreadMaster) {
174
+ const config = await loadThreadConfigExact(plan.coThreadMaster);
175
+ await saveThreadConfig(plan.coThreadMaster, {
176
+ coThreads: (config.coThreads ?? []).map(n => (n === plan.from ? plan.to : n)),
177
+ });
178
+ }
179
+ else if (plan.coThreads.length > 0) {
180
+ await saveThreadConfig(plan.to, { coThreads: plan.coThreads.map(m => m.to) });
181
+ }
182
+ const worktrees = [];
183
+ const coMoves = plan.coThreadMaster ? [{ from: plan.from, to: plan.to }] : plan.coThreads;
184
+ for (const move of coMoves) {
185
+ const outcome = await moveCoThreadWorktree(move.from, move.to, masterDir);
186
+ worktrees.push({ ...move, ...outcome });
187
+ }
188
+ // The store that carries this thread's tags: its own for a plain thread or a
189
+ // master (now under the new name), the master's for a co-thread.
190
+ const storeOwner = plan.coThreadMaster ?? plan.to;
191
+ const storePath = path.join(ctx.threadsDir, `${storeOwner}.content`);
192
+ let attributionUpdated = 0;
193
+ if (fs.existsSync(storePath)) {
194
+ const mapping = {};
195
+ for (const move of plan.moves)
196
+ mapping[move.from] = move.to;
197
+ attributionUpdated = await new ContentStore(storePath).renameThreads(mapping);
198
+ }
199
+ return {
200
+ from: plan.from,
201
+ to: plan.to,
202
+ coThreads: plan.coThreads,
203
+ moved,
204
+ attributionUpdated,
205
+ worktrees,
206
+ };
207
+ }
208
+ //# sourceMappingURL=thread-rename.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"thread-rename.js","sourceRoot":"","sources":["../../src/gateway/thread-rename.ts"],"names":[],"mappings":"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"}