@skrr-ai/cli 0.1.19 → 0.1.21

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 (53) hide show
  1. package/dist/base-command.d.ts +12 -9
  2. package/dist/base-command.js +27 -26
  3. package/dist/commands/commitments/action-proposals/decide.js +22 -5
  4. package/dist/commands/commitments/action-proposals/execute.js +7 -3
  5. package/dist/commands/commitments/action-proposals/propose.d.ts +32 -0
  6. package/dist/commands/commitments/action-proposals/propose.js +111 -0
  7. package/dist/commands/commitments/action-proposals.js +6 -4
  8. package/dist/commands/commitments/create.d.ts +2 -0
  9. package/dist/commands/commitments/create.js +42 -2
  10. package/dist/commands/commitments/doctor.js +11 -0
  11. package/dist/commands/commitments/update.d.ts +2 -0
  12. package/dist/commands/commitments/update.js +40 -5
  13. package/dist/commands/store/browse.d.ts +14 -0
  14. package/dist/commands/store/browse.js +71 -0
  15. package/dist/commands/store/install.d.ts +16 -0
  16. package/dist/commands/store/install.js +54 -0
  17. package/dist/commands/store/releases.d.ts +19 -0
  18. package/dist/commands/store/releases.js +60 -0
  19. package/dist/commands/store/update.d.ts +15 -0
  20. package/dist/commands/store/update.js +69 -0
  21. package/dist/commands/store/updates.d.ts +24 -0
  22. package/dist/commands/store/updates.js +113 -0
  23. package/dist/commands/subscriptions/cancel.d.ts +14 -0
  24. package/dist/commands/subscriptions/cancel.js +47 -0
  25. package/dist/commands/subscriptions/health.d.ts +24 -0
  26. package/dist/commands/subscriptions/health.js +62 -0
  27. package/dist/commands/subscriptions/list.d.ts +9 -0
  28. package/dist/commands/subscriptions/list.js +52 -0
  29. package/dist/commands/subscriptions/status.d.ts +12 -0
  30. package/dist/commands/subscriptions/status.js +49 -0
  31. package/dist/commands/subscriptions/subscribe.d.ts +15 -0
  32. package/dist/commands/subscriptions/subscribe.js +59 -0
  33. package/dist/commands/tasks/complete.d.ts +10 -4
  34. package/dist/commands/tasks/complete.js +7 -5
  35. package/dist/commands/tasks/self-schedule.d.ts +30 -0
  36. package/dist/commands/tasks/self-schedule.js +114 -0
  37. package/dist/commands/triggers/disable.js +3 -1
  38. package/dist/commands/triggers/enable.js +3 -1
  39. package/dist/commands/triggers/rotate-secret.js +5 -0
  40. package/dist/commands/triggers/show.js +3 -1
  41. package/dist/lib/commitment-product.js +9 -0
  42. package/dist/lib/commitments.d.ts +21 -1
  43. package/dist/lib/commitments.js +62 -1
  44. package/dist/lib/node-adapter.js +4 -0
  45. package/dist/lib/task-extras.d.ts +18 -0
  46. package/dist/lib/task-extras.js +19 -1
  47. package/dist/lib/triggers.d.ts +21 -2
  48. package/dist/lib/triggers.js +48 -1
  49. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/refreshClassification.js +9 -2
  50. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/refreshClassification.js +9 -2
  51. package/dist/node_modules/@skrr-ai/data-provider/index.js +4073 -3904
  52. package/oclif.manifest.json +18424 -17362
  53. package/package.json +1 -1
@@ -0,0 +1,12 @@
1
+ import { BaseCommand } from '../../base-command';
2
+ export default class SubscriptionsStatus extends BaseCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ static args: {
6
+ agentId: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
7
+ };
8
+ static flags: {
9
+ json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
10
+ };
11
+ run(): Promise<void>;
12
+ }
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const core_1 = require("@oclif/core");
4
+ const data_provider_1 = require("@skrr-ai/data-provider");
5
+ const base_command_1 = require("../../base-command");
6
+ class SubscriptionsStatus extends base_command_1.BaseCommand {
7
+ static description = 'Show whether a service subscription is live, and until when';
8
+ static examples = [
9
+ '<%= config.bin %> subscriptions status agent_abc123',
10
+ '<%= config.bin %> subscriptions status agent_abc123 --json',
11
+ ];
12
+ static args = {
13
+ agentId: core_1.Args.string({ description: 'The service to check', required: true }),
14
+ };
15
+ static flags = {
16
+ json: core_1.Flags.boolean({ description: 'Output as JSON' }),
17
+ };
18
+ async run() {
19
+ this.requireAuth();
20
+ const { args, flags } = await this.parse(SubscriptionsStatus);
21
+ let response;
22
+ try {
23
+ response = await data_provider_1.dataService.getStoreAgentSubscriptionStatus(args.agentId);
24
+ }
25
+ catch (err) {
26
+ this.handleApiError(err);
27
+ }
28
+ if (flags.json) {
29
+ this.log(JSON.stringify(response, null, 2));
30
+ return;
31
+ }
32
+ if (!response?.subscribed) {
33
+ // The two reasons want different next actions, so they are printed
34
+ // differently rather than collapsed into "not subscribed".
35
+ this.log(response?.reason === 'expired'
36
+ ? 'Not live — the subscription expired. Renewing resumes it.'
37
+ : 'Not subscribed.');
38
+ return;
39
+ }
40
+ this.log(`Live. Entitlement ${response.entitlementId} (v${response.version})`);
41
+ this.log(`Your instance: ${response.instanceAgentId ?? '-'}`);
42
+ this.log(`Compute paid by: ${response.billingMode}`);
43
+ this.log(`Expires: ${response.expiresAt ? String(response.expiresAt) : 'no end date'}`);
44
+ if (response.commitmentId) {
45
+ this.log(`Obligation: ${response.commitmentId}`);
46
+ }
47
+ }
48
+ }
49
+ exports.default = SubscriptionsStatus;
@@ -0,0 +1,15 @@
1
+ import { BaseCommand } from '../../base-command';
2
+ export default class SubscriptionsSubscribe extends BaseCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ static args: {
6
+ agentId: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
7
+ };
8
+ static flags: {
9
+ json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
10
+ 'billing-mode': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
11
+ 'expires-at': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
12
+ space: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
13
+ };
14
+ run(): Promise<void>;
15
+ }
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const core_1 = require("@oclif/core");
4
+ const data_provider_1 = require("@skrr-ai/data-provider");
5
+ const base_command_1 = require("../../base-command");
6
+ class SubscriptionsSubscribe extends base_command_1.BaseCommand {
7
+ static description = 'Subscribe to a published service — installs your own managed instance and opens the obligation it is delivered through';
8
+ static examples = [
9
+ '<%= config.bin %> subscriptions subscribe agent_abc123',
10
+ '<%= config.bin %> subscriptions subscribe agent_abc123 --billing-mode per-user',
11
+ '<%= config.bin %> subscriptions subscribe agent_abc123 --space space_xyz --json',
12
+ ];
13
+ static args = {
14
+ agentId: core_1.Args.string({ description: 'The published service to subscribe to', required: true }),
15
+ };
16
+ static flags = {
17
+ json: core_1.Flags.boolean({ description: 'Output as JSON' }),
18
+ 'billing-mode': core_1.Flags.string({
19
+ description: 'Who pays for the inference this service consumes',
20
+ options: ['owner', 'per-user', 'shared'],
21
+ }),
22
+ 'expires-at': core_1.Flags.string({ description: 'Paid-through date (ISO 8601)' }),
23
+ space: core_1.Flags.string({ description: 'Space the service should work in' }),
24
+ };
25
+ async run() {
26
+ this.requireAuth();
27
+ const { args, flags } = await this.parse(SubscriptionsSubscribe);
28
+ let response;
29
+ try {
30
+ response = await data_provider_1.dataService.subscribeToStoreAgent(args.agentId, {
31
+ ...(flags['billing-mode']
32
+ ? { billingMode: flags['billing-mode'] }
33
+ : {}),
34
+ ...(flags['expires-at'] ? { expiresAt: flags['expires-at'] } : {}),
35
+ ...(flags.space ? { spaceId: flags.space } : {}),
36
+ });
37
+ }
38
+ catch (err) {
39
+ this.handleApiError(err);
40
+ }
41
+ if (flags.json) {
42
+ this.log(JSON.stringify(response, null, 2));
43
+ return;
44
+ }
45
+ this.log(`Subscribed. Entitlement ${response?.entitlementId}`);
46
+ this.log(`Your instance: ${response?.instanceAgentId ?? '-'}`);
47
+ this.log(`Compute paid by: ${response?.billingMode}`);
48
+ if (response?.commitmentId) {
49
+ // The commitment is the thing you can hold the service to — surface it,
50
+ // because "is it working" is answered there and nowhere else.
51
+ this.log(`Obligation: ${response.commitmentId}`);
52
+ }
53
+ if (response?.degraded === 'commitment_pending') {
54
+ this.log('');
55
+ this.log('Note: the subscription is live but its obligation has not been created yet. It is resumable and refundable; retry to finish opening it.');
56
+ }
57
+ }
58
+ }
59
+ exports.default = SubscriptionsSubscribe;
@@ -6,12 +6,19 @@ type CompletionStatus = 'completed' | 'failed';
6
6
  type TaskCommentResult = Awaited<ReturnType<typeof dataService.addTaskComment>>;
7
7
  type MovedTaskResult = Awaited<ReturnType<typeof dataService.moveTask>>;
8
8
  /** Wire shape of POST /api/tasks/:taskId/complete (OSK-1969). */
9
+ /** One thing a completion did not have. Never a refusal — `done` has none. */
10
+ export interface CompletionAdvisory {
11
+ code: string;
12
+ message: string;
13
+ }
9
14
  export interface CompleteEndpointResponse {
10
15
  task: MovedTaskResult | null;
11
16
  comment: TaskCommentResult | null;
12
17
  commentFailed?: boolean;
13
- deliveryHeld?: boolean;
14
- deliveryReason?: string;
18
+ /** What the completion was missing. `done` has no preconditions, so every
19
+ * condition that used to refuse one now travels with it instead. The task
20
+ * reached the requested terminal state regardless. */
21
+ advisories?: CompletionAdvisory[];
15
22
  }
16
23
  export interface CompleteTransport {
17
24
  /** POST /api/tasks/:taskId/complete — atomic move + TASK_OUTPUT comment. */
@@ -35,8 +42,7 @@ export interface DurableCompletionResult {
35
42
  movedTask: MovedTaskResult | null;
36
43
  moved: boolean;
37
44
  commentFailed: boolean;
38
- deliveryHeld?: boolean;
39
- deliveryReason?: string;
45
+ advisories?: CompletionAdvisory[];
40
46
  /** true when the atomic /complete endpoint handled the finalization. */
41
47
  usedAtomicEndpoint: boolean;
42
48
  }
@@ -194,8 +194,7 @@ async function completeTaskDurably(opts) {
194
194
  movedTask: response.task ?? null,
195
195
  moved: true,
196
196
  commentFailed: response.commentFailed === true,
197
- ...(response.deliveryHeld ? { deliveryHeld: true } : {}),
198
- ...(response.deliveryReason ? { deliveryReason: response.deliveryReason } : {}),
197
+ ...(response.advisories?.length ? { advisories: response.advisories } : {}),
199
198
  usedAtomicEndpoint: true,
200
199
  };
201
200
  }
@@ -611,7 +610,7 @@ class TasksComplete extends base_command_1.BaseCommand {
611
610
  ...(report ? { report } : {}),
612
611
  body,
613
612
  },
614
- ...(result.deliveryHeld ? { deliveryHeld: true, deliveryReason: result.deliveryReason } : {}),
613
+ ...(result.advisories?.length ? { advisories: result.advisories } : {}),
615
614
  };
616
615
  if (flags.json) {
617
616
  const url = movedTask
@@ -634,8 +633,11 @@ class TasksComplete extends base_command_1.BaseCommand {
634
633
  ? `Task ${completedTaskId} was already ${newStatus}; the outcome was amended.`
635
634
  : `Moved task ${completedTaskId} to ${newStatus}`);
636
635
  }
637
- if (result.deliveryHeld) {
638
- this.warn('Delivery is not verified on the default branch; the task was held at in_review instead of done.');
636
+ for (const advisory of result.advisories ?? []) {
637
+ // One warn per unmet condition. Each already reads as an observation
638
+ // the completion happened — so the CLI relays them rather than
639
+ // re-wording them into something that sounds like a failure.
640
+ this.warn(advisory.message);
639
641
  }
640
642
  if (report) {
641
643
  const url = (0, web_url_1.taskUrlFrom)(this.cliConfig.baseURL, movedTask ?? { id: completedTaskId });
@@ -0,0 +1,30 @@
1
+ import { BaseCommand } from '../../base-command';
2
+ /**
3
+ * `skrr tasks self-schedule` — park a task for a self-initiated return.
4
+ *
5
+ * Distinct from `skrr tasks schedule`, and the distinction is the point.
6
+ * `schedule` writes the AUTHOR's calendar (`scheduledFor` + `recurrence`), the
7
+ * thing the scheduler consumes and re-arms and every calendar surface renders.
8
+ * This writes a separate `self_scheduled` trigger row, so an agent working the
9
+ * task can say "come back in two hours" without editing the human's Monday-9am
10
+ * recurrence to mean something the human never asked for.
11
+ *
12
+ * Both can be set at once. Neither erases the other.
13
+ */
14
+ export default class TasksSelfSchedule extends BaseCommand {
15
+ static description: string;
16
+ static examples: string[];
17
+ static args: {
18
+ id: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
19
+ };
20
+ static flags: {
21
+ json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
22
+ at: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
23
+ in: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
24
+ note: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
25
+ 'max-per-day': import("@oclif/core/lib/interfaces").OptionFlag<number | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
26
+ show: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
27
+ clear: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
28
+ };
29
+ run(): Promise<void>;
30
+ }
@@ -0,0 +1,114 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const core_1 = require("@oclif/core");
4
+ const base_command_1 = require("../../base-command");
5
+ const task_extras_1 = require("../../lib/task-extras");
6
+ /**
7
+ * `skrr tasks self-schedule` — park a task for a self-initiated return.
8
+ *
9
+ * Distinct from `skrr tasks schedule`, and the distinction is the point.
10
+ * `schedule` writes the AUTHOR's calendar (`scheduledFor` + `recurrence`), the
11
+ * thing the scheduler consumes and re-arms and every calendar surface renders.
12
+ * This writes a separate `self_scheduled` trigger row, so an agent working the
13
+ * task can say "come back in two hours" without editing the human's Monday-9am
14
+ * recurrence to mean something the human never asked for.
15
+ *
16
+ * Both can be set at once. Neither erases the other.
17
+ */
18
+ class TasksSelfSchedule extends base_command_1.BaseCommand {
19
+ static description = "Park a task for a self-initiated return — separate from the author's schedule";
20
+ static examples = [
21
+ '<%= config.bin %> tasks self-schedule <task-id> --in 2h --note "check whether the deploy settled"',
22
+ '<%= config.bin %> tasks self-schedule <task-id> --at 2026-09-09T14:00:00Z',
23
+ '<%= config.bin %> tasks self-schedule <task-id> --show',
24
+ '<%= config.bin %> tasks self-schedule <task-id> --clear',
25
+ ];
26
+ static args = {
27
+ id: core_1.Args.string({ description: 'Task ID', required: true, ignoreStdin: true }),
28
+ };
29
+ static flags = {
30
+ json: core_1.Flags.boolean({ description: 'Output as JSON' }),
31
+ at: core_1.Flags.string({ description: 'Absolute return time (ISO-8601 UTC)' }),
32
+ in: core_1.Flags.string({ description: 'Relative return time such as 90m, 2h, 3d' }),
33
+ note: core_1.Flags.string({
34
+ // Was "handed to the agent as the return prompt", which is not true:
35
+ // `task_execute` builds the run's prompt from the Task, so this string is
36
+ // recorded and never read by the run. See `TaskScheduler/selfSchedule.js`.
37
+ description: 'Why you are coming back — recorded on the return for you to read back',
38
+ }),
39
+ 'max-per-day': core_1.Flags.integer({
40
+ description: 'Cap on self-initiated returns per day (1-24, default 4)',
41
+ }),
42
+ show: core_1.Flags.boolean({ description: 'Print the pending return without changing it' }),
43
+ clear: core_1.Flags.boolean({ description: 'Cancel the pending return' }),
44
+ };
45
+ async run() {
46
+ this.requireAuth();
47
+ const { args, flags } = await this.parse(TasksSelfSchedule);
48
+ if (flags.show && flags.clear) {
49
+ this.error('Pass at most one of --show or --clear.', { exit: 1 });
50
+ }
51
+ let result;
52
+ try {
53
+ if (flags.show) {
54
+ result = await task_extras_1.taskSelfScheduleApi.get(args.id);
55
+ }
56
+ else if (flags.clear) {
57
+ result = await task_extras_1.taskSelfScheduleApi.clear(args.id);
58
+ }
59
+ else {
60
+ const runAt = resolveRunAt(flags.at, flags.in);
61
+ if (!runAt) {
62
+ this.error('Pass --at <ISO-8601>, --in <duration>, or one of --show / --clear.', {
63
+ exit: 1,
64
+ });
65
+ }
66
+ result = await task_extras_1.taskSelfScheduleApi.park(args.id, {
67
+ runAt,
68
+ ...(flags.note ? { note: flags.note } : {}),
69
+ ...(flags['max-per-day'] ? { maxFiresPerDay: flags['max-per-day'] } : {}),
70
+ });
71
+ }
72
+ }
73
+ catch (err) {
74
+ this.handleApiError(err);
75
+ }
76
+ if (flags.json) {
77
+ this.log(JSON.stringify(result ?? {}, null, 2));
78
+ return;
79
+ }
80
+ const runAt = result?.runAt;
81
+ if (!runAt) {
82
+ this.log(`Task ${args.id} has no pending return.`);
83
+ return;
84
+ }
85
+ // `pending` is derived server-side: a return that has already fired keeps
86
+ // its instant on the row, and printing that verbatim would report a return
87
+ // as upcoming an hour after it happened.
88
+ const spent = result?.pending === false;
89
+ this.log(`Task ${args.id} ${spent ? 'last returned at' : 'returns at'} ${runAt}`);
90
+ if (result?.note)
91
+ this.log(`Note: ${result.note}`);
92
+ if (!spent && result?.maxFiresPerDay) {
93
+ this.log(`Cap: ${result.maxFiresPerDay} self-initiated returns/day`);
94
+ }
95
+ }
96
+ }
97
+ exports.default = TasksSelfSchedule;
98
+ /** `90m` / `2h` / `3d`, or an absolute instant. Absolute wins when both are given. */
99
+ function resolveRunAt(at, relative) {
100
+ if (at)
101
+ return at;
102
+ if (!relative)
103
+ return undefined;
104
+ const match = /^(\d+)\s*(m|min|mins|h|hr|hrs|d)$/i.exec(relative.trim());
105
+ if (!match)
106
+ return undefined;
107
+ const value = Number(match[1]);
108
+ const unit = match[2].toLowerCase();
109
+ const MS_PER_UNIT = { m: 60_000, h: 3_600_000, d: 86_400_000 };
110
+ // `min`/`mins`/`hr`/`hrs` collapse onto their first character, which is
111
+ // unambiguous across the three units this accepts.
112
+ const ms = value * (MS_PER_UNIT[unit[0]] ?? MS_PER_UNIT.h);
113
+ return new Date(Date.now() + ms).toISOString();
114
+ }
@@ -26,7 +26,9 @@ class TriggersDisable extends base_command_1.BaseCommand {
26
26
  return;
27
27
  }
28
28
  this.log('Disabled trigger:');
29
- (0, triggers_1.renderTriggerSummary)(trigger, (line) => this.log(line));
29
+ (0, triggers_1.renderTriggerSummary)(trigger, (line) => this.log(line), {
30
+ baseURL: this.cliConfig.baseURL,
31
+ });
30
32
  }
31
33
  }
32
34
  exports.default = TriggersDisable;
@@ -26,7 +26,9 @@ class TriggersEnable extends base_command_1.BaseCommand {
26
26
  return;
27
27
  }
28
28
  this.log('Enabled trigger:');
29
- (0, triggers_1.renderTriggerSummary)(trigger, (line) => this.log(line));
29
+ (0, triggers_1.renderTriggerSummary)(trigger, (line) => this.log(line), {
30
+ baseURL: this.cliConfig.baseURL,
31
+ });
30
32
  }
31
33
  }
32
34
  exports.default = TriggersEnable;
@@ -30,6 +30,11 @@ class TriggersRotateSecret extends base_command_1.BaseCommand {
30
30
  return;
31
31
  }
32
32
  this.log(`Secret: ${String(result.secret ?? '-')}`);
33
+ // The secret alone does not let anyone call in. This command was the natural
34
+ // place to learn the endpoint and printed everything except it, so building
35
+ // the integration meant reading the API source or guessing the path.
36
+ this.log(`URL: POST ${(0, triggers_1.webhookIngestUrl)(this.cliConfig.baseURL, args.id)}`);
37
+ this.log('Sign with header x-trigger-signature; send x-trigger-timestamp alongside it.');
33
38
  this.log(String(result.note ?? 'Save this secret now; it cannot be retrieved later.'));
34
39
  if (result.lastRotatedAt)
35
40
  this.log(`Rotated at: ${String(result.lastRotatedAt)}`);
@@ -29,7 +29,9 @@ class TriggersShow extends base_command_1.BaseCommand {
29
29
  this.log(JSON.stringify(trigger, null, 2));
30
30
  return;
31
31
  }
32
- (0, triggers_1.renderTriggerSummary)(trigger, (line) => this.log(line));
32
+ (0, triggers_1.renderTriggerSummary)(trigger, (line) => this.log(line), {
33
+ baseURL: this.cliConfig.baseURL,
34
+ });
33
35
  }
34
36
  }
35
37
  exports.default = TriggersShow;
@@ -63,6 +63,10 @@ function deliveryForProductMode(mode, delivery) {
63
63
  function renderExecutionReceipt(receipt, log, verbose = false) {
64
64
  log(receipt.summary);
65
65
  log(`Mode: ${productModeLabel(receipt.mode)}`);
66
+ if (receipt.runtime) {
67
+ const identity = receipt.runtime.name || receipt.runtime.id || 'the selected runtime';
68
+ log(`Runtime: ${identity}${receipt.runtime.kind ? ` · ${receipt.runtime.kind}` : ''}${receipt.runtime.provider ? ` · ${receipt.runtime.provider}` : ''}`);
69
+ }
66
70
  for (const phase of receipt.phases) {
67
71
  const mark = { done: '✓', working: '→', waiting: '…', failed: '×', skipped: '–' }[phase.status];
68
72
  log(` ${mark} ${phase.phase[0].toUpperCase() + phase.phase.slice(1)}: ${phase.summary}${phase.at ? ` · ${phase.at}` : ''}`);
@@ -73,6 +77,11 @@ function renderExecutionReceipt(receipt, log, verbose = false) {
73
77
  }
74
78
  if (receipt.nextActor)
75
79
  log(`Next: ${receipt.nextActor.label || receipt.nextActor.kind}${receipt.nextActor.reason ? ` — ${receipt.nextActor.reason}` : ''}`);
80
+ if (receipt.blocker) {
81
+ log(`Blocker: ${receipt.blocker.code}`);
82
+ for (const evidenceRef of receipt.blocker.evidenceRefs || [])
83
+ log(` Evidence: ${evidenceRef}`);
84
+ }
76
85
  if (verbose)
77
86
  log(`Diagnostics:\n${JSON.stringify({ id: receipt.id, revision: receipt.revision, evidence: receipt.evidence, diagnostics: receipt.diagnostics }, null, 2)}`);
78
87
  }
@@ -260,7 +260,20 @@ export declare const commitmentApi: {
260
260
  object: "commitment_action_proposal_list";
261
261
  data: CommitmentActionProposal[];
262
262
  }>;
263
- decideActionProposal: (id: string, proposalId: string, decision: "approve" | "reject") => Promise<Record<string, unknown>>;
263
+ /**
264
+ * Propose a prepared action for a check.
265
+ *
266
+ * The half that was missing. `decide` and `execute` were both reachable, so a
267
+ * headless operator could only ever REACT to proposals the server made — an
268
+ * asymmetry that leaves the domain half-usable in exactly the way the parity
269
+ * rule exists to catch. A locally-running agent that has investigated a
270
+ * commitment and knows what should be done had no way to say so.
271
+ */
272
+ proposeActionProposal: (id: string, body: Record<string, unknown>) => Promise<Record<string, unknown>>;
273
+ decideActionProposal: (id: string, proposalId: string, decision: "approve" | "reject", snapshot?: {
274
+ snapshotDigest?: string;
275
+ bundleVersion?: number;
276
+ }) => Promise<Record<string, unknown>>;
264
277
  executeActionProposal: (id: string, proposalId: string) => Promise<Record<string, unknown>>;
265
278
  preflight: (id: string, nextRunCount?: number) => Promise<CommitmentPreflightReport>;
266
279
  effectivePolicy: (id: string) => Promise<Record<string, unknown>>;
@@ -410,6 +423,13 @@ export declare function readCommitmentAgentDirective({ directive, file, }: {
410
423
  * active or terminal state, but importing it must still create a draft.
411
424
  */
412
425
  export declare function normalizeCreateBody(input: Record<string, unknown>): Record<string, unknown>;
426
+ export declare function parseCommitmentConcurrencyLimit(input: string | undefined): number | null | undefined;
427
+ export declare function commitmentConcurrencyEnvelopePatch({ rawValue, reason, autonomous, }: {
428
+ rawValue?: string;
429
+ reason?: string;
430
+ autonomous?: boolean;
431
+ }): Record<string, unknown> | undefined;
432
+ export declare function describeCommitmentConcurrency(commitment: CommitmentView): string;
413
433
  export declare function parseIntervalToMs(input: string): number;
414
434
  /**
415
435
  * Assemble the `watchedSources` array from CLI flags — one place, so `create`
@@ -6,6 +6,9 @@ exports.authoringAutonomyModeForStored = authoringAutonomyModeForStored;
6
6
  exports.commitmentAutonomyLabel = commitmentAutonomyLabel;
7
7
  exports.readCommitmentAgentDirective = readCommitmentAgentDirective;
8
8
  exports.normalizeCreateBody = normalizeCreateBody;
9
+ exports.parseCommitmentConcurrencyLimit = parseCommitmentConcurrencyLimit;
10
+ exports.commitmentConcurrencyEnvelopePatch = commitmentConcurrencyEnvelopePatch;
11
+ exports.describeCommitmentConcurrency = describeCommitmentConcurrency;
9
12
  exports.parseIntervalToMs = parseIntervalToMs;
10
13
  exports.buildWatchedSources = buildWatchedSources;
11
14
  exports.describeEvidenceHealth = describeEvidenceHealth;
@@ -153,8 +156,20 @@ exports.commitmentApi = {
153
156
  checks: (id, limit) => data_provider_1.request.get((0, triggers_1.withQuery)(`${base(id)}/checks`, { limit })),
154
157
  actionHistory: (id, limit) => data_provider_1.request.get((0, triggers_1.withQuery)(`${base(id)}/action-history`, { limit })),
155
158
  actionProposals: (id, query = {}) => data_provider_1.request.get((0, triggers_1.withQuery)(`${base(id)}/action-proposals`, query)),
156
- decideActionProposal: (id, proposalId, decision) => data_provider_1.request.post(`${base(id)}/action-proposals/${encodeURIComponent(proposalId)}/decision`, {
159
+ /**
160
+ * Propose a prepared action for a check.
161
+ *
162
+ * The half that was missing. `decide` and `execute` were both reachable, so a
163
+ * headless operator could only ever REACT to proposals the server made — an
164
+ * asymmetry that leaves the domain half-usable in exactly the way the parity
165
+ * rule exists to catch. A locally-running agent that has investigated a
166
+ * commitment and knows what should be done had no way to say so.
167
+ */
168
+ proposeActionProposal: (id, body) => data_provider_1.request.post(`${base(id)}/action-proposals`, body),
169
+ decideActionProposal: (id, proposalId, decision, snapshot) => data_provider_1.request.post(`${base(id)}/action-proposals/${encodeURIComponent(proposalId)}/decision`, {
157
170
  decision,
171
+ ...(snapshot?.snapshotDigest ? { snapshotDigest: snapshot.snapshotDigest } : {}),
172
+ ...(snapshot?.bundleVersion ? { bundleVersion: snapshot.bundleVersion } : {}),
158
173
  }),
159
174
  executeActionProposal: (id, proposalId) => data_provider_1.request.post(`${base(id)}/action-proposals/${encodeURIComponent(proposalId)}/execute`, {}),
160
175
  preflight: (id, nextRunCount) => data_provider_1.request.post(`${base(id)}/preflight`, {
@@ -321,6 +336,51 @@ function normalizeCreateBody(input) {
321
336
  }
322
337
  return out;
323
338
  }
339
+ function parseCommitmentConcurrencyLimit(input) {
340
+ if (input === undefined)
341
+ return undefined;
342
+ const raw = String(input).trim().toLowerCase();
343
+ if (['unlimited', 'unbounded', 'none', 'null'].includes(raw))
344
+ return null;
345
+ const value = Number(raw);
346
+ if (!Number.isSafeInteger(value) || value < 1 || value > 100) {
347
+ throw new Error('--max-concurrent-runs must be "unlimited" or an integer from 1 to 100.');
348
+ }
349
+ return value;
350
+ }
351
+ function commitmentConcurrencyEnvelopePatch({ rawValue, reason, autonomous = process.env.OVERSKY_AUTONOMOUS_SESSION === '1', }) {
352
+ const value = parseCommitmentConcurrencyLimit(rawValue);
353
+ const normalizedReason = reason?.trim();
354
+ if (value === undefined) {
355
+ if (normalizedReason) {
356
+ throw new Error('--concurrency-reason requires --max-concurrent-runs.');
357
+ }
358
+ return undefined;
359
+ }
360
+ if (autonomous && value !== null) {
361
+ throw new Error('An autonomous Agent may suggest a finite Commitment concurrency cap, but cannot activate owner policy. Omit the field or use unlimited; an owner can set the cap later.');
362
+ }
363
+ if (value !== null && !normalizedReason) {
364
+ throw new Error('A finite --max-concurrent-runs value requires --concurrency-reason.');
365
+ }
366
+ return {
367
+ maxConcurrentRuns: value,
368
+ ...(normalizedReason ? { maxConcurrentRunsReason: normalizedReason } : {}),
369
+ };
370
+ }
371
+ function describeCommitmentConcurrency(commitment) {
372
+ const envelope = commitment.policy?.autonomyEnvelope;
373
+ const limit = envelope?.maxConcurrentRuns;
374
+ const decision = envelope?.maxConcurrentRunsDecision;
375
+ if (limit == null) {
376
+ const source = decision?.mode === 'unlimited' ? decision.source : 'default';
377
+ return `unlimited (${source})`;
378
+ }
379
+ const authority = decision?.authority || 'unknown';
380
+ const source = decision?.source || 'legacy';
381
+ const review = authority === 'unknown' ? ' — review required' : '';
382
+ return `${limit} (${authority}/${source}${review})`;
383
+ }
324
384
  /**
325
385
  * Parse a human interval ("15m", "6h", "1d", or a bare number of minutes) into
326
386
  * milliseconds. Throws on unparseable input, on trailing junk, and on values
@@ -672,6 +732,7 @@ latestCheck) {
672
732
  log(`Next check:${` ${commitment.cadence.nextCheckAt}`}`);
673
733
  log(`Mode: ${describeAutonomyLine(commitment, latestCheck)}`);
674
734
  log(`Delivery: ${(0, commitment_product_1.deliveryLabel)(commitment.delivery?.mode)}${commitment.delivery?.repo ? ` · ${commitment.delivery.repo}` : ''}${commitment.delivery?.baseBranch ? ` → ${commitment.delivery.baseBranch}` : ''}`);
735
+ log(`Concurrency: ${describeCommitmentConcurrency(commitment)}`);
675
736
  const criteria = commitment.target?.successCriteria || [];
676
737
  const evidenceHealth = latestCheck?.inputs?.evidenceHealth;
677
738
  const evidenceLine = describeEvidenceHealth(evidenceHealth, criteria.length);
@@ -79,6 +79,10 @@ function createNodeAdapter(opts = {}) {
79
79
  const { token } = currentCredential();
80
80
  const headers = {
81
81
  Accept: 'application/json',
82
+ // Server-side authoring provenance. This is attribution, not authority;
83
+ // owner/admin decisions are still authenticated and validated at the
84
+ // route boundary.
85
+ 'X-Skrr-Client': 'cli',
82
86
  ...(extra ?? {}),
83
87
  };
84
88
  if (token) {
@@ -32,6 +32,24 @@ export declare const taskLookupApi: {
32
32
  treePosition: (id: string, body: Json) => Promise<Json>;
33
33
  createAndStart: (body: Json) => Promise<Json>;
34
34
  };
35
+ /**
36
+ * Self-schedule — "come back to this task at T", owned by whoever is working it.
37
+ *
38
+ * A different resource from `/schedule`, not a variant of it. `/schedule` writes
39
+ * `Task.scheduledFor` + `recurrence` (the author's calendar, which the scheduler
40
+ * consumes and re-arms); this writes a `kind:'self_scheduled'` trigger row. Both
41
+ * can be set on one task and neither erases the other, so a headless agent can
42
+ * defer itself without editing the human's recurrence to mean something the
43
+ * human never asked for.
44
+ *
45
+ * PUT, not POST: a task has at most one pending return and parking again
46
+ * replaces it.
47
+ */
48
+ export declare const taskSelfScheduleApi: {
49
+ get: (id: string) => Promise<Json>;
50
+ park: (id: string, body: Json) => Promise<Json>;
51
+ clear: (id: string) => Promise<Json>;
52
+ };
35
53
  /**
36
54
  * The completion URL as a STRING.
37
55
  *
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.taskChatApi = exports.taskLookupApi = exports.taskWorkflowApi = void 0;
3
+ exports.taskChatApi = exports.taskSelfScheduleApi = exports.taskLookupApi = exports.taskWorkflowApi = void 0;
4
4
  exports.taskCompletePath = taskCompletePath;
5
5
  /**
6
6
  * task-extras.ts — the `/api/tasks` endpoints that have no `dataService.*`
@@ -51,6 +51,24 @@ exports.taskLookupApi = {
51
51
  treePosition: (id, body) => data_provider_1.request.patch(`${base(id)}/tree-position`, body),
52
52
  createAndStart: (body) => data_provider_1.request.post(`${MOUNT}/create-and-start`, body),
53
53
  };
54
+ /**
55
+ * Self-schedule — "come back to this task at T", owned by whoever is working it.
56
+ *
57
+ * A different resource from `/schedule`, not a variant of it. `/schedule` writes
58
+ * `Task.scheduledFor` + `recurrence` (the author's calendar, which the scheduler
59
+ * consumes and re-arms); this writes a `kind:'self_scheduled'` trigger row. Both
60
+ * can be set on one task and neither erases the other, so a headless agent can
61
+ * defer itself without editing the human's recurrence to mean something the
62
+ * human never asked for.
63
+ *
64
+ * PUT, not POST: a task has at most one pending return and parking again
65
+ * replaces it.
66
+ */
67
+ exports.taskSelfScheduleApi = {
68
+ get: (id) => data_provider_1.request.get(`${base(id)}/self-schedule`),
69
+ park: (id, body) => data_provider_1.request.put(`${base(id)}/self-schedule`, body),
70
+ clear: (id) => data_provider_1.request.delete(`${base(id)}/self-schedule`),
71
+ };
54
72
  /**
55
73
  * The completion URL as a STRING.
56
74
  *