@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.
Files changed (67) hide show
  1. package/dist/base-command.d.ts +7 -0
  2. package/dist/base-command.js +38 -8
  3. package/dist/commands/agents/chat.js +2 -1
  4. package/dist/commands/agents/create.js +21 -8
  5. package/dist/commands/agents/update.d.ts +55 -0
  6. package/dist/commands/agents/update.js +119 -23
  7. package/dist/commands/commitments/next-tick.js +9 -1
  8. package/dist/commands/context.js +2 -1
  9. package/dist/commands/daemon/config.js +16 -1
  10. package/dist/commands/doctor.js +8 -1
  11. package/dist/commands/login.d.ts +2 -1
  12. package/dist/commands/login.js +26 -7
  13. package/dist/commands/logout.js +3 -0
  14. package/dist/commands/machines/dedicated/show.js +25 -2
  15. package/dist/commands/tasks/approvals/list.d.ts +22 -0
  16. package/dist/commands/tasks/approvals/list.js +80 -3
  17. package/dist/commands/tasks/create.d.ts +5 -0
  18. package/dist/commands/tasks/create.js +28 -7
  19. package/dist/commands/tasks/start.d.ts +14 -0
  20. package/dist/commands/tasks/start.js +31 -1
  21. package/dist/commands/tasks/update.js +30 -3
  22. package/dist/commands/whoami.js +5 -3
  23. package/dist/lib/agent-config.d.ts +15 -0
  24. package/dist/lib/agent-config.js +46 -10
  25. package/dist/lib/agentic-stream.d.ts +21 -1
  26. package/dist/lib/agentic-stream.js +40 -6
  27. package/dist/lib/auth-failure.js +4 -1
  28. package/dist/lib/auth-storage.d.ts +24 -0
  29. package/dist/lib/auth-storage.js +348 -13
  30. package/dist/lib/cli-id.d.ts +22 -0
  31. package/dist/lib/cli-id.js +56 -0
  32. package/dist/lib/cli-identity.d.ts +49 -0
  33. package/dist/lib/cli-identity.js +159 -0
  34. package/dist/lib/commitments.d.ts +4 -0
  35. package/dist/lib/commitments.js +12 -1
  36. package/dist/lib/config.d.ts +17 -0
  37. package/dist/lib/config.js +15 -3
  38. package/dist/lib/cred-envelope.d.ts +12 -1
  39. package/dist/lib/cred-envelope.js +20 -3
  40. package/dist/lib/daemonBroker.d.ts +26 -4
  41. package/dist/lib/daemonBroker.js +82 -10
  42. package/dist/lib/daemonBrokerRefusal.d.ts +51 -0
  43. package/dist/lib/daemonBrokerRefusal.js +127 -0
  44. package/dist/lib/dedicated-machines.d.ts +53 -4
  45. package/dist/lib/dedicated-machines.js +95 -5
  46. package/dist/lib/delegated-cli.js +16 -0
  47. package/dist/lib/first-party-harness-broker.js +6 -4
  48. package/dist/lib/keychain.d.ts +39 -0
  49. package/dist/lib/keychain.js +254 -40
  50. package/dist/lib/label-ref.d.ts +34 -0
  51. package/dist/lib/label-ref.js +72 -1
  52. package/dist/lib/login.js +24 -12
  53. package/dist/lib/oauthLogin.js +2 -8
  54. package/dist/lib/refresh.js +6 -2
  55. package/dist/lib/tasks.js +33 -8
  56. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialEnvelopeBridge.d.ts +13 -1
  57. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialEnvelopeBridge.js +9 -3
  58. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +1 -1
  59. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +4 -3
  60. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialEnvelopeBridge.d.ts +13 -1
  61. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialEnvelopeBridge.js +8 -3
  62. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +1 -1
  63. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +1 -1
  64. package/dist/node_modules/@skrr-ai/auth-core/package.json +1 -1
  65. package/dist/node_modules/@skrr-ai/data-provider/index.js +3138 -3100
  66. package/oclif.manifest.json +26470 -26449
  67. package/package.json +1 -1
@@ -7,6 +7,7 @@ const base_command_1 = require("../base-command");
7
7
  const daemonHandoff_1 = require("../lib/daemonHandoff");
8
8
  const daemon_setup_1 = require("../lib/daemon-setup");
9
9
  const config_1 = require("../lib/config");
10
+ const cli_id_1 = require("../lib/cli-id");
10
11
  const keychain_1 = require("../lib/keychain");
11
12
  const login_1 = require("../lib/login");
12
13
  const device_code_1 = require("../lib/device-code");
@@ -40,16 +41,33 @@ const prompt_1 = require("../lib/prompt");
40
41
  * `cliLoginWithRefreshToken` for the full rationale.
41
42
  */
42
43
  class Login extends base_command_1.BaseCommand {
43
- static description = 'Sign in to a skrr instance (PKCE localhost callback; device-code fallback). ' +
44
- 'On an interactive first login on a computer without the skrr background service, skrr ' +
45
- 'offers to install it. Choose no, or set SKRR_SKIP_DAEMON_HANDOFF=1 before running this ' +
46
- 'command, to skip that optional setup.';
44
+ // OSK-12152 — the help used to describe only the browser flow. `skrr login`
45
+ // asks the local daemon FIRST, and nothing said so; nor that what it stores
46
+ // differs from what plain commands borrow from the daemon on their own.
47
+ static description = 'Sign in to a skrr instance and store a durable session for this profile.\n\n' +
48
+ 'It tries, in order:\n' +
49
+ ' 1. the skrr background service on this computer, when one is running and signed in to the ' +
50
+ 'same server — it mints this profile a CLI session with no browser;\n' +
51
+ ' 2. a browser sign-in through a localhost callback (PKCE);\n' +
52
+ ' 3. a device code — under SSH, with --device-code, or when the callback cannot run.\n' +
53
+ 'If the background service answers but refuses, skrr says why before moving on.\n\n' +
54
+ 'What it stores: a refresh session in this profile’s credential store (the macOS Keychain, ' +
55
+ 'or ~/.skrr/cli-auth.json elsewhere) that keeps renewing itself. Plain skrr commands on a ' +
56
+ 'computer whose background service is signed in need no login at all: they borrow a ' +
57
+ 'short-lived token from it for each run and store nothing (an older background service ' +
58
+ 'mints them a stored session instead). On a Dedicated Runtime, where the service only ' +
59
+ 'lends short-lived tokens, nothing is stored.\n\n' +
60
+ 'Set OVERSKY_SKIP_DAEMON_BROKER=1 to skip step 1 (--device-code skips it too). After signing ' +
61
+ 'in, skrr also signs the background service in, and on an interactive first login on a ' +
62
+ 'computer without it, offers to install it. Set SKRR_SKIP_DAEMON_HANDOFF=1 to skip that ' +
63
+ 'optional setup; it does not skip step 1.';
47
64
  static examples = [
48
65
  '<%= config.bin %> login',
49
66
  '<%= config.bin %> login --env prod',
50
67
  '<%= config.bin %> login --base-url https://skrr.ai',
51
68
  '<%= config.bin %> login --server http://localhost:3090',
52
69
  '<%= config.bin %> login --device-code',
70
+ 'OVERSKY_SKIP_DAEMON_BROKER=1 <%= config.bin %> login',
53
71
  '<%= config.bin %> login --port 1455',
54
72
  '<%= config.bin %> login --recovery-code "W7K2J-X9P3M-R6Y4Q-..."',
55
73
  'OVERSKY_REFRESH_TOKEN=... OVERSKY_CLI_ID=... <%= config.bin %> login',
@@ -244,7 +262,8 @@ class Login extends base_command_1.BaseCommand {
244
262
  *
245
263
  * `cliId` resolution priority:
246
264
  * 1. `OVERSKY_CLI_ID` env var — explicit, wins over everything.
247
- * 2. `cliConfig.cliId` — set by a prior `skrr login` on this machine.
265
+ * 2. `resolveCliId(cliConfig)` — the id a prior `skrr login` of this
266
+ * profile presents (its own, or the machine's).
248
267
  * Honored so a same-machine restore "just works" without forcing
249
268
  * the operator to re-export the cliId.
250
269
  * 3. error — without a cliId the refresh row binding can't be matched
@@ -252,8 +271,8 @@ class Login extends base_command_1.BaseCommand {
252
271
  * pointing at `skrr whoami` on the original box.
253
272
  */
254
273
  async runRefreshTokenLogin(refreshToken) {
255
- const envCliId = process.env.OVERSKY_CLI_ID;
256
- const cliId = typeof envCliId === 'string' && envCliId.length > 0 ? envCliId : this.cliConfig.cliId;
274
+ // `resolveCliId` already puts OVERSKY_CLI_ID / SKRR_CLI_ID first.
275
+ const cliId = (0, cli_id_1.resolveCliId)(this.cliConfig);
257
276
  if (!cliId) {
258
277
  this.error('OVERSKY_REFRESH_TOKEN is set but no cliId is available. The refresh\n' +
259
278
  'row is bound to the cliId it was issued for; without it the server\n' +
@@ -244,6 +244,9 @@ class Logout extends base_command_1.BaseCommand {
244
244
  (0, config_1.saveConfig)({
245
245
  baseURL: cfg.baseURL,
246
246
  ...(cfg.cliId ? { cliId: cfg.cliId } : {}),
247
+ // A named profile's own identity outlives a session exactly as the
248
+ // machine's does, so its next login re-presents it (OSK-12156).
249
+ ...(cfg.profileCliId ? { profileCliId: cfg.profileCliId } : {}),
247
250
  ...(agentSetting.value ? { [first_party_harness_managed_1.AGENT_CONFIG_KEY]: agentSetting.value } : {}),
248
251
  ...(cfg.credentials ? { credentials: cfg.credentials } : {}),
249
252
  migrated: { ...(cfg.migrated ?? {}), authOutOfConfig: true },
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const core_1 = require("@oclif/core");
4
4
  const dedicated_lease_command_1 = require("../../../lib/dedicated-lease-command");
5
+ const daemon_installer_1 = require("../../../lib/daemon-installer");
5
6
  const dedicated_machines_1 = require("../../../lib/dedicated-machines");
6
7
  const harnesses_1 = require("../../../lib/harnesses");
7
8
  class DedicatedMachinesShow extends dedicated_lease_command_1.DedicatedLeaseCommand {
@@ -40,6 +41,12 @@ class DedicatedMachinesShow extends dedicated_lease_command_1.DedicatedLeaseComm
40
41
  catch {
41
42
  harnesses = [];
42
43
  }
44
+ // The latest daemon RELEASE, from the same signed feed a daemon updates
45
+ // from. The lease says which daemon the approved image carries; only this
46
+ // says whether a newer release exists that no approved image carries yet —
47
+ // which is what `skrrd update --check` on the machine reports (OSK-12153).
48
+ // Best-effort: an unreachable feed is said, never read as "none newer".
49
+ const latestDaemonRelease = lease.health?.image ? await readLatestDaemonRelease() : null;
43
50
  if (flags.json) {
44
51
  const engines = (0, dedicated_machines_1.dedicatedLeaseHarnesses)(lease, harnesses).map((harness) => ({
45
52
  harnessId: harness._id,
@@ -47,11 +54,27 @@ class DedicatedMachinesShow extends dedicated_lease_command_1.DedicatedLeaseComm
47
54
  status: harness.status,
48
55
  lastSeenAt: harness.lastSeenAt,
49
56
  }));
50
- this.log(JSON.stringify({ ...response, harnesses: engines }, null, 2));
57
+ this.log(JSON.stringify({
58
+ ...response,
59
+ harnesses: engines,
60
+ ...(latestDaemonRelease ? { latestDaemonRelease } : {}),
61
+ }, null, 2));
51
62
  return;
52
63
  }
53
- for (const line of (0, dedicated_machines_1.describeDedicatedLease)(lease, harnesses))
64
+ for (const line of (0, dedicated_machines_1.describeDedicatedLease)(lease, harnesses, { latestDaemonRelease })) {
54
65
  this.log(line);
66
+ }
55
67
  }
56
68
  }
57
69
  exports.default = DedicatedMachinesShow;
70
+ async function readLatestDaemonRelease() {
71
+ try {
72
+ const result = await (0, daemon_installer_1.fetchVerifiedDaemonManifest)();
73
+ if (result.ok)
74
+ return { version: result.manifest.version };
75
+ return { error: result.code };
76
+ }
77
+ catch (err) {
78
+ return { error: err?.message || 'unreadable' };
79
+ }
80
+ }
@@ -14,6 +14,28 @@ 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
+ /**
34
+ * What to print for such a run: why it is blocked, what needed approval (the
35
+ * run's own recorded sentence, which names the refused tool), that an
36
+ * unattended run cannot prompt, and the two ways forward with exact commands.
37
+ */
38
+ export declare function permissionBlockLines(run: Record<string, unknown>, bin?: string, taskRef?: string): string[];
17
39
  export default class TasksApprovalsList extends BaseCommand {
18
40
  static description: string;
19
41
  static examples: string[];
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.approvalRow = approvalRow;
4
+ exports.permissionBlockedRun = permissionBlockedRun;
5
+ exports.permissionBlockLines = permissionBlockLines;
4
6
  const core_1 = require("@oclif/core");
5
7
  const data_provider_1 = require("@skrr-ai/data-provider");
6
8
  const base_command_1 = require("../../../base-command");
@@ -22,6 +24,52 @@ function approvalRow(a) {
22
24
  created: a.createdAt ? (0, format_1.localTime)(a.createdAt) : '-',
23
25
  };
24
26
  }
27
+ /**
28
+ * OSK-12165 — the task's latest run, when it ended blocked on a permission
29
+ * nobody could grant.
30
+ *
31
+ * An unattended run (`tasks start`) cannot ask anyone: the permission bridge
32
+ * declines every ask at once, so no approval row is ever created, and this
33
+ * command used to answer "No approvals." in exactly the case `tasks start`
34
+ * sends people here for. The evidence lives on the run, not in the approvals
35
+ * collection: the leaf finalizer settles it with `error.code`
36
+ * `permission_blocked`, which `GET /tasks/:id/runs` projects as `failureCode`
37
+ * alongside the sentence (what was refused, and the fix) as `failureReason`.
38
+ *
39
+ * Only the LATEST run counts (the route sorts newest first): a permission block
40
+ * on an older run that a later run got past is history, not the current state.
41
+ */
42
+ function permissionBlockedRun(runs) {
43
+ const latest = runs[0];
44
+ if (!latest)
45
+ return null;
46
+ const code = latest.failureCode ?? latest.failureClass;
47
+ return code === data_provider_1.PERMISSION_BLOCKED_FAILURE_CODE ? latest : null;
48
+ }
49
+ /**
50
+ * What to print for such a run: why it is blocked, what needed approval (the
51
+ * run's own recorded sentence, which names the refused tool), that an
52
+ * unattended run cannot prompt, and the two ways forward with exact commands.
53
+ */
54
+ function permissionBlockLines(run, bin = 'skrr', taskRef = '<task-id>') {
55
+ const runId = String(run.runId ?? run.id ?? '-');
56
+ const reason = typeof run.failureReason === 'string' ? run.failureReason.trim() : '';
57
+ const executor = run.executor;
58
+ const agent = executor && typeof executor.agentId === 'string' && executor.agentId
59
+ ? executor.agentId
60
+ : '<agent>';
61
+ return [
62
+ `The latest run (${runId}) is blocked on a permission, not on an approval request.`,
63
+ ...(reason ? [` ${reason}`] : []),
64
+ 'It ran unattended (`tasks start`), and an unattended run cannot prompt anyone to approve a',
65
+ 'tool call, so the ask was refused on the spot and no approval request was recorded here.',
66
+ '',
67
+ 'To continue, either:',
68
+ ` ${bin} agents chat ${agent} --approve-tools --prompt "..." do the work interactively, approving each call`,
69
+ ` ${bin} agents update ${agent} --default-permission-mode bypassPermissions let unattended runs use the tool`,
70
+ ` then: ${bin} tasks start ${taskRef} (or pick an agent/harness whose mode allows it)`,
71
+ ];
72
+ }
25
73
  class TasksApprovalsList extends base_command_1.BaseCommand {
26
74
  static description = 'List approvals for a task';
27
75
  static examples = [
@@ -44,14 +92,38 @@ class TasksApprovalsList extends base_command_1.BaseCommand {
44
92
  catch (err) {
45
93
  this.handleApiError(err);
46
94
  }
95
+ const r = response;
96
+ const items = r?.data ?? [];
97
+ // OSK-12165 — an unattended run blocked on a permission leaves no approval
98
+ // row, so the empty list alone would say "nothing to act on" about a task
99
+ // that is waiting on exactly that. Read the latest run, best-effort: the
100
+ // approvals list is still the answer if the runs read fails.
101
+ let blockedRun = null;
102
+ try {
103
+ const runs = (await data_provider_1.dataService.getTaskRuns(args.id));
104
+ blockedRun = permissionBlockedRun(runs?.data ?? []);
105
+ }
106
+ catch {
107
+ blockedRun = null;
108
+ }
109
+ const blockLines = blockedRun ? permissionBlockLines(blockedRun, this.config.bin, args.id) : [];
47
110
  if (flags.json) {
111
+ // The server payload stays byte-identical; the pointer goes to stderr so
112
+ // `--json` remains parseable and a script still learns the task is stuck.
113
+ if (blockedRun) {
114
+ this.warn(`The latest run is blocked on a permission an unattended run could not ask for; ` +
115
+ `\`${this.config.bin} tasks approvals list ${args.id}\` (without --json) says what to do.`);
116
+ }
48
117
  this.log(JSON.stringify(response, null, 2));
49
118
  return;
50
119
  }
51
- const r = response;
52
- const items = r?.data ?? [];
53
120
  if (items.length === 0) {
54
- this.log('No approvals.');
121
+ this.log('No approval requests.');
122
+ if (blockLines.length > 0) {
123
+ this.log('');
124
+ for (const line of blockLines)
125
+ this.log(line);
126
+ }
55
127
  return;
56
128
  }
57
129
  const rows = items.map(approvalRow);
@@ -62,6 +134,11 @@ class TasksApprovalsList extends base_command_1.BaseCommand {
62
134
  { key: 'approver', header: 'APPROVER', maxWidth: 30 },
63
135
  { key: 'created', header: 'CREATED' },
64
136
  ], (line) => this.log(line));
137
+ if (blockLines.length > 0) {
138
+ this.log('');
139
+ for (const line of blockLines)
140
+ this.log(line);
141
+ }
65
142
  }
66
143
  }
67
144
  exports.default = TasksApprovalsList;
@@ -25,6 +25,11 @@ import { type TaskRecord } from '../../lib/tasks';
25
25
  * Same shape as `warnDroppedDefaultAssignees` below, which does this for a
26
26
  * silently-dropped assignee (OSK-4857).
27
27
  */
28
+ /**
29
+ * `tagLabelWarning` for a task: the task-label vocabulary of the ambient
30
+ * workspace. Shared by `tasks create` and `tasks update`.
31
+ */
32
+ export declare function taskTagLabelWarning(tags: string[] | undefined, attachedLabelIds: string[] | undefined): Promise<string>;
28
33
  export declare function droppedEstimateWarning(requested: number | undefined, written: Record<string, unknown> | null | undefined, spaceId?: string | null): string | null;
29
34
  /** A create may have committed before any of these response failures surfaced. */
30
35
  export declare function taskCreateMayHaveCommitted(error: unknown): boolean;
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.taskTagLabelWarning = taskTagLabelWarning;
3
4
  exports.droppedEstimateWarning = droppedEstimateWarning;
4
5
  exports.taskCreateMayHaveCommitted = taskCreateMayHaveCommitted;
5
6
  exports.taskMatchesCreateIdentity = taskMatchesCreateIdentity;
@@ -36,6 +37,16 @@ const web_url_1 = require("../../lib/web-url");
36
37
  * Same shape as `warnDroppedDefaultAssignees` below, which does this for a
37
38
  * silently-dropped assignee (OSK-4857).
38
39
  */
40
+ /**
41
+ * `tagLabelWarning` for a task: the task-label vocabulary of the ambient
42
+ * workspace. Shared by `tasks create` and `tasks update`.
43
+ */
44
+ function taskTagLabelWarning(tags, attachedLabelIds) {
45
+ return (0, label_ref_1.tagLabelWarning)(tags, attachedLabelIds, {
46
+ applicableTo: 'task',
47
+ ...(process.env.OVERSKY_WORKSPACE_ID ? { workspaceId: process.env.OVERSKY_WORKSPACE_ID } : {}),
48
+ });
49
+ }
39
50
  function droppedEstimateWarning(requested, written, spaceId) {
40
51
  if (requested === undefined || requested === null)
41
52
  return null;
@@ -137,7 +148,8 @@ class TasksCreate extends base_command_1.BaseCommand {
137
148
  description: 'Stable caller-supplied idempotency key for create/upsert workflows',
138
149
  }),
139
150
  'default-agent': core_1.Flags.string({
140
- description: 'Override the automatic assignee with an Agent. Use "auto" to inherit the space lead ' +
151
+ description: 'Override the automatic assignee with an Agent (it becomes the assignee that runs the task; ' +
152
+ '--assignee-agent names one directly). Use "auto" to inherit the space lead ' +
141
153
  'agent. Without this, a task falls to the agent this session is bound to ' +
142
154
  '(OVERSKY_SESSION_AGENT_ID), to you when --execution-mode is human, or to nobody. ' +
143
155
  'Cannot be combined with --execution-mode human.',
@@ -161,8 +173,9 @@ class TasksCreate extends base_command_1.BaseCommand {
161
173
  description: 'Commitment ID that owns this task. Use for work created directly from a commitment without a linked goal.',
162
174
  }),
163
175
  project: core_1.Flags.string({
164
- description: 'Canonical Project ID — attach the task to a project. A task created with a project ' +
165
- "but no --space resolves and inherits the project's space.",
176
+ description: 'Project to attach the task to: its `id` (uuid; a pasted `_id` is accepted and rewritten ' +
177
+ 'to the `id`), or its ref (OSK-P-5) with --space. Without --space the task inherits ' +
178
+ "the project's space.",
166
179
  }),
167
180
  cycle: core_1.Flags.string({
168
181
  description: 'Cycle ID, or "current" / "next" within the task Space',
@@ -174,7 +187,8 @@ class TasksCreate extends base_command_1.BaseCommand {
174
187
  description: 'Source message ID (requires --source-conversation or daemon session context)',
175
188
  }),
176
189
  'assignee-agent': core_1.Flags.string({
177
- description: 'Assignee agent — id or name, resolved within --space (repeatable)',
190
+ description: 'Assignee agent: the agent that DOES the work when the task starts (the first listed runs). ' +
191
+ 'Id or name, resolved within --space (repeatable)',
178
192
  multiple: true,
179
193
  }),
180
194
  'assignee-user': core_1.Flags.string({
@@ -182,7 +196,8 @@ class TasksCreate extends base_command_1.BaseCommand {
182
196
  multiple: true,
183
197
  }),
184
198
  'dri-agent': core_1.Flags.string({
185
- description: 'Explicit accountable agent DRI (separate from assignees)',
199
+ description: 'Accountable agent (DRI), separate from assignees. `tasks start` runs an assignee, ' +
200
+ 'not the DRI — use --assignee-agent for the agent that does the work',
186
201
  }),
187
202
  'dri-user': core_1.Flags.string({
188
203
  description: 'Explicit accountable human DRI (separate from assignees)',
@@ -219,11 +234,12 @@ class TasksCreate extends base_command_1.BaseCommand {
219
234
  'ancestor it inherits from); with none set, the server drops the value. Set one with ' +
220
235
  '`spaces update <space> --estimate-scale <scale> --estimate-migration <clear|migrate>`.',
221
236
  }),
222
- tag: core_1.Flags.string({ description: 'Tag (repeatable)', multiple: true }),
237
+ tag: core_1.Flags.string({ description: label_ref_1.TAG_FLAG_HELP, multiple: true }),
223
238
  'label-id': core_1.Flags.string({
224
239
  aliases: ['label'],
225
240
  description: 'Attach a task label (repeatable) — id, or a name from `skrr labels list --applicable-to task`. ' +
226
- 'An unknown name is refused, never created. Manage labels with `skrr labels`.',
241
+ 'An unknown name is refused, never created. Also accepted as --label. ' +
242
+ 'Manage labels with `skrr labels`.',
227
243
  multiple: true,
228
244
  }),
229
245
  'working-directory': core_1.Flags.string({
@@ -517,6 +533,11 @@ class TasksCreate extends base_command_1.BaseCommand {
517
533
  const estimateWarning = droppedEstimateWarning(flags.estimate, created, payload.spaceId);
518
534
  if (estimateWarning)
519
535
  this.warn(estimateWarning);
536
+ // A `--tag` spelled like a declared label is almost always a label someone
537
+ // meant to attach (OSK-12053). Say so; never convert it implicitly.
538
+ const tagWarning = await taskTagLabelWarning(flags.tag, resolvedLabelIds);
539
+ if (tagWarning)
540
+ this.warn(tagWarning);
520
541
  if (flags.json) {
521
542
  // The raw task row alone cannot tell a script the assignee it implicitly
522
543
  // asked for was dropped (`assigneeAgentIds: []`) from "nobody was ever
@@ -1,6 +1,7 @@
1
1
  import { BaseCommand } from '../../base-command';
2
2
  export default class TasksStart extends BaseCommand {
3
3
  static description: string;
4
+ static aliases: string[];
4
5
  static examples: string[];
5
6
  static args: {
6
7
  id: import("@oclif/core/lib/interfaces").Arg<string | undefined, Record<string, unknown>>;
@@ -13,3 +14,16 @@ export default class TasksStart extends BaseCommand {
13
14
  };
14
15
  run(): Promise<void>;
15
16
  }
17
+ /**
18
+ * What to run next, printed after a start (OSK-12128). A start used to print
19
+ * two ids and stop, and the way to watch it was found by accident. Every
20
+ * command named here exists; `tasks-start.follow-up.spec.ts` pins that.
21
+ *
22
+ * The last line announces the files a local run adds to its working directory:
23
+ * the daemon writes a per-run instruction file (AGENTS.md for Codex, CLAUDE.md
24
+ * for a worktree session, plus any server-sent context file) when nothing is
25
+ * at that name, and removes what it wrote afterwards unless it was changed
26
+ * (`daemon/src/session-context-file.ts`). Unannounced, those were the first two
27
+ * files in a directory the task said to leave alone.
28
+ */
29
+ export declare function startFollowUpLines(bin: string, taskRef: string): string[];
@@ -1,12 +1,17 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.startFollowUpLines = startFollowUpLines;
3
4
  const core_1 = require("@oclif/core");
4
5
  const data_provider_1 = require("@skrr-ai/data-provider");
5
6
  const base_command_1 = require("../../base-command");
6
7
  const task_resolver_1 = require("../../lib/task-resolver");
7
8
  const git_task_1 = require("../../lib/git-task");
8
9
  class TasksStart extends base_command_1.BaseCommand {
9
- static description = 'Start execution of a task (spawns a conversation + session)';
10
+ static description = 'Start execution of a task: its assignee agent runs it in a new conversation + session. ' +
11
+ 'Alias: tasks execute';
12
+ // `tasks execute` is the verb a first-time user reaches for, and it was
13
+ // "not a command" (OSK-12128). One command, two names.
14
+ static aliases = ['tasks:execute'];
10
15
  static examples = [
11
16
  '<%= config.bin %> tasks start <task-id>',
12
17
  '<%= config.bin %> tasks start',
@@ -109,6 +114,31 @@ class TasksStart extends base_command_1.BaseCommand {
109
114
  this.log(`${branch.created ? 'Created' : 'Checked out'} branch: ${branch.branch}`);
110
115
  this.log(`Conversation: ${result.conversationId}`);
111
116
  this.log(`Session: ${result.sessionId}`);
117
+ for (const line of startFollowUpLines(this.config.bin, taskIdentity.id))
118
+ this.log(line);
112
119
  }
113
120
  }
114
121
  exports.default = TasksStart;
122
+ /**
123
+ * What to run next, printed after a start (OSK-12128). A start used to print
124
+ * two ids and stop, and the way to watch it was found by accident. Every
125
+ * command named here exists; `tasks-start.follow-up.spec.ts` pins that.
126
+ *
127
+ * The last line announces the files a local run adds to its working directory:
128
+ * the daemon writes a per-run instruction file (AGENTS.md for Codex, CLAUDE.md
129
+ * for a worktree session, plus any server-sent context file) when nothing is
130
+ * at that name, and removes what it wrote afterwards unless it was changed
131
+ * (`daemon/src/session-context-file.ts`). Unannounced, those were the first two
132
+ * files in a directory the task said to leave alone.
133
+ */
134
+ function startFollowUpLines(bin, taskRef) {
135
+ return [
136
+ '',
137
+ 'Follow it:',
138
+ ` ${bin} tasks watch ${taskRef} status until it finishes or fails`,
139
+ ` ${bin} tasks transcript ${taskRef} what the agent has said so far`,
140
+ ` ${bin} tasks approvals list ${taskRef} approvals waiting on you, or why a run is blocked on one`,
141
+ 'On a local machine, the run may add its instruction file (AGENTS.md / CLAUDE.md) to the',
142
+ 'working directory while it runs; files the run wrote are removed afterwards unless changed.',
143
+ ];
144
+ }
@@ -9,6 +9,7 @@ const tasks_1 = require("../../lib/tasks");
9
9
  const task_resolver_1 = require("../../lib/task-resolver");
10
10
  const tasks_2 = require("../../lib/tasks");
11
11
  const create_1 = require("./create");
12
+ const label_ref_1 = require("../../lib/label-ref");
12
13
  class TasksUpdate extends base_command_1.BaseCommand {
13
14
  static description = 'Update an existing task (partial patch). Status and PROJECT changes go through ' +
14
15
  '`tasks move` — see `--goal` / `--space` below.';
@@ -156,10 +157,14 @@ class TasksUpdate extends base_command_1.BaseCommand {
156
157
  description: 'Remove a task from the list this one blocks (repeatable)',
157
158
  multiple: true,
158
159
  }),
159
- tag: core_1.Flags.string({ description: 'Replace tags (repeatable)', multiple: true }),
160
+ tag: core_1.Flags.string({
161
+ description: `Replace tags (repeatable, whole-list). ${label_ref_1.TAG_FLAG_HELP}`,
162
+ multiple: true,
163
+ }),
160
164
  'label-id': core_1.Flags.string({
161
165
  aliases: ['label'],
162
- description: 'Replace the task labels with these ids (repeatable, whole-list). ' +
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. ' +
163
168
  'Use `skrr tasks labels attach/detach` for incremental changes.',
164
169
  multiple: true,
165
170
  }),
@@ -318,6 +323,25 @@ class TasksUpdate extends base_command_1.BaseCommand {
318
323
  }
319
324
  }
320
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
+ }
321
345
  const payload = (0, prompt_1.mergeDefined)((0, task_resolver_1.stripTaskIdentityFields)(body), {
322
346
  title: flags.title,
323
347
  expectations: expectationsPatch === null ? [] : (expectationsPatch ?? undefined),
@@ -349,7 +373,7 @@ class TasksUpdate extends base_command_1.BaseCommand {
349
373
  addBlocksIds: flags['add-blocks'],
350
374
  removeBlocksIds: flags['remove-blocks'],
351
375
  tags: flags.tag,
352
- labelIds: flags['label-id'],
376
+ labelIds: resolvedLabelIds,
353
377
  // Worktree isolation override. --worktree ⇒ 'ephemeral', --no-worktree ⇒
354
378
  // 'shared', omitted ⇒ unchanged. (Clear-to-inherit via --from-json.)
355
379
  executionIsolation: (0, tasks_2.executionIsolationFromWorktreeFlag)(flags.worktree),
@@ -444,6 +468,9 @@ class TasksUpdate extends base_command_1.BaseCommand {
444
468
  const estimateWarning = (0, create_1.droppedEstimateWarning)(flags.estimate, updated, updated?.spaceId);
445
469
  if (estimateWarning)
446
470
  this.warn(estimateWarning);
471
+ const tagWarning = await (0, create_1.taskTagLabelWarning)(flags.tag, resolvedLabelIds);
472
+ if (tagWarning)
473
+ this.warn(tagWarning);
447
474
  if (flags.json) {
448
475
  this.log(JSON.stringify((0, web_url_1.withTaskUrl)(this.cliConfig.baseURL, updated), null, 2));
449
476
  return;
@@ -10,6 +10,7 @@ const auth_storage_1 = require("../lib/auth-storage");
10
10
  const keychain_1 = require("../lib/keychain");
11
11
  const cred_envelope_1 = require("../lib/cred-envelope");
12
12
  const config_1 = require("../lib/config");
13
+ const cli_id_1 = require("../lib/cli-id");
13
14
  /**
14
15
  * `skrr whoami`
15
16
  *
@@ -101,7 +102,7 @@ class Whoami extends base_command_1.BaseCommand {
101
102
  daemonId,
102
103
  server: this.cliConfig.baseURL,
103
104
  profile: (0, keychain_1.getActiveProfile)(),
104
- cliId: this.cliConfig.cliId ?? null,
105
+ cliId: (0, cli_id_1.resolveCliId)(this.cliConfig) ?? null,
105
106
  token: {
106
107
  source: this.credentialSourceLabel(),
107
108
  kind: this.resolvedCredential.kind,
@@ -135,8 +136,9 @@ class Whoami extends base_command_1.BaseCommand {
135
136
  // where the login message sends you to find out where you are (OSK-301).
136
137
  this.log(`Server: ${(0, config_1.describeDeployment)(this.cliConfig.baseURL)}`);
137
138
  this.log(`Profile: ${(0, keychain_1.getActiveProfile)()}`);
138
- if (this.cliConfig.cliId) {
139
- this.log(`CLI ID: ${this.cliConfig.cliId}`);
139
+ const cliId = (0, cli_id_1.resolveCliId)(this.cliConfig);
140
+ if (cliId) {
141
+ this.log(`CLI ID: ${cliId}`);
140
142
  }
141
143
  this.log(`Token source: ${this.credentialSourceLabel() ?? '(none)'}`);
142
144
  this.log(`Token kind: ${this.resolvedCredential.kind}`);
@@ -2,6 +2,21 @@ export declare const COMMITMENT_RESPONSIBILITY_LEVELS: readonly ["reasoning_only
2
2
  export declare const REASONING_TOOL_ESCAPES: readonly ["bash", "browser"];
3
3
  /** Warning printed by the three retired Commitment permission flags (OSK-11975). */
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
+ /** The three retired flags themselves: accepted at parse, warned on, never sent. */
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[];
5
20
  export declare const RELEASE_INSTALL_CHANNELS: readonly ["latest", "manual"];
6
21
  /**
7
22
  * `09:00-18:00` or `09:00-18:00@America/Los_Angeles`, or a JSON object.