@skrr-ai/cli 0.1.101 → 0.1.103

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.
Files changed (91) hide show
  1. package/dist/base-command.js +24 -0
  2. package/dist/commands/agent/message/send.js +6 -2
  3. package/dist/commands/agents/triggers/create.js +7 -17
  4. package/dist/commands/agents/triggers/update.js +41 -28
  5. package/dist/commands/commitments/control/apply.d.ts +15 -0
  6. package/dist/commands/commitments/control/apply.js +36 -0
  7. package/dist/commands/commitments/control/preview.d.ts +17 -0
  8. package/dist/commands/commitments/control/preview.js +43 -0
  9. package/dist/commands/commitments/control/show.d.ts +12 -0
  10. package/dist/commands/commitments/control/show.js +29 -0
  11. package/dist/commands/commitments/create.js +5 -2
  12. package/dist/commands/commitments/observation/probe.d.ts +15 -0
  13. package/dist/commands/commitments/observation/probe.js +46 -0
  14. package/dist/commands/commitments/observation/summary.d.ts +9 -0
  15. package/dist/commands/commitments/observation/summary.js +31 -0
  16. package/dist/commands/commitments/preflight.js +18 -1
  17. package/dist/commands/commitments/readiness/show.d.ts +12 -0
  18. package/dist/commands/commitments/readiness/show.js +29 -0
  19. package/dist/commands/commitments/readiness/verify.d.ts +13 -0
  20. package/dist/commands/commitments/readiness/verify.js +33 -0
  21. package/dist/commands/commitments/roster.d.ts +15 -0
  22. package/dist/commands/commitments/roster.js +39 -0
  23. package/dist/commands/commitments/show.js +1 -1
  24. package/dist/commands/commitments/status.d.ts +17 -0
  25. package/dist/commands/commitments/status.js +41 -0
  26. package/dist/commands/commitments/update.js +23 -9
  27. package/dist/commands/commitments/work-plan.d.ts +11 -0
  28. package/dist/commands/commitments/work-plan.js +36 -0
  29. package/dist/commands/compass/effective-policy.d.ts +12 -0
  30. package/dist/commands/compass/effective-policy.js +34 -0
  31. package/dist/commands/compass/update.d.ts +12 -0
  32. package/dist/commands/compass/update.js +27 -0
  33. package/dist/commands/daemon/logs.d.ts +39 -1
  34. package/dist/commands/daemon/logs.js +140 -13
  35. package/dist/commands/followups/checkin.js +46 -3
  36. package/dist/commands/machines/dedicated/exec.js +3 -1
  37. package/dist/commands/proposals/discard.d.ts +4 -0
  38. package/dist/commands/proposals/discard.js +3 -0
  39. package/dist/commands/proposals/keep.d.ts +4 -0
  40. package/dist/commands/proposals/keep.js +3 -0
  41. package/dist/commands/proposals/snooze.d.ts +4 -0
  42. package/dist/commands/proposals/snooze.js +3 -0
  43. package/dist/commands/spaces/workflows/cancel-node.d.ts +16 -0
  44. package/dist/commands/spaces/workflows/cancel-node.js +44 -0
  45. package/dist/commands/spaces/workflows/cancel.d.ts +40 -0
  46. package/dist/commands/spaces/workflows/cancel.js +109 -3
  47. package/dist/commands/spaces/workflows/run.d.ts +9 -0
  48. package/dist/commands/spaces/workflows/run.js +38 -0
  49. package/dist/commands/tasks/approvals/resolve.d.ts +12 -0
  50. package/dist/commands/tasks/approvals/resolve.js +21 -5
  51. package/dist/commands/tasks/schedule.js +1 -1
  52. package/dist/commands/triggers/capabilities.js +7 -0
  53. package/dist/commands/triggers/create.js +12 -18
  54. package/dist/commands/triggers/fire.js +3 -0
  55. package/dist/commands/triggers/update.js +41 -27
  56. package/dist/commands/widgets/check.d.ts +39 -0
  57. package/dist/commands/widgets/check.js +179 -0
  58. package/dist/lib/agent-mailbox.d.ts +0 -1
  59. package/dist/lib/agent-mailbox.js +0 -1
  60. package/dist/lib/commitment-loop-status.d.ts +16 -0
  61. package/dist/lib/commitment-loop-status.js +164 -0
  62. package/dist/lib/commitments.d.ts +47 -1
  63. package/dist/lib/commitments.js +218 -6
  64. package/dist/lib/compasses.d.ts +3 -0
  65. package/dist/lib/compasses.js +1 -0
  66. package/dist/lib/delegated-cli.d.ts +23 -1
  67. package/dist/lib/delegated-cli.js +61 -4
  68. package/dist/lib/first-party-harness.d.ts +1 -1
  69. package/dist/lib/first-party-harness.js +1 -1
  70. package/dist/lib/responsibility-controls.d.ts +11 -0
  71. package/dist/lib/responsibility-controls.js +119 -0
  72. package/dist/lib/session-task-write.d.ts +13 -0
  73. package/dist/lib/session-task-write.js +63 -3
  74. package/dist/lib/stewardship-output.js +13 -2
  75. package/dist/lib/triggers.d.ts +26 -0
  76. package/dist/lib/triggers.js +134 -1
  77. package/dist/lib/widgetCheck.d.ts +123 -0
  78. package/dist/lib/widgetCheck.js +998 -0
  79. package/dist/lib/widgetStaticScan.d.ts +111 -0
  80. package/dist/lib/widgetStaticScan.js +833 -0
  81. package/dist/lib/workflow-node-control.d.ts +10 -0
  82. package/dist/lib/workflow-node-control.js +76 -0
  83. package/dist/lib/workspaces.d.ts +2 -0
  84. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.d.ts +9 -1
  85. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.js +12 -0
  86. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.d.ts +9 -1
  87. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.js +12 -0
  88. package/dist/node_modules/@skrr-ai/auth-core/package.json +1 -1
  89. package/dist/node_modules/@skrr-ai/data-provider/index.js +11924 -8704
  90. package/oclif.manifest.json +1487 -396
  91. package/package.json +11 -2
@@ -1,4 +1,17 @@
1
1
  import type { SessionTaskOperation } from './session-task-endpoints';
2
+ /**
3
+ * The server's refusal when a session's run carries no Task at all — a
4
+ * Commitment, Routine or other autonomous run that is not a Task run. It is
5
+ * the ONE refusal that means "the session-scoped route does not apply here",
6
+ * not "you may not write": such a run legitimately writes the Tasks it created
7
+ * or was handed, through the generic `/api/tasks/:id/*` routes, where
8
+ * `resolveTaskActor` attributes the Agent from the run headers and the Task's
9
+ * own ACL decides. Every other refusal (`session_claim_*`, `task_edit_forbidden`,
10
+ * an unavailable claim check) still fails closed.
11
+ */
12
+ export declare const SESSION_HAS_NO_TASK = "session_has_no_task";
13
+ /** Test hook: forget what this process learned about bound Tasks. */
14
+ export declare function resetSessionTaskWriteCache(): void;
2
15
  /**
3
16
  * A canonical Task write must retain its authenticated session actor even if
4
17
  * the daemon also grants the CLI a wide credential. The wide credential names
@@ -1,9 +1,55 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SESSION_HAS_NO_TASK = void 0;
4
+ exports.resetSessionTaskWriteCache = resetSessionTaskWriteCache;
3
5
  exports.sessionTaskWrite = sessionTaskWrite;
4
6
  const api_fetch_1 = require("./api-fetch");
5
7
  const credential_resolver_1 = require("./credential-resolver");
6
8
  const session_task_endpoints_1 = require("./session-task-endpoints");
9
+ /**
10
+ * The server's refusal when a session's run carries no Task at all — a
11
+ * Commitment, Routine or other autonomous run that is not a Task run. It is
12
+ * the ONE refusal that means "the session-scoped route does not apply here",
13
+ * not "you may not write": such a run legitimately writes the Tasks it created
14
+ * or was handed, through the generic `/api/tasks/:id/*` routes, where
15
+ * `resolveTaskActor` attributes the Agent from the run headers and the Task's
16
+ * own ACL decides. Every other refusal (`session_claim_*`, `task_edit_forbidden`,
17
+ * an unavailable claim check) still fails closed.
18
+ */
19
+ exports.SESSION_HAS_NO_TASK = 'session_has_no_task';
20
+ /**
21
+ * Sessions this process has already learned have no bound Task. A run's Task
22
+ * binding is fixed when the run is admitted, so the answer cannot change for
23
+ * the life of the session; remembering it saves a refused GET on every write.
24
+ */
25
+ const sessionsWithoutBoundTask = new Set();
26
+ /** Test hook: forget what this process learned about bound Tasks. */
27
+ function resetSessionTaskWriteCache() {
28
+ sessionsWithoutBoundTask.clear();
29
+ }
30
+ /** The refusal reason of a 403 `ApiFetchError` (`{ ok:false, reason }` body). */
31
+ function refusalReason(err) {
32
+ if (!err || typeof err !== 'object')
33
+ return undefined;
34
+ const { status, code, body } = err;
35
+ if (status !== 403)
36
+ return undefined;
37
+ if (typeof code === 'string' && code)
38
+ return code;
39
+ if (typeof body !== 'string' || !body)
40
+ return undefined;
41
+ try {
42
+ const parsed = JSON.parse(body);
43
+ if (typeof parsed.reason === 'string')
44
+ return parsed.reason;
45
+ if (typeof parsed.code === 'string')
46
+ return parsed.code;
47
+ }
48
+ catch {
49
+ /* non-JSON body */
50
+ }
51
+ return undefined;
52
+ }
7
53
  /**
8
54
  * A canonical Task write must retain its authenticated session actor even if
9
55
  * the daemon also grants the CLI a wide credential. The wide credential names
@@ -21,14 +67,28 @@ async function sessionTaskWrite(operation, requestedTask, env = process.env) {
21
67
  code: 'SESSION_TASK_CREDENTIAL_MISSING',
22
68
  });
23
69
  }
70
+ // A run with no bound Task (e.g. a Commitment run) has no session-scoped
71
+ // Task surface; its writes take the generic routes (OSK-16124).
72
+ if (sessionsWithoutBoundTask.has(sessionId))
73
+ return null;
24
74
  const prefix = `/api/agentic/sessions/${encodeURIComponent(sessionId)}`;
25
75
  // A scoped endpoint derives its Task and ignores a caller's Task id. Check
26
76
  // the requested coordinate first, so naming a sibling cannot quietly finish
27
77
  // the bound Task instead. The server independently rechecks the live binding
28
78
  // and checkout when committing the write.
29
- const bound = await (0, api_fetch_1.apiFetch)(`${prefix}/task`, {
30
- credential,
31
- });
79
+ let bound;
80
+ try {
81
+ bound = await (0, api_fetch_1.apiFetch)(`${prefix}/task`, {
82
+ credential,
83
+ });
84
+ }
85
+ catch (err) {
86
+ if (refusalReason(err) === exports.SESSION_HAS_NO_TASK) {
87
+ sessionsWithoutBoundTask.add(sessionId);
88
+ return null;
89
+ }
90
+ throw err;
91
+ }
32
92
  if (!bound.id || !(0, session_task_endpoints_1.requestMatchesBoundTask)(bound, requestedTask)) {
33
93
  throw Object.assign(new Error(`This session is bound to task ${bound.identifier || bound.id || '?'} and cannot write the requested Task.`), {
34
94
  code: 'SESSION_SCOPE_TASK_MISMATCH',
@@ -9,12 +9,20 @@ function printStewardshipCoverage(report, log) {
9
9
  log('Coverage: complete within the bounded sources.');
10
10
  return;
11
11
  }
12
- log('Coverage: incomplete. Unreadable sources do not confirm absence.');
12
+ const unreadable = report.coverage.sources.some((source) => source.status === 'unavailable' || source.truncated);
13
+ log(unreadable
14
+ ? 'Coverage: incomplete. Unreadable sources do not confirm absence.'
15
+ : 'Coverage: incomplete. Some Grants are not attributed to this workspace and are not listed.');
13
16
  for (const source of report.coverage.sources) {
14
17
  if (source.status === 'unavailable')
15
18
  log(`${source.name}: unavailable`);
16
19
  else if (source.truncated)
17
20
  log(`${source.name}: truncated at 200 rows`);
21
+ if (source.unlisted) {
22
+ // A capped count is a lower bound; say so rather than print 200 as exact.
23
+ const count = `${source.truncated ? 'at least ' : ''}${source.unlisted}`;
24
+ log(`${source.name}: ${count} Grant${source.unlisted === 1 && !source.truncated ? '' : 's'} on an Agent or Commitment in this workspace carry no workspace stamp, so they are not listed here. Their owners can see them with \`skrr grants list\`.`);
25
+ }
18
26
  }
19
27
  }
20
28
  function printWorkspaceGrants(report, log) {
@@ -27,7 +35,10 @@ function printGrantRows(report, log) {
27
35
  return;
28
36
  }
29
37
  if (!report.grants.length) {
30
- log('No workspace Grants observed.');
38
+ const unlisted = report.coverage.sources.some((source) => source.unlisted);
39
+ log(unlisted
40
+ ? 'No Grant carries this workspace stamp (see the coverage note above).'
41
+ : 'No workspace Grants observed.');
31
42
  return;
32
43
  }
33
44
  (0, format_1.renderTable)(report.grants.map((row) => ({
@@ -244,6 +244,8 @@ export declare function buildOnceConfigFromFlags(flags: Record<string, unknown>,
244
244
  allowNextRunAtAlias?: boolean;
245
245
  }): Record<string, unknown>;
246
246
  export declare function buildSelfScheduledConfigFromFlags(flags: Record<string, unknown>): Record<string, unknown>;
247
+ export declare function buildEventConfigFromFlags(flags: Record<string, unknown>, filterValues: string[] | undefined): Record<string, unknown>;
248
+ export declare function buildStochasticConfigFromFlags(flags: Record<string, unknown>): Record<string, unknown>;
247
249
  export declare function buildFormConfigFromFlags(flags: Record<string, unknown>): Record<string, unknown>;
248
250
  export declare function buildChatMessageConfigFromFlags(flags: Record<string, unknown>): Record<string, unknown>;
249
251
  export declare function parseExecutionReferences(referenceValues: string[] | undefined, referencesJson: string | undefined): Array<Record<string, unknown>> | undefined;
@@ -269,6 +271,30 @@ export declare function setDefined(target: Record<string, unknown>, values: Reco
269
271
  export declare function mergeObjectField(target: Record<string, unknown>, key: string, values: Record<string, unknown> | undefined): void;
270
272
  /** Attach only the discriminator subdocument owned by `kind`. */
271
273
  export declare function mergeTriggerKindConfig(target: Record<string, unknown>, kind: unknown, configs: Record<string, Record<string, unknown> | undefined>): void;
274
+ /**
275
+ * The kinds whose config block each kind-specific flag fills. A flag that is not listed
276
+ * (--label, --mode, --prompt, --concurrency-key …) belongs to every kind.
277
+ *
278
+ * `mergeTriggerKindConfig` keeps only the block the chosen kind owns, so a flag for another
279
+ * kind used to be accepted and thrown away: `--max-fires-per-day 1` on an on_demand trigger
280
+ * stored no cap, `preflight` said `no_cap`, and the second fire still ran (OSK-15491).
281
+ * Declared beside the builders above, which are what read these flags.
282
+ */
283
+ export declare const FLAG_OWNER_KINDS: Readonly<Record<string, readonly string[]>>;
284
+ /**
285
+ * Refuse a kind-specific flag the trigger's kind cannot use, naming each one and the kinds that
286
+ * take it. Silence here is the defect: the caller believes a limit is in force.
287
+ */
288
+ export declare function assertKindFlagsApply(kind: unknown, flags: Record<string, unknown>, options?: {
289
+ kindIsFixed?: boolean;
290
+ }): void;
291
+ /**
292
+ * The sentence for a fire's `scheduledFire` report: what a MANUAL fire skipped that a scheduled
293
+ * one would not. A manual fire is exempt from the enabled gate, the working/quiet-hours overlay and
294
+ * the autonomy ladder, so on its own it certifies nothing about whether the trigger will ever fire
295
+ * by itself (OSK-15492). Absent when the server reported nothing to say.
296
+ */
297
+ export declare function describeScheduledFire(result: Record<string, unknown>): string | undefined;
272
298
  export declare function withQuery(path: string, params: Record<string, string | number | boolean | string[] | undefined>): string;
273
299
  /**
274
300
  * Build the universal-engine scope block for an agent-owned trigger.
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.triggerApi = exports.AgentTriggerScopeError = exports.SCOPE_IMPLIED_REF_KEY = exports.TRIGGER_EVENT_CATALOG = exports.TRIGGER_EVENT_ENTITY_TYPES = exports.TRIGGER_CHAT_AUTH_MODES = exports.TRIGGER_CHAT_RESPONSE_MODES = exports.TRIGGER_CHAT_MODES = exports.TRIGGER_FORM_RESPONSE_MODES = exports.TRIGGER_CONCURRENCY_POLICIES = exports.TRIGGER_EVENT_FILTER_MODES = exports.TRIGGER_EXECUTOR_MODES = exports.TRIGGER_SCOPE_KINDS = exports.TRIGGER_KINDS = void 0;
3
+ exports.triggerApi = exports.AgentTriggerScopeError = exports.FLAG_OWNER_KINDS = exports.SCOPE_IMPLIED_REF_KEY = exports.TRIGGER_EVENT_CATALOG = exports.TRIGGER_EVENT_ENTITY_TYPES = exports.TRIGGER_CHAT_AUTH_MODES = exports.TRIGGER_CHAT_RESPONSE_MODES = exports.TRIGGER_CHAT_MODES = exports.TRIGGER_FORM_RESPONSE_MODES = exports.TRIGGER_CONCURRENCY_POLICIES = exports.TRIGGER_EVENT_FILTER_MODES = exports.TRIGGER_EXECUTOR_MODES = exports.TRIGGER_SCOPE_KINDS = exports.TRIGGER_KINDS = void 0;
4
4
  exports.validateEventReference = validateEventReference;
5
5
  exports.validateEventFilter = validateEventFilter;
6
6
  exports.serverEventCatalogIncludes = serverEventCatalogIncludes;
@@ -13,6 +13,8 @@ exports.buildScheduleConfigFromFlags = buildScheduleConfigFromFlags;
13
13
  exports.assertScheduleTimingShape = assertScheduleTimingShape;
14
14
  exports.buildOnceConfigFromFlags = buildOnceConfigFromFlags;
15
15
  exports.buildSelfScheduledConfigFromFlags = buildSelfScheduledConfigFromFlags;
16
+ exports.buildEventConfigFromFlags = buildEventConfigFromFlags;
17
+ exports.buildStochasticConfigFromFlags = buildStochasticConfigFromFlags;
16
18
  exports.buildFormConfigFromFlags = buildFormConfigFromFlags;
17
19
  exports.buildChatMessageConfigFromFlags = buildChatMessageConfigFromFlags;
18
20
  exports.parseExecutionReferences = parseExecutionReferences;
@@ -21,6 +23,8 @@ exports.assertEmbeddedActionTokensBound = assertEmbeddedActionTokensBound;
21
23
  exports.setDefined = setDefined;
22
24
  exports.mergeObjectField = mergeObjectField;
23
25
  exports.mergeTriggerKindConfig = mergeTriggerKindConfig;
26
+ exports.assertKindFlagsApply = assertKindFlagsApply;
27
+ exports.describeScheduledFire = describeScheduledFire;
24
28
  exports.withQuery = withQuery;
25
29
  exports.agentScope = agentScope;
26
30
  exports.spaceSubscriberScope = spaceSubscriberScope;
@@ -461,6 +465,29 @@ function buildSelfScheduledConfigFromFlags(flags) {
461
465
  });
462
466
  return selfScheduled;
463
467
  }
468
+ function buildEventConfigFromFlags(flags, filterValues) {
469
+ const event = {};
470
+ setDefined(event, {
471
+ entityType: flags.entity,
472
+ eventType: flags.event,
473
+ filterMode: flags['filter-mode'],
474
+ filterField: flags['filter-field'],
475
+ filterValue: filterValues && filterValues.length > 1 ? filterValues : filterValues?.[0],
476
+ debounceMs: flags['debounce-ms'],
477
+ maxPerHour: flags['max-per-hour'],
478
+ });
479
+ return event;
480
+ }
481
+ function buildStochasticConfigFromFlags(flags) {
482
+ const stochastic = {};
483
+ setDefined(stochastic, {
484
+ probability: flags.probability === undefined ? undefined : Number(flags.probability),
485
+ jitterMs: flags['jitter-ms'],
486
+ maxFiresPerDay: flags['max-fires-per-day'],
487
+ evalIntervalMinutes: flags['eval-interval-minutes'],
488
+ });
489
+ return stochastic;
490
+ }
464
491
  function buildFormConfigFromFlags(flags) {
465
492
  const form = {};
466
493
  const fields = parseJsonArray(flags['form-fields'], '--form-fields');
@@ -633,6 +660,112 @@ function mergeTriggerKindConfig(target, kind, configs) {
633
660
  return;
634
661
  mergeObjectField(target, key, configs[key]);
635
662
  }
663
+ /**
664
+ * The kinds whose config block each kind-specific flag fills. A flag that is not listed
665
+ * (--label, --mode, --prompt, --concurrency-key …) belongs to every kind.
666
+ *
667
+ * `mergeTriggerKindConfig` keeps only the block the chosen kind owns, so a flag for another
668
+ * kind used to be accepted and thrown away: `--max-fires-per-day 1` on an on_demand trigger
669
+ * stored no cap, `preflight` said `no_cap`, and the second fire still ran (OSK-15491).
670
+ * Declared beside the builders above, which are what read these flags.
671
+ */
672
+ exports.FLAG_OWNER_KINDS = Object.freeze({
673
+ cron: ['schedule'],
674
+ 'interval-ms': ['schedule'],
675
+ 'interval-minutes': ['schedule'],
676
+ 'daily-time': ['schedule'],
677
+ 'max-runs': ['schedule'],
678
+ 'end-at': ['schedule'],
679
+ 'stale-catchup-ms': ['schedule'],
680
+ timezone: ['schedule', 'once'],
681
+ 'max-fires-per-day': ['schedule', 'stochastic', 'self_scheduled'],
682
+ at: ['once'],
683
+ 'next-run-at': ['once', 'self_scheduled'],
684
+ entity: ['event'],
685
+ event: ['event'],
686
+ 'filter-mode': ['event'],
687
+ 'filter-field': ['event'],
688
+ 'filter-value': ['event'],
689
+ 'debounce-ms': ['event'],
690
+ 'max-per-hour': ['event'],
691
+ probability: ['stochastic'],
692
+ 'jitter-ms': ['stochastic'],
693
+ 'eval-interval-minutes': ['stochastic'],
694
+ 'form-title': ['form'],
695
+ 'form-subtitle': ['form'],
696
+ 'form-fields': ['form'],
697
+ 'form-response-mode': ['form'],
698
+ 'thank-you-message': ['form'],
699
+ 'redirect-url': ['form'],
700
+ 'ip-allowlist': ['form'],
701
+ 'ignore-bots': ['form'],
702
+ 'submit-button-label': ['form'],
703
+ 'chat-mode': ['chat_message'],
704
+ 'chat-response-mode': ['chat_message'],
705
+ 'allowed-origin': ['chat_message'],
706
+ 'allow-file-uploads': ['chat_message'],
707
+ 'allowed-mime-types': ['chat_message'],
708
+ 'initial-message': ['chat_message'],
709
+ 'suggested-prompt': ['chat_message'],
710
+ 'input-placeholder': ['chat_message'],
711
+ 'auth-mode': ['chat_message'],
712
+ 'load-previous-session': ['chat_message'],
713
+ 'chat-title': ['chat_message'],
714
+ 'chat-subtitle': ['chat_message'],
715
+ });
716
+ const flagWasPassed = (value) => value !== undefined && value !== null && !(Array.isArray(value) && value.length === 0);
717
+ /**
718
+ * Refuse a kind-specific flag the trigger's kind cannot use, naming each one and the kinds that
719
+ * take it. Silence here is the defect: the caller believes a limit is in force.
720
+ */
721
+ function assertKindFlagsApply(kind, flags, options = {}) {
722
+ if (typeof kind !== 'string' || !(kind in CONFIG_KEY_BY_TRIGGER_KIND))
723
+ return;
724
+ const misplaced = Object.entries(exports.FLAG_OWNER_KINDS).filter(([flag, owners]) => flagWasPassed(flags[flag]) && !owners.includes(kind));
725
+ if (misplaced.length === 0)
726
+ return;
727
+ const named = misplaced.map(([flag, owners]) => `--${flag} (${owners.join(', ')})`);
728
+ const verb = misplaced.length === 1 ? 'does' : 'do';
729
+ let subject = `--kind ${kind}`;
730
+ let fix = ' Remove it, or choose a kind that takes it.';
731
+ const holdsNothing = !CONFIG_KEY_BY_TRIGGER_KIND[kind];
732
+ if (options.kindIsFixed) {
733
+ subject = `this trigger, whose kind is ${kind}`;
734
+ fix = ' A trigger kind cannot be changed after creation, so remove it.';
735
+ if (holdsNothing)
736
+ fix = ` An ${kind} trigger has no per-trigger settings.${fix}`;
737
+ }
738
+ else if (holdsNothing) {
739
+ fix = ` An ${kind} trigger has no per-trigger settings; remove it, or choose a kind that takes it.`;
740
+ }
741
+ throw new Error(`${named.join(', ')} ${verb} not apply to ${subject}.${fix}`);
742
+ }
743
+ /**
744
+ * The sentence for a fire's `scheduledFire` report: what a MANUAL fire skipped that a scheduled
745
+ * one would not. A manual fire is exempt from the enabled gate, the working/quiet-hours overlay and
746
+ * the autonomy ladder, so on its own it certifies nothing about whether the trigger will ever fire
747
+ * by itself (OSK-15492). Absent when the server reported nothing to say.
748
+ */
749
+ function describeScheduledFire(result) {
750
+ const report = result.scheduledFire;
751
+ if (typeof report !== 'object' || report === null)
752
+ return undefined;
753
+ const { wouldRun, reason, bypassed } = report;
754
+ const why = typeof reason === 'string' && reason ? ` (${reason})` : '';
755
+ if (Array.isArray(bypassed) && bypassed.length > 0) {
756
+ const gates = bypassed
757
+ .map((entry) => `${String(entry.gate)}${entry.reason ? ` (${String(entry.reason)})` : ''}`)
758
+ .join(', ');
759
+ return `Note: this manual fire bypassed ${gates}; a scheduled fire would not run.`;
760
+ }
761
+ if (wouldRun === false) {
762
+ return `Note: a scheduled fire would not run${why}; a manual fire is exempt from that check.`;
763
+ }
764
+ if (wouldRun === null) {
765
+ return `Note: whether a scheduled fire would run could not be determined${why}.`;
766
+ }
767
+ return undefined;
768
+ }
636
769
  function withQuery(path, params) {
637
770
  const search = new URLSearchParams();
638
771
  for (const [key, value] of Object.entries(params)) {
@@ -0,0 +1,123 @@
1
+ import { type RenderUiWidgetFenceInput } from '@skrr-ai/data-provider';
2
+ export declare const WIDGET_CHECK_REPORT_VERSION: 1;
3
+ export type WidgetIssueSeverity = 'error' | 'warn';
4
+ export type WidgetIssueSource = 'grammar' | 'schema' | 'limit' | 'syntax' | 'static' | 'content';
5
+ export interface WidgetIssue {
6
+ severity: WidgetIssueSeverity;
7
+ source: WidgetIssueSource;
8
+ /** Stable machine code, for example `fence/invalid-header` or `static/network-api`. */
9
+ code: string;
10
+ message: string;
11
+ fix?: string;
12
+ /** The part of the widget the finding is about: a section, `header`, `title`, `total`. */
13
+ section?: string;
14
+ /**
15
+ * 1-based. For a `grammar` finding: the line of the INPUT. For a section-level
16
+ * finding (syntax, static): the line within that section's own text.
17
+ */
18
+ line?: number;
19
+ /** True for static findings: a guess from reading the source, not the sandbox's verdict. */
20
+ heuristic?: true;
21
+ }
22
+ export type LimitKey = 'css' | 'html' | 'js' | 'total' | 'title' | 'fallback';
23
+ export interface WidgetUsage {
24
+ /** Characters (UTF-16 code units): what every limit measures. */
25
+ used: number;
26
+ max: number;
27
+ /** Not clamped: a widget at 207% of its limit says 207. */
28
+ percent: number;
29
+ over: boolean;
30
+ /** Set when the parser stopped counting at the cap, so `used` is a floor. */
31
+ atLeast?: true;
32
+ }
33
+ export type WidgetKind = 'fence' | 'payload' | 'tool-input';
34
+ export interface WidgetReport {
35
+ id: string;
36
+ title: string | null;
37
+ kind: WidgetKind;
38
+ /** Fence input only: the line of the input where this block starts. */
39
+ line?: number;
40
+ /** The widget is final (`complete: true`); false for a refused or cut-off block. */
41
+ complete: boolean;
42
+ /**
43
+ * False when the content was never read (a refused header, a block past the
44
+ * per-message cap): `bytes` and `limits` are then zeros that mean "unknown",
45
+ * not "empty".
46
+ */
47
+ measured: boolean;
48
+ /** The ingest would persist it. A refused widget shows an error state instead. */
49
+ accepted: boolean;
50
+ /** Accepted AND no error-severity finding (and no warning under `strict`). */
51
+ ok: boolean;
52
+ /** UTF-8 bytes, for information. The limits count characters. */
53
+ bytes: {
54
+ css: number;
55
+ html: number;
56
+ js: number;
57
+ total: number;
58
+ };
59
+ limits: Record<LimitKey, WidgetUsage>;
60
+ issues: WidgetIssue[];
61
+ }
62
+ export interface WidgetCheckError {
63
+ code: string;
64
+ message: string;
65
+ fix?: string;
66
+ }
67
+ export interface WidgetCheckReport {
68
+ reportVersion: typeof WIDGET_CHECK_REPORT_VERSION;
69
+ /** Every widget would be accepted and none has an error-severity finding. */
70
+ ok: boolean;
71
+ format: 'fence' | 'json' | 'unknown';
72
+ widgets: WidgetReport[];
73
+ /** Problems with the input as a whole (nothing to check, unreadable JSON, too large). */
74
+ errors: WidgetCheckError[];
75
+ /** Notes about the input as a whole that do not refuse anything. */
76
+ warnings: WidgetCheckError[];
77
+ summary: {
78
+ widgets: number;
79
+ accepted: number;
80
+ ok: number;
81
+ errors: number;
82
+ warnings: number;
83
+ };
84
+ heuristic: string;
85
+ }
86
+ export declare const WIDGET_CHECK_HEURISTIC_NOTE: string;
87
+ /** More than this is refused unread: six widgets at their caps are about 1.2 MB. */
88
+ export declare const WIDGET_CHECK_MAX_INPUT_CHARS: number;
89
+ export type WidgetCheckFormat = 'auto' | 'fence' | 'json';
90
+ export interface WidgetCheckOptions {
91
+ format?: WidgetCheckFormat;
92
+ /** Warnings also make the check fail. */
93
+ strict?: boolean;
94
+ }
95
+ export interface WidgetCheckResult {
96
+ report: WidgetCheckReport;
97
+ /** What `--render-fence` may serialise: only widgets the schema accepted, by index. */
98
+ renderable: Array<{
99
+ index: number;
100
+ input: RenderUiWidgetFenceInput;
101
+ }>;
102
+ }
103
+ /** The report for an input that could not even be read (a missing file, a terminal as stdin). */
104
+ export declare function inputErrorReport(error: WidgetCheckError): WidgetCheckReport;
105
+ /**
106
+ * Check one input: a file's text that contains a widget block, or a JSON widget.
107
+ * Total: never throws, whatever the input.
108
+ */
109
+ export declare function checkWidgetInput(raw: string, options?: WidgetCheckOptions): WidgetCheckResult;
110
+ export interface RenderedFences {
111
+ fences: string[];
112
+ errors: WidgetCheckError[];
113
+ }
114
+ /**
115
+ * Serialise the JSON widgets that passed the schema to correctly delimited fence
116
+ * blocks, and prove each one: the block is parsed back with the same parser the
117
+ * ingest uses and must yield the same title, sections and fallback. The colon
118
+ * count is chosen by `renderUiWidgetFence`, so a widget whose code contains a line
119
+ * of colons still gets a closing line it cannot end early.
120
+ */
121
+ export declare function renderWidgetFences(result: WidgetCheckResult): RenderedFences;
122
+ /** The report as lines of plain text, for a person or a model reading a terminal. */
123
+ export declare function formatWidgetCheckReport(report: WidgetCheckReport): string[];