@sigrun/tui 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/dist/actions.d.ts +6 -0
- package/dist/api.d.ts +8 -2
- package/dist/api.js +8 -0
- package/dist/blocks/form/form.js +22 -0
- package/dist/keymaps/emacs.js +5 -0
- package/dist/keymaps/standard.js +6 -0
- package/dist/keymaps/vim.js +5 -0
- package/dist/ui/app.js +4 -0
- package/dist/ui/interactions.d.ts +12 -0
- package/dist/ui/interactions.js +79 -2
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
8
|
|
|
9
|
+
## [0.9.0] - 2026-11-05
|
|
10
|
+
|
|
11
|
+
- Decline on declinable interaction cards (`alt+s` in answer mode; `alt+d` is the
|
|
12
|
+
notices dismiss), the armed-hint chord, and the stepper review fixes (answer
|
|
13
|
+
labels, "Not answered yet" naming, Enter guard).
|
|
14
|
+
|
|
9
15
|
## [0.8.0] - 2026-11-04
|
|
10
16
|
|
|
11
17
|
- Notices: an inline line for the current conversation's newest notice (a key dismisses
|
package/dist/actions.d.ts
CHANGED
|
@@ -63,6 +63,12 @@ export type Action = {
|
|
|
63
63
|
readonly reverse: boolean;
|
|
64
64
|
} | {
|
|
65
65
|
readonly type: "answer-interaction";
|
|
66
|
+
}
|
|
67
|
+
/** Ask-user Task 5 (spec §5.2): decline the armed interaction record
|
|
68
|
+
* (alt+s in every keymap — alt+d is the notices dismiss). Acts ONLY
|
|
69
|
+
* in answer mode on a declinable record. */
|
|
70
|
+
| {
|
|
71
|
+
readonly type: "decline-interaction";
|
|
66
72
|
} | {
|
|
67
73
|
readonly type: "cancel-run";
|
|
68
74
|
} | {
|
package/dist/api.d.ts
CHANGED
|
@@ -21,8 +21,8 @@ import type { NoticeActionResponse, NoticesResponse } from "@sigrun/shared/notic
|
|
|
21
21
|
import type { JobsResponse, JobRunResponse } from "@sigrun/shared/views";
|
|
22
22
|
import type { JobRevisionWire, JobRunWire } from "@sigrun/shared/jobs";
|
|
23
23
|
import type { SessionMeta, SessionMetaPatch } from "@sigrun/shared/session-meta";
|
|
24
|
-
import type { AbortResponse, AmbientList, CompactResponse, ConfigureResponse, ConversationCreateResponse, ConversationForkResponse, ConversationList, ConversationViewWire, InteractionAnswered, InteractionList, InteractionWire, MetaResponse, ModelsResponse, SettingsResponse, SettingsSectionsResponse, SubmissionResponse, WhoamiResponse } from "@sigrun/shared/views";
|
|
25
|
-
export type { AbortResponse, AmbientList, AmbientWire, CompactResponse, ConfigureResponse, ConversationCreateResponse, ConversationForkResponse, ConversationList, ConversationSummary, ConversationViewWire, InteractionAnswered, InteractionList, InteractionWire, MetaResponse, ModelEntry, ModelsResponse, SettingsResponse, SettingsSectionsResponse, SettingsSectionEntry, SettingsSectionIndexEntry, SubmissionResponse, WhoamiResponse, } from "@sigrun/shared/views";
|
|
24
|
+
import type { AbortResponse, AmbientList, CompactResponse, ConfigureResponse, ConversationCreateResponse, ConversationForkResponse, ConversationList, ConversationViewWire, InteractionAnswered, InteractionDeclined, InteractionList, InteractionWire, MetaResponse, ModelsResponse, SettingsResponse, SettingsSectionsResponse, SubmissionResponse, WhoamiResponse } from "@sigrun/shared/views";
|
|
25
|
+
export type { AbortResponse, AmbientList, AmbientWire, CompactResponse, ConfigureResponse, ConversationCreateResponse, ConversationForkResponse, ConversationList, ConversationSummary, ConversationViewWire, InteractionAnswered, InteractionDeclined, InteractionList, InteractionWire, MetaResponse, ModelEntry, ModelsResponse, SettingsResponse, SettingsSectionsResponse, SettingsSectionEntry, SettingsSectionIndexEntry, SubmissionResponse, WhoamiResponse, } from "@sigrun/shared/views";
|
|
26
26
|
export type { CommandRunResponse, CommandsResponse, SlashCommand } from "@sigrun/shared/commands";
|
|
27
27
|
export type { JobSpec, JobSchedule, JobAction, JobWire, JobRunWire, JobRevisionWire, } from "@sigrun/shared/jobs";
|
|
28
28
|
export type { JobsResponse, JobRunResponse } from "@sigrun/shared/views";
|
|
@@ -151,6 +151,12 @@ export declare class SigrunClient {
|
|
|
151
151
|
getInteraction(id: number, callId: string): Promise<InteractionWire>;
|
|
152
152
|
/** `POST .../interactions/:callId/answer` — the ONE generic answer route. */
|
|
153
153
|
answerInteraction(id: number, callId: string, answer: InteractionAnswerInput): Promise<InteractionAnswered>;
|
|
154
|
+
/** `POST .../interactions/:callId/decline` — decline a pending
|
|
155
|
+
* declinable record (ask-user spec §5.2; settles `skipped` with
|
|
156
|
+
* `declinedVia: "button"`). A 404 `not_pending` means the record
|
|
157
|
+
* settled under the user's fingers — the caller refetches through
|
|
158
|
+
* {@link getInteraction} (the mid-stepper settle path). */
|
|
159
|
+
declineInteraction(id: number, callId: string): Promise<InteractionDeclined>;
|
|
154
160
|
/** `GET /v1/conversations/:id/ambient`. */
|
|
155
161
|
getAmbient(id: number): Promise<AmbientList>;
|
|
156
162
|
/** `GET /v1/commands` — the slash-command catalog (a boot constant). */
|
package/dist/api.js
CHANGED
|
@@ -181,6 +181,14 @@ export class SigrunClient {
|
|
|
181
181
|
answerInteraction(id, callId, answer) {
|
|
182
182
|
return this.#send("POST", `/v1/conversations/${id}/interactions/${encodeURIComponent(callId)}/answer`, answer);
|
|
183
183
|
}
|
|
184
|
+
/** `POST .../interactions/:callId/decline` — decline a pending
|
|
185
|
+
* declinable record (ask-user spec §5.2; settles `skipped` with
|
|
186
|
+
* `declinedVia: "button"`). A 404 `not_pending` means the record
|
|
187
|
+
* settled under the user's fingers — the caller refetches through
|
|
188
|
+
* {@link getInteraction} (the mid-stepper settle path). */
|
|
189
|
+
declineInteraction(id, callId) {
|
|
190
|
+
return this.#send("POST", `/v1/conversations/${id}/interactions/${encodeURIComponent(callId)}/decline`, {});
|
|
191
|
+
}
|
|
184
192
|
/** `GET /v1/conversations/:id/ambient`. */
|
|
185
193
|
getAmbient(id) {
|
|
186
194
|
return this.#get(`/v1/conversations/${id}/ambient`);
|
package/dist/blocks/form/form.js
CHANGED
|
@@ -60,6 +60,21 @@ export function stepperSections(block, interaction) {
|
|
|
60
60
|
child.type === "choice"));
|
|
61
61
|
return allChoice ? sections : null;
|
|
62
62
|
}
|
|
63
|
+
/** The unanswered REQUIRED choices of the stepper (the review names
|
|
64
|
+
* them and holds the submit — §7.2 of the ask-user design). Answered
|
|
65
|
+
* means choiceValueText yields text (absent/""/[]/{other: ""} do not
|
|
66
|
+
* count). */
|
|
67
|
+
function unansweredSections(sections, ctx, values) {
|
|
68
|
+
return sections.filter((section) => {
|
|
69
|
+
for (const field of sectionFields(section)) {
|
|
70
|
+
if (field.type !== "choice" || field.required !== true)
|
|
71
|
+
continue;
|
|
72
|
+
if (choiceValueText(field, ctx, values[field.name] ?? null) === "")
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
return false;
|
|
76
|
+
});
|
|
77
|
+
}
|
|
63
78
|
/** The answerable fields of a stepper section (every field the review
|
|
64
79
|
* summarizes — §9.1's review step covers ALL of a section's fields,
|
|
65
80
|
* not only the choice). */
|
|
@@ -310,6 +325,9 @@ export class FormComponent {
|
|
|
310
325
|
const summary = answers.filter((text) => text !== "").join(", ");
|
|
311
326
|
lines.push(truncate(` ${title}: ${summary}`, width));
|
|
312
327
|
});
|
|
328
|
+
for (const section of unansweredSections(sections, this.#ctx, this.#values)) {
|
|
329
|
+
lines.push(truncate(`Not answered yet: ${bindText(section.title, this.#ctx)}`, width));
|
|
330
|
+
}
|
|
313
331
|
if (!this.#readOnly && !this.#submitted) {
|
|
314
332
|
lines.push(truncate(`> [ ${this.#submitLabel} ]`, width));
|
|
315
333
|
}
|
|
@@ -435,6 +453,10 @@ export class FormComponent {
|
|
|
435
453
|
return;
|
|
436
454
|
}
|
|
437
455
|
if (data === "\r" && !this.#submitted) {
|
|
456
|
+
// The review holds the submit while a required question is
|
|
457
|
+
// unanswered (§7.2) — Enter is ignored, not an error.
|
|
458
|
+
if (unansweredSections(sections, this.#ctx, this.#values).length > 0)
|
|
459
|
+
return;
|
|
438
460
|
if (this.#submit())
|
|
439
461
|
this.#submitted = true;
|
|
440
462
|
}
|
package/dist/keymaps/emacs.js
CHANGED
|
@@ -32,6 +32,7 @@ const BINDINGS = [
|
|
|
32
32
|
{ keys: "C-n / C-p", action: "move down / up" },
|
|
33
33
|
{ keys: "enter", action: "open / fold" },
|
|
34
34
|
{ keys: "C-o", action: "answer interaction" },
|
|
35
|
+
{ keys: "M-s", action: "decline interaction (answer mode)" },
|
|
35
36
|
{ keys: "C-l", action: "redraw" },
|
|
36
37
|
{ keys: "C-h", action: "key reference" },
|
|
37
38
|
{ keys: "M-N", action: "notices" },
|
|
@@ -147,6 +148,10 @@ export function createEmacsKeymap() {
|
|
|
147
148
|
return { type: "prompt-jump", delta: 1 };
|
|
148
149
|
if (data === "\x0f")
|
|
149
150
|
return { type: "answer-interaction" };
|
|
151
|
+
// Ask-user Task 5: M-s declines the armed interaction (M-d stays
|
|
152
|
+
// the notices dismiss; the app gates decline to answer mode).
|
|
153
|
+
if (data === "\x1bs")
|
|
154
|
+
return { type: "decline-interaction" };
|
|
150
155
|
if (data === "\x0c")
|
|
151
156
|
return { type: "redraw" };
|
|
152
157
|
if (data === "\x08")
|
package/dist/keymaps/standard.js
CHANGED
|
@@ -21,6 +21,7 @@ const BINDINGS = [
|
|
|
21
21
|
{ keys: "ctrl+n", action: "new conversation" },
|
|
22
22
|
{ keys: "ctrl+t", action: "cycle thinking" },
|
|
23
23
|
{ keys: "ctrl+o", action: "answer interaction" },
|
|
24
|
+
{ keys: "alt+s", action: "decline interaction (answer mode)" },
|
|
24
25
|
{ keys: "ctrl+g", action: "notices" },
|
|
25
26
|
{ keys: "alt+d", action: "dismiss newest notice" },
|
|
26
27
|
{ keys: "ctrl+l", action: "redraw" },
|
|
@@ -60,6 +61,11 @@ export function createStandardKeymap() {
|
|
|
60
61
|
return { type: "cycle-thinking" };
|
|
61
62
|
if (matchesKey(data, Key.ctrl("o")))
|
|
62
63
|
return { type: "answer-interaction" };
|
|
64
|
+
// Ask-user Task 5: alt+s declines the armed interaction (answer
|
|
65
|
+
// mode only — the app gates the action; outside answer mode this
|
|
66
|
+
// falls through as a no-op). alt+d stays the notices dismiss.
|
|
67
|
+
if (matchesKey(data, Key.alt("s")))
|
|
68
|
+
return { type: "decline-interaction" };
|
|
63
69
|
// Notices Task 8: ctrl+g (the notices overlay — a control code,
|
|
64
70
|
// never typed text) and alt+d (dismiss the inline notice; M-d is
|
|
65
71
|
// readline's kill-word, a chord reserved readline never sends).
|
package/dist/keymaps/vim.js
CHANGED
|
@@ -47,6 +47,7 @@ const BINDINGS = [
|
|
|
47
47
|
{ keys: ":", action: "command line" },
|
|
48
48
|
{ keys: "ctrl+p", action: "conversation picker" },
|
|
49
49
|
{ keys: "ctrl+o", action: "answer interaction" },
|
|
50
|
+
{ keys: "alt+s", action: "decline interaction (answer mode)" },
|
|
50
51
|
{ keys: "ctrl+l", action: "redraw" },
|
|
51
52
|
{ keys: "ctrl+c", action: "abort run (busy)" },
|
|
52
53
|
{ keys: "g?", action: "key reference" },
|
|
@@ -132,6 +133,10 @@ export function createVimKeymap(options) {
|
|
|
132
133
|
}
|
|
133
134
|
if (matchesKey(data, Key.ctrl("o")))
|
|
134
135
|
return { type: "answer-interaction" };
|
|
136
|
+
// Ask-user Task 5: alt+s declines the armed interaction (the app
|
|
137
|
+
// gates the action to answer mode + a declinable record).
|
|
138
|
+
if (matchesKey(data, Key.alt("s")))
|
|
139
|
+
return { type: "decline-interaction" };
|
|
135
140
|
return "pass";
|
|
136
141
|
};
|
|
137
142
|
/** Block-surface keys (spec §12.4): NORMAL mode over a `surface`
|
package/dist/ui/app.js
CHANGED
|
@@ -1149,6 +1149,10 @@ export class TuiApp {
|
|
|
1149
1149
|
if (!this.interactions.answerMode)
|
|
1150
1150
|
this.interactions.beginAnswer();
|
|
1151
1151
|
return;
|
|
1152
|
+
case "decline-interaction":
|
|
1153
|
+
// Gated inside: answer mode + a declinable record only.
|
|
1154
|
+
await this.interactions.decline();
|
|
1155
|
+
return;
|
|
1152
1156
|
case "cancel-run":
|
|
1153
1157
|
this.#abortActive();
|
|
1154
1158
|
return;
|
|
@@ -79,6 +79,18 @@ export declare class InteractionPrompter implements Component {
|
|
|
79
79
|
* keep typing while a prompt waits).
|
|
80
80
|
*/
|
|
81
81
|
handleKey(data: string): boolean;
|
|
82
|
+
/**
|
|
83
|
+
* Decline the armed interaction (ask-user spec §5.2 — the keymap's
|
|
84
|
+
* `decline-interaction` action routes here). Gated exactly like the
|
|
85
|
+
* affordance: answer mode armed on the top record, and the record is
|
|
86
|
+
* declinable. Success exits answer mode (the list poll drops the
|
|
87
|
+
* settled record). A 404 `not_pending` — the record settled under
|
|
88
|
+
* the user's fingers — refetches through
|
|
89
|
+
* {@link SigrunClient.getInteraction} and swaps the settled envelope
|
|
90
|
+
* in read-only (the mid-edit settle path); any other failure reports
|
|
91
|
+
* loud and keeps the card writable.
|
|
92
|
+
*/
|
|
93
|
+
decline(): Promise<void>;
|
|
82
94
|
/** Detach (the app calls this on dispose): stale fetches never land. */
|
|
83
95
|
dispose(): void;
|
|
84
96
|
/**
|
package/dist/ui/interactions.js
CHANGED
|
@@ -174,6 +174,13 @@ export class InteractionPrompter {
|
|
|
174
174
|
this.#changed();
|
|
175
175
|
return true;
|
|
176
176
|
}
|
|
177
|
+
// The decline chord is the KEYMAP's (decline-interaction action,
|
|
178
|
+
// dispatched by the launcher): pass it through so the keymap sees
|
|
179
|
+
// it — the surface fall-through below would swallow it and the
|
|
180
|
+
// chord would never fire (review fix; the same ownership rule as
|
|
181
|
+
// the prompt's other action keys).
|
|
182
|
+
if (matchesKey(data, Key.alt("s")))
|
|
183
|
+
return false;
|
|
177
184
|
this.#ensureSurface(top);
|
|
178
185
|
if (this.#surface === undefined) {
|
|
179
186
|
this.#exitAnswer();
|
|
@@ -332,6 +339,67 @@ export class InteractionPrompter {
|
|
|
332
339
|
this.#answerMode = false;
|
|
333
340
|
this.#answerCallId = undefined;
|
|
334
341
|
}
|
|
342
|
+
/**
|
|
343
|
+
* Decline the armed interaction (ask-user spec §5.2 — the keymap's
|
|
344
|
+
* `decline-interaction` action routes here). Gated exactly like the
|
|
345
|
+
* affordance: answer mode armed on the top record, and the record is
|
|
346
|
+
* declinable. Success exits answer mode (the list poll drops the
|
|
347
|
+
* settled record). A 404 `not_pending` — the record settled under
|
|
348
|
+
* the user's fingers — refetches through
|
|
349
|
+
* {@link SigrunClient.getInteraction} and swaps the settled envelope
|
|
350
|
+
* in read-only (the mid-edit settle path); any other failure reports
|
|
351
|
+
* loud and keeps the card writable.
|
|
352
|
+
*/
|
|
353
|
+
async decline() {
|
|
354
|
+
const top = this.#pending[0];
|
|
355
|
+
if (!this.#answerMode || top === undefined)
|
|
356
|
+
return;
|
|
357
|
+
if (this.#answerCallId !== top.callId)
|
|
358
|
+
return;
|
|
359
|
+
if (top.declinable !== true)
|
|
360
|
+
return;
|
|
361
|
+
const id = this.#conversationId;
|
|
362
|
+
if (id === undefined)
|
|
363
|
+
return;
|
|
364
|
+
try {
|
|
365
|
+
await this.#client.declineInteraction(id, top.callId);
|
|
366
|
+
}
|
|
367
|
+
catch (cause) {
|
|
368
|
+
const isNotPending = typeof cause === "object" &&
|
|
369
|
+
cause !== null &&
|
|
370
|
+
cause.status === 404;
|
|
371
|
+
if (!isNotPending) {
|
|
372
|
+
this.onError?.(describeCommandError("decline", cause));
|
|
373
|
+
this.#changed();
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
// 404 not_pending: the settle raced the user. Refetch; a still-
|
|
377
|
+
// pending record must NOT wedge the card read-only.
|
|
378
|
+
try {
|
|
379
|
+
const fresh = await this.#client.getInteraction(id, top.callId);
|
|
380
|
+
if (this.#surface?.record.callId !== top.callId)
|
|
381
|
+
return;
|
|
382
|
+
if (fresh.status === "pending") {
|
|
383
|
+
this.onError?.(describeCommandError("decline", cause));
|
|
384
|
+
this.#changed();
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
this.#surface =
|
|
388
|
+
this.#surface === undefined
|
|
389
|
+
? undefined
|
|
390
|
+
: { ...this.#surface, settled: fresh, access: "read" };
|
|
391
|
+
this.#surface?.state.setAccess("read");
|
|
392
|
+
}
|
|
393
|
+
catch {
|
|
394
|
+
this.onError?.(describeCommandError("decline", cause));
|
|
395
|
+
this.#changed();
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
this.#exitAnswer();
|
|
400
|
+
void this.#refresh();
|
|
401
|
+
this.#changed();
|
|
402
|
+
}
|
|
335
403
|
/** Generation-guarded, coalesced fetch of the pending list. */
|
|
336
404
|
async #refresh() {
|
|
337
405
|
const id = this.#conversationId;
|
|
@@ -408,11 +476,18 @@ export class InteractionPrompter {
|
|
|
408
476
|
return [];
|
|
409
477
|
const lines = [];
|
|
410
478
|
const shown = this.#surface?.settled ?? top;
|
|
479
|
+
// Settled declines name their outcome (spec §5.2's "declined" /
|
|
480
|
+
// "declined — replied in chat"), never the raw 'skipped'.
|
|
481
|
+
const statusText = shown.status === "skipped" && shown.declinedVia !== undefined
|
|
482
|
+
? shown.declinedVia === "message"
|
|
483
|
+
? "declined — replied in chat"
|
|
484
|
+
: "declined"
|
|
485
|
+
: shown.status;
|
|
411
486
|
const suffix = this.#answerMode
|
|
412
487
|
? " — answering"
|
|
413
488
|
: shown.status === "pending"
|
|
414
489
|
? ""
|
|
415
|
-
: ` — ${
|
|
490
|
+
: ` — ${statusText}`;
|
|
416
491
|
lines.push(`[interaction] ${shown.kind}${suffix}`);
|
|
417
492
|
if (top.body !== undefined) {
|
|
418
493
|
this.#ensureSurface(top);
|
|
@@ -428,7 +503,9 @@ export class InteractionPrompter {
|
|
|
428
503
|
}
|
|
429
504
|
if (top.body !== undefined) {
|
|
430
505
|
lines.push(this.#answerMode
|
|
431
|
-
?
|
|
506
|
+
? top.declinable === true
|
|
507
|
+
? "1-9 answer · alt+s decline · Tab next · esc cancel"
|
|
508
|
+
: "1-9 answer · Tab next · esc cancel"
|
|
432
509
|
: "ctrl+o to answer");
|
|
433
510
|
}
|
|
434
511
|
return lines;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sigrun/tui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "sigrun terminal client (pi-tui based) over the \u00a75 API \u2014 spawned by the sigrun CLI, or standalone against a server URL",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"@earendil-works/pi-tui": "^1.3.0",
|
|
24
|
-
"@sigrun/shared": "0.
|
|
24
|
+
"@sigrun/shared": "0.9.0"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"typescript": "^7.0.2",
|
|
@@ -48,4 +48,4 @@
|
|
|
48
48
|
"access": "public",
|
|
49
49
|
"provenance": true
|
|
50
50
|
}
|
|
51
|
-
}
|
|
51
|
+
}
|