@skrr-ai/cli 0.1.126 → 0.1.128

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 (123) hide show
  1. package/dist/base-command.d.ts +23 -0
  2. package/dist/base-command.js +72 -0
  3. package/dist/commands/agents/chat.js +7 -0
  4. package/dist/commands/agents/goals/list.d.ts +2 -2
  5. package/dist/commands/agents/goals/list.js +2 -2
  6. package/dist/commands/commitments/action-history.js +1 -1
  7. package/dist/commands/commitments/cases.js +3 -3
  8. package/dist/commands/commitments/check-now.d.ts +10 -2
  9. package/dist/commands/commitments/check-now.js +53 -3
  10. package/dist/commands/commitments/create.d.ts +6 -14
  11. package/dist/commands/commitments/create.js +61 -6
  12. package/dist/commands/commitments/doctor.js +3 -1
  13. package/dist/commands/commitments/effective-policy.d.ts +11 -1
  14. package/dist/commands/commitments/effective-policy.js +84 -4
  15. package/dist/commands/commitments/hypotheses.js +12 -12
  16. package/dist/commands/commitments/next-tick.js +5 -0
  17. package/dist/commands/commitments/observations.js +18 -17
  18. package/dist/commands/commitments/pack/list.js +2 -1
  19. package/dist/commands/commitments/pack/plan.d.ts +6 -0
  20. package/dist/commands/commitments/pack/plan.js +19 -0
  21. package/dist/commands/commitments/preflight.d.ts +19 -0
  22. package/dist/commands/commitments/preflight.js +108 -10
  23. package/dist/commands/commitments/trigger-status.js +8 -0
  24. package/dist/commands/commitments/update.d.ts +1 -0
  25. package/dist/commands/commitments/update.js +45 -4
  26. package/dist/commands/commitments/wake-capabilities.js +13 -0
  27. package/dist/commands/compass/archive.js +4 -0
  28. package/dist/commands/compass/commitments.js +15 -1
  29. package/dist/commands/compass/create.js +11 -3
  30. package/dist/commands/compass/effective-policy.d.ts +1 -0
  31. package/dist/commands/compass/effective-policy.js +8 -7
  32. package/dist/commands/compass/revert.d.ts +8 -0
  33. package/dist/commands/compass/revert.js +25 -1
  34. package/dist/commands/compass/revisions.d.ts +2 -0
  35. package/dist/commands/compass/revisions.js +18 -2
  36. package/dist/commands/compass/tactic.d.ts +5 -3
  37. package/dist/commands/compass/tactic.js +8 -7
  38. package/dist/commands/compass/update.js +19 -3
  39. package/dist/commands/followups/act.js +1 -1
  40. package/dist/commands/followups/cancel.js +13 -0
  41. package/dist/commands/followups/observability.js +20 -1
  42. package/dist/commands/followups/plans/create.js +12 -1
  43. package/dist/commands/followups/show.js +13 -0
  44. package/dist/commands/followups/watch.js +1 -1
  45. package/dist/commands/goals/create.d.ts +1 -0
  46. package/dist/commands/goals/create.js +4 -1
  47. package/dist/commands/goals/planner-config.d.ts +11 -0
  48. package/dist/commands/goals/planner-config.js +16 -0
  49. package/dist/commands/goals/revisions.js +3 -19
  50. package/dist/commands/machines/update.d.ts +5 -3
  51. package/dist/commands/machines/update.js +70 -9
  52. package/dist/commands/northstars/create.js +3 -1
  53. package/dist/commands/northstars/governance/configure.d.ts +4 -0
  54. package/dist/commands/northstars/governance/configure.js +75 -9
  55. package/dist/commands/northstars/intents/decide.d.ts +6 -0
  56. package/dist/commands/northstars/intents/decide.js +69 -15
  57. package/dist/commands/northstars/intents/list.d.ts +2 -1
  58. package/dist/commands/northstars/intents/list.js +15 -5
  59. package/dist/commands/northstars/intents.d.ts +26 -0
  60. package/dist/commands/northstars/intents.js +49 -0
  61. package/dist/commands/northstars/list.d.ts +1 -0
  62. package/dist/commands/northstars/list.js +24 -3
  63. package/dist/commands/northstars/recommendations/list.d.ts +1 -1
  64. package/dist/commands/northstars/recommendations/list.js +3 -3
  65. package/dist/commands/northstars/recommendations.d.ts +19 -0
  66. package/dist/commands/northstars/recommendations.js +42 -0
  67. package/dist/commands/northstars/review-now.d.ts +1 -0
  68. package/dist/commands/northstars/review-now.js +10 -4
  69. package/dist/commands/northstars/reviews.d.ts +1 -0
  70. package/dist/commands/northstars/reviews.js +19 -3
  71. package/dist/commands/northstars/update.js +8 -1
  72. package/dist/commands/proposals/list.js +11 -2
  73. package/dist/commands/proposals/show.js +9 -1
  74. package/dist/commands/spaces/auto-executor/preview.d.ts +14 -1
  75. package/dist/commands/spaces/auto-executor/preview.js +35 -1
  76. package/dist/commands/spaces/auto-executor/run.d.ts +17 -0
  77. package/dist/commands/spaces/auto-executor/run.js +60 -2
  78. package/dist/commands/spaces/files/upload.js +1 -2
  79. package/dist/commands/spaces/index.js +23 -0
  80. package/dist/commands/spaces/revisions.js +3 -2
  81. package/dist/commands/tasks/revisions.js +3 -2
  82. package/dist/commands/triggers/fire-detail.js +2 -0
  83. package/dist/generated/agent-guides.json +69 -69
  84. package/dist/lib/agentic-stream.d.ts +9 -0
  85. package/dist/lib/agentic-stream.js +14 -0
  86. package/dist/lib/command-miss.js +11 -2
  87. package/dist/lib/commitment-attention-copy.d.ts +96 -0
  88. package/dist/lib/commitment-attention-copy.js +209 -0
  89. package/dist/lib/commitment-loop-status.d.ts +6 -0
  90. package/dist/lib/commitment-loop-status.js +16 -6
  91. package/dist/lib/commitments.d.ts +88 -0
  92. package/dist/lib/commitments.js +188 -0
  93. package/dist/lib/compass-body-help.d.ts +20 -0
  94. package/dist/lib/compass-body-help.js +61 -0
  95. package/dist/lib/compass-refusals.d.ts +35 -0
  96. package/dist/lib/compass-refusals.js +50 -0
  97. package/dist/lib/compasses.d.ts +80 -4
  98. package/dist/lib/compasses.js +232 -12
  99. package/dist/lib/followups.d.ts +22 -1
  100. package/dist/lib/followups.js +21 -1
  101. package/dist/lib/format.d.ts +11 -0
  102. package/dist/lib/format.js +29 -0
  103. package/dist/lib/initiatives.d.ts +9 -2
  104. package/dist/lib/initiatives.js +13 -2
  105. package/dist/lib/machines.d.ts +14 -1
  106. package/dist/lib/machines.js +24 -0
  107. package/dist/lib/northstars.d.ts +158 -24
  108. package/dist/lib/northstars.js +538 -26
  109. package/dist/lib/proposal-decision-command.js +21 -1
  110. package/dist/lib/proposals.d.ts +26 -1
  111. package/dist/lib/proposals.js +163 -0
  112. package/dist/lib/rate-limit-retry.d.ts +5 -0
  113. package/dist/lib/rate-limit-retry.js +19 -0
  114. package/dist/lib/revision-actor.d.ts +2 -0
  115. package/dist/lib/revision-actor.js +16 -0
  116. package/dist/lib/roles.d.ts +1 -1
  117. package/dist/lib/space-charter.js +9 -0
  118. package/dist/lib/task-asset-upload.js +1 -2
  119. package/dist/lib/triggers.d.ts +33 -1
  120. package/dist/lib/triggers.js +77 -3
  121. package/dist/node_modules/@skrr-ai/data-provider/index.js +7537 -7474
  122. package/oclif.manifest.json +357 -34
  123. package/package.json +4 -4
@@ -1,5 +1,6 @@
1
1
  import { Command, Flags, type Interfaces } from '@oclif/core';
2
2
  import { type CliOperationOptions } from './lib/json-output';
3
+ import { type CompassRefusalContext } from './lib/compass-refusals';
3
4
  export { resolveProfileFromArgv } from './lib/keychain';
4
5
  import { dataService } from '@skrr-ai/data-provider';
5
6
  import { type WriteTarget } from './lib/write-target';
@@ -413,6 +414,12 @@ export declare abstract class BaseCommand extends Command {
413
414
  * path the person never typed. See `lib/request-field-flags.ts`.
414
415
  */
415
416
  protected requestFieldFlags?: Readonly<Record<string, string>>;
417
+ /**
418
+ * The Compass (and Agent) this invocation is about, set by a `compass` command before it sends.
419
+ * A stale version or a missing revision is answered with the command to run next, and that
420
+ * command names these. Unset, the hint carries placeholders. See `lib/compass-refusals.ts`.
421
+ */
422
+ protected compassRef?: CompassRefusalContext;
416
423
  protected handleApiError(err: unknown, { subject }?: {
417
424
  subject?: string;
418
425
  }): never;
@@ -515,6 +522,22 @@ export declare function apiRefusal(err: unknown): {
515
522
  /** The parsed JSON body, for a refusal that carries structured context. */
516
523
  body?: Record<string, unknown>;
517
524
  };
525
+ /**
526
+ * The remedy a refused Commitment activation carries.
527
+ *
528
+ * The refusal's message names the blocking Preflight gate by its QUESTION and a reason token
529
+ * ("Can the runtime enforce this Run? (runtime_commitment_contract_unavailable)"). The gate's own
530
+ * `hint` — the sentence that says what to run — travels beside it at
531
+ * `diagnostic.firstBlocker.detail`, and the web client already shows it
532
+ * (`activationFailureMessage`). The CLI dropped it, so `commitments resume` and
533
+ * `commitments create --activate` told a person to "fix the gate the reason names" while
534
+ * `commitments preflight` knew the exact command (OSK-21126).
535
+ *
536
+ * Undefined when the gate offered none, so a caller keeps its generic wording.
537
+ *
538
+ * Exported for tests.
539
+ */
540
+ export declare function commitmentActivationHint(body: Record<string, unknown> | null | undefined): string | undefined;
518
541
  /**
519
542
  * Prefix a failure message with the resource it is about.
520
543
  *
@@ -5,6 +5,7 @@ exports.assigneePayloadNeedsResolution = assigneePayloadNeedsResolution;
5
5
  exports.parseGlobalFlags = parseGlobalFlags;
6
6
  exports.refusalCodeOf = refusalCodeOf;
7
7
  exports.apiRefusal = apiRefusal;
8
+ exports.commitmentActivationHint = commitmentActivationHint;
8
9
  exports.withSubject = withSubject;
9
10
  exports.describeHarnessUnavailable = describeHarnessUnavailable;
10
11
  exports.describeGitHubUnavailable = describeGitHubUnavailable;
@@ -34,6 +35,7 @@ const failed_lookup_1 = require("./lib/failed-lookup");
34
35
  const internal_id_1 = require("./lib/internal-id");
35
36
  const http_error_1 = require("./lib/http-error");
36
37
  const api_error_reason_1 = require("./lib/api-error-reason");
38
+ const compass_refusals_1 = require("./lib/compass-refusals");
37
39
  const guide_pointer_1 = require("./lib/guide-pointer");
38
40
  // Re-exported from its home in `lib/keychain`, beside the other profile
39
41
  // functions. It lived here while `init()` was its only caller; a lib module
@@ -1467,6 +1469,12 @@ class BaseCommand extends core_1.Command {
1467
1469
  * path the person never typed. See `lib/request-field-flags.ts`.
1468
1470
  */
1469
1471
  requestFieldFlags;
1472
+ /**
1473
+ * The Compass (and Agent) this invocation is about, set by a `compass` command before it sends.
1474
+ * A stale version or a missing revision is answered with the command to run next, and that
1475
+ * command names these. Unset, the hint carries placeholders. See `lib/compass-refusals.ts`.
1476
+ */
1477
+ compassRef;
1470
1478
  handleApiError(err, { subject } = {}) {
1471
1479
  // An error this command already raised for the user is not an API failure
1472
1480
  // to translate. `this.error()` throws a CLIError carrying `oclif.exit`, and
@@ -1579,6 +1587,36 @@ class BaseCommand extends core_1.Command {
1579
1587
  details: envelopeDetails(body),
1580
1588
  });
1581
1589
  }
1590
+ else if ((bodyCode === COMMITMENT_ACTIVATION_BLOCKED ||
1591
+ stringProp(body, 'error') === COMMITMENT_ACTIVATION_BLOCKED) &&
1592
+ commitmentActivationHint(body)) {
1593
+ // The route puts the code in `error`, in upper case, which `refusalCodeOf` does not read
1594
+ // as a code — so this refusal arrived as `HTTP_409` with the gate's QUESTION as its only
1595
+ // explanation. Name the code, and say what to do (OSK-21126).
1596
+ this.failWithCliError({
1597
+ message: say(`${bodyMessage || e.message}\n → ${commitmentActivationHint(body)}`),
1598
+ code: COMMITMENT_ACTIVATION_BLOCKED,
1599
+ status: e.status,
1600
+ exit: 1,
1601
+ retryable: false,
1602
+ details: envelopeDetails(body),
1603
+ });
1604
+ }
1605
+ else if ((e.status === 409 || e.status === 404) && (0, compass_refusals_1.compassRefusalCode)(body)) {
1606
+ // The Compass routes sent the code in upper-case `error`, which `refusalCodeOf` does not
1607
+ // read as a code, so a stale writer saw `HTTP_409` and a mistyped version `NOT_FOUND`,
1608
+ // both without the versions that say what to do (OSK-21162, OSK-21152). Read these two
1609
+ // codes by name here rather than widening `refusalCodeOf` for every route.
1610
+ const compassCode = (0, compass_refusals_1.compassRefusalCode)(body);
1611
+ this.failWithCliError({
1612
+ message: say((0, compass_refusals_1.describeCompassRefusal)(compassCode, body, this.compassRef ?? {}, this.config.bin)),
1613
+ code: compassCode,
1614
+ status: e.status,
1615
+ exit: compassCode === compass_refusals_1.COMPASS_REVISION_NOT_FOUND ? 4 : 1,
1616
+ retryable: false,
1617
+ details: envelopeDetails(body),
1618
+ });
1619
+ }
1582
1620
  else if (bodyCode === 'ASSIGNEE_NOT_IN_SPACE') {
1583
1621
  // Code-specific enrichment takes precedence over the status heuristic:
1584
1622
  // the server rejects a non-member assignee by id with no next step, so
@@ -2254,6 +2292,40 @@ function apiRefusal(err) {
2254
2292
  ...(body ? { body } : {}),
2255
2293
  };
2256
2294
  }
2295
+ /** The typed code the server refuses a Commitment activation with. */
2296
+ const COMMITMENT_ACTIVATION_BLOCKED = 'COMMITMENT_ACTIVATION_BLOCKED';
2297
+ /**
2298
+ * The remedy a refused Commitment activation carries.
2299
+ *
2300
+ * The refusal's message names the blocking Preflight gate by its QUESTION and a reason token
2301
+ * ("Can the runtime enforce this Run? (runtime_commitment_contract_unavailable)"). The gate's own
2302
+ * `hint` — the sentence that says what to run — travels beside it at
2303
+ * `diagnostic.firstBlocker.detail`, and the web client already shows it
2304
+ * (`activationFailureMessage`). The CLI dropped it, so `commitments resume` and
2305
+ * `commitments create --activate` told a person to "fix the gate the reason names" while
2306
+ * `commitments preflight` knew the exact command (OSK-21126).
2307
+ *
2308
+ * Undefined when the gate offered none, so a caller keeps its generic wording.
2309
+ *
2310
+ * Exported for tests.
2311
+ */
2312
+ function commitmentActivationHint(body) {
2313
+ const diagnostic = body?.diagnostic;
2314
+ if (!diagnostic || typeof diagnostic !== 'object')
2315
+ return undefined;
2316
+ const blocker = diagnostic.firstBlocker;
2317
+ if (!blocker || typeof blocker !== 'object')
2318
+ return undefined;
2319
+ const detail = blocker.detail;
2320
+ if (!detail || typeof detail !== 'object')
2321
+ return undefined;
2322
+ const { hint, note } = detail;
2323
+ for (const candidate of [hint, note]) {
2324
+ if (typeof candidate === 'string' && candidate.trim())
2325
+ return candidate.trim();
2326
+ }
2327
+ return undefined;
2328
+ }
2257
2329
  /**
2258
2330
  * Prefix a failure message with the resource it is about.
2259
2331
  *
@@ -336,6 +336,13 @@ function noStreamJsonPayload(input) {
336
336
  // The run first failed as handed over and the machine's reply was then
337
337
  // repaired onto the record (OSK-13543).
338
338
  ...(result.repaired ? { repaired: true } : {}),
339
+ // Tool calls refused for want of `--approve-tools`: the turn completed, and these did not run.
340
+ ...(result.permissionDenials?.length
341
+ ? {
342
+ permissionDenials: result.permissionDenials,
343
+ permissionDenialHint: 'Re-run with --approve-tools to allow these tool calls.',
344
+ }
345
+ : {}),
339
346
  response: result.text,
340
347
  // The model a local harness reported running (OSK-12111). Absent on a
341
348
  // cloud turn and on a harness that reports none.
@@ -8,8 +8,8 @@ import { BaseCommand } from '../../../base-command';
8
8
  * Wraps `GET /api/agents/:agent_id/goals` (data-provider `listAgentGoals`),
9
9
  * which returns goals where the agent is owner, DRI, or a contributor, each
10
10
  * enriched with `_agentGoalRole` (which of the three) and `_completionPercent`.
11
- * The endpoint sorts by most-recently-updated and caps at 100 goals; there is
12
- * no pagination cursor, so neither is there a `--cursor` flag here.
11
+ * The endpoint sorts by most-recently-updated and supports cursor pagination.
12
+ * This command currently shows its first page of up to 100 goals.
13
13
  */
14
14
  export default class AgentsGoalsList extends BaseCommand {
15
15
  static description: string;
@@ -14,8 +14,8 @@ const format_1 = require("../../../lib/format");
14
14
  * Wraps `GET /api/agents/:agent_id/goals` (data-provider `listAgentGoals`),
15
15
  * which returns goals where the agent is owner, DRI, or a contributor, each
16
16
  * enriched with `_agentGoalRole` (which of the three) and `_completionPercent`.
17
- * The endpoint sorts by most-recently-updated and caps at 100 goals; there is
18
- * no pagination cursor, so neither is there a `--cursor` flag here.
17
+ * The endpoint sorts by most-recently-updated and supports cursor pagination.
18
+ * This command currently shows its first page of up to 100 goals.
19
19
  */
20
20
  class AgentsGoalsList extends base_command_1.BaseCommand {
21
21
  static description = 'List the goals an agent owns, is DRI for, or contributes to';
@@ -42,8 +42,8 @@ class CommitmentsActionHistory extends base_command_1.BaseCommand {
42
42
  this.handleApiError(err);
43
43
  }
44
44
  if (flags.json) {
45
+ // One JSON document and nothing else: has_more / next_cursor are in it.
45
46
  this.log(JSON.stringify(response, null, 2));
46
- (0, commitments_1.renderListPageHint)(response, (line) => this.log(line));
47
47
  return;
48
48
  }
49
49
  (0, commitment_ledgers_1.renderActionHistory)(response, (line) => this.log(line));
@@ -31,11 +31,11 @@ class CommitmentsCases extends base_command_1.BaseCommand {
31
31
  this.handleApiError(error);
32
32
  }
33
33
  if (flags.json) {
34
+ // One JSON document and nothing else: has_more / next_cursor are in it.
34
35
  this.log(JSON.stringify(result, null, 2));
36
+ return;
35
37
  }
36
- else {
37
- (0, commitments_1.renderCommitmentCases)(result, (line) => this.log(line));
38
- }
38
+ (0, commitments_1.renderCommitmentCases)(result, (line) => this.log(line));
39
39
  (0, commitments_1.renderListPageHint)(result, (line) => this.log(line));
40
40
  }
41
41
  }
@@ -1,6 +1,14 @@
1
1
  import { BaseCommand } from '../../base-command';
2
- /** Render runtime/Always-On blockers returned alongside an accepted Check. */
3
- export declare function manualCheckBlockerLines(result: Record<string, unknown>): string[];
2
+ /** Render runtime/Always-On blockers returned alongside a Check (accepted, or skipped). */
3
+ export declare function manualCheckBlockerLines(result: Record<string, unknown>, heading?: string): string[];
4
+ /**
5
+ * Why a Check was not queued, in words, and what to do. The server answers with a reason code
6
+ * (`commitment_draft`, `commitment_paused`, ...) and the CLI used to print only that, so "Check
7
+ * skipped / Reason: commitment_draft" gave no hint that the Commitment simply was not turned on
8
+ * (dogfood df-261031-commitment-authoring, F11). The code stays in parentheses so it can be
9
+ * searched for and quoted. `null` for a reason this does not know: the caller prints it as it came.
10
+ */
11
+ export declare function describeNotQueuedReason(reason: unknown, id: string, bin?: string): string | null;
4
12
  /**
5
13
  * `skrr commitments check-now <id>` — run one control-loop iteration immediately.
6
14
  *
@@ -1,18 +1,20 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.manualCheckBlockerLines = manualCheckBlockerLines;
4
+ exports.describeNotQueuedReason = describeNotQueuedReason;
4
5
  const core_1 = require("@oclif/core");
5
6
  const base_command_1 = require("../../base-command");
6
7
  const web_url_1 = require("../../lib/web-url");
7
8
  const commitments_1 = require("../../lib/commitments");
8
- /** Render runtime/Always-On blockers returned alongside an accepted Check. */
9
- function manualCheckBlockerLines(result) {
9
+ const ACCEPTED_CHECK_BLOCKER_HEADING = 'Warning: this check can record state, but autonomous agent work is blocked:';
10
+ /** Render runtime/Always-On blockers returned alongside a Check (accepted, or skipped). */
11
+ function manualCheckBlockerLines(result, heading = ACCEPTED_CHECK_BLOCKER_HEADING) {
10
12
  const blockers = Array.isArray(result.executionBlockers)
11
13
  ? result.executionBlockers
12
14
  : [];
13
15
  if (!blockers.length)
14
16
  return [];
15
- const lines = ['Warning: this check can record state, but autonomous agent work is blocked:'];
17
+ const lines = [heading];
16
18
  for (const blocker of blockers) {
17
19
  const label = typeof blocker.label === 'string' && blocker.label ? blocker.label : 'Execution';
18
20
  const reason = typeof blocker.reason === 'string' && blocker.reason ? ` — ${blocker.reason}` : '';
@@ -23,6 +25,40 @@ function manualCheckBlockerLines(result) {
23
25
  }
24
26
  return lines;
25
27
  }
28
+ /**
29
+ * Why a Check was not queued, in words, and what to do. The server answers with a reason code
30
+ * (`commitment_draft`, `commitment_paused`, ...) and the CLI used to print only that, so "Check
31
+ * skipped / Reason: commitment_draft" gave no hint that the Commitment simply was not turned on
32
+ * (dogfood df-261031-commitment-authoring, F11). The code stays in parentheses so it can be
33
+ * searched for and quoted. `null` for a reason this does not know: the caller prints it as it came.
34
+ */
35
+ function describeNotQueuedReason(reason, id, bin = 'skrr') {
36
+ if (typeof reason !== 'string' || !reason)
37
+ return null;
38
+ const resume = `Turn it on with \`${bin} commitments resume ${id}\`.`;
39
+ const sentence = (text) => `${text} (${reason})`;
40
+ switch (reason) {
41
+ case 'commitment_draft':
42
+ return sentence(`Nothing was queued: this Commitment is a draft, so it never runs. ${resume}`);
43
+ case 'commitment_paused':
44
+ return sentence(`Nothing was queued: this Commitment is paused. ${resume}`);
45
+ case 'commitment_archived':
46
+ case 'commitment_completed':
47
+ case 'commitment_cancelled':
48
+ return sentence(`Nothing was queued: this Commitment is ${reason.slice('commitment_'.length)}, so it no longer runs.`);
49
+ case 'commitment_deleted':
50
+ return sentence('Nothing was queued: this Commitment was deleted.');
51
+ case 'commitment_check_not_reserved':
52
+ return sentence('Nothing was queued: the platform did not reserve a new check for it just now. Try again shortly.');
53
+ case 'reservation_exists':
54
+ // Two requests at once: one reserves the check, the other meets that reservation.
55
+ return sentence(`Nothing new was queued: a check for this Commitment is already queued or running. See it with \`${bin} commitments checks ${id}\`.`);
56
+ case 'queue_unavailable':
57
+ return sentence('Nothing was queued: the check queue is unavailable right now. Try again shortly.');
58
+ default:
59
+ return null;
60
+ }
61
+ }
26
62
  /**
27
63
  * `skrr commitments check-now <id>` — run one control-loop iteration immediately.
28
64
  *
@@ -60,6 +96,20 @@ class CommitmentsCheckNow extends base_command_1.BaseCommand {
60
96
  const status = String(result.status ?? 'unknown');
61
97
  const checkId = result.checkId ? String(result.checkId) : null;
62
98
  this.log(`Check ${status}${checkId ? ` — ${checkId}` : ''}`);
99
+ if (status !== 'queued') {
100
+ const sentence = describeNotQueuedReason(result.reason, args.id, this.config.bin);
101
+ if (sentence)
102
+ this.log(sentence);
103
+ else if (result.reason)
104
+ this.log(`Reason: ${String(result.reason)}`);
105
+ // What would stop it from running once it is on, so the fix is not discovered one try at a time.
106
+ const blockerLines = manualCheckBlockerLines(result, 'When it is on, these will also keep agent work from running:');
107
+ if (blockerLines.length)
108
+ this.log('');
109
+ for (const line of blockerLines)
110
+ this.log(line);
111
+ return;
112
+ }
63
113
  if (result.reason)
64
114
  this.log(`Reason: ${String(result.reason)}`);
65
115
  if (status === 'queued') {
@@ -20,19 +20,10 @@ export declare function createCadenceFromFlags({ eventDriven, cadence: cadenceIn
20
20
  cadenceMode?: string;
21
21
  timezone?: string;
22
22
  }): Record<string, unknown> | undefined;
23
- /**
24
- * Creation preserves the policy its author wrote, so the mode that comes back
25
- * is normally the mode that was asked for. When it is not — normalization
26
- * rejected the value, or a governance layer sits lower — the difference has to
27
- * be said out loud at the CLI boundary; otherwise a successful create response
28
- * reads like the requested autonomy is live when something else is running.
29
- */
30
- /**
31
- * A refused `--activate` leaves a draft, and the draft's id is the answer the
32
- * caller needs — so the refusal is reported on the result, never thrown over
33
- * it. `{code, reason, fix}` mirrors the MCP surface's refusal; `guide` points
34
- * at the section that explains the code when one does (resolved offline).
35
- */
23
+ export type ActivationBlocker = {
24
+ gate: string;
25
+ reason: string;
26
+ };
36
27
  export declare function commitmentActivationRefusal(err: unknown, { id, bin }: {
37
28
  id: string;
38
29
  bin: string;
@@ -41,6 +32,7 @@ export declare function commitmentActivationRefusal(err: unknown, { id, bin }: {
41
32
  reason: string;
42
33
  fix: string;
43
34
  guide?: AgentGuidePointer;
35
+ blockers?: ActivationBlocker[];
44
36
  };
45
37
  /**
46
38
  * `skrr commitments create` — author a durable commitment contract.
@@ -89,7 +81,7 @@ export default class CommitmentsCreate extends BaseCommand {
89
81
  repo: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
90
82
  base: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
91
83
  isolation: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
92
- 'max-nudges': import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
84
+ 'max-nudges': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
93
85
  'max-concurrent-runs': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
94
86
  'concurrency-reason': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
95
87
  };
@@ -10,6 +10,7 @@ const base_command_1 = require("../../base-command");
10
10
  const guide_pointer_1 = require("../../lib/guide-pointer");
11
11
  const web_url_1 = require("../../lib/web-url");
12
12
  const prompt_1 = require("../../lib/prompt");
13
+ const commitment_attention_copy_1 = require("../../lib/commitment-attention-copy");
13
14
  const triggers_1 = require("../../lib/triggers");
14
15
  const commitment_product_1 = require("../../lib/commitment-product");
15
16
  const commitments_1 = require("../../lib/commitments");
@@ -153,8 +154,27 @@ function createCadenceFromFlags({ eventDriven, cadence: cadenceInput, cadenceMod
153
154
  * it. `{code, reason, fix}` mirrors the MCP surface's refusal; `guide` points
154
155
  * at the section that explains the code when one does (resolved offline).
155
156
  */
157
+ /** Everything that blocks an activation, when the server listed more than the first gate. */
158
+ function activationBlockers(body) {
159
+ const diagnostic = body?.diagnostic;
160
+ if (!Array.isArray(diagnostic?.blockers))
161
+ return [];
162
+ const blockers = [];
163
+ for (const item of diagnostic.blockers) {
164
+ const gate = item?.gate;
165
+ const reason = item?.reason;
166
+ if (typeof gate === 'string' && gate && typeof reason === 'string' && reason) {
167
+ blockers.push({ gate, reason });
168
+ }
169
+ }
170
+ return blockers;
171
+ }
156
172
  function commitmentActivationRefusal(err, { id, bin }) {
157
173
  const refusal = (0, base_command_1.apiRefusal)(err);
174
+ // `resume` answers with every blocking gate, but only the first is in the message. Fixing it
175
+ // revealed the next one on the following try (Always-On, then the runtime), so the refusal says
176
+ // all of them when there is more than one (dogfood df-261031-commitment-authoring, F12).
177
+ const blockers = activationBlockers(refusal.body);
158
178
  const errorField = refusal.body?.error;
159
179
  const code = refusal.code ||
160
180
  (typeof errorField === 'string' && /^[A-Za-z][A-Za-z0-9_]*$/.test(errorField)
@@ -162,11 +182,17 @@ function commitmentActivationRefusal(err, { id, bin }) {
162
182
  : undefined) ||
163
183
  'COMMITMENT_ACTIVATION_REFUSED';
164
184
  const guide = (0, guide_pointer_1.agentGuidePointerFor)(code);
185
+ // The blocking gate's own remedy beats the generic "fix the gate the reason names": the reason is
186
+ // a token, and the sentence that says what to run is the hint beside it (OSK-21126).
187
+ const hint = (0, base_command_1.commitmentActivationHint)(refusal.body);
165
188
  return {
166
189
  code,
167
190
  reason: refusal.message || (err instanceof Error ? err.message : 'Activation was refused.'),
168
- fix: (0, data_provider_1.describeCommitmentActivationFix)(code === 'COMMITMENT_ACTIVATION_REFUSED' ? undefined : code, { turnOn: `run \`${bin} commitments resume ${id}\`` }),
191
+ fix: hint
192
+ ? `${hint} Then run \`${bin} commitments resume ${id}\`.`
193
+ : (0, data_provider_1.describeCommitmentActivationFix)(code === 'COMMITMENT_ACTIVATION_REFUSED' ? undefined : code, { turnOn: `run \`${bin} commitments resume ${id}\`` }),
169
194
  ...(guide ? { guide } : {}),
195
+ ...(blockers.length > 1 ? { blockers } : {}),
170
196
  };
171
197
  }
172
198
  /**
@@ -210,6 +236,7 @@ cat > commitment.json <<'JSON'
210
236
  }],
211
237
  "policy": {
212
238
  "maxUserNudgesPerDay": 0,
239
+ "quietHours": { "enabled": true, "start": "22:00", "end": "07:00", "timezone": "Asia/Shanghai" },
213
240
  "executionBudget": { "maxRunsPerDay": 0, "maxObservationRunsPerDay": 0, "maxCostUsdPerDay": 0 }
214
241
  }
215
242
  }
@@ -217,11 +244,25 @@ JSON
217
244
  <%= config.bin %> commitments preflight --from-json commitment.json
218
245
  <%= config.bin %> commitments create --from-json commitment.json --json`,
219
246
  '<%= config.bin %> commitments create --agent <agent-id> --title "Keep CI healthy" --outcome "Failed checks are repaired" --watch github --github-event check_run_failed --event-driven --delivery draft-pr --repo owner/repo --base main --status draft',
247
+ `# Govern a Commitment with a Compass. There is no flag: "compassId" goes in the --from-json body.
248
+ # It must be an active Compass of the same Agent (a draft may name an archived one).
249
+ # The binding cannot be changed later: bind a successor Commitment to another Compass instead.
250
+ cat > commitment.json <<'JSON'
251
+ {
252
+ "agentId": "<agent-id>",
253
+ "compassId": "<compass-id>",
254
+ "title": "Keep p99 under 300 ms",
255
+ "status": "draft",
256
+ "target": { "outcome": "p99 API latency stays under 300 ms" }
257
+ }
258
+ JSON
259
+ <%= config.bin %> commitments create --from-json commitment.json --json
260
+ <%= config.bin %> compass commitments <compass-id> --agent <agent-id> # lists it once it is bound`,
220
261
  ];
221
262
  static flags = {
222
263
  json: core_1.Flags.boolean({ description: 'Output the created commitment as JSON' }),
223
264
  'from-json': core_1.Flags.string({
224
- description: 'JSON file (or "-" for stdin) for fields not exposed as flags. See examples below for daily times, named agent_run keys and zero budgets.',
265
+ description: `JSON file (or "-" for stdin) for fields not exposed as flags. See examples below for daily times, named agent_run keys, zero budgets and "compassId" (binds a Compass; fixed at creation). ${commitment_attention_copy_1.QUIET_HOURS_HELP} ${commitment_attention_copy_1.EVENT_SOURCES_HELP}`,
225
266
  }),
226
267
  agent: core_1.Flags.string({ description: 'Agent that owns and watches this commitment (required)' }),
227
268
  kind: core_1.Flags.string({
@@ -321,7 +362,7 @@ JSON
321
362
  options: [...commitments_1.COMMITMENT_EXECUTION_ISOLATION_MODES],
322
363
  hidden: true,
323
364
  }),
324
- 'max-nudges': core_1.Flags.integer({ description: 'Max user nudges per day (0..20)' }),
365
+ 'max-nudges': core_1.Flags.string({ description: commitment_attention_copy_1.MAX_NUDGES_FLAG_HELP }),
325
366
  'max-concurrent-runs': core_1.Flags.string({
326
367
  description: 'Owner concurrency policy: "unlimited" (default) or 1..100. Finite values require --concurrency-reason and cannot be activated by an autonomous Agent session.',
327
368
  }),
@@ -337,7 +378,11 @@ JSON
337
378
  // `createBodySchema` is a z.strictObject, so unwrap a `plan --json`
338
379
  // envelope and drop any key it does not declare (a contract dumped from
339
380
  // `list --json` carries id/version/state/… and would otherwise 400).
340
- payload = (0, commitments_1.normalizeCreateBody)(await (0, prompt_1.readJsonBody)(flags['from-json']));
381
+ const rawBody = await (0, prompt_1.readJsonBody)(flags['from-json']);
382
+ payload = (0, commitments_1.normalizeCreateBody)(rawBody);
383
+ const ignoredWarning = (0, commitments_1.ignoredCreateKeysWarning)((0, commitments_1.ignoredCreateKeys)(rawBody));
384
+ if (ignoredWarning)
385
+ this.warn(ignoredWarning);
341
386
  if (flags.agent)
342
387
  payload.agentId = flags.agent;
343
388
  if (flags.kind)
@@ -430,8 +475,9 @@ JSON
430
475
  (0, commitment_product_1.normalizeTaskReviewPolicy)(declaredDelivery.reviewPolicy);
431
476
  if (flags.isolation)
432
477
  policy.executionIsolation = flags.isolation;
433
- if (flags['max-nudges'] !== undefined)
434
- policy.maxUserNudgesPerDay = flags['max-nudges'];
478
+ const maxNudges = (0, commitment_attention_copy_1.parseMaxNudgesFlag)(flags['max-nudges']);
479
+ if (maxNudges !== undefined)
480
+ policy.maxUserNudgesPerDay = maxNudges;
435
481
  const concurrencyPatch = (0, commitments_1.commitmentConcurrencyEnvelopePatch)({
436
482
  rawValue: flags['max-concurrent-runs'],
437
483
  reason: flags['concurrency-reason'],
@@ -446,6 +492,9 @@ JSON
446
492
  }
447
493
  if (Object.keys(policy).length)
448
494
  payload.policy = policy;
495
+ const quietHoursProblem = (0, commitment_attention_copy_1.quietHoursInputProblem)(policy);
496
+ if (quietHoursProblem)
497
+ throw new Error(quietHoursProblem);
449
498
  if (payload.createdVia === undefined) {
450
499
  payload.createdVia = process.env.OVERSKY_AUTONOMOUS_SESSION === '1' ? 'agent_cli' : 'cli';
451
500
  }
@@ -533,6 +582,12 @@ JSON
533
582
  if (activation && !activation.activated) {
534
583
  const { refusal } = activation;
535
584
  this.warn(`Activation refused (${refusal.code}): ${refusal.reason}`);
585
+ if (refusal.blockers) {
586
+ this.warn(`All ${refusal.blockers.length} things blocking it (it stays a draft until each is fixed):`);
587
+ for (const blocker of refusal.blockers)
588
+ this.warn(` - ${blocker.gate}: ${blocker.reason}`);
589
+ this.warn(`Details: ${this.config.bin} commitments preflight ${String(commitment?.id)}`);
590
+ }
536
591
  if (refusal.fix)
537
592
  this.warn(`Fix: ${refusal.fix}`);
538
593
  if (refusal.guide)
@@ -86,7 +86,9 @@ function spaceLoopLine(value) {
86
86
  if (status === 'admitted') {
87
87
  detail = loop.principalVersion ? ` (principal v${loop.principalVersion})` : '';
88
88
  }
89
- return `Space loop: ${status}${loop.spaceId ? ` · ${loop.spaceId}` : ''}${detail}`;
89
+ // Said in words first, with the code kept in parentheses for anyone who searches by it.
90
+ const said = status !== 'admitted' && loop.detail ? ` — ${loop.detail}` : '';
91
+ return `Space loop: ${status}${loop.spaceId ? ` · ${loop.spaceId}` : ''}${said}${detail}`;
90
92
  }
91
93
  class CommitmentsDoctor extends base_command_1.BaseCommand {
92
94
  static description = 'Summarize commitment readiness and the next operator action';
@@ -1,5 +1,15 @@
1
1
  import { BaseCommand } from '../../base-command';
2
- export declare function effectivePolicyLines(policy: Record<string, any>): string[];
2
+ /**
3
+ * `2026-11-02 00:00` for an instant, in a time zone. Built from parts, never from a locale: `02/11/2026`
4
+ * is the 2nd of November to one reader and the 11th of February to the next, and the budget reset is
5
+ * exactly the date a person reads this line for.
6
+ */
7
+ export declare function isoWallClock(instant: unknown, timeZone?: string): string | undefined;
8
+ /**
9
+ * `stored` is the Commitment's own policy, read beside the effective one: the no-reply back-off
10
+ * is not part of the effective-policy answer, so without it that line is left out, not guessed.
11
+ */
12
+ export declare function effectivePolicyLines(policy: Record<string, any>, stored?: Record<string, any> | null): string[];
3
13
  /** Show the policy actually governing a Commitment without changing it. */
4
14
  export default class CommitmentsEffectivePolicy extends BaseCommand {
5
15
  static description: string;