@lamemind/loom-deck 0.34.1 → 0.34.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +11 -7
- package/dist/overlays/sheet.js +23 -3
- package/dist/spawn.js +16 -4
- package/dist/ui/detail-screen.js +26 -6
- package/dist/viewport.js +6 -5
- package/package.json +1 -1
- package/scripts/deck-run +49 -4
package/dist/cli.js
CHANGED
|
@@ -16,7 +16,7 @@ import { initialDetail, writeTaskEdit, PRI_GLYPH, PRI_LABEL } from './task-edit.
|
|
|
16
16
|
import { ALL, EDIT_PRI, EDIT_PROG, MAX_SESSIONS, MAX_SESSIONS_ALL, META_ROWS, ROW_ALL, ROW_SPOT, SORT_TASTI, SPOT, } from './model.js';
|
|
17
17
|
import { conversationLabel, cpLen, editField, insertAt, isDone, isTextRow, removeAt, EDIT_ROWS, } from './layout.js';
|
|
18
18
|
import { TASK_EMPTY, relTime, sanitizeTyped } from './glyphs.js';
|
|
19
|
-
import { commitTaskEdit, runLaunch, spawnClaudeEmpty, spawnCreateTask, spawnDeck, spawnDeckFork, spawnDeckResume, spawnTerminal, CLAUDE_CMD, DECK_RUN, } from './spawn.js';
|
|
19
|
+
import { commitTaskEdit, runLaunch, spawnClaudeEmpty, spawnCreateTask, spawnDeck, spawnDeckFork, spawnDeckResume, spawnTerminal, CLAUDE_CMD, DECK_RUN, MODEL_DEFAULT, } from './spawn.js';
|
|
20
20
|
import { EditModal, FilterModal, SortModal } from './ui/modals.js';
|
|
21
21
|
import { ReaderScreen, SearchScreen } from './ui/search-screen.js';
|
|
22
22
|
import { DetailScreen } from './ui/detail-screen.js';
|
|
@@ -285,18 +285,22 @@ function Deck({ cwd, tasksPath, tasksDir }) {
|
|
|
285
285
|
// Lo spawn vero, su una task GIÀ risolta. Separato dalla guardia perché il
|
|
286
286
|
// detail passa l'id fotografato all'apertura e non la selezione corrente: la
|
|
287
287
|
// lista lì sotto non è più a schermo, quindi non è più la fonte dell'oggetto.
|
|
288
|
-
function spawnForTask(id, kind, keyLabel) {
|
|
288
|
+
function spawnForTask(id, kind, model, keyLabel) {
|
|
289
289
|
const sid = randomUUID();
|
|
290
290
|
appendTaskBinding(cwd, sid, id);
|
|
291
|
-
const child = spawnDeck(id, cwd, sid, kind);
|
|
291
|
+
const child = spawnDeck(id, cwd, sid, kind, model);
|
|
292
292
|
child.on('error', () => setNote(`⚠ spawn ${id} fallito (${DECK_RUN})`));
|
|
293
293
|
const what = kind === 'none' ? '' : ` · ${kind}`;
|
|
294
|
-
|
|
294
|
+
// Il modello è SEMPRE nominato, anche quando è il default: gli acceleratori
|
|
295
|
+
// della lista non passano dal selettore del detail e usano il default fisso
|
|
296
|
+
// (T108 · via a), quindi senza dirlo l'utente crederebbe di aver ereditato
|
|
297
|
+
// la scelta fatta nell'ultimo detail aperto.
|
|
298
|
+
setNote(`${keyLabel} spawn ${id}${what} · ${model} → tab CC (sid ${sid.slice(0, 8)})`);
|
|
295
299
|
}
|
|
296
300
|
function spawnTaskSession(kind, keyLabel) {
|
|
297
301
|
const task = selectedTaskOr(keyLabel, 'spawnare');
|
|
298
302
|
if (task)
|
|
299
|
-
spawnForTask(task.id, kind, keyLabel);
|
|
303
|
+
spawnForTask(task.id, kind, MODEL_DEFAULT, keyLabel);
|
|
300
304
|
}
|
|
301
305
|
// T53 — apertura del modale nota sulla conversazione selezionata. Come
|
|
302
306
|
// `openEdit`, la bozza parte dal valore ATTUALE: annotare una seconda volta è
|
|
@@ -486,7 +490,7 @@ function Deck({ cwd, tasksPath, tasksDir }) {
|
|
|
486
490
|
columns,
|
|
487
491
|
setMode,
|
|
488
492
|
setNote,
|
|
489
|
-
onAction: (id, kind, label) => spawnForTask(id, kind, label),
|
|
493
|
+
onAction: (id, kind, model, label) => spawnForTask(id, kind, model, label),
|
|
490
494
|
});
|
|
491
495
|
const search = useSearchOverlay({
|
|
492
496
|
sessions,
|
|
@@ -1019,7 +1023,7 @@ function Deck({ cwd, tasksPath, tasksDir }) {
|
|
|
1019
1023
|
// posizione è lo scroll mosso a mano. Il clamp serve comunque — un resize
|
|
1020
1024
|
// può accorciare il testo sotto uno scroll già dato.
|
|
1021
1025
|
const start = Math.min(sheet.top, sheet.maxTop);
|
|
1022
|
-
return (_jsx(DetailScreen, { id: sheet.sheet.id, title: sheet.sheet.title, missing: sheet.sheet.text === null, lines: sheet.lines.slice(start, start + sheet.capacity), spans: sheet.doc?.spans ?? [], top: start, total: sheet.lines.length, capacity: sheet.capacity, action: sheet.action, columns: columns, find: sheet.find, occ: sheet.findRes.occ, occCur: sheet.occCur }));
|
|
1026
|
+
return (_jsx(DetailScreen, { id: sheet.sheet.id, title: sheet.sheet.title, missing: sheet.sheet.text === null, lines: sheet.lines.slice(start, start + sheet.capacity), spans: sheet.doc?.spans ?? [], top: start, total: sheet.lines.length, capacity: sheet.capacity, action: sheet.action, model: sheet.model, columns: columns, find: sheet.find, occ: sheet.findRes.occ, occCur: sheet.occCur }));
|
|
1023
1027
|
}
|
|
1024
1028
|
// ── T52 · schermate sostitutive ─────────────────────────────────────────
|
|
1025
1029
|
// Ricerca e reader sono gli unici modali che NON stanno in flusso sopra i
|
package/dist/overlays/sheet.js
CHANGED
|
@@ -18,12 +18,16 @@ import { scanText, topForOffset } from '../text-search.js';
|
|
|
18
18
|
import { parseMarkdown } from '../markdown.js';
|
|
19
19
|
import { cpLen, insertAt, removeAt } from '../layout.js';
|
|
20
20
|
import { sanitizeTyped } from '../glyphs.js';
|
|
21
|
-
import { DETAIL_ACTIONS } from '../spawn.js';
|
|
21
|
+
import { DETAIL_ACTIONS, MODELS, MODEL_DEFAULT } from '../spawn.js';
|
|
22
22
|
export function useSheetOverlay(deps) {
|
|
23
23
|
const { rows, columns, setMode, setNote, onAction } = deps;
|
|
24
24
|
const [sheet, setSheet] = useState(null);
|
|
25
25
|
const [top, setTop] = useState(0);
|
|
26
26
|
const [action, setAction] = useState(0);
|
|
27
|
+
// T108 — il modello con cui partirà la sessione. Stato del DETAIL e non del
|
|
28
|
+
// deck: si azzera a ogni apertura come scroll e azione, quindi non esiste una
|
|
29
|
+
// selezione invisibile che cambi il comportamento dei tasti della lista.
|
|
30
|
+
const [model, setModel] = useState(MODEL_DEFAULT);
|
|
27
31
|
// T91 — ricerca dentro il detail. `open` distingue i due modi in cui la si
|
|
28
32
|
// lascia: `esc` butta via ciò che il modale ha prodotto (`find` a null,
|
|
29
33
|
// evidenziazione via), `⏎` lo congela e restituisce il controllo allo strato
|
|
@@ -73,11 +77,12 @@ export function useSheetOverlay(deps) {
|
|
|
73
77
|
return;
|
|
74
78
|
setTop(topForOffset(lines, o.start, capacity));
|
|
75
79
|
}, [findRes, occCur, lines, capacity]);
|
|
76
|
-
/** Apre il detail su una task, azzerando scroll, azione e ricerca. */
|
|
80
|
+
/** Apre il detail su una task, azzerando scroll, azione, modello e ricerca. */
|
|
77
81
|
function open(next) {
|
|
78
82
|
setSheet(next);
|
|
79
83
|
setTop(0);
|
|
80
84
|
setAction(0);
|
|
85
|
+
setModel(MODEL_DEFAULT);
|
|
81
86
|
setFind(null);
|
|
82
87
|
setOccIdx(0);
|
|
83
88
|
setNote('');
|
|
@@ -175,7 +180,21 @@ export function useSheetOverlay(deps) {
|
|
|
175
180
|
const id = sheet?.id;
|
|
176
181
|
close();
|
|
177
182
|
if (id)
|
|
178
|
-
onAction(id, act.kind, `⏎ ${act.label}`);
|
|
183
|
+
onAction(id, act.kind, model, `⏎ ${act.label}`);
|
|
184
|
+
}
|
|
185
|
+
else if (key.tab) {
|
|
186
|
+
// T108 — `tab` scorre il catalogo dei modelli. Libero solo QUI: in vista
|
|
187
|
+
// normale cicla la vista del pane a fuoco, e le cifre `1`-`9` sono della
|
|
188
|
+
// legenda launch. Il detail cattura l'input per intero, quindi è dove un
|
|
189
|
+
// alfabeto già speso torna disponibile.
|
|
190
|
+
setModel((m) => MODELS[(MODELS.indexOf(m) + 1) % MODELS.length]);
|
|
191
|
+
}
|
|
192
|
+
else if (input.length === 1 && input >= '1' && input <= String(MODELS.length)) {
|
|
193
|
+
// Scelta diretta: chi sa già quale vuole non paga lo scorrimento. Il test
|
|
194
|
+
// sulla lunghezza non è pleonastico: `useInput` consegna il CHUNK letto da
|
|
195
|
+
// stdin, quindi un incollato come `12` passerebbe il confronto fra
|
|
196
|
+
// stringhe e indicizzerebbe il catalogo fuori range.
|
|
197
|
+
setModel(MODELS[Number(input) - 1]);
|
|
179
198
|
}
|
|
180
199
|
else if (key.leftArrow || key.rightArrow) {
|
|
181
200
|
// Scorrimento CICLICO come le righe di scelta del modale edit: cinque
|
|
@@ -210,6 +229,7 @@ export function useSheetOverlay(deps) {
|
|
|
210
229
|
doc,
|
|
211
230
|
top,
|
|
212
231
|
action,
|
|
232
|
+
model,
|
|
213
233
|
find,
|
|
214
234
|
lines,
|
|
215
235
|
capacity,
|
package/dist/spawn.js
CHANGED
|
@@ -32,6 +32,15 @@ export function spawnOut(cmd, args, opts) {
|
|
|
32
32
|
fake.unref = () => fake;
|
|
33
33
|
return fake;
|
|
34
34
|
}
|
|
35
|
+
// L'ordine È il binding dei tasti `1`-`4` nel detail, non una preferenza di
|
|
36
|
+
// lettura: cambiarlo sposta i tasti sotto le dita di chi li ha imparati.
|
|
37
|
+
export const MODELS = ['fable', 'opus', 'sonnet', 'haiku'];
|
|
38
|
+
// Default del selettore e di ogni percorso di spawn che non passa da lui
|
|
39
|
+
// (`^K`/`^P`/`^R` dalla lista, resume, fork). Duplicato del default di deck-run
|
|
40
|
+
// e non letto da lì: il deck deve poter MOSTRARE la selezione iniziale prima di
|
|
41
|
+
// spawnare alcunché, e un valore che si conosce solo a spawn avvenuto non è
|
|
42
|
+
// mostrabile.
|
|
43
|
+
export const MODEL_DEFAULT = 'opus';
|
|
35
44
|
// T66 — le azioni del detail. Non sono un catalogo nuovo: ognuna è un
|
|
36
45
|
// `--prompt-kind` già esistente più `checkpoint`, e tutte passano dallo stesso
|
|
37
46
|
// `spawnForTask` dei CTRL della lista — una superficie in più, zero percorsi di
|
|
@@ -53,11 +62,14 @@ export const DETAIL_ACTIONS = [
|
|
|
53
62
|
// Il kind è OBBLIGATORIO e non ha default qui: il default vive in deck-run (per
|
|
54
63
|
// le invocazioni a mano), mentre dal deck ogni tasto dichiara il proprio intento
|
|
55
64
|
// — un default silenzioso renderebbe indistinguibili `⏎` e `^K`.
|
|
56
|
-
|
|
57
|
-
|
|
65
|
+
// Il modello, al contrario del kind, ha un default (T108): i percorsi che non
|
|
66
|
+
// passano dal selettore del detail non devono nominarlo per forza, e l'unico
|
|
67
|
+
// valore sensato per loro è quello che il selettore stesso mostra all'apertura.
|
|
68
|
+
export function deckArgs(id, sessionId, kind, model = MODEL_DEFAULT) {
|
|
69
|
+
return [id, '--session-id', sessionId, '--prompt-kind', kind, '--model', model];
|
|
58
70
|
}
|
|
59
|
-
export function spawnDeck(id, cwd, sessionId, kind) {
|
|
60
|
-
const child = spawnOut(DECK_RUN, deckArgs(id, sessionId, kind), {
|
|
71
|
+
export function spawnDeck(id, cwd, sessionId, kind, model = MODEL_DEFAULT) {
|
|
72
|
+
const child = spawnOut(DECK_RUN, deckArgs(id, sessionId, kind, model), {
|
|
61
73
|
cwd,
|
|
62
74
|
detached: true,
|
|
63
75
|
stdio: 'ignore',
|
package/dist/ui/detail-screen.js
CHANGED
|
@@ -5,7 +5,7 @@ import { Box, Text } from 'ink';
|
|
|
5
5
|
import { caretWindow, cut, cutParts, sanitize } from '../width.js';
|
|
6
6
|
import { sliceLine } from '../text-search.js';
|
|
7
7
|
import { sliceSpans } from '../markdown.js';
|
|
8
|
-
import { DETAIL_ACTIONS } from '../spawn.js';
|
|
8
|
+
import { DETAIL_ACTIONS, MODELS } from '../spawn.js';
|
|
9
9
|
import { WARN } from '../glyphs.js';
|
|
10
10
|
/** Resa di ogni costrutto markdown (T75 · D4): un solo livello di enfasi per
|
|
11
11
|
* costrutto, senza un secondo alfabeto da imparare. Heading uguali a ogni
|
|
@@ -72,7 +72,7 @@ export function DetailFindField({ value, caret, cols }) {
|
|
|
72
72
|
* all'arrivo del mouse (T21 · SGR enable + hit-test) senza migrazione. La
|
|
73
73
|
* navigazione da tastiera ci si sovrappone senza conflitti.
|
|
74
74
|
*/
|
|
75
|
-
export function DetailScreen({ id, title, missing, lines, spans, top, total, capacity, action, columns, find, occ, occCur, }) {
|
|
75
|
+
export function DetailScreen({ id, title, missing, lines, spans, top, total, capacity, action, model, columns, find, occ, occCur, }) {
|
|
76
76
|
const last = Math.min(total, top + capacity);
|
|
77
77
|
// Il taglio lo fa il chiamante (invariante ③ di width.ts): la riga bottoni è
|
|
78
78
|
// ASCII, quindi `truncate-end` oggi darebbe il risultato giusto per caso — ma
|
|
@@ -85,13 +85,33 @@ export function DetailScreen({ id, title, missing, lines, spans, top, total, cap
|
|
|
85
85
|
parts.push(' ');
|
|
86
86
|
parts.push(s);
|
|
87
87
|
});
|
|
88
|
-
const dropped = (v) =>
|
|
88
|
+
const dropped = (v, of) => of.filter((s, i) => v[i * 2] !== s).length;
|
|
89
89
|
// Due passate: la seconda serve SOLO quando qualcosa cade, e riserva le
|
|
90
90
|
// colonne del contatore. Riservarle sempre costerebbe 6 colonne su ogni
|
|
91
91
|
// terminale largo per un avviso che lì non comparirà mai.
|
|
92
92
|
let shown = cutParts(parts, width);
|
|
93
|
-
if (dropped(shown) > 0)
|
|
93
|
+
if (dropped(shown, segs) > 0)
|
|
94
94
|
shown = cutParts(parts, Math.max(0, width - 6));
|
|
95
|
-
const cutCount = dropped(shown);
|
|
96
|
-
|
|
95
|
+
const cutCount = dropped(shown, segs);
|
|
96
|
+
// T108 — riga del selettore modello, stesso schema di bottoni affiancati della
|
|
97
|
+
// barra azioni. `priority` sulla voce SELEZIONATA e non sulla prima: qui il
|
|
98
|
+
// troncamento cancellerebbe l'unica informazione che la riga esiste per dare
|
|
99
|
+
// — quale modello sta per essere usato — mentre nella barra azioni la voce
|
|
100
|
+
// attiva è comunque nota dal tasto appena premuto.
|
|
101
|
+
const mSegs = MODELS.map((m, i) => `${i + 1} ${m}`);
|
|
102
|
+
const mParts = [];
|
|
103
|
+
mSegs.forEach((s, i) => {
|
|
104
|
+
if (i > 0)
|
|
105
|
+
mParts.push(' ');
|
|
106
|
+
mParts.push(s);
|
|
107
|
+
});
|
|
108
|
+
const mIdx = Math.max(0, MODELS.indexOf(model));
|
|
109
|
+
// `- 8` = le colonne del prefisso "modello ", che sta fuori dai parts perché
|
|
110
|
+
// non è un bottone e non deve mai cadere.
|
|
111
|
+
const mWidth = Math.max(0, width - 8);
|
|
112
|
+
let mShown = cutParts(mParts, mWidth, mIdx * 2);
|
|
113
|
+
if (dropped(mShown, mSegs) > 0)
|
|
114
|
+
mShown = cutParts(mParts, Math.max(0, mWidth - 6), mIdx * 2);
|
|
115
|
+
const mCut = dropped(mShown, mSegs);
|
|
116
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: "loom-deck" }), _jsxs(Text, { dimColor: true, wrap: "truncate-end", children: [_jsx(Text, { color: "cyan", children: id }), " \u00B7 ", cut(title, Math.max(10, width - 34)), missing ? '' : ` · righe ${total === 0 ? 0 : top + 1}-${last} di ${total}`] }), find?.open ? (_jsxs(Text, { dimColor: true, wrap: "truncate-end", children: [_jsx(Text, { color: "yellow", children: "\u2191\u2193" }), " occorrenza \u00B7 ", _jsx(Text, { color: "yellow", children: "\u2190\u2192" }), " caret \u00B7", ' ', _jsx(Text, { color: "yellow", children: "^U" }), " svuota \u00B7 ", _jsx(Text, { color: "yellow", children: "\u23CE" }), " tieni \u00B7", ' ', _jsx(Text, { color: "yellow", children: "esc" }), " annulla"] })) : (_jsxs(Text, { dimColor: true, wrap: "truncate-end", children: [_jsx(Text, { color: "yellow", children: "\u2191\u2193" }), " riga \u00B7 ", _jsx(Text, { color: "yellow", children: "PgUp/PgDn" }), " pagina \u00B7", ' ', _jsx(Text, { color: "yellow", children: "g/G" }), " estremi \u00B7 ", _jsx(Text, { color: "yellow", children: "\u2190\u2192" }), " azione \u00B7", ' ', _jsx(Text, { color: "yellow", children: "1-4/tab" }), " modello \u00B7 ", _jsx(Text, { color: "yellow", children: "^F" }), " cerca \u00B7", ' ', _jsx(Text, { color: "yellow", children: "\u23CE" }), " esegui \u00B7 ", _jsx(Text, { color: "yellow", children: "esc" }), " chiudi"] })), _jsx(Box, { flexDirection: "column", borderStyle: "single", borderColor: "gray", paddingX: 1, marginTop: 1, children: missing ? (_jsxs(Text, { color: "yellow", wrap: "truncate-end", children: [WARN, " task file non trovato \u00B7 le azioni restano attive (deck-run risolve la task per id)"] })) : (lines.map((l, i) => (_jsx(DetailLine, { line: l, spans: spans, occ: occ, current: occCur }, top + i)))) }), find?.open ? (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { wrap: "truncate-end", children: [_jsx(Text, { dimColor: true, children: "cerca " }), _jsx(DetailFindField, { value: find.q, caret: find.caret, cols: Math.max(10, width - 28) }), find.q.length === 0 ? (_jsx(Text, { dimColor: true, children: " \u00B7 digita per cercare" })) : occ.length === 0 ? (_jsx(Text, { color: "yellow", children: " \u00B7 nessuna occorrenza" })) : (_jsxs(Text, { color: "cyan", children: [' ', "\u00B7 ", occCur + 1, "/", occ.length] }))] }) })) : null, _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Text, { wrap: "truncate-end", children: [_jsx(Text, { dimColor: true, children: "modello " }), mShown.map((part, i) => i % 2 === 1 ? (_jsx(Text, { children: part }, i)) : (_jsx(Text, { bold: i / 2 === mIdx, color: i / 2 === mIdx ? 'green' : 'gray', children: part }, i))), mCut > 0 ? _jsxs(Text, { color: "yellow", children: [" \u00B7 +", mCut] }) : null] }), _jsxs(Text, { wrap: "truncate-end", children: [shown.map((part, i) => i % 2 === 1 ? (_jsx(Text, { children: part }, i)) : (_jsx(Text, { inverse: i / 2 === action, color: i / 2 === action ? 'green' : 'gray', children: part }, i))), cutCount > 0 ? _jsxs(Text, { color: "yellow", children: [" \u00B7 +", cutCount] }) : null] })] })] }));
|
|
97
117
|
}
|
package/dist/viewport.js
CHANGED
|
@@ -217,13 +217,14 @@ export function readerCapacity(rows) {
|
|
|
217
217
|
// 1 marginTop del box testo
|
|
218
218
|
// 2 bordi del box testo
|
|
219
219
|
// 1 marginTop della riga azioni
|
|
220
|
+
// 1 riga modello (T108)
|
|
220
221
|
// 1 riga azioni
|
|
221
222
|
//
|
|
222
|
-
// Le ultime
|
|
223
|
-
//
|
|
224
|
-
// aggiunta va scalata dalla capienza del contenuto o il frame
|
|
225
|
-
// (stessa invariante di TASKS_PANE_CHROME).
|
|
226
|
-
const DETAIL_CHROME =
|
|
223
|
+
// Le ultime tre sono il motivo per cui il detail non può riusare READER_CHROME:
|
|
224
|
+
// barra bottoni e selettore modello sono righe FISSE in più dentro l'overlay, e
|
|
225
|
+
// ogni riga fissa aggiunta va scalata dalla capienza del contenuto o il frame
|
|
226
|
+
// sfonda `rows` (stessa invariante di TASKS_PANE_CHROME).
|
|
227
|
+
const DETAIL_CHROME = 11;
|
|
227
228
|
// T91 — la ricerca dentro il detail: marginTop + riga del campo.
|
|
228
229
|
//
|
|
229
230
|
// `MODAL_HEIGHT` dice che il detail costa 0 ai due pane (li sostituisce), e da
|
package/package.json
CHANGED
package/scripts/deck-run
CHANGED
|
@@ -8,16 +8,19 @@
|
|
|
8
8
|
# "recap stato task <TaskID>" — no skill, solo recap → più controllo).
|
|
9
9
|
# Riusabile identico da TUI (Ink) e da web.
|
|
10
10
|
#
|
|
11
|
-
#
|
|
11
|
+
# Quattro assi ORTOGONALI, da non confondere fra loro:
|
|
12
12
|
# binding task <TaskID> ..... --no-task
|
|
13
13
|
# continuità nuova ........ --resume [--fork]
|
|
14
14
|
# prompt --prompt-kind none|recap|preflight|run|checkpoint
|
|
15
|
+
# modello --model fable|opus|sonnet|haiku
|
|
15
16
|
# Il terzo asse (T56) prima non esisteva: il prompt era una CONSEGUENZA degli
|
|
16
17
|
# altri due (bound ⇒ recap, --no-task ⇒ niente, --resume ⇒ niente), quindi
|
|
17
18
|
# "task-bound SENZA prompt" era inesprimibile e l'unico modo di non avere un
|
|
18
19
|
# prompt era perdere la task. Il catalogo dei prompt vive qui e non nel deck
|
|
19
20
|
# perché questo è il primitive UI-agnostico: il chiamante passa un SIMBOLO, non
|
|
20
21
|
# una stringa — il quoting resta verificato in un posto solo (vedi PROMPT sotto).
|
|
22
|
+
# Il quarto asse (T108) è indipendente dagli altri tre: vale sul ramo bound come
|
|
23
|
+
# su --no-task, su una sessione nuova come su una ripresa.
|
|
21
24
|
#
|
|
22
25
|
# Con --no-task la sessione è NUDA: niente LOOM_TASK, niente prompt iniziale,
|
|
23
26
|
# niente --session-id. Serve al lavoro spot che non appartiene a nessuna task.
|
|
@@ -43,6 +46,7 @@
|
|
|
43
46
|
# LOOM_DECK_STATE_PROFILE PTYXIS_PROFILE annunciata a compass (default: bindings/claude
|
|
44
47
|
# del progetto, letto da dconf; settata a vuoto = nessun annuncio)
|
|
45
48
|
# LOOM_DECK_PERMISSION_MODE override del permissionMode del file config (default: campo file, poi 'manual')
|
|
49
|
+
# LOOM_DECK_MODEL modello quando --model non è passato (default: opus)
|
|
46
50
|
#
|
|
47
51
|
set -euo pipefail
|
|
48
52
|
|
|
@@ -56,6 +60,10 @@ TITLE_NOTE=""
|
|
|
56
60
|
# "kind implicito" da "kind chiesto", perché con --no-task il primo è legittimo
|
|
57
61
|
# (nessun prompt, come sempre) e il secondo è un errore d'uso.
|
|
58
62
|
PROMPT_KIND=""
|
|
63
|
+
# Vuoto = flag non passato: la cascata (env, poi default) si risolve più sotto,
|
|
64
|
+
# insieme alla validazione, così l'ingresso da argomento e quello da env passano
|
|
65
|
+
# per lo stesso enum.
|
|
66
|
+
MODEL=""
|
|
59
67
|
# Positional <TaskID> + flag opzionale --session-id <uuid> (T27): il deck genera
|
|
60
68
|
# l'UUID e lo pinna così il binding sidecar sessionId↔taskId è deterministico.
|
|
61
69
|
# --no-task (T42): modalità NUDA, senza TaskID — nessuna LOOM_TASK, nessun prompt
|
|
@@ -84,8 +92,17 @@ PROMPT_KIND=""
|
|
|
84
92
|
# (emoji/name dal file committato, TaskID) sono controllati — quindi il testo non
|
|
85
93
|
# si quota, si RIDUCE a un alfabeto sicuro (vedi _sane_note): tutto ciò che non
|
|
86
94
|
# ci rientra sparisce, apici inclusi.
|
|
95
|
+
# --model <fable|opus|sonnet|haiku> (T108): modello della sessione. Enum e non
|
|
96
|
+
# stringa libera perché il valore finisce dentro `bash -lc` e un refuso
|
|
97
|
+
# produrrebbe una tab con un comando che il CLI rifiuta. Le voci sono ALIAS e
|
|
98
|
+
# restano tali: espanderle a id versionati (`claude-opus-5`) cablerebbe qui una
|
|
99
|
+
# generazione, che al primo bump diventa un modello inesistente.
|
|
87
100
|
while [[ $# -gt 0 ]]; do
|
|
88
101
|
case "$1" in
|
|
102
|
+
--model)
|
|
103
|
+
MODEL="${2:-}"; shift 2 ;;
|
|
104
|
+
--model=*)
|
|
105
|
+
MODEL="${1#*=}"; shift ;;
|
|
89
106
|
--title-note)
|
|
90
107
|
TITLE_NOTE="${2:-}"; shift 2 ;;
|
|
91
108
|
--title-note=*)
|
|
@@ -112,7 +129,7 @@ while [[ $# -gt 0 ]]; do
|
|
|
112
129
|
esac
|
|
113
130
|
done
|
|
114
131
|
|
|
115
|
-
USAGE="uso: deck-run <TaskID> [--prompt-kind <kind>] [--session-id <uuid>]
|
|
132
|
+
USAGE="uso: deck-run <TaskID> [--model <alias>] [--prompt-kind <kind>] [--session-id <uuid>]
|
|
116
133
|
(es. deck-run T18)
|
|
117
134
|
| deck-run --no-task (sessione nuda, senza task)
|
|
118
135
|
| deck-run <TaskID> --resume <uuid> (riprende sessione scoped)
|
|
@@ -127,6 +144,9 @@ USAGE="uso: deck-run <TaskID> [--prompt-kind <kind>] [--session-id <uuid>]
|
|
|
127
144
|
run /loom-works:run-task <TaskID>
|
|
128
145
|
checkpoint /loom-works:checkpoint-task <TaskID>
|
|
129
146
|
|
|
147
|
+
--model modello della sessione: fable|opus|sonnet|haiku (default: opus)
|
|
148
|
+
valore ignoto → fallback sul default, con avviso su stderr
|
|
149
|
+
|
|
130
150
|
--title-note nota della conversazione, appesa al titolo tab come «nota»
|
|
131
151
|
(ridotta a lettere/cifre/spazi/-/_, cap 60 char)"
|
|
132
152
|
|
|
@@ -274,6 +294,28 @@ case "${PERM_MODE:-}" in
|
|
|
274
294
|
esac
|
|
275
295
|
MODE_FLAG="--permission-mode ${PERM_MODE} "
|
|
276
296
|
|
|
297
|
+
# ── modello (T108) ───────────────────────────────────────────────────────────
|
|
298
|
+
# Precedenza: --model > env LOOM_DECK_MODEL > 'opus'. Nessun campo nel file
|
|
299
|
+
# config: il default è unico per tutta la famiglia, e uno scalare per-progetto
|
|
300
|
+
# aggiungerebbe una chiave allo schema del plugin per un valore che il selettore
|
|
301
|
+
# del deck sovrascrive a ogni spawn.
|
|
302
|
+
# Il flag è passato SEMPRE, anche sul default, per la stessa ragione di
|
|
303
|
+
# permissionMode: lo spawn resta leggibile nel process tree invece di dipendere
|
|
304
|
+
# dal default del CLI, che cambia fra versioni.
|
|
305
|
+
# Valore fuori enum → fallback con avviso, non exit. Regime opposto a
|
|
306
|
+
# --prompt-kind, e il discriminante è dove finisce il valore: un kind ignoto si
|
|
307
|
+
# ferma DENTRO lo script (nessun template da scegliere, l'errore è del deck),
|
|
308
|
+
# un modello ignoto arriverebbe al CLI e produrrebbe una tab con un comando che
|
|
309
|
+
# fallisce all'avvio — cioè il guasto che permissionMode evita degradando.
|
|
310
|
+
MODEL="${MODEL:-${LOOM_DECK_MODEL:-}}"
|
|
311
|
+
case "${MODEL:-}" in
|
|
312
|
+
fable|opus|sonnet|haiku) ;;
|
|
313
|
+
'') MODEL="opus" ;;
|
|
314
|
+
*) echo "modello ignoto: '${MODEL}' → fallback 'opus' (usa fable|opus|sonnet|haiku)" >&2
|
|
315
|
+
MODEL="opus" ;;
|
|
316
|
+
esac
|
|
317
|
+
MODEL_FLAG="--model ${MODEL} "
|
|
318
|
+
|
|
277
319
|
# ── Profilo di stato per compass ─────────────────────────────────────────────
|
|
278
320
|
# Lo stato di una sessione (running/ask/done) viaggia verso compass via D-Bus
|
|
279
321
|
# keyed su $PTYXIS_PROFILE (hook Claude → `compass <stato>`), e compass lo mappa
|
|
@@ -388,10 +430,13 @@ PROMPT_ARG=""
|
|
|
388
430
|
# PROFILE_ENV in testa a entrambi i rami: lo stato è una proprietà della SESSIONE
|
|
389
431
|
# (esiste anche senza task), esattamente come la label del titolo — legarlo al
|
|
390
432
|
# ramo task-bound ripeterebbe l'errore di layer già evitato per la titolazione.
|
|
433
|
+
# MODEL_FLAG in entrambi i rami e prima dei flag di continuità: il modello è la
|
|
434
|
+
# scelta di chi apre la sessione, non una proprietà della task né della ripresa
|
|
435
|
+
# (T108) — stesso layer di MODE_FLAG, che infatti gli sta accanto.
|
|
391
436
|
if [[ $NO_TASK -eq 1 ]]; then
|
|
392
|
-
_default_intab="${PROFILE_ENV}claude --name '${TITLE}' ${MODE_FLAG}${SID_FLAG}${RES_FLAG}${FORK_FLAG}"
|
|
437
|
+
_default_intab="${PROFILE_ENV}claude --name '${TITLE}' ${MODE_FLAG}${MODEL_FLAG}${SID_FLAG}${RES_FLAG}${FORK_FLAG}"
|
|
393
438
|
else
|
|
394
|
-
_default_intab="${PROFILE_ENV}LOOM_TASK=${TASK} claude --name '${TITLE}' ${MODE_FLAG}${SID_FLAG}${RES_FLAG}${FORK_FLAG}${PROMPT_ARG}"
|
|
439
|
+
_default_intab="${PROFILE_ENV}LOOM_TASK=${TASK} claude --name '${TITLE}' ${MODE_FLAG}${MODEL_FLAG}${SID_FLAG}${RES_FLAG}${FORK_FLAG}${PROMPT_ARG}"
|
|
395
440
|
fi
|
|
396
441
|
IN_TAB_CMD="${LOOM_DECK_INTAB_CMD:-$_default_intab}"
|
|
397
442
|
|