@skrr-ai/cli 0.1.19 → 0.1.20
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.
- package/dist/base-command.d.ts +12 -9
- package/dist/base-command.js +27 -26
- package/dist/commands/commitments/action-proposals/propose.d.ts +32 -0
- package/dist/commands/commitments/action-proposals/propose.js +111 -0
- package/dist/commands/commitments/create.d.ts +2 -0
- package/dist/commands/commitments/create.js +42 -2
- package/dist/commands/commitments/doctor.js +11 -0
- package/dist/commands/commitments/update.d.ts +2 -0
- package/dist/commands/commitments/update.js +40 -5
- package/dist/commands/store/browse.d.ts +14 -0
- package/dist/commands/store/browse.js +71 -0
- package/dist/commands/store/install.d.ts +16 -0
- package/dist/commands/store/install.js +54 -0
- package/dist/commands/store/releases.d.ts +19 -0
- package/dist/commands/store/releases.js +60 -0
- package/dist/commands/store/update.d.ts +15 -0
- package/dist/commands/store/update.js +69 -0
- package/dist/commands/store/updates.d.ts +24 -0
- package/dist/commands/store/updates.js +113 -0
- package/dist/commands/subscriptions/cancel.d.ts +14 -0
- package/dist/commands/subscriptions/cancel.js +47 -0
- package/dist/commands/subscriptions/health.d.ts +24 -0
- package/dist/commands/subscriptions/health.js +62 -0
- package/dist/commands/subscriptions/list.d.ts +9 -0
- package/dist/commands/subscriptions/list.js +52 -0
- package/dist/commands/subscriptions/status.d.ts +12 -0
- package/dist/commands/subscriptions/status.js +49 -0
- package/dist/commands/subscriptions/subscribe.d.ts +15 -0
- package/dist/commands/subscriptions/subscribe.js +59 -0
- package/dist/commands/tasks/complete.d.ts +10 -4
- package/dist/commands/tasks/complete.js +7 -5
- package/dist/commands/tasks/self-schedule.d.ts +30 -0
- package/dist/commands/tasks/self-schedule.js +114 -0
- package/dist/commands/triggers/disable.js +3 -1
- package/dist/commands/triggers/enable.js +3 -1
- package/dist/commands/triggers/rotate-secret.js +5 -0
- package/dist/commands/triggers/show.js +3 -1
- package/dist/lib/commitments.d.ts +17 -0
- package/dist/lib/commitments.js +59 -0
- package/dist/lib/node-adapter.js +4 -0
- package/dist/lib/task-extras.d.ts +18 -0
- package/dist/lib/task-extras.js +19 -1
- package/dist/lib/triggers.d.ts +21 -2
- package/dist/lib/triggers.js +48 -1
- package/dist/node_modules/@skrr-ai/data-provider/index.js +4063 -3902
- package/oclif.manifest.json +4038 -2976
- package/package.json +1 -1
|
@@ -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
|
-
|
|
14
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
638
|
-
|
|
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;
|
|
@@ -260,6 +260,16 @@ export declare const commitmentApi: {
|
|
|
260
260
|
object: "commitment_action_proposal_list";
|
|
261
261
|
data: CommitmentActionProposal[];
|
|
262
262
|
}>;
|
|
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>>;
|
|
263
273
|
decideActionProposal: (id: string, proposalId: string, decision: "approve" | "reject") => Promise<Record<string, unknown>>;
|
|
264
274
|
executeActionProposal: (id: string, proposalId: string) => Promise<Record<string, unknown>>;
|
|
265
275
|
preflight: (id: string, nextRunCount?: number) => Promise<CommitmentPreflightReport>;
|
|
@@ -410,6 +420,13 @@ export declare function readCommitmentAgentDirective({ directive, file, }: {
|
|
|
410
420
|
* active or terminal state, but importing it must still create a draft.
|
|
411
421
|
*/
|
|
412
422
|
export declare function normalizeCreateBody(input: Record<string, unknown>): Record<string, unknown>;
|
|
423
|
+
export declare function parseCommitmentConcurrencyLimit(input: string | undefined): number | null | undefined;
|
|
424
|
+
export declare function commitmentConcurrencyEnvelopePatch({ rawValue, reason, autonomous, }: {
|
|
425
|
+
rawValue?: string;
|
|
426
|
+
reason?: string;
|
|
427
|
+
autonomous?: boolean;
|
|
428
|
+
}): Record<string, unknown> | undefined;
|
|
429
|
+
export declare function describeCommitmentConcurrency(commitment: CommitmentView): string;
|
|
413
430
|
export declare function parseIntervalToMs(input: string): number;
|
|
414
431
|
/**
|
|
415
432
|
* Assemble the `watchedSources` array from CLI flags — one place, so `create`
|
package/dist/lib/commitments.js
CHANGED
|
@@ -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,6 +156,16 @@ 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)),
|
|
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),
|
|
156
169
|
decideActionProposal: (id, proposalId, decision) => data_provider_1.request.post(`${base(id)}/action-proposals/${encodeURIComponent(proposalId)}/decision`, {
|
|
157
170
|
decision,
|
|
158
171
|
}),
|
|
@@ -321,6 +334,51 @@ function normalizeCreateBody(input) {
|
|
|
321
334
|
}
|
|
322
335
|
return out;
|
|
323
336
|
}
|
|
337
|
+
function parseCommitmentConcurrencyLimit(input) {
|
|
338
|
+
if (input === undefined)
|
|
339
|
+
return undefined;
|
|
340
|
+
const raw = String(input).trim().toLowerCase();
|
|
341
|
+
if (['unlimited', 'unbounded', 'none', 'null'].includes(raw))
|
|
342
|
+
return null;
|
|
343
|
+
const value = Number(raw);
|
|
344
|
+
if (!Number.isSafeInteger(value) || value < 1 || value > 100) {
|
|
345
|
+
throw new Error('--max-concurrent-runs must be "unlimited" or an integer from 1 to 100.');
|
|
346
|
+
}
|
|
347
|
+
return value;
|
|
348
|
+
}
|
|
349
|
+
function commitmentConcurrencyEnvelopePatch({ rawValue, reason, autonomous = process.env.OVERSKY_AUTONOMOUS_SESSION === '1', }) {
|
|
350
|
+
const value = parseCommitmentConcurrencyLimit(rawValue);
|
|
351
|
+
const normalizedReason = reason?.trim();
|
|
352
|
+
if (value === undefined) {
|
|
353
|
+
if (normalizedReason) {
|
|
354
|
+
throw new Error('--concurrency-reason requires --max-concurrent-runs.');
|
|
355
|
+
}
|
|
356
|
+
return undefined;
|
|
357
|
+
}
|
|
358
|
+
if (autonomous && value !== null) {
|
|
359
|
+
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.');
|
|
360
|
+
}
|
|
361
|
+
if (value !== null && !normalizedReason) {
|
|
362
|
+
throw new Error('A finite --max-concurrent-runs value requires --concurrency-reason.');
|
|
363
|
+
}
|
|
364
|
+
return {
|
|
365
|
+
maxConcurrentRuns: value,
|
|
366
|
+
...(normalizedReason ? { maxConcurrentRunsReason: normalizedReason } : {}),
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
function describeCommitmentConcurrency(commitment) {
|
|
370
|
+
const envelope = commitment.policy?.autonomyEnvelope;
|
|
371
|
+
const limit = envelope?.maxConcurrentRuns;
|
|
372
|
+
const decision = envelope?.maxConcurrentRunsDecision;
|
|
373
|
+
if (limit == null) {
|
|
374
|
+
const source = decision?.mode === 'unlimited' ? decision.source : 'default';
|
|
375
|
+
return `unlimited (${source})`;
|
|
376
|
+
}
|
|
377
|
+
const authority = decision?.authority || 'unknown';
|
|
378
|
+
const source = decision?.source || 'legacy';
|
|
379
|
+
const review = authority === 'unknown' ? ' — review required' : '';
|
|
380
|
+
return `${limit} (${authority}/${source}${review})`;
|
|
381
|
+
}
|
|
324
382
|
/**
|
|
325
383
|
* Parse a human interval ("15m", "6h", "1d", or a bare number of minutes) into
|
|
326
384
|
* milliseconds. Throws on unparseable input, on trailing junk, and on values
|
|
@@ -672,6 +730,7 @@ latestCheck) {
|
|
|
672
730
|
log(`Next check:${` ${commitment.cadence.nextCheckAt}`}`);
|
|
673
731
|
log(`Mode: ${describeAutonomyLine(commitment, latestCheck)}`);
|
|
674
732
|
log(`Delivery: ${(0, commitment_product_1.deliveryLabel)(commitment.delivery?.mode)}${commitment.delivery?.repo ? ` · ${commitment.delivery.repo}` : ''}${commitment.delivery?.baseBranch ? ` → ${commitment.delivery.baseBranch}` : ''}`);
|
|
733
|
+
log(`Concurrency: ${describeCommitmentConcurrency(commitment)}`);
|
|
675
734
|
const criteria = commitment.target?.successCriteria || [];
|
|
676
735
|
const evidenceHealth = latestCheck?.inputs?.evidenceHealth;
|
|
677
736
|
const evidenceLine = describeEvidenceHealth(evidenceHealth, criteria.length);
|
package/dist/lib/node-adapter.js
CHANGED
|
@@ -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
|
*
|
package/dist/lib/task-extras.js
CHANGED
|
@@ -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
|
*
|
package/dist/lib/triggers.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ export declare const TRIGGER_SCOPE_KINDS: readonly ["agent", "group_chat_subscri
|
|
|
18
18
|
* capability was live and unreachable. `watch_until` and `sensor_poll` had been
|
|
19
19
|
* in that state for longer.
|
|
20
20
|
*/
|
|
21
|
-
export declare const TRIGGER_EXECUTOR_MODES: readonly ["send_message", "run_only", "enqueue_group_chat_agent_turn", "enqueue_assistant_turn", "enqueue_space_assistant_turn", "proactive_orchestrator_check", "auto_dream_orchestrator", "task_schedule_fire", "goal_planner_tick", "morning_briefing", "builtin_action", "watch_until", "sensor_poll", "external_work_task", "notify_user", "space_cycle_rollover"];
|
|
21
|
+
export declare const TRIGGER_EXECUTOR_MODES: readonly ["send_message", "run_only", "enqueue_group_chat_agent_turn", "enqueue_assistant_turn", "enqueue_space_assistant_turn", "proactive_orchestrator_check", "auto_dream_orchestrator", "task_schedule_fire", "goal_planner_tick", "morning_briefing", "builtin_action", "task_execute", "watch_until", "sensor_poll", "external_work_task", "notify_user", "space_cycle_rollover"];
|
|
22
22
|
export declare const TRIGGER_EVENT_FILTER_MODES: readonly ["equals", "contains", "in_set"];
|
|
23
23
|
export declare const TRIGGER_CONCURRENCY_POLICIES: readonly ["coalesce_if_active", "allow_parallel"];
|
|
24
24
|
export declare const TRIGGER_FORM_RESPONSE_MODES: readonly ["thank_you_page", "redirect_url", "run_complete"];
|
|
@@ -95,6 +95,16 @@ export interface TriggerDefinition {
|
|
|
95
95
|
chatMessage?: Record<string, unknown>;
|
|
96
96
|
audit?: Record<string, unknown>;
|
|
97
97
|
webhook?: Record<string, unknown>;
|
|
98
|
+
/**
|
|
99
|
+
* Server-DERIVED: maintained by a managed producer, so the mutation routes
|
|
100
|
+
* refuse to edit or remove it. Optional because a server predating the field
|
|
101
|
+
* omits it, which correctly reads as "not producer-owned".
|
|
102
|
+
*
|
|
103
|
+
* Declared here rather than inferred, and never re-derived from an id prefix
|
|
104
|
+
* or an execution mode — the route owns that policy and a local copy is free
|
|
105
|
+
* to disagree with the route that does the refusing.
|
|
106
|
+
*/
|
|
107
|
+
producerOwned?: boolean;
|
|
98
108
|
createdAt?: string;
|
|
99
109
|
updatedAt?: string;
|
|
100
110
|
}
|
|
@@ -327,7 +337,16 @@ export declare function armedStateLabel(enabled: boolean | undefined): string;
|
|
|
327
337
|
* parentheses for scripts.
|
|
328
338
|
*/
|
|
329
339
|
export declare function writeModeLabel(writeMode: string): string;
|
|
330
|
-
|
|
340
|
+
/**
|
|
341
|
+
* Where an external caller POSTs to fire this trigger.
|
|
342
|
+
*
|
|
343
|
+
* Same origin as every other `/api/...` call the CLI makes, so it is built from
|
|
344
|
+
* the configured base URL rather than guessed.
|
|
345
|
+
*/
|
|
346
|
+
export declare function webhookIngestUrl(baseURL: string, triggerId: string): string;
|
|
347
|
+
export declare function renderTriggerSummary(trigger: TriggerDefinition, log: (line: string) => void, options?: {
|
|
348
|
+
baseURL?: string;
|
|
349
|
+
}): void;
|
|
331
350
|
export declare function renderLegacyRuns(runs: LegacyTriggerRun[], log: (line: string) => void): void;
|
|
332
351
|
export declare function renderFireAudit(fires: TriggerFireRecord[], log: (line: string) => void): void;
|
|
333
352
|
/**
|
package/dist/lib/triggers.js
CHANGED
|
@@ -32,6 +32,7 @@ exports.renderTriggerList = renderTriggerList;
|
|
|
32
32
|
exports.formatDuplicateSubscriptionWarning = formatDuplicateSubscriptionWarning;
|
|
33
33
|
exports.armedStateLabel = armedStateLabel;
|
|
34
34
|
exports.writeModeLabel = writeModeLabel;
|
|
35
|
+
exports.webhookIngestUrl = webhookIngestUrl;
|
|
35
36
|
exports.renderTriggerSummary = renderTriggerSummary;
|
|
36
37
|
exports.renderLegacyRuns = renderLegacyRuns;
|
|
37
38
|
exports.renderFireAudit = renderFireAudit;
|
|
@@ -86,6 +87,11 @@ exports.TRIGGER_EXECUTOR_MODES = [
|
|
|
86
87
|
'goal_planner_tick',
|
|
87
88
|
'morning_briefing',
|
|
88
89
|
'builtin_action',
|
|
90
|
+
// The author-visible way to run a Task from a trigger. Its internal sibling
|
|
91
|
+
// `task_schedule_fire` sits two lines up and the server refuses it from any
|
|
92
|
+
// non-managed caller, so without this entry a headless operator could author
|
|
93
|
+
// every kind of task watcher except one that does the task.
|
|
94
|
+
'task_execute',
|
|
89
95
|
// User-authorable capabilities the server allows on the agent scope.
|
|
90
96
|
'watch_until',
|
|
91
97
|
'sensor_poll',
|
|
@@ -784,11 +790,40 @@ function writeModeLabel(writeMode) {
|
|
|
784
790
|
return writeMode;
|
|
785
791
|
}
|
|
786
792
|
}
|
|
787
|
-
|
|
793
|
+
/**
|
|
794
|
+
* Kinds that expose a signed inbound HTTP channel. Mirrors `WEBHOOK_KINDS` in
|
|
795
|
+
* `api/server/routes/triggers.js`, which is what actually decides — this copy
|
|
796
|
+
* only chooses whether to PRINT a URL, so being behind costs a missing hint
|
|
797
|
+
* rather than a wrong answer.
|
|
798
|
+
*/
|
|
799
|
+
const WEBHOOK_INGEST_KINDS = new Set(['on_demand', 'form', 'chat_message']);
|
|
800
|
+
/**
|
|
801
|
+
* Where an external caller POSTs to fire this trigger.
|
|
802
|
+
*
|
|
803
|
+
* Same origin as every other `/api/...` call the CLI makes, so it is built from
|
|
804
|
+
* the configured base URL rather than guessed.
|
|
805
|
+
*/
|
|
806
|
+
function webhookIngestUrl(baseURL, triggerId) {
|
|
807
|
+
return `${String(baseURL).replace(/\/+$/, '')}/api/triggers/webhook/${encodeURIComponent(triggerId)}`;
|
|
808
|
+
}
|
|
809
|
+
function renderTriggerSummary(trigger, log, options = {}) {
|
|
788
810
|
log(`ID: ${trigger.id ?? '-'}`);
|
|
789
811
|
log(`Kind: ${trigger.kind ?? '-'}`);
|
|
790
812
|
log(`Armed: ${armedStateLabel(trigger.enabled)}`);
|
|
791
813
|
log(`Scope: ${scopeLabel(trigger)}`);
|
|
814
|
+
/*
|
|
815
|
+
* Say when the row is not yours to change.
|
|
816
|
+
*
|
|
817
|
+
* `producerOwned` is derived by the server and reported on every trigger read.
|
|
818
|
+
* Without it a headless operator reads `skrr triggers show task-schedule:<id>`,
|
|
819
|
+
* sees an ordinary-looking trigger, runs `triggers delete`, and learns from a
|
|
820
|
+
* 409 — for a row that was never editable. Same argument the `Contract:` /
|
|
821
|
+
* `Writes:` lines below were added under: a trigger with a property this
|
|
822
|
+
* consequential should state it at the moment it is read.
|
|
823
|
+
*/
|
|
824
|
+
if (trigger.producerOwned === true) {
|
|
825
|
+
log('Managed: by the platform — not editable here; change the object that owns it');
|
|
826
|
+
}
|
|
792
827
|
if (trigger.label)
|
|
793
828
|
log(`Label: ${trigger.label}`);
|
|
794
829
|
if (trigger.execution?.mode)
|
|
@@ -809,6 +844,18 @@ function renderTriggerSummary(trigger, log) {
|
|
|
809
844
|
if (trigger.webhook?.configured !== undefined) {
|
|
810
845
|
log(`Webhook: ${trigger.webhook.configured ? 'configured' : 'not configured'}`);
|
|
811
846
|
}
|
|
847
|
+
/*
|
|
848
|
+
* The endpoint, not just "configured".
|
|
849
|
+
*
|
|
850
|
+
* An `on_demand` trigger exists so something outside the platform can fire it,
|
|
851
|
+
* and the CLI could report that a secret was set while never saying where to
|
|
852
|
+
* send it — the one fact the integration cannot be built without. Printed for
|
|
853
|
+
* every webhook-bearing kind, whether or not a secret has been minted yet, so
|
|
854
|
+
* "what is my URL" never requires a rotate.
|
|
855
|
+
*/
|
|
856
|
+
if (options.baseURL && trigger.id && WEBHOOK_INGEST_KINDS.has(String(trigger.kind))) {
|
|
857
|
+
log(`Webhook URL: POST ${webhookIngestUrl(options.baseURL, trigger.id)}`);
|
|
858
|
+
}
|
|
812
859
|
}
|
|
813
860
|
function renderLegacyRuns(runs, log) {
|
|
814
861
|
if (runs.length === 0) {
|