@sayknow-cli/coding-agent 0.5.15 → 0.5.17

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 CHANGED
@@ -2,6 +2,18 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.5.17] - 2026-09-16
6
+
7
+ ### Fixed
8
+
9
+ - Compute the bundled UI-skill directive before workflow-state lookups in `UserPromptSubmit`, and isolate each lookup so a Linux CI recovery/activation failure cannot return empty context for `animate` / `ralplan` routing.
10
+
11
+ ## [0.5.16] - 2026-09-16
12
+
13
+ ### Fixed
14
+
15
+ - Native skill hooks never throw out of `UserPromptSubmit` / `Stop`. A failed install-root or state lookup returns empty context instead of aborting the turn, which is what collapsed the bundled UI-skill directive in Linux CI.
16
+
5
17
  ## [0.5.15] - 2026-09-16
6
18
 
7
19
  ### Fixed
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@sayknow-cli/coding-agent",
4
- "version": "0.5.15",
4
+ "version": "0.5.17",
5
5
  "description": "Sayknow-CLI CLI with read, bash, edit, write tools and session management",
6
6
  "homepage": "https://sayknow-cli.com",
7
7
  "author": "jaybeyond",
@@ -54,12 +54,12 @@
54
54
  "@agentclientprotocol/sdk": "1.3.0",
55
55
  "@babel/parser": "^7.29.3",
56
56
  "@mozilla/readability": "^0.6.0",
57
- "@sayknow-cli/stats": "0.5.15",
58
- "@sayknow-cli/agent-core": "0.5.15",
59
- "@sayknow-cli/ai": "0.5.15",
60
- "@sayknow-cli/natives": "0.5.15",
61
- "@sayknow-cli/tui": "0.5.15",
62
- "@sayknow-cli/utils": "0.5.15",
57
+ "@sayknow-cli/stats": "0.5.17",
58
+ "@sayknow-cli/agent-core": "0.5.17",
59
+ "@sayknow-cli/ai": "0.5.17",
60
+ "@sayknow-cli/natives": "0.5.17",
61
+ "@sayknow-cli/tui": "0.5.17",
62
+ "@sayknow-cli/utils": "0.5.17",
63
63
  "@puppeteer/browsers": "^2.13.0",
64
64
  "@types/turndown": "5.0.6",
65
65
  "@xterm/headless": "^6.0.0",
@@ -288,47 +288,98 @@ function readSessionFile(payload: HookPayload): string | undefined {
288
288
  export async function dispatchSkcNativeSkillHook(
289
289
  payload: HookPayload,
290
290
  options: SkcNativeHookDispatchOptions = {},
291
+ ): Promise<SkcNativeHookDispatchResult> {
292
+ try {
293
+ return await dispatchSkcNativeSkillHookInner(payload, options);
294
+ } catch {
295
+ // A native hook must never fail the turn. Surface nothing rather than
296
+ // aborting UserPromptSubmit / Stop over an install-root or state lookup.
297
+ const hookEventName = readHookEventName(payload);
298
+ return { hookEventName, outputJson: null };
299
+ }
300
+ }
301
+
302
+ async function dispatchSkcNativeSkillHookInner(
303
+ payload: HookPayload,
304
+ options: SkcNativeHookDispatchOptions = {},
291
305
  ): Promise<SkcNativeHookDispatchResult> {
292
306
  const hookEventName = readHookEventName(payload);
293
307
  const cwd = (options.cwd ?? safeString(payload.cwd).trim()) || process.cwd();
294
308
  if (hookEventName === "UserPromptSubmit") {
295
- const recoveryDiagnostics = await collectUserPromptStateRecoveryDiagnostics({
296
- cwd,
297
- sessionId: readSessionId(payload),
298
- threadId: readThreadId(payload),
299
- stateDir: options.stateDir,
300
- prompt: readPromptText(payload),
301
- sessionFile: readSessionFile(payload),
302
- });
303
- const recoveryContext = buildStateRecoveryDiagnosticsContext(recoveryDiagnostics);
304
309
  const prompt = readPromptText(payload);
305
- const skillState = prompt
306
- ? await recordSkillActivation({
307
- cwd,
308
- text: prompt,
309
- sessionId: readSessionId(payload),
310
- threadId: readThreadId(payload),
311
- turnId: readTurnId(payload),
312
- stateDir: options.stateDir,
313
- })
314
- : null;
315
- const effectiveSkillConfig = skillState
316
- ? await resolveEffectiveSkillConfig(cwd, options.effectiveSkillConfig, options.configPaths, {
317
- sessionId: readSessionId(payload),
318
- threadId: readThreadId(payload),
319
- stateDir: options.stateDir,
320
- })
321
- : undefined;
322
- const activeUltragoalContext = skillState
323
- ? null
324
- : await buildActiveUltragoalPromptContext({
325
- cwd,
326
- sessionId: readSessionId(payload),
327
- threadId: readThreadId(payload),
328
- stateDir: options.stateDir,
329
- prompt,
330
- sessionFile: readSessionFile(payload),
331
- });
310
+ // Compute the bundled UI-skill directive first so a later state/recovery
311
+ // lookup cannot abort the turn without it.
312
+ const uiSkillContext = buildUiSkillActivationContext(prompt);
313
+ let externalUiContext: string | null = null;
314
+ try {
315
+ externalUiContext = await buildExternalUiSkillContext({
316
+ cwd,
317
+ home: options.home ?? os.homedir(),
318
+ text: prompt,
319
+ });
320
+ } catch {
321
+ externalUiContext = null;
322
+ }
323
+
324
+ let recoveryContext: string | null = null;
325
+ try {
326
+ const recoveryDiagnostics = await collectUserPromptStateRecoveryDiagnostics({
327
+ cwd,
328
+ sessionId: readSessionId(payload),
329
+ threadId: readThreadId(payload),
330
+ stateDir: options.stateDir,
331
+ prompt,
332
+ sessionFile: readSessionFile(payload),
333
+ });
334
+ recoveryContext = buildStateRecoveryDiagnosticsContext(recoveryDiagnostics);
335
+ } catch {
336
+ recoveryContext = null;
337
+ }
338
+
339
+ let skillState = null;
340
+ try {
341
+ skillState = prompt
342
+ ? await recordSkillActivation({
343
+ cwd,
344
+ text: prompt,
345
+ sessionId: readSessionId(payload),
346
+ threadId: readThreadId(payload),
347
+ turnId: readTurnId(payload),
348
+ stateDir: options.stateDir,
349
+ })
350
+ : null;
351
+ } catch {
352
+ skillState = null;
353
+ }
354
+
355
+ let effectiveSkillConfig: EffectiveSkillConfigInput | undefined;
356
+ try {
357
+ effectiveSkillConfig = skillState
358
+ ? await resolveEffectiveSkillConfig(cwd, options.effectiveSkillConfig, options.configPaths, {
359
+ sessionId: readSessionId(payload),
360
+ threadId: readThreadId(payload),
361
+ stateDir: options.stateDir,
362
+ })
363
+ : undefined;
364
+ } catch {
365
+ effectiveSkillConfig = undefined;
366
+ }
367
+
368
+ let activeUltragoalContext: string | null = null;
369
+ try {
370
+ activeUltragoalContext = skillState
371
+ ? null
372
+ : await buildActiveUltragoalPromptContext({
373
+ cwd,
374
+ sessionId: readSessionId(payload),
375
+ threadId: readThreadId(payload),
376
+ stateDir: options.stateDir,
377
+ prompt,
378
+ sessionFile: readSessionFile(payload),
379
+ });
380
+ } catch {
381
+ activeUltragoalContext = null;
382
+ }
332
383
  if (activeUltragoalContext?.startsWith("BLOCK_ULTRAGOAL_COMPLETION:")) {
333
384
  return {
334
385
  hookEventName,
@@ -342,25 +393,9 @@ export async function dispatchSkcNativeSkillHook(
342
393
  },
343
394
  };
344
395
  }
345
- // Frontend skills SKC routes to but does not vendor (license-restricted
346
- // collections). Resolved against the user's own install roots. A lookup
347
- // failure must not swallow the bundled UI-skill directive.
348
- let externalUiContext: string | null = null;
349
- try {
350
- externalUiContext = await buildExternalUiSkillContext({
351
- cwd,
352
- home: options.home ?? os.homedir(),
353
- text: prompt,
354
- });
355
- } catch {
356
- externalUiContext = null;
357
- }
358
396
  const additionalContext = [
359
397
  skillState ? buildSkillActivationAdditionalContext(skillState, effectiveSkillConfig) : activeUltragoalContext,
360
- // Bundled frontend UI skills are advertised independently of workflow
361
- // activation: they have no mode state and must fire even when a
362
- // workflow keyword already matched.
363
- buildUiSkillActivationContext(prompt),
398
+ uiSkillContext,
364
399
  externalUiContext,
365
400
  recoveryContext,
366
401
  classifyQuestionOnlyPrompt(prompt),