@luckydraw/cumulus 1.0.38 → 1.0.40
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.
- package/CHANGELOG.md +22 -0
- package/dist/gateway/adapters/webchat.d.ts +3 -0
- package/dist/gateway/adapters/webchat.d.ts.map +1 -1
- package/dist/gateway/adapters/webchat.js +84 -1
- package/dist/gateway/adapters/webchat.js.map +1 -1
- package/dist/gateway/config.d.ts +16 -2
- package/dist/gateway/config.d.ts.map +1 -1
- package/dist/gateway/config.js +12 -6
- package/dist/gateway/config.js.map +1 -1
- package/dist/gateway/daemon.d.ts +1 -0
- package/dist/gateway/daemon.d.ts.map +1 -1
- package/dist/gateway/daemon.js +4 -0
- package/dist/gateway/daemon.js.map +1 -1
- package/dist/gateway/one-shot-model.d.ts +92 -0
- package/dist/gateway/one-shot-model.d.ts.map +1 -0
- package/dist/gateway/one-shot-model.js +191 -0
- package/dist/gateway/one-shot-model.js.map +1 -0
- package/dist/gateway/server.d.ts +3 -0
- package/dist/gateway/server.d.ts.map +1 -1
- package/dist/gateway/server.js +4 -0
- package/dist/gateway/server.js.map +1 -1
- package/dist/gateway/stall-detection.d.ts +19 -50
- package/dist/gateway/stall-detection.d.ts.map +1 -1
- package/dist/gateway/stall-detection.js +27 -190
- package/dist/gateway/stall-detection.js.map +1 -1
- package/dist/gateway/static/widget.js +342 -35
- package/dist/gateway/tasks-refresh.d.ts +89 -0
- package/dist/gateway/tasks-refresh.d.ts.map +1 -0
- package/dist/gateway/tasks-refresh.js +224 -0
- package/dist/gateway/tasks-refresh.js.map +1 -0
- package/dist/gateway/tasks.d.ts +21 -1
- package/dist/gateway/tasks.d.ts.map +1 -1
- package/dist/gateway/tasks.js +62 -10
- package/dist/gateway/tasks.js.map +1 -1
- package/dist/gateway/worker.d.ts +86 -0
- package/dist/gateway/worker.d.ts.map +1 -0
- package/dist/gateway/worker.js +0 -0
- package/dist/gateway/worker.js.map +1 -0
- package/dist/lib/tasks-file.d.ts +23 -0
- package/dist/lib/tasks-file.d.ts.map +1 -1
- package/dist/lib/tasks-file.js +49 -0
- package/dist/lib/tasks-file.js.map +1 -1
- package/dist/lib/tasks-format.d.ts.map +1 -1
- package/dist/lib/tasks-format.js +12 -5
- package/dist/lib/tasks-format.js.map +1 -1
- package/package.json +1 -1
|
@@ -144,6 +144,9 @@
|
|
|
144
144
|
// "haiku" for an unmatched id anyway.
|
|
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.
|
|
149
|
+
workerModel: typeof config.workerModel === 'string' ? config.workerModel : '',
|
|
147
150
|
};
|
|
148
151
|
}
|
|
149
152
|
|
|
@@ -208,6 +211,9 @@
|
|
|
208
211
|
// Task 164: '' means "haiku" and must reach the server as an explicit null (clear),
|
|
209
212
|
// not as absent — absent would leave a previously-set classifier model untouched.
|
|
210
213
|
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.
|
|
216
|
+
workerModel: state.workerModel ? state.workerModel : null,
|
|
211
217
|
// Registry travels without credentials — addCredentialPatch fills those
|
|
212
218
|
// in, so an untouched key round-trips as absent and the server preserves it.
|
|
213
219
|
customProviders: (Array.isArray(state.customProviders) ? state.customProviders : []).map(
|
|
@@ -1975,17 +1981,55 @@
|
|
|
1975
1981
|
'}',
|
|
1976
1982
|
'.cumulus-task-dep button:hover { border-color: #666; color: #eee; }',
|
|
1977
1983
|
|
|
1978
|
-
/*
|
|
1984
|
+
/* Toolbar — task 178 put the selection actions above the list; task 179 gave
|
|
1985
|
+
the bar something to show at zero selected, so it is now always present. */
|
|
1979
1986
|
'.cumulus-tasks-selbar {',
|
|
1980
|
-
' display: flex; align-items: center; gap:
|
|
1981
|
-
' padding: 0.
|
|
1987
|
+
' display: flex; align-items: center; gap: 6px; flex-shrink: 0;',
|
|
1988
|
+
' padding: 0.45em 0.6em 0.45em 0.85em; background: #2d2d2d;',
|
|
1989
|
+
' border-bottom: 1px solid #3a3a3a;',
|
|
1990
|
+
'}',
|
|
1991
|
+
'.cumulus-tasks-selcount { font-size: 11.5px; color: #aaa; margin-right: auto; }',
|
|
1992
|
+
'.cumulus-tasks-refresh-note { font-size: 11.5px; color: #888; margin-right: auto; }',
|
|
1993
|
+
'.cumulus-tasks-action--refresh:disabled { opacity: 0.55; cursor: default; }',
|
|
1994
|
+
'.cumulus-tasks-action {',
|
|
1995
|
+
' background: #2a2a2a; border: 1px solid #3f3f3f; color: #ddd;',
|
|
1996
|
+
' border-radius: 4px; padding: 0.28em 0.6em; cursor: pointer; font-size: 11px;',
|
|
1997
|
+
' white-space: nowrap;',
|
|
1998
|
+
'}',
|
|
1999
|
+
'.cumulus-tasks-action:hover { border-color: #666; color: #fff; }',
|
|
2000
|
+
'.cumulus-tasks-action--closed { background: #2f4a35; border-color: #3f5f45; }',
|
|
2001
|
+
'.cumulus-tasks-action--archive { background: #3a3320; border-color: #55492c; }',
|
|
2002
|
+
'.cumulus-tasks-selclear {',
|
|
2003
|
+
' background: none; border: none; color: #777; cursor: pointer;',
|
|
2004
|
+
' font-size: 15px; line-height: 1; padding: 2px 3px;',
|
|
2005
|
+
'}',
|
|
2006
|
+
'.cumulus-tasks-selclear:hover { color: #e0e0e0; }',
|
|
2007
|
+
|
|
2008
|
+
/* Inline comment editor — the drawer never opens the floating popover */
|
|
2009
|
+
'.cumulus-tasks-comment {',
|
|
2010
|
+
' background: #232323; border: 1px solid #3a3a3a; border-radius: 5px;',
|
|
2011
|
+
' margin: 4px 6px; padding: 0.5em 0.6em; flex-shrink: 0;',
|
|
1982
2012
|
'}',
|
|
1983
|
-
'.cumulus-tasks-
|
|
1984
|
-
'
|
|
1985
|
-
'
|
|
1986
|
-
'
|
|
2013
|
+
'.cumulus-tasks-comment-quote {',
|
|
2014
|
+
' font-size: 11px; color: #9a9a9a; white-space: pre-wrap; line-height: 1.4;',
|
|
2015
|
+
' border-left: 2px solid #4a4a4a; padding-left: 0.5em; margin-bottom: 0.45em;',
|
|
2016
|
+
' max-height: 5.6em; overflow-y: auto;',
|
|
1987
2017
|
'}',
|
|
1988
|
-
'.cumulus-tasks-comment-
|
|
2018
|
+
'.cumulus-tasks-comment-input {',
|
|
2019
|
+
' width: 100%; box-sizing: border-box; resize: vertical;',
|
|
2020
|
+
' background: #1a1a1a; border: 1px solid #3a3a3a; border-radius: 4px;',
|
|
2021
|
+
' color: #e0e0e0; font-size: 12px; font-family: inherit; padding: 0.4em 0.5em;',
|
|
2022
|
+
'}',
|
|
2023
|
+
'.cumulus-tasks-comment-actions {',
|
|
2024
|
+
' display: flex; justify-content: flex-end; gap: 6px; margin-top: 0.4em;',
|
|
2025
|
+
'}',
|
|
2026
|
+
'.cumulus-tasks-comment-actions button {',
|
|
2027
|
+
' border-radius: 4px; padding: 0.25em 0.7em; cursor: pointer; font-size: 11px;',
|
|
2028
|
+
' background: #2a2a2a; border: 1px solid #3f3f3f; color: #ccc;',
|
|
2029
|
+
'}',
|
|
2030
|
+
'.cumulus-tasks-comment-add { background: #2f4a35 !important; border-color: #3f5f45 !important; color: #ddd !important; }',
|
|
2031
|
+
'.cumulus-task-title--clickable { cursor: pointer; }',
|
|
2032
|
+
'.cumulus-task-title--clickable:hover { text-decoration: underline; text-decoration-color: #555; }',
|
|
1989
2033
|
'.cumulus-include-add-btn {',
|
|
1990
2034
|
' background: #0066cc; border: none; color: white;',
|
|
1991
2035
|
' padding: 0.4em 0.8em; border-radius: 4px; cursor: pointer;',
|
|
@@ -6653,6 +6697,25 @@
|
|
|
6653
6697
|
stallCard.appendChild(stallSelect);
|
|
6654
6698
|
body.appendChild(stallCard);
|
|
6655
6699
|
|
|
6700
|
+
// Task 179: background workers get their own dial rather than sharing the
|
|
6701
|
+
// classifier's. The jobs differ in difficulty — that one is a yes/no read of
|
|
6702
|
+
// three sentences, this one rewrites a whole task file and judges every
|
|
6703
|
+
// task's state — so the cheap model that is plenty for the first may not be
|
|
6704
|
+
// for the second.
|
|
6705
|
+
var workerCard = document.createElement('div');
|
|
6706
|
+
workerCard.className = 'cumulus-settings-default-card';
|
|
6707
|
+
var workerCardLabel = document.createElement('label');
|
|
6708
|
+
workerCardLabel.textContent = 'Background workers use';
|
|
6709
|
+
var workerSelect = document.createElement('select');
|
|
6710
|
+
workerSelect.className = 'cumulus-settings-select';
|
|
6711
|
+
workerSelect.setAttribute('data-testid', 'webchat-gw-worker-select');
|
|
6712
|
+
workerSelect.addEventListener('change', function () {
|
|
6713
|
+
gwState.workerModel = workerSelect.value;
|
|
6714
|
+
});
|
|
6715
|
+
workerCard.appendChild(workerCardLabel);
|
|
6716
|
+
workerCard.appendChild(workerSelect);
|
|
6717
|
+
body.appendChild(workerCard);
|
|
6718
|
+
|
|
6656
6719
|
var modelsLabel = document.createElement('div');
|
|
6657
6720
|
modelsLabel.className = 'cumulus-settings-section-label';
|
|
6658
6721
|
modelsLabel.textContent = 'Model catalog';
|
|
@@ -7292,6 +7355,18 @@
|
|
|
7292
7355
|
if (stallSelect.value !== (gwState.stallClassifierModel || '')) stallSelect.value = '';
|
|
7293
7356
|
}
|
|
7294
7357
|
|
|
7358
|
+
// Task 179 — same rule, same fallback, its own setting.
|
|
7359
|
+
function renderWorkerSelect() {
|
|
7360
|
+
workerSelect.innerHTML = '';
|
|
7361
|
+
var workerHaiku = document.createElement('option');
|
|
7362
|
+
workerHaiku.value = '';
|
|
7363
|
+
workerHaiku.textContent = 'Haiku (default)';
|
|
7364
|
+
workerSelect.appendChild(workerHaiku);
|
|
7365
|
+
fillModelOptions(workerSelect);
|
|
7366
|
+
workerSelect.value = gwState.workerModel || '';
|
|
7367
|
+
if (workerSelect.value !== (gwState.workerModel || '')) workerSelect.value = '';
|
|
7368
|
+
}
|
|
7369
|
+
|
|
7295
7370
|
// Rebuild the provider list from state before anything reads it, so a
|
|
7296
7371
|
// provider added or removed this session reaches the cards, the default
|
|
7297
7372
|
// dropdown and the add-model select in the same pass (task 147).
|
|
@@ -7316,6 +7391,7 @@
|
|
|
7316
7391
|
renderDefaultSelect();
|
|
7317
7392
|
renderVoiceSelect();
|
|
7318
7393
|
renderStallSelect();
|
|
7394
|
+
renderWorkerSelect();
|
|
7319
7395
|
renderGwModels();
|
|
7320
7396
|
}
|
|
7321
7397
|
|
|
@@ -9221,45 +9297,86 @@
|
|
|
9221
9297
|
errorEl.style.display = 'none';
|
|
9222
9298
|
drawer.appendChild(errorEl);
|
|
9223
9299
|
|
|
9224
|
-
|
|
9225
|
-
|
|
9226
|
-
|
|
9227
|
-
|
|
9228
|
-
|
|
9300
|
+
// Task 178: the selection bar sits ABOVE the list and exists only while
|
|
9301
|
+
// something is ticked. At the bottom it anchored the comment editor below
|
|
9302
|
+
// the panel's own floor, where the panel clipped it — the button worked and
|
|
9303
|
+
// nothing appeared. Nothing here is positioned relative to the viewport now.
|
|
9229
9304
|
var selbar = document.createElement('div');
|
|
9230
9305
|
selbar.className = 'cumulus-tasks-selbar';
|
|
9306
|
+
selbar.setAttribute('data-testid', 'webchat-tasks-selbar');
|
|
9307
|
+
|
|
9308
|
+
// Task 179 — Refresh occupies the toolbar whenever nothing is ticked. It is
|
|
9309
|
+
// the only control here that acts on the WHOLE file rather than a selection,
|
|
9310
|
+
// which is why it swaps out entirely the moment a row is ticked rather than
|
|
9311
|
+
// sitting alongside actions that mean something narrower.
|
|
9312
|
+
var refreshNote = document.createElement('span');
|
|
9313
|
+
refreshNote.className = 'cumulus-tasks-refresh-note';
|
|
9314
|
+
refreshNote.setAttribute('data-testid', 'webchat-tasks-refresh-note');
|
|
9315
|
+
refreshNote.textContent = '';
|
|
9316
|
+
selbar.appendChild(refreshNote);
|
|
9317
|
+
|
|
9318
|
+
var refreshBtn = taskActionButton('Refresh', 'refresh', function () {
|
|
9319
|
+
requestTasksRefresh(drawer, threadName);
|
|
9320
|
+
});
|
|
9321
|
+
refreshBtn.setAttribute(
|
|
9322
|
+
'title',
|
|
9323
|
+
'Convert this file to the current format and re-check every task’s state. ' +
|
|
9324
|
+
'Commits a snapshot to git first.'
|
|
9325
|
+
);
|
|
9326
|
+
selbar.appendChild(refreshBtn);
|
|
9327
|
+
|
|
9231
9328
|
var selcount = document.createElement('span');
|
|
9232
9329
|
selcount.className = 'cumulus-tasks-selcount';
|
|
9233
9330
|
selcount.setAttribute('data-testid', 'webchat-tasks-selcount');
|
|
9234
9331
|
selcount.textContent = '0 selected';
|
|
9235
9332
|
selbar.appendChild(selcount);
|
|
9236
|
-
|
|
9237
|
-
commentBtn
|
|
9238
|
-
|
|
9239
|
-
commentBtn.textContent = 'Comment';
|
|
9240
|
-
commentBtn.disabled = true;
|
|
9241
|
-
commentBtn.addEventListener('click', function () {
|
|
9242
|
-
var labels = drawer._selectedLabels ? drawer._selectedLabels() : [];
|
|
9243
|
-
if (labels.length === 0) return;
|
|
9244
|
-
// The same popover the chat uses for a highlighted quote — the selected
|
|
9245
|
-
// rows ARE the quote, so the chip that lands in the composer is
|
|
9246
|
-
// indistinguishable from one made by highlighting text.
|
|
9247
|
-
showAnnotationPopover(
|
|
9333
|
+
|
|
9334
|
+
var commentBtn = taskActionButton('Comment', 'comment', function () {
|
|
9335
|
+
openTaskComment(
|
|
9248
9336
|
panel,
|
|
9249
|
-
|
|
9250
|
-
|
|
9251
|
-
|
|
9252
|
-
''
|
|
9337
|
+
drawer,
|
|
9338
|
+
drawer._selectedLabels ? drawer._selectedLabels() : [],
|
|
9339
|
+
selbar,
|
|
9340
|
+
'selection'
|
|
9253
9341
|
);
|
|
9254
9342
|
});
|
|
9255
9343
|
selbar.appendChild(commentBtn);
|
|
9344
|
+
|
|
9345
|
+
var closedBtn = taskActionButton('Closed ✓', 'closed', function () {
|
|
9346
|
+
dispositionSelectedTasks(drawer, threadName, 'closed');
|
|
9347
|
+
});
|
|
9348
|
+
selbar.appendChild(closedBtn);
|
|
9349
|
+
var archiveBtn = taskActionButton('Archive', 'archive', function () {
|
|
9350
|
+
dispositionSelectedTasks(drawer, threadName, 'archived');
|
|
9351
|
+
});
|
|
9352
|
+
selbar.appendChild(archiveBtn);
|
|
9353
|
+
|
|
9354
|
+
var clearBtn = document.createElement('button');
|
|
9355
|
+
clearBtn.className = 'cumulus-tasks-selclear';
|
|
9356
|
+
clearBtn.setAttribute('data-testid', 'webchat-tasks-selclear');
|
|
9357
|
+
clearBtn.setAttribute('title', 'Clear selection');
|
|
9358
|
+
clearBtn.textContent = '\xD7';
|
|
9359
|
+
clearBtn.addEventListener('click', function () {
|
|
9360
|
+
clearTaskSelection(drawer);
|
|
9361
|
+
});
|
|
9362
|
+
selbar.appendChild(clearBtn);
|
|
9256
9363
|
drawer.appendChild(selbar);
|
|
9257
9364
|
|
|
9365
|
+
var body = document.createElement('div');
|
|
9366
|
+
body.className = 'cumulus-tasks-body';
|
|
9367
|
+
body.innerHTML = '<div class="cumulus-tasks-empty">Loading…</div>';
|
|
9368
|
+
drawer.appendChild(body);
|
|
9369
|
+
|
|
9258
9370
|
drawer._body = body;
|
|
9259
9371
|
drawer._errorEl = errorEl;
|
|
9260
9372
|
drawer._countEl = count;
|
|
9373
|
+
drawer._selbar = selbar;
|
|
9261
9374
|
drawer._selcount = selcount;
|
|
9262
|
-
drawer.
|
|
9375
|
+
drawer._panel = panel;
|
|
9376
|
+
drawer._refreshBtn = refreshBtn;
|
|
9377
|
+
drawer._refreshNote = refreshNote;
|
|
9378
|
+
// The selection half of the toolbar, hidden together at zero selected.
|
|
9379
|
+
drawer._selGroup = [selcount, commentBtn, closedBtn, archiveBtn, clearBtn];
|
|
9263
9380
|
|
|
9264
9381
|
panel.appendChild(drawer);
|
|
9265
9382
|
if (connection) connection.send({ type: 'tasks_list', threadName: threadName });
|
|
@@ -9385,7 +9502,7 @@
|
|
|
9385
9502
|
row.appendChild(now);
|
|
9386
9503
|
}
|
|
9387
9504
|
|
|
9388
|
-
return { row: row, check: check };
|
|
9505
|
+
return { row: row, check: check, title: title };
|
|
9389
9506
|
}
|
|
9390
9507
|
|
|
9391
9508
|
function renderTasks(panel, threadName, data) {
|
|
@@ -9407,13 +9524,19 @@
|
|
|
9407
9524
|
var done = data.done || [];
|
|
9408
9525
|
drawer._countEl.textContent = data.missing ? '' : queue.length + ' open';
|
|
9409
9526
|
|
|
9527
|
+
// A row-anchored editor lives inside the list being rebuilt; dropping it here
|
|
9528
|
+
// is what stops it surviving as a detached node its Add button still writes to.
|
|
9529
|
+
closeTaskComment(drawer, 'row');
|
|
9410
9530
|
body.innerHTML = '';
|
|
9411
9531
|
|
|
9412
9532
|
if (data.missing) {
|
|
9533
|
+
// The un-migrated case is exactly what Refresh is for, so say so here
|
|
9534
|
+
// rather than leaving the button above looking decorative (task 179).
|
|
9413
9535
|
body.innerHTML =
|
|
9414
|
-
'<div class="cumulus-tasks-empty">This thread has no tasks file
|
|
9415
|
-
'
|
|
9416
|
-
'
|
|
9536
|
+
'<div class="cumulus-tasks-empty">This thread has no tasks file in the current ' +
|
|
9537
|
+
'format.<br><br>If it already has a <code>Tasks.md</code> in its always-include ' +
|
|
9538
|
+
'list, press <b>Refresh</b> to convert it — the file is committed to git first. ' +
|
|
9539
|
+
'Otherwise add one, and it will appear here.</div>';
|
|
9417
9540
|
drawer._selected = {};
|
|
9418
9541
|
updateTaskSelection(drawer);
|
|
9419
9542
|
return;
|
|
@@ -9434,6 +9557,7 @@
|
|
|
9434
9557
|
var built = buildTaskRow(task, { isNow: i === 0, done: false });
|
|
9435
9558
|
wireTaskDrag(built.row, drawer, threadName);
|
|
9436
9559
|
wireTaskCheck(built, drawer);
|
|
9560
|
+
wireTaskComment(built, drawer, task);
|
|
9437
9561
|
|
|
9438
9562
|
if (task.depProposed) {
|
|
9439
9563
|
var dep = document.createElement('span');
|
|
@@ -9459,6 +9583,7 @@
|
|
|
9459
9583
|
done.forEach(function (task) {
|
|
9460
9584
|
var built = buildTaskRow(task, { isNow: false, done: true });
|
|
9461
9585
|
wireTaskCheck(built, drawer);
|
|
9586
|
+
wireTaskComment(built, drawer, task);
|
|
9462
9587
|
body.appendChild(built.row);
|
|
9463
9588
|
});
|
|
9464
9589
|
}
|
|
@@ -9505,6 +9630,23 @@
|
|
|
9505
9630
|
return btn;
|
|
9506
9631
|
}
|
|
9507
9632
|
|
|
9633
|
+
// Clicking a row's title comments on THAT row, whatever is ticked — the
|
|
9634
|
+
// one-task path Karl asked for, and the reason the editor anchors to the row
|
|
9635
|
+
// rather than always to the bar.
|
|
9636
|
+
function wireTaskComment(built, drawer, task) {
|
|
9637
|
+
built.title.classList.add('cumulus-task-title--clickable');
|
|
9638
|
+
built.title.setAttribute('title', 'Comment on this task');
|
|
9639
|
+
built.title.addEventListener('click', function () {
|
|
9640
|
+
var label = task.id + ' · ' + task.title;
|
|
9641
|
+
if (drawer._commentEditor && drawer._commentEditor._taskId === task.id) {
|
|
9642
|
+
closeTaskComment(drawer);
|
|
9643
|
+
return;
|
|
9644
|
+
}
|
|
9645
|
+
openTaskComment(drawer._panel, drawer, [label], built.row, 'row');
|
|
9646
|
+
if (drawer._commentEditor) drawer._commentEditor._taskId = task.id;
|
|
9647
|
+
});
|
|
9648
|
+
}
|
|
9649
|
+
|
|
9508
9650
|
function wireTaskCheck(built, drawer) {
|
|
9509
9651
|
built.check.addEventListener('change', function () {
|
|
9510
9652
|
var id = built.row.getAttribute('data-task-id');
|
|
@@ -9514,10 +9656,163 @@
|
|
|
9514
9656
|
});
|
|
9515
9657
|
}
|
|
9516
9658
|
|
|
9659
|
+
// The toolbar shows exactly one of two things. At zero selected it is
|
|
9660
|
+
// Refresh, which acts on the whole file; with rows ticked it is the count and
|
|
9661
|
+
// the actions that act on those rows. Task 178 hid the bar entirely at zero
|
|
9662
|
+
// because a permanent "0 selected" strip answered a question nobody asked —
|
|
9663
|
+
// 179 gives that space a real use, so the bar stays and its contents swap.
|
|
9517
9664
|
function updateTaskSelection(drawer) {
|
|
9518
9665
|
var n = Object.keys(drawer._selected).length;
|
|
9519
9666
|
drawer._selcount.textContent = n + ' selected';
|
|
9520
|
-
|
|
9667
|
+
var group = drawer._selGroup || [];
|
|
9668
|
+
for (var i = 0; i < group.length; i++) group[i].style.display = n === 0 ? 'none' : '';
|
|
9669
|
+
drawer._refreshBtn.style.display = n === 0 ? '' : 'none';
|
|
9670
|
+
drawer._refreshNote.style.display = n === 0 ? '' : 'none';
|
|
9671
|
+
if (n === 0) closeTaskComment(drawer, 'selection');
|
|
9672
|
+
}
|
|
9673
|
+
|
|
9674
|
+
// Task 179. The button is disabled optimistically rather than on the server's
|
|
9675
|
+
// `running` push: the round-trip is long enough to double-click through, and a
|
|
9676
|
+
// second press is REFUSED by the server, not queued — so an un-disabled button
|
|
9677
|
+
// would just produce an error message for a press the user meant.
|
|
9678
|
+
function requestTasksRefresh(drawer, threadName) {
|
|
9679
|
+
if (drawer._refreshBtn.disabled) return;
|
|
9680
|
+
setTasksRefreshBusy(drawer, true, 'Refreshing…');
|
|
9681
|
+
drawer._errorEl.textContent = '';
|
|
9682
|
+
drawer._errorEl.style.display = 'none';
|
|
9683
|
+
if (connection) connection.send({ type: 'tasks_refresh', threadName: threadName });
|
|
9684
|
+
}
|
|
9685
|
+
|
|
9686
|
+
function setTasksRefreshBusy(drawer, busy, note) {
|
|
9687
|
+
drawer._refreshBtn.disabled = !!busy;
|
|
9688
|
+
drawer._refreshBtn.textContent = busy ? 'Refreshing…' : 'Refresh';
|
|
9689
|
+
drawer._refreshNote.textContent = note || '';
|
|
9690
|
+
}
|
|
9691
|
+
|
|
9692
|
+
// A worker's terminal status. `failed` lands in the same error strip a rejected
|
|
9693
|
+
// drag uses — one place in the drawer where the file's own truth is reported,
|
|
9694
|
+
// whatever refused the write.
|
|
9695
|
+
function applyWorkerStatus(panel, threadName, data) {
|
|
9696
|
+
var drawer = panel.querySelector('.cumulus-tasks-drawer');
|
|
9697
|
+
if (!drawer || drawer._threadName !== threadName) return;
|
|
9698
|
+
if (data.kind !== 'tasks-refresh') return;
|
|
9699
|
+
|
|
9700
|
+
if (data.state === 'running') {
|
|
9701
|
+
setTasksRefreshBusy(drawer, true, 'Refreshing…');
|
|
9702
|
+
return;
|
|
9703
|
+
}
|
|
9704
|
+
setTasksRefreshBusy(drawer, false, data.state === 'done' ? data.detail || '' : '');
|
|
9705
|
+
if (data.state === 'failed') {
|
|
9706
|
+
drawer._errorEl.textContent = data.detail || 'Refresh failed.';
|
|
9707
|
+
drawer._errorEl.style.display = '';
|
|
9708
|
+
}
|
|
9709
|
+
}
|
|
9710
|
+
|
|
9711
|
+
function taskActionButton(label, name, onClick) {
|
|
9712
|
+
var btn = document.createElement('button');
|
|
9713
|
+
btn.className = 'cumulus-tasks-action cumulus-tasks-action--' + name;
|
|
9714
|
+
btn.setAttribute('data-testid', 'webchat-tasks-' + name + '-btn');
|
|
9715
|
+
btn.textContent = label;
|
|
9716
|
+
btn.addEventListener('click', onClick);
|
|
9717
|
+
return btn;
|
|
9718
|
+
}
|
|
9719
|
+
|
|
9720
|
+
function clearTaskSelection(drawer) {
|
|
9721
|
+
var boxes = drawer._body.querySelectorAll('.cumulus-task-check');
|
|
9722
|
+
for (var i = 0; i < boxes.length; i++) boxes[i].checked = false;
|
|
9723
|
+
drawer._selected = {};
|
|
9724
|
+
updateTaskSelection(drawer);
|
|
9725
|
+
}
|
|
9726
|
+
|
|
9727
|
+
// Closing and archiving are the same write with a different glyph, so they are
|
|
9728
|
+
// the same message: the server decides what a disposition does to the file, and
|
|
9729
|
+
// the answer comes back through the one renderer every other write uses.
|
|
9730
|
+
function dispositionSelectedTasks(drawer, threadName, disposition) {
|
|
9731
|
+
var ids = Object.keys(drawer._selected);
|
|
9732
|
+
if (ids.length === 0) return;
|
|
9733
|
+
closeTaskComment(drawer);
|
|
9734
|
+
if (connection)
|
|
9735
|
+
connection.send({
|
|
9736
|
+
type: 'tasks_disposition',
|
|
9737
|
+
threadName: threadName,
|
|
9738
|
+
ids: ids,
|
|
9739
|
+
disposition: disposition,
|
|
9740
|
+
});
|
|
9741
|
+
drawer._selected = {};
|
|
9742
|
+
updateTaskSelection(drawer);
|
|
9743
|
+
}
|
|
9744
|
+
|
|
9745
|
+
// Task 178: the comment editor is part of the drawer, inserted directly below
|
|
9746
|
+
// whatever it is about — the selection bar for several rows, the row itself for
|
|
9747
|
+
// one. It ends at `addAnnotationChip`, the same call the chat's floating
|
|
9748
|
+
// popover makes, so the chip in the composer is identical either way.
|
|
9749
|
+
function openTaskComment(panel, drawer, labels, anchorEl, origin) {
|
|
9750
|
+
closeTaskComment(drawer);
|
|
9751
|
+
if (!labels || labels.length === 0 || !anchorEl || !anchorEl.parentNode) return;
|
|
9752
|
+
|
|
9753
|
+
var editor = document.createElement('div');
|
|
9754
|
+
editor.className = 'cumulus-tasks-comment';
|
|
9755
|
+
editor.setAttribute('data-testid', 'webchat-tasks-comment');
|
|
9756
|
+
editor._origin = origin || 'selection';
|
|
9757
|
+
|
|
9758
|
+
var quote = document.createElement('div');
|
|
9759
|
+
quote.className = 'cumulus-tasks-comment-quote';
|
|
9760
|
+
quote.textContent = labels.join('\n');
|
|
9761
|
+
editor.appendChild(quote);
|
|
9762
|
+
|
|
9763
|
+
var textarea = document.createElement('textarea');
|
|
9764
|
+
textarea.className = 'cumulus-tasks-comment-input';
|
|
9765
|
+
textarea.setAttribute('data-testid', 'webchat-tasks-comment-input');
|
|
9766
|
+
textarea.placeholder = 'Add a comment…';
|
|
9767
|
+
textarea.rows = 3;
|
|
9768
|
+
editor.appendChild(textarea);
|
|
9769
|
+
|
|
9770
|
+
var actions = document.createElement('div');
|
|
9771
|
+
actions.className = 'cumulus-tasks-comment-actions';
|
|
9772
|
+
|
|
9773
|
+
var cancelBtn = document.createElement('button');
|
|
9774
|
+
cancelBtn.className = 'cumulus-tasks-comment-cancel';
|
|
9775
|
+
cancelBtn.setAttribute('data-testid', 'webchat-tasks-comment-cancel');
|
|
9776
|
+
cancelBtn.textContent = 'Cancel';
|
|
9777
|
+
cancelBtn.addEventListener('click', function () {
|
|
9778
|
+
closeTaskComment(drawer);
|
|
9779
|
+
});
|
|
9780
|
+
actions.appendChild(cancelBtn);
|
|
9781
|
+
|
|
9782
|
+
var addBtn = document.createElement('button');
|
|
9783
|
+
addBtn.className = 'cumulus-tasks-comment-add';
|
|
9784
|
+
addBtn.setAttribute('data-testid', 'webchat-tasks-comment-add');
|
|
9785
|
+
addBtn.textContent = 'Add';
|
|
9786
|
+
addBtn.addEventListener('click', function () {
|
|
9787
|
+
var comment = textarea.value.trim();
|
|
9788
|
+
if (comment) addAnnotationChip(panel, labels.join('\n'), comment, false, '');
|
|
9789
|
+
closeTaskComment(drawer);
|
|
9790
|
+
});
|
|
9791
|
+
actions.appendChild(addBtn);
|
|
9792
|
+
editor.appendChild(actions);
|
|
9793
|
+
|
|
9794
|
+
textarea.addEventListener('keydown', function (e) {
|
|
9795
|
+
if (e.key === 'Enter' && !e.shiftKey) {
|
|
9796
|
+
e.preventDefault();
|
|
9797
|
+
addBtn.click();
|
|
9798
|
+
} else if (e.key === 'Escape') {
|
|
9799
|
+
closeTaskComment(drawer);
|
|
9800
|
+
}
|
|
9801
|
+
});
|
|
9802
|
+
|
|
9803
|
+
anchorEl.parentNode.insertBefore(editor, anchorEl.nextSibling);
|
|
9804
|
+
drawer._commentEditor = editor;
|
|
9805
|
+
// Safe to focus here, unlike the chat popover: there is no text selection
|
|
9806
|
+
// for the caret to destroy.
|
|
9807
|
+
textarea.focus();
|
|
9808
|
+
}
|
|
9809
|
+
|
|
9810
|
+
function closeTaskComment(drawer, onlyOrigin) {
|
|
9811
|
+
var editor = drawer._commentEditor;
|
|
9812
|
+
if (!editor) return;
|
|
9813
|
+
if (onlyOrigin && editor._origin !== onlyOrigin) return;
|
|
9814
|
+
if (editor.parentNode) editor.parentNode.removeChild(editor);
|
|
9815
|
+
drawer._commentEditor = null;
|
|
9521
9816
|
}
|
|
9522
9817
|
|
|
9523
9818
|
function openIncludeOverlay(panel, threadName) {
|
|
@@ -10653,6 +10948,18 @@
|
|
|
10653
10948
|
}
|
|
10654
10949
|
break;
|
|
10655
10950
|
|
|
10951
|
+
// A gateway-side background worker changed state (task 179). Deliberately
|
|
10952
|
+
// NOT `thread_activity`: a worker is not a turn, so it must not light the
|
|
10953
|
+
// sidebar dots or claim the thread is busy.
|
|
10954
|
+
case 'worker_status':
|
|
10955
|
+
if (data.threadName) {
|
|
10956
|
+
var workerPanel = document.querySelector(
|
|
10957
|
+
'[data-thread-panel="' + data.threadName + '"]'
|
|
10958
|
+
);
|
|
10959
|
+
if (workerPanel) applyWorkerStatus(workerPanel, data.threadName, data);
|
|
10960
|
+
}
|
|
10961
|
+
break;
|
|
10962
|
+
|
|
10656
10963
|
case 'include_added':
|
|
10657
10964
|
case 'include_removed':
|
|
10658
10965
|
// List already updated by the handler — no extra action needed
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Task 179 — the Refresh button's actual work.
|
|
3
|
+
*
|
|
4
|
+
* Karl's three answers set the shape of this file:
|
|
5
|
+
*
|
|
6
|
+
* 1. *"Write it straight away, but commit to git first so I can undo."* — so a
|
|
7
|
+
* snapshot commit is a PRECONDITION, and a file outside a git repo is
|
|
8
|
+
* refused outright. The permission to write without asking was bought with
|
|
9
|
+
* the undo; without a repo there is no undo, so the permission lapses.
|
|
10
|
+
* 2. *"Both — convert to the new format AND re-check every task's state."* — one
|
|
11
|
+
* model call does both, because they are the same rewrite.
|
|
12
|
+
* 3. *"Build it as a general facility."* — which is `worker.ts`; this module is
|
|
13
|
+
* only its first caller.
|
|
14
|
+
*
|
|
15
|
+
* The load-bearing part is not the prompt. A model handed a whole file and asked
|
|
16
|
+
* for a whole file back will, sooner or later, quietly drop a row — so the answer
|
|
17
|
+
* is checked before it is written, and a run that loses a task id, fails to
|
|
18
|
+
* parse, or comes back without the marker is refused WHOLE. The git snapshot is
|
|
19
|
+
* the belt; these checks are the braces.
|
|
20
|
+
*/
|
|
21
|
+
import { type TasksPayload } from './tasks.js';
|
|
22
|
+
import { type RunWorkerOptions } from './worker.js';
|
|
23
|
+
/** A refusal with a reason the drawer shows verbatim. */
|
|
24
|
+
export declare class TasksRefreshError extends Error {
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Every task id the file currently carries.
|
|
28
|
+
*
|
|
29
|
+
* Deliberately permissive, and matched against the RAW text rather than a parse:
|
|
30
|
+
* the un-migrated files this exists to convert do not parse at all, and they are
|
|
31
|
+
* the ones with the most to lose. Both shapes seen in the wild are covered —
|
|
32
|
+
* `- [ ] 179 · title` (schema) and `- [◕] 178: title` (what came before).
|
|
33
|
+
*
|
|
34
|
+
* The `## Format` section is skipped, and that is not a nicety: the manual
|
|
35
|
+
* carried inside every migrated file demonstrates the grammar with EXAMPLE rows
|
|
36
|
+
* (`- [ ] 073 · StorePool reaping`, `- [⊘] 084 · Creative-writing benchmark`).
|
|
37
|
+
* Counting those as tasks would demand the model preserve four ids that do not
|
|
38
|
+
* exist, so every already-migrated file would fail the drop check and Refresh
|
|
39
|
+
* would work only on un-migrated ones. Found by test, not by reading.
|
|
40
|
+
*/
|
|
41
|
+
export declare function taskIdsIn(content: string): string[];
|
|
42
|
+
/** Strip a ```-fenced wrapper, which a chat-tuned model adds however firmly it is told not to. */
|
|
43
|
+
export declare function unfence(answer: string): string;
|
|
44
|
+
/**
|
|
45
|
+
* Commit the file as it stands, and return whether there was anything to commit.
|
|
46
|
+
*
|
|
47
|
+
* Scoped by pathspec on BOTH commands, so a refresh never sweeps up whatever else
|
|
48
|
+
* the user happened to have staged. "Nothing to commit" is success, not failure:
|
|
49
|
+
* the file is already identical to HEAD, so HEAD *is* the undo point.
|
|
50
|
+
*
|
|
51
|
+
* Not being in a git repo is the one hard refusal here — see the module comment.
|
|
52
|
+
*/
|
|
53
|
+
export declare function snapshotBeforeRefresh(filePath: string): boolean;
|
|
54
|
+
export interface RefreshPromptInput {
|
|
55
|
+
content: string;
|
|
56
|
+
migrated: boolean;
|
|
57
|
+
docs: string[];
|
|
58
|
+
version: string;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* The whole prompt, as one pure function so the instructions are testable
|
|
62
|
+
* without a model.
|
|
63
|
+
*
|
|
64
|
+
* The three facts at the end are the ones the model cannot get from the file and
|
|
65
|
+
* would otherwise invent: which task docs exist, and what the gateway is actually
|
|
66
|
+
* running. That last one is the thing Karl complained about — rows still saying
|
|
67
|
+
* "needs reload" long after the reload happened.
|
|
68
|
+
*/
|
|
69
|
+
export declare function buildRefreshPrompt(input: RefreshPromptInput): string;
|
|
70
|
+
export interface RefreshResult {
|
|
71
|
+
payload: TasksPayload;
|
|
72
|
+
/** False when the file was already identical to HEAD — HEAD is then the undo point. */
|
|
73
|
+
committed: boolean;
|
|
74
|
+
/** Ids the model dropped. Non-empty only on the refusal path, for the message. */
|
|
75
|
+
droppedIds: string[];
|
|
76
|
+
}
|
|
77
|
+
export interface RefreshOptions extends RunWorkerOptions {
|
|
78
|
+
/** Test seam: run this instead of the model. Production leaves it unset. */
|
|
79
|
+
runModel?: (prompt: string) => Promise<string>;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Convert and re-check a thread's tasks file in place.
|
|
83
|
+
*
|
|
84
|
+
* Order matters and is the design: resolve → snapshot → ask → CHECK → write.
|
|
85
|
+
* Nothing between the snapshot and the write touches disk, so every refusal below
|
|
86
|
+
* leaves the file byte-identical to what was just committed.
|
|
87
|
+
*/
|
|
88
|
+
export declare function refreshTasksFile(threadName: string, opts?: RefreshOptions): Promise<RefreshResult>;
|
|
89
|
+
//# sourceMappingURL=tasks-refresh.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tasks-refresh.d.ts","sourceRoot":"","sources":["../../src/gateway/tasks-refresh.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AASH,OAAO,EAAmC,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAChF,OAAO,EAAa,KAAK,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAE/D,yDAAyD;AACzD,qBAAa,iBAAkB,SAAQ,KAAK;CAAG;AAO/C;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAWnD;AAED,kGAAkG;AAClG,wBAAgB,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAO9C;AAMD;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CA6B/D;AAcD,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,OAAO,CAAC;IAClB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,kBAAkB,GAAG,MAAM,CAuCpE;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,YAAY,CAAC;IACtB,uFAAuF;IACvF,SAAS,EAAE,OAAO,CAAC;IACnB,kFAAkF;IAClF,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,cAAe,SAAQ,gBAAgB;IACtD,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;CAChD;AAED;;;;;;GAMG;AACH,wBAAsB,gBAAgB,CACpC,UAAU,EAAE,MAAM,EAClB,IAAI,GAAE,cAAmB,GACxB,OAAO,CAAC,aAAa,CAAC,CAkExB"}
|