@skrr-ai/cli 0.1.51 → 0.1.53
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 +7 -0
- package/dist/base-command.js +38 -8
- package/dist/commands/agents/chat.js +2 -1
- package/dist/commands/agents/create.js +21 -8
- package/dist/commands/agents/update.d.ts +55 -0
- package/dist/commands/agents/update.js +119 -23
- package/dist/commands/commitments/next-tick.js +9 -1
- package/dist/commands/context.js +2 -1
- package/dist/commands/daemon/config.js +16 -1
- package/dist/commands/doctor.js +8 -1
- package/dist/commands/login.d.ts +2 -1
- package/dist/commands/login.js +26 -7
- package/dist/commands/logout.js +3 -0
- package/dist/commands/machines/dedicated/show.js +25 -2
- package/dist/commands/tasks/approvals/list.d.ts +22 -0
- package/dist/commands/tasks/approvals/list.js +80 -3
- package/dist/commands/tasks/create.d.ts +5 -0
- package/dist/commands/tasks/create.js +28 -7
- package/dist/commands/tasks/start.d.ts +14 -0
- package/dist/commands/tasks/start.js +31 -1
- package/dist/commands/tasks/update.js +30 -3
- package/dist/commands/whoami.js +5 -3
- package/dist/lib/agent-config.d.ts +15 -0
- package/dist/lib/agent-config.js +46 -10
- package/dist/lib/agentic-stream.d.ts +21 -1
- package/dist/lib/agentic-stream.js +40 -6
- package/dist/lib/auth-failure.js +4 -1
- package/dist/lib/auth-storage.d.ts +24 -0
- package/dist/lib/auth-storage.js +348 -13
- package/dist/lib/cli-id.d.ts +22 -0
- package/dist/lib/cli-id.js +56 -0
- package/dist/lib/cli-identity.d.ts +49 -0
- package/dist/lib/cli-identity.js +159 -0
- package/dist/lib/commitments.d.ts +4 -0
- package/dist/lib/commitments.js +12 -1
- package/dist/lib/config.d.ts +17 -0
- package/dist/lib/config.js +15 -3
- package/dist/lib/cred-envelope.d.ts +12 -1
- package/dist/lib/cred-envelope.js +20 -3
- package/dist/lib/daemonBroker.d.ts +26 -4
- package/dist/lib/daemonBroker.js +82 -10
- package/dist/lib/daemonBrokerRefusal.d.ts +51 -0
- package/dist/lib/daemonBrokerRefusal.js +127 -0
- package/dist/lib/dedicated-machines.d.ts +53 -4
- package/dist/lib/dedicated-machines.js +95 -5
- package/dist/lib/delegated-cli.js +16 -0
- package/dist/lib/first-party-harness-broker.js +6 -4
- package/dist/lib/keychain.d.ts +39 -0
- package/dist/lib/keychain.js +254 -40
- package/dist/lib/label-ref.d.ts +34 -0
- package/dist/lib/label-ref.js +72 -1
- package/dist/lib/login.js +24 -12
- package/dist/lib/oauthLogin.js +2 -8
- package/dist/lib/refresh.js +6 -2
- package/dist/lib/tasks.js +33 -8
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialEnvelopeBridge.d.ts +13 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialEnvelopeBridge.js +9 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +1 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +4 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialEnvelopeBridge.d.ts +13 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialEnvelopeBridge.js +8 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +1 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +1 -1
- package/dist/node_modules/@skrr-ai/auth-core/package.json +1 -1
- package/dist/node_modules/@skrr-ai/data-provider/index.js +3138 -3100
- package/oclif.manifest.json +26470 -26449
- package/package.json +1 -1
package/dist/base-command.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { type AssigneeResolution, type MemberKind, type SpaceMember } from './li
|
|
|
4
4
|
import { type CliConfig } from './lib/config';
|
|
5
5
|
import { type OutboxMethod } from './lib/outbox';
|
|
6
6
|
import { type ResolvedCredential } from './lib/credential-resolver';
|
|
7
|
+
import { type BrokerRefusalDescription } from './lib/daemonBrokerRefusal';
|
|
7
8
|
import { type DefaultAgentResolution } from './lib/tasks';
|
|
8
9
|
export declare function assigneePayloadNeedsResolution(payload: Record<string, unknown>): boolean;
|
|
9
10
|
/**
|
|
@@ -68,6 +69,12 @@ export declare abstract class BaseCommand extends Command {
|
|
|
68
69
|
*/
|
|
69
70
|
protected resolvedCredential: ResolvedCredential;
|
|
70
71
|
protected bareMode: boolean;
|
|
72
|
+
/**
|
|
73
|
+
* Set when the auto-broker reached a live daemon and it REFUSED the hand-off.
|
|
74
|
+
* `requireAuth()` reports it instead of a bare "Not signed in", because the
|
|
75
|
+
* daemon's reason decides the repair (OSK-12149, OSK-12151).
|
|
76
|
+
*/
|
|
77
|
+
protected brokerRefusal: BrokerRefusalDescription | null;
|
|
71
78
|
init(): Promise<void>;
|
|
72
79
|
/**
|
|
73
80
|
* Read only the two global base flags. A narrow parser — oclif's
|
package/dist/base-command.js
CHANGED
|
@@ -35,6 +35,7 @@ const task_ref_resolver_1 = require("./lib/task-ref-resolver");
|
|
|
35
35
|
const space_resolver_1 = require("./lib/space-resolver");
|
|
36
36
|
const projects_1 = require("./lib/projects");
|
|
37
37
|
const config_1 = require("./lib/config");
|
|
38
|
+
const cli_id_1 = require("./lib/cli-id");
|
|
38
39
|
const keychain_2 = require("./lib/keychain");
|
|
39
40
|
const outbox_1 = require("./lib/outbox");
|
|
40
41
|
const node_adapter_1 = require("./lib/node-adapter");
|
|
@@ -42,6 +43,7 @@ const migrate_1 = require("./lib/migrate");
|
|
|
42
43
|
const auth_core_init_1 = require("./lib/auth-core-init");
|
|
43
44
|
const credential_resolver_1 = require("./lib/credential-resolver");
|
|
44
45
|
const daemonBroker_1 = require("./lib/daemonBroker");
|
|
46
|
+
const daemonBrokerRefusal_1 = require("./lib/daemonBrokerRefusal");
|
|
45
47
|
const refresh_1 = require("./lib/refresh");
|
|
46
48
|
const auth_storage_1 = require("./lib/auth-storage");
|
|
47
49
|
const update_check_1 = require("./lib/update-check");
|
|
@@ -131,6 +133,12 @@ class BaseCommand extends core_1.Command {
|
|
|
131
133
|
*/
|
|
132
134
|
resolvedCredential;
|
|
133
135
|
bareMode = false;
|
|
136
|
+
/**
|
|
137
|
+
* Set when the auto-broker reached a live daemon and it REFUSED the hand-off.
|
|
138
|
+
* `requireAuth()` reports it instead of a bare "Not signed in", because the
|
|
139
|
+
* daemon's reason decides the repair (OSK-12149, OSK-12151).
|
|
140
|
+
*/
|
|
141
|
+
brokerRefusal = null;
|
|
134
142
|
async init() {
|
|
135
143
|
await super.init();
|
|
136
144
|
// The profile is resolved from raw argv, BEFORE oclif parses anything.
|
|
@@ -269,11 +277,13 @@ class BaseCommand extends core_1.Command {
|
|
|
269
277
|
// skrr command reads it through the normal resolver chain — no
|
|
270
278
|
// per-command broker round-trip.
|
|
271
279
|
//
|
|
272
|
-
//
|
|
273
|
-
//
|
|
274
|
-
//
|
|
275
|
-
//
|
|
276
|
-
//
|
|
280
|
+
// A broker failure leaves resolvedCredential null and the command's own
|
|
281
|
+
// `requireAuth()` surfaces the error a moment later. It is quiet only when
|
|
282
|
+
// no daemon was there to ask. A daemon that ANSWERED and refused is kept
|
|
283
|
+
// in `brokerRefusal`, and `requireAuth()` reports its reason and the
|
|
284
|
+
// repair that reason implies — "Not signed in. Run `skrr login`" was true
|
|
285
|
+
// and pointed at the wrong fix (OSK-12149). Rate limiting and a mismatched
|
|
286
|
+
// baseURL keep their own one-line hints below.
|
|
277
287
|
if (!this.resolvedCredential.token) {
|
|
278
288
|
const autoResult = await (0, daemonBroker_1.maybeAutoBroker)({
|
|
279
289
|
cliConfig: this.cliConfig,
|
|
@@ -318,6 +328,9 @@ class BaseCommand extends core_1.Command {
|
|
|
318
328
|
kind: (0, auth_core_1.classifyTokenKind)(accessToken),
|
|
319
329
|
};
|
|
320
330
|
}
|
|
331
|
+
else if ((this.brokerRefusal = (0, daemonBrokerRefusal_1.describeBrokerRefusal)(autoResult.outcome, this.config.bin))) {
|
|
332
|
+
// Reported by requireAuth(); nothing to print here.
|
|
333
|
+
}
|
|
321
334
|
else if (autoResult.outcome.reason === 'rate_limited') {
|
|
322
335
|
// Otherwise the only thing the user sees is "Not signed in", which is
|
|
323
336
|
// true and points at the wrong repair: they are signed in, the local
|
|
@@ -340,7 +353,12 @@ class BaseCommand extends core_1.Command {
|
|
|
340
353
|
}
|
|
341
354
|
}
|
|
342
355
|
if (!this.resolvedCredential.token && ignoredStoredCredential) {
|
|
343
|
-
|
|
356
|
+
const foreign = (0, auth_storage_1.describeForeignCredential)(ignoredStoredCredential);
|
|
357
|
+
if (foreign) {
|
|
358
|
+
process.stderr.write(`[skrr] ${foreign} Run \`skrr login\` to sign this configuration in; the other ` +
|
|
359
|
+
`configuration keeps its own credential.\n`);
|
|
360
|
+
}
|
|
361
|
+
else if (ignoredStoredCredential.originStatus === 'mismatch') {
|
|
344
362
|
process.stderr.write(`[skrr] Ignoring stored credentials issued by ${ignoredStoredCredential.storedServerOrigin}; ` +
|
|
345
363
|
`skrr is configured for ${this.cliConfig.baseURL}. Run \`skrr login --base-url ${this.cliConfig.baseURL}\` ` +
|
|
346
364
|
`or switch back to the issuing server.\n`);
|
|
@@ -616,6 +634,18 @@ class BaseCommand extends core_1.Command {
|
|
|
616
634
|
/** Abort the command with a helpful message if the user isn't signed in. */
|
|
617
635
|
requireAuth() {
|
|
618
636
|
if (!this.resolvedCredential?.token) {
|
|
637
|
+
const refusal = this.brokerRefusal;
|
|
638
|
+
if (refusal) {
|
|
639
|
+
// Same code as the plain case — scripts branch on NOT_SIGNED_IN — with
|
|
640
|
+
// the daemon's answer in the message and in `details.broker`.
|
|
641
|
+
this.failWithCliError({
|
|
642
|
+
message: `Not signed in. ${refusal.headline}\n→ ${refusal.remedy}`,
|
|
643
|
+
code: 'NOT_SIGNED_IN',
|
|
644
|
+
exit: 2,
|
|
645
|
+
retryable: refusal.transient,
|
|
646
|
+
details: { broker: refusal.broker, suggestion: refusal.remedy },
|
|
647
|
+
});
|
|
648
|
+
}
|
|
619
649
|
this.failWithCliError({
|
|
620
650
|
message: `Not signed in. Run \`${this.config.bin} login\` first.`,
|
|
621
651
|
code: 'NOT_SIGNED_IN',
|
|
@@ -1011,7 +1041,7 @@ class BaseCommand extends core_1.Command {
|
|
|
1011
1041
|
? null
|
|
1012
1042
|
: (0, auth_failure_1.describeRefreshRefusal)(readRefreshRefusal(), {
|
|
1013
1043
|
bin: this.config.bin,
|
|
1014
|
-
cliId: this.cliConfig
|
|
1044
|
+
cliId: this.cliConfig ? (0, cli_id_1.resolveCliId)(this.cliConfig) : undefined,
|
|
1015
1045
|
});
|
|
1016
1046
|
this.failWithCliError({
|
|
1017
1047
|
message: say(presentedCiToken
|
|
@@ -1309,7 +1339,7 @@ class BaseCommand extends core_1.Command {
|
|
|
1309
1339
|
if (e.status === 401) {
|
|
1310
1340
|
const refusal = (0, auth_failure_1.describeRefreshRefusal)(readRefreshRefusal(), {
|
|
1311
1341
|
bin: this.config.bin,
|
|
1312
|
-
cliId: this.cliConfig
|
|
1342
|
+
cliId: this.cliConfig ? (0, cli_id_1.resolveCliId)(this.cliConfig) : undefined,
|
|
1313
1343
|
});
|
|
1314
1344
|
const why = refusal
|
|
1315
1345
|
? refusal.message.replace(/\s*Run `[^`]+` to sign in again\.$/, '')
|
|
@@ -269,7 +269,8 @@ class AgentsChat extends base_command_1.BaseCommand {
|
|
|
269
269
|
'calls arrive as `tool.started` / `tool.completed`, once per `toolUseId`, on cloud ' +
|
|
270
270
|
'and local-harness runs alike. A `session.notice` with `notice: ' +
|
|
271
271
|
'"harness-session-reset"` means the local harness could not resume its previous ' +
|
|
272
|
-
'session and started a new one
|
|
272
|
+
'session and started a new one, which does not see earlier turns of the ' +
|
|
273
|
+
'conversation; `message` says why. A `permission.resolved` says ' +
|
|
273
274
|
'how a local harness permission request was decided: `decidedBy` in words, and ' +
|
|
274
275
|
'`basis` — `person` (with `surface`), `mode` (with `mode`), `policy` or ' +
|
|
275
276
|
'`session-grant` — beside the wire `by`.',
|
|
@@ -246,22 +246,30 @@ class AgentsCreate extends base_command_1.BaseCommand {
|
|
|
246
246
|
'09:00-18:00@America/Los_Angeles',
|
|
247
247
|
}),
|
|
248
248
|
// Retired (OSK-11975): a Commitment turn works under the Agent's own
|
|
249
|
-
// permissions,
|
|
249
|
+
// permissions. Hidden, warned on use, and never sent (OSK-12068) — see
|
|
250
|
+
// `mergeAgentConfigFlags`. Kept parseable so an old script still runs.
|
|
250
251
|
'reasoning-tool-escape': core_1.Flags.string({
|
|
251
|
-
description: "
|
|
252
|
+
description: "Retired — has no effect and is not sent. Commitment runs use the agent's own permissions.",
|
|
252
253
|
options: [...agent_config_1.REASONING_TOOL_ESCAPES],
|
|
253
254
|
multiple: true,
|
|
254
|
-
deprecated: { message: agent_config_1.
|
|
255
|
+
deprecated: { message: (0, agent_config_1.retiredCommitmentPermissionFlagMessage)('reasoning-tool-escape') },
|
|
256
|
+
hidden: true,
|
|
255
257
|
}),
|
|
256
258
|
'commitment-responsibility': core_1.Flags.string({
|
|
257
|
-
description: "
|
|
259
|
+
description: "Retired — has no effect and is not sent. Commitment runs use the agent's own permissions.",
|
|
258
260
|
options: [...agent_config_1.COMMITMENT_RESPONSIBILITY_LEVELS],
|
|
259
|
-
deprecated: {
|
|
261
|
+
deprecated: {
|
|
262
|
+
message: (0, agent_config_1.retiredCommitmentPermissionFlagMessage)('commitment-responsibility'),
|
|
263
|
+
},
|
|
264
|
+
hidden: true,
|
|
260
265
|
}),
|
|
261
266
|
'commitment-responsibility-ceiling': core_1.Flags.string({
|
|
262
|
-
description: "
|
|
267
|
+
description: "Retired — has no effect and is not sent. Commitment runs use the agent's own permissions.",
|
|
263
268
|
options: [...agent_config_1.COMMITMENT_RESPONSIBILITY_LEVELS],
|
|
264
|
-
deprecated: {
|
|
269
|
+
deprecated: {
|
|
270
|
+
message: (0, agent_config_1.retiredCommitmentPermissionFlagMessage)('commitment-responsibility-ceiling'),
|
|
271
|
+
},
|
|
272
|
+
hidden: true,
|
|
265
273
|
}),
|
|
266
274
|
'max-turns': core_1.Flags.integer({ description: 'Maximum turns in one agent run (1-500)' }),
|
|
267
275
|
'tool-resources': core_1.Flags.string({
|
|
@@ -389,7 +397,12 @@ class AgentsCreate extends base_command_1.BaseCommand {
|
|
|
389
397
|
// relationshipLoops is an input-only provisioning control. The server
|
|
390
398
|
// applies it by deciding which system commitments to seed, so it is not an
|
|
391
399
|
// Agent document field and is intentionally absent from the response.
|
|
392
|
-
const unapplied = unappliedPayloadKeys(payload, created, [
|
|
400
|
+
const unapplied = unappliedPayloadKeys(payload, created, [
|
|
401
|
+
'relationshipLoops',
|
|
402
|
+
...(0, agent_config_1.deprecatedFieldNames)(created),
|
|
403
|
+
]);
|
|
404
|
+
for (const warning of (0, agent_config_1.deprecatedFieldsWarnings)(created))
|
|
405
|
+
this.warn(warning);
|
|
393
406
|
if (unapplied.length > 0) {
|
|
394
407
|
this.warn(unappliedPayloadKeysWarning(unapplied));
|
|
395
408
|
}
|
|
@@ -18,6 +18,7 @@ export default class AgentsUpdate extends BaseCommand {
|
|
|
18
18
|
'next-target': import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
19
19
|
'working-directory': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
20
20
|
'default-permission-mode': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
21
|
+
'clear-default-permission-mode': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
21
22
|
tools: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
22
23
|
'model-parameters': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
23
24
|
'agent-ids': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
@@ -55,6 +56,60 @@ export default class AgentsUpdate extends BaseCommand {
|
|
|
55
56
|
};
|
|
56
57
|
run(): Promise<void>;
|
|
57
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* The warnings an `agents update` prints for what the server did with the
|
|
61
|
+
* PATCH, in order: one line per field the server reported as ignored, with the
|
|
62
|
+
* SERVER's reason (`deprecatedFields`, OSK-12068), then one line for keys that
|
|
63
|
+
* came back missing for no stated reason.
|
|
64
|
+
*
|
|
65
|
+
* That last check is the one `agents create` makes. Without it, a key the
|
|
66
|
+
* server does not model, a typo of one that it does (`max_turns` for
|
|
67
|
+
* `maxTurns`), or a field only the server sets was discarded with the same
|
|
68
|
+
* "Updated agent" line as a real change. It skips:
|
|
69
|
+
* - `removeProjectIds`, an instruction rather than a stored field;
|
|
70
|
+
* - every field the server already explained in `deprecatedFields`, so it is
|
|
71
|
+
* not ALSO called "not an agent field" with the generic guess;
|
|
72
|
+
* - every key sent as `null`. `null` is a clear, and a cleared field coming
|
|
73
|
+
* back absent (or `null`) is the clear working. `defaultPermissionMode: null`
|
|
74
|
+
* through `--from-json` was reported as "not an agent field" after the server
|
|
75
|
+
* had cleared it on purpose (OSK-12167).
|
|
76
|
+
*
|
|
77
|
+
* Exported for tests.
|
|
78
|
+
*/
|
|
79
|
+
export declare function agentUpdateWarnings(payload: Record<string, unknown>, updated: unknown): string[];
|
|
80
|
+
/**
|
|
81
|
+
* The per-Agent mutation fence's "another request holds this right now"
|
|
82
|
+
* refusals. `updateAgent` takes a short lease on the Agent before it writes, so
|
|
83
|
+
* a concurrent writer — or, until OSK-12138, a lagging replica read of a lease
|
|
84
|
+
* row the same request had just created — refuses the update with one of these.
|
|
85
|
+
* Nothing was written, so repeating the identical PATCH is safe.
|
|
86
|
+
*/
|
|
87
|
+
export declare const AGENT_FENCE_RETRY_CODES: ReadonlySet<string>;
|
|
88
|
+
/**
|
|
89
|
+
* Run an Agent write, and repeat it ONCE if the fence refused it as transient.
|
|
90
|
+
*
|
|
91
|
+
* Retried only when the server said both things: a fence code AND
|
|
92
|
+
* `retryable: true`. `MARKETPLACE_SOURCE_LIFECYCLE_BUSY` also covers a source
|
|
93
|
+
* that is being deleted or published, where the server answers
|
|
94
|
+
* `retryable: false`, and those must surface rather than be repeated. One retry,
|
|
95
|
+
* after a short pause, because the dogfooded case (OSK-12138) cleared on the
|
|
96
|
+
* first identical retry; anything that survives it is worth showing.
|
|
97
|
+
*
|
|
98
|
+
* Exported for tests.
|
|
99
|
+
*/
|
|
100
|
+
export declare function retryOnceOnAgentFence<T>(operation: () => Promise<T>, { delayMs }?: {
|
|
101
|
+
delayMs?: number;
|
|
102
|
+
}): Promise<T>;
|
|
103
|
+
/**
|
|
104
|
+
* The `defaultPermissionMode` a PATCH carries for these flags. `null` is the
|
|
105
|
+
* server's clear, the same spelling `harnesses update --clear-default-permission-mode`
|
|
106
|
+
* sends; before it, a mode once set could not be removed from the CLI at all —
|
|
107
|
+
* `""` fails the flag's options and `null` failed the server (OSK-12070).
|
|
108
|
+
*/
|
|
109
|
+
export declare function defaultPermissionModePatch(flags: {
|
|
110
|
+
'default-permission-mode'?: string;
|
|
111
|
+
'clear-default-permission-mode'?: boolean;
|
|
112
|
+
}): string | null | undefined;
|
|
58
113
|
/**
|
|
59
114
|
* What changing the agent's default permission mode actually changed
|
|
60
115
|
* (OSK-12124). It is the AGENT's default, so it governs every conversation,
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AGENT_FENCE_RETRY_CODES = void 0;
|
|
4
|
+
exports.agentUpdateWarnings = agentUpdateWarnings;
|
|
5
|
+
exports.retryOnceOnAgentFence = retryOnceOnAgentFence;
|
|
6
|
+
exports.defaultPermissionModePatch = defaultPermissionModePatch;
|
|
3
7
|
exports.defaultPermissionModeNote = defaultPermissionModeNote;
|
|
4
8
|
const core_1 = require("@oclif/core");
|
|
5
9
|
const data_provider_1 = require("@skrr-ai/data-provider");
|
|
@@ -56,6 +60,12 @@ class AgentsUpdate extends base_command_1.BaseCommand {
|
|
|
56
60
|
'One-shot harnesses (gemini, opencode, cursor, copilot, goose, pi, openclaw) can only run as ' +
|
|
57
61
|
'bypassPermissions; any other mode is refused for them.',
|
|
58
62
|
options: ['default', 'acceptEdits', 'bypassPermissions', 'plan'],
|
|
63
|
+
exclusive: ['clear-default-permission-mode'],
|
|
64
|
+
}),
|
|
65
|
+
'clear-default-permission-mode': core_1.Flags.boolean({
|
|
66
|
+
description: "Remove this agent's permission-mode override, so its harness's default (then the daemon's) " +
|
|
67
|
+
'applies. Allowed on every harness, including one-shot ones.',
|
|
68
|
+
exclusive: ['default-permission-mode'],
|
|
59
69
|
}),
|
|
60
70
|
tools: core_1.Flags.string({
|
|
61
71
|
description: 'Tools JSON array of plugin keys, e.g. \'["calculator"]\'. `skrr agents tools` lists the keys, ' +
|
|
@@ -99,22 +109,30 @@ class AgentsUpdate extends base_command_1.BaseCommand {
|
|
|
99
109
|
'09:00-18:00@America/Los_Angeles',
|
|
100
110
|
}),
|
|
101
111
|
// Retired (OSK-11975): a Commitment turn works under the Agent's own
|
|
102
|
-
// permissions,
|
|
112
|
+
// permissions. Hidden, warned on use, and never sent (OSK-12068) — see
|
|
113
|
+
// `mergeAgentConfigFlags`. Kept parseable so an old script still runs.
|
|
103
114
|
'reasoning-tool-escape': core_1.Flags.string({
|
|
104
|
-
description: "
|
|
115
|
+
description: "Retired — has no effect and is not sent. Commitment runs use the agent's own permissions.",
|
|
105
116
|
options: [...agent_config_1.REASONING_TOOL_ESCAPES],
|
|
106
117
|
multiple: true,
|
|
107
|
-
deprecated: { message: agent_config_1.
|
|
118
|
+
deprecated: { message: (0, agent_config_1.retiredCommitmentPermissionFlagMessage)('reasoning-tool-escape') },
|
|
119
|
+
hidden: true,
|
|
108
120
|
}),
|
|
109
121
|
'commitment-responsibility': core_1.Flags.string({
|
|
110
|
-
description: "
|
|
122
|
+
description: "Retired — has no effect and is not sent. Commitment runs use the agent's own permissions.",
|
|
111
123
|
options: [...agent_config_1.COMMITMENT_RESPONSIBILITY_LEVELS],
|
|
112
|
-
deprecated: {
|
|
124
|
+
deprecated: {
|
|
125
|
+
message: (0, agent_config_1.retiredCommitmentPermissionFlagMessage)('commitment-responsibility'),
|
|
126
|
+
},
|
|
127
|
+
hidden: true,
|
|
113
128
|
}),
|
|
114
129
|
'commitment-responsibility-ceiling': core_1.Flags.string({
|
|
115
|
-
description: "
|
|
130
|
+
description: "Retired — has no effect and is not sent. Commitment runs use the agent's own permissions.",
|
|
116
131
|
options: [...agent_config_1.COMMITMENT_RESPONSIBILITY_LEVELS],
|
|
117
|
-
deprecated: {
|
|
132
|
+
deprecated: {
|
|
133
|
+
message: (0, agent_config_1.retiredCommitmentPermissionFlagMessage)('commitment-responsibility-ceiling'),
|
|
134
|
+
},
|
|
135
|
+
hidden: true,
|
|
118
136
|
}),
|
|
119
137
|
'max-turns': core_1.Flags.integer({ description: 'Maximum turns in one agent run (1-500)' }),
|
|
120
138
|
'tool-resources': core_1.Flags.string({
|
|
@@ -171,7 +189,7 @@ class AgentsUpdate extends base_command_1.BaseCommand {
|
|
|
171
189
|
category: flags.category,
|
|
172
190
|
executionPolicy,
|
|
173
191
|
workingDirectory: flags['working-directory'],
|
|
174
|
-
defaultPermissionMode: flags
|
|
192
|
+
defaultPermissionMode: defaultPermissionModePatch(flags),
|
|
175
193
|
});
|
|
176
194
|
// `mergeAgentConfigFlags` parses the typed flags (avatar, scene background,
|
|
177
195
|
// working hours, every JSON flag). A parse failure is the caller's input, so
|
|
@@ -184,30 +202,24 @@ class AgentsUpdate extends base_command_1.BaseCommand {
|
|
|
184
202
|
this.error(err.message, { exit: 1 });
|
|
185
203
|
}
|
|
186
204
|
if (Object.keys(payload).length === 0) {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
205
|
+
const retired = agent_config_1.RETIRED_COMMITMENT_PERMISSION_FLAGS.filter((flag) => flags[flag] !== undefined);
|
|
206
|
+
this.error(retired.length > 0
|
|
207
|
+
? `Nothing to update: ${retired.map((flag) => `--${flag}`).join(', ')} ` +
|
|
208
|
+
`${retired.length === 1 ? 'is' : 'are'} retired and not sent. ${agent_config_1.COMMITMENT_PERMISSION_FLAG_RETIRED}`
|
|
209
|
+
: 'Nothing to update. Pass at least one flag (e.g. --name) or --from-json.', { exit: 1 });
|
|
190
210
|
}
|
|
191
211
|
let updated;
|
|
192
212
|
try {
|
|
193
|
-
updated = await data_provider_1.dataService.updateAgent({
|
|
213
|
+
updated = await retryOnceOnAgentFence(() => data_provider_1.dataService.updateAgent({
|
|
194
214
|
agent_id: args.id,
|
|
195
215
|
data: payload,
|
|
196
|
-
});
|
|
216
|
+
}));
|
|
197
217
|
}
|
|
198
218
|
catch (err) {
|
|
199
219
|
this.handleApiError(err);
|
|
200
220
|
}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
// field only the server sets was discarded with the same "Updated agent"
|
|
204
|
-
// line as a real change.
|
|
205
|
-
// `removeProjectIds` is an instruction, not a stored field, so it never
|
|
206
|
-
// comes back and is not reported.
|
|
207
|
-
const unapplied = (0, create_1.unappliedPayloadKeys)(payload, updated, ['removeProjectIds']);
|
|
208
|
-
if (unapplied.length > 0) {
|
|
209
|
-
this.warn((0, create_1.unappliedPayloadKeysWarning)(unapplied));
|
|
210
|
-
}
|
|
221
|
+
for (const warning of agentUpdateWarnings(payload, updated))
|
|
222
|
+
this.warn(warning);
|
|
211
223
|
await (0, runtime_tool_support_1.warnPluginsNotLoaded)(payload.tools, (message) => this.warn(message));
|
|
212
224
|
if (flags.json) {
|
|
213
225
|
this.log(JSON.stringify((0, web_url_1.withAgentUrl)(this.cliConfig.baseURL, updated), null, 2));
|
|
@@ -219,9 +231,93 @@ class AgentsUpdate extends base_command_1.BaseCommand {
|
|
|
219
231
|
const modeNote = defaultPermissionModeNote(flags['default-permission-mode'], a);
|
|
220
232
|
if (modeNote)
|
|
221
233
|
this.log(modeNote);
|
|
234
|
+
if (flags['clear-default-permission-mode']) {
|
|
235
|
+
this.log("Permission: cleared — this agent now follows its harness's default permission mode " +
|
|
236
|
+
"(then the daemon's), starting with each conversation's next turn.");
|
|
237
|
+
}
|
|
222
238
|
}
|
|
223
239
|
}
|
|
224
240
|
exports.default = AgentsUpdate;
|
|
241
|
+
/**
|
|
242
|
+
* The warnings an `agents update` prints for what the server did with the
|
|
243
|
+
* PATCH, in order: one line per field the server reported as ignored, with the
|
|
244
|
+
* SERVER's reason (`deprecatedFields`, OSK-12068), then one line for keys that
|
|
245
|
+
* came back missing for no stated reason.
|
|
246
|
+
*
|
|
247
|
+
* That last check is the one `agents create` makes. Without it, a key the
|
|
248
|
+
* server does not model, a typo of one that it does (`max_turns` for
|
|
249
|
+
* `maxTurns`), or a field only the server sets was discarded with the same
|
|
250
|
+
* "Updated agent" line as a real change. It skips:
|
|
251
|
+
* - `removeProjectIds`, an instruction rather than a stored field;
|
|
252
|
+
* - every field the server already explained in `deprecatedFields`, so it is
|
|
253
|
+
* not ALSO called "not an agent field" with the generic guess;
|
|
254
|
+
* - every key sent as `null`. `null` is a clear, and a cleared field coming
|
|
255
|
+
* back absent (or `null`) is the clear working. `defaultPermissionMode: null`
|
|
256
|
+
* through `--from-json` was reported as "not an agent field" after the server
|
|
257
|
+
* had cleared it on purpose (OSK-12167).
|
|
258
|
+
*
|
|
259
|
+
* Exported for tests.
|
|
260
|
+
*/
|
|
261
|
+
function agentUpdateWarnings(payload, updated) {
|
|
262
|
+
const cleared = Object.keys(payload).filter((key) => payload[key] === null);
|
|
263
|
+
const unapplied = (0, create_1.unappliedPayloadKeys)(payload, updated, [
|
|
264
|
+
'removeProjectIds',
|
|
265
|
+
...cleared,
|
|
266
|
+
...(0, agent_config_1.deprecatedFieldNames)(updated),
|
|
267
|
+
]);
|
|
268
|
+
return [
|
|
269
|
+
...(0, agent_config_1.deprecatedFieldsWarnings)(updated),
|
|
270
|
+
...(unapplied.length > 0 ? [(0, create_1.unappliedPayloadKeysWarning)(unapplied)] : []),
|
|
271
|
+
];
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* The per-Agent mutation fence's "another request holds this right now"
|
|
275
|
+
* refusals. `updateAgent` takes a short lease on the Agent before it writes, so
|
|
276
|
+
* a concurrent writer — or, until OSK-12138, a lagging replica read of a lease
|
|
277
|
+
* row the same request had just created — refuses the update with one of these.
|
|
278
|
+
* Nothing was written, so repeating the identical PATCH is safe.
|
|
279
|
+
*/
|
|
280
|
+
exports.AGENT_FENCE_RETRY_CODES = new Set([
|
|
281
|
+
'MARKETPLACE_SOURCE_LIFECYCLE_RACE',
|
|
282
|
+
'MARKETPLACE_SOURCE_LIFECYCLE_BUSY',
|
|
283
|
+
'MARKETPLACE_SOURCE_LIFECYCLE_CAS_LOST',
|
|
284
|
+
]);
|
|
285
|
+
/**
|
|
286
|
+
* Run an Agent write, and repeat it ONCE if the fence refused it as transient.
|
|
287
|
+
*
|
|
288
|
+
* Retried only when the server said both things: a fence code AND
|
|
289
|
+
* `retryable: true`. `MARKETPLACE_SOURCE_LIFECYCLE_BUSY` also covers a source
|
|
290
|
+
* that is being deleted or published, where the server answers
|
|
291
|
+
* `retryable: false`, and those must surface rather than be repeated. One retry,
|
|
292
|
+
* after a short pause, because the dogfooded case (OSK-12138) cleared on the
|
|
293
|
+
* first identical retry; anything that survives it is worth showing.
|
|
294
|
+
*
|
|
295
|
+
* Exported for tests.
|
|
296
|
+
*/
|
|
297
|
+
async function retryOnceOnAgentFence(operation, { delayMs = 750 } = {}) {
|
|
298
|
+
try {
|
|
299
|
+
return await operation();
|
|
300
|
+
}
|
|
301
|
+
catch (err) {
|
|
302
|
+
const { code, body } = (0, base_command_1.apiRefusal)(err);
|
|
303
|
+
if (!code || !exports.AGENT_FENCE_RETRY_CODES.has(code) || body?.retryable !== true)
|
|
304
|
+
throw err;
|
|
305
|
+
if (delayMs > 0)
|
|
306
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
307
|
+
return operation();
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* The `defaultPermissionMode` a PATCH carries for these flags. `null` is the
|
|
312
|
+
* server's clear, the same spelling `harnesses update --clear-default-permission-mode`
|
|
313
|
+
* sends; before it, a mode once set could not be removed from the CLI at all —
|
|
314
|
+
* `""` fails the flag's options and `null` failed the server (OSK-12070).
|
|
315
|
+
*/
|
|
316
|
+
function defaultPermissionModePatch(flags) {
|
|
317
|
+
if (flags['clear-default-permission-mode'])
|
|
318
|
+
return null;
|
|
319
|
+
return flags['default-permission-mode'];
|
|
320
|
+
}
|
|
225
321
|
/**
|
|
226
322
|
* What changing the agent's default permission mode actually changed
|
|
227
323
|
* (OSK-12124). It is the AGENT's default, so it governs every conversation,
|
|
@@ -86,10 +86,18 @@ function formatNextTick(projection) {
|
|
|
86
86
|
// dispatch-refusal loop can't hide behind an on_track projection.
|
|
87
87
|
if (projection.health || projection.liveness) {
|
|
88
88
|
const census = projection.liveness?.terminalDispositionCensus;
|
|
89
|
+
const plural = (count) => (count === 1 ? '' : 's');
|
|
89
90
|
const detail = [
|
|
90
91
|
projection.liveness?.diagnosisKind,
|
|
91
92
|
census?.workFailures
|
|
92
|
-
? `${census.workFailures} recent run failure${census.workFailures
|
|
93
|
+
? `${census.workFailures} recent run failure${plural(census.workFailures)}`
|
|
94
|
+
: null,
|
|
95
|
+
// A run that never started is not a failure of the work (OSK-12137).
|
|
96
|
+
census?.machineFailures
|
|
97
|
+
? `${census.machineFailures} run${plural(census.machineFailures)} never started`
|
|
98
|
+
: null,
|
|
99
|
+
census?.contractFailures
|
|
100
|
+
? `${census.contractFailures} result${plural(census.contractFailures)} rejected`
|
|
93
101
|
: null,
|
|
94
102
|
]
|
|
95
103
|
.filter(Boolean)
|
package/dist/commands/context.js
CHANGED
|
@@ -4,6 +4,7 @@ const core_1 = require("@oclif/core");
|
|
|
4
4
|
const data_provider_1 = require("@skrr-ai/data-provider");
|
|
5
5
|
const base_command_1 = require("../base-command");
|
|
6
6
|
const config_1 = require("../lib/config");
|
|
7
|
+
const cli_id_1 = require("../lib/cli-id");
|
|
7
8
|
const keychain_1 = require("../lib/keychain");
|
|
8
9
|
const cli_runtime_1 = require("../lib/cli-runtime");
|
|
9
10
|
const runtime_context_1 = require("../lib/runtime-context");
|
|
@@ -48,7 +49,7 @@ class Context extends base_command_1.BaseCommand {
|
|
|
48
49
|
profile: (0, keychain_1.getActiveProfile)(),
|
|
49
50
|
config: {
|
|
50
51
|
path: (0, config_1.getConfigPath)(),
|
|
51
|
-
cliId: this.cliConfig
|
|
52
|
+
cliId: (0, cli_id_1.resolveCliId)(this.cliConfig) ?? null,
|
|
52
53
|
},
|
|
53
54
|
auth: {
|
|
54
55
|
signedIn: Boolean(this.resolvedCredential?.token),
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const core_1 = require("@oclif/core");
|
|
4
4
|
const exec_runtime_binary_1 = require("../../lib/exec-runtime-binary");
|
|
5
|
+
const live_runtime_binary_1 = require("../../lib/live-runtime-binary");
|
|
5
6
|
/**
|
|
6
7
|
* `skrr daemon config` — set this machine's daemon configuration, including
|
|
7
8
|
* each tool category's permission mode (OSK-12099). Before this the only way
|
|
@@ -23,7 +24,21 @@ class DaemonConfig extends core_1.Command {
|
|
|
23
24
|
// flag this wrapper has not redeclared (OSK-296).
|
|
24
25
|
const argv = this.argv;
|
|
25
26
|
try {
|
|
26
|
-
|
|
27
|
+
// Config writes the file the LIVE daemon reloads, so it must be parsed by
|
|
28
|
+
// the live daemon's own binary — the same routing status uses (OSK-12102).
|
|
29
|
+
// Forwarding to the first `skrrd` on PATH let an older build accept
|
|
30
|
+
// `--auto-approve-bash banana` as false and reject `--mode`, then write
|
|
31
|
+
// the live daemon's config anyway (OSK-12099 verifier).
|
|
32
|
+
const pathBinary = (0, exec_runtime_binary_1.findRuntimeBinary)();
|
|
33
|
+
const decision = pathBinary
|
|
34
|
+
? (0, live_runtime_binary_1.decideStatusBinary)(pathBinary, (0, live_runtime_binary_1.profileFromArgs)(argv), (0, live_runtime_binary_1.defaultLiveRuntimeDeps)())
|
|
35
|
+
: null;
|
|
36
|
+
const note = decision ? (0, live_runtime_binary_1.describeStatusBinary)(decision) : null;
|
|
37
|
+
if (note)
|
|
38
|
+
process.stderr.write(`${note}\n`);
|
|
39
|
+
const code = await (0, exec_runtime_binary_1.execRuntimeBinary)(['config', ...argv], {
|
|
40
|
+
binary: decision?.binary,
|
|
41
|
+
});
|
|
27
42
|
this.exit(code);
|
|
28
43
|
}
|
|
29
44
|
catch (err) {
|
package/dist/commands/doctor.js
CHANGED
|
@@ -43,6 +43,7 @@ const auth_core_1 = require("@skrr-ai/auth-core");
|
|
|
43
43
|
const base_command_1 = require("../base-command");
|
|
44
44
|
const auth_storage_1 = require("../lib/auth-storage");
|
|
45
45
|
const config_1 = require("../lib/config");
|
|
46
|
+
const cli_id_1 = require("../lib/cli-id");
|
|
46
47
|
const keychain_1 = require("../lib/keychain");
|
|
47
48
|
const cli_runtime_1 = require("../lib/cli-runtime");
|
|
48
49
|
const command_manifest_1 = require("../lib/command-manifest");
|
|
@@ -272,6 +273,9 @@ class Doctor extends base_command_1.BaseCommand {
|
|
|
272
273
|
? ['auth_token_present_but_user_lookup_failed']
|
|
273
274
|
: []),
|
|
274
275
|
...(!checks.authToken ? ['not_signed_in_or_no_token_available'] : []),
|
|
276
|
+
...(credential.ownerStatus === 'foreign'
|
|
277
|
+
? ['stored_credential_belongs_to_another_skrr_config_run_skrr_login']
|
|
278
|
+
: []),
|
|
275
279
|
];
|
|
276
280
|
const payload = {
|
|
277
281
|
ok: issues.length === 0,
|
|
@@ -286,13 +290,16 @@ class Doctor extends base_command_1.BaseCommand {
|
|
|
286
290
|
profile: (0, keychain_1.getActiveProfile)(),
|
|
287
291
|
config: {
|
|
288
292
|
path: (0, config_1.getConfigPath)(),
|
|
289
|
-
cliId: this.cliConfig
|
|
293
|
+
cliId: (0, cli_id_1.resolveCliId)(this.cliConfig) ?? null,
|
|
290
294
|
},
|
|
291
295
|
auth: {
|
|
292
296
|
signedIn: Boolean(account),
|
|
293
297
|
tokenSource: this.credentialSourceLabel(),
|
|
294
298
|
tokenKind: this.resolvedCredential?.kind ?? null,
|
|
295
299
|
refreshAvailable: Boolean(credential.bundle?.refreshToken),
|
|
300
|
+
// OSK-12135 — why a stored credential was not used, when it belongs to
|
|
301
|
+
// another skrr config on this machine. Null when there is nothing to say.
|
|
302
|
+
storedCredentialOwner: (0, auth_storage_1.describeForeignCredential)(credential),
|
|
296
303
|
accessExpiresAt: accessExpiresAt ? new Date(accessExpiresAt).toISOString() : null,
|
|
297
304
|
accessExpired: accessExpiresAt ? accessExpiresAt <= now : null,
|
|
298
305
|
refreshExpiresAt: refreshExpiresAt ? new Date(refreshExpiresAt).toISOString() : null,
|
package/dist/commands/login.d.ts
CHANGED
|
@@ -44,7 +44,8 @@ export default class Login extends BaseCommand {
|
|
|
44
44
|
*
|
|
45
45
|
* `cliId` resolution priority:
|
|
46
46
|
* 1. `OVERSKY_CLI_ID` env var — explicit, wins over everything.
|
|
47
|
-
* 2. `cliConfig
|
|
47
|
+
* 2. `resolveCliId(cliConfig)` — the id a prior `skrr login` of this
|
|
48
|
+
* profile presents (its own, or the machine's).
|
|
48
49
|
* Honored so a same-machine restore "just works" without forcing
|
|
49
50
|
* the operator to re-export the cliId.
|
|
50
51
|
* 3. error — without a cliId the refresh row binding can't be matched
|