@skrr-ai/cli 0.1.101 → 0.1.103

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/dist/base-command.js +24 -0
  2. package/dist/commands/agent/message/send.js +6 -2
  3. package/dist/commands/agents/triggers/create.js +7 -17
  4. package/dist/commands/agents/triggers/update.js +41 -28
  5. package/dist/commands/commitments/control/apply.d.ts +15 -0
  6. package/dist/commands/commitments/control/apply.js +36 -0
  7. package/dist/commands/commitments/control/preview.d.ts +17 -0
  8. package/dist/commands/commitments/control/preview.js +43 -0
  9. package/dist/commands/commitments/control/show.d.ts +12 -0
  10. package/dist/commands/commitments/control/show.js +29 -0
  11. package/dist/commands/commitments/create.js +5 -2
  12. package/dist/commands/commitments/observation/probe.d.ts +15 -0
  13. package/dist/commands/commitments/observation/probe.js +46 -0
  14. package/dist/commands/commitments/observation/summary.d.ts +9 -0
  15. package/dist/commands/commitments/observation/summary.js +31 -0
  16. package/dist/commands/commitments/preflight.js +18 -1
  17. package/dist/commands/commitments/readiness/show.d.ts +12 -0
  18. package/dist/commands/commitments/readiness/show.js +29 -0
  19. package/dist/commands/commitments/readiness/verify.d.ts +13 -0
  20. package/dist/commands/commitments/readiness/verify.js +33 -0
  21. package/dist/commands/commitments/roster.d.ts +15 -0
  22. package/dist/commands/commitments/roster.js +39 -0
  23. package/dist/commands/commitments/show.js +1 -1
  24. package/dist/commands/commitments/status.d.ts +17 -0
  25. package/dist/commands/commitments/status.js +41 -0
  26. package/dist/commands/commitments/update.js +23 -9
  27. package/dist/commands/commitments/work-plan.d.ts +11 -0
  28. package/dist/commands/commitments/work-plan.js +36 -0
  29. package/dist/commands/compass/effective-policy.d.ts +12 -0
  30. package/dist/commands/compass/effective-policy.js +34 -0
  31. package/dist/commands/compass/update.d.ts +12 -0
  32. package/dist/commands/compass/update.js +27 -0
  33. package/dist/commands/daemon/logs.d.ts +39 -1
  34. package/dist/commands/daemon/logs.js +140 -13
  35. package/dist/commands/followups/checkin.js +46 -3
  36. package/dist/commands/machines/dedicated/exec.js +3 -1
  37. package/dist/commands/proposals/discard.d.ts +4 -0
  38. package/dist/commands/proposals/discard.js +3 -0
  39. package/dist/commands/proposals/keep.d.ts +4 -0
  40. package/dist/commands/proposals/keep.js +3 -0
  41. package/dist/commands/proposals/snooze.d.ts +4 -0
  42. package/dist/commands/proposals/snooze.js +3 -0
  43. package/dist/commands/spaces/workflows/cancel-node.d.ts +16 -0
  44. package/dist/commands/spaces/workflows/cancel-node.js +44 -0
  45. package/dist/commands/spaces/workflows/cancel.d.ts +40 -0
  46. package/dist/commands/spaces/workflows/cancel.js +109 -3
  47. package/dist/commands/spaces/workflows/run.d.ts +9 -0
  48. package/dist/commands/spaces/workflows/run.js +38 -0
  49. package/dist/commands/tasks/approvals/resolve.d.ts +12 -0
  50. package/dist/commands/tasks/approvals/resolve.js +21 -5
  51. package/dist/commands/tasks/schedule.js +1 -1
  52. package/dist/commands/triggers/capabilities.js +7 -0
  53. package/dist/commands/triggers/create.js +12 -18
  54. package/dist/commands/triggers/fire.js +3 -0
  55. package/dist/commands/triggers/update.js +41 -27
  56. package/dist/commands/widgets/check.d.ts +39 -0
  57. package/dist/commands/widgets/check.js +179 -0
  58. package/dist/lib/agent-mailbox.d.ts +0 -1
  59. package/dist/lib/agent-mailbox.js +0 -1
  60. package/dist/lib/commitment-loop-status.d.ts +16 -0
  61. package/dist/lib/commitment-loop-status.js +164 -0
  62. package/dist/lib/commitments.d.ts +47 -1
  63. package/dist/lib/commitments.js +218 -6
  64. package/dist/lib/compasses.d.ts +3 -0
  65. package/dist/lib/compasses.js +1 -0
  66. package/dist/lib/delegated-cli.d.ts +23 -1
  67. package/dist/lib/delegated-cli.js +61 -4
  68. package/dist/lib/first-party-harness.d.ts +1 -1
  69. package/dist/lib/first-party-harness.js +1 -1
  70. package/dist/lib/responsibility-controls.d.ts +11 -0
  71. package/dist/lib/responsibility-controls.js +119 -0
  72. package/dist/lib/session-task-write.d.ts +13 -0
  73. package/dist/lib/session-task-write.js +63 -3
  74. package/dist/lib/stewardship-output.js +13 -2
  75. package/dist/lib/triggers.d.ts +26 -0
  76. package/dist/lib/triggers.js +134 -1
  77. package/dist/lib/widgetCheck.d.ts +123 -0
  78. package/dist/lib/widgetCheck.js +998 -0
  79. package/dist/lib/widgetStaticScan.d.ts +111 -0
  80. package/dist/lib/widgetStaticScan.js +833 -0
  81. package/dist/lib/workflow-node-control.d.ts +10 -0
  82. package/dist/lib/workflow-node-control.js +76 -0
  83. package/dist/lib/workspaces.d.ts +2 -0
  84. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.d.ts +9 -1
  85. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.js +12 -0
  86. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.d.ts +9 -1
  87. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.js +12 -0
  88. package/dist/node_modules/@skrr-ai/auth-core/package.json +1 -1
  89. package/dist/node_modules/@skrr-ai/data-provider/index.js +11924 -8704
  90. package/oclif.manifest.json +1487 -396
  91. package/package.json +11 -2
@@ -99,18 +99,32 @@ class FollowupsCheckin extends base_command_1.BaseCommand {
99
99
  this.handleApiError(err);
100
100
  return;
101
101
  }
102
+ const producer = agent.boundToSession ? 'agent_in_turn' : 'user_explicit';
103
+ // An explicit request from a person that arms nothing is a refusal, and a script reading the
104
+ // exit code must see it as one (OSK-15413). A check-in the AGENT chose to make is different:
105
+ // there, staying quiet when the budget is spent is the designed outcome.
106
+ if (response.dropped && producer === 'user_explicit') {
107
+ this.failWithCliError({
108
+ message: budgetRefusalMessage(agent.id, response.budget),
109
+ code: 'FOLLOWUP_BUDGET_EXHAUSTED',
110
+ exit: 1,
111
+ retryable: false,
112
+ details: { budget: response.budget ?? null, response },
113
+ });
114
+ return;
115
+ }
102
116
  if (flags.json) {
103
117
  this.log(JSON.stringify(response, null, 2));
104
118
  return;
105
119
  }
106
120
  const row = response.followup;
107
121
  if (response.reused) {
108
- this.log(`Already armed: ${row?.id} — one pending check-in per conversation, and this is it.`);
122
+ this.log(reusedMessage(row, subject, flags.why));
109
123
  return;
110
124
  }
111
125
  if (response.dropped) {
112
- // silent_drop is the check-in budget's contract: the answer to "one
113
- // greeting too many" is quiet, not an error the caller can retry.
126
+ // silent_drop is the check-in budget's contract for an agent's own check-in: the answer to
127
+ // "one greeting too many" is quiet, not an error the caller can retry.
114
128
  this.log('Dropped by the daily check-in budget — staying quiet is the correct outcome.');
115
129
  return;
116
130
  }
@@ -118,3 +132,32 @@ class FollowupsCheckin extends base_command_1.BaseCommand {
118
132
  }
119
133
  }
120
134
  exports.default = FollowupsCheckin;
135
+ /**
136
+ * What a reused answer means for the person who asked: a second check-in on a subject that already
137
+ * has one arms nothing and changes nothing, so the note and time they typed were NOT applied. Say
138
+ * so, show what is armed, and name the two ways forward (OSK-15411). Exit stays 0: the follow-up
139
+ * they wanted to exist does exist.
140
+ */
141
+ function reusedMessage(row, subject, requestedWhy) {
142
+ const lines = [
143
+ `Already armed: ${row?.id} — one pending check-in per subject (${subject.type}:${subject.externalKey}), and this is it.`,
144
+ 'Nothing in this request was applied: it armed no new check-in and changed neither the note nor the time.',
145
+ ];
146
+ if (row) {
147
+ const sameNote = row.why === requestedWhy;
148
+ lines.push(`Armed: ${row.why ? `"${row.why}"` : '(no note)'}, next ${row.nextRunAt ?? 'when its schedule says'}${sameNote ? ' (the same note you gave)' : ''}.`);
149
+ }
150
+ lines.push(`To change the time: skrr followups reschedule ${row?.id ?? '<id>'} --at <instant> or --in <offset>.`, "To change the note: cancel it and create it again (a cancelled check-in still counts against today's check-in budget).");
151
+ return lines.join('\n');
152
+ }
153
+ /** The refusal for an explicit check-in the daily budget will not take, with the number and the way to raise it. */
154
+ function budgetRefusalMessage(agentId, budget) {
155
+ const { used, limit } = (budget && typeof budget === 'object' ? budget : {});
156
+ const spent = typeof used === 'number' && typeof limit === 'number'
157
+ ? `${agentId} has used ${used} of its ${limit} check-ins today`
158
+ : `${agentId} has used its daily check-in budget`;
159
+ return [
160
+ `No check-in was armed: ${spent}. The day resets at 00:00 UTC, and a cancelled check-in still counts.`,
161
+ `Raise the limit with: skrr agents triggers policy ${agentId} --followup-budget checkin=<count>`,
162
+ ].join(' ');
163
+ }
@@ -104,7 +104,9 @@ class DedicatedMachinesExec extends base_command_1.BaseCommand {
104
104
  description: `How long to let it run, such as 30s or 5m (default 2m, max ${dedicated_machines_1.DEDICATED_EXEC_MAX_TIMEOUT_MS / 60000}m). The process group is killed at the deadline.`,
105
105
  }),
106
106
  'request-id': core_1.Flags.string({
107
- description: 'Idempotency key for this command. Repeating the invocation with the same id returns the run it already started (and its output, for 5 minutes after it ends) instead of running it again. Letters, digits and . _ : / - only.',
107
+ description: 'Idempotency key for this command. Repeating the invocation with the same id returns the run it already started instead of running it again. ' +
108
+ 'Output is kept for at most 5 minutes after it ends and at most 60 seconds after the first terminal read, whichever expires sooner. ' +
109
+ 'Letters, digits and . _ : / - only.',
108
110
  }),
109
111
  workspace: core_1.Flags.string({ description: 'Workspace scope' }),
110
112
  json: core_1.Flags.boolean({ description: 'Output the result as JSON' }),
@@ -1,5 +1,9 @@
1
1
  import { ProposalDecisionCommand } from '../../lib/proposal-decision-command';
2
2
  export default class ProposalsDiscard extends ProposalDecisionCommand {
3
3
  static description: string;
4
+ static args: {
5
+ kind: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
6
+ id: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
7
+ };
4
8
  static decision: "discard";
5
9
  }
@@ -3,6 +3,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const proposal_decision_command_1 = require("../../lib/proposal-decision-command");
4
4
  class ProposalsDiscard extends proposal_decision_command_1.ProposalDecisionCommand {
5
5
  static description = 'Discard one displayed proposal through its domain authority';
6
+ // Own, not inherited: oclif spreads the command class into the parser input, and a static a
7
+ // subclass only inherits is dropped, which left these commands with no declared KIND/ID (OSK-15346).
8
+ static args = proposal_decision_command_1.ProposalDecisionCommand.args;
6
9
  static decision = 'discard';
7
10
  }
8
11
  exports.default = ProposalsDiscard;
@@ -1,5 +1,9 @@
1
1
  import { ProposalDecisionCommand } from '../../lib/proposal-decision-command';
2
2
  export default class ProposalsKeep extends ProposalDecisionCommand {
3
3
  static description: string;
4
+ static args: {
5
+ kind: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
6
+ id: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
7
+ };
4
8
  static decision: "keep";
5
9
  }
@@ -3,6 +3,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const proposal_decision_command_1 = require("../../lib/proposal-decision-command");
4
4
  class ProposalsKeep extends proposal_decision_command_1.ProposalDecisionCommand {
5
5
  static description = 'Keep one displayed proposal through its domain authority';
6
+ // Own, not inherited: oclif spreads the command class into the parser input, and a static a
7
+ // subclass only inherits is dropped, which left these commands with no declared KIND/ID (OSK-15346).
8
+ static args = proposal_decision_command_1.ProposalDecisionCommand.args;
6
9
  static decision = 'keep';
7
10
  }
8
11
  exports.default = ProposalsKeep;
@@ -1,5 +1,9 @@
1
1
  import { ProposalDecisionCommand } from '../../lib/proposal-decision-command';
2
2
  export default class ProposalsSnooze extends ProposalDecisionCommand {
3
3
  static description: string;
4
+ static args: {
5
+ kind: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
6
+ id: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
7
+ };
4
8
  static decision: "snooze";
5
9
  }
@@ -3,6 +3,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const proposal_decision_command_1 = require("../../lib/proposal-decision-command");
4
4
  class ProposalsSnooze extends proposal_decision_command_1.ProposalDecisionCommand {
5
5
  static description = 'Snooze one displayed proposal through its domain authority';
6
+ // Own, not inherited: oclif spreads the command class into the parser input, and a static a
7
+ // subclass only inherits is dropped, which left these commands with no declared KIND/ID (OSK-15346).
8
+ static args = proposal_decision_command_1.ProposalDecisionCommand.args;
6
9
  static decision = 'snooze';
7
10
  }
8
11
  exports.default = ProposalsSnooze;
@@ -0,0 +1,16 @@
1
+ import { BaseCommand } from '../../../base-command';
2
+ export default class SpacesWorkflowsCancelNode extends BaseCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ static args: {
6
+ spaceId: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
7
+ runId: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
8
+ nodeKey: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
9
+ };
10
+ static flags: {
11
+ 'from-json': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
12
+ reason: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
13
+ json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
14
+ };
15
+ run(): Promise<void>;
16
+ }
@@ -0,0 +1,44 @@
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
+ const prompt_1 = require("../../../lib/prompt");
7
+ const workflow_node_control_1 = require("../../../lib/workflow-node-control");
8
+ class SpacesWorkflowsCancelNode extends base_command_1.BaseCommand {
9
+ static description = 'Request cancellation of one exact workflow node attempt and show the returned execution state';
10
+ static examples = [
11
+ '<%= config.bin %> spaces workflows run <space> <run> --json > reviewed-run.json',
12
+ '<%= config.bin %> spaces workflows cancel-node <space> <run> <node> --from-json reviewed-run.json',
13
+ ];
14
+ static args = {
15
+ spaceId: core_1.Args.string({ description: 'Owning Space ID', required: true, ignoreStdin: true }),
16
+ runId: core_1.Args.string({ description: 'Workflow run ID', required: true, ignoreStdin: true }),
17
+ nodeKey: core_1.Args.string({ description: 'Node key to stop', required: true, ignoreStdin: true }),
18
+ };
19
+ static flags = {
20
+ 'from-json': core_1.Flags.string({ description: 'Saved canonical workflow run snapshot, or - for stdin', required: true }),
21
+ reason: core_1.Flags.string({ description: 'Cancellation reason (at most 500 characters)' }),
22
+ json: core_1.Flags.boolean({ description: 'Output the updated workflow run as JSON' }),
23
+ };
24
+ async run() {
25
+ this.requireAuth();
26
+ const { args, flags } = await this.parse(SpacesWorkflowsCancelNode);
27
+ try {
28
+ const body = (0, workflow_node_control_1.nodeCancellationFromSnapshot)(args.spaceId, args.runId, args.nodeKey, await (0, prompt_1.readJsonBody)(flags['from-json']), flags.reason);
29
+ const run = await data_provider_1.dataService.cancelSpaceWorkflowNode(args.spaceId, args.runId, args.nodeKey, body);
30
+ if (flags.json) {
31
+ this.log(JSON.stringify(run, null, 2));
32
+ return;
33
+ }
34
+ const node = run.nodes?.find((item) => item.nodeKey === args.nodeKey);
35
+ this.log(`Cancellation for ${args.nodeKey}: ${node?.cancellation?.state ?? 'unknown'}.`);
36
+ this.log(`Returned node state: ${node?.status ?? 'unknown'}.`);
37
+ this.log(`Workflow run ${run.id}: ${run.status}.`);
38
+ }
39
+ catch (error) {
40
+ this.handleApiError(error);
41
+ }
42
+ }
43
+ }
44
+ exports.default = SpacesWorkflowsCancelNode;
@@ -1,4 +1,43 @@
1
1
  import { BaseCommand } from '../../../base-command';
2
+ /**
3
+ * Child workflow runs a cancelled run was waiting on. A cancelled node keeps its `waitingFor`,
4
+ * and every node kind that starts a child run (`workflow_call` = subworkflow, `loop` iteration,
5
+ * `task_plan`) records that run as `waitingFor.workflowRunId`. Reading the id rather than the
6
+ * kind means a new child-starting node kind is not silently missed.
7
+ */
8
+ export declare function childWorkflowRunIds(run: {
9
+ id?: string;
10
+ nodes?: Array<{
11
+ waitingFor?: unknown;
12
+ }>;
13
+ }): string[];
14
+ /**
15
+ * Lines for a cancel that has not finished converging. `cancellation.state: settled` with
16
+ * nothing pending is the only converged answer; anything else must not read as "stopped".
17
+ */
18
+ export declare function describeCancellationProgress(spaceId: string, run: {
19
+ id: string;
20
+ cancellation?: {
21
+ state?: string;
22
+ descendantsPending?: boolean;
23
+ executorsPending?: number;
24
+ };
25
+ }): string[];
26
+ /**
27
+ * The lines that tell a person which child runs a `preserve_children` cancel left running.
28
+ * "Cancelled" next to a child that is still waiting forever is the part nobody could see
29
+ * (OSK-15331), so say it and say how to stop them. Re-cancelling the parent with
30
+ * `--scope subtree` widens the stop to every descendant (the server persists the wider
31
+ * decision before touching any descendant). An unreadable child is reported as unreadable,
32
+ * never as stopped.
33
+ */
34
+ export declare function describePreservedChildren(spaceId: string, parentRunId: string, children: Array<{
35
+ id: string;
36
+ status?: string;
37
+ unreadable?: boolean;
38
+ }>, { reviewedScope }?: {
39
+ reviewedScope?: boolean;
40
+ }): string[];
2
41
  export default class SpacesWorkflowsCancel extends BaseCommand {
3
42
  static description: string;
4
43
  static args: {
@@ -7,6 +46,7 @@ export default class SpacesWorkflowsCancel extends BaseCommand {
7
46
  };
8
47
  static flags: {
9
48
  json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
49
+ scope: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
10
50
  yes: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
11
51
  };
12
52
  run(): Promise<void>;
@@ -1,10 +1,84 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.childWorkflowRunIds = childWorkflowRunIds;
4
+ exports.describeCancellationProgress = describeCancellationProgress;
5
+ exports.describePreservedChildren = describePreservedChildren;
3
6
  const core_1 = require("@oclif/core");
4
7
  const data_provider_1 = require("@skrr-ai/data-provider");
5
8
  const base_command_1 = require("../../../base-command");
6
9
  const prompt_1 = require("../../../lib/prompt");
7
10
  const space_output_1 = require("../../../lib/space-output");
11
+ const CANCELLATION_SCOPES = [
12
+ 'preserve_children',
13
+ 'subtree',
14
+ ];
15
+ /**
16
+ * Child workflow runs a cancelled run was waiting on. A cancelled node keeps its `waitingFor`,
17
+ * and every node kind that starts a child run (`workflow_call` = subworkflow, `loop` iteration,
18
+ * `task_plan`) records that run as `waitingFor.workflowRunId`. Reading the id rather than the
19
+ * kind means a new child-starting node kind is not silently missed.
20
+ */
21
+ function childWorkflowRunIds(run) {
22
+ const ids = new Set();
23
+ for (const node of run.nodes ?? []) {
24
+ const waitingFor = node.waitingFor;
25
+ const childId = waitingFor?.workflowRunId;
26
+ if (typeof childId === 'string' && childId && childId !== run.id)
27
+ ids.add(childId);
28
+ }
29
+ return [...ids];
30
+ }
31
+ /**
32
+ * Lines for a cancel that has not finished converging. `cancellation.state: settled` with
33
+ * nothing pending is the only converged answer; anything else must not read as "stopped".
34
+ */
35
+ function describeCancellationProgress(spaceId, run) {
36
+ const c = run.cancellation;
37
+ if (!c)
38
+ return [];
39
+ const pending = c.state !== 'settled' || c.descendantsPending || Number(c.executorsPending) > 0;
40
+ if (!pending)
41
+ return [];
42
+ const detail = `state ${c.state ?? 'unknown'}, descendant runs pending: ${c.descendantsPending ? 'yes' : 'no'}, executors pending: ${Number(c.executorsPending) || 0}`;
43
+ // `failed` means the cleanup gave up: reading again never converges it, only a reconcile retries.
44
+ if (c.state === 'failed') {
45
+ return [
46
+ `Cancellation cleanup gave up (${detail}).`,
47
+ ` Retry it with: skrr spaces workflows reconcile ${spaceId} ${run.id}`,
48
+ ];
49
+ }
50
+ return [
51
+ `Cancellation is still converging (${detail}).`,
52
+ ` Re-read it with: skrr spaces workflows run ${spaceId} ${run.id}`,
53
+ ];
54
+ }
55
+ /**
56
+ * The lines that tell a person which child runs a `preserve_children` cancel left running.
57
+ * "Cancelled" next to a child that is still waiting forever is the part nobody could see
58
+ * (OSK-15331), so say it and say how to stop them. Re-cancelling the parent with
59
+ * `--scope subtree` widens the stop to every descendant (the server persists the wider
60
+ * decision before touching any descendant). An unreadable child is reported as unreadable,
61
+ * never as stopped.
62
+ */
63
+ function describePreservedChildren(spaceId, parentRunId, children, { reviewedScope = false } = {}) {
64
+ const live = children.filter((child) => child.unreadable || (0, data_provider_1.isActiveWorkflowRunStatus)(child.status));
65
+ if (live.length === 0)
66
+ return [];
67
+ const lines = [
68
+ `Left running (scope preserve_children): ${live.length} child workflow run${live.length === 1 ? '' : 's'} started by this run.`,
69
+ ];
70
+ for (const child of live) {
71
+ lines.push(` ${child.id} (${child.unreadable ? 'status unreadable' : child.status})`);
72
+ }
73
+ if (reviewedScope) {
74
+ // A stop under a reviewed scope is frozen to that review, so cancelling the parent again
75
+ // does not widen it; each child is stopped on its own.
76
+ lines.push(`This run was stopped under a reviewed scope, so cancelling it again will not widen the stop.`, `Stop a child with: skrr spaces workflows cancel ${spaceId} <child-run-id> --scope subtree --yes`);
77
+ return lines;
78
+ }
79
+ lines.push(`Stop them now: skrr spaces workflows cancel ${spaceId} ${parentRunId} --scope subtree --yes`, ` (widens this cancel to every descendant run), or stop one: skrr spaces workflows cancel ${spaceId} <child-run-id> --scope subtree --yes`);
80
+ return lines;
81
+ }
8
82
  class SpacesWorkflowsCancel extends base_command_1.BaseCommand {
9
83
  static description = 'Cancel a workflow run and stop further graph coordination';
10
84
  static args = {
@@ -12,15 +86,25 @@ class SpacesWorkflowsCancel extends base_command_1.BaseCommand {
12
86
  runId: core_1.Args.string({ description: 'Workflow run ID', required: true, ignoreStdin: true }),
13
87
  };
14
88
  static flags = {
15
- json: core_1.Flags.boolean({ description: 'Output the cancelled run as JSON' }),
89
+ json: core_1.Flags.boolean({
90
+ description: 'Output the cancelled run as JSON (cancellation.scope and the waitingFor.workflowRunId of the cancelled nodes show which child runs were left running)',
91
+ }),
92
+ scope: core_1.Flags.string({
93
+ description: 'What happens to child workflow runs this run started: preserve_children (default) leaves them running, subtree stops every descendant run too',
94
+ options: [...CANCELLATION_SCOPES],
95
+ }),
16
96
  yes: core_1.Flags.boolean({ char: 'y', description: 'Skip confirmation prompt' }),
17
97
  };
18
98
  async run() {
19
99
  this.requireAuth();
20
100
  const { args, flags } = await this.parse(SpacesWorkflowsCancel);
101
+ const scope = flags.scope;
21
102
  let confirmed;
22
103
  try {
23
- confirmed = await (0, prompt_1.confirmDestructive)(`Cancel workflow run ${args.runId}? This stops workflow coordination; already-dispatched task execution may continue.`, flags.yes);
104
+ const scopeNote = scope === 'subtree'
105
+ ? ' Child workflow runs it started are cancelled too.'
106
+ : ' Child workflow runs it started keep running (--scope subtree stops them too).';
107
+ confirmed = await (0, prompt_1.confirmDestructive)(`Cancel workflow run ${args.runId}? This stops workflow coordination; already-dispatched task execution may continue.${scopeNote}`, flags.yes);
24
108
  }
25
109
  catch (error) {
26
110
  this.error(error.message, {
@@ -34,7 +118,7 @@ class SpacesWorkflowsCancel extends base_command_1.BaseCommand {
34
118
  }
35
119
  let run;
36
120
  try {
37
- run = await data_provider_1.dataService.cancelSpaceWorkflowRun(args.spaceId, args.runId);
121
+ run = await data_provider_1.dataService.cancelSpaceWorkflowRun(args.spaceId, args.runId, scope ? { scope } : {});
38
122
  }
39
123
  catch (error) {
40
124
  this.handleApiError(error);
@@ -45,6 +129,28 @@ class SpacesWorkflowsCancel extends base_command_1.BaseCommand {
45
129
  return;
46
130
  }
47
131
  this.log(`Cancelled workflow run ${run.id} (${run.status}).`);
132
+ for (const line of describeCancellationProgress(args.spaceId, run))
133
+ this.log(line);
134
+ if ((run.cancellation?.scope ?? run.cancellationScope ?? scope) === 'subtree')
135
+ return;
136
+ const childIds = childWorkflowRunIds(run);
137
+ if (childIds.length === 0)
138
+ return;
139
+ const children = await Promise.all(childIds.map(async (id) => {
140
+ try {
141
+ const child = await data_provider_1.dataService.getSpaceWorkflowRun(args.spaceId, id);
142
+ return { id, status: child.status };
143
+ }
144
+ catch {
145
+ return { id, unreadable: true };
146
+ }
147
+ }));
148
+ const reviewedScope = Boolean(run.cancellationExpectedScope);
149
+ for (const line of describePreservedChildren(args.spaceId, run.id, children, {
150
+ reviewedScope,
151
+ })) {
152
+ this.log(line);
153
+ }
48
154
  }
49
155
  }
50
156
  exports.default = SpacesWorkflowsCancel;
@@ -16,6 +16,15 @@ export declare function describeNodeAttention(node: {
16
16
  message?: string;
17
17
  } | null;
18
18
  }): string;
19
+ export declare function describeLoopItemsNote(node: {
20
+ nodeKey?: unknown;
21
+ nodeType?: unknown;
22
+ output?: {
23
+ itemsStatus?: unknown;
24
+ itemsKey?: unknown;
25
+ itemsFoundType?: unknown;
26
+ } | null;
27
+ }): string;
19
28
  export default class SpacesWorkflowsRun extends BaseCommand {
20
29
  static description: string;
21
30
  static args: {
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.describeNodeAttention = describeNodeAttention;
4
+ exports.describeLoopItemsNote = describeLoopItemsNote;
4
5
  const core_1 = require("@oclif/core");
5
6
  const data_provider_1 = require("@skrr-ai/data-provider");
6
7
  const base_command_1 = require("../../../base-command");
@@ -26,6 +27,38 @@ function describeNodeAttention(node) {
26
27
  return `${reason}: ${message}`;
27
28
  return reason || message || 'needs attention';
28
29
  }
30
+ /**
31
+ * A note for a loop that succeeded having iterated nothing because its
32
+ * collection never arrived, or '' when there is nothing to say (OSK-15324).
33
+ *
34
+ * A zero-iteration loop is `succeeded`, so the plain table reads as healthy
35
+ * whether the list was legitimately empty or the value the author meant never
36
+ * reached the loop. The server records which on the node output; say it. An
37
+ * empty list is the legitimate case and stays silent.
38
+ */
39
+ /** One printable line: control characters (an ESC sequence included) become spaces. */
40
+ function oneLine(value) {
41
+ // eslint-disable-next-line no-control-regex
42
+ return (0, format_1.collapseWhitespace)(value.replace(/[\u0000-\u001f\u007f-\u009f]/g, ' '));
43
+ }
44
+ function describeLoopItemsNote(node) {
45
+ if (node.nodeType !== 'loop')
46
+ return '';
47
+ const output = node.output;
48
+ // `itemsKey` is author-chosen free text, unlike a node key, so it is printed the way the
49
+ // table prints a cell: one line, no control characters.
50
+ const key = oneLine(typeof output?.itemsKey === 'string' && output.itemsKey ? output.itemsKey : 'items');
51
+ const name = oneLine(String(node.nodeKey ?? 'loop'));
52
+ if (output?.itemsStatus === 'missing') {
53
+ return `${name}: ran 0 iterations because its input "${key}" was missing or null`;
54
+ }
55
+ if (output?.itemsStatus === 'not_array') {
56
+ const found = typeof output.itemsFoundType === 'string' ? output.itemsFoundType : 'a value';
57
+ const article = /^[aeiou]/i.test(found) ? 'an' : 'a';
58
+ return `${name}: ran 0 iterations because its input "${key}" was ${article} ${oneLine(found)}, not a list`;
59
+ }
60
+ return '';
61
+ }
29
62
  class SpacesWorkflowsRun extends base_command_1.BaseCommand {
30
63
  static description = 'Inspect a workflow run and each durable node state';
31
64
  static args = {
@@ -84,6 +117,11 @@ class SpacesWorkflowsRun extends base_command_1.BaseCommand {
84
117
  ? [{ key: 'attention', header: 'ATTENTION' }]
85
118
  : []),
86
119
  ], (line) => this.log(line));
120
+ for (const node of nodes) {
121
+ const note = describeLoopItemsNote(node);
122
+ if (note)
123
+ this.log(`Note: ${note}`);
124
+ }
87
125
  }
88
126
  }
89
127
  }
@@ -1,4 +1,16 @@
1
+ import { type TaskApprovalResolveParams } from '@skrr-ai/data-provider';
1
2
  import { BaseCommand } from '../../../base-command';
3
+ /**
4
+ * The request body for resolving an approval. The server's field is `comment`
5
+ * (`TaskApprovalResolveParams`); `--reason` is only the flag's name. It was sent
6
+ * as `reason` through an `as unknown as` cast, so the type checker could not see
7
+ * the mismatch and the server's non-strict schema dropped the key: the command
8
+ * succeeded and the reason was stored nowhere (OSK-15362).
9
+ */
10
+ export declare function approvalResolutionBody(flags: {
11
+ status: 'approved' | 'rejected';
12
+ reason?: string;
13
+ }): TaskApprovalResolveParams;
2
14
  export default class TasksApprovalsResolve extends BaseCommand {
3
15
  static description: string;
4
16
  static examples: string[];
@@ -1,8 +1,22 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.approvalResolutionBody = approvalResolutionBody;
3
4
  const core_1 = require("@oclif/core");
4
5
  const data_provider_1 = require("@skrr-ai/data-provider");
5
6
  const base_command_1 = require("../../../base-command");
7
+ /**
8
+ * The request body for resolving an approval. The server's field is `comment`
9
+ * (`TaskApprovalResolveParams`); `--reason` is only the flag's name. It was sent
10
+ * as `reason` through an `as unknown as` cast, so the type checker could not see
11
+ * the mismatch and the server's non-strict schema dropped the key: the command
12
+ * succeeded and the reason was stored nowhere (OSK-15362).
13
+ */
14
+ function approvalResolutionBody(flags) {
15
+ const body = { status: flags.status };
16
+ if (flags.reason)
17
+ body.comment = flags.reason;
18
+ return body;
19
+ }
6
20
  class TasksApprovalsResolve extends base_command_1.BaseCommand {
7
21
  static description = 'Resolve (approve or reject) a task approval';
8
22
  static examples = [
@@ -27,15 +41,15 @@ class TasksApprovalsResolve extends base_command_1.BaseCommand {
27
41
  this.requireAuth();
28
42
  const { args, flags } = await this.parse(TasksApprovalsResolve);
29
43
  const taskRef = await this.resolveEntityRef('task', args.id);
30
- const data = { status: flags.status };
31
- if (flags.reason)
32
- data.reason = flags.reason;
33
44
  let result;
34
45
  try {
35
46
  result = await data_provider_1.dataService.resolveTaskApproval({
36
47
  taskId: taskRef,
37
48
  approvalId: args.approvalId,
38
- data: data,
49
+ data: approvalResolutionBody({
50
+ status: flags.status,
51
+ reason: flags.reason,
52
+ }),
39
53
  });
40
54
  }
41
55
  catch (err) {
@@ -46,7 +60,9 @@ class TasksApprovalsResolve extends base_command_1.BaseCommand {
46
60
  return;
47
61
  }
48
62
  const a = result;
49
- this.log(`Approval ${a.id ?? a._id ?? args.approvalId} → ${flags.status}`);
63
+ // Say what was recorded, so a reason that was not stored cannot pass unseen.
64
+ const recorded = typeof a.comment === 'string' && a.comment ? ` (reason: ${a.comment})` : '';
65
+ this.log(`Approval ${a.id ?? a._id ?? args.approvalId} → ${flags.status}${recorded}`);
50
66
  }
51
67
  }
52
68
  exports.default = TasksApprovalsResolve;
@@ -28,7 +28,7 @@ class TasksSchedule extends base_command_1.BaseCommand {
28
28
  dependsOn: ['cron'],
29
29
  }),
30
30
  'max-runs': core_1.Flags.integer({
31
- description: 'Cap total fires (recurring only)',
31
+ description: 'Cap total occurrences (recurring only): each scheduled fire counts, and so does an occurrence you complete by hand before the scheduler touches it',
32
32
  dependsOn: ['cron'],
33
33
  }),
34
34
  'end-date': core_1.Flags.string({
@@ -37,6 +37,13 @@ class TriggersCapabilities extends base_command_1.BaseCommand {
37
37
  ? capabilities.templateVariables.map((v) => typeof v === 'object' && v !== null ? String(v.name) : String(v))
38
38
  : capabilities.templateVariableNames;
39
39
  this.log(`Template vars: ${Array.isArray(vars) ? vars.join(', ') : '-'}`);
40
+ // Some scopes cannot create a trigger until something else is set up, and the refusal
41
+ // otherwise arrives only after submitting.
42
+ const needs = capabilities.prerequisites;
43
+ if (Array.isArray(needs)) {
44
+ for (const need of needs)
45
+ this.log(`Needs: ${String(need)}`);
46
+ }
40
47
  // Beside the modes, because `external_work_task` is a mode that cannot be
41
48
  // authored without one of these.
42
49
  const contracts = capabilities.effectContracts;
@@ -20,7 +20,10 @@ class TriggersCreate extends base_command_1.BaseCommand {
20
20
  'from-json': core_1.Flags.string({
21
21
  description: 'Path to a JSON file (or "-" for stdin) to use as the request body',
22
22
  }),
23
- scope: core_1.Flags.string({ description: 'Trigger scope kind', options: [...triggers_1.TRIGGER_SCOPE_KINDS] }),
23
+ scope: core_1.Flags.string({
24
+ description: 'Trigger scope kind. A scope can have prerequisites (a space_oracle Space needs a Leader agent): `triggers capabilities <scope>` lists them under "Needs"',
25
+ options: [...triggers_1.TRIGGER_SCOPE_KINDS],
26
+ }),
24
27
  ref: core_1.Flags.string({
25
28
  description: 'Scope ref as key=value (repeatable), for example --ref agentId=abc',
26
29
  multiple: true,
@@ -88,7 +91,9 @@ class TriggersCreate extends base_command_1.BaseCommand {
88
91
  multiple: true,
89
92
  }),
90
93
  timezone: core_1.Flags.string({ description: 'IANA timezone for schedule triggers' }),
91
- 'max-fires-per-day': core_1.Flags.integer({ description: 'Daily fire cap' }),
94
+ 'max-fires-per-day': core_1.Flags.integer({
95
+ description: 'Daily fire cap (schedule, stochastic and self_scheduled triggers only)',
96
+ }),
92
97
  'max-runs': core_1.Flags.integer({ description: 'Lifetime fire cap for schedule triggers' }),
93
98
  'end-at': core_1.Flags.string({
94
99
  description: 'ISO-8601 datetime after which a schedule stops firing',
@@ -189,6 +194,9 @@ class TriggersCreate extends base_command_1.BaseCommand {
189
194
  let payload;
190
195
  try {
191
196
  payload = await (0, prompt_1.readJsonBody)(flags['from-json']);
197
+ // A flag for a kind other than --kind is refused, not dropped: the block it fills is not
198
+ // sent, so the caller would believe a limit was set that nothing holds (OSK-15491).
199
+ (0, triggers_1.assertKindFlagsApply)(flags.kind ?? payload.kind, flags);
192
200
  const refs = (0, triggers_1.parseKeyValueFlags)(flags.ref, '--ref', flags.scope ?? payload.scope?.kind);
193
201
  if (flags.scope || Object.keys(refs).length > 0) {
194
202
  payload.scope = {
@@ -264,22 +272,8 @@ class TriggersCreate extends base_command_1.BaseCommand {
264
272
  filterField: flags['filter-field'],
265
273
  filterValues,
266
274
  });
267
- (0, triggers_1.setDefined)(event, {
268
- entityType: flags.entity,
269
- eventType: flags.event,
270
- filterMode: flags['filter-mode'],
271
- filterField: flags['filter-field'],
272
- filterValue: filterValues && filterValues.length > 1 ? filterValues : filterValues?.[0],
273
- debounceMs: flags['debounce-ms'],
274
- maxPerHour: flags['max-per-hour'],
275
- });
276
- const stochastic = {};
277
- (0, triggers_1.setDefined)(stochastic, {
278
- probability: flags.probability === undefined ? undefined : Number(flags.probability),
279
- jitterMs: flags['jitter-ms'],
280
- maxFiresPerDay: flags['max-fires-per-day'],
281
- evalIntervalMinutes: flags['eval-interval-minutes'],
282
- });
275
+ Object.assign(event, (0, triggers_1.buildEventConfigFromFlags)(flags, filterValues));
276
+ const stochastic = (0, triggers_1.buildStochasticConfigFromFlags)(flags);
283
277
  const selfScheduled = (0, triggers_1.buildSelfScheduledConfigFromFlags)(flags);
284
278
  const form = (0, triggers_1.buildFormConfigFromFlags)(flags);
285
279
  const chatMessage = (0, triggers_1.buildChatMessageConfigFromFlags)(flags);