@skrr-ai/cli 0.1.52 → 0.1.54
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/commands/agents/chat.js +2 -1
- package/dist/commands/agents/create.js +13 -4
- package/dist/commands/agents/update.d.ts +32 -0
- package/dist/commands/agents/update.js +65 -14
- package/dist/commands/tasks/approvals/list.d.ts +58 -0
- package/dist/commands/tasks/approvals/list.js +173 -4
- package/dist/commands/tasks/create.js +2 -1
- package/dist/commands/tasks/start.js +1 -1
- package/dist/commands/tasks/update.js +23 -3
- package/dist/lib/agent-config.d.ts +13 -0
- package/dist/lib/agent-config.js +32 -0
- package/dist/lib/agentic-stream.d.ts +1 -1
- package/dist/lib/agentic-stream.js +1 -1
- package/dist/lib/auth-storage.js +7 -3
- package/dist/lib/cred-envelope.d.ts +12 -1
- package/dist/lib/cred-envelope.js +20 -3
- package/dist/lib/dedicated-machines.d.ts +20 -2
- package/dist/lib/dedicated-machines.js +46 -5
- package/dist/lib/keychain.js +26 -1
- package/dist/lib/label-ref.d.ts +1 -1
- package/dist/lib/label-ref.js +7 -3
- 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 +18 -8
- 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 +18 -9
- 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 +182 -175
- package/oclif.manifest.json +28759 -28747
- package/package.json +1 -1
|
@@ -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`.',
|
|
@@ -252,19 +252,23 @@ class AgentsCreate extends base_command_1.BaseCommand {
|
|
|
252
252
|
description: "Retired — has no effect and is not sent. Commitment runs use the agent's own permissions.",
|
|
253
253
|
options: [...agent_config_1.REASONING_TOOL_ESCAPES],
|
|
254
254
|
multiple: true,
|
|
255
|
-
deprecated: { message: agent_config_1.
|
|
255
|
+
deprecated: { message: (0, agent_config_1.retiredCommitmentPermissionFlagMessage)('reasoning-tool-escape') },
|
|
256
256
|
hidden: true,
|
|
257
257
|
}),
|
|
258
258
|
'commitment-responsibility': core_1.Flags.string({
|
|
259
259
|
description: "Retired — has no effect and is not sent. Commitment runs use the agent's own permissions.",
|
|
260
260
|
options: [...agent_config_1.COMMITMENT_RESPONSIBILITY_LEVELS],
|
|
261
|
-
deprecated: {
|
|
261
|
+
deprecated: {
|
|
262
|
+
message: (0, agent_config_1.retiredCommitmentPermissionFlagMessage)('commitment-responsibility'),
|
|
263
|
+
},
|
|
262
264
|
hidden: true,
|
|
263
265
|
}),
|
|
264
266
|
'commitment-responsibility-ceiling': core_1.Flags.string({
|
|
265
267
|
description: "Retired — has no effect and is not sent. Commitment runs use the agent's own permissions.",
|
|
266
268
|
options: [...agent_config_1.COMMITMENT_RESPONSIBILITY_LEVELS],
|
|
267
|
-
deprecated: {
|
|
269
|
+
deprecated: {
|
|
270
|
+
message: (0, agent_config_1.retiredCommitmentPermissionFlagMessage)('commitment-responsibility-ceiling'),
|
|
271
|
+
},
|
|
268
272
|
hidden: true,
|
|
269
273
|
}),
|
|
270
274
|
'max-turns': core_1.Flags.integer({ description: 'Maximum turns in one agent run (1-500)' }),
|
|
@@ -393,7 +397,12 @@ class AgentsCreate extends base_command_1.BaseCommand {
|
|
|
393
397
|
// relationshipLoops is an input-only provisioning control. The server
|
|
394
398
|
// applies it by deciding which system commitments to seed, so it is not an
|
|
395
399
|
// Agent document field and is intentionally absent from the response.
|
|
396
|
-
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);
|
|
397
406
|
if (unapplied.length > 0) {
|
|
398
407
|
this.warn(unappliedPayloadKeysWarning(unapplied));
|
|
399
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,27 @@ 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[];
|
|
58
80
|
/**
|
|
59
81
|
* The per-Agent mutation fence's "another request holds this right now"
|
|
60
82
|
* refusals. `updateAgent` takes a short lease on the Agent before it writes, so
|
|
@@ -78,6 +100,16 @@ export declare const AGENT_FENCE_RETRY_CODES: ReadonlySet<string>;
|
|
|
78
100
|
export declare function retryOnceOnAgentFence<T>(operation: () => Promise<T>, { delayMs }?: {
|
|
79
101
|
delayMs?: number;
|
|
80
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;
|
|
81
113
|
/**
|
|
82
114
|
* What changing the agent's default permission mode actually changed
|
|
83
115
|
* (OSK-12124). It is the AGENT's default, so it governs every conversation,
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.AGENT_FENCE_RETRY_CODES = void 0;
|
|
4
|
+
exports.agentUpdateWarnings = agentUpdateWarnings;
|
|
4
5
|
exports.retryOnceOnAgentFence = retryOnceOnAgentFence;
|
|
6
|
+
exports.defaultPermissionModePatch = defaultPermissionModePatch;
|
|
5
7
|
exports.defaultPermissionModeNote = defaultPermissionModeNote;
|
|
6
8
|
const core_1 = require("@oclif/core");
|
|
7
9
|
const data_provider_1 = require("@skrr-ai/data-provider");
|
|
@@ -58,6 +60,12 @@ class AgentsUpdate extends base_command_1.BaseCommand {
|
|
|
58
60
|
'One-shot harnesses (gemini, opencode, cursor, copilot, goose, pi, openclaw) can only run as ' +
|
|
59
61
|
'bypassPermissions; any other mode is refused for them.',
|
|
60
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'],
|
|
61
69
|
}),
|
|
62
70
|
tools: core_1.Flags.string({
|
|
63
71
|
description: 'Tools JSON array of plugin keys, e.g. \'["calculator"]\'. `skrr agents tools` lists the keys, ' +
|
|
@@ -107,19 +115,23 @@ class AgentsUpdate extends base_command_1.BaseCommand {
|
|
|
107
115
|
description: "Retired — has no effect and is not sent. Commitment runs use the agent's own permissions.",
|
|
108
116
|
options: [...agent_config_1.REASONING_TOOL_ESCAPES],
|
|
109
117
|
multiple: true,
|
|
110
|
-
deprecated: { message: agent_config_1.
|
|
118
|
+
deprecated: { message: (0, agent_config_1.retiredCommitmentPermissionFlagMessage)('reasoning-tool-escape') },
|
|
111
119
|
hidden: true,
|
|
112
120
|
}),
|
|
113
121
|
'commitment-responsibility': core_1.Flags.string({
|
|
114
122
|
description: "Retired — has no effect and is not sent. Commitment runs use the agent's own permissions.",
|
|
115
123
|
options: [...agent_config_1.COMMITMENT_RESPONSIBILITY_LEVELS],
|
|
116
|
-
deprecated: {
|
|
124
|
+
deprecated: {
|
|
125
|
+
message: (0, agent_config_1.retiredCommitmentPermissionFlagMessage)('commitment-responsibility'),
|
|
126
|
+
},
|
|
117
127
|
hidden: true,
|
|
118
128
|
}),
|
|
119
129
|
'commitment-responsibility-ceiling': core_1.Flags.string({
|
|
120
130
|
description: "Retired — has no effect and is not sent. Commitment runs use the agent's own permissions.",
|
|
121
131
|
options: [...agent_config_1.COMMITMENT_RESPONSIBILITY_LEVELS],
|
|
122
|
-
deprecated: {
|
|
132
|
+
deprecated: {
|
|
133
|
+
message: (0, agent_config_1.retiredCommitmentPermissionFlagMessage)('commitment-responsibility-ceiling'),
|
|
134
|
+
},
|
|
123
135
|
hidden: true,
|
|
124
136
|
}),
|
|
125
137
|
'max-turns': core_1.Flags.integer({ description: 'Maximum turns in one agent run (1-500)' }),
|
|
@@ -177,7 +189,7 @@ class AgentsUpdate extends base_command_1.BaseCommand {
|
|
|
177
189
|
category: flags.category,
|
|
178
190
|
executionPolicy,
|
|
179
191
|
workingDirectory: flags['working-directory'],
|
|
180
|
-
defaultPermissionMode: flags
|
|
192
|
+
defaultPermissionMode: defaultPermissionModePatch(flags),
|
|
181
193
|
});
|
|
182
194
|
// `mergeAgentConfigFlags` parses the typed flags (avatar, scene background,
|
|
183
195
|
// working hours, every JSON flag). A parse failure is the caller's input, so
|
|
@@ -206,16 +218,8 @@ class AgentsUpdate extends base_command_1.BaseCommand {
|
|
|
206
218
|
catch (err) {
|
|
207
219
|
this.handleApiError(err);
|
|
208
220
|
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
// field only the server sets was discarded with the same "Updated agent"
|
|
212
|
-
// line as a real change.
|
|
213
|
-
// `removeProjectIds` is an instruction, not a stored field, so it never
|
|
214
|
-
// comes back and is not reported.
|
|
215
|
-
const unapplied = (0, create_1.unappliedPayloadKeys)(payload, updated, ['removeProjectIds']);
|
|
216
|
-
if (unapplied.length > 0) {
|
|
217
|
-
this.warn((0, create_1.unappliedPayloadKeysWarning)(unapplied));
|
|
218
|
-
}
|
|
221
|
+
for (const warning of agentUpdateWarnings(payload, updated))
|
|
222
|
+
this.warn(warning);
|
|
219
223
|
await (0, runtime_tool_support_1.warnPluginsNotLoaded)(payload.tools, (message) => this.warn(message));
|
|
220
224
|
if (flags.json) {
|
|
221
225
|
this.log(JSON.stringify((0, web_url_1.withAgentUrl)(this.cliConfig.baseURL, updated), null, 2));
|
|
@@ -227,9 +231,45 @@ class AgentsUpdate extends base_command_1.BaseCommand {
|
|
|
227
231
|
const modeNote = defaultPermissionModeNote(flags['default-permission-mode'], a);
|
|
228
232
|
if (modeNote)
|
|
229
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
|
+
}
|
|
230
238
|
}
|
|
231
239
|
}
|
|
232
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
|
+
}
|
|
233
273
|
/**
|
|
234
274
|
* The per-Agent mutation fence's "another request holds this right now"
|
|
235
275
|
* refusals. `updateAgent` takes a short lease on the Agent before it writes, so
|
|
@@ -267,6 +307,17 @@ async function retryOnceOnAgentFence(operation, { delayMs = 750 } = {}) {
|
|
|
267
307
|
return operation();
|
|
268
308
|
}
|
|
269
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
|
+
}
|
|
270
321
|
/**
|
|
271
322
|
* What changing the agent's default permission mode actually changed
|
|
272
323
|
* (OSK-12124). It is the AGENT's default, so it governs every conversation,
|
|
@@ -14,6 +14,64 @@ export declare function approvalRow(a: Record<string, unknown>): {
|
|
|
14
14
|
approver: string;
|
|
15
15
|
created: string;
|
|
16
16
|
};
|
|
17
|
+
/**
|
|
18
|
+
* OSK-12165 — the task's latest run, when it ended blocked on a permission
|
|
19
|
+
* nobody could grant.
|
|
20
|
+
*
|
|
21
|
+
* An unattended run (`tasks start`) cannot ask anyone: the permission bridge
|
|
22
|
+
* declines every ask at once, so no approval row is ever created, and this
|
|
23
|
+
* command used to answer "No approvals." in exactly the case `tasks start`
|
|
24
|
+
* sends people here for. The evidence lives on the run, not in the approvals
|
|
25
|
+
* collection: the leaf finalizer settles it with `error.code`
|
|
26
|
+
* `permission_blocked`, which `GET /tasks/:id/runs` projects as `failureCode`
|
|
27
|
+
* alongside the sentence (what was refused, and the fix) as `failureReason`.
|
|
28
|
+
*
|
|
29
|
+
* Only the LATEST run counts (the route sorts newest first): a permission block
|
|
30
|
+
* on an older run that a later run got past is history, not the current state.
|
|
31
|
+
*/
|
|
32
|
+
export declare function permissionBlockedRun(runs: Array<Record<string, unknown>>): Record<string, unknown> | null;
|
|
33
|
+
/** One refused call: the tool and its input, as the run recorded them. */
|
|
34
|
+
export type PermissionRefusal = {
|
|
35
|
+
tool: string | null;
|
|
36
|
+
input: string;
|
|
37
|
+
};
|
|
38
|
+
/** One way forward, as a command a person or a script can run. */
|
|
39
|
+
export type PermissionBlockRemedy = {
|
|
40
|
+
action: 'run_interactively' | 'allow_unattended' | 'restart';
|
|
41
|
+
command: string;
|
|
42
|
+
description: string;
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* The structured answer `--json` carries as `blockedRun` (OSK-12173). Before
|
|
46
|
+
* this the explanation existed only as prose, and with `--json` only on
|
|
47
|
+
* stderr, so a script reading the JSON saw `{"data":[]}` and nothing to act on.
|
|
48
|
+
*/
|
|
49
|
+
export type BlockedRunSummary = {
|
|
50
|
+
runId: string;
|
|
51
|
+
failureCode: string;
|
|
52
|
+
/** The cause, from the run's own sentence, without its generic remedy. */
|
|
53
|
+
reason: string | null;
|
|
54
|
+
agentId: string | null;
|
|
55
|
+
/**
|
|
56
|
+
* The refused calls with their inputs. Empty when the run recorded none
|
|
57
|
+
* (runs finalized before the server kept them, or evidence without an input
|
|
58
|
+
* preview). Never inferred.
|
|
59
|
+
*/
|
|
60
|
+
refused: PermissionRefusal[];
|
|
61
|
+
remedies: PermissionBlockRemedy[];
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* The block as data: which run, why, what was refused, and the ways forward
|
|
65
|
+
* with the agent id filled in (`<agent>` only when the run recorded no agent).
|
|
66
|
+
*/
|
|
67
|
+
export declare function blockedRunSummary(run: Record<string, unknown>, bin?: string, taskRef?: string): BlockedRunSummary;
|
|
68
|
+
/**
|
|
69
|
+
* What to print for such a run: why it is blocked, what needed approval (the
|
|
70
|
+
* run's own sentence, plus each refused input it recorded), that an unattended
|
|
71
|
+
* run cannot prompt, and the ways forward, each stated once with the real
|
|
72
|
+
* agent id.
|
|
73
|
+
*/
|
|
74
|
+
export declare function permissionBlockLines(run: Record<string, unknown>, bin?: string, taskRef?: string): string[];
|
|
17
75
|
export default class TasksApprovalsList extends BaseCommand {
|
|
18
76
|
static description: string;
|
|
19
77
|
static examples: string[];
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.approvalRow = approvalRow;
|
|
4
|
+
exports.permissionBlockedRun = permissionBlockedRun;
|
|
5
|
+
exports.blockedRunSummary = blockedRunSummary;
|
|
6
|
+
exports.permissionBlockLines = permissionBlockLines;
|
|
4
7
|
const core_1 = require("@oclif/core");
|
|
5
8
|
const data_provider_1 = require("@skrr-ai/data-provider");
|
|
6
9
|
const base_command_1 = require("../../../base-command");
|
|
@@ -22,6 +25,131 @@ function approvalRow(a) {
|
|
|
22
25
|
created: a.createdAt ? (0, format_1.localTime)(a.createdAt) : '-',
|
|
23
26
|
};
|
|
24
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* OSK-12165 — the task's latest run, when it ended blocked on a permission
|
|
30
|
+
* nobody could grant.
|
|
31
|
+
*
|
|
32
|
+
* An unattended run (`tasks start`) cannot ask anyone: the permission bridge
|
|
33
|
+
* declines every ask at once, so no approval row is ever created, and this
|
|
34
|
+
* command used to answer "No approvals." in exactly the case `tasks start`
|
|
35
|
+
* sends people here for. The evidence lives on the run, not in the approvals
|
|
36
|
+
* collection: the leaf finalizer settles it with `error.code`
|
|
37
|
+
* `permission_blocked`, which `GET /tasks/:id/runs` projects as `failureCode`
|
|
38
|
+
* alongside the sentence (what was refused, and the fix) as `failureReason`.
|
|
39
|
+
*
|
|
40
|
+
* Only the LATEST run counts (the route sorts newest first): a permission block
|
|
41
|
+
* on an older run that a later run got past is history, not the current state.
|
|
42
|
+
*/
|
|
43
|
+
function permissionBlockedRun(runs) {
|
|
44
|
+
const latest = runs[0];
|
|
45
|
+
if (!latest)
|
|
46
|
+
return null;
|
|
47
|
+
const code = latest.failureCode ?? latest.failureClass;
|
|
48
|
+
return code === data_provider_1.PERMISSION_BLOCKED_FAILURE_CODE ? latest : null;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Where the run's own fix begins in its recorded sentence.
|
|
52
|
+
*
|
|
53
|
+
* The server writes `failureReason` as the cause followed by the fix
|
|
54
|
+
* (`permissionBlockedRun.PERMISSION_BLOCK_GUIDANCE`), and that fix names the
|
|
55
|
+
* agent only as `<agent>` because the sentence is shared by every surface.
|
|
56
|
+
* This command knows the agent, so it states the fix itself, with the real id,
|
|
57
|
+
* and keeps only the cause from the sentence (OSK-12173): printing both showed
|
|
58
|
+
* the remedy twice, once with a placeholder.
|
|
59
|
+
*/
|
|
60
|
+
const RECORDED_REMEDY_MARKER = 'Re-running will be refused';
|
|
61
|
+
function refusalsOf(run) {
|
|
62
|
+
const raw = run.permissionRefusals;
|
|
63
|
+
if (!Array.isArray(raw))
|
|
64
|
+
return [];
|
|
65
|
+
const out = [];
|
|
66
|
+
for (const entry of raw) {
|
|
67
|
+
if (!entry || typeof entry !== 'object')
|
|
68
|
+
continue;
|
|
69
|
+
const { tool, input } = entry;
|
|
70
|
+
if (typeof input !== 'string' || !input.trim())
|
|
71
|
+
continue;
|
|
72
|
+
out.push({ tool: typeof tool === 'string' && tool ? tool : null, input: input.trim() });
|
|
73
|
+
}
|
|
74
|
+
return out;
|
|
75
|
+
}
|
|
76
|
+
/** The cause, cut before the recorded remedy; `<agent>` filled in if any survives. */
|
|
77
|
+
function blockReason(run, agentId) {
|
|
78
|
+
const full = typeof run.failureReason === 'string' ? run.failureReason.trim() : '';
|
|
79
|
+
if (!full)
|
|
80
|
+
return null;
|
|
81
|
+
const at = full.indexOf(RECORDED_REMEDY_MARKER);
|
|
82
|
+
const cause = at > 0 ? full.slice(0, at).trim() : full;
|
|
83
|
+
return agentId ? cause.split('<agent>').join(agentId) : cause;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* The block as data: which run, why, what was refused, and the ways forward
|
|
87
|
+
* with the agent id filled in (`<agent>` only when the run recorded no agent).
|
|
88
|
+
*/
|
|
89
|
+
function blockedRunSummary(run, bin = 'skrr', taskRef = '<task-id>') {
|
|
90
|
+
const executor = run.executor;
|
|
91
|
+
const agentId = executor && typeof executor.agentId === 'string' && executor.agentId ? executor.agentId : null;
|
|
92
|
+
const agent = agentId ?? '<agent>';
|
|
93
|
+
const code = run.failureCode ?? run.failureClass;
|
|
94
|
+
return {
|
|
95
|
+
runId: String(run.runId ?? run.id ?? '-'),
|
|
96
|
+
failureCode: typeof code === 'string' ? code : data_provider_1.PERMISSION_BLOCKED_FAILURE_CODE,
|
|
97
|
+
reason: blockReason(run, agentId),
|
|
98
|
+
agentId,
|
|
99
|
+
refused: refusalsOf(run),
|
|
100
|
+
remedies: [
|
|
101
|
+
{
|
|
102
|
+
action: 'run_interactively',
|
|
103
|
+
command: `${bin} agents chat ${agent} --approve-tools --prompt "..."`,
|
|
104
|
+
description: 'Do the work interactively, approving each call.',
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
action: 'allow_unattended',
|
|
108
|
+
command: `${bin} agents update ${agent} --default-permission-mode bypassPermissions`,
|
|
109
|
+
description: 'Let unattended runs use the tool without asking.',
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
action: 'restart',
|
|
113
|
+
command: `${bin} tasks start ${taskRef}`,
|
|
114
|
+
description: 'Start the task again once its agent may use the tool (or pick an agent/harness whose mode allows it).',
|
|
115
|
+
},
|
|
116
|
+
],
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
const SHELL_TOOL = /^(bash|shell|local_shell|exec_command|commandexecution|command_execution)$/i;
|
|
120
|
+
function toolLabel(tool) {
|
|
121
|
+
if (!tool)
|
|
122
|
+
return '';
|
|
123
|
+
return SHELL_TOOL.test(tool) ? ' (shell)' : ` (${tool})`;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* What to print for such a run: why it is blocked, what needed approval (the
|
|
127
|
+
* run's own sentence, plus each refused input it recorded), that an unattended
|
|
128
|
+
* run cannot prompt, and the ways forward, each stated once with the real
|
|
129
|
+
* agent id.
|
|
130
|
+
*/
|
|
131
|
+
function permissionBlockLines(run, bin = 'skrr', taskRef = '<task-id>') {
|
|
132
|
+
const summary = blockedRunSummary(run, bin, taskRef);
|
|
133
|
+
const reason = summary.reason ?? '';
|
|
134
|
+
// The sentence already quotes a lone refused input (shortened); list an
|
|
135
|
+
// input only when the sentence does not already show it.
|
|
136
|
+
const refusedLines = summary.refused
|
|
137
|
+
.filter((refusal) => !reason.includes(refusal.input.slice(0, 60)))
|
|
138
|
+
.map((refusal) => ` Refused: ${refusal.input}${toolLabel(refusal.tool)}`);
|
|
139
|
+
const [chat, update, restart] = summary.remedies;
|
|
140
|
+
return [
|
|
141
|
+
`The latest run (${summary.runId}) is blocked on a permission, not on an approval request.`,
|
|
142
|
+
...(reason ? [` ${reason}`] : []),
|
|
143
|
+
...refusedLines,
|
|
144
|
+
'It ran unattended (`tasks start`), and an unattended run cannot prompt anyone to approve a',
|
|
145
|
+
'tool call, so the ask was refused on the spot and no approval request was recorded here.',
|
|
146
|
+
'',
|
|
147
|
+
'To continue, either:',
|
|
148
|
+
` ${chat.command} do the work interactively, approving each call`,
|
|
149
|
+
` ${update.command} let unattended runs use the tool`,
|
|
150
|
+
` then: ${restart.command} (or pick an agent/harness whose mode allows it)`,
|
|
151
|
+
];
|
|
152
|
+
}
|
|
25
153
|
class TasksApprovalsList extends base_command_1.BaseCommand {
|
|
26
154
|
static description = 'List approvals for a task';
|
|
27
155
|
static examples = [
|
|
@@ -44,14 +172,50 @@ class TasksApprovalsList extends base_command_1.BaseCommand {
|
|
|
44
172
|
catch (err) {
|
|
45
173
|
this.handleApiError(err);
|
|
46
174
|
}
|
|
175
|
+
const r = response;
|
|
176
|
+
const items = r?.data ?? [];
|
|
177
|
+
// OSK-12165 — an unattended run blocked on a permission leaves no approval
|
|
178
|
+
// row, so the empty list alone would say "nothing to act on" about a task
|
|
179
|
+
// that is waiting on exactly that. Read the latest run, best-effort: the
|
|
180
|
+
// approvals list is still the answer if the runs read fails.
|
|
181
|
+
// `runsRead` separates "the latest run is not blocked" from "we could not
|
|
182
|
+
// look": only the first may be reported to a script as `blockedRun: null`.
|
|
183
|
+
let blockedRun = null;
|
|
184
|
+
let runsRead = false;
|
|
185
|
+
try {
|
|
186
|
+
const runs = (await data_provider_1.dataService.getTaskRuns(args.id));
|
|
187
|
+
blockedRun = permissionBlockedRun(runs?.data ?? []);
|
|
188
|
+
runsRead = true;
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
blockedRun = null;
|
|
192
|
+
}
|
|
47
193
|
if (flags.json) {
|
|
48
|
-
|
|
194
|
+
// OSK-12173 — the server payload is kept as it was (`data` unchanged)
|
|
195
|
+
// and the block is added beside it as data a script can act on:
|
|
196
|
+
// `blockedRun` is the summary, or null when the latest run is not
|
|
197
|
+
// blocked. It is omitted when the runs could not be read, because null
|
|
198
|
+
// would claim a state nobody checked.
|
|
199
|
+
const payload = response && typeof response === 'object' && !Array.isArray(response)
|
|
200
|
+
? response
|
|
201
|
+
: { data: items };
|
|
202
|
+
const out = runsRead
|
|
203
|
+
? {
|
|
204
|
+
...payload,
|
|
205
|
+
blockedRun: blockedRun ? blockedRunSummary(blockedRun, this.config.bin, args.id) : null,
|
|
206
|
+
}
|
|
207
|
+
: payload;
|
|
208
|
+
this.log(JSON.stringify(out, null, 2));
|
|
49
209
|
return;
|
|
50
210
|
}
|
|
51
|
-
const
|
|
52
|
-
const items = r?.data ?? [];
|
|
211
|
+
const blockLines = blockedRun ? permissionBlockLines(blockedRun, this.config.bin, args.id) : [];
|
|
53
212
|
if (items.length === 0) {
|
|
54
|
-
this.log('No
|
|
213
|
+
this.log('No approval requests.');
|
|
214
|
+
if (blockLines.length > 0) {
|
|
215
|
+
this.log('');
|
|
216
|
+
for (const line of blockLines)
|
|
217
|
+
this.log(line);
|
|
218
|
+
}
|
|
55
219
|
return;
|
|
56
220
|
}
|
|
57
221
|
const rows = items.map(approvalRow);
|
|
@@ -62,6 +226,11 @@ class TasksApprovalsList extends base_command_1.BaseCommand {
|
|
|
62
226
|
{ key: 'approver', header: 'APPROVER', maxWidth: 30 },
|
|
63
227
|
{ key: 'created', header: 'CREATED' },
|
|
64
228
|
], (line) => this.log(line));
|
|
229
|
+
if (blockLines.length > 0) {
|
|
230
|
+
this.log('');
|
|
231
|
+
for (const line of blockLines)
|
|
232
|
+
this.log(line);
|
|
233
|
+
}
|
|
65
234
|
}
|
|
66
235
|
}
|
|
67
236
|
exports.default = TasksApprovalsList;
|
|
@@ -238,7 +238,8 @@ class TasksCreate extends base_command_1.BaseCommand {
|
|
|
238
238
|
'label-id': core_1.Flags.string({
|
|
239
239
|
aliases: ['label'],
|
|
240
240
|
description: 'Attach a task label (repeatable) — id, or a name from `skrr labels list --applicable-to task`. ' +
|
|
241
|
-
'An unknown name is refused, never created.
|
|
241
|
+
'An unknown name is refused, never created. Also accepted as --label. ' +
|
|
242
|
+
'Manage labels with `skrr labels`.',
|
|
242
243
|
multiple: true,
|
|
243
244
|
}),
|
|
244
245
|
'working-directory': core_1.Flags.string({
|
|
@@ -137,7 +137,7 @@ function startFollowUpLines(bin, taskRef) {
|
|
|
137
137
|
'Follow it:',
|
|
138
138
|
` ${bin} tasks watch ${taskRef} status until it finishes or fails`,
|
|
139
139
|
` ${bin} tasks transcript ${taskRef} what the agent has said so far`,
|
|
140
|
-
` ${bin} tasks approvals list ${taskRef}
|
|
140
|
+
` ${bin} tasks approvals list ${taskRef} approvals waiting on you, or why a run is blocked on one`,
|
|
141
141
|
'On a local machine, the run may add its instruction file (AGENTS.md / CLAUDE.md) to the',
|
|
142
142
|
'working directory while it runs; files the run wrote are removed afterwards unless changed.',
|
|
143
143
|
];
|
|
@@ -163,7 +163,8 @@ class TasksUpdate extends base_command_1.BaseCommand {
|
|
|
163
163
|
}),
|
|
164
164
|
'label-id': core_1.Flags.string({
|
|
165
165
|
aliases: ['label'],
|
|
166
|
-
description: 'Replace the task labels
|
|
166
|
+
description: 'Replace the task labels (repeatable, whole-list) — id, or a name from ' +
|
|
167
|
+
'`skrr labels list --applicable-to task`. Also accepted as --label. ' +
|
|
167
168
|
'Use `skrr tasks labels attach/detach` for incremental changes.',
|
|
168
169
|
multiple: true,
|
|
169
170
|
}),
|
|
@@ -322,6 +323,25 @@ class TasksUpdate extends base_command_1.BaseCommand {
|
|
|
322
323
|
}
|
|
323
324
|
}
|
|
324
325
|
}
|
|
326
|
+
// Same resolution as `tasks create`: the `--tag` warning below suggests
|
|
327
|
+
// `--label-id <name>`, so this flag has to accept the name it suggests
|
|
328
|
+
// (OSK-12166). Ids pass through untouched.
|
|
329
|
+
let resolvedLabelIds;
|
|
330
|
+
if (flags['label-id'] !== undefined) {
|
|
331
|
+
try {
|
|
332
|
+
resolvedLabelIds = await (0, label_ref_1.resolveLabelRefs)(flags['label-id'], {
|
|
333
|
+
applicableTo: 'task',
|
|
334
|
+
...(process.env.OVERSKY_WORKSPACE_ID
|
|
335
|
+
? { workspaceId: process.env.OVERSKY_WORKSPACE_ID }
|
|
336
|
+
: {}),
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
catch (err) {
|
|
340
|
+
if (err instanceof label_ref_1.LabelRefError)
|
|
341
|
+
this.error(err.message, { exit: 1 });
|
|
342
|
+
this.handleApiError(err);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
325
345
|
const payload = (0, prompt_1.mergeDefined)((0, task_resolver_1.stripTaskIdentityFields)(body), {
|
|
326
346
|
title: flags.title,
|
|
327
347
|
expectations: expectationsPatch === null ? [] : (expectationsPatch ?? undefined),
|
|
@@ -353,7 +373,7 @@ class TasksUpdate extends base_command_1.BaseCommand {
|
|
|
353
373
|
addBlocksIds: flags['add-blocks'],
|
|
354
374
|
removeBlocksIds: flags['remove-blocks'],
|
|
355
375
|
tags: flags.tag,
|
|
356
|
-
labelIds:
|
|
376
|
+
labelIds: resolvedLabelIds,
|
|
357
377
|
// Worktree isolation override. --worktree ⇒ 'ephemeral', --no-worktree ⇒
|
|
358
378
|
// 'shared', omitted ⇒ unchanged. (Clear-to-inherit via --from-json.)
|
|
359
379
|
executionIsolation: (0, tasks_2.executionIsolationFromWorktreeFlag)(flags.worktree),
|
|
@@ -448,7 +468,7 @@ class TasksUpdate extends base_command_1.BaseCommand {
|
|
|
448
468
|
const estimateWarning = (0, create_1.droppedEstimateWarning)(flags.estimate, updated, updated?.spaceId);
|
|
449
469
|
if (estimateWarning)
|
|
450
470
|
this.warn(estimateWarning);
|
|
451
|
-
const tagWarning = await (0, create_1.taskTagLabelWarning)(flags.tag,
|
|
471
|
+
const tagWarning = await (0, create_1.taskTagLabelWarning)(flags.tag, resolvedLabelIds);
|
|
452
472
|
if (tagWarning)
|
|
453
473
|
this.warn(tagWarning);
|
|
454
474
|
if (flags.json) {
|
|
@@ -4,6 +4,19 @@ export declare const REASONING_TOOL_ESCAPES: readonly ["bash", "browser"];
|
|
|
4
4
|
export declare const COMMITMENT_PERMISSION_FLAG_RETIRED = "This setting no longer has any effect: Commitment runs use the agent's own permissions.";
|
|
5
5
|
/** The three retired flags themselves: accepted at parse, warned on, never sent. */
|
|
6
6
|
export declare const RETIRED_COMMITMENT_PERMISSION_FLAGS: readonly ["reasoning-tool-escape", "commitment-responsibility", "commitment-responsibility-ceiling"];
|
|
7
|
+
/**
|
|
8
|
+
* The deprecation warning ONE retired flag prints. oclif prints a
|
|
9
|
+
* `deprecated.message` verbatim, so a shared message printed the same line
|
|
10
|
+
* once per flag with nothing saying which flag each one was about (OSK-12068).
|
|
11
|
+
*/
|
|
12
|
+
export declare function retiredCommitmentPermissionFlagMessage(flag: (typeof RETIRED_COMMITMENT_PERMISSION_FLAGS)[number]): string;
|
|
13
|
+
/**
|
|
14
|
+
* The fields the server ignored and why, from a write's `deprecatedFields`
|
|
15
|
+
* notice, one line each. Empty when the response carries none.
|
|
16
|
+
*/
|
|
17
|
+
export declare function deprecatedFieldsWarnings(response: unknown): string[];
|
|
18
|
+
/** The payload keys the server reported as ignored, so they are not also called "not kept". */
|
|
19
|
+
export declare function deprecatedFieldNames(response: unknown): string[];
|
|
7
20
|
export declare const RELEASE_INSTALL_CHANNELS: readonly ["latest", "manual"];
|
|
8
21
|
/**
|
|
9
22
|
* `09:00-18:00` or `09:00-18:00@America/Los_Angeles`, or a JSON object.
|