@skrr-ai/cli 0.1.50 → 0.1.52

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 (131) hide show
  1. package/dist/base-command.d.ts +7 -0
  2. package/dist/base-command.js +95 -8
  3. package/dist/commands/agents/chat.d.ts +14 -0
  4. package/dist/commands/agents/chat.js +37 -1
  5. package/dist/commands/agents/create.js +8 -4
  6. package/dist/commands/agents/show.d.ts +9 -0
  7. package/dist/commands/agents/show.js +53 -0
  8. package/dist/commands/agents/update.d.ts +32 -0
  9. package/dist/commands/agents/update.js +81 -9
  10. package/dist/commands/balance/show.d.ts +34 -0
  11. package/dist/commands/balance/show.js +92 -2
  12. package/dist/commands/commitments/doctor.d.ts +1 -0
  13. package/dist/commands/commitments/doctor.js +19 -4
  14. package/dist/commands/commitments/effective-policy.js +15 -1
  15. package/dist/commands/commitments/next-tick.js +12 -2
  16. package/dist/commands/commitments/preflight.js +10 -0
  17. package/dist/commands/commitments/show.js +11 -1
  18. package/dist/commands/context.js +2 -1
  19. package/dist/commands/daemon/config.d.ts +15 -0
  20. package/dist/commands/daemon/config.js +53 -0
  21. package/dist/commands/daemon/index.js +1 -0
  22. package/dist/commands/daemon/status.js +15 -1
  23. package/dist/commands/doctor.js +8 -1
  24. package/dist/commands/harnesses/models.d.ts +54 -0
  25. package/dist/commands/harnesses/models.js +100 -7
  26. package/dist/commands/harnesses/show.d.ts +11 -0
  27. package/dist/commands/harnesses/show.js +35 -0
  28. package/dist/commands/harnesses/update.d.ts +28 -1
  29. package/dist/commands/harnesses/update.js +73 -10
  30. package/dist/commands/login.d.ts +2 -1
  31. package/dist/commands/login.js +26 -7
  32. package/dist/commands/logout.js +3 -0
  33. package/dist/commands/machines/dedicated/show.js +25 -2
  34. package/dist/commands/outbox/list.d.ts +12 -1
  35. package/dist/commands/outbox/list.js +31 -2
  36. package/dist/commands/revoke-daemon.js +9 -0
  37. package/dist/commands/spaces/create.d.ts +1 -0
  38. package/dist/commands/spaces/create.js +17 -0
  39. package/dist/commands/spaces/show.js +5 -0
  40. package/dist/commands/spaces/update.d.ts +1 -0
  41. package/dist/commands/spaces/update.js +16 -0
  42. package/dist/commands/tasks/create.d.ts +6 -0
  43. package/dist/commands/tasks/create.js +31 -6
  44. package/dist/commands/tasks/show.js +45 -0
  45. package/dist/commands/tasks/start.d.ts +14 -0
  46. package/dist/commands/tasks/start.js +31 -1
  47. package/dist/commands/tasks/update.d.ts +1 -0
  48. package/dist/commands/tasks/update.js +29 -1
  49. package/dist/commands/tasks/upsert.d.ts +1 -0
  50. package/dist/commands/tasks/upsert.js +16 -0
  51. package/dist/commands/whoami.js +5 -3
  52. package/dist/lib/agent-config.d.ts +2 -0
  53. package/dist/lib/agent-config.js +14 -10
  54. package/dist/lib/agentic-stream.d.ts +88 -1
  55. package/dist/lib/agentic-stream.js +330 -9
  56. package/dist/lib/auth-core-init.d.ts +2 -10
  57. package/dist/lib/auth-core-init.js +20 -67
  58. package/dist/lib/auth-failure.d.ts +33 -0
  59. package/dist/lib/auth-failure.js +68 -0
  60. package/dist/lib/auth-storage.d.ts +24 -0
  61. package/dist/lib/auth-storage.js +342 -11
  62. package/dist/lib/cli-id.d.ts +22 -0
  63. package/dist/lib/cli-id.js +56 -0
  64. package/dist/lib/cli-identity.d.ts +49 -0
  65. package/dist/lib/cli-identity.js +159 -0
  66. package/dist/lib/commitments.d.ts +25 -1
  67. package/dist/lib/commitments.js +46 -1
  68. package/dist/lib/config.d.ts +17 -0
  69. package/dist/lib/config.js +15 -3
  70. package/dist/lib/daemonBroker.d.ts +37 -1
  71. package/dist/lib/daemonBroker.js +146 -22
  72. package/dist/lib/daemonBrokerRefusal.d.ts +51 -0
  73. package/dist/lib/daemonBrokerRefusal.js +127 -0
  74. package/dist/lib/dedicated-machines.d.ts +33 -2
  75. package/dist/lib/dedicated-machines.js +50 -1
  76. package/dist/lib/delegated-cli.js +20 -1
  77. package/dist/lib/exec-runtime-binary.d.ts +3 -1
  78. package/dist/lib/exec-runtime-binary.js +4 -2
  79. package/dist/lib/first-party-harness-broker.js +10 -5
  80. package/dist/lib/harnesses.d.ts +10 -0
  81. package/dist/lib/keychain.d.ts +39 -0
  82. package/dist/lib/keychain.js +228 -39
  83. package/dist/lib/label-ref.d.ts +34 -0
  84. package/dist/lib/label-ref.js +68 -1
  85. package/dist/lib/live-runtime-binary.d.ts +40 -0
  86. package/dist/lib/live-runtime-binary.js +193 -0
  87. package/dist/lib/login.d.ts +23 -0
  88. package/dist/lib/login.js +67 -21
  89. package/dist/lib/message-intent.js +3 -1
  90. package/dist/lib/oauthLogin.js +2 -8
  91. package/dist/lib/refresh.js +6 -2
  92. package/dist/lib/tasks.d.ts +20 -0
  93. package/dist/lib/tasks.js +87 -8
  94. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialEnvelopeBridge.d.ts +2 -0
  95. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialEnvelopeBridge.js +26 -1
  96. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/daemonToolApproval.d.ts +28 -4
  97. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/daemonToolApproval.js +23 -5
  98. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.js +41 -3
  99. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +3 -1
  100. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +25 -3
  101. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/loopbackHttp.d.ts +32 -0
  102. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/loopbackHttp.js +268 -0
  103. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineId.d.ts +2 -2
  104. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineId.js +11 -10
  105. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineUuid.d.ts +36 -0
  106. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineUuid.js +156 -0
  107. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/refresh.d.ts +8 -0
  108. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/refresh.js +8 -1
  109. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/spawnEnv.d.ts +22 -0
  110. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/spawnEnv.js +61 -2
  111. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialEnvelopeBridge.d.ts +2 -0
  112. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialEnvelopeBridge.js +25 -1
  113. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/daemonToolApproval.d.ts +28 -4
  114. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/daemonToolApproval.js +22 -4
  115. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.js +41 -3
  116. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +3 -1
  117. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +10 -1
  118. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/loopbackHttp.d.ts +32 -0
  119. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/loopbackHttp.js +257 -0
  120. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineId.d.ts +2 -2
  121. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineId.js +11 -10
  122. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineUuid.d.ts +36 -0
  123. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineUuid.js +147 -0
  124. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/refresh.d.ts +8 -0
  125. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/refresh.js +8 -1
  126. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/spawnEnv.d.ts +22 -0
  127. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/spawnEnv.js +58 -1
  128. package/dist/node_modules/@skrr-ai/auth-core/package.json +21 -1
  129. package/dist/node_modules/@skrr-ai/data-provider/index.js +3139 -3097
  130. package/oclif.manifest.json +28591 -28438
  131. package/package.json +1 -1
@@ -19,6 +19,7 @@ export default class SpacesCreate extends BaseCommand {
19
19
  'group-chat': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
20
20
  parent: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
21
21
  'inherit-access': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
22
+ 'working-directory': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
22
23
  };
23
24
  run(): Promise<void>;
24
25
  }
@@ -9,6 +9,7 @@ const label_ref_1 = require("../../lib/label-ref");
9
9
  const web_url_1 = require("../../lib/web-url");
10
10
  const prompt_1 = require("../../lib/prompt");
11
11
  const created_line_1 = require("../../lib/created-line");
12
+ const tasks_1 = require("../../lib/tasks");
12
13
  class SpacesCreate extends base_command_1.BaseCommand {
13
14
  static description = 'Create a new space';
14
15
  static examples = [
@@ -51,6 +52,11 @@ class SpacesCreate extends base_command_1.BaseCommand {
51
52
  allowNo: true,
52
53
  description: 'Inherit members/permissions from the parent chain (default). Use --no-inherit-access to make a private subspace.',
53
54
  }),
55
+ 'working-directory': core_1.Flags.string({
56
+ description: "Space-level working directory for this space's local Harness runs — an absolute " +
57
+ "path on the executing daemon's filesystem. The middle layer of task → space → " +
58
+ 'agent: a per-task override wins, the agent default applies when neither is set.',
59
+ }),
54
60
  };
55
61
  async run() {
56
62
  this.requireAuth();
@@ -95,7 +101,18 @@ class SpacesCreate extends base_command_1.BaseCommand {
95
101
  groupChatId: flags['group-chat'],
96
102
  parentSpaceId: flags.parent,
97
103
  inheritAccess: flags['inherit-access'],
104
+ workingDirectory: (0, tasks_1.workingDirectoryFromFlag)(flags['working-directory']),
98
105
  });
106
+ // Post-merge, so --working-directory and --from-json are checked
107
+ // identically: a relative path is refused here rather than stored and
108
+ // silently fallen back from at run time on the daemon.
109
+ const workingDirectoryError = (0, tasks_1.workingDirectoryValidationError)(payload.workingDirectory);
110
+ if (workingDirectoryError) {
111
+ this.error(workingDirectoryError, { exit: 2 });
112
+ }
113
+ // 'none' on create means "no space default" — the field is absent anyway.
114
+ if (payload.workingDirectory === null)
115
+ delete payload.workingDirectory;
99
116
  if (!payload.title || typeof payload.title !== 'string') {
100
117
  this.error('A title is required. Pass --title "..." or include it in --from-json.', {
101
118
  exit: 1,
@@ -57,6 +57,11 @@ class SpacesShow extends base_command_1.BaseCommand {
57
57
  this.log(`Leader: ${s.leadAgentId ?? '-'}`);
58
58
  this.log(`Target Date: ${s.targetDate ?? '-'}`);
59
59
  this.log(`Goals: ${Array.isArray(s.goalIds) ? s.goalIds.length : 0}`);
60
+ // Only when set — the space default is the middle layer of the
61
+ // task → space → agent cascade, and most spaces leave it unset.
62
+ if (typeof s.workingDirectory === 'string' && s.workingDirectory) {
63
+ this.log(`Directory: ${s.workingDirectory}`);
64
+ }
60
65
  this.log(`Archived: ${s.archivedAt ? 'yes' : 'no'}`);
61
66
  this.log(`Parent: ${s.parentSpaceId ?? '(root)'}`);
62
67
  this.log(`Depth: ${s.depth ?? 0}`);
@@ -30,6 +30,7 @@ export default class SpacesUpdate extends BaseCommand {
30
30
  'auto-start-on-backlog-exit': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
31
31
  'triage-external-intake': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
32
32
  'default-task-template': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
33
+ 'working-directory': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
33
34
  };
34
35
  run(): Promise<void>;
35
36
  }
@@ -135,6 +135,12 @@ class SpacesUpdate extends base_command_1.BaseCommand {
135
135
  description: 'Default task template for new tasks in this space — a template id, reference, or ' +
136
136
  'exact title (the server resolves it to the template). Pass "none" to clear.',
137
137
  }),
138
+ 'working-directory': core_1.Flags.string({
139
+ description: "Space-level working directory for this space's local Harness runs — an absolute " +
140
+ "path on the executing daemon's filesystem. The middle layer of task → space → " +
141
+ 'agent: a per-task override wins, the agent default applies when neither is set. ' +
142
+ 'Pass "none" to clear.',
143
+ }),
138
144
  };
139
145
  async run() {
140
146
  this.requireAuth();
@@ -159,11 +165,21 @@ class SpacesUpdate extends base_command_1.BaseCommand {
159
165
  autoStartOnBacklogExit: flags['auto-start-on-backlog-exit'],
160
166
  triageExternalIntake: flags['triage-external-intake'],
161
167
  defaultTaskTemplateId: clearable(flags['default-task-template']),
168
+ // Per-space working-directory default. 'none'/'clear'/'null' ⇒ null
169
+ // ($unset → resolution falls through to the agent default).
170
+ workingDirectory: (0, tasks_1.workingDirectoryFromFlag)(flags['working-directory']),
162
171
  estimateScale: flags['estimate-scale'],
163
172
  estimateScaleMigration: flags['estimate-migration'],
164
173
  defaultEstimate: flags['default-estimate'],
165
174
  useDefaultEstimateForUnestimated: flags['use-default-estimate'],
166
175
  });
176
+ // Post-merge, so --working-directory and --from-json are checked
177
+ // identically. A relative path stores fine and then silently falls back on
178
+ // the daemon at run time — refuse it here, where the mistake can be named.
179
+ const workingDirectoryError = (0, tasks_1.workingDirectoryValidationError)(payload.workingDirectory);
180
+ if (workingDirectoryError) {
181
+ this.error(workingDirectoryError, { exit: 2 });
182
+ }
167
183
  // Worktree-isolation default lives on the space's deliveryPolicy subdoc,
168
184
  // which the API replaces wholesale on update. Merge into the policy the user
169
185
  // supplied via --from-json, else the space's current policy, so setting just
@@ -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;
@@ -65,6 +70,7 @@ export default class TasksCreate extends BaseCommand {
65
70
  estimate: import("@oclif/core/lib/interfaces").OptionFlag<number | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
66
71
  tag: import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
67
72
  'label-id': import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
73
+ 'working-directory': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
68
74
  worktree: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
69
75
  'lifecycle-definition': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
70
76
  'lifecycle-version': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
@@ -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,13 +234,18 @@ 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
241
  'An unknown name is refused, never created. Manage labels with `skrr labels`.',
227
242
  multiple: true,
228
243
  }),
244
+ 'working-directory': core_1.Flags.string({
245
+ description: "Working directory for this task's local Harness runs — an absolute path on " +
246
+ "the daemon's filesystem. Overrides the agent's default; omit to inherit the " +
247
+ 'agent default, then the daemon session default.',
248
+ }),
229
249
  worktree: core_1.Flags.boolean({
230
250
  allowNo: true,
231
251
  description: "Isolate this task's autonomous runs in a one-shot git worktree (--worktree), " +
@@ -512,6 +532,11 @@ class TasksCreate extends base_command_1.BaseCommand {
512
532
  const estimateWarning = droppedEstimateWarning(flags.estimate, created, payload.spaceId);
513
533
  if (estimateWarning)
514
534
  this.warn(estimateWarning);
535
+ // A `--tag` spelled like a declared label is almost always a label someone
536
+ // meant to attach (OSK-12053). Say so; never convert it implicitly.
537
+ const tagWarning = await taskTagLabelWarning(flags.tag, resolvedLabelIds);
538
+ if (tagWarning)
539
+ this.warn(tagWarning);
515
540
  if (flags.json) {
516
541
  // The raw task row alone cannot tell a script the assignee it implicitly
517
542
  // asked for was dropped (`assigneeAgentIds: []`) from "nobody was ever
@@ -402,6 +402,51 @@ class TasksShow extends base_command_1.BaseCommand {
402
402
  this.log(`Result: ${priorLine}`);
403
403
  }
404
404
  this.log(`Executor: ${taskExecutorLabel(t, this.config.bin)}`);
405
+ // Effective run directory, resolved the same way the server resolves it —
406
+ // most specific scope wins: task → space → agent. The daemon layers below
407
+ // (session.defaultCwd → --cwd) are the daemon's own and cannot be read
408
+ // here, so when no owned layer wins the row is omitted rather than
409
+ // labelled "inherited". Two extra reads, paid only when the row can say
410
+ // something (a detail view).
411
+ if (typeof t.workingDirectory === 'string' && t.workingDirectory) {
412
+ this.log(`Directory: ${t.workingDirectory} (task)`);
413
+ }
414
+ else {
415
+ let spaceDir = null;
416
+ let agentDir = null;
417
+ if (typeof t.spaceId === 'string' && t.spaceId) {
418
+ try {
419
+ const space = (await data_provider_1.dataService.getSpace(t.spaceId));
420
+ if (typeof space?.workingDirectory === 'string' && space.workingDirectory) {
421
+ spaceDir = space.workingDirectory;
422
+ }
423
+ }
424
+ catch {
425
+ // Unreadable space → no claim, not a wrong one.
426
+ }
427
+ }
428
+ if (!spaceDir) {
429
+ const agentRef = (typeof t.driAgentId === 'string' && t.driAgentId) ||
430
+ (Array.isArray(t.assigneeAgentIds) ? t.assigneeAgentIds[0] : null);
431
+ if (agentRef) {
432
+ try {
433
+ const agentRow = (await data_provider_1.dataService.getAgentById({
434
+ agent_id: agentRef,
435
+ }));
436
+ if (typeof agentRow?.workingDirectory === 'string' && agentRow.workingDirectory) {
437
+ agentDir = agentRow.workingDirectory;
438
+ }
439
+ }
440
+ catch {
441
+ // Same rule: unreadable → silence.
442
+ }
443
+ }
444
+ }
445
+ const inheritedDir = spaceDir ?? agentDir;
446
+ if (inheritedDir) {
447
+ this.log(`Directory: ${inheritedDir} (${spaceDir ? 'space' : 'agent'} default)`);
448
+ }
449
+ }
405
450
  this.log(`Priority: ${t.priority ?? '-'}`);
406
451
  this.log(`Parent: ${t.parentId ?? '-'}`);
407
452
  this.log(`Space: ${t.spaceId ?? '-'}`);
@@ -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} approval requests waiting on you`,
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
+ }
@@ -58,6 +58,7 @@ export default class TasksUpdate extends BaseCommand {
58
58
  tag: import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
59
59
  'label-id': import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
60
60
  'repair-release-pins': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
61
+ 'working-directory': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
61
62
  worktree: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
62
63
  };
63
64
  run(): Promise<void>;
@@ -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,7 +157,10 @@ 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
166
  description: 'Replace the task labels with these ids (repeatable, whole-list). ' +
@@ -168,6 +172,11 @@ class TasksUpdate extends base_command_1.BaseCommand {
168
172
  'rejected with AGENT_RELEASE_PIN_STATE_INVALID: pins that govern no current ' +
169
173
  'assignee are dropped and broken ones are re-resolved (recorded in the activity log).',
170
174
  }),
175
+ 'working-directory': core_1.Flags.string({
176
+ description: "Working directory for this task's local Harness runs — an absolute path on " +
177
+ 'the daemon\'s filesystem. Overrides the agent\'s default. Pass "none" to ' +
178
+ 'clear back to inheriting the agent default.',
179
+ }),
171
180
  worktree: core_1.Flags.boolean({
172
181
  allowNo: true,
173
182
  description: "Isolate this task's autonomous runs in a git worktree (--worktree) or force " +
@@ -348,6 +357,10 @@ class TasksUpdate extends base_command_1.BaseCommand {
348
357
  // Worktree isolation override. --worktree ⇒ 'ephemeral', --no-worktree ⇒
349
358
  // 'shared', omitted ⇒ unchanged. (Clear-to-inherit via --from-json.)
350
359
  executionIsolation: (0, tasks_2.executionIsolationFromWorktreeFlag)(flags.worktree),
360
+ // Per-task working-directory override. --working-directory <abs path> ⇒
361
+ // set; "none"/"clear"/"null" ⇒ null (clears → inherit the agent
362
+ // default); omitted ⇒ unchanged.
363
+ workingDirectory: (0, tasks_2.workingDirectoryFromFlag)(flags['working-directory']),
351
364
  // A mutation option, not a field — `undefined` unless explicitly asked
352
365
  // for, so it never counts toward the "nothing to update" check below.
353
366
  repairReleasePins: flags['repair-release-pins'] ? true : undefined,
@@ -361,6 +374,18 @@ class TasksUpdate extends base_command_1.BaseCommand {
361
374
  retryable: false,
362
375
  });
363
376
  }
377
+ // Post-merge, so flag and --from-json inputs are checked identically. A
378
+ // relative path stores fine and then silently falls back at run time —
379
+ // refuse it here, where the mistake can still be named.
380
+ const workingDirectoryError = (0, tasks_2.workingDirectoryValidationError)(payload.workingDirectory);
381
+ if (workingDirectoryError) {
382
+ this.failWithCliError({
383
+ message: workingDirectoryError,
384
+ code: 'INVALID_TASK_WORKING_DIRECTORY',
385
+ exit: 2,
386
+ retryable: false,
387
+ });
388
+ }
364
389
  if (payloadKeys.length === 0) {
365
390
  this.failWithCliError({
366
391
  message: 'Nothing to update. Pass at least one flag (e.g. --title) or --from-json.',
@@ -423,6 +448,9 @@ class TasksUpdate extends base_command_1.BaseCommand {
423
448
  const estimateWarning = (0, create_1.droppedEstimateWarning)(flags.estimate, updated, updated?.spaceId);
424
449
  if (estimateWarning)
425
450
  this.warn(estimateWarning);
451
+ const tagWarning = await (0, create_1.taskTagLabelWarning)(flags.tag, flags['label-id']);
452
+ if (tagWarning)
453
+ this.warn(tagWarning);
426
454
  if (flags.json) {
427
455
  this.log(JSON.stringify((0, web_url_1.withTaskUrl)(this.cliConfig.baseURL, updated), null, 2));
428
456
  return;
@@ -20,6 +20,7 @@ export default class TasksUpsert extends BaseCommand {
20
20
  'assignee-user': import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
21
21
  'execution-mode': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
22
22
  'due-date': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
23
+ 'working-directory': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
23
24
  tag: import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
24
25
  };
25
26
  run(): Promise<void>;
@@ -57,6 +57,10 @@ class TasksUpsert extends base_command_1.BaseCommand {
57
57
  options: ['agent', 'human'],
58
58
  }),
59
59
  'due-date': core_1.Flags.string({ description: 'Due date (ISO string)' }),
60
+ 'working-directory': core_1.Flags.string({
61
+ description: "Working directory for this task's local Harness runs — an absolute path on " +
62
+ 'the daemon\'s filesystem. Pass "none" to clear an existing override.',
63
+ }),
60
64
  tag: core_1.Flags.string({ description: 'Replace/create tag (repeatable)', multiple: true }),
61
65
  };
62
66
  async run() {
@@ -75,6 +79,18 @@ class TasksUpsert extends base_command_1.BaseCommand {
75
79
  });
76
80
  }
77
81
  const payload = (0, tasks_1.buildTaskCreatePayload)(body, flags);
82
+ // Checked here, not inside validateTaskCreatePayload's create-leg call —
83
+ // an upsert that resolves to UPDATE never reaches that validator, and a
84
+ // relative path would silently no-op at run time on the daemon.
85
+ const workingDirectoryError = (0, tasks_1.workingDirectoryValidationError)(payload.workingDirectory);
86
+ if (workingDirectoryError) {
87
+ this.failWithCliError({
88
+ message: workingDirectoryError,
89
+ code: 'INVALID_TASK_WORKING_DIRECTORY',
90
+ exit: 2,
91
+ retryable: false,
92
+ });
93
+ }
78
94
  let scope;
79
95
  try {
80
96
  scope = await (0, tasks_1.resolveTaskCreateScope)(payload);
@@ -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,8 @@ 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"];
5
7
  export declare const RELEASE_INSTALL_CHANNELS: readonly ["latest", "manual"];
6
8
  /**
7
9
  * `09:00-18:00` or `09:00-18:00@America/Los_Angeles`, or a JSON object.
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.RELEASE_INSTALL_CHANNELS = exports.COMMITMENT_PERMISSION_FLAG_RETIRED = exports.REASONING_TOOL_ESCAPES = exports.COMMITMENT_RESPONSIBILITY_LEVELS = void 0;
3
+ exports.RELEASE_INSTALL_CHANNELS = exports.RETIRED_COMMITMENT_PERMISSION_FLAGS = exports.COMMITMENT_PERMISSION_FLAG_RETIRED = exports.REASONING_TOOL_ESCAPES = exports.COMMITMENT_RESPONSIBILITY_LEVELS = void 0;
4
4
  exports.parseWorkingHoursFlag = parseWorkingHoursFlag;
5
5
  exports.mergeAgentConfigFlags = mergeAgentConfigFlags;
6
6
  const prompt_1 = require("./prompt");
@@ -9,6 +9,12 @@ exports.COMMITMENT_RESPONSIBILITY_LEVELS = ['reasoning_only', 'full_ownership'];
9
9
  exports.REASONING_TOOL_ESCAPES = ['bash', 'browser'];
10
10
  /** Warning printed by the three retired Commitment permission flags (OSK-11975). */
11
11
  exports.COMMITMENT_PERMISSION_FLAG_RETIRED = "This setting no longer has any effect: Commitment runs use the agent's own permissions.";
12
+ /** The three retired flags themselves: accepted at parse, warned on, never sent. */
13
+ exports.RETIRED_COMMITMENT_PERMISSION_FLAGS = [
14
+ 'reasoning-tool-escape',
15
+ 'commitment-responsibility',
16
+ 'commitment-responsibility-ceiling',
17
+ ];
12
18
  exports.RELEASE_INSTALL_CHANNELS = ['latest', 'manual'];
13
19
  /**
14
20
  * `09:00-18:00` or `09:00-18:00@America/Los_Angeles`, or a JSON object.
@@ -102,15 +108,13 @@ function mergeAgentConfigFlags(body, flags) {
102
108
  if (flags['todo-working-hours'] !== undefined) {
103
109
  parsed.todoWorkingHours = parseWorkingHoursFlag(String(flags['todo-working-hours']));
104
110
  }
105
- if (flags['reasoning-tool-escape'] !== undefined) {
106
- parsed.reasoningToolEscapes = flags['reasoning-tool-escape'];
107
- }
108
- if (flags['commitment-responsibility'] !== undefined) {
109
- parsed.commitmentResponsibilityDefault = flags['commitment-responsibility'];
110
- }
111
- if (flags['commitment-responsibility-ceiling'] !== undefined) {
112
- parsed.commitmentResponsibilityCeiling = flags['commitment-responsibility-ceiling'];
113
- }
111
+ // `--reasoning-tool-escape`, `--commitment-responsibility` and
112
+ // `--commitment-responsibility-ceiling` are retired (OSK-11975) and are
113
+ // deliberately NOT forwarded. The server still accepts and echoes the stored
114
+ // fields, so sending them wrote a value nothing reads and handed it back as
115
+ // if it were live — and "a ceiling a commitment may not exceed" is a safety
116
+ // claim a person then believes (OSK-12068). The flags stay parseable so old
117
+ // scripts do not break; oclif prints COMMITMENT_PERMISSION_FLAG_RETIRED.
114
118
  if (flags['tool-resources'] !== undefined) {
115
119
  parsed.tool_resources = JSON.parse(String(flags['tool-resources']));
116
120
  }