@skrr-ai/cli 0.1.116 → 0.1.118

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 (94) hide show
  1. package/dist/base-command.d.ts +21 -0
  2. package/dist/base-command.js +95 -8
  3. package/dist/commands/actions/browse.js +2 -1
  4. package/dist/commands/actions/install.d.ts +2 -0
  5. package/dist/commands/actions/install.js +67 -2
  6. package/dist/commands/actions/list.js +2 -1
  7. package/dist/commands/actions/show.js +2 -1
  8. package/dist/commands/commitments/create.d.ts +21 -2
  9. package/dist/commands/commitments/create.js +74 -4
  10. package/dist/commands/convos/recovery.js +12 -4
  11. package/dist/commands/followups/plans/list.js +1 -1
  12. package/dist/commands/guide.d.ts +21 -0
  13. package/dist/commands/guide.js +46 -0
  14. package/dist/commands/instructions/guide.js +34 -52
  15. package/dist/commands/memory/maintain.js +1 -1
  16. package/dist/commands/skills/browse.js +2 -1
  17. package/dist/commands/skills/details.js +4 -2
  18. package/dist/commands/skills/featured.js +2 -1
  19. package/dist/commands/skills/install.d.ts +2 -0
  20. package/dist/commands/skills/install.js +62 -3
  21. package/dist/commands/skills/search.js +2 -1
  22. package/dist/commands/slack/send-as-agent.js +3 -3
  23. package/dist/commands/spaces/create.js +8 -2
  24. package/dist/commands/spaces/guide.d.ts +21 -0
  25. package/dist/commands/spaces/guide.js +40 -0
  26. package/dist/commands/spaces/index.d.ts +0 -1
  27. package/dist/commands/spaces/index.js +1 -6
  28. package/dist/commands/store/acquisitions/list.js +2 -1
  29. package/dist/commands/store/acquisitions/show.js +2 -1
  30. package/dist/commands/store/browse.js +4 -2
  31. package/dist/commands/store/install.d.ts +1 -0
  32. package/dist/commands/store/install.js +26 -2
  33. package/dist/commands/store/releases.js +4 -2
  34. package/dist/commands/store/show.js +3 -2
  35. package/dist/commands/store/tip.js +2 -2
  36. package/dist/commands/tasks/checkout.js +2 -3
  37. package/dist/commands/tasks/comments/add.js +3 -5
  38. package/dist/commands/tasks/complete.js +2 -3
  39. package/dist/commands/tasks/deliverable/add.d.ts +1 -1
  40. package/dist/commands/tasks/deliverable/add.js +4 -7
  41. package/dist/commands/tasks/expectations/assess.js +2 -2
  42. package/dist/commands/tasks/guide.d.ts +8 -0
  43. package/dist/commands/tasks/guide.js +23 -10
  44. package/dist/commands/tasks/report.js +9 -5
  45. package/dist/commands/tasks/resource/add.js +2 -3
  46. package/dist/commands/tasks/result/submit.js +2 -3
  47. package/dist/commands/tasks/resume/save.js +2 -3
  48. package/dist/commands/tasks/review.js +2 -3
  49. package/dist/commands/tasks/triage.js +2 -1
  50. package/dist/commands/tasks/updates/add.js +2 -3
  51. package/dist/generated/agent-guides.json +8312 -0
  52. package/dist/generated/managed-instruction-guides.json +1 -1
  53. package/dist/help.js +4 -3
  54. package/dist/hooks/command-not-found.js +1 -1
  55. package/dist/lib/action-listings.js +2 -1
  56. package/dist/lib/agent-guide-reference-gate.d.ts +28 -0
  57. package/dist/lib/agent-guide-reference-gate.js +186 -0
  58. package/dist/lib/agent-guides.d.ts +51 -0
  59. package/dist/lib/agent-guides.js +170 -0
  60. package/dist/lib/api-fetch.js +3 -1
  61. package/dist/lib/command-miss.d.ts +13 -2
  62. package/dist/lib/command-miss.js +134 -8
  63. package/dist/lib/guide-pointer.d.ts +15 -0
  64. package/dist/lib/guide-pointer.js +51 -0
  65. package/dist/lib/lookbackPlans.d.ts +3 -1
  66. package/dist/lib/lookbackPlans.js +9 -0
  67. package/dist/lib/node-adapter.js +4 -3
  68. package/dist/lib/outbox.d.ts +0 -14
  69. package/dist/lib/outbox.js +0 -21
  70. package/dist/lib/roles.d.ts +1 -1
  71. package/dist/lib/store-listing.d.ts +6 -1
  72. package/dist/lib/store-listing.js +6 -1
  73. package/dist/lib/task-admission.js +27 -5
  74. package/dist/lib/task-batch.d.ts +10 -7
  75. package/dist/lib/task-batch.js +5 -20
  76. package/dist/lib/task-operations.d.ts +8 -20
  77. package/dist/lib/task-operations.js +9 -94
  78. package/dist/lib/task-preflight.js +70 -33
  79. package/dist/lib/task-runtime-headers.d.ts +11 -0
  80. package/dist/lib/task-runtime-headers.js +73 -0
  81. package/dist/lib/task-write-attribution.js +1 -1
  82. package/dist/lib/usage-discovery.d.ts +4 -0
  83. package/dist/lib/usage-discovery.js +5 -0
  84. package/dist/lib/usd-micros.d.ts +27 -0
  85. package/dist/lib/usd-micros.js +67 -2
  86. package/dist/lib/voice-call-intent.js +3 -1
  87. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/harnessPermissionControls.d.ts +17 -0
  88. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/harnessPermissionControls.js +35 -2
  89. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/harnessPermissionControls.d.ts +17 -0
  90. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/harnessPermissionControls.js +44 -0
  91. package/dist/node_modules/@skrr-ai/auth-core/package.json +1 -1
  92. package/dist/node_modules/@skrr-ai/data-provider/index.js +11927 -11678
  93. package/oclif.manifest.json +251 -14
  94. package/package.json +4 -2
@@ -19,7 +19,11 @@
19
19
  * Keeping the text here rather than in either caller is the point: a fix to one
20
20
  * phrasing reaches both, and neither surface can quietly drift from the other.
21
21
  */
22
+ var __importDefault = (this && this.__importDefault) || function (mod) {
23
+ return (mod && mod.__esModule) ? mod : { "default": mod };
24
+ };
22
25
  Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.TASK_COMMAND_MISS_TARGETS = void 0;
23
27
  exports.editDistance = editDistance;
24
28
  exports.nearestCommands = nearestCommands;
25
29
  exports.helpSubjectId = helpSubjectId;
@@ -29,6 +33,7 @@ exports.lookupFromConfig = lookupFromConfig;
29
33
  exports.resolveVerbSynonym = resolveVerbSynonym;
30
34
  exports.typedCommandWords = typedCommandWords;
31
35
  exports.commandMissEnvelope = commandMissEnvelope;
36
+ const agent_guides_json_1 = __importDefault(require("../generated/agent-guides.json"));
32
37
  /** A uuid-ish token, which is what a stray space id looks like. */
33
38
  const LOOKS_LIKE_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
34
39
  const DAEMON_READINESS = [
@@ -67,6 +72,78 @@ function describeAnsweredElsewhere(config, resolved, extras) {
67
72
  suggestions: targets.map((t) => `${config.bin} ${t.usage}`),
68
73
  };
69
74
  }
75
+ /**
76
+ * Commands behind the Task spellings agents reach for before they have read
77
+ * the guide. Keep these ids here with their redirects so the manifest contract
78
+ * test can prove every printed target ships in this CLI build.
79
+ */
80
+ exports.TASK_COMMAND_MISS_TARGETS = Object.freeze({
81
+ topic: 'tasks',
82
+ search: 'tasks:list',
83
+ resultShow: 'tasks:result:show',
84
+ resultSubmit: 'tasks:result:submit',
85
+ });
86
+ function hasTopic(config, id) {
87
+ return config.topicNames.some((topic) => topic.toLowerCase() === id);
88
+ }
89
+ function describeTaskCommandRedirect(config, parts, typedWords) {
90
+ const lower = parts.map((part) => part.toLowerCase());
91
+ // `task` is an understandable singular spelling of the Tasks topic, but a
92
+ // generic singular-to-plural rule would steal real topics such as `agent`.
93
+ if (lower.length === 1 &&
94
+ lower[0] === 'task' &&
95
+ config.findCommand('task') === undefined &&
96
+ !hasTopic(config, 'task') &&
97
+ hasTopic(config, exports.TASK_COMMAND_MISS_TARGETS.topic)) {
98
+ const target = `${config.bin} ${exports.TASK_COMMAND_MISS_TARGETS.topic} --help`;
99
+ return {
100
+ message: `\`${config.bin} task\` is not a topic; use \`${config.bin} tasks\` for Task commands.`,
101
+ fix: `See \`${target}\`.`,
102
+ suggestions: [target],
103
+ };
104
+ }
105
+ if (lower[0] === 'tasks' &&
106
+ lower[1] === 'search' &&
107
+ hasTopic(config, 'tasks') &&
108
+ config.findCommand(exports.TASK_COMMAND_MISS_TARGETS.search) !== undefined) {
109
+ const query = typedWords.slice(2).join(' ').trim();
110
+ const target = `${config.bin} tasks list --q ${query ? JSON.stringify(query) : '<query>'}`;
111
+ return {
112
+ message: `\`${config.bin} tasks search\` is not a command; search Tasks with \`${config.bin} tasks list --q\`.`,
113
+ fix: `Run \`${target}\`.`,
114
+ suggestions: [target],
115
+ };
116
+ }
117
+ if (lower.length === 3 &&
118
+ lower[0] === 'tasks' &&
119
+ lower[1] === 'result' &&
120
+ hasTopic(config, 'tasks:result')) {
121
+ const task = typedWords[2] ?? parts[2];
122
+ const targets = [
123
+ config.findCommand(exports.TASK_COMMAND_MISS_TARGETS.resultShow) !== undefined
124
+ ? {
125
+ command: `${config.bin} tasks result show ${task}`,
126
+ answers: 'read the current Result',
127
+ }
128
+ : null,
129
+ config.findCommand(exports.TASK_COMMAND_MISS_TARGETS.resultSubmit) !== undefined
130
+ ? {
131
+ command: `${config.bin} tasks result submit ${task} --summary "<summary>"`,
132
+ answers: 'submit a Result',
133
+ }
134
+ : null,
135
+ ].filter((target) => target !== null);
136
+ if (targets.length === 0)
137
+ return null;
138
+ return {
139
+ message: `\`${config.bin} ${typedWords.slice(0, 3).join(' ')}\` is not a command; ` +
140
+ `\`${config.bin} tasks result\` is a topic.`,
141
+ fix: targets.map((target) => ` \`${target.command}\` — ${target.answers}`).join('\n'),
142
+ suggestions: targets.map((target) => target.command),
143
+ };
144
+ }
145
+ return null;
146
+ }
70
147
  const WHOAMI = {
71
148
  id: 'whoami',
72
149
  answers: 'who you are signed in as, which server, and where the credential came from',
@@ -147,12 +224,28 @@ const DEPENDENCY_WORKFLOW = [
147
224
  },
148
225
  ];
149
226
  const TASK_OPERATIONS = [
150
- { id: 'tasks:guide', usage: 'tasks guide', answers: 'find the commands for Task planning, dependencies, execution, and results' },
227
+ {
228
+ id: 'guide',
229
+ usage: 'guide tasks',
230
+ answers: 'read the tasks guide — planning, dependencies, execution, and results',
231
+ },
151
232
  ];
152
233
  const TASK_PREFLIGHT = [
153
- { id: 'tasks:preflight', usage: 'tasks preflight <task>', answers: 'read workflow blockers and execution prerequisites without starting work; shell execution remains unverified' },
154
- { id: 'tasks:actionability', usage: 'tasks actionability <task>', answers: 'read the canonical next actor and allowed workflow actions' },
155
- { id: 'tasks:start', usage: 'tasks start <task> --dry-run', answers: 'read only the server execution preview; never starts a Run' },
234
+ {
235
+ id: 'tasks:preflight',
236
+ usage: 'tasks preflight <task>',
237
+ answers: 'read workflow blockers and execution prerequisites without starting work; shell execution remains unverified',
238
+ },
239
+ {
240
+ id: 'tasks:actionability',
241
+ usage: 'tasks actionability <task>',
242
+ answers: 'read the canonical next actor and allowed workflow actions',
243
+ },
244
+ {
245
+ id: 'tasks:start',
246
+ usage: 'tasks start <task> --dry-run',
247
+ answers: 'read only the server execution preview; never starts a Run',
248
+ },
156
249
  ];
157
250
  const WORKFLOW_HINTS = {
158
251
  'spaces:projects': PROJECT_WORKFLOW,
@@ -167,6 +260,33 @@ const WORKFLOW_HINTS = {
167
260
  'tasks:doctor': TASK_PREFLIGHT,
168
261
  'tasks:readiness': TASK_PREFLIGHT,
169
262
  };
263
+ /**
264
+ * `<topic> guide`, the pre-`skrr guide` spelling of "read the guide for that
265
+ * domain". A topic with no `guide` command of its own (`agents guide`,
266
+ * `store guide`, or an alias like `task-management guide`) is the reader
267
+ * asking for the front door — the ids come from the bundled registry, so a
268
+ * new guide becomes reachable here the release it ships, never by a second
269
+ * list kept in this file.
270
+ */
271
+ const BUNDLED_GUIDE_NAMES = agent_guides_json_1.default.index.guides;
272
+ function describeGuideRedirect(config, parts) {
273
+ if (parts.length !== 2 || parts[1].toLowerCase() !== 'guide')
274
+ return null;
275
+ // `skrr guide` itself must exist, and a real `<topic> guide` command answers for itself.
276
+ if (config.findCommand('guide') === undefined)
277
+ return null;
278
+ const typed = parts[0].toLowerCase();
279
+ const guide = BUNDLED_GUIDE_NAMES.find((entry) => entry.id === typed || (entry.aliases ?? []).includes(typed));
280
+ if (!guide)
281
+ return null;
282
+ const read = `${config.bin} guide ${guide.id}`;
283
+ return {
284
+ message: `\`${config.bin} ${parts.join(' ')}\` is not a command; ` +
285
+ `guides are read through \`${config.bin} guide\`.`,
286
+ fix: ` \`${read}\` — the ${guide.id} guide`,
287
+ suggestions: [read],
288
+ };
289
+ }
170
290
  function describeIntentMiss(config, parts) {
171
291
  // An installed command (even one whose module cannot load) always owns its name.
172
292
  const known = [...config.commandIDs, ...config.topicNames];
@@ -285,8 +405,8 @@ function helpSubjectId(tokens, config) {
285
405
  return taken.join(':');
286
406
  }
287
407
  /** The text form: exactly what a miss has always printed. */
288
- function describeCommandMiss(config, id) {
289
- const miss = analyzeCommandMiss(config, id);
408
+ function describeCommandMiss(config, id, typedWords) {
409
+ const miss = analyzeCommandMiss(config, id, typedWords);
290
410
  return miss.fix ? `${miss.message}\n${miss.fix}` : miss.message;
291
411
  }
292
412
  /**
@@ -301,13 +421,19 @@ function describeCommandMiss(config, id) {
301
421
  * suggest the nearest siblings under the same topic before falling back to
302
422
  * the whole command list.
303
423
  */
304
- function analyzeCommandMiss(config, id) {
424
+ function analyzeCommandMiss(config, id, typedWords) {
305
425
  const parts = String(id ?? '')
306
426
  .split(':')
307
427
  .filter(Boolean);
428
+ const taskRedirect = describeTaskCommandRedirect(config, parts, typedWords ?? parts);
429
+ if (taskRedirect)
430
+ return taskRedirect;
308
431
  const intent = describeIntentMiss(config, parts);
309
432
  if (intent)
310
433
  return intent;
434
+ const guideRedirect = describeGuideRedirect(config, parts);
435
+ if (guideRedirect)
436
+ return guideRedirect;
311
437
  for (let take = parts.length - 1; take >= 1; take -= 1) {
312
438
  const found = config.findCommand(parts.slice(0, take).join(':'));
313
439
  if (!found)
@@ -514,7 +640,7 @@ function typedCommandWords(id, rawArgv) {
514
640
  * the report found it.
515
641
  */
516
642
  function commandMissEnvelope(config, id, words) {
517
- const miss = analyzeCommandMiss(config, id);
643
+ const miss = analyzeCommandMiss(config, id, words);
518
644
  return {
519
645
  ok: false,
520
646
  error: {
@@ -0,0 +1,15 @@
1
+ /** The one section a refusal code points at, plus the command that reads it. */
2
+ export interface AgentGuidePointer {
3
+ id: string;
4
+ section: string;
5
+ /** `skrr guide <id> <section>` — what the `Guide:` line and `error.guide.command` carry. */
6
+ command: string;
7
+ }
8
+ /**
9
+ * The pointer for a refusal code: `{id, section, command}`, or `null` when no
10
+ * section explains it, the error carries no code, or the bundled vocabulary is
11
+ * unavailable. Never throws.
12
+ */
13
+ export declare function agentGuidePointerFor(code: string | null | undefined): AgentGuidePointer | null;
14
+ /** The `Guide:` line a refusal's human text ends with. */
15
+ export declare function agentGuidePointerLine(pointer: AgentGuidePointer): string;
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.agentGuidePointerFor = agentGuidePointerFor;
7
+ exports.agentGuidePointerLine = agentGuidePointerLine;
8
+ /**
9
+ * Agent guides — the pointer a refusal carries (OSK-17977).
10
+ *
11
+ * The cheapest moment to teach is the moment something goes wrong. A section's
12
+ * manifest entry declares the refusal codes it `explains`; the server registry
13
+ * enforces that each code has exactly one section. The CLI resolves the same
14
+ * table OFFLINE, from its bundled copy (`generated/agent-guides.json`), so a
15
+ * refusal that never reached the server — and one on a CLI that cannot reach
16
+ * it at all — still names the section that explains it.
17
+ *
18
+ * The lookup is deliberately fragile-proof: error rendering runs this on every
19
+ * failure, including inside test harnesses that swap `@skrr-ai/data-provider`
20
+ * for a partial mock. A pointer is advisory — it must never be the reason a
21
+ * refusal fails to render.
22
+ */
23
+ const data_provider_1 = require("@skrr-ai/data-provider");
24
+ const agent_guides_json_1 = __importDefault(require("../generated/agent-guides.json"));
25
+ const catalog = agent_guides_json_1.default.index?.guides ?? [];
26
+ /**
27
+ * The pointer for a refusal code: `{id, section, command}`, or `null` when no
28
+ * section explains it, the error carries no code, or the bundled vocabulary is
29
+ * unavailable. Never throws.
30
+ */
31
+ function agentGuidePointerFor(code) {
32
+ if (typeof code !== 'string' || !code)
33
+ return null;
34
+ try {
35
+ const address = (0, data_provider_1.findAgentGuideSectionExplaining)(catalog, code);
36
+ if (!address?.section)
37
+ return null;
38
+ return {
39
+ id: address.id,
40
+ section: address.section,
41
+ command: (0, data_provider_1.agentGuideReadCommand)(address, 'cli'),
42
+ };
43
+ }
44
+ catch {
45
+ return null;
46
+ }
47
+ }
48
+ /** The `Guide:` line a refusal's human text ends with. */
49
+ function agentGuidePointerLine(pointer) {
50
+ return `Guide: ${pointer.command}`;
51
+ }
@@ -1,4 +1,4 @@
1
- import { lookbackPlansApi, isLookbackMutationOutcomeUnknown, type CreateLookbackPlanRequest, type LookbackPlan, type LookbackPlanInput } from '@skrr-ai/data-provider';
1
+ import { lookbackPlansApi, isLookbackMutationOutcomeUnknown, type CreateLookbackPlanRequest, type LookbackPlan, type LookbackPlanInput, type LookbackPlansScope } from '@skrr-ai/data-provider';
2
2
  export { lookbackPlansApi, isLookbackMutationOutcomeUnknown };
3
3
  export declare function lookbackInputFailure(error: unknown): {
4
4
  code: "LOOKBACK_INVALID_FIELD" | "LOOKBACK_INVALID_TIME" | "LOOKBACK_INVALID_TIMEZONE" | "LOOKBACK_AMBIGUOUS_TIME" | "LOOKBACK_NONEXISTENT_TIME" | "LOOKBACK_LIMIT_EXCEEDED";
@@ -46,3 +46,5 @@ export declare function lookbackArmingLine(result: {
46
46
  reused?: boolean;
47
47
  armed: boolean;
48
48
  }): string;
49
+ /** An empty list is only "none" for the workspace it looked in: name it (OSK-17930). */
50
+ export declare function lookbackPlansEmptyMessage(scope?: LookbackPlansScope): string;
@@ -7,6 +7,7 @@ exports.lookbackCreateBody = lookbackCreateBody;
7
7
  exports.lookbackInput = lookbackInput;
8
8
  exports.lookbackPlanSummary = lookbackPlanSummary;
9
9
  exports.lookbackArmingLine = lookbackArmingLine;
10
+ exports.lookbackPlansEmptyMessage = lookbackPlansEmptyMessage;
10
11
  const core_1 = require("@oclif/core");
11
12
  const data_provider_1 = require("@skrr-ai/data-provider");
12
13
  Object.defineProperty(exports, "lookbackPlansApi", { enumerable: true, get: function () { return data_provider_1.lookbackPlansApi; } });
@@ -119,3 +120,11 @@ function lookbackArmingLine(result) {
119
120
  const status = result.armed ? 'armed' : 'saved; scheduling is pending';
120
121
  return `Lookback plan ${result.plan.id} ${status}${result.reused ? ' (existing plan)' : ''}.`;
121
122
  }
123
+ /** An empty list is only "none" for the workspace it looked in: name it (OSK-17930). */
124
+ function lookbackPlansEmptyMessage(scope) {
125
+ if (!scope)
126
+ return 'No lookback plans.';
127
+ if (!scope.workspaceId)
128
+ return "No lookback plans in no workspace (use --workspace to read a workspace's plans).";
129
+ return `No lookback plans in workspace ${scope.workspaceId}${scope.source === 'default' ? ' (your default; use --workspace to read another)' : ''}.`;
130
+ }
@@ -16,6 +16,7 @@ const dpop_auth_1 = require("./dpop-auth");
16
16
  const refresh_1 = require("./refresh");
17
17
  const delegated_cli_1 = require("./delegated-cli");
18
18
  const workspace_header_1 = require("./workspace-header");
19
+ const task_runtime_headers_1 = require("./task-runtime-headers");
19
20
  const non_json_response_1 = require("./non-json-response");
20
21
  require("./auth-core-init");
21
22
  const rate_limit_retry_1 = require("./rate-limit-retry");
@@ -150,7 +151,8 @@ function createNodeAdapter(opts = {}) {
150
151
  /** The last 429's `Retry-After`, read off the response of the most recent attempt. */
151
152
  let lastRetryAfterMs;
152
153
  async function rawRequest(method, url, body, options, isFormData) {
153
- const headers = buildHeaders(options?.headers);
154
+ const finalUrl = appendQuery(resolveUrl(url), options?.params);
155
+ const headers = (0, task_runtime_headers_1.withTaskRuntimeHeaders)(buildHeaders(options?.headers), finalUrl, method);
154
156
  let finalBody;
155
157
  if (body !== undefined && body !== null && method !== 'GET') {
156
158
  if (isFormData) {
@@ -162,12 +164,11 @@ function createNodeAdapter(opts = {}) {
162
164
  finalBody = JSON.stringify(body);
163
165
  }
164
166
  }
165
- const finalUrl = appendQuery(resolveUrl(url), options?.params);
166
167
  // OSK-12020 — a DPoP-bound hand-off token needs a fresh proof per request
167
168
  // (and per retry: proofs are single-use). No-op for every other token.
168
169
  Object.assign(headers, await (0, dpop_auth_1.dpopHeadersFor)(currentCredential().token, method, finalUrl));
169
170
  // E6: bounded retry on 429 for a GET or an idempotency-keyed write.
170
- const { response, retryAfterMs } = await (0, rate_limit_retry_1.fetchWithRateLimitRetry)(() => fetch(finalUrl, { method, headers, body: finalBody }), { method, headers });
171
+ const { response, retryAfterMs } = await (0, rate_limit_retry_1.fetchWithRateLimitRetry)(() => fetch(finalUrl, { method, headers, body: finalBody, ...(0, task_runtime_headers_1.taskRuntimeRedirectOptions)(headers) }), { method, headers });
171
172
  lastRetryAfterMs = retryAfterMs;
172
173
  return response;
173
174
  }
@@ -36,20 +36,6 @@ export interface OutboxEntry {
36
36
  * silently dropped.
37
37
  */
38
38
  export declare const MAX_REPLAY_ATTEMPTS = 10;
39
- /**
40
- * Derive a stable idempotency key from the write's own content.
41
- *
42
- * Content-derived rather than random, and that is the whole design: a random
43
- * key would make every replay of the SAME intent look like a new write, which
44
- * is exactly what the server-side dedupe exists to prevent. Hashing
45
- * (kind, scope, payload) means replaying the same comment collapses onto the
46
- * original, while a genuinely different comment on the same task hashes
47
- * differently and still posts.
48
- *
49
- * Truncated to 32 hex chars — comfortably collision-resistant for a per-user,
50
- * 24-hour dedupe window, and short enough to read in `skrr outbox list`.
51
- */
52
- export declare function idempotencyKeyFor(kind: string, scope: string, payload: string): string;
53
39
  export declare function isOutageFailure(err: unknown): boolean;
54
40
  export declare function isRetryableWrite(err: unknown): boolean;
55
41
  export declare function enqueueWrite(input: Omit<OutboxEntry, 'id' | 'queuedAt' | 'attempts'>, profile?: string): OutboxEntry;
@@ -34,7 +34,6 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.MAX_REPLAY_ATTEMPTS = void 0;
37
- exports.idempotencyKeyFor = idempotencyKeyFor;
38
37
  exports.isOutageFailure = isOutageFailure;
39
38
  exports.isRetryableWrite = isRetryableWrite;
40
39
  exports.enqueueWrite = enqueueWrite;
@@ -93,26 +92,6 @@ const crypto = __importStar(require("node:crypto"));
93
92
  * silently dropped.
94
93
  */
95
94
  exports.MAX_REPLAY_ATTEMPTS = 10;
96
- /**
97
- * Derive a stable idempotency key from the write's own content.
98
- *
99
- * Content-derived rather than random, and that is the whole design: a random
100
- * key would make every replay of the SAME intent look like a new write, which
101
- * is exactly what the server-side dedupe exists to prevent. Hashing
102
- * (kind, scope, payload) means replaying the same comment collapses onto the
103
- * original, while a genuinely different comment on the same task hashes
104
- * differently and still posts.
105
- *
106
- * Truncated to 32 hex chars — comfortably collision-resistant for a per-user,
107
- * 24-hour dedupe window, and short enough to read in `skrr outbox list`.
108
- */
109
- function idempotencyKeyFor(kind, scope, payload) {
110
- const digest = crypto
111
- .createHash('sha256')
112
- .update(`${kind}\u0000${scope}\u0000${payload}`)
113
- .digest('hex');
114
- return `${kind}_${digest.slice(0, 32)}`;
115
- }
116
95
  /**
117
96
  * Per-profile queue directory.
118
97
  *
@@ -53,7 +53,7 @@ export declare const roleApi: {
53
53
  config?: Record<string, unknown> | undefined;
54
54
  }[];
55
55
  sources: {
56
- source: "space" | "tasks" | "email" | "goal" | "files" | "local" | "daemon" | "browser" | "conversations" | "slack" | "memory" | "github" | "messages" | "calendar" | "docs" | "metrics" | "agent_run" | "agent_initiatives" | "documents" | "product_metrics" | "business_metrics" | "state_monitor" | "monitor";
56
+ source: "space" | "tasks" | "memory" | "email" | "goal" | "files" | "local" | "daemon" | "browser" | "conversations" | "slack" | "github" | "messages" | "calendar" | "docs" | "metrics" | "agent_run" | "agent_initiatives" | "documents" | "product_metrics" | "business_metrics" | "state_monitor" | "monitor";
57
57
  enabled: boolean;
58
58
  config?: Record<string, unknown> | undefined;
59
59
  }[];
@@ -15,7 +15,12 @@ import { type ReleaseCapabilityManifestView, type StoreAgentInstallFunnelSummary
15
15
  * "withheld" with the floor it missed, never as 0 — a zero is a claim that
16
16
  * nobody did the thing, and the server did not make that claim.
17
17
  */
18
- /** A listing's price in token credits (1,000,000 = $1), as the Store states it. */
18
+ /**
19
+ * A listing's price as the Store prints it — `$4.00`, or `Free`. The wire
20
+ * value is integer micro-USD (1,000,000 = $1); every store, skills and
21
+ * actions command prints through this one formatter so no surface can quote
22
+ * the same price in a different unit (OSK-18256).
23
+ */
19
24
  export declare function formatListingPrice(price: number | null | undefined): string;
20
25
  /** `4.6 ★ (12 ratings)`, or `No ratings yet` — never `0.0` for an unrated listing. */
21
26
  export declare function formatListingRating(rating: number | null | undefined, count: number | null | undefined): string;
@@ -29,7 +29,12 @@ const data_provider_1 = require("@skrr-ai/data-provider");
29
29
  * "withheld" with the floor it missed, never as 0 — a zero is a claim that
30
30
  * nobody did the thing, and the server did not make that claim.
31
31
  */
32
- /** A listing's price in token credits (1,000,000 = $1), as the Store states it. */
32
+ /**
33
+ * A listing's price as the Store prints it — `$4.00`, or `Free`. The wire
34
+ * value is integer micro-USD (1,000,000 = $1); every store, skills and
35
+ * actions command prints through this one formatter so no surface can quote
36
+ * the same price in a different unit (OSK-18256).
37
+ */
33
38
  function formatListingPrice(price) {
34
39
  return (0, data_provider_1.formatStoreAcquisitionPrice)(price);
35
40
  }
@@ -6,14 +6,34 @@ function admissionLines(value) {
6
6
  if (!value)
7
7
  return ['Execution admission: not reported by this server.'];
8
8
  const lines = [`Approval prerequisites: ${value.state}.`];
9
+ const observation = value.runtimePolicyObservation;
9
10
  if (value.permission) {
10
- lines.push(`Permission policy: ${value.permission.mode || 'inherited/unknown'} (${value.permission.source || 'source unavailable'}).`);
11
+ lines.push(!value.permission.mode &&
12
+ value.permission.source === 'daemon' &&
13
+ observation?.source !== 'not_applicable'
14
+ ? 'Permission override: inherit daemon (no Agent or Harness override).'
15
+ : `Permission policy: ${value.permission.mode || 'inherited/unknown'} (${value.permission.source || 'source unavailable'}).`);
16
+ }
17
+ const configured = observation?.configuredDefault;
18
+ if (observation ? observation.source !== 'not_applicable' : value.permission?.source === 'daemon') {
19
+ if (configured && observation?.source === 'daemon_registration') {
20
+ lines.push(`Daemon last reported configured default: ${configured.permissionMode} (${configured.provider}/${configured.engine}; process registration at ${observation.reportedAt}). This is not the future effective policy.`);
21
+ }
22
+ else {
23
+ lines.push('Daemon configured default: unreported. Check status on the selected machine and daemon profile; reconnect or update that daemon to refresh its report.');
24
+ }
25
+ if (value.taskCommandApproval?.support === 'reported') {
26
+ lines.push(`Exact Task-command approval protocol: reported (${value.taskCommandApproval.provider}/${value.taskCommandApproval.engine}). Run, owner and command readiness remain unverified; no command is authorized.`);
27
+ }
28
+ else {
29
+ lines.push('Exact Task-command approval protocol: unreported for the selected engine.');
30
+ }
11
31
  }
12
32
  if (value.workingDirectory) {
13
33
  lines.push(`Working directory: ${value.workingDirectory.path || 'daemon configured default'} (${value.workingDirectory.source}; existence and access are checked on the machine at start).`);
14
34
  }
15
35
  if (value.unattended && !value.approverAvailable) {
16
- lines.push('This run is unattended. Tools that need approval cannot obtain it during the run.');
36
+ lines.push('This run is unattended; generic provider interactive approval is unavailable. Exact Task-command requests have a separate approval path when supported.');
17
37
  }
18
38
  for (const constraint of value.constraints || []) {
19
39
  if (constraint.message)
@@ -29,7 +49,9 @@ function isAdmissionPreview(value) {
29
49
  return false;
30
50
  const row = value;
31
51
  const admission = row.executionAdmission;
32
- return row.dryRun === true && typeof row.taskId === 'string' && Boolean(admission &&
33
- ['ready', 'blocked', 'unknown'].includes(String(admission.state)) &&
34
- admission.scope === 'approval_prerequisites');
52
+ return (row.dryRun === true &&
53
+ typeof row.taskId === 'string' &&
54
+ Boolean(admission &&
55
+ ['ready', 'blocked', 'unknown'].includes(String(admission.state)) &&
56
+ admission.scope === 'approval_prerequisites'));
35
57
  }
@@ -1,3 +1,13 @@
1
+ /**
2
+ * Domain lib for `POST|PATCH /api/tasks/batch` (docs/architecture/task-batch-api-2026-10-08.md).
3
+ *
4
+ * `tasks create-tree` and `tasks bulk-update` reach the batch routes through
5
+ * `dataService.createTasksBatch|updateTasksBatch` and nothing else. This file owns
6
+ * the parts a command should not restate: chunking at the server cap, a
7
+ * tempId translation, and the "this server has no batch route" signal that
8
+ * sends a command back to its serial path. The shared transport owns each
9
+ * request's fresh idempotency key.
10
+ */
1
11
  import { type TaskBatchItemResult, type TaskBatchPatchItem } from '@skrr-ai/data-provider';
2
12
  export type ApiFailure = {
3
13
  message: string;
@@ -16,13 +26,6 @@ export declare function describeApiFailure(err: unknown): ApiFailure;
16
26
  */
17
27
  export declare function isBatchRouteMissing(err: unknown): boolean;
18
28
  export declare function chunkItems<T>(items: T[], size?: number): T[][];
19
- /**
20
- * Same chunk, same key: a retry of an interrupted command replays the stored
21
- * result instead of creating twice. The key is a fingerprint of the items, so a
22
- * DIFFERENT chunk can never collide with it (the server answers 422 on a key
23
- * reused with a different body).
24
- */
25
- export declare function batchIdempotencyKey(kind: 'create' | 'patch', items: unknown[]): string;
26
29
  export type BatchCreateNode = {
27
30
  key: string;
28
31
  parentKey?: string;
@@ -4,7 +4,6 @@ exports.batchRetryPolicy = void 0;
4
4
  exports.describeApiFailure = describeApiFailure;
5
5
  exports.isBatchRouteMissing = isBatchRouteMissing;
6
6
  exports.chunkItems = chunkItems;
7
- exports.batchIdempotencyKey = batchIdempotencyKey;
8
7
  exports.reconcileSubmittedNodes = reconcileSubmittedNodes;
9
8
  exports.createNodesViaBatch = createNodesViaBatch;
10
9
  exports.patchTasksViaBatch = patchTasksViaBatch;
@@ -14,10 +13,10 @@ exports.patchTasksViaBatch = patchTasksViaBatch;
14
13
  * `tasks create-tree` and `tasks bulk-update` reach the batch routes through
15
14
  * `dataService.createTasksBatch|updateTasksBatch` and nothing else. This file owns
16
15
  * the parts a command should not restate: chunking at the server cap, a
17
- * deterministic idempotency key per chunk, tempId translation, and the "this
18
- * server has no batch route" signal that sends a command back to its serial path.
16
+ * tempId translation, and the "this server has no batch route" signal that
17
+ * sends a command back to its serial path. The shared transport owns each
18
+ * request's fresh idempotency key.
19
19
  */
20
- const node_crypto_1 = require("node:crypto");
21
20
  const data_provider_1 = require("@skrr-ai/data-provider");
22
21
  const tasks_1 = require("./tasks");
23
22
  /** Normalize a thrown API error (or a per-item batch error) into one shape. */
@@ -60,16 +59,6 @@ function chunkItems(items, size = data_provider_1.TASK_BATCH_MAX_ITEMS ?? 100) {
60
59
  out.push(items.slice(i, i + size));
61
60
  return out;
62
61
  }
63
- /**
64
- * Same chunk, same key: a retry of an interrupted command replays the stored
65
- * result instead of creating twice. The key is a fingerprint of the items, so a
66
- * DIFFERENT chunk can never collide with it (the server answers 422 on a key
67
- * reused with a different body).
68
- */
69
- function batchIdempotencyKey(kind, items) {
70
- const digest = (0, node_crypto_1.createHash)('sha256').update((0, data_provider_1.stableJson)(items)).digest('hex').slice(0, 40);
71
- return `cli-${kind}-batch:${digest}`;
72
- }
73
62
  /** A lost response is not a lost write. Only READ here; never retry a write. */
74
63
  async function reconcileSubmittedNodes(nodes, mapping, error, deadlineAt = Date.now() + 5000) {
75
64
  const rows = [];
@@ -316,9 +305,7 @@ async function createNodesViaBatch(nodes, mapping, options = {}) {
316
305
  let response;
317
306
  try {
318
307
  options.onSubmit?.(sent);
319
- response = await data_provider_1.dataService.createTasksBatch(items, {
320
- idempotencyKey: batchIdempotencyKey('create', items),
321
- });
308
+ response = await data_provider_1.dataService.createTasksBatch(items);
322
309
  }
323
310
  catch (err) {
324
311
  if (round === 1 && isBatchRouteMissing(err))
@@ -510,9 +497,7 @@ async function patchTasksViaBatch(updates) {
510
497
  continue;
511
498
  }
512
499
  try {
513
- const response = await data_provider_1.dataService.updateTasksBatch(chunk, {
514
- idempotencyKey: batchIdempotencyKey('patch', chunk),
515
- });
500
+ const response = await data_provider_1.dataService.updateTasksBatch(chunk);
516
501
  for (const [i, result] of response.results.entries()) {
517
502
  results.push({ ...result, index: offset + i });
518
503
  }
@@ -1,25 +1,13 @@
1
- /** Documentation and display paths only. The server still owns allowedActions. */
1
+ /**
2
+ * Documentation and display paths only. The server still owns allowedActions.
3
+ *
4
+ * `skrr tasks guide`'s command map retired into the tasks guide's `commands`
5
+ * section (`skrr guide tasks commands`), the one copy every surface reads;
6
+ * what remains here is the action→command remedies preflight and
7
+ * actionability render into server-declared allowedActions.
8
+ */
2
9
  export declare function taskActionCommand(action: string, taskId: string, targetId: string | undefined, bin?: string, context?: {
3
10
  allowedActions?: ReadonlyArray<{
4
11
  action: string;
5
12
  }>;
6
13
  }): string | undefined;
7
- export declare function taskOperationsGuide(bin?: string): {
8
- title: string;
9
- sections: ({
10
- title: string;
11
- commands: {
12
- command: string;
13
- purpose: string;
14
- }[];
15
- note?: undefined;
16
- } | {
17
- title: string;
18
- note: string;
19
- commands: {
20
- command: string;
21
- purpose: string;
22
- }[];
23
- })[];
24
- };
25
- export declare function taskOperationsGuideLines(bin?: string): string[];