@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.
- package/dist/base-command.d.ts +21 -0
- package/dist/base-command.js +95 -8
- package/dist/commands/actions/browse.js +2 -1
- package/dist/commands/actions/install.d.ts +2 -0
- package/dist/commands/actions/install.js +67 -2
- package/dist/commands/actions/list.js +2 -1
- package/dist/commands/actions/show.js +2 -1
- package/dist/commands/commitments/create.d.ts +21 -2
- package/dist/commands/commitments/create.js +74 -4
- package/dist/commands/convos/recovery.js +12 -4
- package/dist/commands/followups/plans/list.js +1 -1
- package/dist/commands/guide.d.ts +21 -0
- package/dist/commands/guide.js +46 -0
- package/dist/commands/instructions/guide.js +34 -52
- package/dist/commands/memory/maintain.js +1 -1
- package/dist/commands/skills/browse.js +2 -1
- package/dist/commands/skills/details.js +4 -2
- package/dist/commands/skills/featured.js +2 -1
- package/dist/commands/skills/install.d.ts +2 -0
- package/dist/commands/skills/install.js +62 -3
- package/dist/commands/skills/search.js +2 -1
- package/dist/commands/slack/send-as-agent.js +3 -3
- package/dist/commands/spaces/create.js +8 -2
- package/dist/commands/spaces/guide.d.ts +21 -0
- package/dist/commands/spaces/guide.js +40 -0
- package/dist/commands/spaces/index.d.ts +0 -1
- package/dist/commands/spaces/index.js +1 -6
- package/dist/commands/store/acquisitions/list.js +2 -1
- package/dist/commands/store/acquisitions/show.js +2 -1
- package/dist/commands/store/browse.js +4 -2
- package/dist/commands/store/install.d.ts +1 -0
- package/dist/commands/store/install.js +26 -2
- package/dist/commands/store/releases.js +4 -2
- package/dist/commands/store/show.js +3 -2
- package/dist/commands/store/tip.js +2 -2
- package/dist/commands/tasks/checkout.js +2 -3
- package/dist/commands/tasks/comments/add.js +3 -5
- package/dist/commands/tasks/complete.js +2 -3
- package/dist/commands/tasks/deliverable/add.d.ts +1 -1
- package/dist/commands/tasks/deliverable/add.js +4 -7
- package/dist/commands/tasks/expectations/assess.js +2 -2
- package/dist/commands/tasks/guide.d.ts +8 -0
- package/dist/commands/tasks/guide.js +23 -10
- package/dist/commands/tasks/report.js +9 -5
- package/dist/commands/tasks/resource/add.js +2 -3
- package/dist/commands/tasks/result/submit.js +2 -3
- package/dist/commands/tasks/resume/save.js +2 -3
- package/dist/commands/tasks/review.js +2 -3
- package/dist/commands/tasks/triage.js +2 -1
- package/dist/commands/tasks/updates/add.js +2 -3
- package/dist/generated/agent-guides.json +8312 -0
- package/dist/generated/managed-instruction-guides.json +1 -1
- package/dist/help.js +4 -3
- package/dist/hooks/command-not-found.js +1 -1
- package/dist/lib/action-listings.js +2 -1
- package/dist/lib/agent-guide-reference-gate.d.ts +28 -0
- package/dist/lib/agent-guide-reference-gate.js +186 -0
- package/dist/lib/agent-guides.d.ts +51 -0
- package/dist/lib/agent-guides.js +170 -0
- package/dist/lib/api-fetch.js +3 -1
- package/dist/lib/command-miss.d.ts +13 -2
- package/dist/lib/command-miss.js +134 -8
- package/dist/lib/guide-pointer.d.ts +15 -0
- package/dist/lib/guide-pointer.js +51 -0
- package/dist/lib/lookbackPlans.d.ts +3 -1
- package/dist/lib/lookbackPlans.js +9 -0
- package/dist/lib/node-adapter.js +4 -3
- package/dist/lib/outbox.d.ts +0 -14
- package/dist/lib/outbox.js +0 -21
- package/dist/lib/roles.d.ts +1 -1
- package/dist/lib/store-listing.d.ts +6 -1
- package/dist/lib/store-listing.js +6 -1
- package/dist/lib/task-admission.js +27 -5
- package/dist/lib/task-batch.d.ts +10 -7
- package/dist/lib/task-batch.js +5 -20
- package/dist/lib/task-operations.d.ts +8 -20
- package/dist/lib/task-operations.js +9 -94
- package/dist/lib/task-preflight.js +70 -33
- package/dist/lib/task-runtime-headers.d.ts +11 -0
- package/dist/lib/task-runtime-headers.js +73 -0
- package/dist/lib/task-write-attribution.js +1 -1
- package/dist/lib/usage-discovery.d.ts +4 -0
- package/dist/lib/usage-discovery.js +5 -0
- package/dist/lib/usd-micros.d.ts +27 -0
- package/dist/lib/usd-micros.js +67 -2
- package/dist/lib/voice-call-intent.js +3 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/harnessPermissionControls.d.ts +17 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/harnessPermissionControls.js +35 -2
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/harnessPermissionControls.d.ts +17 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/harnessPermissionControls.js +44 -0
- package/dist/node_modules/@skrr-ai/auth-core/package.json +1 -1
- package/dist/node_modules/@skrr-ai/data-provider/index.js +11927 -11678
- package/oclif.manifest.json +251 -14
- package/package.json +4 -2
package/dist/lib/command-miss.js
CHANGED
|
@@ -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
|
-
{
|
|
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
|
-
{
|
|
154
|
-
|
|
155
|
-
|
|
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
|
+
}
|
package/dist/lib/node-adapter.js
CHANGED
|
@@ -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
|
|
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
|
}
|
package/dist/lib/outbox.d.ts
CHANGED
|
@@ -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;
|
package/dist/lib/outbox.js
CHANGED
|
@@ -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
|
*
|
package/dist/lib/roles.d.ts
CHANGED
|
@@ -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" | "
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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(
|
|
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
|
|
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 &&
|
|
33
|
-
|
|
34
|
-
admission
|
|
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
|
}
|
package/dist/lib/task-batch.d.ts
CHANGED
|
@@ -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;
|
package/dist/lib/task-batch.js
CHANGED
|
@@ -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
|
-
*
|
|
18
|
-
*
|
|
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
|
-
/**
|
|
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[];
|