@skrr-ai/cli 0.1.98 → 0.1.100

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 (71) hide show
  1. package/dist/base-command.d.ts +7 -0
  2. package/dist/base-command.js +10 -1
  3. package/dist/commands/agents/create.js +3 -3
  4. package/dist/commands/commitments/create.js +29 -8
  5. package/dist/commands/commitments/observations.js +3 -1
  6. package/dist/commands/commitments/preflight.js +20 -0
  7. package/dist/commands/commitments/update.js +69 -14
  8. package/dist/commands/memory/mounts/resolve.js +8 -2
  9. package/dist/commands/memory/stores/list.js +5 -1
  10. package/dist/commands/proposals/list.js +2 -0
  11. package/dist/commands/signals/monitors/create.d.ts +2 -2
  12. package/dist/commands/signals/monitors/create.js +2 -2
  13. package/dist/commands/signals/monitors/list.d.ts +2 -2
  14. package/dist/commands/signals/monitors/list.js +2 -2
  15. package/dist/commands/signals/monitors/pause.d.ts +2 -2
  16. package/dist/commands/signals/monitors/pause.js +2 -2
  17. package/dist/commands/signals/monitors/resume.d.ts +2 -2
  18. package/dist/commands/signals/monitors/resume.js +2 -2
  19. package/dist/commands/signals/monitors/revise.d.ts +2 -2
  20. package/dist/commands/signals/monitors/revise.js +2 -2
  21. package/dist/commands/signals/monitors/show.d.ts +2 -2
  22. package/dist/commands/signals/monitors/show.js +2 -2
  23. package/dist/commands/signals/monitors/test.d.ts +2 -2
  24. package/dist/commands/signals/monitors/test.js +2 -2
  25. package/dist/commands/signals/monitors/transitions.d.ts +2 -2
  26. package/dist/commands/signals/monitors/transitions.js +2 -2
  27. package/dist/commands/signals/monitors/validate.d.ts +2 -2
  28. package/dist/commands/signals/monitors/validate.js +2 -2
  29. package/dist/commands/signals/schemas/list.d.ts +2 -2
  30. package/dist/commands/signals/schemas/list.js +2 -2
  31. package/dist/commands/signals/schemas/show.d.ts +2 -2
  32. package/dist/commands/signals/schemas/show.js +2 -2
  33. package/dist/commands/signals/sources/list.d.ts +2 -2
  34. package/dist/commands/signals/sources/list.js +2 -2
  35. package/dist/commands/signals/sources/receipts.d.ts +2 -2
  36. package/dist/commands/signals/sources/receipts.js +2 -2
  37. package/dist/commands/signals/sources/show.d.ts +2 -2
  38. package/dist/commands/signals/sources/show.js +2 -2
  39. package/dist/commands/spaces/workflows/create.js +1 -1
  40. package/dist/commands/spaces/workflows/node-types.d.ts +45 -0
  41. package/dist/commands/spaces/workflows/node-types.js +86 -0
  42. package/dist/commands/spaces/workflows/update.js +5 -3
  43. package/dist/commands/tasks/assign.js +3 -4
  44. package/dist/commands/tasks/complete.d.ts +1 -0
  45. package/dist/commands/tasks/complete.js +1 -0
  46. package/dist/commands/tasks/create.js +4 -2
  47. package/dist/commands/tasks/deliverable/add.d.ts +1 -0
  48. package/dist/commands/tasks/deliverable/add.js +1 -0
  49. package/dist/commands/tasks/result/submit.d.ts +1 -0
  50. package/dist/commands/tasks/result/submit.js +1 -0
  51. package/dist/commands/tasks/resume/save.d.ts +1 -0
  52. package/dist/commands/tasks/resume/save.js +1 -0
  53. package/dist/commands/tasks/retry.js +38 -6
  54. package/dist/commands/tasks/schedule.js +3 -3
  55. package/dist/commands/tasks/show.js +3 -1
  56. package/dist/commands/tasks/update.js +31 -4
  57. package/dist/commands/tasks/updates/add.d.ts +1 -0
  58. package/dist/commands/tasks/updates/add.js +1 -0
  59. package/dist/lib/commitment-terms.js +1 -1
  60. package/dist/lib/commitments.js +34 -2
  61. package/dist/lib/dedicated-machines.d.ts +5 -7
  62. package/dist/lib/dedicated-machines.js +16 -10
  63. package/dist/lib/scoped-memory-command.d.ts +7 -0
  64. package/dist/lib/scoped-memory-command.js +36 -0
  65. package/dist/lib/signals.js +22 -13
  66. package/dist/lib/space-usage.js +1 -1
  67. package/dist/node_modules/@skrr-ai/data-provider/index.js +35 -19
  68. package/dist/signal-command.d.ts +11 -0
  69. package/dist/signal-command.js +15 -0
  70. package/oclif.manifest.json +1355 -32
  71. package/package.json +1 -1
@@ -1,9 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const core_1 = require("@oclif/core");
4
- const base_command_1 = require("../../../base-command");
4
+ const signal_command_1 = require("../../../signal-command");
5
5
  const signals_1 = require("../../../lib/signals");
6
- class SourcesList extends base_command_1.BaseCommand {
6
+ class SourcesList extends signal_command_1.SignalCommand {
7
7
  static description = 'List Signal sources (read-only; credentials are never shown)';
8
8
  static flags = { json: core_1.Flags.boolean({ description: 'Output as JSON' }) };
9
9
  async run() {
@@ -1,5 +1,5 @@
1
- import { BaseCommand } from '../../../base-command';
2
- export default class SourcesReceipts extends BaseCommand {
1
+ import { SignalCommand } from '../../../signal-command';
2
+ export default class SourcesReceipts extends SignalCommand {
3
3
  static description: string;
4
4
  static flags: {
5
5
  json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
@@ -1,9 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const core_1 = require("@oclif/core");
4
- const base_command_1 = require("../../../base-command");
4
+ const signal_command_1 = require("../../../signal-command");
5
5
  const signals_1 = require("../../../lib/signals");
6
- class SourcesReceipts extends base_command_1.BaseCommand {
6
+ class SourcesReceipts extends signal_command_1.SignalCommand {
7
7
  static description = 'List recent redacted receipts of a Signal source';
8
8
  static flags = { json: core_1.Flags.boolean({ description: 'Output as JSON' }) };
9
9
  static args = { id: core_1.Args.string({ required: true, ignoreStdin: true }) };
@@ -1,5 +1,5 @@
1
- import { BaseCommand } from '../../../base-command';
2
- export default class SourcesShow extends BaseCommand {
1
+ import { SignalCommand } from '../../../signal-command';
2
+ export default class SourcesShow extends SignalCommand {
3
3
  static description: string;
4
4
  static flags: {
5
5
  json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
@@ -1,9 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const core_1 = require("@oclif/core");
4
- const base_command_1 = require("../../../base-command");
4
+ const signal_command_1 = require("../../../signal-command");
5
5
  const signals_1 = require("../../../lib/signals");
6
- class SourcesShow extends base_command_1.BaseCommand {
6
+ class SourcesShow extends signal_command_1.SignalCommand {
7
7
  static description = 'Show one Signal source (read-only)';
8
8
  static flags = { json: core_1.Flags.boolean({ description: 'Output as JSON' }) };
9
9
  static args = { id: core_1.Args.string({ required: true, ignoreStdin: true }) };
@@ -29,7 +29,7 @@ class SpacesWorkflowsCreate extends base_command_1.BaseCommand {
29
29
  title: core_1.Flags.string({ required: true, description: 'Workflow title' }),
30
30
  description: core_1.Flags.string({ description: 'Optional description' }),
31
31
  graph: core_1.Flags.string({
32
- description: 'Required exactly once with --draft/--starter: path to graph JSON ({ nodes, edges })',
32
+ description: 'Required exactly once with --draft/--starter: path to graph JSON ({ nodes, edges }); inspect config with spaces workflows node-types',
33
33
  exactlyOne: ['graph', 'draft', 'starter'],
34
34
  }),
35
35
  draft: core_1.Flags.string({
@@ -0,0 +1,45 @@
1
+ import { type WorkflowNodeType } from '@skrr-ai/data-provider';
2
+ import { BaseCommand } from '../../../base-command';
3
+ export declare function workflowNodeTypeReference(type: WorkflowNodeType): {
4
+ type: WorkflowNodeType;
5
+ typeVersion: number;
6
+ label: string;
7
+ description: string;
8
+ requiredConfig: string[];
9
+ fields: {
10
+ path: string;
11
+ name: string;
12
+ label: string;
13
+ kind: import("@skrr-ai/data-provider").WorkflowNodeFieldKind;
14
+ placeholder?: string;
15
+ help?: string;
16
+ showWhen?: {
17
+ field: string;
18
+ equals: unknown;
19
+ };
20
+ options?: Array<{
21
+ value: string;
22
+ label: string;
23
+ }>;
24
+ }[];
25
+ defaultNode: {
26
+ key: string;
27
+ type: WorkflowNodeType;
28
+ typeVersion: number;
29
+ config: Record<string, unknown>;
30
+ join: "all";
31
+ bindings: {};
32
+ };
33
+ outcomes: import("@skrr-ai/data-provider").WorkflowOutcomeSpec[];
34
+ };
35
+ export default class SpacesWorkflowsNodeTypes extends BaseCommand {
36
+ static description: string;
37
+ static examples: string[];
38
+ static args: {
39
+ type: import("@oclif/core/lib/interfaces").Arg<string | undefined, Record<string, unknown>>;
40
+ };
41
+ static flags: {
42
+ json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
43
+ };
44
+ run(): Promise<void>;
45
+ }
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.workflowNodeTypeReference = workflowNodeTypeReference;
4
+ const core_1 = require("@oclif/core");
5
+ const data_provider_1 = require("@skrr-ai/data-provider");
6
+ const base_command_1 = require("../../../base-command");
7
+ function workflowNodeTypeReference(type) {
8
+ const definition = data_provider_1.WORKFLOW_NODE_TYPES[type];
9
+ const node = {
10
+ key: `${definition.keyPrefix}_1`,
11
+ type,
12
+ typeVersion: definition.version,
13
+ config: definition.defaultConfig(),
14
+ join: 'all',
15
+ bindings: {},
16
+ };
17
+ return {
18
+ type,
19
+ typeVersion: definition.version,
20
+ label: definition.label,
21
+ description: definition.description,
22
+ requiredConfig: definition.requiredConfig,
23
+ fields: definition.fields.map((field) => ({
24
+ ...field,
25
+ path: field.name === 'join' ? 'join' : `config.${field.name}`,
26
+ })),
27
+ defaultNode: node,
28
+ outcomes: definition.outcomes(node),
29
+ };
30
+ }
31
+ class SpacesWorkflowsNodeTypes extends base_command_1.BaseCommand {
32
+ static description = 'Read node config fields, defaults and review lens preset choices from the installed CLI catalog (no server requests)';
33
+ static examples = [
34
+ '<%= config.bin %> spaces workflows node-types',
35
+ '<%= config.bin %> spaces workflows node-types loop --json',
36
+ '<%= config.bin %> spaces workflows node-types review_loop --json',
37
+ ];
38
+ static args = {
39
+ type: core_1.Args.string({ description: 'Node type; omit to list all types', ignoreStdin: true }),
40
+ };
41
+ static flags = {
42
+ json: core_1.Flags.boolean({ description: 'Output node fields, choices and default node JSON' }),
43
+ };
44
+ async run() {
45
+ const { args, flags } = await this.parse(SpacesWorkflowsNodeTypes);
46
+ if (args.type && !(0, data_provider_1.isWorkflowNodeType)(args.type)) {
47
+ this.failWithCliError({
48
+ message: `Unknown node type: ${args.type}. Run skrr spaces workflows node-types to list types.`,
49
+ code: 'WORKFLOW_NODE_TYPE_UNKNOWN',
50
+ exit: 2,
51
+ retryable: false,
52
+ });
53
+ return;
54
+ }
55
+ const types = args.type
56
+ ? [args.type]
57
+ : Object.keys(data_provider_1.WORKFLOW_NODE_TYPES);
58
+ const references = types.map(workflowNodeTypeReference);
59
+ const note = 'Installed CLI authoring reference. Defaults may require configuration; fill required fields and agent choices, then validate the draft against your server before publishing.';
60
+ if (flags.json) {
61
+ this.log(JSON.stringify({
62
+ object: 'workflow_node_catalog', source: 'installed_cli', note, nodeTypes: references,
63
+ }, null, 2));
64
+ return;
65
+ }
66
+ this.log(note);
67
+ for (const reference of references) {
68
+ this.log(`\n${reference.type} · ${reference.label} · v${reference.typeVersion}`);
69
+ if (!args.type)
70
+ continue;
71
+ this.log(reference.description);
72
+ this.log(`Required config keys: ${reference.requiredConfig.join(', ') || 'none declared'}`);
73
+ for (const field of reference.fields) {
74
+ const choices = field.options?.length
75
+ ? ` (${field.options.map((option) => option.value).join(', ')})` : '';
76
+ this.log(` ${field.path}: ${field.label}${choices}`);
77
+ if (field.help)
78
+ this.log(` ${field.help}`);
79
+ }
80
+ this.log(`Default node: ${JSON.stringify(reference.defaultNode, null, 2)}`);
81
+ }
82
+ if (!args.type)
83
+ this.log('\nRead one type: skrr spaces workflows node-types <type> --json');
84
+ }
85
+ }
86
+ exports.default = SpacesWorkflowsNodeTypes;
@@ -51,19 +51,21 @@ class SpacesWorkflowsUpdate extends base_command_1.BaseCommand {
51
51
  title: core_1.Flags.string({ description: 'New workflow title' }),
52
52
  description: core_1.Flags.string({ description: 'New workflow description' }),
53
53
  'clear-description': core_1.Flags.boolean({ description: 'Remove the workflow description' }),
54
- graph: core_1.Flags.string({ description: 'Path to graph JSON ({ nodes, edges })' }),
54
+ graph: core_1.Flags.string({
55
+ description: 'Path to graph JSON ({ nodes, edges }); inspect config with spaces workflows node-types',
56
+ }),
55
57
  draft: core_1.Flags.string({
56
58
  description: 'Path to complete draft JSON ({ semantic, presentation })',
57
59
  }),
58
60
  'semantic-revision': core_1.Flags.integer({
59
- description: 'Expected semantic revision; defaults to the latest server value',
61
+ description: 'Expected semantic revision; pass with --presentation-revision. For graph/draft writes, omit both to fetch the latest server revisions',
60
62
  }),
61
63
  'max-duration': core_1.Flags.string({
62
64
  description: 'Run duration limit, e.g. 90m, 4h or 2d. A run still open past it is cancelled with WORKFLOW_RUN_DEADLINE. Applies from the next publish; runs already started keep their deadline',
63
65
  }),
64
66
  'clear-max-duration': core_1.Flags.boolean({ description: 'Remove the run duration limit' }),
65
67
  'presentation-revision': core_1.Flags.integer({
66
- description: 'Expected presentation revision; defaults to the latest server value',
68
+ description: 'Expected presentation revision; pass with --semantic-revision. For graph/draft writes, omit both to fetch the latest server revisions',
67
69
  }),
68
70
  };
69
71
  async run() {
@@ -196,11 +196,10 @@ class TasksAssign extends base_command_1.BaseCommand {
196
196
  if (flags.json) {
197
197
  this.log(JSON.stringify({
198
198
  ...(0, web_url_1.withTaskUrl)(this.cliConfig.baseURL, updated),
199
- // What each free-text reference actually became — so a caller can
200
- // verify the resolution rather than trust it.
199
+ // Keep request details separate from the canonical assignee arrays
200
+ // returned by the server, especially when removing co-assignees.
201
+ assignmentAction: flags.remove ? 'remove' : flags.replace ? 'replace' : 'add',
201
202
  resolved: [...people.resolved, ...agents.resolved],
202
- assigneeUserIds: userIds,
203
- assigneeAgentIds: agentIds,
204
203
  }, null, 2));
205
204
  return;
206
205
  }
@@ -10,6 +10,7 @@ export declare function describePendingReview({ statusType, taskId, resultId, bi
10
10
  }>;
11
11
  }): string | null;
12
12
  export default class TasksComplete extends BaseCommand {
13
+ static autonomousCredentialPolicy: "bound-session";
13
14
  static description: string;
14
15
  static examples: string[];
15
16
  static args: {
@@ -99,6 +99,7 @@ function describePendingReview({ statusType, taskId, resultId, bin, advisories,
99
99
  'may close without one — an override is recorded on the resolution.)');
100
100
  }
101
101
  class TasksComplete extends base_command_1.BaseCommand {
102
+ static autonomousCredentialPolicy = 'bound-session';
102
103
  static description = 'Submit one versioned Result and advance the Task workflow';
103
104
  static examples = [
104
105
  '<%= config.bin %> tasks complete <task> --summary "Implemented and verified the fix" --deliverable <resource-link-id>',
@@ -159,9 +159,11 @@ class TasksCreate extends base_command_1.BaseCommand {
159
159
  description: 'Task priority',
160
160
  options: [...tasks_1.TASK_PRIORITIES],
161
161
  }),
162
- parent: core_1.Flags.string({ description: 'Parent task ID' }),
162
+ parent: core_1.Flags.string({
163
+ description: 'Structural parent task ID. Child statuses roll up; all terminal children can automatically close the parent, with unmet obligations recorded. Use --spawned-by for supporting work without status rollup.',
164
+ }),
163
165
  'spawned-by': core_1.Flags.string({
164
- description: 'Task whose execution produced this one (lineage). Distinct from --parent (containment) and blockers (ordering).',
166
+ description: 'Task whose execution produced this one (lineage only, without parent status rollup). Use --parent for structural containment and blockers for ordering.',
165
167
  }),
166
168
  'spawn-reason': core_1.Flags.string({
167
169
  description: 'Why this work exists',
@@ -10,6 +10,7 @@ export declare function defaultDeliverableIdempotencyKey(task: string, payload:
10
10
  * optional structured one.
11
11
  */
12
12
  export default class TasksDeliverableAdd extends BaseCommand {
13
+ static autonomousCredentialPolicy: "bound-session";
13
14
  static description: string;
14
15
  static examples: string[];
15
16
  static args: {
@@ -59,6 +59,7 @@ function defaultDeliverableIdempotencyKey(task, payload) {
59
59
  * optional structured one.
60
60
  */
61
61
  class TasksDeliverableAdd extends base_command_1.BaseCommand {
62
+ static autonomousCredentialPolicy = 'bound-session';
62
63
  static description = 'Record something this task produced (a commit, a page, a URL, a file, a message, a dataset).\n' +
63
64
  '\n' +
64
65
  'IDENTITY is the part before @; LOCATION is the part after it. They are not the same ' +
@@ -1,5 +1,6 @@
1
1
  import { BaseCommand } from '../../../base-command';
2
2
  export default class TasksResultSubmit extends BaseCommand {
3
+ static autonomousCredentialPolicy: "bound-session";
3
4
  static description: string;
4
5
  static examples: string[];
5
6
  static args: {
@@ -53,6 +53,7 @@ function parseJson(value, label) {
53
53
  }
54
54
  }
55
55
  class TasksResultSubmit extends base_command_1.BaseCommand {
56
+ static autonomousCredentialPolicy = 'bound-session';
56
57
  static description = 'Submit the Task Result; this does not self-accept it or turn a domain conclusion into a Review';
57
58
  static examples = [
58
59
  '<%= config.bin %> tasks result submit <task> --summary "NO-SHIP until Gates A-D pass" --conclusion-type release_recommendation --conclusion-json \'{"posture":"no_ship"}\'',
@@ -1,5 +1,6 @@
1
1
  import { BaseCommand } from '../../../base-command';
2
2
  export default class TasksResumeSave extends BaseCommand {
3
+ static autonomousCredentialPolicy: "bound-session";
3
4
  static description: string;
4
5
  static args: {
5
6
  task: import("@oclif/core/lib/interfaces").Arg<string | undefined, Record<string, unknown>>;
@@ -9,6 +9,7 @@ const api_fetch_1 = require("../../../lib/api-fetch");
9
9
  const session_task_write_1 = require("../../../lib/session-task-write");
10
10
  const prompt_1 = require("../../../lib/prompt");
11
11
  class TasksResumeSave extends base_command_1.BaseCommand {
12
+ static autonomousCredentialPolicy = 'bound-session';
12
13
  static description = 'Save recovery state without publishing a visible Task Update';
13
14
  static args = {
14
15
  task: core_1.Args.string({ required: false, ignoreStdin: true, description: 'Task id or ref' }),
@@ -4,6 +4,7 @@ const core_1 = require("@oclif/core");
4
4
  const data_provider_1 = require("@skrr-ai/data-provider");
5
5
  const base_command_1 = require("../../base-command");
6
6
  const task_resolver_1 = require("../../lib/task-resolver");
7
+ const start_1 = require("./start");
7
8
  /**
8
9
  * OSK-3815 — the CLI verb for the `retry_task` action.
9
10
  *
@@ -72,8 +73,9 @@ class TasksRetry extends base_command_1.BaseCommand {
72
73
  // Step 1 — clear the terminal state. `todo` rather than `backlog`: the task
73
74
  // was already triaged into the working set once, and a retry is not a
74
75
  // decision to deprioritise it.
76
+ let movedTask;
75
77
  try {
76
- await data_provider_1.dataService.moveTask({
78
+ movedTask = await data_provider_1.dataService.moveTask({
77
79
  taskId: taskRef,
78
80
  status: 'todo',
79
81
  });
@@ -81,13 +83,23 @@ class TasksRetry extends base_command_1.BaseCommand {
81
83
  catch (err) {
82
84
  this.handleApiError(err, { subject: `Task ${taskRef}` });
83
85
  }
86
+ // This receipt describes the first committed transition. Dispatch may
87
+ // change the Task again, so it is not an overall current-state snapshot.
88
+ const statusTransition = { requestedStatus: 'todo', task: movedTask };
84
89
  if (flags['no-start']) {
85
90
  if (flags.json) {
86
- this.log(JSON.stringify({ taskId: taskRef, status: 'todo', started: false }));
91
+ this.log(JSON.stringify({
92
+ taskId: taskRef,
93
+ status: movedTask?.status,
94
+ started: false,
95
+ statusTransition,
96
+ }));
87
97
  return;
88
98
  }
89
- this.log(`Cleared failed state on ${taskRef} — now todo, not started.`);
90
- this.log(`Start it with: skrr tasks start ${taskRef}`);
99
+ this.log(`Cleared failed state on ${taskRef}; execution was not requested.`);
100
+ if (typeof movedTask?.status === 'string')
101
+ this.log(`Status: ${movedTask.status}`);
102
+ this.log(`Start it with: ${this.config.bin} tasks start ${taskRef}`);
91
103
  return;
92
104
  }
93
105
  let result;
@@ -95,12 +107,32 @@ class TasksRetry extends base_command_1.BaseCommand {
95
107
  result = await data_provider_1.dataService.startTask(taskRef, flags.skill ? { skillId: flags.skill } : undefined);
96
108
  }
97
109
  catch (err) {
110
+ const waiting = (0, start_1.waitingForMachine)(err);
111
+ if (waiting) {
112
+ if (flags.json) {
113
+ this.log(JSON.stringify({
114
+ waiting: true,
115
+ code: 'TASK_WAITING_FOR_MACHINE',
116
+ taskId: taskRef,
117
+ started: false,
118
+ statusTransition,
119
+ ...waiting,
120
+ }, null, 2));
121
+ return;
122
+ }
123
+ this.log(`Task ${taskRef} is waiting for the machine to start; the task will run by itself once it is ready.`);
124
+ if (waiting.message)
125
+ this.log(waiting.message);
126
+ return;
127
+ }
128
+ const observedStatus = typeof movedTask?.status === 'string' ? ` (returned status '${movedTask.status}')` : '';
98
129
  this.handleApiError(err, {
99
- subject: `Task ${taskRef} (failed state was cleared — it is now 'todo')`,
130
+ subject: `Task ${taskRef} (status transition committed${observedStatus}; start was not confirmed. ` +
131
+ `Check ${this.config.bin} tasks show ${taskRef}, then use ${this.config.bin} tasks start ${taskRef} if needed)`,
100
132
  });
101
133
  }
102
134
  if (flags.json) {
103
- this.log(JSON.stringify({ ...result, taskId: taskRef, status: 'todo', started: true }, null, 2));
135
+ this.log(JSON.stringify({ ...result, taskId: taskRef, started: true, statusTransition }, null, 2));
104
136
  return;
105
137
  }
106
138
  this.log(`Retried task ${taskRef}`);
@@ -18,7 +18,7 @@ class TasksSchedule extends base_command_1.BaseCommand {
18
18
  static flags = {
19
19
  json: core_1.Flags.boolean({ description: 'Output as JSON' }),
20
20
  at: core_1.Flags.string({
21
- description: 'First fire time as ISO-8601 UTC (e.g. 2026-06-01T15:30:00Z)',
21
+ description: 'First fire time as ISO-8601 UTC ending in Z (e.g. 2026-06-01T15:30:00Z); numeric offsets such as +00:00 are not accepted',
22
22
  }),
23
23
  cron: core_1.Flags.string({
24
24
  description: 'Recurrence cron pattern (UTC unless --timezone is set)',
@@ -36,7 +36,7 @@ class TasksSchedule extends base_command_1.BaseCommand {
36
36
  dependsOn: ['cron'],
37
37
  }),
38
38
  'from-json': core_1.Flags.string({
39
- description: 'Path to a JSON file (or "-" for stdin) with the schedule body',
39
+ description: 'Path to a JSON file (or "-" for stdin) with the schedule body; scheduledFor must use ISO-8601 UTC ending in Z',
40
40
  }),
41
41
  };
42
42
  async run() {
@@ -63,7 +63,7 @@ class TasksSchedule extends base_command_1.BaseCommand {
63
63
  recurrence,
64
64
  });
65
65
  if (!payload.scheduledFor || typeof payload.scheduledFor !== 'string') {
66
- this.error('A first-fire time is required. Pass --at <ISO-8601> or include scheduledFor in --from-json.', { exit: 1 });
66
+ this.error('A first-fire time is required. Pass --at <ISO-8601 UTC ending in Z> or include scheduledFor in --from-json using the same format.', { exit: 1 });
67
67
  }
68
68
  let result;
69
69
  try {
@@ -518,7 +518,9 @@ class TasksShow extends base_command_1.BaseCommand {
518
518
  : ''}`);
519
519
  const actionability = t.actionability;
520
520
  if (actionability) {
521
- this.log(`Next action: ${actionability.nextActor ?? '-'} · ${actionability.state ?? '-'} · ${actionability.reasonCode ?? '-'}`);
521
+ this.log(actionability.state === 'terminal'
522
+ ? 'Workflow: ended · no next executor'
523
+ : `Next action: ${actionability.nextActor ?? '-'} · ${actionability.state ?? '-'} · ${actionability.reasonCode ?? '-'}`);
522
524
  this.log(`Why: ${actionability.reason ?? '-'}`);
523
525
  await this.printBlockerDetail(t, actionability);
524
526
  }
@@ -308,20 +308,47 @@ class TasksUpdate extends base_command_1.BaseCommand {
308
308
  const field = expectationsPatch !== undefined || body.expectations !== undefined
309
309
  ? 'Changing Expectations'
310
310
  : 'Changing the description';
311
- this.log(`${field} advances the Task specification, so ${count} ${noun} ` +
312
- 'will stop counting and must be recorded again.');
311
+ const disclosure = [
312
+ `${field} advances the Task specification, so ${count} ${noun} ` +
313
+ 'will stop counting and must be recorded again.',
314
+ ];
313
315
  /*
314
316
  * NAME them. The count alone made the warning unactionable — and it is
315
317
  * task-WIDE, so Expectations you are not touching lose their judgements
316
318
  * too, which a bare number actively hides.
317
319
  */
318
320
  for (const row of settled) {
319
- this.log(` ${row.id} ${row.sentence || row.text || ''}`);
321
+ disclosure.push(` ${row.id} ${row.sentence || row.text || ''}`);
320
322
  }
321
323
  if (settled.length > 0) {
322
- this.log('Revocation covers every Expectation on this Task, including ones this edit ' +
324
+ disclosure.push('Revocation covers every Expectation on this Task, including ones this edit ' +
323
325
  'does not touch.');
324
326
  }
327
+ if (flags.json) {
328
+ // JSON callers acknowledge with --yes. Neither disclosure prose nor
329
+ // an interactive prompt may precede the single machine-readable result.
330
+ this.failWithCliError({
331
+ message: disclosure.join('\n') +
332
+ '\nNo change made — re-run with `--yes` to accept the revocation.',
333
+ code: 'TASK_SPEC_CHANGE_CONFIRMATION_REQUIRED',
334
+ exit: 2,
335
+ retryable: false,
336
+ details: {
337
+ taskId: taskRef,
338
+ assessmentCount: acceptanceUnreadable ? null : settled.length,
339
+ acceptanceReadable: !acceptanceUnreadable,
340
+ affectedExpectations: settled.map((row) => ({
341
+ id: row.id,
342
+ text: row.sentence || row.text || '',
343
+ })),
344
+ revocationScope: 'all_expectations',
345
+ confirmationFlag: '--yes',
346
+ disclosure,
347
+ },
348
+ });
349
+ }
350
+ for (const line of disclosure)
351
+ this.log(line);
325
352
  const ok = await this.confirm('Continue? [y/N] ');
326
353
  if (!ok) {
327
354
  /*
@@ -1,5 +1,6 @@
1
1
  import { BaseCommand } from '../../../base-command';
2
2
  export default class TasksUpdatesAdd extends BaseCommand {
3
+ static autonomousCredentialPolicy: "bound-session";
3
4
  static description: string;
4
5
  static examples: string[];
5
6
  static args: {
@@ -60,6 +60,7 @@ function parseJsonObject(value, label) {
60
60
  return parsed;
61
61
  }
62
62
  class TasksUpdatesAdd extends base_command_1.BaseCommand {
63
+ static autonomousCredentialPolicy = 'bound-session';
63
64
  static description = 'Record a meaningful Task Update, optionally with recovery state';
64
65
  static examples = [
65
66
  '<%= config.bin %> tasks updates add <task> --type progress --summary "Migration review complete" --data \'{"completedSinceLast":["Checked migrations 001-014"],"nextActions":["Review authorization"]}\'',
@@ -82,7 +82,7 @@ function renderCommitmentTerms(commitment, log) {
82
82
  ? `${subject.type} ${subject.label || subject.externalKey}`
83
83
  : 'standing (not about one instance)'}`);
84
84
  const sensors = commitment.sensors ?? [];
85
- log(`Watches: ${sensors.length
85
+ log(`Sensors: ${sensors.length
86
86
  ? sensors
87
87
  .map((s) => `${sensorRefKey(s)}${s.sensorType ? ` (${s.sensorType})` : ''}`)
88
88
  .join(', ')
@@ -1037,6 +1037,23 @@ columns, empty, log) {
1037
1037
  }
1038
1038
  (0, format_1.renderTable)(rows, columns, log);
1039
1039
  }
1040
+ function describeRunCount(usage, key) {
1041
+ const value = usage?.[key];
1042
+ const status = usage?.[`${key}Status`];
1043
+ if (status === 'unknown' ||
1044
+ typeof value !== 'number' ||
1045
+ !Number.isSafeInteger(value) ||
1046
+ value < 0) {
1047
+ return 'unknown';
1048
+ }
1049
+ // A native session proves a floor, not the provider's complete internal
1050
+ // activity. Preserve that distinction in the human ledger, too.
1051
+ if (status === 'lower_bound')
1052
+ return `at least ${value}`;
1053
+ if (status !== undefined && status !== null)
1054
+ return 'unknown';
1055
+ return String(value);
1056
+ }
1040
1057
  /** Human view for `commitments ledger`; JSON remains the automation surface. */
1041
1058
  function renderCommitmentLedger(ledger, log) {
1042
1059
  const checks = records(ledger.checks);
@@ -1073,12 +1090,16 @@ function renderCommitmentLedger(ledger, log) {
1073
1090
  id: text(item.id),
1074
1091
  status: text(item.status),
1075
1092
  attempt: `${text(item.attempt, '0')}/${text(item.maxAttempts, '-')}`,
1093
+ turns: describeRunCount(record(item.usage), 'turns'),
1094
+ tools: describeRunCount(record(item.usage), 'toolCallCount'),
1076
1095
  reason: text(firstValue(item, ['reason', 'error']), ''),
1077
1096
  finished: text(firstValue(item, ['completedAt', 'updatedAt', 'startedAt'])),
1078
1097
  })), [
1079
1098
  { key: 'id', header: 'RUN ID', verbatim: true },
1080
1099
  { key: 'status', header: 'STATUS', maxWidth: 12 },
1081
1100
  { key: 'attempt', header: 'ATTEMPT' },
1101
+ { key: 'turns', header: 'TURNS' },
1102
+ { key: 'tools', header: 'TOOLS' },
1082
1103
  { key: 'reason', header: 'REASON', maxWidth: 36 },
1083
1104
  { key: 'finished', header: 'LAST UPDATED', maxWidth: 24 },
1084
1105
  ], 'No runs recorded.', log);
@@ -1247,13 +1268,24 @@ function formatCommitmentCycles(page) {
1247
1268
  */
1248
1269
  function formatCycleLatency(cycle) {
1249
1270
  const latency = cycle.latency;
1271
+ const history = cycle.historyDetail;
1272
+ const disclosure = history?.state === 'compacted'
1273
+ ? [
1274
+ ` Detailed history was compacted${history.at ? ` at ${history.at}` : ''}; available reason and source timestamps were retained.`,
1275
+ ]
1276
+ : history?.state === 'unavailable'
1277
+ ? [' Detailed history is unavailable; why it is missing was not recorded.']
1278
+ : [];
1250
1279
  const missingFor = (stage) => (latency?.pendingStages ? latency.pendingStages.includes(stage) : latency?.pending)
1251
1280
  ? 'pending'
1252
1281
  : 'unmeasurable';
1253
1282
  if (latency?.measured === false) {
1254
- return [` latency: ${latency.pending ? 'pending' : 'unmeasurable'}`];
1283
+ return [...disclosure, ` latency: ${latency.pending ? 'pending' : 'unmeasurable'}`];
1255
1284
  }
1256
- return Object.entries(latency?.stages || {}).map(([stage, ms]) => ` ${stage}: ${typeof ms === 'number' ? `${ms}ms` : missingFor(stage)}`);
1285
+ return [
1286
+ ...disclosure,
1287
+ ...Object.entries(latency?.stages || {}).map(([stage, ms]) => ` ${stage === 'event_to_turn' ? 'event_to_turn (server handoff)' : stage}: ${typeof ms === 'number' ? `${ms}ms` : missingFor(stage)}`),
1288
+ ];
1257
1289
  }
1258
1290
  const ANSWER_GLYPH = { yes: '✓', no: '✗', 'n/a': '–' };
1259
1291
  /** The six questions for the latest Cycle, the first ✗ carrying its fix. */
@@ -633,15 +633,13 @@ export declare function dedicatedLeaseDisplayName(lease: DedicatedLease | Harnes
633
633
  /** `$12.34` — cents are what the API speaks; dollars are what a person reads. */
634
634
  export declare function formatUsdCents(cents: number | null | undefined): string;
635
635
  /**
636
- * `$1.23 this month for machines of this type · $500.00 cap for this machine`,
637
- * or why that sentence cannot be written.
636
+ * Name the monthly total that the machine's configured cap is checked against.
638
637
  *
639
- * The two figures are different scopes and the sentence has to say so. The spend
640
- * is a shared pot the server computes for (owner, team, machineProduct), so every
638
+ * The spend is a shared pot the server computes for (owner, team, machineProduct), so every
641
639
  * lease of the same product reports the same number — a machine created minutes
642
- * ago with no active seconds still reports the month's total. The cap is this
643
- * lease's own. Printed as "$1.23 this month of $500.00 cap" the only available
644
- * reading was that this machine had spent it.
640
+ * ago with no active seconds still reports the month's total. The cap is set on
641
+ * this lease, but admission compares it against that shared projected total.
642
+ * An old response without spendScope cannot establish who owns the total.
645
643
  */
646
644
  export declare function dedicatedSpendSummary(lease: DedicatedLease): string;
647
645
  /**