@sayknow-cli/coding-agent 0.5.11 → 0.5.14
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 +35 -0
- package/dist/types/cli/setup-cli.d.ts +1 -1
- package/dist/types/config/file-lock.d.ts +8 -0
- package/dist/types/config/model-registry.d.ts +1 -0
- package/dist/types/config/models-config-schema.d.ts +2 -0
- package/dist/types/defaults/skc-ui-skills.d.ts +12 -0
- package/dist/types/hooks/ui-skill-keywords.d.ts +84 -0
- package/dist/types/session-import/redact.d.ts +1 -1
- package/dist/types/setup/external-ui-skills.d.ts +37 -0
- package/dist/types/skc-runtime/ultragoal-runtime.d.ts +13 -1
- package/dist/types/skc-runtime/ultragoal-succession.d.ts +251 -0
- package/dist/types/skc-runtime/workflow-placeholder.d.ts +6 -0
- package/package.json +7 -7
- package/src/cli/setup-cli.ts +22 -1
- package/src/cli/skills-cli.ts +29 -10
- package/src/commands/setup.ts +1 -0
- package/src/config/file-lock-gc.ts +39 -2
- package/src/config/file-lock.ts +22 -1
- package/src/config/model-registry.ts +116 -40
- package/src/config/models-config-schema.ts +1 -0
- package/src/defaults/skc/skills/ultragoal/SKILL.md +3 -0
- package/src/defaults/skc/ui-skills/LICENSE.appllama +21 -0
- package/src/defaults/skc/ui-skills/LICENSE.emilkowalski +21 -0
- package/src/defaults/skc/ui-skills/NOTICE.md +43 -0
- package/src/defaults/skc/ui-skills/animate/SKILL.md +536 -0
- package/src/defaults/skc/ui-skills/animation-vocabulary/SKILL.md +178 -0
- package/src/defaults/skc/ui-skills/apple-design/SKILL.md +285 -0
- package/src/defaults/skc/ui-skills/appllama-app-design-skill/SKILL.md +821 -0
- package/src/defaults/skc/ui-skills/ask-sonner/SKILL.md +157 -0
- package/src/defaults/skc/ui-skills/emil-design-eng/SKILL.md +671 -0
- package/src/defaults/skc/ui-skills/find-animation-opportunities/SKILL.md +137 -0
- package/src/defaults/skc/ui-skills/improve-animations/SKILL.md +305 -0
- package/src/defaults/skc/ui-skills/mobile-native/SKILL.md +308 -0
- package/src/defaults/skc/ui-skills/pick-ui-library/SKILL.md +82 -0
- package/src/defaults/skc/ui-skills/prototype/SKILL.md +300 -0
- package/src/defaults/skc/ui-skills/react-bits/SKILL.md +113 -0
- package/src/defaults/skc/ui-skills/review-animations/SKILL.md +312 -0
- package/src/defaults/skc-ui-skills.ts +108 -0
- package/src/extensibility/runtime-skill-discovery.ts +8 -2
- package/src/hooks/native-skill-hook.ts +14 -0
- package/src/hooks/ui-skill-keywords.ts +312 -0
- package/src/internal-urls/docs-index.generated.ts +1 -1
- package/src/prompts/agents/architect.md +1 -0
- package/src/prompts/agents/critic.md +1 -0
- package/src/prompts/agents/executor.md +1 -0
- package/src/prompts/agents/planner.md +1 -0
- package/src/prompts/system/system-prompt.md +1 -0
- package/src/prompts/tools/skill.md +2 -2
- package/src/sdk/session.ts +7 -5
- package/src/session-import/redact.ts +11 -3
- package/src/setup/external-ui-skills.ts +132 -0
- package/src/skc-runtime/state-writer.ts +24 -27
- package/src/skc-runtime/ultragoal-runtime.ts +125 -45
- package/src/skc-runtime/ultragoal-succession.ts +1667 -0
- package/src/skc-runtime/workflow-placeholder.ts +33 -0
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Automatic frontend UI/UX skill routing.
|
|
3
|
+
*
|
|
4
|
+
* The bundled UI craft skills (see `defaults/skc-ui-skills.ts`) are compiled
|
|
5
|
+
* into the binary, so the agent can invoke them without any user install. This
|
|
6
|
+
* module decides WHEN: the native `UserPromptSubmit` hook matches the prompt
|
|
7
|
+
* against the patterns below and injects a directive naming the skill to load.
|
|
8
|
+
*
|
|
9
|
+
* Unlike the canonical workflow skills, UI skills have no `skc state` mode
|
|
10
|
+
* state. Detection here never seeds workflow state; it only advertises the
|
|
11
|
+
* skill for the current turn.
|
|
12
|
+
*
|
|
13
|
+
* Patterns cover English and Korean because prompts arrive in both.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import * as path from "node:path";
|
|
17
|
+
import type { BundledSkcUiSkillName } from "../defaults/skc-ui-skills";
|
|
18
|
+
|
|
19
|
+
export interface UiSkillKeywordDefinition {
|
|
20
|
+
skill: BundledSkcUiSkillName;
|
|
21
|
+
/** Higher wins when several patterns match the same prompt. */
|
|
22
|
+
priority: number;
|
|
23
|
+
pattern: RegExp;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Base craft skill paired with any specialized web frontend match. */
|
|
27
|
+
export const UI_SKILL_BASE: BundledSkcUiSkillName = "emil-design-eng";
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Skills that already carry their own complete craft bar, so pairing them with
|
|
31
|
+
* the web baseline only adds noise. `appllama-app-design-skill` targets native
|
|
32
|
+
* Expo/React Native screens and ships its own motion and fidelity rules.
|
|
33
|
+
*/
|
|
34
|
+
const SELF_SUFFICIENT_UI_SKILLS = new Set<BundledSkcUiSkillName>(["appllama-app-design-skill"]);
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Ordered specialized detectors. The first (highest priority) match selects the
|
|
38
|
+
* lead skill; `emil-design-eng` is added as the craft baseline for build and
|
|
39
|
+
* review work.
|
|
40
|
+
*/
|
|
41
|
+
export const UI_SKILL_KEYWORD_DEFINITIONS: readonly UiSkillKeywordDefinition[] = [
|
|
42
|
+
{
|
|
43
|
+
// Animated React components the React Bits registry already solves.
|
|
44
|
+
// Requires React; the skill itself gates on stack and on the frequency
|
|
45
|
+
// rule before anything is installed.
|
|
46
|
+
skill: "react-bits",
|
|
47
|
+
priority: 110,
|
|
48
|
+
pattern:
|
|
49
|
+
/\b(animated|glitch|shiny|blur|scramble|decrypt|particle|aurora|marquee|dither)\b[^.?!]{0,40}\b(text|background|heading|hero|title)\b|\b(text animation|animated background|cursor (effect|trail)|hover (effect|glow)|scroll (reveal|float|velocity))\b|\breact ?bits\b|애니메이션\s*(텍스트|배경)|텍스트\s*애니메이션|배경\s*애니메이션|커서\s*효과|리액트\s*비츠/i,
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
// Native app screens (Expo / React Native), not a web page on a phone.
|
|
53
|
+
// Highest priority: naming the native stack, or an app-screen surface
|
|
54
|
+
// like a paywall or tab bar, is an unambiguous signal.
|
|
55
|
+
skill: "appllama-app-design-skill",
|
|
56
|
+
priority: 100,
|
|
57
|
+
pattern:
|
|
58
|
+
/\b(expo|react[\s-]?native|reanimated|expo[\s-]?router|flashlist)\b|\b(app|mobile)\s+(screen|onboarding|paywall)\b|\b(paywall|tab bar|bottom sheet|app store)\b|\b(build|design|polish)\b[^.?!]{0,40}\b(mobile|ios|android)\s+(app|screen)\b|리액트\s*네이티브|익스포|네이티브\s*앱|앱\s*화면|온보딩|페이월|탭\s*바/i,
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
skill: "ask-sonner",
|
|
62
|
+
priority: 95,
|
|
63
|
+
pattern: /\bsonner\b|\btoast(s|er)?\b|토스트/i,
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
skill: "pick-ui-library",
|
|
67
|
+
priority: 90,
|
|
68
|
+
pattern:
|
|
69
|
+
/\b(which|what|pick|choose|recommend)\b[^.?!]{0,60}\b(library|package|component|dependency)\b|\b(ui|component)\s+librar(y|ies)\b|라이브러리\s*(추천|선택|골라)|어떤\s*라이브러리/i,
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
skill: "prototype",
|
|
73
|
+
priority: 85,
|
|
74
|
+
pattern:
|
|
75
|
+
/\b(prototype|variants?|several versions|different versions|mockups?)\b|프로토타입|시안|여러\s*(버전|안)/i,
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
skill: "improve-animations",
|
|
79
|
+
priority: 80,
|
|
80
|
+
pattern: /\b(audit|improve|fix)\b[^.?!]{0,40}\b(animations?|motion)\b|애니메이션\s*(개선|감사|정리)|모션\s*개선/i,
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
skill: "review-animations",
|
|
84
|
+
priority: 78,
|
|
85
|
+
pattern:
|
|
86
|
+
/\breview\b[^.?!]{0,40}\b(animations?|motion|transitions?)\b|애니메이션\s*(리뷰|검토)|모션\s*(리뷰|검토)/i,
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
skill: "find-animation-opportunities",
|
|
90
|
+
priority: 76,
|
|
91
|
+
pattern: /\bwhat\b[^.?!]{0,40}\b(could|should)\b[^.?!]{0,20}\banimat/i,
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
skill: "animation-vocabulary",
|
|
95
|
+
priority: 74,
|
|
96
|
+
pattern:
|
|
97
|
+
/\bwhat'?s? it called\b|\bwhat is it called\b|\bname of (that|the) (effect|animation)\b|뭐라고\s*부르|이름이\s*뭐/i,
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
skill: "mobile-native",
|
|
101
|
+
priority: 70,
|
|
102
|
+
pattern:
|
|
103
|
+
/\b(mobile|phone|ios|android|touch|pwa|safe area|100vh|viewport)\b[^.?!]{0,60}\b(web|app|site|feel|native|layout|scroll|tap)\b|\bfeels? like a website\b|모바일\s*(웹|앱)|네이티브\s*(느낌|처럼)/i,
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
skill: "apple-design",
|
|
107
|
+
priority: 68,
|
|
108
|
+
pattern:
|
|
109
|
+
/\bapple\b[^.?!]{0,40}\b(design|motion|style|feel|hig)\b|\b(spring|gesture|drag|swipe|sheet)\b[^.?!]{0,40}\b(animation|motion|physics)\b|애플\s*(디자인|스타일|느낌)/i,
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
skill: "animate",
|
|
113
|
+
priority: 65,
|
|
114
|
+
pattern:
|
|
115
|
+
/\b(animate|animation|motion|transition|easing|keyframes?|hover effect|micro-?interaction)\b|애니메이션|모션|트랜지션|이징/i,
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
skill: UI_SKILL_BASE,
|
|
119
|
+
priority: 40,
|
|
120
|
+
pattern:
|
|
121
|
+
/\b(ui|ux|frontend|front-end|interface|design system|component|css|tailwind|styling|layout|responsive|modal|dropdown|tooltip|popover|drawer|button|spacing|typography)\b|프론트\s*엔드|프론트엔드|디자인\s*(시스템)?|컴포넌트|버튼|모달|드롭다운|툴팁|레이아웃|반응형|스타일링/i,
|
|
122
|
+
},
|
|
123
|
+
];
|
|
124
|
+
|
|
125
|
+
export interface UiSkillKeywordMatch {
|
|
126
|
+
skill: BundledSkcUiSkillName;
|
|
127
|
+
keyword: string;
|
|
128
|
+
priority: number;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Prompts that are purely about SKC's own terminal UI are not web frontend work. */
|
|
132
|
+
const TERMINAL_ONLY_PATTERN = /\b(tui|terminal|ansi|tmux|curses)\b/i;
|
|
133
|
+
const WEB_SURFACE_PATTERN =
|
|
134
|
+
/\b(web|browser|css|html|react|vue|svelte|next\.?js|tailwind|dom|mobile|ios|android|page|screen)\b|웹|브라우저|화면|페이지/i;
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Detect frontend UI/UX intent. Returns matches ordered by priority, most
|
|
138
|
+
* specific first, with `emil-design-eng` appended as the craft baseline.
|
|
139
|
+
*/
|
|
140
|
+
export function detectUiSkillKeywords(text: string): UiSkillKeywordMatch[] {
|
|
141
|
+
const trimmed = text.trim();
|
|
142
|
+
if (!trimmed) return [];
|
|
143
|
+
// A prompt about SKC's own TUI with no web surface mentioned is handled by
|
|
144
|
+
// docs/ui-design-visual-qa.md, not the web craft skills.
|
|
145
|
+
if (TERMINAL_ONLY_PATTERN.test(trimmed) && !WEB_SURFACE_PATTERN.test(trimmed)) return [];
|
|
146
|
+
|
|
147
|
+
const matches: UiSkillKeywordMatch[] = [];
|
|
148
|
+
for (const definition of UI_SKILL_KEYWORD_DEFINITIONS) {
|
|
149
|
+
const match = trimmed.match(definition.pattern);
|
|
150
|
+
if (!match) continue;
|
|
151
|
+
matches.push({ skill: definition.skill, keyword: match[0], priority: definition.priority });
|
|
152
|
+
}
|
|
153
|
+
if (matches.length === 0) return [];
|
|
154
|
+
|
|
155
|
+
matches.sort((a, b) => b.priority - a.priority || a.skill.localeCompare(b.skill));
|
|
156
|
+
const deduped: UiSkillKeywordMatch[] = [];
|
|
157
|
+
for (const item of matches) {
|
|
158
|
+
if (deduped.some(existing => existing.skill === item.skill)) continue;
|
|
159
|
+
deduped.push(item);
|
|
160
|
+
}
|
|
161
|
+
// Keep the lead skill plus the craft baseline; more than that is noise.
|
|
162
|
+
const lead = deduped[0]!;
|
|
163
|
+
if (lead.skill === UI_SKILL_BASE || SELF_SUFFICIENT_UI_SKILLS.has(lead.skill)) return [lead];
|
|
164
|
+
const base = deduped.find(item => item.skill === UI_SKILL_BASE);
|
|
165
|
+
return base ? [lead, base] : [lead];
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Build the `UserPromptSubmit` directive that makes the agent load the matching
|
|
170
|
+
* bundled UI skill for this turn. Returns null when the prompt is not frontend
|
|
171
|
+
* UI/UX work.
|
|
172
|
+
*/
|
|
173
|
+
export function buildUiSkillActivationContext(text: string): string | null {
|
|
174
|
+
const matches = detectUiSkillKeywords(text);
|
|
175
|
+
if (matches.length === 0) return null;
|
|
176
|
+
const names = matches.map(match => `\`${match.skill}\``).join(" then ");
|
|
177
|
+
return [
|
|
178
|
+
`SKC detected frontend UI/UX work in this prompt (matched "${matches[0]!.keyword}").`,
|
|
179
|
+
`Before writing or reviewing that surface, load ${names} with the \`skill\` tool.`,
|
|
180
|
+
"These skills are bundled with SKC: never tell the user to install them, and never skip them because the task looks small.",
|
|
181
|
+
"They are not workflow skills and have no `skc state` mode state.",
|
|
182
|
+
].join(" ");
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Frontend skills SKC routes to but deliberately does NOT vendor.
|
|
187
|
+
*
|
|
188
|
+
* `transitions.dev` publishes an agent skill, but its terms license only the
|
|
189
|
+
* CLI and Refine tooling under MIT; the transition collection itself carries a
|
|
190
|
+
* separate clause that forbids repackaging or publishing the collection (or a
|
|
191
|
+
* substantial part of it). Compiling it into the SKC binary and shipping that
|
|
192
|
+
* to every user is exactly the prohibited redistribution, while installing it
|
|
193
|
+
* into your own project is explicitly allowed. So SKC routes to a user-owned
|
|
194
|
+
* install and never carries the content.
|
|
195
|
+
*/
|
|
196
|
+
export interface ExternalUiSkillDefinition {
|
|
197
|
+
skill: string;
|
|
198
|
+
priority: number;
|
|
199
|
+
pattern: RegExp;
|
|
200
|
+
/**
|
|
201
|
+
* Upstream's own non-interactive install. `-a universal` lands the skill in
|
|
202
|
+
* `<project>/.agents/skills/`, which SKC's agents provider loads. It is the
|
|
203
|
+
* agent that runs this through its normal bash tool, so the install stays a
|
|
204
|
+
* visible, permissioned tool call — never a silent fetch inside the hook.
|
|
205
|
+
*/
|
|
206
|
+
installCommand: string;
|
|
207
|
+
source: string;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export const EXTERNAL_UI_SKILL_DEFINITIONS: readonly ExternalUiSkillDefinition[] = [
|
|
211
|
+
{
|
|
212
|
+
skill: "transitions-dev",
|
|
213
|
+
priority: 120,
|
|
214
|
+
pattern:
|
|
215
|
+
/\b(transition|skeleton (loader|reveal)|shimmer|sliding tabs|streaming text|spinning counter|stagger(ed)? (text )?reveal|pop-?in|icon swap|badge (slide|pop))\b|트랜지션|스켈레톤|시머|스트리밍\s*텍스트|스태거/i,
|
|
216
|
+
installCommand: "npx -y skills@latest add Jakubantalik/transitions.dev --skill transitions-dev -a universal -y",
|
|
217
|
+
source: "https://github.com/Jakubantalik/transitions.dev",
|
|
218
|
+
},
|
|
219
|
+
];
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Skill roots SKC actually loads.
|
|
223
|
+
*
|
|
224
|
+
* Project roots are read by the runtime/claude/codex/agents providers. User
|
|
225
|
+
* roots are deliberately narrower: `~/.claude` and `~/.codex` are ignored by
|
|
226
|
+
* design, so treating them as "installed" would report a skill SKC can never
|
|
227
|
+
* load. Only SKC's own user roots and `~/.agent[s]` qualify.
|
|
228
|
+
*/
|
|
229
|
+
const PROJECT_SKILL_ROOTS = [
|
|
230
|
+
[".skc", "skills"],
|
|
231
|
+
[".claude", "skills"],
|
|
232
|
+
[".codex", "skills"],
|
|
233
|
+
[".agents", "skills"],
|
|
234
|
+
[".agent", "skills"],
|
|
235
|
+
] as const;
|
|
236
|
+
|
|
237
|
+
const USER_SKILL_ROOTS = [
|
|
238
|
+
[".skc", "agent", "skills"],
|
|
239
|
+
[".skc", "skills"],
|
|
240
|
+
[".agents", "skills"],
|
|
241
|
+
[".agent", "skills"],
|
|
242
|
+
] as const;
|
|
243
|
+
|
|
244
|
+
function skillCandidatePaths(cwd: string, home: string, skill: string): string[] {
|
|
245
|
+
const roots: string[] = [];
|
|
246
|
+
// Providers walk up from cwd toward the repo root; mirror a bounded walk.
|
|
247
|
+
let current = path.resolve(cwd);
|
|
248
|
+
const resolvedHome = path.resolve(home);
|
|
249
|
+
for (let depth = 0; depth < 12; depth++) {
|
|
250
|
+
if (current !== resolvedHome) {
|
|
251
|
+
for (const segments of PROJECT_SKILL_ROOTS) {
|
|
252
|
+
roots.push(path.join(current, ...segments, skill, "SKILL.md"));
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
const parent = path.dirname(current);
|
|
256
|
+
if (parent === current) break;
|
|
257
|
+
current = parent;
|
|
258
|
+
}
|
|
259
|
+
for (const segments of USER_SKILL_ROOTS) {
|
|
260
|
+
roots.push(path.join(resolvedHome, ...segments, skill, "SKILL.md"));
|
|
261
|
+
}
|
|
262
|
+
return roots;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
async function isSkillInstalled(cwd: string, home: string, skill: string): Promise<boolean> {
|
|
266
|
+
for (const candidate of skillCandidatePaths(cwd, home, skill)) {
|
|
267
|
+
if (await Bun.file(candidate).exists()) return true;
|
|
268
|
+
}
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export function detectExternalUiSkills(text: string): ExternalUiSkillDefinition[] {
|
|
273
|
+
const trimmed = text.trim();
|
|
274
|
+
if (!trimmed) return [];
|
|
275
|
+
if (TERMINAL_ONLY_PATTERN.test(trimmed) && !WEB_SURFACE_PATTERN.test(trimmed)) return [];
|
|
276
|
+
return EXTERNAL_UI_SKILL_DEFINITIONS.filter(definition => definition.pattern.test(trimmed)).sort(
|
|
277
|
+
(a, b) => b.priority - a.priority,
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Directive for frontend skills SKC routes to but does not vendor.
|
|
283
|
+
*
|
|
284
|
+
* Installed: load it. Missing: the agent runs upstream's own non-interactive
|
|
285
|
+
* install itself, so the user never types the command. The install runs through
|
|
286
|
+
* the agent's bash tool — visible and permissioned — rather than as a silent
|
|
287
|
+
* network fetch inside this hook.
|
|
288
|
+
*/
|
|
289
|
+
export async function buildExternalUiSkillContext(input: {
|
|
290
|
+
cwd: string;
|
|
291
|
+
home: string;
|
|
292
|
+
text: string;
|
|
293
|
+
}): Promise<string | null> {
|
|
294
|
+
const matches = detectExternalUiSkills(input.text);
|
|
295
|
+
if (matches.length === 0) return null;
|
|
296
|
+
const parts: string[] = [];
|
|
297
|
+
for (const match of matches) {
|
|
298
|
+
if (await isSkillInstalled(input.cwd, input.home, match.skill)) {
|
|
299
|
+
parts.push(
|
|
300
|
+
`Load the installed \`${match.skill}\` skill with the \`skill\` tool before implementing this transition, and prefer its snippets over hand-rolled CSS.`,
|
|
301
|
+
);
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
parts.push(
|
|
305
|
+
`\`${match.skill}\` (${match.source}) covers this transition and is not installed yet.`,
|
|
306
|
+
`Install it yourself now — run \`${match.installCommand}\` with the \`bash\` tool, then load \`${match.skill}\` with the \`skill\` tool and use its snippets.`,
|
|
307
|
+
"Do not ask the user to run it. SKC routes to this install instead of bundling the skill because its license permits using the transitions but forbids redistributing the collection.",
|
|
308
|
+
"If the install fails (offline, blocked registry), say so once and fall back to the bundled UI skills.",
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
return parts.length > 0 ? parts.join(" ") : null;
|
|
312
|
+
}
|
|
@@ -134,5 +134,5 @@ export const EMBEDDED_DOCS: Readonly<Record<string, string>> = {
|
|
|
134
134
|
"tree.md": "# `/tree` Command Reference\n\n`/tree` opens the interactive **Session Tree** navigator. It lets you jump to any entry in the current session file and continue from that point.\n\nThis is an in-file leaf move, not a new session export.\n\n## What `/tree` does\n\n- Builds a tree from current session entries (`SessionManager.getTree()`)\n- Opens `TreeSelectorComponent` with keyboard navigation, filters, and search\n- On selection, calls `AgentSession.navigateTree(targetId, { summarize, customInstructions })`\n- Rebuilds visible chat from the new leaf path\n- Optionally prefills editor text when selecting a user/custom message\n\nPrimary implementation:\n\n- `src/modes/controllers/input-controller.ts` (`/tree`, keybinding wiring, double-escape behavior)\n- `src/modes/controllers/selector-controller.ts` (tree UI launch + summary prompt flow)\n- `src/modes/components/tree-selector.ts` (navigation, filters, search, labels, rendering)\n- `src/session/agent-session.ts` (`navigateTree` leaf switching + optional summary)\n- `src/session/session-manager.ts` (`getTree`, `branch`, `branchWithSummary`, `resetLeaf`, label persistence)\n\n## How to open it\n\nAny of the following opens the same selector:\n\n- `/tree`\n- configured keybinding action `tree`\n- double-escape on empty editor when `doubleEscapeAction = \"tree\"` (default)\n- `/branch` when `doubleEscapeAction = \"tree\"` (routes to tree selector instead of user-only branch picker)\n\n## Tree UI model\n\nThe tree is rendered from session entry parent pointers (`id` / `parentId`).\n\n- Children are sorted by timestamp ascending (older first, newer lower)\n- Active branch (path from root to current leaf) is marked with a bullet\n- Labels (if present) render as `[label]` before node text\n- If multiple roots exist (orphaned/broken parent chains), they are shown under a virtual branching root\n\n```text\nExample tree view (active path marked with •):\n\n├─ user: \"Start task\"\n│ └─ assistant: \"Plan\"\n│ ├─ • user: \"Try approach A\"\n│ │ └─ • assistant: \"A result\"\n│ │ └─ • [milestone] user: \"Continue A\"\n│ └─ user: \"Try approach B\"\n│ └─ assistant: \"B result\"\n```\n\nThe selector recenters around current selection and shows up to:\n\n- `max(5, floor(terminalHeight / 2))` rows\n\n## Keybindings inside tree selector\n\n- `Up` / `Down`: move selection (wraps)\n- `Left` / `Right`: page up / page down\n- `Enter`: select node\n- `Esc`: clear search if active; otherwise close selector\n- `Ctrl+C`: close selector\n- `Type`: append to search query\n- `Backspace`: delete search character\n- `Shift+L`: edit/clear label on selected entry\n- `Ctrl+O`: cycle filter forward\n- `Shift+Ctrl+O`: cycle filter backward\n- `Alt+D/T/U/L/A`: jump directly to specific filter mode\n\n## Filters and search semantics\n\nFilter modes (`TreeList`):\n\n1. `default`\n2. `no-tools`\n3. `user-only`\n4. `labeled-only`\n5. `all`\n\n### `default`\n\nShows most conversational nodes, but hides bookkeeping entry types:\n\n- `label`\n- `custom`\n- `model_change`\n- `thinking_level_change`\n\n### `no-tools`\n\nSame as `default`, plus hides `toolResult` messages.\n\n### `user-only`\n\nOnly `message` entries where role is `user`.\n\n### `labeled-only`\n\nOnly entries that currently resolve to a label.\n\n### `all`\n\nEverything in the session tree, including bookkeeping/custom entries.\n\n### Tool-only assistant node behavior\n\nAssistant messages that contain **only tool calls** (no text) are hidden by default in all filtered views unless:\n\n- message is error/aborted (`stopReason` not `stop`/`toolUse`), or\n- it is the current leaf (always kept visible)\n\n### Search behavior\n\n- Query is tokenized by spaces\n- Matching is case-insensitive\n- All tokens must match (AND semantics)\n- Searchable text includes label, role, and type-specific content (message text, branch summary text, custom type, tool command snippets, etc.)\n\n## Selection outcomes (important)\n\n`navigateTree` computes new leaf behavior from selected entry type:\n\n### Selecting `user` message\n\n- New leaf becomes selected entry’s `parentId`\n- If parent is `null` (root user message), leaf resets to root (`resetLeaf()`)\n- Selected message text is copied to editor for editing/resubmit\n\n### Selecting `custom_message`\n\n- Same leaf rule as user messages (`parentId`)\n- Text content is extracted and copied to editor\n\n### Selecting non-user node (assistant/tool/summary/compaction/custom bookkeeping/etc.)\n\n- New leaf becomes selected node id\n- Editor is not prefilled\n\n### Selecting current leaf\n\n- No-op; selector closes with “Already at this point”\n\n```text\nSelection decision (simplified):\n\nselected node\n │\n ├─ is current leaf? ── yes ──> close selector (no-op)\n │\n ├─ is user/custom_message? ── yes ──> leaf := parentId (or resetLeaf for root)\n │ + prefill editor text\n │\n └─ otherwise ──> leaf := selected node id\n + no editor prefill\n```\n\n## Summary-on-switch flow\n\nSummary prompt is controlled by `branchSummary.enabled` (default: `false`).\n\nWhen enabled, after picking a node the UI asks:\n\n- `No summary`\n- `Summarize`\n- `Summarize with custom prompt`\n\nFlow details:\n\n- Escape in summary prompt reopens tree selector\n- Custom prompt cancellation returns to summary choice loop\n- During summarization, UI shows loader and binds `Esc` to `abortBranchSummary()`\n- If summarization aborts, tree selector reopens and no move is applied\n\n`navigateTree` internals:\n\n- Collects abandoned-branch entries from old leaf to common ancestor\n- Emits `session_before_tree` (extensions can cancel or inject summary)\n- Uses default summarizer only if requested and needed\n- Applies move with:\n - `branchWithSummary(...)` when summary exists\n - `branch(newLeafId)` for non-root move without summary\n - `resetLeaf()` for root move without summary\n- Replaces agent conversation with rebuilt session context\n- Emits `session_tree`\n\nNote: if user requests summary but there is nothing to summarize, navigation proceeds without creating a summary entry.\n\n## Labels\n\nLabel edits in tree UI call `appendLabelChange(targetId, label)`.\n\n- non-empty label sets/updates resolved label\n- empty label clears it\n- labels are stored as append-only `label` entries\n- tree nodes display resolved label state, not raw label-entry history\n\n## `/tree` vs adjacent operations\n\n| Operation | Scope | Result |\n| --------- | ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `/tree` | Current session file | Moves leaf to selected point (same file) |\n| `/branch` | Usually current session file -> new session file | By default branches from selected **user** message into a new session file; if `doubleEscapeAction = \"tree\"`, `/branch` opens tree navigation UI instead |\n| `/fork` | Whole current session | Duplicates session into a new persisted session file |\n| `/resume` | Session list | Switches to another session file |\n\nKey distinction: `/tree` is a navigation/repositioning tool inside one session file. `/branch`, `/fork`, and `/resume` all change session-file context.\n\n## Operator workflows\n\n### Re-run from an earlier user prompt without losing current branch\n\n1. `/tree`\n2. search/select earlier user message\n3. choose `No summary` (or summarize if needed)\n4. edit prefilled text in editor\n5. submit\n\nEffect: new branch grows from selected point within same session file.\n\n### Leave current branch with context breadcrumb\n\n1. enable `branchSummary.enabled`\n2. `/tree` and select target node\n3. choose `Summarize` (or custom prompt)\n\nEffect: a `branch_summary` entry is appended at the target position before continuing.\n\n### Investigate hidden bookkeeping entries\n\n1. `/tree`\n2. press `Alt+A` (all)\n3. search for `model`, `thinking`, `custom`, or labels\n\nEffect: inspect full internal timeline, not just conversational nodes.\n\n### Bookmark pivot points for later jumps\n\n1. `/tree`\n2. move to entry\n3. `Shift+L` and set label\n4. later use `Alt+L` (`labeled-only`) to jump quickly\n\nEffect: fast navigation among durable branch landmarks.\n",
|
|
135
135
|
"ttsr-injection-lifecycle.md": "# TTSR Injection Lifecycle\n\nThis document covers the current Time Traveling Stream Rules (TTSR) runtime path from rule discovery to stream interruption, retry injection, extension notifications, and session-state handling.\n\n## Implementation files\n\n- [`../src/sdk/session.ts`](../packages/coding-agent/src/sdk/session.ts)\n- [`../src/export/ttsr.ts`](../packages/coding-agent/src/export/ttsr.ts)\n- [`../src/session/agent-session.ts`](../packages/coding-agent/src/session/agent-session.ts)\n- [`../src/session/session-manager.ts`](../packages/coding-agent/src/session/session-manager.ts)\n- [`../src/prompts/system/ttsr-interrupt.md`](../packages/coding-agent/src/prompts/system/ttsr-interrupt.md)\n- [`../src/capability/index.ts`](../packages/coding-agent/src/capability/index.ts)\n- [`../src/extensibility/extensions/types.ts`](../packages/coding-agent/src/extensibility/extensions/types.ts)\n- [`../src/extensibility/hooks/types.ts`](../packages/coding-agent/src/extensibility/hooks/types.ts)\n- [`../src/extensibility/custom-tools/types.ts`](../packages/coding-agent/src/extensibility/custom-tools/types.ts)\n- [`../src/modes/controllers/event-controller.ts`](../packages/coding-agent/src/modes/controllers/event-controller.ts)\n\n## 1. Discovery feed and rule registration\n\nAt session creation, `createAgentSession()` loads discovered rules and constructs a `TtsrManager`:\n\n```ts\nconst ttsrSettings = settings.getGroup(\"ttsr\");\nconst ttsrManager = new TtsrManager(ttsrSettings);\nconst rulesResult = await loadCapability<Rule>(ruleCapability.id, { cwd });\nfor (const rule of rulesResult.items) {\n if (rule.condition?.length && ttsrManager.addRule(rule)) continue;\n // non-TTSR rules continue through normal rule handling\n}\n```\n\n### Pre-registration dedupe behavior\n\n`loadCapability(\"rules\")` deduplicates by `rule.name` with first-wins semantics (higher provider priority first). Shadowed duplicates are removed before TTSR registration.\n\n### `TtsrManager.addRule()` behavior\n\nRegistration is skipped when:\n\n- `rule.condition` is absent or all condition regexes fail to compile\n- a rule with the same `rule.name` was already registered in this manager\n- the rule scope excludes all monitored streams\n\nInvalid regex conditions and unreachable scopes are logged as warnings and ignored; session startup continues.\n\n### Setting caveat\n\n`TtsrSettings.enabled=false` makes `TtsrManager` no-op (`addRule`, `checkDelta`, `hasRules`, `restoreInjected`), so no conditional rules are registered or injected.\n\n## 2. Streaming monitor lifecycle\n\nTTSR detection runs inside `AgentSession.#handleAgentEvent`.\n\n### Turn start\n\nOn `turn_start`, the stream buffer is reset:\n\n- `ttsrManager.resetBuffer()`\n\n### During stream (`message_update`)\n\nWhen assistant updates arrive and rules exist:\n\n- monitor `text_delta`, `thinking_delta`, and `toolcall_delta`\n- append delta into a source/tool scoped manager buffer\n- call `checkDelta(delta, matchContext)`\n\n`checkDelta()` iterates registered rules and returns all matching rules that pass scope, global-path, condition, and repeat policy checks.\n\n## 3. Trigger decision and immediate abort path\n\nWhen one or more rules match and at least one matched rule allows interruption:\n\n1. Matched rules are deduplicated into `#pendingTtsrInjections`.\n2. `#ttsrAbortPending = true` and a TTSR resume gate is created.\n3. `agent.abort()` is called immediately.\n4. `ttsr_triggered` event is emitted asynchronously (fire-and-forget).\n5. retry work is scheduled via the post-prompt task scheduler with a 50ms delay.\n\nAbort is not blocked on extension callbacks.\n\n## 4. Retry scheduling, context mode, and reminder injection\n\nAfter the 50ms timeout:\n\n1. `#ttsrAbortPending = false`\n2. read `ttsrManager.getSettings().contextMode`\n3. if `contextMode === \"discard\"`, drop the targeted partial assistant output with `agent.replaceMessages(...slice(0, targetAssistantIndex))`\n4. build injection content from pending rules using `ttsr-interrupt.md` template\n5. append and persist a hidden `custom_message`/runtime custom message with `customType: \"ttsr-injection\"` and `details.rules`\n6. mark those rule names injected, persist a `ttsr_injection` entry, and call `agent.continue()` to retry generation\n\nTemplate payload is:\n\n```xml\n<system-interrupt reason=\"rule_violation\" rule=\"{{name}}\" path=\"{{path}}\">\n...\n{{content}}\n</system-interrupt>\n```\n\nPending injections are cleared after content generation.\n\n### `contextMode` behavior on partial output\n\n- `discard`: partial/aborted assistant message is removed before retry.\n- `keep`: partial assistant output remains in conversation state; reminder is appended after it.\n\n### Non-interrupting matches\n\nNon-interrupting matches split by `matchContext.source`:\n\n- **`source === \"tool\"` (tool-source match).** The rule is bucketed into `#perToolTtsrInjections`, keyed by the matched tool call's `id`. There is **no** deferred follow-up turn and the stream is not aborted. When the tool actually produces a result, the `afterToolCall` hook prepends a rendered `ttsr-tool-reminder.md` block to `ctx.result.content` (a single `text` block inserted ahead of the tool's own content), and persists a `ttsr_injection` entry with the consumed rule names. The template payload is:\n\n ```xml\n <system-reminder reason=\"rule_violation\" rule=\"{{name}}\" path=\"{{path}}\">\n ...\n {{content}}\n </system-reminder>\n ```\n\n- **`source === \"text\"` / `\"thinking\"` (prose-source match).** Behavior is unchanged: the rule is queued in `#pendingTtsrInjections` and, after a successful non-error, non-aborted assistant message, `AgentSession` injects the hidden `ttsr-injection` custom message as a follow-up and schedules continuation.\n\nWithin a single matching batch, each rule is attached to exactly one sibling tool call — if multiple sibling tool calls would satisfy the same rule, deduplication picks one and the others are left untouched. Multiple distinct rules can still fold onto the same tool call.\n\n#### Implications for tool authors and transcript readers\n\n- The tool's own `toolResult` content is preserved verbatim; the reminder is **prepended** as an additional leading text block. Renderers that assume `content[0]` is the tool's primary output must scan past any block whose text begins with `<system-reminder reason=\"rule_violation\"` (or filter on the wrapper tag) to find the real payload.\n- The reminder is in-band on the tool result, not a separate `custom_message`/`ttsr-injection` entry. Transcript readers looking for non-interrupting TTSR activity on tool-source rules MUST inspect tool results (and the persisted `ttsr_injection` entry list), not just synthetic injection entries.\n- A single tool result may carry reminders for several rules concatenated with a blank line between rendered templates.\n- If the assistant message ends with `stopReason === \"aborted\"` or `\"error\"` before the matched tools run, the pending per-tool buckets are cleared — those rules are **not** persisted as injected and remain eligible to re-trigger on a future turn (subject to repeat policy).\n\n## 5. Repeat policy and gap logic\n\n`TtsrManager` tracks `#messageCount` and per-rule `lastInjectedAt`.\n\n### `repeatMode: \"once\"`\n\nA rule can trigger only once after it has an injection record.\n\n### `repeatMode: \"after-gap\"`\n\nA rule can re-trigger only when:\n\n- `messageCount - lastInjectedAt >= repeatGap`\n\n`messageCount` increments on `turn_end`, so gap is measured in completed turns, not stream chunks.\n\n## 6. Event emission and extension/hook surfaces\n\n### Session event\n\n`AgentSessionEvent` includes:\n\n```ts\n{ type: \"ttsr_triggered\"; rules: Rule[] }\n```\n\n### Extension runner\n\n`#emitSessionEvent()` routes the event to:\n\n- extension listeners (`ExtensionRunner.emit({ type: \"ttsr_triggered\", rules })`)\n- local session subscribers\n\n### Hook and custom-tool typing\n\n- extension API exposes `on(\"ttsr_triggered\", ...)`\n- hook API exposes `on(\"ttsr_triggered\", ...)`\n- custom tools receive `onSession({ reason: \"ttsr_triggered\", rules })`\n\n### Interactive-mode rendering difference\n\nInteractive mode uses `session.isTtsrAbortPending` to suppress showing the aborted assistant stop reason as a visible failure during TTSR interruption, and renders a `TtsrNotificationComponent` when the event arrives.\n\n## 7. Persistence and resume state (current implementation)\n\n`SessionManager` persists injected-rule state:\n\n- entry type: `ttsr_injection`\n- append API: `appendTtsrInjection(ruleNames)`\n- query API: `getInjectedTtsrRules()`\n- context reconstruction includes `SessionContext.injectedTtsrRules`\n\n`TtsrManager` supports restoration via `restoreInjected(ruleNames)`.\n\n### Current wiring status\n\nIn the current runtime path:\n\n- interrupted injections append a hidden `custom_message` with `customType: \"ttsr-injection\"` and append a `ttsr_injection` entry via `appendTtsrInjection(...)`\n- deferred non-interrupting prose-source injections are marked/persisted when their queued custom message reaches `message_end`\n- non-interrupting tool-source injections are marked at match time and persisted via `appendTtsrInjection(...)` from the `afterToolCall` hook when the matched tool's result is produced\n- `createAgentSession()` restores `existingSession.injectedTtsrRules` into `ttsrManager`\n\nNet effect: injected-rule suppression is persisted/restored across session reload/resume for the current branch path.\n\n## 8. Race boundaries and ordering guarantees\n\n### Abort vs retry callback\n\n- abort is synchronous from TTSR handler perspective (`agent.abort()` called immediately)\n- retry is deferred by timer (`50ms`)\n- extension notification is asynchronous and intentionally not awaited before abort/retry scheduling\n\n### Multiple matches in same stream window\n\n`checkDelta()` returns all currently matching eligible rules for that scoped buffer. Pending injections are deduplicated by rule name before injection.\n\n### Between abort and continue\n\nDuring the timer window, state can change (user interruption, mode actions, additional events). The retry call is best-effort: `agent.continue().catch(() => {})` swallows follow-up errors.\n\n## 9. Edge cases summary\n\n- Invalid `condition` regex: skipped with warning; other conditions/rules continue.\n- Duplicate rule names at capability layer: lower-priority duplicates are shadowed before registration.\n- Duplicate names at manager layer: second registration is ignored.\n- `contextMode: \"keep\"`: partial violating output can remain in context before reminder retry. **Cost warning:** every aborted partial turn is retained, so context (and token spend) grows each time a rule fires. Prefer the default `discard` unless the partial output is specifically needed.\n- `interruptMode: \"never\"`: prose-source matches queue a deferred hidden injection after a successful assistant message; tool-source matches fold an in-band `<system-reminder>` into the matched tool call's `toolResult` content via the `afterToolCall` hook (no mid-stream abort, no separate follow-up turn).\n- Tool-source non-interrupting buckets are cleared when the parent assistant message ends with `stopReason === \"aborted\"` or `\"error\"`, so rules whose target tool never produced a result remain eligible to re-trigger.\n- Repeat-after-gap depends on turn count increments at `turn_end`; mid-turn chunks do not advance gap counters.\n",
|
|
136
136
|
"tui-runtime-internals.md": "# TUI runtime internals\n\nThis document maps the non-theme runtime path from terminal input to rendered output in interactive mode. It focuses on behavior in `packages/tui` and its integration from `packages/coding-agent` controllers.\n\n## Runtime layers and ownership\n\n- **`packages/tui` engine**: terminal lifecycle, stdin normalization, focus routing, render scheduling, differential painting, overlay composition, hardware cursor placement.\n- **`packages/coding-agent` interactive mode**: builds component tree, binds editor callbacks and keymaps, reacts to agent/session events, and translates domain state (streaming, tool execution, retries, plan mode) into UI components.\n\nBoundary rule: the TUI engine is message-agnostic. It only knows `Component.render(width)`, `handleInput(data)`, focus, and overlays. Agent semantics stay in interactive controllers.\n\n## Implementation files\n\n- [`../src/modes/interactive-mode.ts`](../packages/coding-agent/src/modes/interactive-mode.ts)\n- [`../src/modes/controllers/event-controller.ts`](../packages/coding-agent/src/modes/controllers/event-controller.ts)\n- [`../src/modes/controllers/input-controller.ts`](../packages/coding-agent/src/modes/controllers/input-controller.ts)\n- [`../src/modes/components/custom-editor.ts`](../packages/coding-agent/src/modes/components/custom-editor.ts)\n- [`../../tui/src/tui.ts`](../packages/tui/src/tui.ts)\n- [`../../tui/src/terminal.ts`](../packages/tui/src/terminal.ts)\n- [`../../tui/src/editor-component.ts`](../packages/tui/src/editor-component.ts)\n- [`../../tui/src/stdin-buffer.ts`](../packages/tui/src/stdin-buffer.ts)\n- [`../../tui/src/components/loader.ts`](../packages/tui/src/components/loader.ts)\n\n## Boot and component tree assembly\n\n`InteractiveMode` constructs `TUI(new ProcessTerminal(), settings.get(\"showHardwareCursor\"))`, applies `settings.get(\"clearOnShrink\")`, and creates persistent containers:\n\n- `chatContainer`\n- `pendingMessagesContainer`\n- `statusContainer`\n- `todoContainer`\n- `btwContainer`\n- `statusLine`\n- `hookWidgetContainerAbove`\n- `editorContainer` (holds `CustomEditor`)\n- `hookWidgetContainerBelow`\n\n`init()` wires the tree in that order, focuses the editor, registers input handlers via `InputController`, subscribes terminal appearance changes into theme auto-detection, starts TUI, and requests a forced render.\nA forced render (`requestRender(true)`) resets previous-line caches and cursor bookkeeping before repainting.\n\n## Terminal lifecycle and stdin normalization\n\n`ProcessTerminal.start()`:\n\n1. Enables raw mode and bracketed paste.\n2. Attaches resize handler.\n3. Creates a `StdinBuffer` to split partial escape chunks into complete sequences.\n4. Queries Kitty keyboard protocol support (`CSI ? u`), then enables protocol flags if supported; otherwise enables modifyOtherKeys fallback after a short timeout.\n5. Queries OSC 11 background color and enables Mode 2031 appearance notifications for dark/light theme detection.\n6. On Windows, attempts VT input enablement via `kernel32` mode flags.\n `StdinBuffer` behavior:\n\n- Buffers fragmented escape sequences (CSI/OSC/DCS/APC/SS3).\n- Emits `data` only when a sequence is complete or timeout-flushed.\n- Detects bracketed paste and emits a `paste` event with raw pasted text.\n\nThis prevents partial escape chunks from being misinterpreted as normal keypresses.\n\n## Input routing and focus model\n\nInput path:\n\n`stdin -> ProcessTerminal -> StdinBuffer -> TUI.#handleInput -> focusedComponent.handleInput`\n\nRouting details:\n\n1. TUI runs registered input listeners first (`addInputListener`), allowing consume/transform behavior.\n2. TUI handles global debug shortcut (`shift+ctrl+d`) before component dispatch.\n3. If focused component belongs to an overlay that is now hidden/invisible, TUI reassigns focus to next visible overlay or saved pre-overlay focus.\n4. Key release events are filtered unless focused component sets `wantsKeyRelease = true`.\n5. After dispatch, TUI schedules render.\n\n`setFocus()` also toggles `Focusable.focused`, which controls whether components emit `CURSOR_MARKER` for hardware cursor placement.\n\n## Key handling split: editor vs controller\n\n`CustomEditor` intercepts high-priority combos first (escape, ctrl-c/d/z, ctrl-v, ctrl-p variants, ctrl-t, alt-up, extension custom keys) and delegates the rest to base `Editor` behavior (text editing, history, autocomplete, cursor movement).\n\n`InputController.setupKeyHandlers()` then binds editor callbacks to mode actions:\n\n- cancellation / mode exits on `Escape`\n- shutdown on double `Ctrl+C` or empty-editor `Ctrl+D`\n- suspend/resume on `Ctrl+Z`\n- slash-command and selector hotkeys\n- follow-up/dequeue toggles and expansion toggles\n\nThis keeps key parsing/editor mechanics in `packages/tui` and mode semantics in coding-agent controllers.\n\n## Render loop and diffing strategy\n\n`TUI.requestRender()` is debounced to one render per tick using `process.nextTick`. Multiple state changes in the same turn coalesce.\n\n`#doRender()` pipeline:\n\n1. Render root component tree to `newLines`.\n2. Composite visible overlays (if any).\n3. Extract and strip `CURSOR_MARKER` from visible viewport lines.\n4. Append segment reset suffixes for non-image lines.\n5. Choose a viewport repaint, full repaint, or differential patch:\n - real process terminals repaint the visible viewport for width/height changes, forced renders, and edits above the live viewport so native scrollback is not cleared/replayed; host markers refine policy only after this process-terminal capability is established;\n - virtual/headless terminals retain full clear/replay regardless of inherited terminal-host environment markers, keeping historical buffer repair deterministic;\n - steady-state visible changes use differential patches, including viewport repaint when a contraction exposes earlier transcript rows.\n6. For differential updates, patch only changed line ranges and clear stale trailing lines when needed.\n7. Reposition hardware cursor for IME support.\n\nRender writes use synchronized output mode (`CSI ? 2026 h/l`) to reduce flicker/tearing.\n\n## Render safety constraints\n\nCritical safety checks in `TUI`:\n\n- Non-image rendered lines are expected to fit terminal width; the differential path truncates overwide lines as a last-resort guard and can write debug diagnostics when redraw debugging is enabled.\n- Overlay compositing includes defensive truncation and post-composite width guarding.\n- Width changes re-render wrapped content; real process terminals limit emission to the visible viewport because their native scrollback position is not observable.\n- Cursor position is clamped before movement.\n\nThese constraints are runtime guards plus component conventions; renderers should still return width-safe lines rather than rely on truncation.\n\n## Virtual viewport (default-on, `PI_TUI_VIRTUAL_VIEWPORT`)\n\nBy default `#doRender` reuses the previous normalized off-screen prefix and only normalizes/diffs the visible window (terminal rows + a small overscan) when the terminal width is unchanged and the off-screen raw prefix is unchanged from the previous frame. This bounds steady-state append/edit work on very long sessions / weak hardware while preserving byte-identical output.\n\nSet `PI_TUI_VIRTUAL_VIEWPORT=0` (or `false`) to opt out and restore the legacy path that normalizes/truncates and diffs the full rendered transcript every frame (`O(total lines)`). The fast path compares the off-screen raw prefix by raw value equality per line, which short-circuits to a fast reference check when components return stable string instances for unchanged lines; reused entries are deterministic normalizations of identical raw lines. Any width change, off-screen edit, forced render (`requestRender(true)`), or first frame transparently falls back to the full path. `PI_TUI_METRICS` exposes `lineCounts` gauges (`rendered`, `normalized`, `measured`, `diffed`, and `offscreenScan`) to observe the bound.\n\n### Manual transcript scrolling and sticky composer\n\n`CustomEditor` routes `PageUp` and `PageDown` to `TUI.scrollViewportPages()` when autocomplete is not active. Page keys move by the visible transcript lane height minus one; SGR mouse-wheel input moves by `DEFAULT_WHEEL_LINES` (three rows). The TUI records semantic anchors for eligible transcript rows so a manually selected viewport can survive streaming updates, content contraction, and width-dependent reflow.\n\nWhile manual ownership is active, `statusLine` and every following direct child (hooks, editor, pet floor) remain fixed at the bottom. The transcript scrolls only in the remaining rows. If semantic output changes while the user is reviewing history, the TUI shows `New output — type to follow`; reflow and transient chrome changes do not trigger it. Ordinary composer input and paste preserve the existing policy: focus stays on the editor, then `followLiveViewport()` returns to current output before processing the input.\n\nSome rendered pages contain no semantic rows—for example, a page made entirely of tool output or transient panels. Paging into such a page switches manual viewport ownership to the numeric transcript offset instead of rejecting the keypress. Paging back to eligible transcript content establishes a fresh semantic anchor. Pinned chrome and the notice are outside transcript selection/copy coordinates. Under constrained height, the notice and decorative pet/low-priority rows are dropped before the focused editor and status content.\n\nManual-era output remains authoritative in the application transcript but is not retroactively replayed into native terminal/tmux scrollback when following live. Later ordinary live output may naturally move current tail rows into host history.\n\nWhen a downward movement (wheel or `PageDown`) in `scrollViewportBy` clamps to the true maximum transcript top for the current effective manual capacity, the TUI transitions through the existing `followLiveViewport()` transaction instead of painting another manual frame. This makes the bottom reachable through both discrete wheel steps and full-page jumps: a partial downward movement that does not reach the bottom retains manual ownership and the new-output notice, and upward movement never follows. The transition reuses the same live-follow path that ordinary composer input triggers, so focus, pinned chrome, manual-anchor clearing, notice clearing, and fatal terminal transaction semantics remain consistent. Manual-era output is never replayed into native/host scrollback on the transition; the next new semantic output appends through the live frontier exactly once.\n\n### PR1 semantic revision and observer safety\n\nA visible, capped IRC sidebar contributes its semantic projection to the manual-viewport new-output revision even when it produces no inline transcript component. The revision advances only for actual semantic output: duplicate, elided, hidden, geometry-only, and theme-only changes do not show the notice. Re-submitting an equal output source is a no-render operation. When the pinned suffix is constrained, the renderer selects its suffix rows without copying the full transcript-length prefix.\n\n`Session Observer` reads only stable source snapshots. It retains an incomplete append until a complete JSONL line is available, validates replacement candidates before publishing them, and clears cached transcript/model/tool content when the source is replaced, truncated, deleted, unreadable, or malformed. This is source-acquisition safety only: the observer still eagerly rebuilds full-history transcript projections, so its memory and refresh work remain `O(total observed history)`. PR1 does not add projection virtualization or bounded full-history memory.\n\n`PI_TUI_METRICS` structural counters are opt-in deterministic evidence. Timing and RSS samples are advisory observations, not release thresholds.\n\n## Resize handling\n\nResize events are event-driven from `ProcessTerminal` to `TUI.requestResizeRender()`.\n\nEffects:\n\n- Real process terminals repaint only the visible viewport on width/height changes, avoiding scrollback-hostile clear/replay cycles; known host markers and the legacy multiplexer override refine this process-terminal policy.\n- Virtual/headless terminals retain full redraw regardless of inherited host markers for deterministic buffer repair.\n- Viewport/top tracking avoids invalid relative cursor math when content or terminal size changes.\n- Overlay visibility can depend on terminal dimensions (`OverlayOptions.visible`); focus is corrected when overlays become non-visible after resize.\n\n## Streaming and incremental UI updates\n\n`EventController` subscribes to `AgentSessionEvent` and updates UI incrementally:\n\n- `agent_start`: starts loader in `statusContainer`.\n- `message_start` assistant: creates `streamingComponent` and mounts it.\n- `message_update`: updates streaming assistant content; creates/updates tool execution components as tool calls appear.\n- `tool_execution_update/end`: updates tool result components and completion state.\n- `message_end`: finalizes assistant stream, handles aborted/error annotations, marks pending tool args complete on normal stop.\n- `agent_end`: stops loaders, clears transient stream state, flushes deferred model switch, issues terminal completion notification if backgrounded, and runs the user-level completion command hook when configured.\n\nRead-tool grouping is intentionally stateful (`#lastReadGroup`) to coalesce consecutive read tool calls into one visual block until a non-read break occurs.\n\n## Status and loader orchestration\n\nStatus lane ownership:\n\n- `statusContainer` holds transient loaders (`loadingAnimation`, `autoCompactionLoader`, `retryLoader`).\n- `statusLine` renders persistent status/hooks/plan indicators and drives editor top border updates.\n\nLoader behavior:\n\n- `Loader` updates every 80ms via interval and requests render each frame.\n- Escape handlers are temporarily overridden during auto-compaction and auto-retry to cancel those operations.\n- On end/cancel paths, controllers restore prior escape handlers and stop/clear loader components.\n\n## Mode transitions and backgrounding\n\n### Bash/Python input modes\n\nInput text prefixes toggle editor border mode flags:\n\n- `!` -> bash mode\n- `$` (non-template literal prefix) -> python mode\n\nEscape exits inactive mode by clearing editor text and restoring border color; when execution is active, escape aborts the running task instead.\n\n### Plan mode\n\n`InteractiveMode` tracks plan mode flags, status-line state, active tools, and model switching. Enter/exit updates session mode entries and status/UI state, including deferred model switch if streaming is active.\n\n### Suspend/resume (`Ctrl+Z`)\n\n`InputController.handleCtrlZ()`:\n\n1. Registers one-shot `SIGCONT` handler to restart TUI and force render.\n2. Stops TUI before suspend.\n3. Sends `SIGTSTP` to process group.\n\n### Background mode (`/background` or `/bg`)\n\n`handleBackgroundCommand()`:\n\n- Rejects when idle.\n- Switches tool UI context to non-interactive (`hasUI=false`) so interactive UI tools fail fast.\n- Stops loaders/status line and unsubscribes foreground event handler.\n- Subscribes background event handler (primarily waits for `agent_end`).\n- Stops TUI and sends `SIGTSTP` (POSIX job control path).\n\nOn `agent_end` in background with no queued work, controller sends the terminal completion notification and shuts down. The configured `completion.notifyCommand` hook is not limited to background mode; it runs on completed agent turns so local tools such as cmux can receive foreground and background completion events.\n\n## Cancellation paths\n\nPrimary cancellation inputs:\n\n- `Escape` during active stream loader: restores queued messages to editor and aborts agent.\n- `Escape` during bash/python execution: aborts running command.\n- `Escape` during auto-compaction/retry: invokes dedicated abort methods through temporary escape handlers.\n- `Ctrl+C` single press: clear editor; double press within 500ms: shutdown.\n\nCancellation is state-conditional; same key can mean abort, mode-exit, selector trigger, or no-op depending on runtime state.\n\n## Event-driven vs throttled behavior\n\nEvent-driven updates:\n\n- Agent session events (`EventController`)\n- Key input callbacks (`InputController`)\n- terminal resize callback\n- terminal appearance callbacks, SIGWINCH theme reevaluation, and git branch watchers in `InteractiveMode`\n\nThrottled/debounced paths:\n\n- TUI rendering is tick-debounced (`requestRender` coalescing).\n- Loader animation is fixed-interval (80ms), each frame requesting render.\n- Editor autocomplete updates (inside `Editor`) use debounce timers, reducing recompute churn during typing.\n\nThe runtime therefore mixes event-driven state transitions with bounded render cadence to keep interactivity responsive without repaint storms.\n",
|
|
137
|
-
"ui-design-visual-qa.md": "# UI design and visual QA workflow\n\nThis is the repo-owned contract for future Sayknow-CLI UI, web, dashboard, terminal, and TUI visual work. It adapts the useful OMO design-reference and visual-QA workflow without vendoring any third-party design corpus.\n\nIt is not a fifth bundled workflow skill. Sayknow-CLI's public workflow surface remains `deep-interview`, `ralplan`, `ultragoal`, and `team
|
|
137
|
+
"ui-design-visual-qa.md": "# UI design and visual QA workflow\n\nThis is the repo-owned contract for future Sayknow-CLI UI, web, dashboard, terminal, and TUI visual work. It adapts the useful OMO design-reference and visual-QA workflow without vendoring any third-party design corpus.\n\nIt is not a fifth bundled workflow skill. Sayknow-CLI's public workflow surface remains `deep-interview`, `ralplan`, `ultragoal`, and `team`. Frontend UI/UX craft itself is handled by the bundled UI skills under `packages/coding-agent/src/defaults/skc/ui-skills` (`emil-design-eng`, `animate`, and siblings); this document is the SKC-owned visual-QA process for SKC's own TUI/dashboard surfaces, used inside those workflows or direct implementation.\n\n## Required branch before implementation\n\nBefore writing broad UI code, choose and record exactly one workflow branch in the plan, issue update, PR body, or local `DESIGN.md`:\n\n1. **Existing design system** — the repository or product area already has a `DESIGN.md`, component system, theme, or comparable visual grammar. Read it first and update it when the change extends the system.\n2. **Greenfield selected references** — no usable system exists, so pick a small shortlist of design references before implementation. Record the exact references loaded and translate them into first-party project guidance; do not copy raw vendor files into the repo.\n3. **Extract existing system first** — the surface exists but its rules are implicit. The first deliverable is a minimal `DESIGN.md` that extracts the current tokens, layout grammar, component anatomy, and state rules before new product screens are built.\n\nA UI task that skips this branch selection is not ready for implementation.\n\n## `DESIGN.md` source material\n\nNew UI work must create or update the nearest product `DESIGN.md` before broad product-screen implementation. The document must be first-party source material for the implementation, not a screenshot dump or mood board. Include:\n\n- **Tokens** — colors, typography, spacing, radii, borders, iconography, density, terminal color roles, and accessibility contrast constraints.\n- **Layout grammar** — grids, page regions, navigation, hierarchy, rhythm, empty space, alignment, and composition rules.\n- **Component anatomy** — named parts, slots, content rules, affordances, constraints, and when not to use the component.\n- **States** — default, hover, active, focus, disabled, loading, empty, error, selected, expanded/collapsed, and permission/connection failure states where relevant.\n- **Motion and depth** — animation timing/easing, transitions, shadows/elevation, overlays, focus movement, and reduced-motion behavior.\n- **Responsive behavior** — mobile, tablet, desktop, narrow terminal, wide terminal, font scaling, wrapping, overflow, and high-density layouts.\n\nFor greenfield selected references, `DESIGN.md` must name the selected references and explain what was translated from each into the first-party system. It must not embed raw third-party corpus text as the system of record.\n\n## Component showcase before product screens\n\nBuild or update a component showcase/state harness before implementing broad product screens. The harness may be Storybook, a local route, a CLI/TUI fixture, a docs page with runnable examples, or a purpose-built screenshot fixture, but it must expose the component states needed by the product surface.\n\nThe harness must cover:\n\n- all states listed in `DESIGN.md` that the component supports;\n- representative realistic content, including long strings, empty content, error copy, and localization-sensitive text;\n- mobile/tablet/desktop or narrow/medium/wide terminal layouts as applicable;\n- keyboard focus and accessibility-visible states for interactive components.\n\nProduct screens can follow only after the harness proves the component vocabulary is stable enough to reuse.\n\n## Visual QA contract\n\nVisual QA is completion evidence, not decoration. A UI story is not done until it has fresh full-surface evidence from the current branch.\n\nRequired evidence:\n\n- **Fresh capture** — evidence must be generated after the current implementation, not reused from an older branch, design reference, or unrelated run.\n- **Full-surface coverage** — capture every page, route, modal, drawer, tab, breakpoint, component state, and error/empty/loading state in scope. Sampling a few representative screenshots is not enough.\n- **No hidden tails** — scrollable surfaces require evidence for top, middle, bottom, sticky regions, overflow behavior, and any virtualized content boundaries.\n- **CJK semantic line breaks block completion** — Korean, Japanese, Chinese, and mixed CJK/Latin copy must not wrap in semantically broken or visually misleading ways. Bad CJK line breaks are blocking defects, not cosmetic polish.\n- **Independent review before done** — a reviewer or review lane that did not author the implementation must inspect the full evidence set against `DESIGN.md`, the component harness, and acceptance criteria before the work is marked complete.\n- **Evidence references** — PRs should link or attach the current evidence artifacts instead of describing them only in prose.\n\n## Terminal and TUI evidence\n\nTerminal/TUI visual work must preserve terminal semantics in future helper flows. Until Sayknow-CLI has a dedicated helper, terminal visual-QA evidence must document the exact helper requirements and avoid flattening away the data needed for review.\n\nA terminal/TUI evidence helper must produce, at minimum:\n\n- `terminal.txt` with readable plain text;\n- `terminal-ansi.txt` preserving ANSI SGR color/style sequences and terminal control semantics needed for replay/review;\n- `terminal.html` rendering the ANSI-styled output for browser review;\n- optional `terminal.png` generated from the styled rendering when image evidence is useful;\n- `metadata.json` containing command or replay source, terminal size, font/rendering assumptions, capture timestamp, tool version, wrapping/truncation policy, and whether the artifact is from a live PTY, replay, or fixture.\n\nReviewers must reject terminal/TUI evidence that only contains flattened text when the change depends on color, emphasis, cursor state, layout, wrapping, or other ANSI/terminal behavior.\n\n## Provenance boundary\n\nDo not vendor raw third-party design corpora, screenshots, brand guides, prompt packs, or critique/reference material into this repository without an explicit materializer and provenance design.\n\nAllowed in a first PR:\n\n- first-party `DESIGN.md` guidance written for Sayknow-CLI;\n- a small hand-written reference index that names public sources and records why they were consulted;\n- pinned links, citations, or manifests that do not copy raw third-party corpus content.\n\nRequires explicit design before inclusion:\n\n- raw third-party corpus files;\n- copied design-system text, screenshots, or asset packs;\n- submodules or generated materialized corpora;\n- generated screenshots committed as durable source assets.\n\nA future materializer design must define source ownership, license/provenance metadata, pinning strategy, reproducible generation, update review, generated-file boundaries, and what artifacts are safe to attach to GitHub without committing.\n\n## PR checklist\n\nFor UI work, include this checklist in the PR body or equivalent review artifact:\n\n- [ ] UI workflow branch selected: existing `DESIGN.md`, greenfield selected references, or extract existing system first.\n- [ ] `DESIGN.md` created/updated with tokens, layout grammar, component anatomy, states, motion/depth, and responsive behavior.\n- [ ] Component showcase/state harness exists before broad product screens.\n- [ ] Fresh full-surface visual evidence covers every in-scope page/state/breakpoint; no sampling.\n- [ ] CJK semantic line breaks were checked and any defects were fixed before completion.\n- [ ] Terminal/TUI evidence preserves ANSI semantics or documents the exact helper requirements above.\n- [ ] Independent review inspected the evidence before the work was marked done.\n- [ ] No raw third-party corpus was vendored without an explicit materializer/provenance design.\n",
|
|
138
138
|
};
|
|
@@ -40,6 +40,7 @@ You may receive a forked parent-conversation snapshot as background. Your read-o
|
|
|
40
40
|
- Never approve carryover CRITICAL or HIGH severity issues (raised in a prior pass and still unresolved). A fresh CRITICAL/HIGH minted from pass 2 on previously-approved ground blocks only with an explicit why-not-visible-earlier justification (rule 2); without that justification, record it as a non-blocking caveat with its severity noted. On pass 1 every CRITICAL/HIGH blocks.
|
|
41
41
|
- Do not skip spec compliance to jump to style nitpicks.
|
|
42
42
|
- Be constructive: explain why an issue matters and how to fix it or strengthen the design.
|
|
43
|
+
- For frontend UI/UX or motion reviews, invoke `emil-design-eng` and `review-animations` (and `mobile-native` / `apple-design` / `appllama-app-design-skill` when those surfaces apply) before rating visual or motion craft. Do not ask the user to install these skills.
|
|
43
44
|
</constraints>
|
|
44
45
|
|
|
45
46
|
<re_review_ratchet>
|
|
@@ -30,6 +30,7 @@ Review plan clarity, completeness, verification, big-picture fit, referenced fil
|
|
|
30
30
|
- Do not invent problems; report no issues found when the plan passes.
|
|
31
31
|
- Escalate routing needs upward: planner for plan revision, the deep-interview skill for requirements gathering, architect for code analysis.
|
|
32
32
|
- For consensus planning, reject shallow alternatives, driver contradictions, vague risks, weak verification, missing acceptance criteria, or under-specified areas needing expansion before execution.
|
|
33
|
+
- For frontend UI/UX plans, reject missing invocation of the bundled UI skills (`emil-design-eng` and the matching motion/library/prototype/mobile skill) when the work is visual or interactive.
|
|
33
34
|
</constraints>
|
|
34
35
|
|
|
35
36
|
<re_review_ratchet>
|
|
@@ -21,6 +21,7 @@ Explore just enough context, implement the smallest correct change, and leave co
|
|
|
21
21
|
- Explore first, ask last. Ask only when progress is impossible or the next decision is destructive, credentialed, external-production, or materially scope-changing.
|
|
22
22
|
- Use normal repository inspection for file/symbol/pattern lookup. Do not recommend deprecated repository-explore workflows.
|
|
23
23
|
- Respect repository instructions, especially no new dependencies unless explicitly requested.
|
|
24
|
+
- For frontend UI/UX, animation, visual polish, or component-craft work, invoke the matching bundled UI skill (`emil-design-eng`, `animate`, `review-animations`, `improve-animations`, `find-animation-opportunities`, `pick-ui-library`, `prototype`, `mobile-native`, `appllama-app-design-skill` for native Expo/React Native screens, `ask-sonner`, `apple-design`, `animation-vocabulary`) before implementing. Do not ask the user to install these skills.
|
|
24
25
|
</constraints>
|
|
25
26
|
|
|
26
27
|
<execution_loop>
|
|
@@ -31,6 +31,7 @@ Leave execution with a right-sized, evidence-grounded plan: scope, steps, accept
|
|
|
31
31
|
- Right-size the step count; do not default to a fixed number of steps.
|
|
32
32
|
- Do not redesign architecture unless the task requires it.
|
|
33
33
|
- Use SKC command/path semantics (`skc`, `.skc`) for product-facing guidance.
|
|
34
|
+
- For frontend UI/UX work, plan against the bundled UI skills (`emil-design-eng`, `animate`, `review-animations`, `improve-animations`, `find-animation-opportunities`, `pick-ui-library`, `prototype`, `mobile-native`, `appllama-app-design-skill`) rather than inventing a parallel visual process.
|
|
34
35
|
</constraints>
|
|
35
36
|
|
|
36
37
|
<execution_loop>
|
|
@@ -26,6 +26,7 @@ Optimize for correctness first, maintainability second, and brevity third. Prefe
|
|
|
26
26
|
- Clear work with non-trivial architecture or sequencing risk uses `/skill:ralplan --deliberate` and stops pending approval.
|
|
27
27
|
- Use `/skill:ultragoal` for durable goal ledgers and `/skill:team` for approved coordinated persistent work.
|
|
28
28
|
- Delegate large implementation slices to `executor`; use `planner`, `architect`, or `critic` for bounded planning and review.
|
|
29
|
+
- Frontend UI/UX work (web, dashboard, mobile web, native app screens, animation, visual polish, component craft) MUST invoke the matching bundled UI skill before writing or reviewing that surface: `emil-design-eng` for general UI/UX, `animate` for new motion, `review-animations` / `improve-animations` / `find-animation-opportunities` for motion review/audit/opportunity, `pick-ui-library` for library choice, `prototype` for variant exploration, `mobile-native` for phone-web native feel, `appllama-app-design-skill` for native Expo/React Native app screens, `ask-sonner` for Sonner toasts, `apple-design` for Apple-style motion/materials, `animation-vocabulary` for naming a motion effect. These are bundled; do not ask the user to install them. They are not workflow skills and do not use `skc state`.
|
|
29
30
|
- Active skills are authoritative: never ignore an invoked skill; read the full skill text and follow it exactly.
|
|
30
31
|
- Before explicit execution approval, planning and interview workflows NEVER edit product source, run mutating shell commands, commit, push, open PRs, or delegate implementation.
|
|
31
32
|
</routing>
|
|
@@ -6,10 +6,10 @@ Invoke another available skill in the current turn.
|
|
|
6
6
|
</conditions>
|
|
7
7
|
|
|
8
8
|
<instruction>
|
|
9
|
-
- `name` is the skill name as it appears in `/skill:<name>` (e.g. `ralplan`, `ultragoal`, `team`, `deep-interview`)
|
|
9
|
+
- `name` is the skill name as it appears in `/skill:<name>` (e.g. `ralplan`, `ultragoal`, `team`, `deep-interview`, or a bundled UI skill such as `emil-design-eng`)
|
|
10
10
|
- `args` is the free-form argument string the skill would receive after `/skill:<name>` on the command line
|
|
11
11
|
- The tool loads the callee's SKILL.md into the current turn and handles native workflow caller→callee state handoff when the caller is one of the built-in SKC workflows.
|
|
12
|
-
- The chain is refused while a native workflow caller is still active. If your current skill is one of `deep-interview`, `ralplan`, `ultragoal`, or `team` and has not yet reached a terminal phase, prepare it first with `skc state <skill> write --input '{"current_phase":"handoff"}' --json`; no other handoff command is needed.
|
|
12
|
+
- The chain is refused while a native workflow caller is still active. If your current skill is one of `deep-interview`, `ralplan`, `ultragoal`, or `team` and has not yet reached a terminal phase, prepare it first with `skc state <skill> write --input '{"current_phase":"handoff"}' --json`; no other handoff command is needed. Bundled UI skills and runtime project/user skills do not use `skc state <skill>`.
|
|
13
13
|
- Call once per chain step. To chain `A → B → C`, A calls `skill(B)`; B's next agent turn calls `skill(C)`.
|
|
14
14
|
</instruction>
|
|
15
15
|
|
package/src/sdk/session.ts
CHANGED
|
@@ -63,6 +63,7 @@ import "../discovery";
|
|
|
63
63
|
import { resolveConfigValue } from "../config/resolve-config-value";
|
|
64
64
|
import { getEmbeddedDefaultSkcSkills } from "../defaults/skc-defaults";
|
|
65
65
|
import { BUNDLED_GROK_BUILD_EXTENSION_ID, getBundledGrokBuildExtensionFactory } from "../defaults/skc-grok-cli";
|
|
66
|
+
import { withEmbeddedSkcUiSkills } from "../defaults/skc-ui-skills";
|
|
66
67
|
import { initializeWithSettings } from "../discovery";
|
|
67
68
|
import { disposeAllVmContexts, disposeVmContextsByOwner } from "../eval/js/context-manager";
|
|
68
69
|
import { disposeAllKernelSessions, disposeKernelSessionsByOwner } from "../eval/py/executor";
|
|
@@ -982,7 +983,7 @@ function withEmbeddedDefaultSkcSkills(skills: Skill[]): Skill[] {
|
|
|
982
983
|
byName.set(defaultSkill.name, defaultSkill);
|
|
983
984
|
}
|
|
984
985
|
}
|
|
985
|
-
return [...byName.values()];
|
|
986
|
+
return withEmbeddedSkcUiSkills([...byName.values()]);
|
|
986
987
|
}
|
|
987
988
|
|
|
988
989
|
export function resolveIntentTracingEnabled(intentTracingSetting: boolean | undefined, hasUI: boolean): boolean {
|
|
@@ -1383,10 +1384,11 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1383
1384
|
skills = withEmbeddedDefaultSkcSkills(skillsResult.skills);
|
|
1384
1385
|
skillWarnings = skillsResult.warnings;
|
|
1385
1386
|
} else {
|
|
1386
|
-
// SKC's four public workflow skills
|
|
1387
|
-
// default
|
|
1388
|
-
// filesystem skill discovery remains gated by
|
|
1389
|
-
skills
|
|
1387
|
+
// SKC's four public workflow skills plus bundled UI craft skills are
|
|
1388
|
+
// compiled into the binary so the default surface survives accidental
|
|
1389
|
+
// .skc deletion. Arbitrary filesystem skill discovery remains gated by
|
|
1390
|
+
// skills.enabled above.
|
|
1391
|
+
skills = withEmbeddedSkcUiSkills(getEmbeddedDefaultSkcSkills());
|
|
1390
1392
|
skillWarnings = [];
|
|
1391
1393
|
}
|
|
1392
1394
|
|
|
@@ -9,8 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
export const IMPORT_REDACTED_PLACEHOLDER = "[REDACTED]";
|
|
11
11
|
/** Bumped when patterns change; persisted in import provenance. */
|
|
12
|
-
export const IMPORT_SANITIZER_VERSION =
|
|
13
|
-
|
|
12
|
+
export const IMPORT_SANITIZER_VERSION = 5;
|
|
14
13
|
interface RedactionRule {
|
|
15
14
|
readonly id: string;
|
|
16
15
|
readonly pattern: RegExp;
|
|
@@ -68,7 +67,16 @@ const REDACTION_RULES: readonly RedactionRule[] = [
|
|
|
68
67
|
// Long hex secrets (64+ hex chars: webhook secrets, signing keys).
|
|
69
68
|
{ id: "hex-secret", pattern: new RegExp(`\\b${HEX}{64,}\\b`, "g") },
|
|
70
69
|
// Basic-auth credentials embedded in URLs (https://user:pass@host).
|
|
71
|
-
|
|
70
|
+
// The scheme-character run is boundary anchored, so the unbounded suffix is
|
|
71
|
+
// attempted once per maximal run instead of once at every prefix. Keeping the
|
|
72
|
+
// leading non-letter characters in the capture preserves redaction for URLs
|
|
73
|
+
// embedded after digits or scheme punctuation without imposing an arbitrary
|
|
74
|
+
// scheme-length cap. Unbounded `[a-z0-9+.-]*` without that boundary re-tries
|
|
75
|
+
// every prefix of a long alphabetic run, which is quadratic in the input.
|
|
76
|
+
{
|
|
77
|
+
id: "url-credential",
|
|
78
|
+
pattern: /(?<![A-Za-z0-9+.-])([0-9+.-]*[a-z][a-z0-9+.-]*:\/\/)[^/\s:@]{1,256}:[^/\s@]{1,256}@/gi,
|
|
79
|
+
},
|
|
72
80
|
// Sensitive env/KEY assignments: OPENAI_API_KEY=..., token: ..., password = ...
|
|
73
81
|
// The name prefix is optional so a bare sensitive name (`password: …`) also matches.
|
|
74
82
|
{
|