@skrr-ai/cli 0.1.96 → 0.1.97

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.
@@ -72,7 +72,7 @@ class DaemonInstall extends core_1.Command {
72
72
  this.log('');
73
73
  this.log(' You can install it directly instead:');
74
74
  this.log(' macOS: brew install dush1023/oversky/oversky');
75
- this.log(' Linux: curl -fsSL https://raw.githubusercontent.com/dush1023/OverSky/main/scripts/install-daemon.sh | bash');
75
+ this.log(' Linux: curl -fsSL https://updates.skrr.ai/daemon/install.sh | bash');
76
76
  this.exit(1);
77
77
  }
78
78
  this.log(` Installed skrrd ${outcome.version} (${outcome.platformKey}) at ${outcome.binaryPath}`);
@@ -38,7 +38,9 @@ const core_1 = require("@oclif/core");
38
38
  const base_command_1 = require("../../../base-command");
39
39
  const outbox_1 = require("../../../lib/outbox");
40
40
  const api_fetch_1 = require("../../../lib/api-fetch");
41
- const session_task_endpoints_1 = require("../../../lib/session-task-endpoints");
41
+ const credential_resolver_1 = require("../../../lib/credential-resolver");
42
+ const unsupported_managed_task_write_1 = require("../../../lib/unsupported-managed-task-write");
43
+ const session_task_write_1 = require("../../../lib/session-task-write");
42
44
  const prompt_1 = require("../../../lib/prompt");
43
45
  const task_resolver_1 = require("../../../lib/task-resolver");
44
46
  // Server-side validator on `commentMarkId` (api/server/routes/tasks.js).
@@ -142,6 +144,18 @@ class TasksCommentsAdd extends base_command_1.BaseCommand {
142
144
  // pre-formed `anchor` object and have CLI flags override individual
143
145
  // fields (e.g. only --anchor-quote, keeping the JSON's commentMarkId).
144
146
  const anchor = this.buildAnchor(flags, commentBody);
147
+ if (anchor || flags['parent-comment'] || commentBody.parentCommentId ||
148
+ flags.reopen || commentBody.reopen || flags.interrupt || commentBody.interrupt) {
149
+ // These fields are absent from the scoped comment contract. Refuse
150
+ // before uploads, rather than posting a different comment than asked.
151
+ try {
152
+ (0, unsupported_managed_task_write_1.refuseUnsupportedManagedTaskWrite)('Replies, anchors and comment workflow changes');
153
+ }
154
+ catch (error) {
155
+ this.handleApiError(error);
156
+ return;
157
+ }
158
+ }
145
159
  // Merge the full JSON body with typed flags so --from-json fields like
146
160
  // mentionedAgentIds, reopen, and interrupt are preserved. Boolean flags
147
161
  // default to false; only pass them through when actually set so they
@@ -207,30 +221,18 @@ class TasksCommentsAdd extends base_command_1.BaseCommand {
207
221
  // apiFetch rather than `dataService.addTaskComment`: the shared transport
208
222
  // accepts no per-call headers, so it cannot carry the key.
209
223
  //
210
- // WL-5 Step 1/2 — a session on a NARROWED credential cannot use
211
- // `/api/tasks/:taskId/comments`, whose chain begins with `requireJwtAuth`.
212
- // The session-scoped sibling derives the task from the claim, so it takes
213
- // no taskId, and it is deliberately narrower: `body` and
224
+ // Retain the authenticated Agent even beside a wide delegated credential.
225
+ // The scoped sibling derives its Task and supports `body` and
214
226
  // `mentionedAgentIds` only. `reopen`/`interrupt` are workflow mutations
215
227
  // and are not reachable through the commenting door on that path.
216
- const sessionPath = await (0, session_task_endpoints_1.sessionScopedTaskPath)('comment');
217
- if (sessionPath && (flags.reopen || flags.interrupt)) {
218
- // Say so rather than dropping it. A flag the user passed that silently
219
- // does nothing is worse than a refusal — they would read the 201 as
220
- // "the task reopened" and only find out much later that it did not.
221
- this.warn('This session uses a scoped credential, where --reopen/--interrupt are not available: ' +
222
- 'they are workflow changes, not comments. The comment was posted; the task was not ' +
223
- 'reopened or interrupted.');
224
- }
225
- const mentioned = Array.isArray(mentionedFromFlag) && mentionedFromFlag.length > 0
226
- ? mentionedFromFlag
227
- : undefined;
228
- result = sessionPath
229
- ? await (0, api_fetch_1.apiFetch)(sessionPath, {
228
+ const sessionWrite = await (0, session_task_write_1.sessionTaskWrite)('comment', taskRef);
229
+ result = sessionWrite
230
+ ? await (0, api_fetch_1.apiFetch)(sessionWrite.path, {
230
231
  method: 'POST',
232
+ credential: sessionWrite.credential,
231
233
  body: {
232
234
  body: data.body,
233
- ...(mentioned ? { mentionedAgentIds: mentioned } : {}),
235
+ ...(data.mentionedAgentIds ? { mentionedAgentIds: data.mentionedAgentIds } : {}),
234
236
  },
235
237
  headers: { 'X-Idempotency-Key': idempotencyKey },
236
238
  })
@@ -241,6 +243,13 @@ class TasksCommentsAdd extends base_command_1.BaseCommand {
241
243
  });
242
244
  }
243
245
  catch (err) {
246
+ // Offline replay cannot preserve this live session actor. Never queue
247
+ // an Agent write as an ordinary Human comment. Retry the exact key
248
+ // while this authenticated session remains valid.
249
+ if ((0, credential_resolver_1.resolveTrustedAutonomousCredential)()) {
250
+ this.handleApiError(err);
251
+ return;
252
+ }
244
253
  // A comment body is authored content: losing it to a 503 costs the
245
254
  // writing, not just a retry. Queue it durably instead (OSK-2806). The
246
255
  // key is derived from the task + body so replaying the SAME comment
@@ -40,7 +40,7 @@ const task_closure_1 = require("../../lib/task-closure");
40
40
  const api_fetch_1 = require("../../lib/api-fetch");
41
41
  const outbox_1 = require("../../lib/outbox");
42
42
  const prompt_1 = require("../../lib/prompt");
43
- const session_task_endpoints_1 = require("../../lib/session-task-endpoints");
43
+ const session_task_write_1 = require("../../lib/session-task-write");
44
44
  const task_resolver_1 = require("../../lib/task-resolver");
45
45
  const task_extras_1 = require("../../lib/task-extras");
46
46
  const web_url_1 = require("../../lib/web-url");
@@ -290,9 +290,11 @@ class TasksComplete extends base_command_1.BaseCommand {
290
290
  };
291
291
  const idempotencyKey = optionalString(input.idempotencyKey) ||
292
292
  (0, outbox_1.idempotencyKeyFor)('task-complete', identity.id, JSON.stringify(payload));
293
- const sessionPath = await (0, session_task_endpoints_1.sessionScopedTaskPath)('completion');
293
+ const sessionWrite = await (0, session_task_write_1.sessionTaskWrite)('completion', identity.id);
294
294
  let taskId = identity.id;
295
- if (!sessionPath) {
295
+ if (sessionWrite)
296
+ taskId = sessionWrite.taskId;
297
+ else {
296
298
  try {
297
299
  const resolved = await this.resolveTaskReference(identity.id);
298
300
  taskId = String(resolved);
@@ -303,8 +305,9 @@ class TasksComplete extends base_command_1.BaseCommand {
303
305
  }
304
306
  let result;
305
307
  try {
306
- result = await (0, api_fetch_1.apiFetch)(sessionPath || (0, task_extras_1.taskCompletePath)(taskId), {
308
+ result = await (0, api_fetch_1.apiFetch)(sessionWrite?.path || (0, task_extras_1.taskCompletePath)(taskId), {
307
309
  method: 'POST',
310
+ credential: sessionWrite?.credential,
308
311
  body: { ...payload, idempotencyKey },
309
312
  headers: { 'X-Idempotency-Key': idempotencyKey },
310
313
  });
@@ -42,7 +42,7 @@ const base_command_1 = require("../../../base-command");
42
42
  const task_asset_upload_1 = require("../../../lib/task-asset-upload");
43
43
  const outbox_1 = require("../../../lib/outbox");
44
44
  const api_fetch_1 = require("../../../lib/api-fetch");
45
- const session_task_endpoints_1 = require("../../../lib/session-task-endpoints");
45
+ const session_task_write_1 = require("../../../lib/session-task-write");
46
46
  const tasks_1 = require("../../../lib/tasks");
47
47
  function defaultDeliverableIdempotencyKey(task, payload) {
48
48
  // A retry of one exact observation keeps its key. A new version, provider,
@@ -192,10 +192,11 @@ class TasksDeliverableAdd extends base_command_1.BaseCommand {
192
192
  idempotencyKey: flags['idempotency-key'] ||
193
193
  defaultDeliverableIdempotencyKey(args.task, observation),
194
194
  };
195
- const sessionPath = await (0, session_task_endpoints_1.sessionScopedTaskPath)('deliverable');
196
- result = sessionPath
197
- ? await (0, api_fetch_1.apiFetch)(sessionPath, {
195
+ const sessionWrite = await (0, session_task_write_1.sessionTaskWrite)('deliverable', args.task);
196
+ result = sessionWrite
197
+ ? await (0, api_fetch_1.apiFetch)(sessionWrite.path, {
198
198
  method: 'POST',
199
+ credential: sessionWrite.credential,
199
200
  body: payload,
200
201
  })
201
202
  : await data_provider_1.dataService.addTaskDeliverable(args.task, payload);
@@ -5,7 +5,7 @@ const data_provider_1 = require("@skrr-ai/data-provider");
5
5
  const base_command_1 = require("../../../base-command");
6
6
  const task_resolver_1 = require("../../../lib/task-resolver");
7
7
  const api_fetch_1 = require("../../../lib/api-fetch");
8
- const session_task_endpoints_1 = require("../../../lib/session-task-endpoints");
8
+ const session_task_write_1 = require("../../../lib/session-task-write");
9
9
  const task_artifact_flags_1 = require("../../../lib/task-artifact-flags");
10
10
  const tasks_1 = require("../../../lib/tasks");
11
11
  class TasksEventsAppend extends base_command_1.BaseCommand {
@@ -99,17 +99,18 @@ class TasksEventsAppend extends base_command_1.BaseCommand {
99
99
  ? deliverables.map((ref) => ({ label: 'deliverable', ref }))
100
100
  : undefined,
101
101
  };
102
- const path = await (0, session_task_endpoints_1.sessionScopedTaskPath)('event');
102
+ let sessionWrite;
103
103
  let result;
104
104
  try {
105
- result = path
106
- ? await (0, api_fetch_1.apiFetch)(path, { method: 'POST', body: data })
105
+ sessionWrite = await (0, session_task_write_1.sessionTaskWrite)('event', task.id);
106
+ result = sessionWrite
107
+ ? await (0, api_fetch_1.apiFetch)(sessionWrite.path, { method: 'POST', body: data, credential: sessionWrite.credential })
107
108
  : await data_provider_1.dataService.appendTaskWorkEvent({ taskId: task.id, data: data });
108
109
  }
109
110
  catch (error) {
110
111
  this.handleApiError(error);
111
112
  }
112
- if (path && result && typeof result === 'object' && 'entry' in result)
113
+ if (sessionWrite && result && typeof result === 'object' && 'entry' in result)
113
114
  result = result.entry;
114
115
  if (flags.json)
115
116
  return this.log(JSON.stringify(result, null, 2));
@@ -6,7 +6,7 @@ const base_command_1 = require("../../../base-command");
6
6
  const outbox_1 = require("../../../lib/outbox");
7
7
  const task_resolver_1 = require("../../../lib/task-resolver");
8
8
  const api_fetch_1 = require("../../../lib/api-fetch");
9
- const session_task_endpoints_1 = require("../../../lib/session-task-endpoints");
9
+ const session_task_write_1 = require("../../../lib/session-task-write");
10
10
  class TasksExpectationsAssess extends base_command_1.BaseCommand {
11
11
  static description = 'Assess one current Task Expectation with evidence and a reason';
12
12
  // Both positionals are declared optional because oclif REJECTS an args table
@@ -136,9 +136,9 @@ class TasksExpectationsAssess extends base_command_1.BaseCommand {
136
136
  ? flags['idempotency-key']
137
137
  : (0, outbox_1.idempotencyKeyFor)('task-expectation-assessment', task.id, JSON.stringify({ expectationId: id, ...payload })),
138
138
  };
139
- const sessionPath = await (0, session_task_endpoints_1.sessionScopedTaskPath)('assessment');
140
- return sessionPath
141
- ? await (0, api_fetch_1.apiFetch)(sessionPath, { method: 'POST', body: data })
139
+ const sessionWrite = await (0, session_task_write_1.sessionTaskWrite)('assessment', task.id);
140
+ return sessionWrite
141
+ ? await (0, api_fetch_1.apiFetch)(sessionWrite.path, { method: 'POST', body: data, credential: sessionWrite.credential })
142
142
  : await data_provider_1.dataService.assessTaskExpectation({ taskId: task.id, expectationId: id, data });
143
143
  };
144
144
  if (flags.all) {
@@ -5,7 +5,8 @@ const data_provider_1 = require("@skrr-ai/data-provider");
5
5
  const api_fetch_1 = require("../../lib/api-fetch");
6
6
  const http_error_1 = require("../../lib/http-error");
7
7
  const workflow_state_hint_1 = require("../../lib/workflow-state-hint");
8
- const session_task_endpoints_1 = require("../../lib/session-task-endpoints");
8
+ const unsupported_managed_task_write_1 = require("../../lib/unsupported-managed-task-write");
9
+ const session_task_write_1 = require("../../lib/session-task-write");
9
10
  const base_command_1 = require("../../base-command");
10
11
  const task_closure_1 = require("../../lib/task-closure");
11
12
  const web_url_1 = require("../../lib/web-url");
@@ -78,6 +79,15 @@ class TasksMove extends base_command_1.BaseCommand {
78
79
  }
79
80
  const taskRef = await this.resolveEntityRef('task', taskIdentity.id);
80
81
  const projectId = (0, projects_1.resolveTaskProjectFlag)(flags.project);
82
+ if (projectId !== undefined) {
83
+ try {
84
+ (0, unsupported_managed_task_write_1.refuseUnsupportedManagedTaskWrite)('Project changes', 'mcp__tasks__task_update');
85
+ }
86
+ catch (error) {
87
+ this.handleApiError(error);
88
+ return;
89
+ }
90
+ }
81
91
  if (!flags.status && projectId === undefined) {
82
92
  this.failWithCliError({
83
93
  message: 'Nothing to move. Pass --status <status>, --project <project-id>, or both.',
@@ -92,16 +102,10 @@ class TasksMove extends base_command_1.BaseCommand {
92
102
  let updated;
93
103
  try {
94
104
  if (flags.status) {
95
- // WL-5 Step 1/2 — a session on a NARROWED credential cannot use
96
- // `/api/tasks/:taskId/move`: that chain begins with `requireJwtAuth`,
97
- // which cannot decode an `agst_`. The session-scoped sibling derives
98
- // the task from the claim instead, so it takes no taskId at all.
99
- //
100
- // `null` here is the ordinary answer — a human, or a delegated session
101
- // still holding a wide credential. See `sessionScopedTaskPath`.
102
- const sessionPath = await (0, session_task_endpoints_1.sessionScopedTaskPath)('move');
103
- if (sessionPath) {
104
- const res = (await (0, api_fetch_1.apiFetch)(sessionPath, {
105
+ const sessionWrite = await (0, session_task_write_1.sessionTaskWrite)('move', taskRef);
106
+ if (sessionWrite) {
107
+ const res = (await (0, api_fetch_1.apiFetch)(sessionWrite.path, {
108
+ credential: sessionWrite.credential,
105
109
  method: 'POST',
106
110
  body: {
107
111
  status: flags.status,
@@ -8,6 +8,8 @@ const task_report_1 = require("../../lib/task-report");
8
8
  const task_resolver_1 = require("../../lib/task-resolver");
9
9
  const tasks_1 = require("../../lib/tasks");
10
10
  const task_artifact_flags_1 = require("../../lib/task-artifact-flags");
11
+ const api_fetch_1 = require("../../lib/api-fetch");
12
+ const session_task_write_1 = require("../../lib/session-task-write");
11
13
  const web_url_1 = require("../../lib/web-url");
12
14
  /**
13
15
  * Compatibility spelling for submitting a versioned Task Result.
@@ -108,10 +110,13 @@ class TasksReport extends base_command_1.BaseCommand {
108
110
  continue;
109
111
  }
110
112
  try {
111
- const recorded = await data_provider_1.dataService.addTaskDeliverable(task.id, {
112
- ...parsed.value,
113
- idempotencyKey: (0, outbox_1.idempotencyKeyFor)('task-deliverable', task.id, ref),
114
- });
113
+ const data = { ...parsed.value, idempotencyKey: (0, outbox_1.idempotencyKeyFor)('task-deliverable', task.id, ref) };
114
+ const sessionWrite = await (0, session_task_write_1.sessionTaskWrite)('deliverable', task.id);
115
+ const recorded = sessionWrite
116
+ ? await (0, api_fetch_1.apiFetch)(sessionWrite.path, {
117
+ method: 'POST', body: data, credential: sessionWrite.credential,
118
+ })
119
+ : await data_provider_1.dataService.addTaskDeliverable(task.id, data);
115
120
  resourceLinkIds.push(recorded.link.id);
116
121
  }
117
122
  catch (error) {
@@ -146,13 +151,16 @@ class TasksReport extends base_command_1.BaseCommand {
146
151
  };
147
152
  let result;
148
153
  try {
149
- result = await data_provider_1.dataService.submitTaskResult({
150
- taskId: task.id,
151
- data: {
152
- ...payload,
153
- idempotencyKey: (0, outbox_1.idempotencyKeyFor)('task-result', task.id, JSON.stringify(payload)),
154
- },
155
- });
154
+ const data = {
155
+ ...payload,
156
+ idempotencyKey: (0, outbox_1.idempotencyKeyFor)('task-result', task.id, JSON.stringify(payload)),
157
+ };
158
+ const sessionWrite = await (0, session_task_write_1.sessionTaskWrite)('result', task.id);
159
+ result = sessionWrite
160
+ ? await (0, api_fetch_1.apiFetch)(sessionWrite.path, {
161
+ method: 'POST', body: data, credential: sessionWrite.credential,
162
+ })
163
+ : await data_provider_1.dataService.submitTaskResult({ taskId: task.id, data });
156
164
  }
157
165
  catch (error) {
158
166
  this.handleApiError(error);
@@ -41,7 +41,7 @@ const task_resolver_1 = require("../../../lib/task-resolver");
41
41
  const web_url_1 = require("../../../lib/web-url");
42
42
  const api_fetch_1 = require("../../../lib/api-fetch");
43
43
  const task_closure_1 = require("../../../lib/task-closure");
44
- const session_task_endpoints_1 = require("../../../lib/session-task-endpoints");
44
+ const session_task_write_1 = require("../../../lib/session-task-write");
45
45
  function parseJson(value, label) {
46
46
  if (!value)
47
47
  return undefined;
@@ -181,10 +181,11 @@ class TasksResultSubmit extends base_command_1.BaseCommand {
181
181
  idempotencyKey: flags['idempotency-key'] ||
182
182
  (0, outbox_1.idempotencyKeyFor)('task-result', task.id, JSON.stringify(payload)),
183
183
  };
184
- const sessionPath = await (0, session_task_endpoints_1.sessionScopedTaskPath)('result');
185
- result = sessionPath
186
- ? await (0, api_fetch_1.apiFetch)(sessionPath, {
184
+ const sessionWrite = await (0, session_task_write_1.sessionTaskWrite)('result', task.id);
185
+ result = sessionWrite
186
+ ? await (0, api_fetch_1.apiFetch)(sessionWrite.path, {
187
187
  method: 'POST',
188
+ credential: sessionWrite.credential,
188
189
  body: data,
189
190
  })
190
191
  : await data_provider_1.dataService.submitTaskResult({ taskId: task.id, data });
@@ -6,7 +6,7 @@ const base_command_1 = require("../../../base-command");
6
6
  const outbox_1 = require("../../../lib/outbox");
7
7
  const task_resolver_1 = require("../../../lib/task-resolver");
8
8
  const api_fetch_1 = require("../../../lib/api-fetch");
9
- const session_task_endpoints_1 = require("../../../lib/session-task-endpoints");
9
+ const session_task_write_1 = require("../../../lib/session-task-write");
10
10
  const prompt_1 = require("../../../lib/prompt");
11
11
  class TasksResumeSave extends base_command_1.BaseCommand {
12
12
  static description = 'Save recovery state without publishing a visible Task Update';
@@ -53,10 +53,11 @@ class TasksResumeSave extends base_command_1.BaseCommand {
53
53
  idempotencyKey: flags['idempotency-key'] ||
54
54
  (0, outbox_1.idempotencyKeyFor)('task-resume', task.id, JSON.stringify(resume)),
55
55
  };
56
- const sessionPath = await (0, session_task_endpoints_1.sessionScopedTaskPath)('resume');
57
- result = sessionPath
58
- ? await (0, api_fetch_1.apiFetch)(sessionPath, {
56
+ const sessionWrite = await (0, session_task_write_1.sessionTaskWrite)('resume', task.id);
57
+ result = sessionWrite
58
+ ? await (0, api_fetch_1.apiFetch)(sessionWrite.path, {
59
59
  method: 'POST',
60
+ credential: sessionWrite.credential,
60
61
  body: payload,
61
62
  })
62
63
  : await data_provider_1.dataService.saveTaskResumePoint({ taskId: task.id, data: payload });
@@ -6,7 +6,7 @@ const base_command_1 = require("../../base-command");
6
6
  const outbox_1 = require("../../lib/outbox");
7
7
  const task_resolver_1 = require("../../lib/task-resolver");
8
8
  const api_fetch_1 = require("../../lib/api-fetch");
9
- const session_task_endpoints_1 = require("../../lib/session-task-endpoints");
9
+ const session_task_write_1 = require("../../lib/session-task-write");
10
10
  class TasksReview extends base_command_1.BaseCommand {
11
11
  static description = 'Accept or request changes on one exact Task Result';
12
12
  static args = {
@@ -38,9 +38,9 @@ class TasksReview extends base_command_1.BaseCommand {
38
38
  idempotencyKey: flags['idempotency-key'] ||
39
39
  (0, outbox_1.idempotencyKeyFor)('task-review', task.id, JSON.stringify(payload)),
40
40
  };
41
- const sessionPath = await (0, session_task_endpoints_1.sessionScopedTaskPath)('review');
42
- result = sessionPath
43
- ? await (0, api_fetch_1.apiFetch)(sessionPath, { method: 'POST', body: data })
41
+ const sessionWrite = await (0, session_task_write_1.sessionTaskWrite)('review', task.id);
42
+ result = sessionWrite
43
+ ? await (0, api_fetch_1.apiFetch)(sessionWrite.path, { method: 'POST', body: data, credential: sessionWrite.credential })
44
44
  : await data_provider_1.dataService.reviewTaskResult({ taskId: task.id, data });
45
45
  }
46
46
  catch (error) {
@@ -39,7 +39,7 @@ const base_command_1 = require("../../../base-command");
39
39
  const outbox_1 = require("../../../lib/outbox");
40
40
  const task_resolver_1 = require("../../../lib/task-resolver");
41
41
  const api_fetch_1 = require("../../../lib/api-fetch");
42
- const session_task_endpoints_1 = require("../../../lib/session-task-endpoints");
42
+ const session_task_write_1 = require("../../../lib/session-task-write");
43
43
  function parseJsonObject(value, label) {
44
44
  if (!value)
45
45
  return undefined;
@@ -176,10 +176,11 @@ class TasksUpdatesAdd extends base_command_1.BaseCommand {
176
176
  resume: resume,
177
177
  idempotencyKey,
178
178
  };
179
- const sessionPath = await (0, session_task_endpoints_1.sessionScopedTaskPath)('update');
180
- result = sessionPath
181
- ? await (0, api_fetch_1.apiFetch)(sessionPath, {
179
+ const sessionWrite = await (0, session_task_write_1.sessionTaskWrite)('update', task.id);
180
+ result = sessionWrite
181
+ ? await (0, api_fetch_1.apiFetch)(sessionWrite.path, {
182
182
  method: 'POST',
183
+ credential: sessionWrite.credential,
183
184
  body: payload,
184
185
  })
185
186
  : await data_provider_1.dataService.appendTaskUpdate({ taskId: task.id, data: payload });
@@ -30,7 +30,7 @@ class MailboxHumanCommand extends base_command_1.BaseCommand {
30
30
  }
31
31
  if (this.credentialProvenance?.kind === 'daemon-handoff') {
32
32
  this.failWithCliError({
33
- message: 'Mailbox metadata requires a direct human CLI sign-in. Run `skrr login` in your own terminal, then try again.',
33
+ message: 'Mailbox metadata requires a direct human CLI sign-in. Run `skrr login --device-code` in your own terminal to skip the daemon broker, then try again.',
34
34
  code: 'MAILBOX_HUMAN_CLI_REQUIRED',
35
35
  exit: 2,
36
36
  retryable: false,
@@ -0,0 +1,18 @@
1
+ import type { SessionTaskOperation } from './session-task-endpoints';
2
+ /**
3
+ * A canonical Task write must retain its authenticated session actor even if
4
+ * the daemon also grants the CLI a wide credential. The wide credential names
5
+ * its account, not the Agent that is executing this Task. Passing the trusted
6
+ * session credential explicitly makes apiFetch keep the server's bound actor.
7
+ * Human invocations continue through their ordinary Task endpoints.
8
+ */
9
+ export declare function sessionTaskWrite(operation: Exclude<SessionTaskOperation, 'read'>, requestedTask: string, env?: Record<string, string | undefined>): Promise<{
10
+ path: string;
11
+ credential: import("@skrr-ai/auth-core/dist/esm/credentialResolver").ResolvedCredential;
12
+ taskId: string;
13
+ task: {
14
+ id?: string;
15
+ _id?: string;
16
+ identifier?: string;
17
+ } & Record<string, unknown>;
18
+ } | null>;
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sessionTaskWrite = sessionTaskWrite;
4
+ const api_fetch_1 = require("./api-fetch");
5
+ const credential_resolver_1 = require("./credential-resolver");
6
+ const session_task_endpoints_1 = require("./session-task-endpoints");
7
+ /**
8
+ * A canonical Task write must retain its authenticated session actor even if
9
+ * the daemon also grants the CLI a wide credential. The wide credential names
10
+ * its account, not the Agent that is executing this Task. Passing the trusted
11
+ * session credential explicitly makes apiFetch keep the server's bound actor.
12
+ * Human invocations continue through their ordinary Task endpoints.
13
+ */
14
+ async function sessionTaskWrite(operation, requestedTask, env = process.env) {
15
+ const credential = (0, credential_resolver_1.resolveTrustedAutonomousCredential)(env);
16
+ if (!credential)
17
+ return null;
18
+ const sessionId = env.OVERSKY_SESSION_ID?.trim();
19
+ if (!credential.token || !sessionId) {
20
+ throw Object.assign(new Error('Task writes require this runtime’s bound session credential.'), {
21
+ code: 'SESSION_TASK_CREDENTIAL_MISSING',
22
+ });
23
+ }
24
+ const prefix = `/api/agentic/sessions/${encodeURIComponent(sessionId)}`;
25
+ // A scoped endpoint derives its Task and ignores a caller's Task id. Check
26
+ // the requested coordinate first, so naming a sibling cannot quietly finish
27
+ // the bound Task instead. The server independently rechecks the live binding
28
+ // and checkout when committing the write.
29
+ const bound = await (0, api_fetch_1.apiFetch)(`${prefix}/task`, {
30
+ credential,
31
+ });
32
+ if (!bound.id || !(0, session_task_endpoints_1.requestMatchesBoundTask)(bound, requestedTask)) {
33
+ throw Object.assign(new Error(`This session is bound to task ${bound.identifier || bound.id || '?'} and cannot write the requested Task.`), {
34
+ code: 'SESSION_SCOPE_TASK_MISMATCH',
35
+ });
36
+ }
37
+ return {
38
+ path: `${prefix}/${session_task_endpoints_1.SESSION_TASK_PATH_SUFFIX[operation]}`,
39
+ credential,
40
+ taskId: bound.id,
41
+ task: bound,
42
+ };
43
+ }
@@ -47,9 +47,8 @@ exports.postFileToStorage = postFileToStorage;
47
47
  * ≤25MB or image → buffered multipart (canonicalization needs the bytes)
48
48
  * larger non-image → intent → presigned POST to S3 → complete (OSK-9867)
49
49
  *
50
- * Session routing follows the CREDENTIAL, resolved here once per file so a
51
- * narrowed agent session lands on `task-attachments*` and a human (or a
52
- * delegated session holding a wide credential) lands on `/api/tasks/:id/*`.
50
+ * Agent uploads retain the authenticated session producer even beside a wide
51
+ * delegated credential. Human uploads use `/api/tasks/:id/*`.
53
52
  *
54
53
  * Retry safety is server-side: the sha256 IS the asset identity, so a second
55
54
  * upload of the same bytes returns the existing ready row instead of
@@ -73,7 +72,7 @@ const data_provider_1 = require("@skrr-ai/data-provider");
73
72
  const base_command_1 = require("../base-command");
74
73
  const file_mime_1 = require("./file-mime");
75
74
  const api_fetch_1 = require("./api-fetch");
76
- const session_task_endpoints_1 = require("./session-task-endpoints");
75
+ const session_task_write_1 = require("./session-task-write");
77
76
  function mb(bytes) {
78
77
  return `${Math.floor(bytes / (1024 * 1024))} MB`;
79
78
  }
@@ -89,14 +88,9 @@ function storedDigest(asset) {
89
88
  * refuses to act on any other task before a byte leaves the machine.
90
89
  */
91
90
  async function readTaskForUpload(taskId) {
92
- const readPath = await (0, session_task_endpoints_1.sessionScopedTaskPath)('read');
93
- if (readPath) {
94
- const bound = await (0, api_fetch_1.apiFetch)(readPath);
95
- if (!(0, session_task_endpoints_1.requestMatchesBoundTask)(bound, taskId)) {
96
- throw new Error(`This session is bound to task ${String(bound.identifier ?? bound.id ?? '?')} — a scoped credential cannot attach files to ${taskId}.`);
97
- }
98
- return bound;
99
- }
91
+ const target = await (0, session_task_write_1.sessionTaskWrite)('attachment', taskId);
92
+ if (target)
93
+ return target.task;
100
94
  return (await data_provider_1.dataService.getTask(taskId, { enrich: false }));
101
95
  }
102
96
  /**
@@ -124,14 +118,13 @@ async function uploadTaskAssetFile(opts) {
124
118
  throw new Error(`${filename} exceeds the direct-upload limit (${mb(data_provider_1.TASK_DIRECT_UPLOAD_MAX_BYTES)})`);
125
119
  }
126
120
  const direct = !isImage && size > data_provider_1.TASK_ATTACHMENT_MAX_BYTES;
127
- const sessionPath = await (0, session_task_endpoints_1.sessionScopedTaskPath)('attachment');
121
+ const sessionTarget = await (0, session_task_write_1.sessionTaskWrite)('attachment', taskId);
128
122
  let result;
129
123
  try {
130
- if (sessionPath) {
131
- await readTaskForUpload(taskId);
124
+ if (sessionTarget) {
132
125
  result = direct
133
- ? await sessionDirectUpload({ filePath, size, filename, mimeType, role, ledger })
134
- : await sessionBufferedUpload({ filePath, filename, mimeType, role, ledger });
126
+ ? await sessionDirectUpload({ filePath, size, filename, mimeType, role, ledger }, sessionTarget)
127
+ : await sessionBufferedUpload({ filePath, filename, mimeType, role, ledger }, sessionTarget);
135
128
  }
136
129
  else {
137
130
  result = direct
@@ -229,9 +222,10 @@ async function genericBufferedUpload(p) {
229
222
  formData: fileFormData(p.filePath, p.filename, p.mimeType, p.role, p.ledger),
230
223
  });
231
224
  }
232
- async function sessionBufferedUpload(p) {
233
- return (0, api_fetch_1.apiFetch)((await (0, session_task_endpoints_1.sessionScopedTaskPath)('attachment')), {
225
+ async function sessionBufferedUpload(p, target) {
226
+ return (0, api_fetch_1.apiFetch)(target.path, {
234
227
  method: 'POST',
228
+ credential: target.credential,
235
229
  body: fileFormData(p.filePath, p.filename, p.mimeType, p.role, p.ledger),
236
230
  });
237
231
  }
@@ -267,9 +261,10 @@ async function genericDirectUpload(p) {
267
261
  attachmentId: intent.attachment.id,
268
262
  });
269
263
  }
270
- async function sessionDirectUpload(p) {
271
- const intent = await (0, api_fetch_1.apiFetch)((await (0, session_task_endpoints_1.sessionScopedTaskPath)('attachmentIntent')), {
264
+ async function sessionDirectUpload(p, target) {
265
+ const intent = await (0, api_fetch_1.apiFetch)(`${target.path}/intent`, {
272
266
  method: 'POST',
267
+ credential: target.credential,
273
268
  body: {
274
269
  filename: p.filename,
275
270
  mimeType: p.mimeType,
@@ -296,8 +291,9 @@ async function sessionDirectUpload(p) {
296
291
  filename: p.filename,
297
292
  mimeType: p.mimeType,
298
293
  });
299
- return (0, api_fetch_1.apiFetch)((await (0, session_task_endpoints_1.sessionScopedTaskPath)('attachmentComplete')), {
294
+ return (0, api_fetch_1.apiFetch)(`${target.path}/complete`, {
300
295
  method: 'POST',
296
+ credential: target.credential,
301
297
  body: { attachmentId: intent.attachment.id },
302
298
  });
303
299
  }
@@ -0,0 +1,2 @@
1
+ /** Refuse before a composite command can partially mutate a Task as Human. */
2
+ export declare function refuseUnsupportedManagedTaskWrite(operation: string, supportedTool?: string, env?: Record<string, string | undefined>): void;
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.refuseUnsupportedManagedTaskWrite = refuseUnsupportedManagedTaskWrite;
4
+ const credential_resolver_1 = require("./credential-resolver");
5
+ /** Refuse before a composite command can partially mutate a Task as Human. */
6
+ function refuseUnsupportedManagedTaskWrite(operation, supportedTool, env = process.env) {
7
+ if (!(0, credential_resolver_1.resolveTrustedAutonomousCredential)(env))
8
+ return;
9
+ throw Object.assign(new Error(`${operation} have no bound-session CLI endpoint. ` +
10
+ (supportedTool ? `Use ${supportedTool} through this Agent’s authenticated tool runtime, or ask a person to make the change. ` : 'Ask a person to make this change. ') +
11
+ 'No Task was changed and no files were uploaded.'), { code: 'SESSION_TASK_WRITE_UNSUPPORTED' });
12
+ }
@@ -43929,7 +43929,7 @@ var Tz = e.z.object({ t: e.z.literal("subagent-ref"), subagent: e.z.string().min
43929
43929
  var wz = e.z.object({ name: e.z.string().min(1), description: e.z.string().optional(), argHint: e.z.string().optional(), provider: e.z.string().optional(), execution: e.z.enum(["forward-text", "insert-text", "daemon-rpc"]).optional(), handler: e.z.string().optional(), insertText: e.z.string().optional(), category: e.z.enum(["command", "skill", "plugin", "model", "mode"]).optional(), scope: e.z.enum(["user", "project", "cli"]), bodyPreview: e.z.string().optional(), triggers: e.z.array(e.z.enum(["user", "model"])).optional(), pathHash: e.z.string().optional(), skillRoot: e.z.string().optional() });
43930
43930
  var Rz = e.z.object({ t: e.z.literal("slash-commands-available"), source: e.z.enum(["cli-init", "commands-dir", "codex-app-server", "acp", "skills"]), commands: e.z.array(wz) });
43931
43931
  var Cz = e.z.object({ t: e.z.literal("permission-ask"), requestId: e.z.string().min(1), toolUseId: e.z.string().min(1).optional(), toolName: e.z.string().min(1).optional(), inputPreview: e.z.string().optional(), defaultDecision: e.z.enum(["allow", "deny"]).optional(), timeoutMs: e.z.number().int().nonnegative().optional() });
43932
- var Pz = e.z.object({ t: e.z.literal("permission-mode-change"), mode: e.z.enum(["default", "acceptEdits", "bypassPermissions", "plan", "auto", "manual"]), source: e.z.enum(["cli", "user"]).optional(), toolUseId: e.z.string().min(1).optional() });
43932
+ var Pz = e.z.object({ t: e.z.literal("permission-mode-change"), mode: e.z.enum(["default", "acceptEdits", "bypassPermissions", "plan", "auto", "manual"]), source: e.z.enum(["cli", "user"]).optional(), toolUseId: e.z.string().min(1).optional(), receipt: e.z.object({ version: e.z.literal(1), authority: e.z.literal("daemon-policy") }).optional() });
43933
43933
  var Oz = e.z.object({ t: e.z.literal("permission-resolved"), requestId: e.z.string().min(1), localId: e.z.string().min(1).optional(), decision: e.z.enum(["allow", "deny"]), by: e.z.enum(["cockpit", "avatar", "timeout", "server", "cli", "cache"]), basis: e.z.enum(["person", "mode", "policy", "session-grant"]).optional(), mode: e.z.string().min(1).max(64).optional(), surface: e.z.enum(["web", "desktop", "cli", "mobile", "daemon", "unknown"]).optional() });
43934
43934
  var Nz = e.z.object({ t: e.z.literal("question-ask"), requestId: e.z.string().min(1), questionId: e.z.string().min(1).optional(), prompt: e.z.string().optional(), options: e.z.array(e.z.string()).optional(), allowFreeText: e.z.boolean().optional() });
43935
43935
  var Mz = e.z.object({ t: e.z.literal("question-resolved"), requestId: e.z.string().min(1), localId: e.z.string().min(1).optional(), answer: e.z.string(), by: e.z.enum(["cockpit", "avatar", "timeout"]) });
@@ -130039,5 +130039,5 @@
130039
130039
  ]
130040
130040
  }
130041
130041
  },
130042
- "version": "0.1.96"
130042
+ "version": "0.1.97"
130043
130043
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skrr-ai/cli",
3
- "version": "0.1.96",
3
+ "version": "0.1.97",
4
4
  "description": "Command-line interface for skrr",
5
5
  "license": "ISC",
6
6
  "author": "skrr",