@skrr-ai/cli 0.1.10 → 0.1.11
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 +1 -13
- package/dist/base-command.js +78 -1
- package/dist/commands/browser/skill/show.js +7 -1
- package/dist/commands/code/index.d.ts +1 -0
- package/dist/commands/code/index.js +9 -1
- package/dist/commands/commitments/analytics/index.js +2 -0
- package/dist/commands/daemon/byok.d.ts +1 -0
- package/dist/commands/daemon/byok.js +2 -1
- package/dist/commands/daemon/install.d.ts +1 -0
- package/dist/commands/daemon/install.js +2 -1
- package/dist/commands/daemon/login.d.ts +31 -0
- package/dist/commands/daemon/login.js +56 -0
- package/dist/commands/daemon/restart.d.ts +9 -0
- package/dist/commands/daemon/restart.js +36 -0
- package/dist/commands/daemon/start.d.ts +1 -0
- package/dist/commands/daemon/start.js +2 -1
- package/dist/commands/daemon/status.d.ts +1 -0
- package/dist/commands/daemon/status.js +2 -1
- package/dist/commands/daemon/stop.d.ts +1 -0
- package/dist/commands/daemon/stop.js +2 -1
- package/dist/commands/daemon/uninstall.d.ts +1 -0
- package/dist/commands/daemon/uninstall.js +2 -1
- package/dist/commands/daemon/unlock.d.ts +9 -0
- package/dist/commands/daemon/unlock.js +33 -0
- package/dist/commands/goals/key-results/create.js +32 -1
- package/dist/commands/goals/key-results/update.d.ts +11 -0
- package/dist/commands/goals/key-results/update.js +80 -2
- package/dist/commands/goals/plan-now.d.ts +54 -2
- package/dist/commands/goals/plan-now.js +175 -18
- package/dist/commands/goals/planner-config.d.ts +60 -9
- package/dist/commands/goals/planner-config.js +82 -34
- package/dist/commands/goals/revisions.js +17 -0
- package/dist/commands/goals/show.d.ts +17 -0
- package/dist/commands/goals/show.js +90 -3
- package/dist/commands/login.js +30 -0
- package/dist/commands/spaces/create.js +2 -1
- package/dist/commands/spaces/index.js +9 -1
- package/dist/commands/spaces/list.d.ts +18 -0
- package/dist/commands/spaces/list.js +57 -7
- package/dist/commands/spaces/show.js +4 -1
- package/dist/commands/spaces/summary.d.ts +4 -0
- package/dist/commands/spaces/summary.js +77 -1
- package/dist/commands/spaces/update.d.ts +4 -0
- package/dist/commands/spaces/update.js +38 -1
- package/dist/commands/tasks/actionability.js +40 -1
- package/dist/commands/tasks/activity.d.ts +29 -0
- package/dist/commands/tasks/activity.js +47 -0
- package/dist/commands/tasks/complete.d.ts +47 -0
- package/dist/commands/tasks/complete.js +159 -12
- package/dist/commands/tasks/create.d.ts +26 -0
- package/dist/commands/tasks/create.js +60 -1
- package/dist/commands/tasks/events/append.d.ts +2 -0
- package/dist/commands/tasks/events/append.js +41 -10
- package/dist/commands/tasks/events/list.js +60 -9
- package/dist/commands/tasks/output.js +22 -2
- package/dist/commands/tasks/ready.d.ts +38 -0
- package/dist/commands/tasks/ready.js +37 -0
- package/dist/commands/tasks/runs.d.ts +22 -0
- package/dist/commands/tasks/runs.js +110 -2
- package/dist/commands/tasks/show.d.ts +28 -0
- package/dist/commands/tasks/show.js +61 -0
- package/dist/commands/tasks/timeline.d.ts +7 -0
- package/dist/commands/tasks/timeline.js +34 -3
- package/dist/commands/tasks/update.d.ts +20 -0
- package/dist/commands/tasks/update.js +38 -1
- package/dist/commands/whoami.d.ts +40 -0
- package/dist/commands/whoami.js +57 -9
- package/dist/commands/wiki/ls.d.ts +23 -0
- package/dist/commands/wiki/ls.js +63 -10
- package/dist/commands/wiki/mv.d.ts +70 -0
- package/dist/commands/wiki/mv.js +198 -4
- package/dist/commands/wiki/rm.js +12 -1
- package/dist/commands/wiki/write.js +35 -3
- package/dist/help.d.ts +27 -0
- package/dist/help.js +48 -0
- package/dist/hooks/command-not-found.d.ts +31 -0
- package/dist/hooks/command-not-found.js +12 -80
- package/dist/lib/command-miss.d.ts +60 -0
- package/dist/lib/command-miss.js +128 -0
- package/dist/lib/commitment-analytics.d.ts +10 -0
- package/dist/lib/commitment-analytics.js +10 -0
- package/dist/lib/config.js +6 -2
- package/dist/lib/daemonBroker.d.ts +7 -1
- package/dist/lib/daemonBroker.js +71 -0
- package/dist/lib/daemonHandoff.d.ts +38 -0
- package/dist/lib/daemonHandoff.js +297 -0
- package/dist/lib/exec-oversky.d.ts +30 -0
- package/dist/lib/exec-oversky.js +41 -0
- package/dist/lib/format.d.ts +7 -0
- package/dist/lib/format.js +27 -5
- package/dist/lib/login.js +22 -2
- package/dist/lib/sky-code.js +1 -1
- package/dist/lib/task-transcript.d.ts +9 -0
- package/dist/lib/task-transcript.js +13 -2
- package/dist/lib/tasks.d.ts +34 -0
- package/dist/lib/tasks.js +70 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/configRoot.d.ts +50 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/configRoot.js +67 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +1 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +7 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/configRoot.d.ts +50 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/configRoot.js +59 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +1 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +3 -0
- package/dist/node_modules/@skrr-ai/auth-core/package.json +1 -1
- package/dist/node_modules/@skrr-ai/data-provider/index.js +15 -4
- package/oclif.manifest.json +15771 -15538
- package/package.json +5 -3
|
@@ -4,17 +4,33 @@ const core_1 = require("@oclif/core");
|
|
|
4
4
|
const data_provider_1 = require("@skrr-ai/data-provider");
|
|
5
5
|
const base_command_1 = require("../../../base-command");
|
|
6
6
|
const task_resolver_1 = require("../../../lib/task-resolver");
|
|
7
|
+
const format_1 = require("../../../lib/format");
|
|
8
|
+
const tasks_1 = require("../../../lib/tasks");
|
|
7
9
|
class TasksEventsList extends base_command_1.BaseCommand {
|
|
8
10
|
static description = 'List durable task events';
|
|
9
|
-
static args = {
|
|
11
|
+
static args = {
|
|
12
|
+
id: core_1.Args.string({
|
|
13
|
+
required: false,
|
|
14
|
+
ignoreStdin: true,
|
|
15
|
+
description: 'Task ID. Optional inside a task runtime, which supplies it',
|
|
16
|
+
}),
|
|
17
|
+
};
|
|
10
18
|
static flags = {
|
|
11
|
-
task: core_1.Flags.string(
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
19
|
+
task: core_1.Flags.string({
|
|
20
|
+
description: 'Task ID; overrides the runtime context and the positional',
|
|
21
|
+
}),
|
|
22
|
+
json: core_1.Flags.boolean({ description: 'Output the raw event list as JSON' }),
|
|
23
|
+
limit: core_1.Flags.integer({ default: 50, description: 'Maximum events to return' }),
|
|
24
|
+
cursor: core_1.Flags.string({ description: 'Continue a previous page, from its next_cursor' }),
|
|
25
|
+
kind: core_1.Flags.string({
|
|
26
|
+
description: 'Show only events of this kind',
|
|
27
|
+
options: [...tasks_1.TASK_WORK_EVENT_KINDS],
|
|
28
|
+
}),
|
|
29
|
+
run: core_1.Flags.string({ description: 'Show only events from this execution run' }),
|
|
30
|
+
actor: core_1.Flags.string({
|
|
31
|
+
description: 'Show only events from this actor kind',
|
|
32
|
+
options: ['user', 'agent', 'system'],
|
|
33
|
+
}),
|
|
18
34
|
};
|
|
19
35
|
async run() {
|
|
20
36
|
this.requireAuth();
|
|
@@ -33,7 +49,42 @@ class TasksEventsList extends base_command_1.BaseCommand {
|
|
|
33
49
|
catch (error) {
|
|
34
50
|
this.handleApiError(error);
|
|
35
51
|
}
|
|
36
|
-
|
|
52
|
+
// This printed raw Mongo documents — `_id` and `__v` included — regardless
|
|
53
|
+
// of `--json`, which was a byte-identical no-op: the flag advertised a mode
|
|
54
|
+
// the command did not have. Its two siblings, `tasks timeline` and `tasks
|
|
55
|
+
// activity`, both render tables, so the one command an agent is pointed at
|
|
56
|
+
// for the durable ledger was the one that made you read storage internals.
|
|
57
|
+
if (flags.json) {
|
|
58
|
+
this.log(JSON.stringify(result, null, 2));
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const rows = (result?.data ?? []);
|
|
62
|
+
if (rows.length === 0) {
|
|
63
|
+
this.log('No events.');
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
(0, format_1.renderTable)(rows.map((event) => ({
|
|
67
|
+
time: String(event.occurredAt ?? '-')
|
|
68
|
+
.replace('T', ' ')
|
|
69
|
+
.slice(0, 19),
|
|
70
|
+
kind: String(event.kind ?? '-'),
|
|
71
|
+
actor: String(event.actor?.kind ?? '-'),
|
|
72
|
+
summary: (0, format_1.truncate)(String(event.summary ?? '-'), 80),
|
|
73
|
+
run: String(event.executionRunId ?? '-'),
|
|
74
|
+
})), [
|
|
75
|
+
{ key: 'time', header: 'TIME' },
|
|
76
|
+
{ key: 'kind', header: 'KIND' },
|
|
77
|
+
{ key: 'actor', header: 'ACTOR' },
|
|
78
|
+
{ key: 'summary', header: 'SUMMARY', maxWidth: 80 },
|
|
79
|
+
{ key: 'run', header: 'RUN', maxWidth: 24 },
|
|
80
|
+
], (line) => this.log(line));
|
|
81
|
+
// The fuller fields (changed / next / evidence / artifacts) are on the rows
|
|
82
|
+
// and deliberately not columns — five of them would not fit a terminal.
|
|
83
|
+
// Point at the flag that has them rather than truncating them into noise.
|
|
84
|
+
this.log('');
|
|
85
|
+
this.log('Full event bodies (changed, next, evidence, artifacts): --json');
|
|
86
|
+
if (result?.next_cursor)
|
|
87
|
+
this.log(`Next page: --cursor ${result.next_cursor}`);
|
|
37
88
|
}
|
|
38
89
|
}
|
|
39
90
|
exports.default = TasksEventsList;
|
|
@@ -94,6 +94,9 @@ class TasksOutput extends base_command_1.BaseCommand {
|
|
|
94
94
|
? { report: latestReport, occurredAt: latestReportEvent?.occurredAt }
|
|
95
95
|
: null,
|
|
96
96
|
taskOutput: outputComment,
|
|
97
|
+
// Present in JSON as well, so a script can detect a task closed
|
|
98
|
+
// more than once without diffing comment bodies.
|
|
99
|
+
completionCount: outputComment ? outputComment.supersededCount + 1 : 0,
|
|
97
100
|
finalMessage,
|
|
98
101
|
messageCount: messages.length,
|
|
99
102
|
}, null, 2));
|
|
@@ -104,12 +107,13 @@ class TasksOutput extends base_command_1.BaseCommand {
|
|
|
104
107
|
conversationId,
|
|
105
108
|
latestRun,
|
|
106
109
|
latestReport,
|
|
110
|
+
latestReportAt: latestReportEvent?.occurredAt ? String(latestReportEvent.occurredAt) : null,
|
|
107
111
|
outputComment,
|
|
108
112
|
finalMessage,
|
|
109
113
|
full: flags.full,
|
|
110
114
|
});
|
|
111
115
|
}
|
|
112
|
-
renderHuman({ task, conversationId, latestRun, latestReport, outputComment, finalMessage, full, }) {
|
|
116
|
+
renderHuman({ task, conversationId, latestRun, latestReport, latestReportAt, outputComment, finalMessage, full, }) {
|
|
113
117
|
const ref = String(task.identifier ?? task.id ?? '-');
|
|
114
118
|
const agents = Array.isArray(task.assigneeAgentIds) ? task.assigneeAgentIds : [];
|
|
115
119
|
this.log(`Task: ${ref} — ${task.title ?? '-'}`);
|
|
@@ -120,7 +124,13 @@ class TasksOutput extends base_command_1.BaseCommand {
|
|
|
120
124
|
: 'never executed'}`);
|
|
121
125
|
this.log('');
|
|
122
126
|
if (latestReport) {
|
|
123
|
-
|
|
127
|
+
// Timestamped like the Verdict below it. These two blocks are selected
|
|
128
|
+
// independently — the newest task-report work event and the newest
|
|
129
|
+
// TASK_OUTPUT comment — so on a task completed more than once they can
|
|
130
|
+
// come from different completions and disagree. Undated, that was
|
|
131
|
+
// invisible.
|
|
132
|
+
const reportAt = latestReportAt ? ` · ${(0, task_transcript_1.formatTime)(latestReportAt)}` : '';
|
|
133
|
+
this.log(`Outcome (task report${reportAt}):`);
|
|
124
134
|
this.log(` ${latestReport.outcome}`);
|
|
125
135
|
this.log(' findings:');
|
|
126
136
|
for (const finding of latestReport.findings)
|
|
@@ -156,6 +166,16 @@ class TasksOutput extends base_command_1.BaseCommand {
|
|
|
156
166
|
// Reconcile the three signals a parked run shows — `completed`,
|
|
157
167
|
// `UNVERIFIED`, and `Last run: failed` — which otherwise read as a
|
|
158
168
|
// contradiction. (OSK-4945)
|
|
169
|
+
if (outputComment.supersededCount > 0) {
|
|
170
|
+
// `tasks complete` succeeds on an already-done task and posts another
|
|
171
|
+
// block, so the canonical-looking screen can be assembled from several
|
|
172
|
+
// closeouts. Say so rather than presenting it as one record.
|
|
173
|
+
const total = outputComment.supersededCount + 1;
|
|
174
|
+
const earlier = outputComment.supersededCount === 1 ? 'block' : 'blocks';
|
|
175
|
+
this.log(` note: completed ${total} times — ${outputComment.supersededCount} earlier TASK_OUTPUT ${earlier} exist.`);
|
|
176
|
+
this.log(' The Outcome above may come from a different completion than this');
|
|
177
|
+
this.log(' Verdict. `skrr tasks comments list` shows every one.');
|
|
178
|
+
}
|
|
159
179
|
const note = (0, task_transcript_1.unverifiedOutcomeNote)(parsed.verdict);
|
|
160
180
|
if (note)
|
|
161
181
|
this.log(` note: ${note}`);
|
|
@@ -1,4 +1,27 @@
|
|
|
1
1
|
import { BaseCommand } from '../../base-command';
|
|
2
|
+
/**
|
|
3
|
+
* Repair oclif's required-flag copy for a flag named after a preposition. (S4 dogfood finding)
|
|
4
|
+
*
|
|
5
|
+
* oclif builds the reason as `Missing required flag ${name}` — bare, unquoted —
|
|
6
|
+
* and wraps it in `The following error occurred:`. For almost every flag that
|
|
7
|
+
* reads fine (`Missing required flag idempotency-key`). For `--for` it produces
|
|
8
|
+
*
|
|
9
|
+
* Missing required flag for
|
|
10
|
+
*
|
|
11
|
+
* which reads as a sentence cut off mid-clause: missing a required flag FOR
|
|
12
|
+
* WHAT? The reader has to already know `for` is the flag's name to parse it,
|
|
13
|
+
* and the one reader who does not is exactly the one getting this error.
|
|
14
|
+
*
|
|
15
|
+
* Restoring the `--` makes the name unmistakably a name. The rewrite is
|
|
16
|
+
* deliberately narrow — it matches the whole reason line and leaves anything
|
|
17
|
+
* else untouched — so an oclif that changes this template degrades to today's
|
|
18
|
+
* wording rather than mangling a different message.
|
|
19
|
+
*
|
|
20
|
+
* The general form of this belongs in `BaseCommand.catch`, where it would cover
|
|
21
|
+
* every command; this is the local fix for the one flag where the ambiguity is
|
|
22
|
+
* actually harmful.
|
|
23
|
+
*/
|
|
24
|
+
export declare function clarifyMissingRequiredFlag(message: string): string;
|
|
2
25
|
export default class TasksReady extends BaseCommand {
|
|
3
26
|
static description: string;
|
|
4
27
|
static examples: string[];
|
|
@@ -11,5 +34,20 @@ export default class TasksReady extends BaseCommand {
|
|
|
11
34
|
limit: import("@oclif/core/lib/interfaces").OptionFlag<number, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
12
35
|
cursor: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
13
36
|
};
|
|
37
|
+
/**
|
|
38
|
+
* Rewrite the parse-error copy on the way out, then hand the error back
|
|
39
|
+
* unchanged in every other respect — same class, same `oclif.exit`, same
|
|
40
|
+
* `--json` envelope from `BaseCommand.catch`. Only the sentence changes.
|
|
41
|
+
*/
|
|
42
|
+
protected catch(err: Error & {
|
|
43
|
+
code?: string;
|
|
44
|
+
exitCode?: number;
|
|
45
|
+
oclif?: {
|
|
46
|
+
exit?: number;
|
|
47
|
+
};
|
|
48
|
+
parse?: unknown;
|
|
49
|
+
suggestions?: unknown;
|
|
50
|
+
retryable?: boolean;
|
|
51
|
+
}): Promise<unknown>;
|
|
14
52
|
run(): Promise<void>;
|
|
15
53
|
}
|
|
@@ -1,10 +1,36 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.clarifyMissingRequiredFlag = clarifyMissingRequiredFlag;
|
|
3
4
|
const core_1 = require("@oclif/core");
|
|
4
5
|
const data_provider_1 = require("@skrr-ai/data-provider");
|
|
5
6
|
const base_command_1 = require("../../base-command");
|
|
6
7
|
const format_1 = require("../../lib/format");
|
|
7
8
|
const web_url_1 = require("../../lib/web-url");
|
|
9
|
+
/**
|
|
10
|
+
* Repair oclif's required-flag copy for a flag named after a preposition. (S4 dogfood finding)
|
|
11
|
+
*
|
|
12
|
+
* oclif builds the reason as `Missing required flag ${name}` — bare, unquoted —
|
|
13
|
+
* and wraps it in `The following error occurred:`. For almost every flag that
|
|
14
|
+
* reads fine (`Missing required flag idempotency-key`). For `--for` it produces
|
|
15
|
+
*
|
|
16
|
+
* Missing required flag for
|
|
17
|
+
*
|
|
18
|
+
* which reads as a sentence cut off mid-clause: missing a required flag FOR
|
|
19
|
+
* WHAT? The reader has to already know `for` is the flag's name to parse it,
|
|
20
|
+
* and the one reader who does not is exactly the one getting this error.
|
|
21
|
+
*
|
|
22
|
+
* Restoring the `--` makes the name unmistakably a name. The rewrite is
|
|
23
|
+
* deliberately narrow — it matches the whole reason line and leaves anything
|
|
24
|
+
* else untouched — so an oclif that changes this template degrades to today's
|
|
25
|
+
* wording rather than mangling a different message.
|
|
26
|
+
*
|
|
27
|
+
* The general form of this belongs in `BaseCommand.catch`, where it would cover
|
|
28
|
+
* every command; this is the local fix for the one flag where the ambiguity is
|
|
29
|
+
* actually harmful.
|
|
30
|
+
*/
|
|
31
|
+
function clarifyMissingRequiredFlag(message) {
|
|
32
|
+
return message.replace(/Missing required flag ([A-Za-z0-9][\w-]*)$/gm, (_full, name) => `Missing required flag --${name}`);
|
|
33
|
+
}
|
|
8
34
|
class TasksReady extends base_command_1.BaseCommand {
|
|
9
35
|
static description = 'List tasks ready for a Human or Agent, using server-owned readiness';
|
|
10
36
|
static examples = [
|
|
@@ -24,6 +50,17 @@ class TasksReady extends base_command_1.BaseCommand {
|
|
|
24
50
|
limit: core_1.Flags.integer({ description: 'Max results', default: 50 }),
|
|
25
51
|
cursor: core_1.Flags.string({ description: 'Pagination cursor from a previous page' }),
|
|
26
52
|
};
|
|
53
|
+
/**
|
|
54
|
+
* Rewrite the parse-error copy on the way out, then hand the error back
|
|
55
|
+
* unchanged in every other respect — same class, same `oclif.exit`, same
|
|
56
|
+
* `--json` envelope from `BaseCommand.catch`. Only the sentence changes.
|
|
57
|
+
*/
|
|
58
|
+
async catch(err) {
|
|
59
|
+
if (typeof err?.message === 'string') {
|
|
60
|
+
err.message = clarifyMissingRequiredFlag(err.message);
|
|
61
|
+
}
|
|
62
|
+
return super.catch(err);
|
|
63
|
+
}
|
|
27
64
|
async run() {
|
|
28
65
|
this.requireAuth();
|
|
29
66
|
const { flags } = await this.parse(TasksReady);
|
|
@@ -1,10 +1,32 @@
|
|
|
1
1
|
import { BaseCommand } from '../../base-command';
|
|
2
|
+
import { type RunFailure } from './activity';
|
|
2
3
|
/**
|
|
3
4
|
* OSK-4836 — say WHERE a run executed. A named daemon wins over the environment
|
|
4
5
|
* label; an environment with no daemon is the cloud worker (or a runtime whose
|
|
5
6
|
* daemon id was not recorded). `-` when the row predates executor stamping.
|
|
6
7
|
*/
|
|
7
8
|
export declare function formatRunExecutor(run: Record<string, unknown>): string;
|
|
9
|
+
/**
|
|
10
|
+
* Whether this run row is one whose reason the caller cannot see. (S3 dogfood finding)
|
|
11
|
+
*
|
|
12
|
+
* `{ status: 'failed', errors: 1 }` and nothing else — a count of errors with
|
|
13
|
+
* no error. The row is complete as far as the projection goes, so the only way
|
|
14
|
+
* to tell a caller why is to go and read it from somewhere the CLI can reach.
|
|
15
|
+
* This decides when that lookup is worth a round trip: never for a healthy
|
|
16
|
+
* list, always once something failed.
|
|
17
|
+
*/
|
|
18
|
+
export declare function runNeedsFailureReason(run: Record<string, unknown>): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* The run's OWN failure fields, when the server carries them.
|
|
21
|
+
*
|
|
22
|
+
* The projection now derives `failureReason` / `failureCode` / `failureClass`
|
|
23
|
+
* from the terminal event it already had in hand, so the reason travels with
|
|
24
|
+
* the run rather than having to be reassembled from the activity log by runId.
|
|
25
|
+
* Exported for `runs.spec.ts`.
|
|
26
|
+
*/
|
|
27
|
+
export declare function failureFromRun(run: Record<string, unknown>): RunFailure | null;
|
|
28
|
+
/** The reason block printed under the table — one entry per run we could explain. */
|
|
29
|
+
export declare function renderRunFailures(runs: Array<Record<string, unknown>>, failures: Map<string, RunFailure>, bin?: string, taskRef?: string): string[];
|
|
8
30
|
export default class TasksRuns extends BaseCommand {
|
|
9
31
|
static description: string;
|
|
10
32
|
static examples: string[];
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.formatRunExecutor = formatRunExecutor;
|
|
4
|
+
exports.runNeedsFailureReason = runNeedsFailureReason;
|
|
5
|
+
exports.failureFromRun = failureFromRun;
|
|
6
|
+
exports.renderRunFailures = renderRunFailures;
|
|
4
7
|
const core_1 = require("@oclif/core");
|
|
5
8
|
const data_provider_1 = require("@skrr-ai/data-provider");
|
|
6
9
|
const base_command_1 = require("../../base-command");
|
|
7
10
|
const format_1 = require("../../lib/format");
|
|
11
|
+
const activity_1 = require("./activity");
|
|
8
12
|
function formatTime(value) {
|
|
9
13
|
try {
|
|
10
14
|
return new Date(String(value)).toISOString().slice(0, 19).replace('T', ' ');
|
|
@@ -29,6 +33,79 @@ function formatRunExecutor(run) {
|
|
|
29
33
|
}
|
|
30
34
|
return '-';
|
|
31
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* Whether this run row is one whose reason the caller cannot see. (S3 dogfood finding)
|
|
38
|
+
*
|
|
39
|
+
* `{ status: 'failed', errors: 1 }` and nothing else — a count of errors with
|
|
40
|
+
* no error. The row is complete as far as the projection goes, so the only way
|
|
41
|
+
* to tell a caller why is to go and read it from somewhere the CLI can reach.
|
|
42
|
+
* This decides when that lookup is worth a round trip: never for a healthy
|
|
43
|
+
* list, always once something failed.
|
|
44
|
+
*/
|
|
45
|
+
function runNeedsFailureReason(run) {
|
|
46
|
+
if (run.status === 'failed' || run.outcome === 'failed')
|
|
47
|
+
return true;
|
|
48
|
+
return typeof run.errors === 'number' && run.errors > 0;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* The run's OWN failure fields, when the server carries them.
|
|
52
|
+
*
|
|
53
|
+
* The projection now derives `failureReason` / `failureCode` / `failureClass`
|
|
54
|
+
* from the terminal event it already had in hand, so the reason travels with
|
|
55
|
+
* the run rather than having to be reassembled from the activity log by runId.
|
|
56
|
+
* Exported for `runs.spec.ts`.
|
|
57
|
+
*/
|
|
58
|
+
function failureFromRun(run) {
|
|
59
|
+
const message = typeof run.failureReason === 'string' ? run.failureReason.trim() : '';
|
|
60
|
+
if (!message)
|
|
61
|
+
return null;
|
|
62
|
+
return {
|
|
63
|
+
runId: String(run.runId ?? run.id ?? run._id ?? '-'),
|
|
64
|
+
message,
|
|
65
|
+
code: typeof run.failureCode === 'string' ? run.failureCode : null,
|
|
66
|
+
causeClass: typeof run.failureClass === 'string' ? run.failureClass : null,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/** The reason block printed under the table — one entry per run we could explain. */
|
|
70
|
+
function renderRunFailures(runs, failures, bin = 'skrr', taskRef = '<task-id>') {
|
|
71
|
+
const needing = runs.filter(runNeedsFailureReason);
|
|
72
|
+
if (needing.length === 0)
|
|
73
|
+
return [];
|
|
74
|
+
const lines = ['', 'Why they failed:'];
|
|
75
|
+
let explained = 0;
|
|
76
|
+
let usedActivity = false;
|
|
77
|
+
for (const run of needing) {
|
|
78
|
+
const runId = String(run.runId ?? run.id ?? run._id ?? '-');
|
|
79
|
+
// The run's own field first. The activity join stays as the fallback
|
|
80
|
+
// because a CLI talks to whatever server it is pointed at, and one that
|
|
81
|
+
// predates the projection field still has the activity log — dropping the
|
|
82
|
+
// fallback would make this command worse against an older deployment than
|
|
83
|
+
// it was before the field existed.
|
|
84
|
+
const own = failureFromRun(run);
|
|
85
|
+
const failure = own ?? failures.get(runId);
|
|
86
|
+
if (!failure) {
|
|
87
|
+
// No invented reason. Saying "unknown" is the honest row, and it is
|
|
88
|
+
// materially different from the row above it.
|
|
89
|
+
lines.push(` ${runId} no reason recorded in the activity log.`);
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
explained += 1;
|
|
93
|
+
if (!own)
|
|
94
|
+
usedActivity = true;
|
|
95
|
+
const tags = [failure.code, failure.causeClass].filter(Boolean).join(' · ');
|
|
96
|
+
lines.push(` ${runId}${tags ? ` ${tags}` : ''}`);
|
|
97
|
+
lines.push(` ${failure.message}`);
|
|
98
|
+
}
|
|
99
|
+
// Name the SOURCE only when it is not the run itself: a reason that came with
|
|
100
|
+
// the row needs no directions to somewhere else.
|
|
101
|
+
if (explained === 0) {
|
|
102
|
+
lines.push(` (the run projection carries no message; try \`${bin} tasks show ${taskRef}\` → executionError)`);
|
|
103
|
+
}
|
|
104
|
+
else if (usedActivity) {
|
|
105
|
+
lines.push(` (from the activity log — \`${bin} tasks activity ${taskRef} --json\` → details.error)`);
|
|
106
|
+
}
|
|
107
|
+
return lines;
|
|
108
|
+
}
|
|
32
109
|
class TasksRuns extends base_command_1.BaseCommand {
|
|
33
110
|
static description = 'List execution runs for a task';
|
|
34
111
|
static examples = [
|
|
@@ -51,12 +128,26 @@ class TasksRuns extends base_command_1.BaseCommand {
|
|
|
51
128
|
catch (err) {
|
|
52
129
|
this.handleApiError(err);
|
|
53
130
|
}
|
|
131
|
+
const r = response;
|
|
132
|
+
const items = r?.data ?? [];
|
|
133
|
+
// A failed run whose reason the ROW does not carry. Split from "failed" so
|
|
134
|
+
// the note below cannot keep asserting the projection has no reason field
|
|
135
|
+
// once the server started sending one — a warning that outlives its cause
|
|
136
|
+
// is just a false statement with a warning icon.
|
|
137
|
+
const unexplained = items.filter(runNeedsFailureReason).some((run) => !failureFromRun(run));
|
|
54
138
|
if (flags.json) {
|
|
139
|
+
// The server payload goes out byte-identical — enriching it would put a
|
|
140
|
+
// CLI-derived reason under a key a caller would read as the server's.
|
|
141
|
+
// The pointer goes to STDERR instead, so `--json` stays parseable and a
|
|
142
|
+
// scripted caller still learns where the message actually lives.
|
|
143
|
+
if (unexplained) {
|
|
144
|
+
this.warn('A failed run here carries `errors` but no `failureReason` — this server predates that ' +
|
|
145
|
+
`field. Read it from \`${this.config.bin} tasks show ${args.id} --json\` (executionError) or ` +
|
|
146
|
+
`\`${this.config.bin} tasks activity ${args.id} --json\` (details.error, keyed by runId).`);
|
|
147
|
+
}
|
|
55
148
|
this.log(JSON.stringify(response, null, 2));
|
|
56
149
|
return;
|
|
57
150
|
}
|
|
58
|
-
const r = response;
|
|
59
|
-
const items = r?.data ?? [];
|
|
60
151
|
if (items.length === 0) {
|
|
61
152
|
this.log('No runs.');
|
|
62
153
|
return;
|
|
@@ -77,6 +168,23 @@ class TasksRuns extends base_command_1.BaseCommand {
|
|
|
77
168
|
{ key: 'duration', header: 'DURATION' },
|
|
78
169
|
{ key: 'tokens', header: 'TOKENS' },
|
|
79
170
|
], (line) => this.log(line));
|
|
171
|
+
// Only once something failed: a healthy list pays nothing for this.
|
|
172
|
+
if (!unexplained)
|
|
173
|
+
return;
|
|
174
|
+
let failures = new Map();
|
|
175
|
+
try {
|
|
176
|
+
const activity = (await data_provider_1.dataService.getTaskActivity(args.id, 200));
|
|
177
|
+
failures = (0, activity_1.runFailuresFromActivity)(activity?.data);
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
// Best effort by design. The runs listing already succeeded, and a second
|
|
181
|
+
// read that fails must not turn a working command into an error — the
|
|
182
|
+
// block below then says "no reason recorded", which is true of what we
|
|
183
|
+
// can see.
|
|
184
|
+
}
|
|
185
|
+
for (const line of renderRunFailures(items, failures, this.config.bin, args.id)) {
|
|
186
|
+
this.log(line);
|
|
187
|
+
}
|
|
80
188
|
}
|
|
81
189
|
}
|
|
82
190
|
exports.default = TasksRuns;
|
|
@@ -1,4 +1,32 @@
|
|
|
1
1
|
import { BaseCommand } from '../../base-command';
|
|
2
|
+
/**
|
|
3
|
+
* The failure rows of `tasks show`, when there are any. (S3 dogfood finding)
|
|
4
|
+
*
|
|
5
|
+
* The report behind this said a failed task reports `lastError: null` with no
|
|
6
|
+
* message anywhere a program looks. Half of that is true and half is a naming
|
|
7
|
+
* accident worth separating, because they need different fixes:
|
|
8
|
+
*
|
|
9
|
+
* - `lastError` is not a field. `jq .lastError` on a key that does not exist
|
|
10
|
+
* answers `null`, which is indistinguishable from a field that exists and is
|
|
11
|
+
* empty. The reason IS in `--json`, under `executionError`, alongside
|
|
12
|
+
* `lastFailedAt` and `executionAttempts`.
|
|
13
|
+
* - Nothing in the human view ever printed it. `tasks show` rendered `Status:
|
|
14
|
+
* failed` and then twenty rows that say nothing about why, so there was no
|
|
15
|
+
* way to LEARN the field's name short of dumping the whole object — which is
|
|
16
|
+
* exactly how the reader ended up guessing `lastError`.
|
|
17
|
+
*
|
|
18
|
+
* So the fix is discoverability, not a new field: print what the payload
|
|
19
|
+
* already carries, under a label that names the key a script should read.
|
|
20
|
+
*
|
|
21
|
+
* The rules this function will not break:
|
|
22
|
+
* - Never assert a reason we do not have. A task that failed with no
|
|
23
|
+
* `executionError` gets the pointer and no invented text.
|
|
24
|
+
* - Never claim a failure that is not one. Absent both a failure marker and a
|
|
25
|
+
* failed status, this renders nothing at all.
|
|
26
|
+
*
|
|
27
|
+
* Pure and exported so the wording is testable without an oclif runtime.
|
|
28
|
+
*/
|
|
29
|
+
export declare function taskFailureLines(t: Record<string, unknown>, bin?: string): string[];
|
|
2
30
|
export default class TasksShow extends BaseCommand {
|
|
3
31
|
static description: string;
|
|
4
32
|
static examples: string[];
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.taskFailureLines = taskFailureLines;
|
|
3
4
|
const core_1 = require("@oclif/core");
|
|
4
5
|
const data_provider_1 = require("@skrr-ai/data-provider");
|
|
5
6
|
const api_fetch_1 = require("../../lib/api-fetch");
|
|
@@ -8,6 +9,62 @@ const base_command_1 = require("../../base-command");
|
|
|
8
9
|
const task_resolver_1 = require("../../lib/task-resolver");
|
|
9
10
|
const web_url_1 = require("../../lib/web-url");
|
|
10
11
|
const html_text_1 = require("../../lib/html-text");
|
|
12
|
+
/**
|
|
13
|
+
* The failure rows of `tasks show`, when there are any. (S3 dogfood finding)
|
|
14
|
+
*
|
|
15
|
+
* The report behind this said a failed task reports `lastError: null` with no
|
|
16
|
+
* message anywhere a program looks. Half of that is true and half is a naming
|
|
17
|
+
* accident worth separating, because they need different fixes:
|
|
18
|
+
*
|
|
19
|
+
* - `lastError` is not a field. `jq .lastError` on a key that does not exist
|
|
20
|
+
* answers `null`, which is indistinguishable from a field that exists and is
|
|
21
|
+
* empty. The reason IS in `--json`, under `executionError`, alongside
|
|
22
|
+
* `lastFailedAt` and `executionAttempts`.
|
|
23
|
+
* - Nothing in the human view ever printed it. `tasks show` rendered `Status:
|
|
24
|
+
* failed` and then twenty rows that say nothing about why, so there was no
|
|
25
|
+
* way to LEARN the field's name short of dumping the whole object — which is
|
|
26
|
+
* exactly how the reader ended up guessing `lastError`.
|
|
27
|
+
*
|
|
28
|
+
* So the fix is discoverability, not a new field: print what the payload
|
|
29
|
+
* already carries, under a label that names the key a script should read.
|
|
30
|
+
*
|
|
31
|
+
* The rules this function will not break:
|
|
32
|
+
* - Never assert a reason we do not have. A task that failed with no
|
|
33
|
+
* `executionError` gets the pointer and no invented text.
|
|
34
|
+
* - Never claim a failure that is not one. Absent both a failure marker and a
|
|
35
|
+
* failed status, this renders nothing at all.
|
|
36
|
+
*
|
|
37
|
+
* Pure and exported so the wording is testable without an oclif runtime.
|
|
38
|
+
*/
|
|
39
|
+
function taskFailureLines(t, bin = 'skrr') {
|
|
40
|
+
const error = typeof t.executionError === 'string' ? t.executionError.trim() : '';
|
|
41
|
+
const failedAt = t.lastFailedAt ? formatTime(t.lastFailedAt) : null;
|
|
42
|
+
const isFailed = t.status === 'failed';
|
|
43
|
+
if (!error && !failedAt && !isFailed)
|
|
44
|
+
return [];
|
|
45
|
+
const lines = [];
|
|
46
|
+
const attempts = typeof t.executionAttempts === 'number' ? t.executionAttempts : null;
|
|
47
|
+
const runId = typeof t.lastFinalizedRunId === 'string' ? t.lastFinalizedRunId : null;
|
|
48
|
+
const facts = [
|
|
49
|
+
failedAt,
|
|
50
|
+
attempts != null ? `attempt ${attempts}` : null,
|
|
51
|
+
runId ? `run ${runId}` : null,
|
|
52
|
+
].filter(Boolean);
|
|
53
|
+
if (facts.length > 0)
|
|
54
|
+
lines.push(`Failed: ${facts.join(' · ')}`);
|
|
55
|
+
if (error) {
|
|
56
|
+
// Labelled with the JSON key, because a reader who needs this in a script
|
|
57
|
+
// needs the key more than they need the prose.
|
|
58
|
+
lines.push(`Error: ${error}`);
|
|
59
|
+
lines.push(' (`executionError` in --json)');
|
|
60
|
+
}
|
|
61
|
+
else if (isFailed) {
|
|
62
|
+
lines.push('Error: none recorded on the task.');
|
|
63
|
+
}
|
|
64
|
+
const ref = String(t.identifier ?? t.id ?? t._id ?? '<task-id>');
|
|
65
|
+
lines.push(`Per-run why: ${bin} tasks activity ${ref} --json (details.error, keyed by runId)`);
|
|
66
|
+
return lines;
|
|
67
|
+
}
|
|
11
68
|
class TasksShow extends base_command_1.BaseCommand {
|
|
12
69
|
static description = 'Show details for a single task (enriched)';
|
|
13
70
|
static examples = [
|
|
@@ -112,6 +169,10 @@ class TasksShow extends base_command_1.BaseCommand {
|
|
|
112
169
|
if (url)
|
|
113
170
|
this.log(`URL: ${url}`);
|
|
114
171
|
this.log(`Status: ${t.status ?? '-'}`);
|
|
172
|
+
// Immediately under Status, because "failed" is the row that raises the
|
|
173
|
+
// question and this is the answer to it.
|
|
174
|
+
for (const line of taskFailureLines(t, this.config.bin))
|
|
175
|
+
this.log(line);
|
|
115
176
|
this.log(`Executor: ${t.executionMode ?? 'legacy / inferred'}`);
|
|
116
177
|
this.log(`Priority: ${t.priority ?? '-'}`);
|
|
117
178
|
this.log(`Parent: ${t.parentId ?? '-'}`);
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
import { BaseCommand } from '../../base-command';
|
|
2
|
+
/**
|
|
3
|
+
* `<action> (<k=v ...>)`, or just the preview when there is nothing to add.
|
|
4
|
+
*
|
|
5
|
+
* Exported for `timeline.spec.ts`. `formatCell` is what `tasks activity`
|
|
6
|
+
* renders `details` with, so the two surfaces describe one entry the same way.
|
|
7
|
+
*/
|
|
8
|
+
export declare function withDetails(item: Record<string, unknown>): string;
|
|
2
9
|
export default class TasksTimeline extends BaseCommand {
|
|
3
10
|
static description: string;
|
|
4
11
|
static args: {
|
|
@@ -1,9 +1,22 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.withDetails = withDetails;
|
|
3
4
|
const core_1 = require("@oclif/core");
|
|
4
5
|
const data_provider_1 = require("@skrr-ai/data-provider");
|
|
5
6
|
const base_command_1 = require("../../base-command");
|
|
6
7
|
const format_1 = require("../../lib/format");
|
|
8
|
+
const tasks_1 = require("../../lib/tasks");
|
|
9
|
+
/**
|
|
10
|
+
* `<action> (<k=v ...>)`, or just the preview when there is nothing to add.
|
|
11
|
+
*
|
|
12
|
+
* Exported for `timeline.spec.ts`. `formatCell` is what `tasks activity`
|
|
13
|
+
* renders `details` with, so the two surfaces describe one entry the same way.
|
|
14
|
+
*/
|
|
15
|
+
function withDetails(item) {
|
|
16
|
+
const preview = String(item.preview ?? '-');
|
|
17
|
+
const details = (0, format_1.formatCell)(item.details, '');
|
|
18
|
+
return details ? `${preview} (${details})` : preview;
|
|
19
|
+
}
|
|
7
20
|
class TasksTimeline extends base_command_1.BaseCommand {
|
|
8
21
|
static description = 'Show the unified task timeline: work log, comments, lifecycle, and execution';
|
|
9
22
|
static args = {
|
|
@@ -13,7 +26,14 @@ class TasksTimeline extends base_command_1.BaseCommand {
|
|
|
13
26
|
json: core_1.Flags.boolean({ description: 'Output JSON' }),
|
|
14
27
|
limit: core_1.Flags.integer({ default: 50 }),
|
|
15
28
|
cursor: core_1.Flags.string(),
|
|
16
|
-
kind
|
|
29
|
+
// `--kind` names a typed WORK-EVENT kind, not one of the timeline's four
|
|
30
|
+
// sources. Undeclared and undescribed, it read like a source filter and
|
|
31
|
+
// silently accepted anything; the server now refuses an unknown kind, and
|
|
32
|
+
// declaring the options here refuses a typo before it costs a round trip.
|
|
33
|
+
kind: core_1.Flags.string({
|
|
34
|
+
description: 'Show only work events of this kind (excludes comments, lifecycle, execution)',
|
|
35
|
+
options: [...tasks_1.TASK_WORK_EVENT_KINDS],
|
|
36
|
+
}),
|
|
17
37
|
run: core_1.Flags.string(),
|
|
18
38
|
actor: core_1.Flags.string({ options: ['user', 'agent', 'system'] }),
|
|
19
39
|
};
|
|
@@ -41,9 +61,20 @@ class TasksTimeline extends base_command_1.BaseCommand {
|
|
|
41
61
|
time: String(item.occurredAt ?? '-')
|
|
42
62
|
.replace('T', ' ')
|
|
43
63
|
.slice(0, 19),
|
|
44
|
-
|
|
64
|
+
// A work event's own kind is the thing you came to see. Showing the
|
|
65
|
+
// source alone collapsed blocker, decision, checkpoint and evidence
|
|
66
|
+
// into one indistinguishable `work_event`.
|
|
67
|
+
source: item.eventKind
|
|
68
|
+
? `${String(item.kind ?? '-')}:${String(item.eventKind)}`
|
|
69
|
+
: String(item.kind ?? '-'),
|
|
45
70
|
actor: String(item.actor?.kind ?? '-'),
|
|
46
|
-
|
|
71
|
+
// A lifecycle row's preview is the bare action, so `status_changed`
|
|
72
|
+
// appeared three times in a row without ever naming a status. The
|
|
73
|
+
// answer is in `details` (`status=blocked`), which `tasks activity`
|
|
74
|
+
// has always shown off the same entries. Appended rather than
|
|
75
|
+
// substituted: the action is still what happened, `details` is what
|
|
76
|
+
// it happened to.
|
|
77
|
+
detail: (0, format_1.truncate)(withDetails(item), 90),
|
|
47
78
|
run: String(item.runId ?? '-'),
|
|
48
79
|
})), [
|
|
49
80
|
{ key: 'time', header: 'TIME' },
|
|
@@ -16,6 +16,26 @@ export default class TasksUpdate extends BaseCommand {
|
|
|
16
16
|
'start-date': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
17
17
|
'due-date': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
18
18
|
estimate: import("@oclif/core/lib/interfaces").OptionFlag<number | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
19
|
+
/**
|
|
20
|
+
* Declared, hidden, and refused — on purpose. (S4 dogfood finding)
|
|
21
|
+
*
|
|
22
|
+
* `--status` is the single most likely flag a person types at `tasks
|
|
23
|
+
* update`, and undeclared it produced oclif's `Nonexistent flag: --status`
|
|
24
|
+
* followed by a ~160-line dump of this command's help. The answer is in
|
|
25
|
+
* that dump — this command's own description says status changes go through
|
|
26
|
+
* `tasks move` — buried under thirty other flags.
|
|
27
|
+
*
|
|
28
|
+
* Declaring it routes `--status` past the parser and into
|
|
29
|
+
* `taskUpdateMutationGuard`, which already refuses a `status` key arriving
|
|
30
|
+
* via `--from-json` with the exact sentence naming `tasks move`. So the two
|
|
31
|
+
* ways of asking for the same wrong thing now get the same right answer,
|
|
32
|
+
* the same `TASK_STATUS_REQUIRES_MOVE` code in `--json`, and no help dump —
|
|
33
|
+
* and the copy has ONE home rather than a second parallel string.
|
|
34
|
+
*
|
|
35
|
+
* `hidden` because it is not a capability: help must not advertise a flag
|
|
36
|
+
* whose only behaviour is to explain itself.
|
|
37
|
+
*/
|
|
38
|
+
status: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
19
39
|
'duplicate-of': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
20
40
|
goal: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
21
41
|
cycle: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|