@guuey/chat 0.4.0 → 0.6.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/dist/hitl.d.ts +58 -0
- package/dist/hitl.d.ts.map +1 -0
- package/dist/hitl.js +81 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/native/components.d.ts +110 -0
- package/dist/native/components.d.ts.map +1 -0
- package/dist/native/components.js +341 -0
- package/dist/native/markdown.d.ts +31 -0
- package/dist/native/markdown.d.ts.map +1 -0
- package/dist/native/markdown.js +69 -0
- package/dist/native/theme-native.d.ts +27 -0
- package/dist/native/theme-native.d.ts.map +1 -0
- package/dist/native/theme-native.js +28 -0
- package/dist/native/transcript.d.ts +48 -0
- package/dist/native/transcript.d.ts.map +1 -0
- package/dist/native/transcript.js +86 -0
- package/dist/native.d.ts +26 -0
- package/dist/native.d.ts.map +1 -0
- package/dist/native.js +27 -0
- package/dist/plan.d.ts +15 -0
- package/dist/plan.d.ts.map +1 -1
- package/dist/plan.js +211 -13
- package/dist/policy.d.ts +4 -0
- package/dist/policy.d.ts.map +1 -1
- package/dist/policy.js +2 -0
- package/dist/react/components.d.ts +42 -5
- package/dist/react/components.d.ts.map +1 -1
- package/dist/react/components.js +51 -1
- package/dist/react/guuey-chat.d.ts +62 -4
- package/dist/react/guuey-chat.d.ts.map +1 -1
- package/dist/react/guuey-chat.js +73 -8
- package/dist/react/transcript.d.ts +1 -1
- package/dist/react/transcript.d.ts.map +1 -1
- package/dist/react/transcript.js +2 -2
- package/dist/react/use-transcript.d.ts +21 -2
- package/dist/react/use-transcript.d.ts.map +1 -1
- package/dist/react/use-transcript.js +57 -3
- package/dist/react.d.ts +2 -2
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +1 -1
- package/dist/strings.d.ts +13 -0
- package/dist/strings.d.ts.map +1 -1
- package/dist/strings.js +8 -0
- package/dist/types.d.ts +146 -7
- package/dist/types.d.ts.map +1 -1
- package/package.json +18 -7
- package/src/corpus/README.md +13 -1
- package/src/corpus/__snapshots__/corpus.test.ts.snap +304 -0
- package/src/corpus/drive.ts +3 -3
- package/src/corpus/fixtures.ts +220 -1
- package/src/hitl.ts +103 -0
- package/src/index.ts +15 -0
- package/src/native/components.tsx +812 -0
- package/src/native/markdown.tsx +205 -0
- package/src/native/theme-native.ts +48 -0
- package/src/native/transcript.tsx +189 -0
- package/src/native.tsx +63 -0
- package/src/plan.ts +233 -20
- package/src/policy.ts +4 -0
- package/src/react/components.tsx +171 -8
- package/src/react/guuey-chat.tsx +143 -9
- package/src/react/transcript.tsx +4 -3
- package/src/react/use-transcript.ts +83 -5
- package/src/react.tsx +3 -1
- package/src/strings.ts +25 -0
- package/src/types.ts +152 -6
- package/styles.css +21 -0
package/src/corpus/drive.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { Reducer, type AgEvent } from "@silverprotocol/core";
|
|
9
9
|
import type { InvokeTurnEvent } from "@guuey/agent-client";
|
|
10
|
-
import type {
|
|
10
|
+
import type { ProfilePromptInput, TranscriptInputs, TranscriptMessage } from "../types.js";
|
|
11
11
|
|
|
12
12
|
export interface DriveOptions {
|
|
13
13
|
userText?: string;
|
|
@@ -22,7 +22,7 @@ export interface DriveOptions {
|
|
|
22
22
|
historyCards?: TranscriptInputs["historyCards"];
|
|
23
23
|
historyState?: TranscriptInputs["historyState"];
|
|
24
24
|
/** Resolve accumulated prompts to this state (fixture 4's answered leg). */
|
|
25
|
-
promptState?:
|
|
25
|
+
promptState?: ProfilePromptInput["state"];
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
export function driveTurn(events: InvokeTurnEvent[], opts: DriveOptions = {}): TranscriptInputs {
|
|
@@ -32,7 +32,7 @@ export function driveTurn(events: InvokeTurnEvent[], opts: DriveOptions = {}): T
|
|
|
32
32
|
let assistantText = "";
|
|
33
33
|
let activeTool: string | null = null;
|
|
34
34
|
let error: TranscriptInputs["error"] = null;
|
|
35
|
-
const prompts:
|
|
35
|
+
const prompts: ProfilePromptInput[] = [];
|
|
36
36
|
let done = false;
|
|
37
37
|
|
|
38
38
|
for (const ev of events) {
|
package/src/corpus/fixtures.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* THE STANDING RULE (see README.md): a new weird transcript found in
|
|
9
9
|
* production becomes a fixture BEFORE its fix lands. The corpus only grows.
|
|
10
10
|
*/
|
|
11
|
-
import type
|
|
11
|
+
import { Reducer, type AgEvent } from "@silverprotocol/core";
|
|
12
12
|
import type { InvokeTurnEvent } from "@guuey/agent-client";
|
|
13
13
|
import type { TranscriptInputs } from "../types.js";
|
|
14
14
|
import {
|
|
@@ -320,6 +320,99 @@ export function userSendFailure(): TranscriptInputs {
|
|
|
320
320
|
});
|
|
321
321
|
}
|
|
322
322
|
|
|
323
|
+
/**
|
|
324
|
+
* 19. persisted-plus-live — the turn-scoped fold composition (portal's
|
|
325
|
+
* persisted-overlay data model): a persisted prefix the session's Reducer
|
|
326
|
+
* never saw + a session fold covering the trailing turns, one settled (its
|
|
327
|
+
* card ALSO persisted — the dedupe case) and one live with a running tool.
|
|
328
|
+
*/
|
|
329
|
+
export function persistedPlusLive(): TranscriptInputs {
|
|
330
|
+
const reducer = new Reducer();
|
|
331
|
+
let seq = 0;
|
|
332
|
+
const s = (): number => seq++;
|
|
333
|
+
const push = (events: AgEvent[]): void => {
|
|
334
|
+
for (const ev of events) reducer.push(ev);
|
|
335
|
+
};
|
|
336
|
+
// Session turn A (settled — turn.done carries the outcome):
|
|
337
|
+
push([
|
|
338
|
+
{ type: "turn.start", threadId: "thread-corpus", turnId: "turn-a", seq: s() },
|
|
339
|
+
{ type: "message.start", id: "msg-a", role: "assistant", turnId: "turn-a", threadId: "thread-corpus", seq: s() },
|
|
340
|
+
{ type: "text.start", id: "ta", seq: s() },
|
|
341
|
+
{ type: "text.delta", id: "ta", delta: "Booked the second option.", seq: s() },
|
|
342
|
+
{ type: "text.end", id: "ta", seq: s() },
|
|
343
|
+
{ type: "tool.start", toolCallId: "tA", name: "ggui_render", seq: s() },
|
|
344
|
+
{
|
|
345
|
+
type: "tool.done",
|
|
346
|
+
toolCallId: "tA",
|
|
347
|
+
content: [],
|
|
348
|
+
outcome: "ok",
|
|
349
|
+
uiData: { sessionId: GGUI_SLICE.sessionId, resourceUri: GGUI_RESOURCE_URI, action: "create" },
|
|
350
|
+
_meta: { [GGUI_META_KEY]: GGUI_SLICE },
|
|
351
|
+
seq: s(),
|
|
352
|
+
},
|
|
353
|
+
{ type: "turn.done", turnId: "turn-a", outcome: { type: "success" }, seq: s() },
|
|
354
|
+
]);
|
|
355
|
+
// Session turn B (live — no turn.done yet): one completed tool already in
|
|
356
|
+
// the fold, a second still active (the fold materializes a tool-call at
|
|
357
|
+
// `tool.done`; the ACTIVE call is the status line's job, not a row).
|
|
358
|
+
push([
|
|
359
|
+
{ type: "turn.start", threadId: "thread-corpus", turnId: "turn-b", seq: s() },
|
|
360
|
+
{ type: "message.start", id: "msg-b", role: "assistant", turnId: "turn-b", threadId: "thread-corpus", seq: s() },
|
|
361
|
+
{ type: "tool.start", toolCallId: "tB", name: "load_booking", turnId: "turn-b", seq: s() },
|
|
362
|
+
{ type: "tool.args.assembled", toolCallId: "tB", input: { id: "second" }, turnId: "turn-b", seq: s() },
|
|
363
|
+
{
|
|
364
|
+
type: "tool.done",
|
|
365
|
+
toolCallId: "tB",
|
|
366
|
+
content: [{ type: "text", text: "loaded" }],
|
|
367
|
+
outcome: "ok",
|
|
368
|
+
turnId: "turn-b",
|
|
369
|
+
seq: s(),
|
|
370
|
+
},
|
|
371
|
+
]);
|
|
372
|
+
return {
|
|
373
|
+
result: reducer.result(),
|
|
374
|
+
assistantText: "",
|
|
375
|
+
status: "using-tool",
|
|
376
|
+
statusElapsedMs: 0,
|
|
377
|
+
activeTool: "check_calendar",
|
|
378
|
+
error: null,
|
|
379
|
+
prompts: [],
|
|
380
|
+
// The flat transcript: one PERSISTED turn the fold never saw, then the
|
|
381
|
+
// session turns (turn A's settled text row is present — the thread
|
|
382
|
+
// viewer's liveTail/refetch carries it; turn B is in flight, no row).
|
|
383
|
+
messages: [
|
|
384
|
+
{ role: "user", text: "plan my trip" },
|
|
385
|
+
{ role: "assistant", text: "Here are some options." },
|
|
386
|
+
{ role: "user", text: "book the second one" },
|
|
387
|
+
{ role: "assistant", text: "Booked the second option." },
|
|
388
|
+
{ role: "user", text: "now move it to Thursday" },
|
|
389
|
+
],
|
|
390
|
+
historyCards: [
|
|
391
|
+
// An OLD persisted card (pre-session) — must render.
|
|
392
|
+
{
|
|
393
|
+
seq: 1,
|
|
394
|
+
at: "2026-08-15T00:00:00Z",
|
|
395
|
+
cardSnapshot: {
|
|
396
|
+
parts: [
|
|
397
|
+
{ type: "tool-result", toolCallId: "old1", content: [], uiData: { resourceUri: "ui://ggui/render/old/1" } },
|
|
398
|
+
],
|
|
399
|
+
},
|
|
400
|
+
},
|
|
401
|
+
// Turn A's card, ALSO persisted by the read plane — the live fold's
|
|
402
|
+
// mount owns this identity; the snapshot must dedupe away.
|
|
403
|
+
{
|
|
404
|
+
seq: 5,
|
|
405
|
+
at: "2026-08-15T00:05:00Z",
|
|
406
|
+
cardSnapshot: {
|
|
407
|
+
parts: [
|
|
408
|
+
{ type: "tool-result", toolCallId: "tA", content: [], uiData: { resourceUri: GGUI_RESOURCE_URI } },
|
|
409
|
+
],
|
|
410
|
+
},
|
|
411
|
+
},
|
|
412
|
+
],
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
|
|
323
416
|
/** 17. stalled-then-adopted — #192: calm identical, debug carries the marker. */
|
|
324
417
|
export function stalledThenAdopted(): { adopted: TranscriptInputs; streamed: TranscriptInputs } {
|
|
325
418
|
const build = (): InvokeTurnEvent[] => {
|
|
@@ -336,3 +429,129 @@ export function stalledThenAdopted(): { adopted: TranscriptInputs; streamed: Tra
|
|
|
336
429
|
streamed: driveTurn(build(), { userText: "move my booking" }),
|
|
337
430
|
};
|
|
338
431
|
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* 20. hitl-grant-modes (spec draft.2, silverprotocol#16) — a paused turn's
|
|
435
|
+
* PERSISTED ask declares three asker-scoped accept variants. Driven through
|
|
436
|
+
* the real Reducer: the declaration the card renders from is
|
|
437
|
+
* `turn.done.outcome:"paused".asks[]`, exactly what a late-joining client
|
|
438
|
+
* sees. Ids are deliberately arbitrary strings — display must key off
|
|
439
|
+
* label/description, never id semantics.
|
|
440
|
+
*/
|
|
441
|
+
export function hitlGrantModes(): TranscriptInputs {
|
|
442
|
+
const reducer = new Reducer();
|
|
443
|
+
let seq = 0;
|
|
444
|
+
const s = (): number => seq++;
|
|
445
|
+
const events: AgEvent[] = [
|
|
446
|
+
{ type: "turn.start", threadId: "thread-hitl", turnId: "turn-h", seq: s() },
|
|
447
|
+
{ type: "message.start", id: "msg-h", role: "assistant", turnId: "turn-h", threadId: "thread-hitl", seq: s() },
|
|
448
|
+
{ type: "text.start", id: "th", seq: s() },
|
|
449
|
+
{ type: "text.delta", id: "th", delta: "I can remember this preference.", seq: s() },
|
|
450
|
+
{ type: "text.end", id: "th", seq: s() },
|
|
451
|
+
{
|
|
452
|
+
type: "turn.done",
|
|
453
|
+
turnId: "turn-h",
|
|
454
|
+
outcome: {
|
|
455
|
+
type: "paused",
|
|
456
|
+
asks: [
|
|
457
|
+
{
|
|
458
|
+
askId: "ask-remember-1",
|
|
459
|
+
kind: "approval",
|
|
460
|
+
message: "Remember your seating preference?",
|
|
461
|
+
grantModes: [
|
|
462
|
+
{ id: "m.durable", label: "Always", description: "Keep it for every future chat" },
|
|
463
|
+
{ id: "m.scoped", label: "Just this chat" },
|
|
464
|
+
],
|
|
465
|
+
requestState: "rs-bytes-1",
|
|
466
|
+
},
|
|
467
|
+
],
|
|
468
|
+
},
|
|
469
|
+
seq: s(),
|
|
470
|
+
},
|
|
471
|
+
];
|
|
472
|
+
for (const ev of events) reducer.push(ev);
|
|
473
|
+
return {
|
|
474
|
+
result: reducer.result(),
|
|
475
|
+
assistantText: "",
|
|
476
|
+
status: "ready",
|
|
477
|
+
statusElapsedMs: 0,
|
|
478
|
+
activeTool: null,
|
|
479
|
+
error: null,
|
|
480
|
+
prompts: [], // the test composes these via hitlPromptsFromFold + the answer ledger
|
|
481
|
+
messages: [{ role: "user", text: "book my usual seat" }],
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* 21. notice-rows (spec draft.2) — both arrival paths: a flat adapter
|
|
487
|
+
* notice (the portal self-hosted mapping) and a fold-borne framework
|
|
488
|
+
* notice (the claude informational promotion). Labeled, non-conversational,
|
|
489
|
+
* never agent-voiced; provenance shows only under debug.
|
|
490
|
+
*/
|
|
491
|
+
export function noticeRows(): TranscriptInputs {
|
|
492
|
+
const reducer = new Reducer();
|
|
493
|
+
let seq = 0;
|
|
494
|
+
const s = (): number => seq++;
|
|
495
|
+
const events: AgEvent[] = [
|
|
496
|
+
{ type: "turn.start", threadId: "thread-n", turnId: "turn-n", seq: s() },
|
|
497
|
+
{
|
|
498
|
+
type: "message.start",
|
|
499
|
+
id: "msg-n0",
|
|
500
|
+
role: "notice",
|
|
501
|
+
noticeSource: "framework",
|
|
502
|
+
turnId: "turn-n",
|
|
503
|
+
threadId: "thread-n",
|
|
504
|
+
seq: s(),
|
|
505
|
+
},
|
|
506
|
+
// The notice's text lands while it is the sole open message, so block
|
|
507
|
+
// routing is unambiguous; the assistant message opens after.
|
|
508
|
+
{ type: "text.start", id: "tn0", seq: s() },
|
|
509
|
+
{ type: "text.delta", id: "tn0", delta: "Model fell back to concise mode", seq: s() },
|
|
510
|
+
{ type: "text.end", id: "tn0", seq: s() },
|
|
511
|
+
{ type: "message.end", id: "msg-n0", turnId: "turn-n", seq: s() },
|
|
512
|
+
{ type: "message.start", id: "msg-n1", role: "assistant", turnId: "turn-n", threadId: "thread-n", seq: s() },
|
|
513
|
+
{ type: "text.start", id: "tn", seq: s() },
|
|
514
|
+
{ type: "text.delta", id: "tn", delta: "Done.", seq: s() },
|
|
515
|
+
{ type: "text.end", id: "tn", seq: s() },
|
|
516
|
+
{ type: "turn.done", turnId: "turn-n", outcome: { type: "success" }, seq: s() },
|
|
517
|
+
];
|
|
518
|
+
for (const ev of events) reducer.push(ev);
|
|
519
|
+
return {
|
|
520
|
+
result: reducer.result(),
|
|
521
|
+
assistantText: "",
|
|
522
|
+
status: "ready",
|
|
523
|
+
statusElapsedMs: 0,
|
|
524
|
+
activeTool: null,
|
|
525
|
+
error: null,
|
|
526
|
+
prompts: [],
|
|
527
|
+
messages: [
|
|
528
|
+
{ role: "notice", text: "Session resumed on a new device", noticeSource: "adapter" },
|
|
529
|
+
{ role: "user", text: "hi" },
|
|
530
|
+
{ role: "assistant", text: "Done." },
|
|
531
|
+
],
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* 22. promoted-view — guuey#204 "promote and reference": the mount a host
|
|
537
|
+
* stage shows chips in the transcript; every other mount stays full; no
|
|
538
|
+
* key (or a stale one) is byte-identical to today.
|
|
539
|
+
*/
|
|
540
|
+
export function promotedView(): { inputs: TranscriptInputs; promotedKey: string } {
|
|
541
|
+
const s = seqSource();
|
|
542
|
+
const events: InvokeTurnEvent[] = [session, frame(boot(s), { status: "thinking" })];
|
|
543
|
+
for (const id of ["t1", "t2"] as const) {
|
|
544
|
+
events.push(
|
|
545
|
+
frame(
|
|
546
|
+
toolPart(s, id, "ggui_render", {
|
|
547
|
+
uiData: { uri: `ui://cards/${id}`, mimeType: "text/html", text: `<div>${id}</div>` },
|
|
548
|
+
content: [],
|
|
549
|
+
}),
|
|
550
|
+
{ status: "using-tool", activeTool: "ggui_render" },
|
|
551
|
+
),
|
|
552
|
+
);
|
|
553
|
+
}
|
|
554
|
+
events.push(doneEvent);
|
|
555
|
+
const inputs = driveTurn(events, { userText: "show me two cards" });
|
|
556
|
+
return { inputs, promotedKey: "view.t2" };
|
|
557
|
+
}
|
package/src/hitl.ts
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* R10's AgJSON HITL half (spec draft.2, silverprotocol/typescript-sdk#16):
|
|
3
|
+
* lifting persisted asks out of the fold, and constructing validated
|
|
4
|
+
* answers.
|
|
5
|
+
*
|
|
6
|
+
* Two contract points are load-bearing here:
|
|
7
|
+
*
|
|
8
|
+
* - **Asks render from the PERSISTED declaration** — `AgPausedAsk` in
|
|
9
|
+
* `turn.done.outcome:"paused".asks[]`. The live `hitl.ask` event is
|
|
10
|
+
* live-only by spec (§1.3); a client that joins a thread late renders
|
|
11
|
+
* the paused record, so this module reads ONLY that record and both
|
|
12
|
+
* paths converge.
|
|
13
|
+
* - **Every answer is validated BEFORE dispatch** — `validateHitlAnswer`
|
|
14
|
+
* from core enforces required-iff-declared, echo-must-be-declared, and
|
|
15
|
+
* the `requestState` byte-echo MUST. `buildHitlAnswer` constructs
|
|
16
|
+
* answers that pass by construction and THROWS if they don't: a failed
|
|
17
|
+
* validation here is a kit bug, never a user-visible state.
|
|
18
|
+
*
|
|
19
|
+
* Answer states encode guuey's ratified dismissal ruling (#16): dismissal
|
|
20
|
+
* maps to `cancelled` (still-pending, re-askable — the card stays
|
|
21
|
+
* answerable), `declined` is the durable explicit deny.
|
|
22
|
+
*/
|
|
23
|
+
import {
|
|
24
|
+
validateHitlAnswer,
|
|
25
|
+
type AgGrantMode,
|
|
26
|
+
type AgHitlAnswer,
|
|
27
|
+
type AgPausedAsk,
|
|
28
|
+
type AgReduceResult,
|
|
29
|
+
} from "@silverprotocol/core";
|
|
30
|
+
import type { HitlPromptInput } from "./types.js";
|
|
31
|
+
|
|
32
|
+
/** A host-side record of how an ask was answered (the assembler's ledger value). */
|
|
33
|
+
export interface HitlAnswerRecord {
|
|
34
|
+
status: "resolved" | "declined" | "cancelled";
|
|
35
|
+
/** Present iff `status === "resolved"` and the ask declared modes. */
|
|
36
|
+
grantModeId?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** A user action on a HITL card: pick a declared mode, decline, or dismiss. */
|
|
40
|
+
export type HitlPromptAction = { grantModeId: string } | "accept" | "decline" | "dismiss";
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Display text for a grant mode: the asker's `label`, else the id as
|
|
44
|
+
* LITERAL text. Mode semantics are asker-scoped (normative, spec §7):
|
|
45
|
+
* falling back to the id displays identity, never interpreted meaning —
|
|
46
|
+
* a renderer must not special-case id strings.
|
|
47
|
+
*/
|
|
48
|
+
export function grantModeDisplay(mode: AgGrantMode): string {
|
|
49
|
+
return mode.label ?? mode.id;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Lift every persisted ask in the fold into an R10 prompt input, merged
|
|
54
|
+
* with the host's answer ledger (keyed by askId). Pure; input order is the
|
|
55
|
+
* fold's turn order.
|
|
56
|
+
*/
|
|
57
|
+
export function hitlPromptsFromFold(
|
|
58
|
+
result: AgReduceResult | null,
|
|
59
|
+
answers: Readonly<Record<string, HitlAnswerRecord>> = {},
|
|
60
|
+
): HitlPromptInput[] {
|
|
61
|
+
if (result === null) return [];
|
|
62
|
+
const prompts: HitlPromptInput[] = [];
|
|
63
|
+
for (const turn of result.turns) {
|
|
64
|
+
if (turn.outcome?.type !== "paused") continue;
|
|
65
|
+
for (const ask of turn.outcome.asks) {
|
|
66
|
+
const answer = answers[ask.askId];
|
|
67
|
+
prompts.push({
|
|
68
|
+
id: ask.askId,
|
|
69
|
+
kind: "hitl",
|
|
70
|
+
ask,
|
|
71
|
+
state: answer?.status ?? "pending",
|
|
72
|
+
...(answer?.grantModeId !== undefined ? { grantModeId: answer.grantModeId } : {}),
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return prompts;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Construct the wire answer for a card action and validate it against the
|
|
81
|
+
* ask's own persisted record before anything dispatches it.
|
|
82
|
+
*
|
|
83
|
+
* `"accept"` is the plain-ask affirmative (valid only when the ask
|
|
84
|
+
* declared no modes — with a declaration, accepting IS picking a mode).
|
|
85
|
+
* A validation failure throws: the inputs came from the declaration
|
|
86
|
+
* itself, so a mismatch is a construction bug, not a user error.
|
|
87
|
+
*/
|
|
88
|
+
export function buildHitlAnswer(ask: AgPausedAsk, action: HitlPromptAction): AgHitlAnswer {
|
|
89
|
+
const answer: AgHitlAnswer = {
|
|
90
|
+
askId: ask.askId,
|
|
91
|
+
status:
|
|
92
|
+
action === "decline" ? "declined" : action === "dismiss" ? "cancelled" : "resolved",
|
|
93
|
+
...(typeof action === "object" ? { grantModeId: action.grantModeId } : {}),
|
|
94
|
+
// The pre-existing byte-echo MUST (spec §7): the answer carries the
|
|
95
|
+
// ask's requestState verbatim when one exists.
|
|
96
|
+
...(ask.requestState !== undefined ? { requestState: ask.requestState } : {}),
|
|
97
|
+
};
|
|
98
|
+
const verdict = validateHitlAnswer(ask, answer);
|
|
99
|
+
if (!verdict.ok) {
|
|
100
|
+
throw new Error(`buildHitlAnswer constructed an invalid answer (${verdict.code}): ${verdict.message}`);
|
|
101
|
+
}
|
|
102
|
+
return answer;
|
|
103
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* rendering, tests, and RN bundlers consume the view-model directly.
|
|
12
12
|
*/
|
|
13
13
|
export {
|
|
14
|
+
newestViewKey,
|
|
14
15
|
planTranscript,
|
|
15
16
|
} from "./plan.js";
|
|
16
17
|
export {
|
|
@@ -24,6 +25,13 @@ export {
|
|
|
24
25
|
type ChatStrings,
|
|
25
26
|
} from "./strings.js";
|
|
26
27
|
export { transcriptInputsFromHistory } from "./history-inputs.js";
|
|
28
|
+
export {
|
|
29
|
+
buildHitlAnswer,
|
|
30
|
+
grantModeDisplay,
|
|
31
|
+
hitlPromptsFromFold,
|
|
32
|
+
type HitlAnswerRecord,
|
|
33
|
+
type HitlPromptAction,
|
|
34
|
+
} from "./hitl.js";
|
|
27
35
|
export {
|
|
28
36
|
DEFAULT_CHAT_THEME,
|
|
29
37
|
GUUEY_CHAT_THEME,
|
|
@@ -32,6 +40,7 @@ export {
|
|
|
32
40
|
resolveTheme,
|
|
33
41
|
} from "./theme.js";
|
|
34
42
|
export type {
|
|
43
|
+
ChatDebugEvent,
|
|
35
44
|
CitationsItem,
|
|
36
45
|
CodeItem,
|
|
37
46
|
CompactionItem,
|
|
@@ -40,7 +49,12 @@ export type {
|
|
|
40
49
|
ErrorItem,
|
|
41
50
|
HistoryBoundaryItem,
|
|
42
51
|
ItemKey,
|
|
52
|
+
HitlPromptInput,
|
|
53
|
+
HitlPromptItem,
|
|
43
54
|
MediaItem,
|
|
55
|
+
NoticeItem,
|
|
56
|
+
ProfilePromptInput,
|
|
57
|
+
ProfilePromptItem,
|
|
44
58
|
PromptItem,
|
|
45
59
|
PromptItemInput,
|
|
46
60
|
ReasoningItem,
|
|
@@ -55,4 +69,5 @@ export type {
|
|
|
55
69
|
UnknownItem,
|
|
56
70
|
UserMessageItem,
|
|
57
71
|
ViewMountItem,
|
|
72
|
+
ViewRefItem,
|
|
58
73
|
} from "./types.js";
|