@skrr-ai/cli 0.1.18 → 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/delivery.js +1 -0
- package/dist/commands/commitments/doctor.js +11 -0
- package/dist/commands/commitments/mode.js +1 -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 +2475 -1413
- package/package.json +1 -1
package/dist/base-command.d.ts
CHANGED
|
@@ -371,18 +371,21 @@ export declare function describeResourceNotFound(serverMessage: string | undefin
|
|
|
371
371
|
export declare function describeAssigneeNotInSpace(serverMessage: string | undefined, bin?: string): string;
|
|
372
372
|
export declare function describeForbidden(bodyMessage?: string | null, method?: string | null): string;
|
|
373
373
|
/**
|
|
374
|
-
*
|
|
375
|
-
*
|
|
376
|
-
*
|
|
374
|
+
* Identify an authority that is unmistakably machine-only rather than a human
|
|
375
|
+
* CLI session. This is a diagnostic guard only, never an authorization
|
|
376
|
+
* decision: the server remains authoritative for every token.
|
|
377
377
|
*
|
|
378
378
|
* Opaque daemon access tokens have a stable public prefix. Legacy daemon JWTs
|
|
379
|
-
* expose `scope: "daemon"`;
|
|
380
|
-
*
|
|
381
|
-
*
|
|
382
|
-
*
|
|
379
|
+
* expose `scope: "daemon"`; bootstrap-only JWTs use `"daemon-install"`. Both
|
|
380
|
+
* scopes are incompatible with human CLI commands, so identifying either lets
|
|
381
|
+
* the CLI give a repair instruction instead of describing a predictable 403 as
|
|
382
|
+
* a transient edge failure. Malformed and unknown token shapes intentionally
|
|
383
|
+
* return false so a generic authentication failure remains honest.
|
|
383
384
|
*/
|
|
385
|
+
type IncompatibleCliCredentialScope = 'daemon' | 'daemon-install';
|
|
386
|
+
/** Compatibility export for callers specifically distinguishing daemon scope. */
|
|
384
387
|
export declare function isDaemonScopedCredential(token?: string | null): boolean;
|
|
385
|
-
/** The one repair for a
|
|
386
|
-
export declare function describeDaemonScopedCredential(bin: string): string;
|
|
388
|
+
/** The one repair for a machine-only token passed to a human CLI command. */
|
|
389
|
+
export declare function describeDaemonScopedCredential(bin: string, scope?: IncompatibleCliCredentialScope, source?: ResolvedCredential['source']): string;
|
|
387
390
|
export declare function isRetryableFailure(status: number | undefined, body: Record<string, unknown> | null): boolean;
|
|
388
391
|
export { Flags };
|
package/dist/base-command.js
CHANGED
|
@@ -578,9 +578,10 @@ class BaseCommand extends core_1.Command {
|
|
|
578
578
|
retryable: false,
|
|
579
579
|
});
|
|
580
580
|
}
|
|
581
|
-
|
|
581
|
+
const incompatibleScope = incompatibleCliCredentialScope(this.resolvedCredential.token);
|
|
582
|
+
if (incompatibleScope) {
|
|
582
583
|
this.failWithCliError({
|
|
583
|
-
message: describeDaemonScopedCredential(this.config.bin),
|
|
584
|
+
message: describeDaemonScopedCredential(this.config.bin, incompatibleScope, this.resolvedCredential.source),
|
|
584
585
|
code: 'DAEMON_SCOPE_CREDENTIAL',
|
|
585
586
|
exit: 2,
|
|
586
587
|
retryable: false,
|
|
@@ -817,10 +818,10 @@ class BaseCommand extends core_1.Command {
|
|
|
817
818
|
// request and a supplied `--token` can bypass their normal preflight.
|
|
818
819
|
// A daemon credential reaching a human endpoint is deterministic, not
|
|
819
820
|
// an edge outage; retrying only repeats the same 401/403.
|
|
820
|
-
|
|
821
|
-
|
|
821
|
+
const incompatibleScope = incompatibleCliCredentialScope(this.resolvedCredential?.token);
|
|
822
|
+
if ((e.status === 401 || e.status === 403) && incompatibleScope) {
|
|
822
823
|
this.failWithCliError({
|
|
823
|
-
message: say(describeDaemonScopedCredential(this.config.bin)),
|
|
824
|
+
message: say(describeDaemonScopedCredential(this.config.bin, incompatibleScope, this.resolvedCredential?.source)),
|
|
824
825
|
code: 'DAEMON_SCOPE_CREDENTIAL',
|
|
825
826
|
status: e.status,
|
|
826
827
|
exit: 2,
|
|
@@ -1416,38 +1417,38 @@ function describeForbidden(bodyMessage, method) {
|
|
|
1416
1417
|
}
|
|
1417
1418
|
return `${lead}.`;
|
|
1418
1419
|
}
|
|
1419
|
-
|
|
1420
|
-
* True when a credential is unmistakably machine/daemon authority rather
|
|
1421
|
-
* than a human CLI session. This is a diagnostic guard only, never an
|
|
1422
|
-
* authorization decision: the server remains authoritative for every token.
|
|
1423
|
-
*
|
|
1424
|
-
* Opaque daemon access tokens have a stable public prefix. Legacy daemon JWTs
|
|
1425
|
-
* expose `scope: "daemon"`; decoding the unsigned payload lets the CLI give a
|
|
1426
|
-
* repair instruction instead of calling a predictable 403 a transient edge
|
|
1427
|
-
* failure. Malformed and unknown token shapes intentionally return false so a
|
|
1428
|
-
* generic authentication failure remains honest.
|
|
1429
|
-
*/
|
|
1430
|
-
function isDaemonScopedCredential(token) {
|
|
1420
|
+
function incompatibleCliCredentialScope(token) {
|
|
1431
1421
|
if (typeof token !== 'string' || token.length === 0)
|
|
1432
|
-
return
|
|
1422
|
+
return null;
|
|
1433
1423
|
if (token.startsWith('osk_dmn_'))
|
|
1434
|
-
return
|
|
1424
|
+
return 'daemon';
|
|
1435
1425
|
const payload = token.split('.')[1];
|
|
1436
1426
|
if (!payload)
|
|
1437
|
-
return
|
|
1427
|
+
return null;
|
|
1438
1428
|
try {
|
|
1439
1429
|
const decoded = JSON.parse(Buffer.from(payload, 'base64url').toString('utf8'));
|
|
1440
|
-
|
|
1430
|
+
const scope = typeof decoded === 'object' &&
|
|
1441
1431
|
decoded !== null &&
|
|
1442
|
-
decoded.scope === '
|
|
1432
|
+
typeof decoded.scope === 'string'
|
|
1433
|
+
? decoded.scope
|
|
1434
|
+
: null;
|
|
1435
|
+
return scope === 'daemon' || scope === 'daemon-install' ? scope : null;
|
|
1443
1436
|
}
|
|
1444
1437
|
catch {
|
|
1445
|
-
return
|
|
1438
|
+
return null;
|
|
1446
1439
|
}
|
|
1447
1440
|
}
|
|
1448
|
-
/**
|
|
1449
|
-
function
|
|
1450
|
-
return (
|
|
1441
|
+
/** Compatibility export for callers specifically distinguishing daemon scope. */
|
|
1442
|
+
function isDaemonScopedCredential(token) {
|
|
1443
|
+
return incompatibleCliCredentialScope(token) === 'daemon';
|
|
1444
|
+
}
|
|
1445
|
+
/** The one repair for a machine-only token passed to a human CLI command. */
|
|
1446
|
+
function describeDaemonScopedCredential(bin, scope = 'daemon', source) {
|
|
1447
|
+
const scopeLabel = scope === 'daemon-install' ? 'daemon-install-scoped' : 'daemon-scoped';
|
|
1448
|
+
const subject = source === 'keychain' || source === 'file'
|
|
1449
|
+
? `The stored credential has ${scope === 'daemon-install' ? 'daemon-install' : 'daemon'} scope`
|
|
1450
|
+
: `This command received a ${scopeLabel} credential`;
|
|
1451
|
+
return (`${subject}, which cannot access user API commands. ` +
|
|
1451
1452
|
`Run \`${bin} login\` to create a CLI credential. For the local runtime itself, use \`${bin} daemon login\`.`);
|
|
1452
1453
|
}
|
|
1453
1454
|
function isRetryableFailure(status, body) {
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { BaseCommand } from '../../../base-command';
|
|
2
|
+
/**
|
|
3
|
+
* Propose a prepared action against a commitment check.
|
|
4
|
+
*
|
|
5
|
+
* `decide` and `execute` shipped without this, so a headless operator could only
|
|
6
|
+
* ever REACT to proposals the server made. An agent that has investigated a
|
|
7
|
+
* commitment and knows what should be done had no way to say so — the domain
|
|
8
|
+
* was half-reachable in exactly the way the parity rule exists to catch.
|
|
9
|
+
*
|
|
10
|
+
* Nothing here executes. A proposal is FROZEN and waits for `decide`, which is
|
|
11
|
+
* the whole point of the two-step: the thing that decides what to do and the
|
|
12
|
+
* thing that authorizes it are deliberately different acts.
|
|
13
|
+
*/
|
|
14
|
+
export default class CommitmentsActionProposalsPropose extends BaseCommand {
|
|
15
|
+
static description: string;
|
|
16
|
+
static examples: string[];
|
|
17
|
+
static args: {
|
|
18
|
+
commitmentId: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
|
|
19
|
+
};
|
|
20
|
+
static flags: {
|
|
21
|
+
json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
22
|
+
check: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
23
|
+
type: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
24
|
+
config: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
25
|
+
file: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
26
|
+
diagnosis: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
27
|
+
changes: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
28
|
+
verification: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
29
|
+
run: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
30
|
+
};
|
|
31
|
+
run(): Promise<void>;
|
|
32
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const node_fs_1 = require("node:fs");
|
|
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
|
+
const commitments_1 = require("../../../lib/commitments");
|
|
8
|
+
/**
|
|
9
|
+
* Propose a prepared action against a commitment check.
|
|
10
|
+
*
|
|
11
|
+
* `decide` and `execute` shipped without this, so a headless operator could only
|
|
12
|
+
* ever REACT to proposals the server made. An agent that has investigated a
|
|
13
|
+
* commitment and knows what should be done had no way to say so — the domain
|
|
14
|
+
* was half-reachable in exactly the way the parity rule exists to catch.
|
|
15
|
+
*
|
|
16
|
+
* Nothing here executes. A proposal is FROZEN and waits for `decide`, which is
|
|
17
|
+
* the whole point of the two-step: the thing that decides what to do and the
|
|
18
|
+
* thing that authorizes it are deliberately different acts.
|
|
19
|
+
*/
|
|
20
|
+
class CommitmentsActionProposalsPropose extends base_command_1.BaseCommand {
|
|
21
|
+
static description = 'Propose a prepared action for a commitment check (frozen until approved)';
|
|
22
|
+
static examples = [
|
|
23
|
+
'<%= config.bin %> commitments action-proposals propose cmt_123 --check chk_456 --type create_task --config \'{"title":"Roll back the deploy"}\'',
|
|
24
|
+
'<%= config.bin %> commitments action-proposals propose cmt_123 --check chk_456 --file proposal.json',
|
|
25
|
+
];
|
|
26
|
+
static args = {
|
|
27
|
+
commitmentId: core_1.Args.string({ description: 'Commitment ID', required: true, ignoreStdin: true }),
|
|
28
|
+
};
|
|
29
|
+
static flags = {
|
|
30
|
+
json: core_1.Flags.boolean({ description: 'Output the created proposal as JSON' }),
|
|
31
|
+
check: core_1.Flags.string({
|
|
32
|
+
description: 'The commitment check this responds to (see `commitments checks`)',
|
|
33
|
+
required: true,
|
|
34
|
+
}),
|
|
35
|
+
type: core_1.Flags.string({
|
|
36
|
+
description: 'Action type',
|
|
37
|
+
options: [...data_provider_1.COMMITMENT_PUBLIC_ACTION_BUNDLE_ACTION_TYPES],
|
|
38
|
+
exclusive: ['file'],
|
|
39
|
+
}),
|
|
40
|
+
config: core_1.Flags.string({ description: 'Action config as JSON', exclusive: ['file'] }),
|
|
41
|
+
file: core_1.Flags.string({
|
|
42
|
+
description: 'A JSON file holding the whole request body — for a multi-action bundle, or anything a flag cannot express',
|
|
43
|
+
exclusive: ['type', 'config'],
|
|
44
|
+
}),
|
|
45
|
+
diagnosis: core_1.Flags.string({ description: 'What you concluded is wrong' }),
|
|
46
|
+
changes: core_1.Flags.string({ description: 'What the action would change' }),
|
|
47
|
+
verification: core_1.Flags.string({ description: 'How to tell afterwards whether it worked' }),
|
|
48
|
+
run: core_1.Flags.string({ description: 'The autonomous run that produced this, if any' }),
|
|
49
|
+
};
|
|
50
|
+
async run() {
|
|
51
|
+
this.requireAuth();
|
|
52
|
+
const { args, flags } = await this.parse(CommitmentsActionProposalsPropose);
|
|
53
|
+
let body;
|
|
54
|
+
if (flags.file) {
|
|
55
|
+
try {
|
|
56
|
+
body = JSON.parse((0, node_fs_1.readFileSync)(flags.file, 'utf8'));
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
this.error(`Could not read ${flags.file} as JSON: ${error.message}`, {
|
|
60
|
+
exit: 1,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
if (!flags.type) {
|
|
66
|
+
// Refused rather than defaulted: an action type the caller did not name
|
|
67
|
+
// is a decision nobody made, and this endpoint arms real work.
|
|
68
|
+
this.error('Supply --type (or --file for a multi-action bundle).', { exit: 1 });
|
|
69
|
+
}
|
|
70
|
+
let config;
|
|
71
|
+
if (flags.config) {
|
|
72
|
+
try {
|
|
73
|
+
config = JSON.parse(flags.config);
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
this.error(`--config is not valid JSON: ${error.message}`, { exit: 1 });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
body = { action: { type: flags.type, ...(config ? { config } : {}) } };
|
|
80
|
+
}
|
|
81
|
+
body.commitmentCheckId = flags.check;
|
|
82
|
+
if (flags.run) {
|
|
83
|
+
body.autonomousRunId = flags.run;
|
|
84
|
+
}
|
|
85
|
+
const bundle = {
|
|
86
|
+
...(flags.diagnosis ? { diagnosis: flags.diagnosis } : {}),
|
|
87
|
+
...(flags.changes ? { proposedChanges: flags.changes } : {}),
|
|
88
|
+
...(flags.verification ? { verificationPlan: flags.verification } : {}),
|
|
89
|
+
};
|
|
90
|
+
if (Object.keys(bundle).length > 0) {
|
|
91
|
+
body.bundle = { ...(body.bundle ?? {}), ...bundle };
|
|
92
|
+
}
|
|
93
|
+
let result;
|
|
94
|
+
try {
|
|
95
|
+
result = await commitments_1.commitmentApi.proposeActionProposal(args.commitmentId, body);
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
this.handleApiError(error);
|
|
99
|
+
}
|
|
100
|
+
if (flags.json) {
|
|
101
|
+
this.log(JSON.stringify(result, null, 2));
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const proposal = result
|
|
105
|
+
?.proposal;
|
|
106
|
+
this.log(`Proposed ${proposal?.id ?? ''} (${proposal?.status ?? 'proposed'}).`);
|
|
107
|
+
// Say the two-step out loud: proposing is not doing.
|
|
108
|
+
this.log(`Nothing runs until it is approved: skrr commitments action-proposals decide ${args.commitmentId} ${proposal?.id ?? '<proposal>'} --decision approve`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
exports.default = CommitmentsActionProposalsPropose;
|
|
@@ -76,6 +76,8 @@ export default class CommitmentsCreate extends BaseCommand {
|
|
|
76
76
|
autonomy: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
77
77
|
isolation: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
78
78
|
'max-nudges': import("@oclif/core/lib/interfaces").OptionFlag<number | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
79
|
+
'max-concurrent-runs': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
80
|
+
'concurrency-reason': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
79
81
|
};
|
|
80
82
|
run(): Promise<void>;
|
|
81
83
|
}
|
|
@@ -288,6 +288,12 @@ class CommitmentsCreate extends base_command_1.BaseCommand {
|
|
|
288
288
|
hidden: true,
|
|
289
289
|
}),
|
|
290
290
|
'max-nudges': core_1.Flags.integer({ description: 'Max user nudges per day (0..20)' }),
|
|
291
|
+
'max-concurrent-runs': core_1.Flags.string({
|
|
292
|
+
description: 'Owner concurrency policy: "unlimited" (default) or 1..100. Finite values require --concurrency-reason and cannot be activated by an autonomous Agent session.',
|
|
293
|
+
}),
|
|
294
|
+
'concurrency-reason': core_1.Flags.string({
|
|
295
|
+
description: 'Why the owner deliberately chose a finite Commitment concurrency cap',
|
|
296
|
+
}),
|
|
291
297
|
};
|
|
292
298
|
async run() {
|
|
293
299
|
this.requireAuth();
|
|
@@ -366,7 +372,12 @@ class CommitmentsCreate extends base_command_1.BaseCommand {
|
|
|
366
372
|
if (cadence)
|
|
367
373
|
payload.cadence = cadence;
|
|
368
374
|
assertEventDrivenWatchedSource(payload);
|
|
369
|
-
|
|
375
|
+
// `--from-json` may already carry a policy. Start from it so an explicit
|
|
376
|
+
// flag refines the submitted contract instead of replacing it.
|
|
377
|
+
const existingPolicy = payload.policy && typeof payload.policy === 'object' && !Array.isArray(payload.policy)
|
|
378
|
+
? payload.policy
|
|
379
|
+
: {};
|
|
380
|
+
const policy = { ...existingPolicy };
|
|
370
381
|
if (flags.autonomy)
|
|
371
382
|
policy.autonomyMode = flags.autonomy;
|
|
372
383
|
if (flags.mode && flags.autonomy)
|
|
@@ -381,6 +392,18 @@ class CommitmentsCreate extends base_command_1.BaseCommand {
|
|
|
381
392
|
policy.executionIsolation = flags.isolation;
|
|
382
393
|
if (flags['max-nudges'] !== undefined)
|
|
383
394
|
policy.maxUserNudgesPerDay = flags['max-nudges'];
|
|
395
|
+
const concurrencyPatch = (0, commitments_1.commitmentConcurrencyEnvelopePatch)({
|
|
396
|
+
rawValue: flags['max-concurrent-runs'],
|
|
397
|
+
reason: flags['concurrency-reason'],
|
|
398
|
+
});
|
|
399
|
+
if (concurrencyPatch) {
|
|
400
|
+
const existingEnvelope = policy.autonomyEnvelope &&
|
|
401
|
+
typeof policy.autonomyEnvelope === 'object' &&
|
|
402
|
+
!Array.isArray(policy.autonomyEnvelope)
|
|
403
|
+
? policy.autonomyEnvelope
|
|
404
|
+
: {};
|
|
405
|
+
policy.autonomyEnvelope = { ...existingEnvelope, ...concurrencyPatch };
|
|
406
|
+
}
|
|
384
407
|
if (Object.keys(policy).length)
|
|
385
408
|
payload.policy = policy;
|
|
386
409
|
const payloadPolicy = payload.policy && typeof payload.policy === 'object' && !Array.isArray(payload.policy)
|
|
@@ -391,13 +414,30 @@ class CommitmentsCreate extends base_command_1.BaseCommand {
|
|
|
391
414
|
: typeof payloadPolicy.autonomyMode === 'string'
|
|
392
415
|
? payloadPolicy.autonomyMode
|
|
393
416
|
: 'monitor';
|
|
394
|
-
if (payload.delivery &&
|
|
417
|
+
if (payload.delivery &&
|
|
418
|
+
typeof payload.delivery === 'object' &&
|
|
419
|
+
!Array.isArray(payload.delivery)) {
|
|
395
420
|
const declared = payload.delivery;
|
|
396
421
|
const coherent = (0, commitment_product_1.deliveryForProductMode)(selectedMode, declared);
|
|
397
422
|
deliveryNormalizedForMonitor =
|
|
398
423
|
(0, commitment_product_1.productMode)(selectedMode) === 'monitor' && declared.mode !== 'report_only';
|
|
399
424
|
payload.delivery = coherent;
|
|
400
425
|
}
|
|
426
|
+
if (payload.createdVia === undefined) {
|
|
427
|
+
payload.createdVia = process.env.OVERSKY_AUTONOMOUS_SESSION === '1' ? 'agent_cli' : 'cli';
|
|
428
|
+
}
|
|
429
|
+
// `--from-json` is intentionally powerful, but it must not be an escape
|
|
430
|
+
// hatch around the same authoring decision the explicit flag enforces.
|
|
431
|
+
const envelope = policy.autonomyEnvelope;
|
|
432
|
+
if (envelope && Object.prototype.hasOwnProperty.call(envelope, 'maxConcurrentRuns')) {
|
|
433
|
+
const value = envelope.maxConcurrentRuns;
|
|
434
|
+
if (process.env.OVERSKY_AUTONOMOUS_SESSION === '1' && value != null) {
|
|
435
|
+
throw new Error('An autonomous Agent cannot activate a finite Commitment concurrency cap from --from-json. Omit it or set it to null; an owner may set it later.');
|
|
436
|
+
}
|
|
437
|
+
if (value != null && !String(envelope.maxConcurrentRunsReason || '').trim()) {
|
|
438
|
+
throw new Error('A finite policy.autonomyEnvelope.maxConcurrentRuns in --from-json requires maxConcurrentRunsReason.');
|
|
439
|
+
}
|
|
440
|
+
}
|
|
401
441
|
}
|
|
402
442
|
catch (err) {
|
|
403
443
|
this.error(err.message, { exit: 1 });
|
|
@@ -14,6 +14,7 @@ class CommitmentsDelivery extends base_command_1.BaseCommand {
|
|
|
14
14
|
id: core_1.Args.string({ required: true, ignoreStdin: true }),
|
|
15
15
|
mode: core_1.Args.string({
|
|
16
16
|
required: true,
|
|
17
|
+
ignoreStdin: true,
|
|
17
18
|
description: 'Delivery destination. Code destinations require Prepare or Execute (or a Pack Shadow promotion target).',
|
|
18
19
|
options: [
|
|
19
20
|
...commitment_product_1.COMMITMENT_DELIVERY_CHOICES,
|
|
@@ -18,6 +18,16 @@ function doctorVerdict(input) {
|
|
|
18
18
|
if (operatingState.coverage && operatingState.coverage.complete === false) {
|
|
19
19
|
return { verdict: 'degraded', next: 'Retry doctor; one or more operating-state projections are unavailable' };
|
|
20
20
|
}
|
|
21
|
+
const autonomy = operatingState.autonomy;
|
|
22
|
+
const budget = autonomy?.budget;
|
|
23
|
+
const concurrentRuns = budget?.concurrentRuns;
|
|
24
|
+
const decision = concurrentRuns?.decision;
|
|
25
|
+
if (concurrentRuns?.mode === 'limited' && decision?.status === 'review_required') {
|
|
26
|
+
return {
|
|
27
|
+
verdict: 'policy_review_required',
|
|
28
|
+
next: 'Confirm the cap with --max-concurrent-runs <n> --concurrency-reason "...", or remove it with --max-concurrent-runs unlimited',
|
|
29
|
+
};
|
|
30
|
+
}
|
|
21
31
|
if (operatingState.status && commitment.status === 'active') {
|
|
22
32
|
const status = String(operatingState.status);
|
|
23
33
|
return {
|
|
@@ -76,6 +86,7 @@ class CommitmentsDoctor extends base_command_1.BaseCommand {
|
|
|
76
86
|
if (autonomy) {
|
|
77
87
|
this.log(`Mode: ${(0, commitments_1.commitmentAutonomyLabel)(String(autonomy.declaredMode || ''))} → ${(0, commitments_1.commitmentAutonomyLabel)(String(autonomy.effectiveMode || ''))}`);
|
|
78
88
|
}
|
|
89
|
+
this.log(`Concurrency: ${(0, commitments_1.describeCommitmentConcurrency)(commitment)}`);
|
|
79
90
|
const approvals = operatingState.approvals;
|
|
80
91
|
if (approvals?.pendingCount)
|
|
81
92
|
this.log(`Waiting approval: ${String(approvals.pendingCount)}`);
|
|
@@ -12,6 +12,7 @@ class CommitmentsMode extends base_command_1.BaseCommand {
|
|
|
12
12
|
id: core_1.Args.string({ required: true, ignoreStdin: true }),
|
|
13
13
|
mode: core_1.Args.string({
|
|
14
14
|
required: true,
|
|
15
|
+
ignoreStdin: true,
|
|
15
16
|
options: [...commitment_product_1.COMMITMENT_PRODUCT_MODES],
|
|
16
17
|
}),
|
|
17
18
|
};
|
|
@@ -47,6 +47,8 @@ export default class CommitmentsUpdate extends BaseCommand {
|
|
|
47
47
|
'cadence-mode': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
48
48
|
timezone: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
49
49
|
isolation: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
50
|
+
'max-concurrent-runs': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
51
|
+
'concurrency-reason': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
50
52
|
};
|
|
51
53
|
run(): Promise<void>;
|
|
52
54
|
}
|
|
@@ -105,6 +105,12 @@ class CommitmentsUpdate extends base_command_1.BaseCommand {
|
|
|
105
105
|
'capability.',
|
|
106
106
|
options: [...commitments_1.COMMITMENT_EXECUTION_ISOLATION_MODES],
|
|
107
107
|
}),
|
|
108
|
+
'max-concurrent-runs': core_1.Flags.string({
|
|
109
|
+
description: 'Set 1..100, or "unlimited" to remove the owner cap. Finite values require --concurrency-reason.',
|
|
110
|
+
}),
|
|
111
|
+
'concurrency-reason': core_1.Flags.string({
|
|
112
|
+
description: 'Why the owner deliberately chose a finite Commitment concurrency cap',
|
|
113
|
+
}),
|
|
108
114
|
};
|
|
109
115
|
async run() {
|
|
110
116
|
this.requireAuth();
|
|
@@ -220,11 +226,40 @@ class CommitmentsUpdate extends base_command_1.BaseCommand {
|
|
|
220
226
|
cadence.timezone = flags.timezone;
|
|
221
227
|
payload.cadence = cadence;
|
|
222
228
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
229
|
+
const concurrencyPatch = (0, commitments_1.commitmentConcurrencyEnvelopePatch)({
|
|
230
|
+
rawValue: flags['max-concurrent-runs'],
|
|
231
|
+
reason: flags['concurrency-reason'],
|
|
232
|
+
});
|
|
233
|
+
if (flags.isolation || concurrencyPatch) {
|
|
234
|
+
const existingPolicy = payload.policy && typeof payload.policy === 'object' && !Array.isArray(payload.policy)
|
|
235
|
+
? payload.policy
|
|
236
|
+
: {};
|
|
237
|
+
const nextPolicy = { ...existingPolicy };
|
|
238
|
+
if (flags.isolation)
|
|
239
|
+
nextPolicy.executionIsolation = flags.isolation;
|
|
240
|
+
if (concurrencyPatch) {
|
|
241
|
+
const existingEnvelope = nextPolicy.autonomyEnvelope &&
|
|
242
|
+
typeof nextPolicy.autonomyEnvelope === 'object' &&
|
|
243
|
+
!Array.isArray(nextPolicy.autonomyEnvelope)
|
|
244
|
+
? nextPolicy.autonomyEnvelope
|
|
245
|
+
: {};
|
|
246
|
+
nextPolicy.autonomyEnvelope = { ...existingEnvelope, ...concurrencyPatch };
|
|
247
|
+
}
|
|
248
|
+
payload.policy = nextPolicy;
|
|
249
|
+
}
|
|
250
|
+
const finalPolicy = payload.policy && typeof payload.policy === 'object' && !Array.isArray(payload.policy)
|
|
251
|
+
? payload.policy
|
|
252
|
+
: undefined;
|
|
253
|
+
const finalEnvelope = finalPolicy?.autonomyEnvelope &&
|
|
254
|
+
typeof finalPolicy.autonomyEnvelope === 'object' &&
|
|
255
|
+
!Array.isArray(finalPolicy.autonomyEnvelope)
|
|
256
|
+
? finalPolicy.autonomyEnvelope
|
|
257
|
+
: undefined;
|
|
258
|
+
if (process.env.OVERSKY_AUTONOMOUS_SESSION === '1' &&
|
|
259
|
+
finalEnvelope &&
|
|
260
|
+
Object.prototype.hasOwnProperty.call(finalEnvelope, 'maxConcurrentRuns')) {
|
|
261
|
+
throw new Error('An autonomous Agent cannot change owner Commitment concurrency policy. Ask the owner to run this update outside the Agent session.');
|
|
262
|
+
}
|
|
228
263
|
}
|
|
229
264
|
catch (err) {
|
|
230
265
|
this.error(err.message, { exit: 1 });
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { BaseCommand } from '../../base-command';
|
|
2
|
+
export default class StoreBrowse extends BaseCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
7
|
+
q: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
8
|
+
category: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
9
|
+
limit: import("@oclif/core/lib/interfaces").OptionFlag<number, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
10
|
+
cursor: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
11
|
+
sort: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
12
|
+
};
|
|
13
|
+
run(): Promise<void>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
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 format_1 = require("../../lib/format");
|
|
7
|
+
class StoreBrowse extends base_command_1.BaseCommand {
|
|
8
|
+
static description = 'Browse published agents in the store';
|
|
9
|
+
static examples = [
|
|
10
|
+
'<%= config.bin %> store browse',
|
|
11
|
+
'<%= config.bin %> store browse --q "cost watchdog"',
|
|
12
|
+
'<%= config.bin %> store browse --category engineering --limit 10 --json',
|
|
13
|
+
];
|
|
14
|
+
static flags = {
|
|
15
|
+
json: core_1.Flags.boolean({ description: 'Output as JSON' }),
|
|
16
|
+
q: core_1.Flags.string({ description: 'Search query' }),
|
|
17
|
+
category: core_1.Flags.string({ description: 'Filter by category' }),
|
|
18
|
+
limit: core_1.Flags.integer({ description: 'Max results', default: 20 }),
|
|
19
|
+
cursor: core_1.Flags.string({ description: 'Pagination cursor from a previous page' }),
|
|
20
|
+
sort: core_1.Flags.string({ description: 'Sort order', options: ['popular', 'newest', 'rating'] }),
|
|
21
|
+
};
|
|
22
|
+
async run() {
|
|
23
|
+
this.requireAuth();
|
|
24
|
+
const { flags } = await this.parse(StoreBrowse);
|
|
25
|
+
let response;
|
|
26
|
+
try {
|
|
27
|
+
response = await data_provider_1.dataService.getStoreAgents({
|
|
28
|
+
limit: flags.limit,
|
|
29
|
+
...(flags.q ? { q: flags.q } : {}),
|
|
30
|
+
...(flags.category ? { category: flags.category } : {}),
|
|
31
|
+
...(flags.cursor ? { cursor: flags.cursor } : {}),
|
|
32
|
+
...(flags.sort ? { sortBy: flags.sort } : {}),
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
this.handleApiError(err);
|
|
37
|
+
}
|
|
38
|
+
if (flags.json) {
|
|
39
|
+
this.log(JSON.stringify(response, null, 2));
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const r = response;
|
|
43
|
+
const items = r?.data ?? [];
|
|
44
|
+
if (items.length === 0) {
|
|
45
|
+
this.log('No agents found.');
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
(0, format_1.renderTable)(items.map((a) => ({
|
|
49
|
+
id: String(a.id ?? '-'),
|
|
50
|
+
name: String(a.name ?? '-'),
|
|
51
|
+
// Price is a property of the LISTING, so it is read from the listing
|
|
52
|
+
// row rather than from anything the agent carries.
|
|
53
|
+
price: a.price ? String(a.price) : 'free',
|
|
54
|
+
rating: a.rating ? `${Number(a.rating).toFixed(1)} (${a.ratingCount ?? 0})` : '-',
|
|
55
|
+
installs: String(a.installCount ?? 0),
|
|
56
|
+
author: String(a.authorName ?? '-'),
|
|
57
|
+
})), [
|
|
58
|
+
{ key: 'id', header: 'ID' },
|
|
59
|
+
{ key: 'name', header: 'NAME', maxWidth: 30 },
|
|
60
|
+
{ key: 'price', header: 'PRICE' },
|
|
61
|
+
{ key: 'rating', header: 'RATING' },
|
|
62
|
+
{ key: 'installs', header: 'INSTALLS' },
|
|
63
|
+
{ key: 'author', header: 'PUBLISHER', maxWidth: 22 },
|
|
64
|
+
], (line) => this.log(line));
|
|
65
|
+
if (r?.next_cursor) {
|
|
66
|
+
this.log('');
|
|
67
|
+
this.log(`Next page: --cursor ${r.next_cursor}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
exports.default = StoreBrowse;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { BaseCommand } from '../../base-command';
|
|
2
|
+
export default class StoreInstall extends BaseCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static args: {
|
|
6
|
+
agentId: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
|
|
7
|
+
};
|
|
8
|
+
static flags: {
|
|
9
|
+
json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
10
|
+
release: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
11
|
+
space: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
12
|
+
'include-bootstrap': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
13
|
+
'include-memory': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
14
|
+
};
|
|
15
|
+
run(): Promise<void>;
|
|
16
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const core_1 = require("@oclif/core");
|
|
4
|
+
const data_provider_1 = require("@skrr-ai/data-provider");
|
|
5
|
+
const base_command_1 = require("../../base-command");
|
|
6
|
+
class StoreInstall extends base_command_1.BaseCommand {
|
|
7
|
+
static description = 'Install a published agent — you get your own copy of it';
|
|
8
|
+
static examples = [
|
|
9
|
+
'<%= config.bin %> store install agent_abc123',
|
|
10
|
+
'<%= config.bin %> store install agent_abc123 --release ar_def456',
|
|
11
|
+
'<%= config.bin %> store install agent_abc123 --json',
|
|
12
|
+
];
|
|
13
|
+
static args = {
|
|
14
|
+
agentId: core_1.Args.string({ description: 'The store agent to install', required: true }),
|
|
15
|
+
};
|
|
16
|
+
static flags = {
|
|
17
|
+
json: core_1.Flags.boolean({ description: 'Output as JSON' }),
|
|
18
|
+
release: core_1.Flags.string({ description: 'Install a specific release (defaults to the latest)' }),
|
|
19
|
+
space: core_1.Flags.string({ description: 'Space to install into' }),
|
|
20
|
+
'include-bootstrap': core_1.Flags.boolean({
|
|
21
|
+
description: "Include the publisher's shared bootstrap files, if they published any",
|
|
22
|
+
}),
|
|
23
|
+
'include-memory': core_1.Flags.boolean({
|
|
24
|
+
description: "Include the publisher's shared memories, if they published any",
|
|
25
|
+
}),
|
|
26
|
+
};
|
|
27
|
+
async run() {
|
|
28
|
+
this.requireAuth();
|
|
29
|
+
const { args, flags } = await this.parse(StoreInstall);
|
|
30
|
+
let response;
|
|
31
|
+
try {
|
|
32
|
+
response = await data_provider_1.dataService.installStoreAgent({
|
|
33
|
+
agentId: args.agentId,
|
|
34
|
+
...(flags.release ? { releaseId: flags.release } : {}),
|
|
35
|
+
...(flags.space ? { spaceId: flags.space } : {}),
|
|
36
|
+
...(flags['include-bootstrap'] ? { includeBootstrap: true } : {}),
|
|
37
|
+
...(flags['include-memory'] ? { includeMemory: true } : {}),
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
this.handleApiError(err);
|
|
42
|
+
}
|
|
43
|
+
if (flags.json) {
|
|
44
|
+
this.log(JSON.stringify(response, null, 2));
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const installedId = response?.installedAgentId;
|
|
48
|
+
this.log(`Installed as ${installedId ?? '-'}.`);
|
|
49
|
+
// The thing worth saying once: this is a COPY, and it is yours. Edits you
|
|
50
|
+
// make to it survive the publisher's future updates.
|
|
51
|
+
this.log('This is your own copy. Edits you make to it are kept when the publisher ships an update.');
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
exports.default = StoreInstall;
|