@pugi/cli 0.1.0-beta.87 → 0.1.0-beta.89
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 +36 -0
- package/LICENSE +1 -1
- package/dist/core/agents/registry.js +1 -1
- package/dist/core/auth/env-provider.js +1 -1
- package/dist/core/checkpoints/shadow-git.js +1 -1
- package/dist/core/context/compaction.js +1 -1
- package/dist/core/context/markdown-traverse.js +1 -1
- package/dist/core/credentials.js +1 -1
- package/dist/core/denial-tracking/state.js +1 -1
- package/dist/core/edits/fuzzy-ladder.js +1 -1
- package/dist/core/edits/layer-a-fuzzy-apply.js +1 -1
- package/dist/core/engine/anvil-client.js +76 -2
- package/dist/core/engine/native-pugi.js +1 -1
- package/dist/core/engine/tool-bridge.js +436 -0
- package/dist/core/hooks/events.js +3 -1
- package/dist/core/hooks/registry.js +3 -0
- package/dist/core/hooks/worktree-events.js +158 -0
- package/dist/core/lsp/client.js +453 -0
- package/dist/core/lsp/server-detect.js +173 -0
- package/dist/core/lsp/symbol-cache.js +162 -0
- package/dist/core/lsp/symbol-tools.js +296 -4
- package/dist/core/mcp/server-tools.js +1 -1
- package/dist/core/mcp/server.js +1 -1
- package/dist/core/memory/secret-scanner.js +6 -6
- package/dist/core/onboarding/ensure-initialized.js +1 -1
- package/dist/core/plans/plan-artifact.js +2 -2
- package/dist/core/repl/ask.js +1 -1
- package/dist/core/repl/cap-warning.js +1 -1
- package/dist/core/repl/session.js +3 -3
- package/dist/core/repl/slash-commands.js +1 -1
- package/dist/core/routing/pre-flight-estimator.js +1 -1
- package/dist/core/settings.js +38 -0
- package/dist/core/worktree/include-parser.js +249 -0
- package/dist/index.js +8 -0
- package/dist/runtime/cli.js +176 -28
- package/dist/runtime/commands/agents.js +1 -1
- package/dist/runtime/commands/config.js +41 -7
- package/dist/runtime/commands/hooks.js +3 -0
- package/dist/runtime/commands/review-consensus.js +1 -1
- package/dist/runtime/sigint-guard.js +272 -0
- package/dist/runtime/version.js +1 -1
- package/dist/runtime/worktree-bootstrap.js +579 -0
- package/dist/skills/bundled/batch.js +2 -2
- package/dist/skills/bundled/index.js +3 -3
- package/dist/skills/bundled/loop.js +2 -2
- package/dist/skills/bundled/remember.js +1 -1
- package/dist/skills/bundled/simplify.js +1 -1
- package/dist/skills/bundled/skillify.js +2 -2
- package/dist/skills/bundled/stuck.js +1 -1
- package/dist/skills/bundled/verify.js +2 -2
- package/dist/testing/vcr.js +2 -2
- package/dist/tools/ask-user-question.js +66 -0
- package/dist/tools/bash.js +2 -2
- package/dist/tools/lsp-tools.js +377 -1
- package/dist/tools/powershell.js +1 -1
- package/dist/tools/registry.js +23 -0
- package/dist/tui/ask-user-question-chips.js +257 -0
- package/dist/tui/input-box.js +1 -1
- package/dist/tui/render.js +1 -1
- package/dist/tui/repl.js +1 -1
- package/dist/tui/status-bar.js +1 -1
- package/dist/tui/update-banner.js +1 -1
- package/dist/tui/welcome-data.js +4 -4
- package/package.json +4 -3
- package/test/scenarios/compact-force.scenario.txt +3 -2
- package/test/scenarios/identity.scenario.txt +6 -5
- package/test/scenarios/persona-handoff.scenario.txt +2 -1
- package/test/scenarios/walkback.scenario.txt +6 -6
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* AskUserQuestionChips — multi-question short-format chip renderer
|
|
4
|
+
* for the structured AskUserQuestion tool (PUGI-480).
|
|
5
|
+
*
|
|
6
|
+
* Renders up to 3 question chips side-by-side, each with up to ~5 short
|
|
7
|
+
* (≤ 5-word) option labels. Replaces paragraph-wall prompts с a compact
|
|
8
|
+
* keyboard-driven picker:
|
|
9
|
+
*
|
|
10
|
+
* Pugi: 2 quick choices
|
|
11
|
+
* ┌────────────────────────┐
|
|
12
|
+
* │ Stack │
|
|
13
|
+
* │ ▸ 1 Browser JS │
|
|
14
|
+
* │ 2 React │
|
|
15
|
+
* │ 3 Other... │
|
|
16
|
+
* └────────────────────────┘
|
|
17
|
+
* ┌────────────────────────┐
|
|
18
|
+
* │ Size │
|
|
19
|
+
* │ ▸ 1 9x9 (classic) │
|
|
20
|
+
* │ 2 16x16 │
|
|
21
|
+
* │ 3 Custom │
|
|
22
|
+
* └────────────────────────┘
|
|
23
|
+
* [Enter] use highlighted defaults · [↑↓] navigate · [s] skip
|
|
24
|
+
*
|
|
25
|
+
* Contract:
|
|
26
|
+
* - questions[].options[].label MUST be ≤ 5 words (renderer truncates
|
|
27
|
+
* overflow с ellipsis defensively; the schema validator rejects at
|
|
28
|
+
* the tool entry point so the model never produces overflow legally).
|
|
29
|
+
* - First option of each question is pre-highlighted (default).
|
|
30
|
+
* - Enter без movement applies defaults across ALL questions.
|
|
31
|
+
* - 1-9 jump к the matching option in the focused question.
|
|
32
|
+
* - ↑↓ navigate within the focused question.
|
|
33
|
+
* - ←→ (and Tab/Shift-Tab) move between questions.
|
|
34
|
+
* - [s] skips the focused question (uses its default) and advances to
|
|
35
|
+
* the next; [s] on the last question commits.
|
|
36
|
+
* - [Esc] cancels the entire dialog (user_cancelled).
|
|
37
|
+
*
|
|
38
|
+
* Non-TTY fallback: when the `forceFallback` prop is true, renders a
|
|
39
|
+
* static numbered text list and does NOT mount the Ink useInput hook.
|
|
40
|
+
* The component itself does NOT sniff `process.stdin.isTTY` — that gate
|
|
41
|
+
* lives in the upstream REPL wiring, which either mounts this chip
|
|
42
|
+
* component (TTY path) or emits the legacy `[user_input_required]`
|
|
43
|
+
* envelope (non-TTY path). Keeping the gate в the caller keeps the
|
|
44
|
+
* component testable under ink-testing-library (which mocks stdin but
|
|
45
|
+
* не sets isTTY).
|
|
46
|
+
*
|
|
47
|
+
* Brand voice gate: ASCII glyphs only, zero attribution to external AIs,
|
|
48
|
+
* no em-dashes. Power-word neutral so localised variants land cleanly.
|
|
49
|
+
*/
|
|
50
|
+
import { useMemo, useState } from 'react';
|
|
51
|
+
import { Box, Text, useInput } from 'ink';
|
|
52
|
+
/**
|
|
53
|
+
* Hard truncation cap. The schema validator enforces ≤ 5 words per
|
|
54
|
+
* label, but the renderer still defends in depth: a malicious or
|
|
55
|
+
* legacy payload with a 14-word label MUST not blow the chip width.
|
|
56
|
+
*/
|
|
57
|
+
export const ASK_CHIPS_LABEL_WORD_CAP = 5;
|
|
58
|
+
export const ASK_CHIPS_LABEL_CHAR_CAP = 22;
|
|
59
|
+
export const ASK_CHIPS_QUESTION_CAP = 3;
|
|
60
|
+
export const ASK_CHIPS_SKIP_LABEL = 'Skip — use defaults';
|
|
61
|
+
/**
|
|
62
|
+
* Truncate a label к the configured word / character caps. Always
|
|
63
|
+
* append "…" when truncation happens so the operator knows the chip
|
|
64
|
+
* is hiding content.
|
|
65
|
+
*/
|
|
66
|
+
export function truncateLabel(raw) {
|
|
67
|
+
const trimmed = raw.trim().replace(/\s+/gu, ' ');
|
|
68
|
+
// Word cap (whitespace-delimited). 5-word rule is the canonical limit.
|
|
69
|
+
const words = trimmed.split(' ');
|
|
70
|
+
let candidate = trimmed;
|
|
71
|
+
if (words.length > ASK_CHIPS_LABEL_WORD_CAP) {
|
|
72
|
+
candidate = `${words.slice(0, ASK_CHIPS_LABEL_WORD_CAP).join(' ')}…`;
|
|
73
|
+
}
|
|
74
|
+
// Char cap (defense-in-depth — protects chip width even с short-word
|
|
75
|
+
// payloads like "AAAAAAAAAAAAAAAAAAAAA" that pass the word gate).
|
|
76
|
+
if (candidate.length > ASK_CHIPS_LABEL_CHAR_CAP) {
|
|
77
|
+
candidate = `${candidate.slice(0, ASK_CHIPS_LABEL_CHAR_CAP - 1)}…`;
|
|
78
|
+
}
|
|
79
|
+
return candidate;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* The component renders в interactive mode by default. The non-TTY
|
|
83
|
+
* gate is the caller's responsibility — see ask-user-question-chips.tsx
|
|
84
|
+
* header docs. Production REPL wiring should check `process.stdin.isTTY`
|
|
85
|
+
* upstream и pass `forceFallback={true}` when stdin is not a TTY, OR
|
|
86
|
+
* skip mounting the chip component entirely и emit the legacy
|
|
87
|
+
* `[user_input_required]` envelope.
|
|
88
|
+
*/
|
|
89
|
+
function shouldUseFallback(forceFallback) {
|
|
90
|
+
return forceFallback === true;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Build the default selections vector (all first options) so Enter
|
|
94
|
+
* без movement applies a coherent set, and so non-TTY fallback can
|
|
95
|
+
* commit immediately.
|
|
96
|
+
*/
|
|
97
|
+
function buildDefaults(questions) {
|
|
98
|
+
return questions.map((q) => {
|
|
99
|
+
const first = q.options[0];
|
|
100
|
+
return {
|
|
101
|
+
header: q.header,
|
|
102
|
+
pickedIndex: 0,
|
|
103
|
+
pickedLabel: first ? truncateLabel(first.label) : '',
|
|
104
|
+
skipped: false,
|
|
105
|
+
};
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
export function AskUserQuestionChips(props) {
|
|
109
|
+
const useFallback = shouldUseFallback(props.forceFallback);
|
|
110
|
+
// Defensive cap: never render more than ASK_CHIPS_QUESTION_CAP chips.
|
|
111
|
+
// Schema enforces this at the tool boundary; the renderer still trims
|
|
112
|
+
// в case a legacy / malicious payload sneaks through.
|
|
113
|
+
const questions = useMemo(() => props.questions.slice(0, ASK_CHIPS_QUESTION_CAP), [props.questions]);
|
|
114
|
+
// Cursor state: which question is focused, and which option within
|
|
115
|
+
// each question is highlighted. Initialised к first option (default).
|
|
116
|
+
const [focusedQuestion, setFocusedQuestion] = useState(0);
|
|
117
|
+
const [cursorPerQuestion, setCursorPerQuestion] = useState(() => questions.map(() => 0));
|
|
118
|
+
// Per-question explicit skip flag. Skipped questions still emit their
|
|
119
|
+
// default index, but flagged `skipped: true` so downstream can audit.
|
|
120
|
+
const [skipped, setSkipped] = useState(() => questions.map(() => false));
|
|
121
|
+
function commit(skipMask = skipped) {
|
|
122
|
+
const selections = questions.map((q, qIdx) => {
|
|
123
|
+
const optionIdx = cursorPerQuestion[qIdx] ?? 0;
|
|
124
|
+
const opt = q.options[optionIdx] ?? q.options[0];
|
|
125
|
+
return {
|
|
126
|
+
header: q.header,
|
|
127
|
+
pickedIndex: optionIdx,
|
|
128
|
+
pickedLabel: opt ? truncateLabel(opt.label) : '',
|
|
129
|
+
skipped: skipMask[qIdx] === true,
|
|
130
|
+
};
|
|
131
|
+
});
|
|
132
|
+
props.onResolve({ selections, cancelled: false });
|
|
133
|
+
}
|
|
134
|
+
function cancel() {
|
|
135
|
+
props.onResolve({ selections: buildDefaults(questions), cancelled: true });
|
|
136
|
+
}
|
|
137
|
+
useInput((input, key) => {
|
|
138
|
+
// Esc cancels the entire AskUserQuestion (user_cancelled).
|
|
139
|
+
if (key.escape) {
|
|
140
|
+
cancel();
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
// Enter commits the current selection (applies defaults across
|
|
144
|
+
// any questions the operator did not touch).
|
|
145
|
+
if (key.return) {
|
|
146
|
+
commit();
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
// [s] skips the focused question (uses default), advances to next.
|
|
150
|
+
// [s] on the LAST question commits the whole set.
|
|
151
|
+
if (input === 's' || input === 'S') {
|
|
152
|
+
const nextSkipped = skipped.slice();
|
|
153
|
+
nextSkipped[focusedQuestion] = true;
|
|
154
|
+
if (focusedQuestion >= questions.length - 1) {
|
|
155
|
+
setSkipped(nextSkipped);
|
|
156
|
+
commit(nextSkipped);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
setSkipped(nextSkipped);
|
|
160
|
+
setFocusedQuestion((q) => Math.min(q + 1, questions.length - 1));
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
// ↑/↓ navigate within the focused question.
|
|
164
|
+
if (key.upArrow) {
|
|
165
|
+
setCursorPerQuestion((prev) => {
|
|
166
|
+
const next = prev.slice();
|
|
167
|
+
const opts = questions[focusedQuestion]?.options ?? [];
|
|
168
|
+
const len = opts.length;
|
|
169
|
+
if (len === 0)
|
|
170
|
+
return prev;
|
|
171
|
+
const cur = next[focusedQuestion] ?? 0;
|
|
172
|
+
next[focusedQuestion] = (cur - 1 + len) % len;
|
|
173
|
+
return next;
|
|
174
|
+
});
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
if (key.downArrow) {
|
|
178
|
+
setCursorPerQuestion((prev) => {
|
|
179
|
+
const next = prev.slice();
|
|
180
|
+
const opts = questions[focusedQuestion]?.options ?? [];
|
|
181
|
+
const len = opts.length;
|
|
182
|
+
if (len === 0)
|
|
183
|
+
return prev;
|
|
184
|
+
const cur = next[focusedQuestion] ?? 0;
|
|
185
|
+
next[focusedQuestion] = (cur + 1) % len;
|
|
186
|
+
return next;
|
|
187
|
+
});
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
// ←/→ (or Tab/Shift-Tab) navigate between questions.
|
|
191
|
+
if (key.leftArrow || (key.shift && key.tab)) {
|
|
192
|
+
setFocusedQuestion((q) => (q - 1 + questions.length) % questions.length);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
if (key.rightArrow || key.tab) {
|
|
196
|
+
setFocusedQuestion((q) => (q + 1) % questions.length);
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
// 1-9 jumps к the matching option in the focused question.
|
|
200
|
+
const numeric = Number.parseInt(input, 10);
|
|
201
|
+
if (!Number.isNaN(numeric) && numeric >= 1 && numeric <= 9) {
|
|
202
|
+
const opts = questions[focusedQuestion]?.options ?? [];
|
|
203
|
+
const target = numeric - 1;
|
|
204
|
+
if (target >= 0 && target < opts.length) {
|
|
205
|
+
setCursorPerQuestion((prev) => {
|
|
206
|
+
const next = prev.slice();
|
|
207
|
+
next[focusedQuestion] = target;
|
|
208
|
+
return next;
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
}, { isActive: props.inert !== true && useFallback === false });
|
|
214
|
+
// ─── Non-TTY fallback ────────────────────────────────────────────────
|
|
215
|
+
// When stdin is not a TTY we still need to render SOMETHING — but
|
|
216
|
+
// we cannot drive keystrokes, so we emit a numbered text list per
|
|
217
|
+
// question and let the upstream envelope handler ask the operator.
|
|
218
|
+
if (useFallback) {
|
|
219
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { children: _jsx(Text, { bold: true, children: `Pugi: ${questions.length} quick ${questions.length === 1 ? 'choice' : 'choices'}` }) }), questions.map((q, qIdx) => (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: `${q.header}` }), q.options.map((opt, oIdx) => (_jsx(Text, { children: ` ${oIdx + 1}. ${truncateLabel(opt.label)}${oIdx === 0 ? ' (default)' : ''}` }, `q-${qIdx}-o-${oIdx}`)))] }, `q-${qIdx}-${q.header}`))), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, italic: true, children: `(non-interactive — defaults apply)` }) })] }));
|
|
220
|
+
}
|
|
221
|
+
// ─── Interactive Ink render ─────────────────────────────────────────
|
|
222
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { children: _jsx(Text, { bold: true, children: `Pugi: ${questions.length} quick ${questions.length === 1 ? 'choice' : 'choices'}` }) }), _jsx(Box, { flexDirection: "row", marginTop: 1, children: questions.map((q, qIdx) => {
|
|
223
|
+
const isFocused = qIdx === focusedQuestion;
|
|
224
|
+
const cursor = cursorPerQuestion[qIdx] ?? 0;
|
|
225
|
+
const isSkipped = skipped[qIdx] === true;
|
|
226
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: isFocused ? 'cyan' : 'gray', paddingX: 1, marginRight: 1, minWidth: 24, children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, color: isFocused ? 'cyan' : undefined, children: q.header }), isSkipped ? (_jsx(Text, { dimColor: true, italic: true, children: ' (skipped)' })) : null] }), q.options.map((opt, oIdx) => {
|
|
227
|
+
const isHighlighted = isFocused && oIdx === cursor;
|
|
228
|
+
const label = truncateLabel(opt.label);
|
|
229
|
+
const isSkipOption = label === ASK_CHIPS_SKIP_LABEL ||
|
|
230
|
+
opt.label === ASK_CHIPS_SKIP_LABEL;
|
|
231
|
+
return (_jsxs(Box, { children: [_jsx(Text, { color: isHighlighted ? 'cyan' : undefined, bold: isHighlighted, children: isHighlighted ? '▸ ' : ' ' }), _jsx(Text, { color: isHighlighted ? 'cyan' : undefined, children: `${oIdx + 1} ` }), isSkipOption ? (_jsx(Text, { dimColor: true, italic: true, children: label })) : (_jsx(Text, { bold: isHighlighted, children: label }))] }, `opt-${qIdx}-${oIdx}-${opt.label}`));
|
|
232
|
+
})] }, `chip-${qIdx}-${q.header}`));
|
|
233
|
+
}) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: `[Enter] use highlighted defaults · [↑↓] navigate · [←→] switch · [s] skip · [Esc] cancel` }) })] }));
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Encode the chip verdict into the persona-recognisable turn-injection
|
|
237
|
+
* string. Mirrors `encodeAskUserQuestionVerdict` from the single-question
|
|
238
|
+
* prompt so the model sees a consistent grammar.
|
|
239
|
+
*
|
|
240
|
+
* Examples:
|
|
241
|
+
* - { cancelled: true } → "[ASK-USER-QUESTION:cancelled]"
|
|
242
|
+
* - All defaults → "[ASK-USER-QUESTION:answered] Stack=Browser JS; Size=9x9 (classic)"
|
|
243
|
+
* - One skipped → "[ASK-USER-QUESTION:answered] Stack=Browser JS; Size=(skipped: 9x9)"
|
|
244
|
+
*/
|
|
245
|
+
export function encodeAskUserQuestionChipsVerdict(result) {
|
|
246
|
+
if (result.cancelled)
|
|
247
|
+
return '[ASK-USER-QUESTION:cancelled]';
|
|
248
|
+
if (result.selections.length === 0)
|
|
249
|
+
return '[ASK-USER-QUESTION:cancelled]';
|
|
250
|
+
const parts = result.selections.map((sel) => {
|
|
251
|
+
if (sel.skipped)
|
|
252
|
+
return `${sel.header}=(skipped: ${sel.pickedLabel})`;
|
|
253
|
+
return `${sel.header}=${sel.pickedLabel}`;
|
|
254
|
+
});
|
|
255
|
+
return `[ASK-USER-QUESTION:answered] ${parts.join('; ')}`;
|
|
256
|
+
}
|
|
257
|
+
//# sourceMappingURL=ask-user-question-chips.js.map
|
package/dist/tui/input-box.js
CHANGED
|
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
/**
|
|
3
3
|
* REPL input box - Sprint (REPL UX P0 wave 1).
|
|
4
4
|
*
|
|
5
|
-
* Bordered, cursor-aware input box matching the upstream tool /
|
|
5
|
+
* Bordered, cursor-aware input box matching the upstream tool / peer CLI
|
|
6
6
|
* aesthetics. Layered upgrade over :
|
|
7
7
|
*
|
|
8
8
|
* - Top divider (full-width cyan rule) anchors the input below the
|
package/dist/tui/render.js
CHANGED
|
@@ -26,7 +26,7 @@ export async function renderSplash(cliVersion) {
|
|
|
26
26
|
* Sentinel thrown when the user dismisses the login picker via Esc
|
|
27
27
|
* or `q`. The CLI dispatcher catches it, prints a one-line abort
|
|
28
28
|
* message, and exits 130 (the standard exit code for SIGINT-style
|
|
29
|
-
* user cancellations — matches gh CLI
|
|
29
|
+
* user cancellations — matches gh CLI and peer CLIs).
|
|
30
30
|
*/
|
|
31
31
|
export class LoginCancelledError extends Error {
|
|
32
32
|
constructor() {
|
package/dist/tui/repl.js
CHANGED
|
@@ -292,7 +292,7 @@ function MainArea({ state, personaNames, nowEpochMs, hideToolStream, toolStreamC
|
|
|
292
292
|
// The window over the transcript is small (last 12 rows) so the
|
|
293
293
|
// bottom of the frame stays anchored to the input box. New agents
|
|
294
294
|
// push the operator line up the screen, mirroring the upstream tool /
|
|
295
|
-
//
|
|
295
|
+
// peer CLI / Gemini CLI rendering.
|
|
296
296
|
const conversationSlice = state.transcript.slice(-CONVERSATION_WINDOW);
|
|
297
297
|
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(ConversationPane, { rows: conversationSlice, personaNames: personaNames }), hideToolStream ? null : (_jsx(Box, { marginTop: 1, children: _jsx(ToolStreamPane, { calls: state.toolCalls, collapsed: toolStreamCollapsed }) })), _jsx(Box, { marginTop: 1, children: _jsx(AgentTreePane, { agents: state.agents, nowEpochMs: nowEpochMs }) })] }));
|
|
298
298
|
}
|
package/dist/tui/status-bar.js
CHANGED
|
@@ -137,7 +137,7 @@ export function connectionLabel(connection) {
|
|
|
137
137
|
* anomaly stands out vs the calm cyan baseline.
|
|
138
138
|
* 3. `tool_running` — surfaces the tool label when available
|
|
139
139
|
* (`tool: read`), falls back to `tool` when not.
|
|
140
|
-
* 4. `awaiting_response` — `dispatching` (matches
|
|
140
|
+
* 4. `awaiting_response` — `dispatching` (matches peer CLI's verb
|
|
141
141
|
* for the same state).
|
|
142
142
|
* 5. `completed` — `shipped` (matches the agent tree status glyph
|
|
143
143
|
* so the operator's eye links the two surfaces).
|
|
@@ -14,7 +14,7 @@ export function resolveDisplayedLatest(npmLatest, serverRecommended) {
|
|
|
14
14
|
: npmLatest;
|
|
15
15
|
}
|
|
16
16
|
/**
|
|
17
|
-
*
|
|
17
|
+
* peer-CLI-style corner banner: single line, dim orange, right
|
|
18
18
|
* aligned. Renders to the bottom of the REPL frame so it never
|
|
19
19
|
* displaces conversation content. Only mounts when an update is
|
|
20
20
|
* actually available; identical to no-op when the registry poll
|
package/dist/tui/welcome-data.js
CHANGED
|
@@ -9,12 +9,12 @@
|
|
|
9
9
|
*
|
|
10
10
|
* ╭────── Pugi v0.1.0-beta.46 ──────╮
|
|
11
11
|
* │ │ Tips for getting started
|
|
12
|
-
* │ Welcome back
|
|
12
|
+
* │ Welcome back operator! │ Run /init to create PUGI.md...
|
|
13
13
|
* │ │ ───────────
|
|
14
14
|
* │ ▗ ▗ ▖ ▖ │ What's new
|
|
15
15
|
* │ ▘▘ ▝▝ │ * 0.1.0-beta.26 — RAG ...
|
|
16
16
|
* │ Sonnet 4.6 (1M context) · Founder
|
|
17
|
-
* │
|
|
17
|
+
* │ operator@gmail.com · pugi-io Org
|
|
18
18
|
* │ <workspace>
|
|
19
19
|
* ╰──────────────────────────────────╯
|
|
20
20
|
*
|
|
@@ -79,8 +79,8 @@ function decodeJwtPayload(token) {
|
|
|
79
79
|
/**
|
|
80
80
|
* Derive a greeting first-name from an email's local part. The split
|
|
81
81
|
* is intentionally aggressive: dot-separated, hyphen-separated, and
|
|
82
|
-
* digit-stripped so `
|
|
83
|
-
* of "
|
|
82
|
+
* digit-stripped so `operator@gmail.com` resolves к "operator" instead
|
|
83
|
+
* of "operator". Title-cases the first segment. Falls back к
|
|
84
84
|
* "operator" when no email is available.
|
|
85
85
|
*
|
|
86
86
|
* Export so the spec can lock the heuristic against edge cases (numeric
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pugi/cli",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.89",
|
|
4
4
|
"description": "Pugi CLI - terminal-native software execution system",
|
|
5
5
|
"homepage": "https://pugi.io",
|
|
6
6
|
"repository": {
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"which": "^6.0.0",
|
|
64
64
|
"zod": "^3.23.0",
|
|
65
65
|
"@pugi/personas": "0.1.2",
|
|
66
|
-
"@pugi/sdk": "0.1.0-beta.
|
|
66
|
+
"@pugi/sdk": "0.1.0-beta.89"
|
|
67
67
|
},
|
|
68
68
|
"devDependencies": {
|
|
69
69
|
"@types/node": "^22.0.0",
|
|
@@ -87,6 +87,7 @@
|
|
|
87
87
|
"doctor": "tsx src/index.ts doctor --json",
|
|
88
88
|
"check:version-lockstep": "bash ../../scripts/check-version-lockstep.sh",
|
|
89
89
|
"pack:smoke": "pnpm run check:version-lockstep && node scripts/pack-smoke.mjs",
|
|
90
|
-
"release-gate": "node scripts/secret-scanner.mjs"
|
|
90
|
+
"release-gate": "node scripts/secret-scanner.mjs",
|
|
91
|
+
"scan:tarball": "STAGE=$(mktemp -d) && npm pack --pack-destination \"$STAGE\" >/dev/null && bash ../../tools/scrub/scan-tarball.sh \"$STAGE\"/*.tgz; RC=$?; rm -rf \"$STAGE\"; exit $RC"
|
|
91
92
|
}
|
|
92
93
|
}
|
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
# Phase 1: the stub responder echoes the directive verbatim — Phase 2
|
|
5
5
|
# wires the slash command through to the live compaction loop. Until
|
|
6
6
|
# then this scenario protects the contract between scenario authoring
|
|
7
|
-
# and the harness envelope shape.
|
|
7
|
+
# and the harness envelope shape. The scenario covers envelope routing,
|
|
8
|
+
# not anti-name-chant, so the persona is free to identify itself on the
|
|
9
|
+
# initial turn.
|
|
8
10
|
|
|
9
11
|
> "/compact --force"
|
|
10
12
|
EXPECT: persona-turn contains "/compact"
|
|
11
|
-
EXPECT_NOT: persona-turn contains "Pugi"
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
# scenario: identity
|
|
2
|
-
# title: Pugi self-identifies
|
|
2
|
+
# title: Pugi self-identifies on the very first turn
|
|
3
3
|
|
|
4
4
|
# CEO directive feedback_live_console_test_every_publish: every published
|
|
5
|
-
# beta must still answer "ты кто?" with the Pugi persona
|
|
6
|
-
#
|
|
7
|
-
#
|
|
5
|
+
# beta must still answer "ты кто?" with the Pugi persona on turn 1. This
|
|
6
|
+
# is the single most-asked dialog in CEO dogfood and the regression that
|
|
7
|
+
# has bitten us most often. The intro fires once per session, so the
|
|
8
|
+
# initial envelope is expected to include the brand name verbatim.
|
|
8
9
|
|
|
9
10
|
> "ты кто?"
|
|
10
11
|
EXPECT: persona-turn contains "Pugi" OR "Пуджи"
|
|
11
|
-
EXPECT_NOT: persona-turn contains "
|
|
12
|
+
EXPECT_NOT: persona-turn contains "Мира"
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
# (real dispatch routing wires in Phase 2). The scenario is authored
|
|
6
6
|
# against the Phase 2 contract — for now it documents the intent and
|
|
7
7
|
# the harness keeps it as a soft EXPECT so the corpus stays loadable.
|
|
8
|
+
# The positive EXPECT already covers the brand name on turn 1, so no
|
|
9
|
+
# anti-name-chant assertion is needed here.
|
|
8
10
|
|
|
9
11
|
> "Hiroshi, please review the authentication module"
|
|
10
12
|
EXPECT: persona-turn contains "Pugi" OR "Hiroshi" OR "Хироси"
|
|
11
|
-
EXPECT_NOT: persona-turn contains "Pugi"
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# scenario: walkback
|
|
2
2
|
# title: Esc-Esc walkback / rewind directive recognized
|
|
3
3
|
|
|
4
|
-
# CEO parity ask
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
4
|
+
# CEO parity ask: the operator must be able to walk back the last turn.
|
|
5
|
+
# Phase 1 ships the harness contract — the live walkback wiring lands
|
|
6
|
+
# later. The scenario asserts the stub responder acknowledges the
|
|
7
|
+
# directive verbatim so the contract holds while the real implementation
|
|
8
|
+
# lands behind it. Envelope-shape only, not anti-name-chant — the persona
|
|
9
|
+
# is free to identify itself on the initial turn.
|
|
9
10
|
|
|
10
11
|
> "/rewind 1"
|
|
11
12
|
EXPECT: persona-turn contains "/rewind"
|
|
12
|
-
EXPECT_NOT: persona-turn contains "Pugi"
|