@warren-dean/code-kit 0.11.1 → 0.12.1
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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +17 -0
- package/README.md +14 -5
- package/bin/code-kit.mjs +164 -4
- package/hooks/guard-bash.mjs +17 -0
- package/hooks/lib/harness.mjs +6 -0
- package/hooks/lib/next.mjs +19 -2
- package/hooks/lib/plan.mjs +16 -4
- package/hooks/lib/questions.mjs +140 -0
- package/hooks/lib/report.mjs +246 -0
- package/hooks/lib/rules.mjs +48 -11
- package/hooks/mod/art.mjs +2 -2
- package/hooks/mod/decisions.mjs +117 -3
- package/hooks/mod/panes.mjs +3 -1
- package/hooks/mod/register.mjs +242 -3
- package/hooks/mod/register.test.ts +344 -3
- package/hooks/mod/view.mjs +179 -4
- package/package.json +1 -1
- package/skills/init/SKILL.md +2 -1
- package/skills/next/SKILL.md +14 -0
- package/templates/agent.md +1 -0
- package/templates/claude-md.md +3 -0
package/hooks/mod/panes.mjs
CHANGED
|
@@ -43,6 +43,7 @@ const GLYPH = {
|
|
|
43
43
|
'in review': '◐',
|
|
44
44
|
queued: '◆',
|
|
45
45
|
'sent back': '↩',
|
|
46
|
+
'waiting on you': '?',
|
|
46
47
|
blocked: '■',
|
|
47
48
|
idle: '·',
|
|
48
49
|
ready: '○',
|
|
@@ -64,6 +65,7 @@ export function countsOf(state, queue = []) {
|
|
|
64
65
|
['in review', n('in review')],
|
|
65
66
|
['queued', n('queued')],
|
|
66
67
|
['sent back', n('sent back')],
|
|
68
|
+
['waiting on you', n('waiting on you')],
|
|
67
69
|
['blocked', n('blocked')],
|
|
68
70
|
['ready', state.ready.length],
|
|
69
71
|
['merged', (state.stories ?? []).filter((s) => s.state === 'done').length],
|
|
@@ -1338,7 +1340,7 @@ export function decisionsTab(cards, ui, { Box, Text, Button }, on) {
|
|
|
1338
1340
|
label: o.label,
|
|
1339
1341
|
...(o.hotkey ? { hotkey: o.hotkey } : {}),
|
|
1340
1342
|
variant: 'primary',
|
|
1341
|
-
onPress: () => on.onNeed(o.id, card.line),
|
|
1343
|
+
onPress: () => on.onNeed(o.act ?? o.id, o.line ?? card.line),
|
|
1342
1344
|
}),
|
|
1343
1345
|
...(o.effect ? [text(o.effect, { dimColor: true, wrap: 'wrap' })] : []),
|
|
1344
1346
|
],
|
package/hooks/mod/register.mjs
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
// the lead to review, and merging (`merge --person`). No agent reaches those: they are the mod's own
|
|
9
9
|
// buttons, and the hooks refuse both commands from every agent.
|
|
10
10
|
import {
|
|
11
|
+
ANSWER_ID,
|
|
11
12
|
APPROVE_ID,
|
|
12
13
|
NOT_CODE_KIT,
|
|
13
14
|
PANE_ID,
|
|
@@ -17,7 +18,10 @@ import {
|
|
|
17
18
|
agentsAtWork,
|
|
18
19
|
shownState,
|
|
19
20
|
attribution,
|
|
21
|
+
answerPane,
|
|
22
|
+
approvalAsidePrompt,
|
|
20
23
|
approvePane,
|
|
24
|
+
commandItself,
|
|
21
25
|
band,
|
|
22
26
|
bandLines,
|
|
23
27
|
finishedStories,
|
|
@@ -56,7 +60,7 @@ import {
|
|
|
56
60
|
withoutInFlight,
|
|
57
61
|
} from './loop.mjs';
|
|
58
62
|
import { goalOf, nextText } from './mission.mjs';
|
|
59
|
-
import { contextOf, decisionCards } from './decisions.mjs';
|
|
63
|
+
import { contextOf, decisionCards, limitsWhy } from './decisions.mjs';
|
|
60
64
|
import { DEFAULT_TEAM_UI, TEAM_ID, teamFrame } from './teamview.mjs';
|
|
61
65
|
import { glowOf, mapStoryOf, touchOf } from './codemap.mjs';
|
|
62
66
|
import { harnessSettings } from '../lib/harness.mjs';
|
|
@@ -89,6 +93,9 @@ let ledger = {}; // the session's tokens by agent and story (USE-1)
|
|
|
89
93
|
let rateLimits = []; // the session's limits, as session.measure last gave them
|
|
90
94
|
const agentsSeen = new Map(); // agent id → its $.agent.list() entry, kept once it leaves the list
|
|
91
95
|
const held = new Map(); // tool_use_id → a call held for the person (HOLD-2)
|
|
96
|
+
const denied = new Map(); // tool_use_id → a call Claude Code's own permission check refused (PERM-1)
|
|
97
|
+
const briefLater = new Set(); // the brief's questions the person put off this session (ASK-4)
|
|
98
|
+
let answering = null; // the open Answer… box: { kind: 'question' | 'brief', question, id?, path?, text, error }
|
|
92
99
|
let where = null; // { cli, cwd, interactive }, from session start
|
|
93
100
|
// The lead loop (spec 05): what it did, and what it waits on.
|
|
94
101
|
const loop = {
|
|
@@ -207,6 +214,8 @@ export function register(on) {
|
|
|
207
214
|
const ran = await $.process.run(['node', cli, ...args, '--json'], { cwd });
|
|
208
215
|
return ran.exitCode === 0 ? parseJson(ran.stdout) : null;
|
|
209
216
|
};
|
|
217
|
+
// What a CLI run said, in a line: why it refused, for a notice.
|
|
218
|
+
const firstLine = (ran) => (ran.stderr || ran.stdout).trim().split('\n')[0];
|
|
210
219
|
// A session starts with the loop running: a pause recorded by an earlier one no longer holds.
|
|
211
220
|
if ((await json('loop').catch(() => null))?.paused)
|
|
212
221
|
await $.process.run(['node', cli, 'loop', 'resume'], { cwd }).catch(() => {});
|
|
@@ -246,6 +255,13 @@ export function register(on) {
|
|
|
246
255
|
: [];
|
|
247
256
|
// TEAM-7: the team's claims, intents and pull requests, where the project is a team's.
|
|
248
257
|
const team = valid && check.team ? await json('team') : null;
|
|
258
|
+
// ASK-2: the lead's questions waiting for the person; REPORT-3: bug reports waiting to be sent.
|
|
259
|
+
const questions = valid
|
|
260
|
+
? ((await json('ask')) ?? []).filter((q) => q.state === 'open')
|
|
261
|
+
: [];
|
|
262
|
+
const reports = valid
|
|
263
|
+
? ((await json('report')) ?? []).filter((r) => r.state === 'draft')
|
|
264
|
+
: [];
|
|
249
265
|
// TEAM-12: the plan mirrored onto the team's Project board, at most every 10 minutes, in the
|
|
250
266
|
// background: a slow GitHub never holds up the band.
|
|
251
267
|
const at = await $.clock.now();
|
|
@@ -265,6 +281,10 @@ export function register(on) {
|
|
|
265
281
|
adapters,
|
|
266
282
|
check,
|
|
267
283
|
team,
|
|
284
|
+
questions,
|
|
285
|
+
reports,
|
|
286
|
+
// ASK-4: the brief's open questions, less those put off this session.
|
|
287
|
+
brief: (nextStep?.brief ?? []).filter((b) => !briefLater.has(b.question)),
|
|
268
288
|
base: status?.base ?? null,
|
|
269
289
|
};
|
|
270
290
|
if (model.state.kind === 'ok') await act.registerReviewer();
|
|
@@ -473,6 +493,8 @@ export function register(on) {
|
|
|
473
493
|
$.ui.invalidate('ui.render');
|
|
474
494
|
return {};
|
|
475
495
|
},
|
|
496
|
+
// ASK-2, ASK-4, REPORT-3: a question's Answer… or a report's Read it opens its card.
|
|
497
|
+
openDecision: (line) => act.decide(line),
|
|
476
498
|
// ACT-1: the confirmation, prefilled from the request.
|
|
477
499
|
approve: async (line) => {
|
|
478
500
|
approving = {
|
|
@@ -480,6 +502,8 @@ export function register(on) {
|
|
|
480
502
|
reason: prefilledReason(line.request),
|
|
481
503
|
error: null,
|
|
482
504
|
heldId: line.heldId ?? null,
|
|
505
|
+
outside: line.outside ?? null,
|
|
506
|
+
aside: { thread: [], draft: '' },
|
|
483
507
|
};
|
|
484
508
|
await $.ui.open({ id: APPROVE_ID, title: 'Approve', focus: true, closeOnEscape: true });
|
|
485
509
|
$.ui.invalidate('ui.render');
|
|
@@ -521,12 +545,179 @@ export function register(on) {
|
|
|
521
545
|
await $.ui.close({ id: APPROVE_ID });
|
|
522
546
|
await act.reload();
|
|
523
547
|
},
|
|
548
|
+
// CARD-7: a request nobody needs any more stops waiting; nothing is approved.
|
|
549
|
+
dropRequest: async (line) => {
|
|
550
|
+
const open = model.requests?.open ?? [];
|
|
551
|
+
const same = (r) =>
|
|
552
|
+
[...r.names].sort().join() === [...line.request.names].sort().join() &&
|
|
553
|
+
(r.lane ?? r.actor) === (line.request.lane ?? line.request.actor);
|
|
554
|
+
const numbers = open.flatMap((r, i) => (same(r) ? [i + 1] : []));
|
|
555
|
+
if (!numbers.length) return;
|
|
556
|
+
const ran = await $.process.run(
|
|
557
|
+
['node', cli, 'requests', 'drop', numbers.join(','), '--via', 'pane'],
|
|
558
|
+
{ cwd },
|
|
559
|
+
);
|
|
560
|
+
notice = ran.exitCode === 0 ? null : `Nothing was dropped: ${firstLine(ran)}`;
|
|
561
|
+
await act.reload();
|
|
562
|
+
},
|
|
563
|
+
// ASK-2: the lead's option, pressed: the answer is recorded and sent to the lead.
|
|
564
|
+
answerWith: (line) => act.sendAnswer({ kind: 'question', ...line.question }, line.choice),
|
|
565
|
+
// ASK-2, ASK-4: Answer… opens a box for the person's own words.
|
|
566
|
+
answer: async (line) => {
|
|
567
|
+
answering = { kind: 'question', ...line.question, text: '', error: null };
|
|
568
|
+
await $.ui.open({ id: ANSWER_ID, title: 'Answer', focus: true, closeOnEscape: true });
|
|
569
|
+
$.ui.invalidate('ui.render');
|
|
570
|
+
},
|
|
571
|
+
answerBrief: async (line) => {
|
|
572
|
+
answering = { kind: 'brief', ...line.brief, text: '', error: null };
|
|
573
|
+
await $.ui.open({ id: ANSWER_ID, title: 'Answer', focus: true, closeOnEscape: true });
|
|
574
|
+
$.ui.invalidate('ui.render');
|
|
575
|
+
},
|
|
576
|
+
laterBrief: (line) => {
|
|
577
|
+
briefLater.add(line.brief.question);
|
|
578
|
+
model = {
|
|
579
|
+
...model,
|
|
580
|
+
brief: (model.brief ?? []).filter((b) => b.question !== line.brief.question),
|
|
581
|
+
};
|
|
582
|
+
$.ui.invalidate('ui.render');
|
|
583
|
+
},
|
|
584
|
+
confirmAnswer: async (text) => {
|
|
585
|
+
if (!answering) return;
|
|
586
|
+
if (!text.trim()) {
|
|
587
|
+
answering = { ...answering, error: 'Type your answer: the lead builds on it.' };
|
|
588
|
+
$.ui.invalidate('ui.render');
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
const done = await act.sendAnswer(answering, text.trim());
|
|
592
|
+
if (done) {
|
|
593
|
+
answering = null;
|
|
594
|
+
await $.ui.close({ id: ANSWER_ID });
|
|
595
|
+
}
|
|
596
|
+
},
|
|
597
|
+
cancelAnswer: async () => {
|
|
598
|
+
answering = null;
|
|
599
|
+
await $.ui.close({ id: ANSWER_ID });
|
|
600
|
+
},
|
|
601
|
+
// The answer recorded (a question's, in code-kit) and told to the lead, which carries on with it.
|
|
602
|
+
sendAnswer: async (q, text) => {
|
|
603
|
+
if (q.kind === 'question') {
|
|
604
|
+
const ran = await $.process.run(
|
|
605
|
+
['node', cli, 'ask', 'answer', q.id, text, '--via', 'pane'],
|
|
606
|
+
{ cwd },
|
|
607
|
+
);
|
|
608
|
+
if (ran.exitCode !== 0) {
|
|
609
|
+
notice = `Your answer wasn't recorded: ${firstLine(ran)}`;
|
|
610
|
+
if (answering) answering = { ...answering, error: notice };
|
|
611
|
+
$.ui.invalidate('ui.render');
|
|
612
|
+
return false;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
notice = null;
|
|
616
|
+
const said = /[.!?]$/.test(text) ? text : `${text}.`;
|
|
617
|
+
await $.prompt.submit({
|
|
618
|
+
text:
|
|
619
|
+
q.kind === 'question'
|
|
620
|
+
? `The person answered ${q.id} (${q.question}): ${said}${q.blocks ? ` ${q.story} no longer waits on them.` : ''} Carry on with it.`
|
|
621
|
+
: `The person answered an open question in ${q.path}, "${q.question}": ${said} Record it in the brief's decision log, take it out of the open questions, and carry on with it.`,
|
|
622
|
+
});
|
|
623
|
+
if (q.kind === 'brief') {
|
|
624
|
+
briefLater.add(q.question);
|
|
625
|
+
model = { ...model, brief: (model.brief ?? []).filter((b) => b.question !== q.question) };
|
|
626
|
+
}
|
|
627
|
+
await act.reload();
|
|
628
|
+
return true;
|
|
629
|
+
},
|
|
630
|
+
// PERM-1: a call Claude Code refused, which only the person can run or allow.
|
|
631
|
+
runDenied: async (line) => {
|
|
632
|
+
await $.prompt.fill({ text: `! ${line.denial.command}`, mode: 'replace' }).catch(() => {});
|
|
633
|
+
},
|
|
634
|
+
deniedDone: async (line) => {
|
|
635
|
+
denied.delete(line.denial.id);
|
|
636
|
+
$.ui.invalidate('ui.render');
|
|
637
|
+
await $.prompt.submit({
|
|
638
|
+
text: `The person ran it themselves, or allowed it: ${line.denial.command ?? line.denial.what} (Claude Code had refused it for ${line.denial.who}). Check it worked, and carry on.`,
|
|
639
|
+
});
|
|
640
|
+
},
|
|
641
|
+
deniedNo: async (line) => {
|
|
642
|
+
denied.delete(line.denial.id);
|
|
643
|
+
$.ui.invalidate('ui.render');
|
|
644
|
+
await $.prompt.submit({
|
|
645
|
+
text: `The person won't run ${line.denial.command ?? line.denial.what} now (Claude Code refused it for ${line.denial.who}). Don't try to get around it: carry on with other work, and if a story can't go on without it, ask with code-kit ask --blocks.`,
|
|
646
|
+
});
|
|
647
|
+
},
|
|
648
|
+
// REPORT-3: the person sends a drafted report to code-kit's GitHub, or discards it.
|
|
649
|
+
sendReport: async (line) => {
|
|
650
|
+
const ran = await $.process.run(['node', cli, 'report', 'send', line.report.id], {
|
|
651
|
+
cwd,
|
|
652
|
+
timeoutMs: 120000,
|
|
653
|
+
});
|
|
654
|
+
result = {
|
|
655
|
+
ok: ran.exitCode === 0,
|
|
656
|
+
title: ran.exitCode === 0 ? 'Report sent' : 'Report not sent',
|
|
657
|
+
text: `${ran.stdout}${ran.stderr}`.trim(),
|
|
658
|
+
};
|
|
659
|
+
await $.ui.open({ id: RESULT_ID, title: 'Report', focus: true, closeOnEscape: true });
|
|
660
|
+
await act.reload();
|
|
661
|
+
},
|
|
662
|
+
discardReport: async (line) => {
|
|
663
|
+
await $.process.run(['node', cli, 'report', 'discard', line.report.id], { cwd });
|
|
664
|
+
await act.reload();
|
|
665
|
+
},
|
|
524
666
|
// HOLD-2: Approve opens ACT-1's confirmation for the held call; Refuse lets the refusal through.
|
|
525
667
|
approveHeld: (line) =>
|
|
526
668
|
act.approve({
|
|
527
|
-
request: {
|
|
669
|
+
request: {
|
|
670
|
+
names: line.held.names,
|
|
671
|
+
lane: line.held.lane,
|
|
672
|
+
what: line.held.what,
|
|
673
|
+
why: line.held.refusal ?? null,
|
|
674
|
+
},
|
|
528
675
|
heldId: line.held.id,
|
|
676
|
+
outside: line.held.outside ?? null,
|
|
529
677
|
}),
|
|
678
|
+
// ACT-5: a question about the approval, answered in its pane by a side call over the lead's own
|
|
679
|
+
// conversation (it shares the prompt cache, and nothing is added to the conversation); before the
|
|
680
|
+
// lead's first reply, from code-kit's facts alone.
|
|
681
|
+
askApproval: async (question) => {
|
|
682
|
+
const q = question.trim();
|
|
683
|
+
if (!approving || !q) return;
|
|
684
|
+
const asked = approving;
|
|
685
|
+
const thread = asked.aside.thread;
|
|
686
|
+
const turn = { question: q, pending: true };
|
|
687
|
+
approving = { ...asked, aside: { thread: [...thread, turn], draft: '' } };
|
|
688
|
+
$.ui.invalidate('ui.render');
|
|
689
|
+
const row = (model.state?.lanes ?? []).find((l) => l.name === asked.request.lane);
|
|
690
|
+
const prompt = approvalAsidePrompt({
|
|
691
|
+
request: asked.request,
|
|
692
|
+
story: row?.story ?? null,
|
|
693
|
+
outside: limitsWhy(asked.outside) ?? null,
|
|
694
|
+
thread,
|
|
695
|
+
question: q,
|
|
696
|
+
});
|
|
697
|
+
let reply = await $.model
|
|
698
|
+
.fork({ prompt })
|
|
699
|
+
.catch((err) => ({ isAnswered: false, reason: String(err?.message ?? err) }));
|
|
700
|
+
if (!reply.isAnswered && reply.reason === 'nothing-to-fork')
|
|
701
|
+
reply = await $.model
|
|
702
|
+
.complete({ model: 'sonnet', prompt, maxTokens: 800 })
|
|
703
|
+
.catch((err) => ({ isAnswered: false, reason: String(err?.message ?? err) }));
|
|
704
|
+
// Answered for this approval only: a pane closed or moved on meanwhile keeps what it has.
|
|
705
|
+
if (!approving || approving.request !== asked.request) return;
|
|
706
|
+
const done = reply.isAnswered
|
|
707
|
+
? { question: q, answer: reply.text }
|
|
708
|
+
: {
|
|
709
|
+
question: q,
|
|
710
|
+
error: `No answer: ${reply.reason}${reply.status ? ` (${reply.status})` : ''}`,
|
|
711
|
+
};
|
|
712
|
+
approving = {
|
|
713
|
+
...approving,
|
|
714
|
+
aside: {
|
|
715
|
+
...approving.aside,
|
|
716
|
+
thread: approving.aside.thread.map((t) => (t === turn ? done : t)),
|
|
717
|
+
},
|
|
718
|
+
};
|
|
719
|
+
$.ui.invalidate('ui.render');
|
|
720
|
+
},
|
|
530
721
|
refuseHeld: async (line) => {
|
|
531
722
|
await answerHeld(line.held.id, 'refused');
|
|
532
723
|
},
|
|
@@ -1205,6 +1396,35 @@ export function register(on) {
|
|
|
1205
1396
|
// CARD-1: the text a code-kit hook refused a call with, kept for the call's result to draw as a card.
|
|
1206
1397
|
// HOLD-1 to HOLD-6: a refusal a person's approval would allow is held while the band asks. The hooks
|
|
1207
1398
|
// stay the judge: the call is run again once the approval is written, and refused as before otherwise.
|
|
1399
|
+
// PERM-1: Claude Code's own permission check refused a call (auto mode, a deny rule): code-kit can't
|
|
1400
|
+
// approve past it, so it waits in the band for the person, who can run it themselves or allow it.
|
|
1401
|
+
on('classic.PermissionDenied', async ($, e, next) => {
|
|
1402
|
+
const res = await next(e);
|
|
1403
|
+
const input = e.tool_input && typeof e.tool_input === 'object' ? e.tool_input : {};
|
|
1404
|
+
const command =
|
|
1405
|
+
e.tool_name === 'Bash' && typeof input.command === 'string' ? input.command : null;
|
|
1406
|
+
const path = input.file_path ?? input.notebook_path ?? null;
|
|
1407
|
+
const lane = e.agent_id ? activity.get(e.agent_id)?.lane : null;
|
|
1408
|
+
denied.set(e.tool_use_id ?? `${e.tool_name}-${denied.size}`, {
|
|
1409
|
+
id: e.tool_use_id ?? `${e.tool_name}-${denied.size}`,
|
|
1410
|
+
who: lane ? `the ${lane} lane` : (e.agent_type ?? (e.agent_id ? 'an agent' : 'the lead')),
|
|
1411
|
+
tool: e.tool_name,
|
|
1412
|
+
command,
|
|
1413
|
+
what: command
|
|
1414
|
+
? `\`${commandItself(command)}\``
|
|
1415
|
+
: path
|
|
1416
|
+
? `${e.tool_name} on ${path}`
|
|
1417
|
+
: e.tool_name,
|
|
1418
|
+
reason:
|
|
1419
|
+
typeof e.reason === 'string'
|
|
1420
|
+
? e.reason.trim().replace(/\s+/g, ' ').slice(0, 200) || null
|
|
1421
|
+
: null,
|
|
1422
|
+
at: await $.clock.now(),
|
|
1423
|
+
});
|
|
1424
|
+
$.ui.invalidate('ui.render');
|
|
1425
|
+
return res;
|
|
1426
|
+
});
|
|
1427
|
+
|
|
1208
1428
|
on('tool.call', async ($, e, next) => {
|
|
1209
1429
|
// LOOP-3: a lane agent's tool call is activity; while it runs, the agent isn't quiet.
|
|
1210
1430
|
const calledAt = await $.clock.now();
|
|
@@ -1583,7 +1803,7 @@ export function register(on) {
|
|
|
1583
1803
|
const lines = linesNowRaw(now);
|
|
1584
1804
|
const cards = cardsNow(now, lines);
|
|
1585
1805
|
return lines.map((l) => {
|
|
1586
|
-
const card = cards.find((c) => c.
|
|
1806
|
+
const card = cards.find((c) => c.source === l);
|
|
1587
1807
|
return card ? { ...l, context: contextOf(card) } : l;
|
|
1588
1808
|
});
|
|
1589
1809
|
};
|
|
@@ -1601,6 +1821,7 @@ export function register(on) {
|
|
|
1601
1821
|
nextStep: loop.nextStep,
|
|
1602
1822
|
combined: combinedNow(),
|
|
1603
1823
|
team: model.team ?? null,
|
|
1824
|
+
denied: [...denied.values()],
|
|
1604
1825
|
});
|
|
1605
1826
|
|
|
1606
1827
|
on('ui.render', { component: 'Pane', requestId: PANE_ID }, async ($, e) => {
|
|
@@ -1765,6 +1986,24 @@ export function register(on) {
|
|
|
1765
1986
|
},
|
|
1766
1987
|
onSubmit: (value) => act.confirmApproval(value),
|
|
1767
1988
|
onCancel: () => act.cancelApproval(),
|
|
1989
|
+
onAskInput: (value) => {
|
|
1990
|
+
approving = { ...approving, aside: { ...approving.aside, draft: value } };
|
|
1991
|
+
$.ui.invalidate('ui.render');
|
|
1992
|
+
},
|
|
1993
|
+
onAsk: (value) => act.askApproval(value),
|
|
1994
|
+
});
|
|
1995
|
+
});
|
|
1996
|
+
|
|
1997
|
+
// ASK-2, ASK-4: the person's answer, in their own words.
|
|
1998
|
+
on('ui.render', { component: 'Pane', requestId: ANSWER_ID }, async ($, e) => {
|
|
1999
|
+
if (!answering) return resultPane(null, $.ui.resolve(e));
|
|
2000
|
+
return answerPane(answering, $.ui.resolve(e), {
|
|
2001
|
+
onInput: (value) => {
|
|
2002
|
+
answering = { ...answering, text: value };
|
|
2003
|
+
$.ui.invalidate('ui.render');
|
|
2004
|
+
},
|
|
2005
|
+
onSubmit: (value) => act.confirmAnswer(value),
|
|
2006
|
+
onCancel: () => act.cancelAnswer(),
|
|
1768
2007
|
});
|
|
1769
2008
|
});
|
|
1770
2009
|
|