@skrr-ai/cli 0.1.116 → 0.1.117
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/install.d.ts +1 -0
- package/dist/commands/actions/install.js +46 -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/install.d.ts +1 -0
- package/dist/commands/skills/install.js +41 -2
- 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/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 +8290 -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/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/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/outbox.d.ts +0 -14
- package/dist/lib/outbox.js +0 -21
- package/dist/lib/roles.d.ts +1 -1
- 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/usage-discovery.d.ts +4 -0
- package/dist/lib/usage-discovery.js +5 -0
- package/dist/lib/voice-call-intent.js +3 -1
- package/dist/node_modules/@skrr-ai/data-provider/index.js +6593 -6383
- package/oclif.manifest.json +228 -13
- 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/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
|
}[];
|
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[];
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.taskActionCommand = taskActionCommand;
|
|
4
|
-
exports.taskOperationsGuide = taskOperationsGuide;
|
|
5
|
-
exports.taskOperationsGuideLines = taskOperationsGuideLines;
|
|
6
4
|
const shell_quote_1 = require("./shell-quote");
|
|
7
|
-
/**
|
|
5
|
+
/**
|
|
6
|
+
* Documentation and display paths only. The server still owns allowedActions.
|
|
7
|
+
*
|
|
8
|
+
* `skrr tasks guide`'s command map retired into the tasks guide's `commands`
|
|
9
|
+
* section (`skrr guide tasks commands`), the one copy every surface reads;
|
|
10
|
+
* what remains here is the action→command remedies preflight and
|
|
11
|
+
* actionability render into server-declared allowedActions.
|
|
12
|
+
*/
|
|
8
13
|
function taskActionCommand(action, taskId, targetId, bin = 'skrr', context = {}) {
|
|
9
14
|
const task = (0, shell_quote_1.shellQuoteArg)(taskId);
|
|
10
15
|
const target = targetId ? (0, shell_quote_1.shellQuoteArg)(targetId) : '<id>';
|
|
@@ -13,7 +18,7 @@ function taskActionCommand(action, taskId, targetId, bin = 'skrr', context = {})
|
|
|
13
18
|
assign_task: `tasks assign ${task} --help`,
|
|
14
19
|
// Managed work already has a current Result. Its server-declared
|
|
15
20
|
// continuation surface distinguishes closing from submitting a new Result.
|
|
16
|
-
complete_task: context.allowedActions?.some(item => item.action === 'continue_task')
|
|
21
|
+
complete_task: context.allowedActions?.some((item) => item.action === 'continue_task')
|
|
17
22
|
? `tasks move ${task} --status done`
|
|
18
23
|
: `tasks complete ${task}`,
|
|
19
24
|
continue_task: `tasks continue ${task} --text "<instruction>" --command-key <key>`,
|
|
@@ -31,93 +36,3 @@ function taskActionCommand(action, taskId, targetId, bin = 'skrr', context = {})
|
|
|
31
36
|
};
|
|
32
37
|
return commands[action] ? `${bin} ${commands[action]}` : undefined;
|
|
33
38
|
}
|
|
34
|
-
function taskOperationsGuide(bin = 'skrr') {
|
|
35
|
-
const command = (usage, purpose) => ({ command: `${bin} ${usage}`, purpose });
|
|
36
|
-
return {
|
|
37
|
-
title: 'Task operations',
|
|
38
|
-
sections: [
|
|
39
|
-
{
|
|
40
|
-
title: 'Inspect and plan',
|
|
41
|
-
commands: [
|
|
42
|
-
command('tasks show <task> --json', 'Read the Task, assignment, and dependency IDs.'),
|
|
43
|
-
command('tasks actionability <task>', 'Read the canonical next actor, blocker, and allowed actions.'),
|
|
44
|
-
command('tasks preflight <task>', 'Read workflow actionability and execution prerequisites together; creates no Run.'),
|
|
45
|
-
command('tasks ready --for agent --agent <agent-id>', 'Find workflow-actionable work for an Agent; this does not verify its runtime.'),
|
|
46
|
-
command('tasks create-tree --help', 'Create a related Task tree; blockedByTempIds declares dependencies within that tree.'),
|
|
47
|
-
],
|
|
48
|
-
},
|
|
49
|
-
{
|
|
50
|
-
title: 'Choose assignment',
|
|
51
|
-
note: 'Executor intent, assignment, and starting a Run are separate choices. --unassigned clears both assignee kinds and suppresses automatic defaults; it does not start work.',
|
|
52
|
-
commands: [
|
|
53
|
-
command('tasks create --title "Task title" --execution-mode agent --unassigned --dry-run --json', 'Preview Agent-intended work without selecting an Agent or writing a Task.'),
|
|
54
|
-
command('tasks assign <task> --unassigned', 'Clear current assignees while retaining executor intent; creates no Run.'),
|
|
55
|
-
command('tasks assign <task> --help', 'Choose an eligible Human or Agent explicitly using the native assignment options.'),
|
|
56
|
-
],
|
|
57
|
-
},
|
|
58
|
-
{
|
|
59
|
-
title: 'Apply and recover a batch',
|
|
60
|
-
note: 'Keep the same JSON, scope, and external-key options after an incomplete result. Explicit node externalKey values are preserved; otherwise keys derive from the file and defaults. Changing that input can name new Tasks.',
|
|
61
|
-
commands: [
|
|
62
|
-
command('tasks create-tree --from-json tasks.json --unassigned --readback --json', 'Apply the batch and reread its declared structured fields. This does not start Tasks.'),
|
|
63
|
-
command('tasks create-tree --from-json tasks.json --unassigned --resume --readback --json', 'Continue the unchanged file: confirm existing IDs without patching them, create only missing nodes, then read back.'),
|
|
64
|
-
command('tasks create-tree --from-json tasks.json --unassigned --verify-spec --json', 'Read-only structured-field comparison. Rich text and unsupported fields are explicitly unchecked; no Expectation assessment or drift repair is performed.'),
|
|
65
|
-
command('tasks create-tree --from-json tasks.json --verify --json', 'Read-only existence check by external key. This makes no specification-equality claim.'),
|
|
66
|
-
],
|
|
67
|
-
},
|
|
68
|
-
{
|
|
69
|
-
title: 'Name a Project or Goal',
|
|
70
|
-
note: 'Use exact scoped Project/Goal titles, native refs, or canonical IDs. Duplicate names refuse instead of choosing one. A qualified Project supplies containment; a qualified Goal changes the Goal relationship without moving the Task. Project external keys remain exact and case-sensitive, including keys containing slashes.',
|
|
71
|
-
commands: [
|
|
72
|
-
command('tasks create --title "Task title" --space <space-id> --project "Exact project title" --goal "Exact Goal title" --unassigned --dry-run --json', 'Preview exact names inside the selected Space.'),
|
|
73
|
-
command('tasks create --title "Task title" --project "<space-ref>/<project-ref-or-exact-title>" --goal "<space-ref>/<Goal-exact-title>" --unassigned --dry-run --json', 'Preview qualified references; native relationship permissions still apply.'),
|
|
74
|
-
command('tasks create --title "Task title" --space <space-id> --project "<project-external-key>" --unassigned --dry-run --json', 'Reuse an existing Project’s native external key without rewriting it.'),
|
|
75
|
-
],
|
|
76
|
-
},
|
|
77
|
-
{
|
|
78
|
-
title: 'Edit dependencies',
|
|
79
|
-
note: 'A waits for B: update A with --add-blocked-by B. These commands change dependencies; inspecting a blocker does not remove it.',
|
|
80
|
-
commands: [
|
|
81
|
-
command('tasks update <task-A> --add-blocked-by <task-B>', 'Make A wait for B.'),
|
|
82
|
-
command('tasks update <task-A> --remove-blocked-by <task-B>', 'Remove that dependency when the relationship is no longer intended.'),
|
|
83
|
-
command('tasks update <task-B> --add-blocks <task-A>', 'The same relationship, edited from B.'),
|
|
84
|
-
],
|
|
85
|
-
},
|
|
86
|
-
{
|
|
87
|
-
title: 'Check execution before starting',
|
|
88
|
-
note: 'Preflight reads server projections at separate times. It runs no shell command and cannot prove machine directory access, tool availability, or future approval. Starting rechecks current authority.',
|
|
89
|
-
commands: [
|
|
90
|
-
command('tasks start <task> --dry-run --json', 'Read only the server-owned execution preview through the dedicated preview endpoint.'),
|
|
91
|
-
command('agents show <agent-id>', 'Inspect the assigned Agent and its configured runtime.'),
|
|
92
|
-
command('harnesses show <harness-id> --json', 'Inspect the actual Harness record and reported provider credential readiness, where available.'),
|
|
93
|
-
command('harnesses installers --daemon <daemon-id>', 'Ask that daemon about installed coding CLIs and sign-in where it can tell; this is not a shell execution test.'),
|
|
94
|
-
command('harnesses ping <harness-id>', 'Explicitly probe a local daemon for reachability; an answer does not prove a provider can run a task.'),
|
|
95
|
-
command('tasks start <task>', 'Start a new Run after reviewing the checks. This command executes work.'),
|
|
96
|
-
],
|
|
97
|
-
},
|
|
98
|
-
{
|
|
99
|
-
title: 'Follow and finish',
|
|
100
|
-
commands: [
|
|
101
|
-
command('tasks show <task>', 'Inspect current execution and follow the Task link.'),
|
|
102
|
-
command('tasks expectations <task>', 'Read what is owed and how it will be checked.'),
|
|
103
|
-
command('tasks result submit <task> --help', 'Submit the concrete result and evidence.'),
|
|
104
|
-
command('tasks review <task> --help', 'Review another actor’s exact Result.'),
|
|
105
|
-
command('tasks complete <task> --help', 'Submit a Result and close out when appropriate.'),
|
|
106
|
-
command('instructions guide task-management', 'Read the detailed canonical workflow guide; --offline uses the bundled release copy.'),
|
|
107
|
-
],
|
|
108
|
-
},
|
|
109
|
-
],
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
function taskOperationsGuideLines(bin = 'skrr') {
|
|
113
|
-
const guide = taskOperationsGuide(bin);
|
|
114
|
-
const lines = [guide.title];
|
|
115
|
-
for (const section of guide.sections) {
|
|
116
|
-
lines.push('', section.title);
|
|
117
|
-
if (section.note)
|
|
118
|
-
lines.push(section.note);
|
|
119
|
-
for (const entry of section.commands)
|
|
120
|
-
lines.push(` ${entry.command}\n ${entry.purpose}`);
|
|
121
|
-
}
|
|
122
|
-
return lines;
|
|
123
|
-
}
|