@skrr-ai/cli 0.1.37 → 0.1.38
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/bin/dev-fallback.js +167 -4
- package/dist/base-command.js +46 -2
- package/dist/commands/agents/actions/create.js +18 -7
- package/dist/commands/agents/actions/delete.d.ts +1 -0
- package/dist/commands/agents/actions/delete.js +7 -0
- package/dist/commands/agents/actions/update.js +5 -3
- package/dist/commands/agents/api-actions/add.js +5 -2
- package/dist/commands/agents/api-actions/index.d.ts +11 -0
- package/dist/commands/agents/api-actions/index.js +27 -1
- package/dist/commands/agents/avatar/default/index.d.ts +11 -0
- package/dist/commands/agents/avatar/default/index.js +17 -0
- package/dist/commands/agents/avatar/default/set.d.ts +2 -1
- package/dist/commands/agents/avatar/default/set.js +32 -7
- package/dist/commands/agents/avatar/upload.js +7 -1
- package/dist/commands/agents/categories/delete.js +8 -0
- package/dist/commands/agents/categories/index.d.ts +21 -5
- package/dist/commands/agents/categories/index.js +68 -15
- package/dist/commands/agents/chat.d.ts +21 -0
- package/dist/commands/agents/chat.js +94 -7
- package/dist/commands/agents/create.js +11 -4
- package/dist/commands/agents/list.d.ts +15 -0
- package/dist/commands/agents/list.js +31 -1
- package/dist/commands/agents/scene-background/upload.d.ts +30 -0
- package/dist/commands/agents/scene-background/upload.js +97 -0
- package/dist/commands/agents/show.js +13 -0
- package/dist/commands/agents/tools/calls.d.ts +23 -5
- package/dist/commands/agents/tools/calls.js +81 -18
- package/dist/commands/agents/update.js +15 -5
- package/dist/commands/code/{oversky-agent.d.ts → skrr-agent.d.ts} +3 -3
- package/dist/commands/code/{oversky-agent.js → skrr-agent.js} +11 -8
- package/dist/commands/convos/messages.d.ts +13 -0
- package/dist/commands/convos/messages.js +40 -16
- package/dist/commands/daemon/index.js +8 -1
- package/dist/commands/daemon/logs.d.ts +26 -0
- package/dist/commands/daemon/logs.js +56 -0
- package/dist/commands/daemon/uninstall.js +1 -1
- package/dist/commands/followups/act.js +3 -2
- package/dist/commands/followups/cancel.d.ts +6 -3
- package/dist/commands/followups/cancel.js +16 -6
- package/dist/commands/followups/observability.d.ts +23 -0
- package/dist/commands/followups/observability.js +114 -0
- package/dist/commands/followups/reschedule.js +1 -1
- package/dist/commands/followups/resolve.js +3 -3
- package/dist/commands/followups/show.js +2 -1
- package/dist/commands/followups/watch.js +9 -5
- package/dist/commands/harnesses/forget.js +28 -0
- package/dist/commands/harnesses/install.d.ts +41 -0
- package/dist/commands/harnesses/install.js +165 -0
- package/dist/commands/harnesses/installers.d.ts +45 -0
- package/dist/commands/harnesses/installers.js +155 -0
- package/dist/commands/harnesses/list.d.ts +82 -1
- package/dist/commands/harnesses/list.js +104 -12
- package/dist/commands/harnesses/models.js +10 -1
- package/dist/commands/harnesses/show.d.ts +15 -0
- package/dist/commands/harnesses/show.js +44 -3
- package/dist/commands/harnesses/update.js +1 -1
- package/dist/commands/harnesses/usage.d.ts +25 -0
- package/dist/commands/harnesses/usage.js +24 -2
- package/dist/commands/instructions/status.js +13 -1
- package/dist/commands/machines/dedicated/catalog.js +4 -15
- package/dist/commands/machines/dedicated/create.d.ts +8 -0
- package/dist/commands/machines/dedicated/create.js +58 -1
- package/dist/commands/machines/dedicated/destroy.js +18 -4
- package/dist/commands/machines/dedicated/list.js +9 -6
- package/dist/commands/machines/hosted/list.js +12 -0
- package/dist/commands/machines/list.js +11 -0
- package/dist/commands/machines/show.d.ts +1 -0
- package/dist/commands/machines/show.js +40 -11
- package/dist/commands/profiles/list.d.ts +23 -0
- package/dist/commands/profiles/list.js +96 -0
- package/dist/commands/search/skills.d.ts +11 -1
- package/dist/commands/search/skills.js +26 -8
- package/dist/commands/skills/details.d.ts +2 -0
- package/dist/commands/skills/details.js +24 -1
- package/dist/commands/skills/import-as-actions.d.ts +32 -0
- package/dist/commands/skills/import-as-actions.js +175 -0
- package/dist/commands/skills/install.d.ts +2 -0
- package/dist/commands/skills/install.js +24 -1
- package/dist/commands/skills/invocable.js +6 -2
- package/dist/commands/skills/local.d.ts +40 -0
- package/dist/commands/skills/local.js +160 -0
- package/dist/commands/skills/search.d.ts +11 -1
- package/dist/commands/skills/search.js +29 -8
- package/dist/commands/spaces/actions/add.js +23 -5
- package/dist/commands/spaces/actions/available.js +3 -0
- package/dist/commands/spaces/actions/list.d.ts +5 -0
- package/dist/commands/spaces/actions/list.js +49 -3
- package/dist/commands/spaces/actions/run.d.ts +28 -0
- package/dist/commands/spaces/actions/run.js +146 -0
- package/dist/commands/spaces/actions/update.js +27 -5
- package/dist/commands/spaces/label-summaries.d.ts +27 -0
- package/dist/commands/spaces/label-summaries.js +64 -0
- package/dist/commands/spaces/task-rollups.d.ts +5 -0
- package/dist/commands/spaces/task-rollups.js +15 -4
- package/dist/commands/spaces/workflows/lifecycle-diagnostics.js +63 -3
- package/dist/commands/tasks/actions/attach.js +32 -7
- package/dist/commands/tasks/complete.js +13 -0
- package/dist/commands/tasks/create.js +1 -1
- package/dist/commands/tasks/events/append.js +29 -6
- package/dist/commands/tasks/expectations/assess.d.ts +1 -0
- package/dist/commands/tasks/expectations/assess.js +43 -5
- package/dist/commands/tasks/expectations.js +77 -16
- package/dist/commands/tasks/follow-up.js +2 -2
- package/dist/commands/tasks/lifecycle/definitions.js +21 -2
- package/dist/commands/tasks/lifecycle/detach.d.ts +7 -0
- package/dist/commands/tasks/lifecycle/detach.js +43 -3
- package/dist/commands/tasks/lifecycle/migrate.d.ts +2 -0
- package/dist/commands/tasks/lifecycle/migrate.js +42 -3
- package/dist/commands/tasks/lifecycle/reopen.d.ts +2 -0
- package/dist/commands/tasks/lifecycle/reopen.js +40 -3
- package/dist/commands/tasks/lifecycle/transition.js +33 -2
- package/dist/commands/tasks/list.js +16 -9
- package/dist/commands/tasks/move.js +11 -13
- package/dist/commands/tasks/review-queue.d.ts +15 -0
- package/dist/commands/tasks/review-queue.js +180 -31
- package/dist/commands/tasks/show.d.ts +11 -0
- package/dist/commands/tasks/show.js +105 -12
- package/dist/commands/tasks/trust.js +7 -2
- package/dist/commands/tasks/update.js +46 -10
- package/dist/commands/tasks/updates/add.js +1 -1
- package/dist/commands/token/list.d.ts +22 -0
- package/dist/commands/token/list.js +68 -0
- package/dist/commands/token/revoke.d.ts +23 -0
- package/dist/commands/token/revoke.js +53 -0
- package/dist/lib/agent-actions.d.ts +57 -2
- package/dist/lib/agent-actions.js +113 -21
- package/dist/lib/agent-pin-mute.d.ts +10 -5
- package/dist/lib/agent-pin-mute.js +17 -4
- package/dist/lib/agent-visual-refs.d.ts +27 -0
- package/dist/lib/agent-visual-refs.js +127 -2
- package/dist/lib/agentic-stream.d.ts +6 -0
- package/dist/lib/agentic-stream.js +34 -3
- package/dist/lib/assignee-resolver.d.ts +1 -1
- package/dist/lib/assignee-resolver.js +8 -0
- package/dist/lib/bulk-task-targets.d.ts +12 -0
- package/dist/lib/bulk-task-targets.js +34 -1
- package/dist/lib/cli-installers.d.ts +130 -0
- package/dist/lib/cli-installers.js +145 -0
- package/dist/lib/command-miss.js +55 -7
- package/dist/lib/dedicated-lease-command.d.ts +13 -0
- package/dist/lib/dedicated-lease-command.js +22 -1
- package/dist/lib/dedicated-machines.d.ts +95 -1
- package/dist/lib/dedicated-machines.js +236 -2
- package/dist/lib/failed-lookup.d.ts +3 -0
- package/dist/lib/failed-lookup.js +40 -0
- package/dist/lib/first-party-harness-agent.js +9 -2
- package/dist/lib/first-party-harness-doctor.d.ts +15 -0
- package/dist/lib/first-party-harness-doctor.js +126 -27
- package/dist/lib/followups.d.ts +14 -6
- package/dist/lib/followups.js +31 -6
- package/dist/lib/harnesses.d.ts +8 -0
- package/dist/lib/instruction-input.d.ts +19 -1
- package/dist/lib/instruction-input.js +52 -1
- package/dist/lib/label-scope.d.ts +15 -0
- package/dist/lib/label-scope.js +25 -5
- package/dist/lib/local-skills.d.ts +98 -0
- package/dist/lib/local-skills.js +195 -0
- package/dist/lib/machines.d.ts +10 -10
- package/dist/lib/machines.js +23 -1
- package/dist/lib/search-query.d.ts +19 -0
- package/dist/lib/search-query.js +35 -0
- package/dist/lib/space-custom-actions.d.ts +72 -0
- package/dist/lib/space-custom-actions.js +117 -0
- package/dist/lib/space-resolver.d.ts +1 -21
- package/dist/lib/space-resolver.js +19 -10
- package/dist/lib/task-closure.d.ts +3 -2
- package/dist/lib/task-closure.js +18 -20
- package/dist/lib/task-lifecycle-output.js +117 -9
- package/dist/lib/task-ref-resolver.d.ts +1 -17
- package/dist/lib/task-ref-resolver.js +18 -8
- package/dist/lib/tasks.js +9 -3
- package/dist/node_modules/@skrr-ai/data-provider/index.js +5076 -4947
- package/oclif.manifest.json +26635 -25649
- package/package.json +9 -3
|
@@ -5,6 +5,8 @@ exports.parseJsonObjectFlag = parseJsonObjectFlag;
|
|
|
5
5
|
exports.fetchAgentForActionEdit = fetchAgentForActionEdit;
|
|
6
6
|
exports.getAgentActions = getAgentActions;
|
|
7
7
|
exports.findAgentAction = findAgentAction;
|
|
8
|
+
exports.storedActionOrLocal = storedActionOrLocal;
|
|
9
|
+
exports.appendAgentAction = appendAgentAction;
|
|
8
10
|
exports.updateAgentActions = updateAgentActions;
|
|
9
11
|
exports.makePromptAction = makePromptAction;
|
|
10
12
|
exports.makeBuiltinAction = makeBuiltinAction;
|
|
@@ -17,22 +19,13 @@ exports.renderAction = renderAction;
|
|
|
17
19
|
const node_crypto_1 = require("node:crypto");
|
|
18
20
|
const data_provider_1 = require("@skrr-ai/data-provider");
|
|
19
21
|
const format_1 = require("./format");
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
'task_mark_done',
|
|
28
|
-
'task_assign',
|
|
29
|
-
'goal_create',
|
|
30
|
-
'goal_mark_done',
|
|
31
|
-
'smart_schedule_plan',
|
|
32
|
-
'smart_schedule_delta_watch',
|
|
33
|
-
];
|
|
34
|
-
const _builtinActionTypesAreExhaustive = true;
|
|
35
|
-
void _builtinActionTypesAreExhaustive;
|
|
22
|
+
const label_ref_1 = require("./label-ref");
|
|
23
|
+
const api_fetch_1 = require("./api-fetch");
|
|
24
|
+
// The vocabulary is owned by `@skrr-ai/data-provider`, which also proves it
|
|
25
|
+
// complete against `BuiltinActionType`. This file used to hold a second copy
|
|
26
|
+
// with its own guard (OSK-9668).
|
|
27
|
+
var data_provider_2 = require("@skrr-ai/data-provider");
|
|
28
|
+
Object.defineProperty(exports, "BUILTIN_ACTION_TYPES", { enumerable: true, get: function () { return data_provider_2.BUILTIN_ACTION_TYPES; } });
|
|
36
29
|
exports.ACTION_TARGET_TYPES = [
|
|
37
30
|
'group-chats',
|
|
38
31
|
'spaces',
|
|
@@ -66,6 +59,88 @@ function getAgentActions(agent) {
|
|
|
66
59
|
function findAgentAction(agent, actionId) {
|
|
67
60
|
return getAgentActions(agent).find((action) => action.id === actionId);
|
|
68
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* The row as the SERVER stored it, not as we sent it.
|
|
64
|
+
*
|
|
65
|
+
* `updateAgentActions` returns the updated Agent, and both editors used to
|
|
66
|
+
* discard it and print the object they had just built — so the output was the
|
|
67
|
+
* input, restated. That is not a cosmetic difference: the server normalizes
|
|
68
|
+
* `agentActions` through an allowlist before persisting, so a field it drops is
|
|
69
|
+
* echoed back by the CLI as though it had been saved. A `label` scope and
|
|
70
|
+
* `outcome: durable` were both silently discarded that way and the CLI reported
|
|
71
|
+
* success with the values the caller had typed (OSK-9628).
|
|
72
|
+
*
|
|
73
|
+
* Falling back to the local object keeps the command working if the write
|
|
74
|
+
* succeeds but the response is unexpectedly shaped; it cannot make the output
|
|
75
|
+
* wrong in a way it was not already.
|
|
76
|
+
*/
|
|
77
|
+
function storedActionOrLocal(agent, local) {
|
|
78
|
+
if (!agent)
|
|
79
|
+
return local;
|
|
80
|
+
return findAgentAction(agent, local.id) ?? local;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Append ONE action atomically.
|
|
84
|
+
*
|
|
85
|
+
* `agents actions create` used to read the agent, append in memory and write
|
|
86
|
+
* the whole library back through `updateAgentActions`. Two concurrent creates
|
|
87
|
+
* therefore each read the same library and the later write erased the earlier
|
|
88
|
+
* action — six parallel creates all printed success and left one (OSK-9673).
|
|
89
|
+
* This endpoint appends with a single conditional `$push` on the server, so
|
|
90
|
+
* concurrent creates all land, the cap is enforced at the instant of the write,
|
|
91
|
+
* and a retried request whose id already landed returns `created: false`
|
|
92
|
+
* instead of adding a second copy. The client-generated id is what makes that
|
|
93
|
+
* retry safe, so it is sent rather than left for the server to assign.
|
|
94
|
+
*
|
|
95
|
+
* Update and delete still use `updateAgentActions` and still read-modify-write.
|
|
96
|
+
*/
|
|
97
|
+
async function appendAgentAction(agentId, action) {
|
|
98
|
+
try {
|
|
99
|
+
return await (0, api_fetch_1.apiFetch)(`/api/agents/${encodeURIComponent(agentId)}/agent-actions`, { method: 'POST', body: { action } });
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
if (!isMissingRoute(err))
|
|
103
|
+
throw err;
|
|
104
|
+
return appendAgentActionByRewrite(agentId, action);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* The server predates the append route.
|
|
109
|
+
*
|
|
110
|
+
* A released CLI is not guaranteed a server that has every route it knows —
|
|
111
|
+
* a self-hosted instance, or production mid-rollout — and without this,
|
|
112
|
+
* `agents actions create` against such a server simply failed. A missing ROUTE
|
|
113
|
+
* answers 404 with no `code` and a "No API route matches" message; a missing
|
|
114
|
+
* AGENT answers 404 with `code: 'agent_not_found'`, which must NOT fall back.
|
|
115
|
+
*/
|
|
116
|
+
function isMissingRoute(err) {
|
|
117
|
+
return (err instanceof api_fetch_1.ApiFetchError &&
|
|
118
|
+
err.status === 404 &&
|
|
119
|
+
!err.code &&
|
|
120
|
+
typeof err.body === 'string' &&
|
|
121
|
+
err.body.includes('No API route matches'));
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* The pre-append behaviour, kept only for servers without the route: read the
|
|
125
|
+
* library, append in memory, write it back. It is NOT safe under concurrency —
|
|
126
|
+
* that is the defect the route exists to fix — but it is correct for a single
|
|
127
|
+
* writer, which beats refusing to create anything.
|
|
128
|
+
*/
|
|
129
|
+
async function appendAgentActionByRewrite(agentId, action) {
|
|
130
|
+
const agent = await fetchAgentForActionEdit(agentId);
|
|
131
|
+
const actions = getAgentActions(agent);
|
|
132
|
+
const earlier = actions.find((candidate) => candidate.id === action.id);
|
|
133
|
+
if (earlier) {
|
|
134
|
+
return { action: earlier, created: false, total: actions.length };
|
|
135
|
+
}
|
|
136
|
+
const placed = typeof action.order === 'number' ? action : { ...action, order: nextActionOrder(actions) };
|
|
137
|
+
const saved = await updateAgentActions(agentId, [...actions, placed]);
|
|
138
|
+
return {
|
|
139
|
+
action: storedActionOrLocal(saved, placed),
|
|
140
|
+
created: true,
|
|
141
|
+
total: getAgentActions(saved).length || actions.length + 1,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
69
144
|
async function updateAgentActions(agentId, actions) {
|
|
70
145
|
return data_provider_1.dataService.updateAgent({
|
|
71
146
|
agent_id: agentId,
|
|
@@ -113,11 +188,27 @@ exports.AGENT_ACTION_OUTCOME_CHOICES = ['ephemeral', 'durable'];
|
|
|
113
188
|
* `--scope space --space s1` twice is the kind of ceremony people skip and then
|
|
114
189
|
* wonder why the action is everywhere. An explicit `--scope general` clears it.
|
|
115
190
|
*/
|
|
116
|
-
|
|
117
|
-
|
|
191
|
+
/**
|
|
192
|
+
* `--label` accepts an id OR a name, like every other `--label` in the CLI.
|
|
193
|
+
*
|
|
194
|
+
* It used to take the value verbatim as a `labelId`. A name was therefore
|
|
195
|
+
* stored as though it were an id, matched no space's `labelIds`, and produced
|
|
196
|
+
* an action offered NOWHERE — silently, with the create reporting success. That
|
|
197
|
+
* is exactly the "an action that disappears reads as deletion" hazard the label
|
|
198
|
+
* tier was designed around, arriving through the CLI instead of through a card
|
|
199
|
+
* that forgot to pass its labels. An unknown name was accepted too, so a typo
|
|
200
|
+
* was indistinguishable from a working scope.
|
|
201
|
+
*
|
|
202
|
+
* `resolveLabelRefs` is the same resolver `spaces create --label` uses: it
|
|
203
|
+
* short-circuits when every value already looks like an id (so a scripted
|
|
204
|
+
* caller pays no request), refuses an unknown name with the near misses named,
|
|
205
|
+
* and refuses an ambiguous one rather than guessing.
|
|
206
|
+
*/
|
|
207
|
+
async function resolveAgentActionScopeFlags(flags) {
|
|
208
|
+
const labelRefs = (flags.label ?? []).map((value) => value.trim()).filter(Boolean);
|
|
118
209
|
const spaceId = flags.space?.trim();
|
|
119
210
|
const declared = flags.scope?.trim();
|
|
120
|
-
const kind = declared ?? (spaceId ? 'space' :
|
|
211
|
+
const kind = declared ?? (spaceId ? 'space' : labelRefs.length ? 'label' : undefined);
|
|
121
212
|
if (!kind)
|
|
122
213
|
return undefined;
|
|
123
214
|
if (kind === 'general')
|
|
@@ -129,8 +220,9 @@ function resolveAgentActionScopeFlags(flags) {
|
|
|
129
220
|
return { kind: 'space', spaceId, ...(spaceTitle ? { spaceTitle } : {}) };
|
|
130
221
|
}
|
|
131
222
|
if (kind === 'label') {
|
|
132
|
-
if (!
|
|
133
|
-
throw new Error('--scope label requires at least one --label <label-id>.');
|
|
223
|
+
if (!labelRefs.length)
|
|
224
|
+
throw new Error('--scope label requires at least one --label <label-id-or-name>.');
|
|
225
|
+
const labelIds = await (0, label_ref_1.resolveLabelRefs)(labelRefs, { applicableTo: 'space' });
|
|
134
226
|
return { kind: 'label', labelIds };
|
|
135
227
|
}
|
|
136
228
|
throw new Error(`--scope must be one of ${exports.AGENT_ACTION_SCOPE_CHOICES.join(', ')}.`);
|
|
@@ -8,11 +8,16 @@
|
|
|
8
8
|
* a command in a script, where the outcome would depend on state the script
|
|
9
9
|
* never read.
|
|
10
10
|
*
|
|
11
|
-
* So the desired state is
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
11
|
+
* So the desired state is sent, and the endpoint now takes one. It used to be
|
|
12
|
+
* read-then-flip-if-different, which made each command idempotent when run
|
|
13
|
+
* alone and inverted under concurrency: six `skrr agents pin` calls that each
|
|
14
|
+
* read "unpinned" sent six flips, and six flips from false is false — pinning an
|
|
15
|
+
* agent six times left it unpinned. That was never fixable from out here, as the
|
|
16
|
+
* previous version of this comment said; it needed the endpoint to accept a
|
|
17
|
+
* desired state, which is what `pinned`/`muted` in the body now is.
|
|
18
|
+
*
|
|
19
|
+
* `changed` comes from the server, which is the only place that can compare the
|
|
20
|
+
* old value with the new one without a second race.
|
|
16
21
|
*/
|
|
17
22
|
export type AgentFlag = 'pin' | 'mute';
|
|
18
23
|
export interface FlagChange {
|
|
@@ -5,13 +5,26 @@ const data_provider_1 = require("@skrr-ai/data-provider");
|
|
|
5
5
|
async function setAgentFlag(agentId, flag, desired) {
|
|
6
6
|
const current = await data_provider_1.dataService.getAgentPreferences(agentId);
|
|
7
7
|
const held = flag === 'pin' ? current.preferences?.isPinned : current.preferences?.isMuted;
|
|
8
|
+
// Nothing to send. True on every server version, and the one case where a
|
|
9
|
+
// write would be pure risk.
|
|
8
10
|
if (Boolean(held) === desired) {
|
|
9
11
|
return { value: desired, changed: false };
|
|
10
12
|
}
|
|
13
|
+
// The read stays, and it is NOT what the race was. The race was read-then-
|
|
14
|
+
// FLIP: the write depended on what was read, so two readers of the same value
|
|
15
|
+
// sent two flips. The write now carries the desired state, so any number of
|
|
16
|
+
// them land on it — the read only decides whether to speak at all.
|
|
17
|
+
//
|
|
18
|
+
// It also keeps this honest against a server that has not been deployed yet.
|
|
19
|
+
// A build without the desired-state branch ignores the body and flips; from a
|
|
20
|
+
// state we have just confirmed differs, one flip still reaches `desired`, so
|
|
21
|
+
// the reported outcome is true either way. Reporting the server's `changed`
|
|
22
|
+
// alone was not: an old server omits it, `?? false` read that as "nothing to
|
|
23
|
+
// do", and `skrr agents pin` said so while flipping the bit.
|
|
11
24
|
if (flag === 'pin') {
|
|
12
|
-
const result = await data_provider_1.dataService.toggleAgentPin(agentId);
|
|
13
|
-
return { value: result.isPinned, changed: true };
|
|
25
|
+
const result = await data_provider_1.dataService.toggleAgentPin(agentId, desired);
|
|
26
|
+
return { value: result.isPinned, changed: result.changed ?? true };
|
|
14
27
|
}
|
|
15
|
-
const result = await data_provider_1.dataService.toggleAgentMute(agentId);
|
|
16
|
-
return { value: result.isMuted, changed: true };
|
|
28
|
+
const result = await data_provider_1.dataService.toggleAgentMute(agentId, desired);
|
|
29
|
+
return { value: result.isMuted, changed: result.changed ?? true };
|
|
17
30
|
}
|
|
@@ -1,6 +1,33 @@
|
|
|
1
1
|
import type { AgentAvatarRef, AgentSceneBackground } from '@skrr-ai/data-provider';
|
|
2
|
+
/**
|
|
3
|
+
* The `--scene-background` help, shared by `agents create` and `agents update`.
|
|
4
|
+
*
|
|
5
|
+
* It named three forms and no preset ids, while the refusal it led to named a
|
|
6
|
+
* fourth form (JSON) and listed all the ids — so the only way to learn the valid
|
|
7
|
+
* values was to type a wrong one on purpose. `--avatar`, one flag away, already
|
|
8
|
+
* did this right. Built from the shipped catalog so it cannot fall behind it.
|
|
9
|
+
*/
|
|
10
|
+
export declare const SCENE_BACKGROUND_FLAG_HELP: string;
|
|
11
|
+
export declare function imageUploadMimeType(filePath: string): string | undefined;
|
|
2
12
|
/** Preset ids this build ships, for an error message that can be acted on. */
|
|
3
13
|
export declare function avatarPresetIds(): string[];
|
|
4
14
|
export declare function sceneBackgroundPresetIds(): string[];
|
|
5
15
|
export declare function parseAvatarRefFlag(value: string): AgentAvatarRef;
|
|
6
16
|
export declare function parseSceneBackgroundFlag(value: string): AgentSceneBackground;
|
|
17
|
+
/**
|
|
18
|
+
* The avatar line for `agents show` text output.
|
|
19
|
+
*
|
|
20
|
+
* `agents show` printed no avatar at all, so its output was byte-identical before
|
|
21
|
+
* and after `agents update --avatar preset:studio-halo`: "has the Halo preset"
|
|
22
|
+
* and "has no face whatsoever" could not be told apart without `--json` and
|
|
23
|
+
* knowing which raw field to decode. The coercion `--avatar` performs is the one
|
|
24
|
+
* thing a reader would want confirmed, and it was the one thing hidden.
|
|
25
|
+
*
|
|
26
|
+
* Rendered in the same shorthand `--avatar` accepts, so what is printed is what
|
|
27
|
+
* can be typed back. An ABSENT avatar is kept apart from an explicit
|
|
28
|
+
* `{type:'default'}`: both show the platform logo, but only the absent one was
|
|
29
|
+
* never chosen, and that is exactly what `agents avatar default apply` rewrites.
|
|
30
|
+
*/
|
|
31
|
+
export declare function agentAvatarLine(raw: unknown): string;
|
|
32
|
+
/** The scene-background line, in the shorthand `--scene-background` accepts. */
|
|
33
|
+
export declare function agentSceneBackgroundLine(raw: unknown): string;
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SCENE_BACKGROUND_FLAG_HELP = void 0;
|
|
4
|
+
exports.imageUploadMimeType = imageUploadMimeType;
|
|
3
5
|
exports.avatarPresetIds = avatarPresetIds;
|
|
4
6
|
exports.sceneBackgroundPresetIds = sceneBackgroundPresetIds;
|
|
5
7
|
exports.parseAvatarRefFlag = parseAvatarRefFlag;
|
|
6
8
|
exports.parseSceneBackgroundFlag = parseSceneBackgroundFlag;
|
|
9
|
+
exports.agentAvatarLine = agentAvatarLine;
|
|
10
|
+
exports.agentSceneBackgroundLine = agentSceneBackgroundLine;
|
|
7
11
|
/**
|
|
8
12
|
* The two "how does this agent look" fields, parsed from a flag.
|
|
9
13
|
*
|
|
@@ -35,6 +39,37 @@ exports.parseSceneBackgroundFlag = parseSceneBackgroundFlag;
|
|
|
35
39
|
* "your avatar silently became someone else's" rather than a parse error.
|
|
36
40
|
*/
|
|
37
41
|
const data_provider_1 = require("@skrr-ai/data-provider");
|
|
42
|
+
/**
|
|
43
|
+
* The `--scene-background` help, shared by `agents create` and `agents update`.
|
|
44
|
+
*
|
|
45
|
+
* It named three forms and no preset ids, while the refusal it led to named a
|
|
46
|
+
* fourth form (JSON) and listed all the ids — so the only way to learn the valid
|
|
47
|
+
* values was to type a wrong one on purpose. `--avatar`, one flag away, already
|
|
48
|
+
* did this right. Built from the shipped catalog so it cannot fall behind it.
|
|
49
|
+
*/
|
|
50
|
+
exports.SCENE_BACKGROUND_FLAG_HELP = 'Backdrop for the 3D view: default, preset:<id>, image:<filepath>@<source>, or a JSON ' +
|
|
51
|
+
`object such as {"type":"preset","id":"midnight"}. Presets: ${data_provider_1.AGENT_SCENE_BACKGROUND_PRESETS.map((preset) => preset.id).join(', ')}. For your own photo use \`skrr agents scene-background upload\` — ` +
|
|
52
|
+
'<filepath> is the storage path that command produces, not a file on disk.';
|
|
53
|
+
/**
|
|
54
|
+
* The MIME type to send an image upload as, from its file extension.
|
|
55
|
+
*
|
|
56
|
+
* The upload routes gate on the multipart part's type, and a part built as
|
|
57
|
+
* `new Blob([bytes])` has none, so it arrives as `application/octet-stream` and
|
|
58
|
+
* is refused whatever its bytes are. `agents avatar upload` shipped that way and
|
|
59
|
+
* could not upload any file (OSK-9841). Returns undefined for anything that is
|
|
60
|
+
* not an image type the routes accept, so a caller can refuse locally.
|
|
61
|
+
*/
|
|
62
|
+
const UPLOAD_IMAGE_TYPES = {
|
|
63
|
+
'.png': 'image/png',
|
|
64
|
+
'.jpg': 'image/jpeg',
|
|
65
|
+
'.jpeg': 'image/jpeg',
|
|
66
|
+
'.webp': 'image/webp',
|
|
67
|
+
'.gif': 'image/gif',
|
|
68
|
+
};
|
|
69
|
+
function imageUploadMimeType(filePath) {
|
|
70
|
+
const dot = filePath.lastIndexOf('.');
|
|
71
|
+
return dot < 0 ? undefined : UPLOAD_IMAGE_TYPES[filePath.slice(dot).toLowerCase()];
|
|
72
|
+
}
|
|
38
73
|
/** `image:path/to.png@s3` — the `@` is the LAST one, so a path may contain `@`. */
|
|
39
74
|
function parseImage(rest, flag) {
|
|
40
75
|
const at = rest.lastIndexOf('@');
|
|
@@ -62,6 +97,43 @@ function parseJsonObject(value) {
|
|
|
62
97
|
return undefined;
|
|
63
98
|
}
|
|
64
99
|
}
|
|
100
|
+
/**
|
|
101
|
+
* Hold a JSON value to the same rules as the shorthand, for the shapes the
|
|
102
|
+
* grammar knows.
|
|
103
|
+
*
|
|
104
|
+
* The JSON form used to be returned unchecked, so the one form with no worked
|
|
105
|
+
* example was also the one with no local validation: `{"type":"preset"}` went to
|
|
106
|
+
* the server and came back as a raw zod dump
|
|
107
|
+
* (`sceneBackground.id: Invalid input: expected string, received undefined`),
|
|
108
|
+
* while `preset:` with no id got a sentence. A KNOWN `type` is now validated here
|
|
109
|
+
* exactly as its shorthand is. An unknown `type` still passes through untouched,
|
|
110
|
+
* because letting a shape this grammar has not learned reach the server is the
|
|
111
|
+
* reason the escape hatch exists.
|
|
112
|
+
*/
|
|
113
|
+
function checkJsonRef(json, flag, shorthand) {
|
|
114
|
+
const type = json.type;
|
|
115
|
+
const str = (value) => (typeof value === 'string' ? value : '');
|
|
116
|
+
if (type === 'default')
|
|
117
|
+
return;
|
|
118
|
+
if (type === 'preset' || type === 'asset') {
|
|
119
|
+
if (!str(json.id)) {
|
|
120
|
+
throw new Error(`${flag}: a JSON value of type "${type}" needs a string "id" — for example ` +
|
|
121
|
+
`{"type":"${type}","id":"<id>"}, or the shorthand ${type}:<id>.`);
|
|
122
|
+
}
|
|
123
|
+
shorthand(`${type}:${str(json.id)}`);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
if (type === 'image') {
|
|
127
|
+
if (!str(json.filepath) || !str(json.source)) {
|
|
128
|
+
throw new Error(`${flag}: a JSON value of type "image" needs string "filepath" and "source" — ` +
|
|
129
|
+
'for example {"type":"image","filepath":"<storage path>","source":"s3"}.');
|
|
130
|
+
}
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
if (typeof type !== 'string' || !type) {
|
|
134
|
+
throw new Error(`${flag}: a JSON value needs a string "type" (default, preset, image, …).`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
65
137
|
/** Preset ids this build ships, for an error message that can be acted on. */
|
|
66
138
|
function avatarPresetIds() {
|
|
67
139
|
return data_provider_1.AGENT_AVATAR_PRESETS.map((preset) => preset.id);
|
|
@@ -82,8 +154,10 @@ function someOf(ids, limit = 6) {
|
|
|
82
154
|
}
|
|
83
155
|
function parseAvatarRefFlag(value) {
|
|
84
156
|
const json = parseJsonObject(value);
|
|
85
|
-
if (json)
|
|
157
|
+
if (json) {
|
|
158
|
+
checkJsonRef(json, '--avatar', parseAvatarRefFlag);
|
|
86
159
|
return json;
|
|
160
|
+
}
|
|
87
161
|
const raw = value.trim();
|
|
88
162
|
if (raw === 'default')
|
|
89
163
|
return { type: 'default' };
|
|
@@ -113,8 +187,10 @@ function parseAvatarRefFlag(value) {
|
|
|
113
187
|
}
|
|
114
188
|
function parseSceneBackgroundFlag(value) {
|
|
115
189
|
const json = parseJsonObject(value);
|
|
116
|
-
if (json)
|
|
190
|
+
if (json) {
|
|
191
|
+
checkJsonRef(json, '--scene-background', parseSceneBackgroundFlag);
|
|
117
192
|
return json;
|
|
193
|
+
}
|
|
118
194
|
const raw = value.trim();
|
|
119
195
|
if (raw === 'default')
|
|
120
196
|
return { type: 'default' };
|
|
@@ -140,3 +216,52 @@ function parseSceneBackgroundFlag(value) {
|
|
|
140
216
|
throw new Error(`--scene-background: cannot read "${value}". Accepted: default, preset:<id>, ` +
|
|
141
217
|
`image:<filepath>@<source>, or a JSON object. Presets: ${sceneBackgroundPresetIds().join(', ')}.`);
|
|
142
218
|
}
|
|
219
|
+
/**
|
|
220
|
+
* The avatar line for `agents show` text output.
|
|
221
|
+
*
|
|
222
|
+
* `agents show` printed no avatar at all, so its output was byte-identical before
|
|
223
|
+
* and after `agents update --avatar preset:studio-halo`: "has the Halo preset"
|
|
224
|
+
* and "has no face whatsoever" could not be told apart without `--json` and
|
|
225
|
+
* knowing which raw field to decode. The coercion `--avatar` performs is the one
|
|
226
|
+
* thing a reader would want confirmed, and it was the one thing hidden.
|
|
227
|
+
*
|
|
228
|
+
* Rendered in the same shorthand `--avatar` accepts, so what is printed is what
|
|
229
|
+
* can be typed back. An ABSENT avatar is kept apart from an explicit
|
|
230
|
+
* `{type:'default'}`: both show the platform logo, but only the absent one was
|
|
231
|
+
* never chosen, and that is exactly what `agents avatar default apply` rewrites.
|
|
232
|
+
*/
|
|
233
|
+
function agentAvatarLine(raw) {
|
|
234
|
+
if (raw == null) {
|
|
235
|
+
return 'none chosen — shows the platform logo (`agents avatar default apply` would fill it)';
|
|
236
|
+
}
|
|
237
|
+
const ref = raw;
|
|
238
|
+
switch (ref.type) {
|
|
239
|
+
case 'default':
|
|
240
|
+
return 'default — the platform logo, chosen';
|
|
241
|
+
case 'preset': {
|
|
242
|
+
const id = typeof ref.id === 'string' ? ref.id : '';
|
|
243
|
+
const preset = (0, data_provider_1.getAgentAvatarPreset)(id);
|
|
244
|
+
return preset ? `preset:${id} (${preset.name})` : `preset:${id} (not in this build)`;
|
|
245
|
+
}
|
|
246
|
+
case 'asset':
|
|
247
|
+
return `asset:${typeof ref.id === 'string' ? ref.id : '?'} (from your avatar library)`;
|
|
248
|
+
case 'image':
|
|
249
|
+
return 'an uploaded image';
|
|
250
|
+
default:
|
|
251
|
+
return 'unrecognised value — see `--json`';
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
/** The scene-background line, in the shorthand `--scene-background` accepts. */
|
|
255
|
+
function agentSceneBackgroundLine(raw) {
|
|
256
|
+
const ref = (raw ?? { type: 'default' });
|
|
257
|
+
if (ref.type === 'default')
|
|
258
|
+
return 'default';
|
|
259
|
+
if (ref.type === 'preset') {
|
|
260
|
+
const id = typeof ref.id === 'string' ? ref.id : '';
|
|
261
|
+
const preset = data_provider_1.AGENT_SCENE_BACKGROUND_PRESETS.find((candidate) => candidate.id === id);
|
|
262
|
+
return preset ? `preset:${id} (${preset.name})` : `preset:${id} (not in this build)`;
|
|
263
|
+
}
|
|
264
|
+
if (ref.type === 'image')
|
|
265
|
+
return 'an uploaded image';
|
|
266
|
+
return 'unrecognised value — see `--json`';
|
|
267
|
+
}
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The answer a question gets when no terminal exists to ask it on. Written to the
|
|
3
|
+
* agent, so it says what happened and what to do rather than pretending a person
|
|
4
|
+
* replied.
|
|
5
|
+
*/
|
|
6
|
+
export declare const NON_INTERACTIVE_QUESTION_ANSWER: string;
|
|
1
7
|
export type AgenticStreamEvent = {
|
|
2
8
|
type: string;
|
|
3
9
|
sessionId?: string;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.AgenticStreamClient = exports.USER_CONNECTION_CAP_CODE = void 0;
|
|
3
|
+
exports.AgenticStreamClient = exports.USER_CONNECTION_CAP_CODE = exports.NON_INTERACTIVE_QUESTION_ANSWER = void 0;
|
|
4
4
|
exports.writeJsonLine = writeJsonLine;
|
|
5
5
|
exports.envelopesText = envelopesText;
|
|
6
6
|
exports.envelopeKey = envelopeKey;
|
|
@@ -19,6 +19,13 @@ const socket_io_client_1 = require("socket.io-client");
|
|
|
19
19
|
const data_provider_1 = require("@skrr-ai/data-provider");
|
|
20
20
|
const config_1 = require("./config");
|
|
21
21
|
const publicEndpoints_generated_1 = require("./publicEndpoints.generated");
|
|
22
|
+
/**
|
|
23
|
+
* The answer a question gets when no terminal exists to ask it on. Written to the
|
|
24
|
+
* agent, so it says what happened and what to do rather than pretending a person
|
|
25
|
+
* replied.
|
|
26
|
+
*/
|
|
27
|
+
exports.NON_INTERACTIVE_QUESTION_ANSWER = 'No answer: this turn was started non-interactively (no terminal), so nobody can reply to ' +
|
|
28
|
+
'questions. Proceed with your best judgement, or finish by stating exactly what you need.';
|
|
22
29
|
/**
|
|
23
30
|
* Write exactly one complete JSON value on one line.
|
|
24
31
|
*
|
|
@@ -667,10 +674,25 @@ class AgenticStreamClient {
|
|
|
667
674
|
async handleQuestion(event) {
|
|
668
675
|
if (this.closed)
|
|
669
676
|
return;
|
|
677
|
+
// The same question can arrive twice (both delivery paths carry it — a
|
|
678
|
+
// non-interactive run showed two `question.requested` for one questionId,
|
|
679
|
+
// each answered). Answer it once, as tool events are emitted once.
|
|
680
|
+
if (!this.firstTimeSeen('question', event.questionId))
|
|
681
|
+
return;
|
|
670
682
|
this.emit({ ...event, type: 'question.requested' });
|
|
683
|
+
// Nobody can answer a question in a run with no terminal. This used to
|
|
684
|
+
// prompt on stdin anyway: a scripted `agents chat --jsonl` printed the
|
|
685
|
+
// question and a `>` to stderr, read nothing, and the turn sat for the
|
|
686
|
+
// server's full 300-second question timeout — then ended with the agent told
|
|
687
|
+
// "Not answered … timed out", at the cost of the whole wait. Answer at once
|
|
688
|
+
// instead, saying why, so the agent proceeds or states what it needs. An
|
|
689
|
+
// explicit `input` (tests, an embedding caller) is treated as interactive.
|
|
690
|
+
const interactive = this.options.input !== undefined || Boolean(process.stdin.isTTY);
|
|
671
691
|
const answers = {};
|
|
672
692
|
for (const question of event.questions ?? []) {
|
|
673
|
-
const answer =
|
|
693
|
+
const answer = interactive
|
|
694
|
+
? await this.ask(`${question.header ?? 'Question'}: ${question.question}\n> `)
|
|
695
|
+
: exports.NON_INTERACTIVE_QUESTION_ANSWER;
|
|
674
696
|
answers[question.header ?? question.question] = answer;
|
|
675
697
|
}
|
|
676
698
|
if (event.questionId && this.socket) {
|
|
@@ -680,7 +702,12 @@ class AgenticStreamClient {
|
|
|
680
702
|
answers,
|
|
681
703
|
});
|
|
682
704
|
}
|
|
683
|
-
this.emit({
|
|
705
|
+
this.emit({
|
|
706
|
+
type: 'question.answered',
|
|
707
|
+
questionId: event.questionId,
|
|
708
|
+
answers,
|
|
709
|
+
...(interactive ? {} : { nonInteractive: true }),
|
|
710
|
+
});
|
|
684
711
|
}
|
|
685
712
|
ask(prompt) {
|
|
686
713
|
// A prompt raised after close has nobody to answer it, and re-creating the
|
|
@@ -773,6 +800,10 @@ class AgenticStreamClient {
|
|
|
773
800
|
output.write(`\n[${event.type}]\n`);
|
|
774
801
|
this.renderedText = this.accumulated;
|
|
775
802
|
}
|
|
803
|
+
else if (event.type === 'question.answered' && event.nonInteractive) {
|
|
804
|
+
output.write('\n[question] no terminal to answer on, so the agent was told to proceed without an answer\n');
|
|
805
|
+
this.renderedText = this.accumulated;
|
|
806
|
+
}
|
|
776
807
|
}
|
|
777
808
|
finish(status, event) {
|
|
778
809
|
if (this.settled)
|
|
@@ -33,7 +33,7 @@ export type ResolutionFailure = {
|
|
|
33
33
|
* NO_SPACE — the reference needs a membership list to match against, but
|
|
34
34
|
* the task/payload has no space, so only raw ids are possible.
|
|
35
35
|
*/
|
|
36
|
-
code: 'NOT_FOUND' | 'AMBIGUOUS' | 'NO_SPACE';
|
|
36
|
+
code: 'NOT_FOUND' | 'AMBIGUOUS' | 'NO_SPACE' | 'NOT_A_REFERENCE';
|
|
37
37
|
query: string;
|
|
38
38
|
kind: MemberKind;
|
|
39
39
|
/** For AMBIGUOUS: the tied matches. For NOT_FOUND: everything available. */
|
|
@@ -7,6 +7,7 @@ exports.describeMember = describeMember;
|
|
|
7
7
|
exports.describeCandidates = describeCandidates;
|
|
8
8
|
exports.resolveAssigneeReferences = resolveAssigneeReferences;
|
|
9
9
|
exports.formatResolutionFailure = formatResolutionFailure;
|
|
10
|
+
const failed_lookup_1 = require("./failed-lookup");
|
|
10
11
|
const data_provider_1 = require("@skrr-ai/data-provider");
|
|
11
12
|
const OBJECT_ID_RE = /^[0-9a-fA-F]{24}$/;
|
|
12
13
|
const UUID_RE = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
|
|
@@ -84,6 +85,11 @@ function resolveMember(query, members, kind) {
|
|
|
84
85
|
if (!q) {
|
|
85
86
|
return { ok: false, code: 'NOT_FOUND', query: raw, kind, candidates: pool };
|
|
86
87
|
}
|
|
88
|
+
// A failed lookup's output is refused before the name tiers can match it.
|
|
89
|
+
// See `failed-lookup.ts`.
|
|
90
|
+
if ((0, failed_lookup_1.isFailedLookupValue)(raw)) {
|
|
91
|
+
return { ok: false, code: 'NOT_A_REFERENCE', query: raw, kind, candidates: [] };
|
|
92
|
+
}
|
|
87
93
|
const tiers = [
|
|
88
94
|
// 1. Exact id. Case-sensitive on purpose — ids are opaque.
|
|
89
95
|
(m) => m.id === raw,
|
|
@@ -186,6 +192,8 @@ function formatResolutionFailure(failure, bin = 'skrr') {
|
|
|
186
192
|
const noun = failure.kind === 'user' ? 'person' : 'agent';
|
|
187
193
|
const plural = failure.kind === 'user' ? 'people' : 'agents';
|
|
188
194
|
switch (failure.code) {
|
|
195
|
+
case 'NOT_A_REFERENCE':
|
|
196
|
+
return (0, failed_lookup_1.describeFailedLookupValue)(failure.query, noun);
|
|
189
197
|
case 'AMBIGUOUS':
|
|
190
198
|
return (`"${failure.query}" matches ${failure.candidates.length} ${plural} in this space:\n` +
|
|
191
199
|
`${describeCandidates(failure.candidates)}\n` +
|
|
@@ -29,6 +29,18 @@ export type BulkApplyResult = {
|
|
|
29
29
|
* semantics match `skrr tasks bulk-update`). `apply` returns whether the task
|
|
30
30
|
* actually changed (already-satisfied tasks are reported as no-ops).
|
|
31
31
|
*/
|
|
32
|
+
/**
|
|
33
|
+
* The server's reason for a failed per-task write, not the transport's.
|
|
34
|
+
*
|
|
35
|
+
* A bulk run used to record `err.message`, which for a dataService call is the
|
|
36
|
+
* transport line — `HTTP 400 Bad Request — PATCH https://…/api/tasks/<id>` — so
|
|
37
|
+
* `tasks actions attach --all-in-space` printed that once per task and named
|
|
38
|
+
* neither the ref nor the rule that refused it (OSK-9652). The same call made
|
|
39
|
+
* for a single task goes through `handleApiError`, which reads the body. This
|
|
40
|
+
* reads it the same way: `message`, then `error`, plus the first validation
|
|
41
|
+
* detail when the server sent one.
|
|
42
|
+
*/
|
|
43
|
+
export declare function bulkFailureMessage(err: unknown): string;
|
|
32
44
|
export declare function applyToTaskTargets(targets: Array<{
|
|
33
45
|
id: string;
|
|
34
46
|
identifier?: string;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.listSpaceTaskTargets = listSpaceTaskTargets;
|
|
4
|
+
exports.bulkFailureMessage = bulkFailureMessage;
|
|
4
5
|
exports.applyToTaskTargets = applyToTaskTargets;
|
|
5
6
|
exports.formatBulkOutcome = formatBulkOutcome;
|
|
6
7
|
exports.parseStatusFilter = parseStatusFilter;
|
|
@@ -34,6 +35,38 @@ async function listSpaceTaskTargets(options) {
|
|
|
34
35
|
* semantics match `skrr tasks bulk-update`). `apply` returns whether the task
|
|
35
36
|
* actually changed (already-satisfied tasks are reported as no-ops).
|
|
36
37
|
*/
|
|
38
|
+
/**
|
|
39
|
+
* The server's reason for a failed per-task write, not the transport's.
|
|
40
|
+
*
|
|
41
|
+
* A bulk run used to record `err.message`, which for a dataService call is the
|
|
42
|
+
* transport line — `HTTP 400 Bad Request — PATCH https://…/api/tasks/<id>` — so
|
|
43
|
+
* `tasks actions attach --all-in-space` printed that once per task and named
|
|
44
|
+
* neither the ref nor the rule that refused it (OSK-9652). The same call made
|
|
45
|
+
* for a single task goes through `handleApiError`, which reads the body. This
|
|
46
|
+
* reads it the same way: `message`, then `error`, plus the first validation
|
|
47
|
+
* detail when the server sent one.
|
|
48
|
+
*/
|
|
49
|
+
function bulkFailureMessage(err) {
|
|
50
|
+
const e = err;
|
|
51
|
+
if (e && typeof e.body === 'string' && e.body.trim()) {
|
|
52
|
+
try {
|
|
53
|
+
const body = JSON.parse(e.body);
|
|
54
|
+
const reason = [body.message, body.error].find((value) => typeof value === 'string' && value.trim().length > 0);
|
|
55
|
+
if (reason) {
|
|
56
|
+
const detail = Array.isArray(body.details) ? body.details[0] : undefined;
|
|
57
|
+
const detailText = detail && typeof detail.message === 'string'
|
|
58
|
+
? ` — ${Array.isArray(detail.path) && detail.path.length ? `${detail.path.join('.')}: ` : ''}${detail.message}`
|
|
59
|
+
: '';
|
|
60
|
+
const status = typeof e.status === 'number' ? ` (HTTP ${e.status})` : '';
|
|
61
|
+
return `${reason}${detailText}${status}`;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
/* not JSON: fall back to the transport message below */
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return err instanceof Error ? err.message : String(err);
|
|
69
|
+
}
|
|
37
70
|
async function applyToTaskTargets(targets, apply) {
|
|
38
71
|
const result = { succeeded: [], failed: [] };
|
|
39
72
|
for (const target of targets) {
|
|
@@ -46,7 +79,7 @@ async function applyToTaskTargets(targets, apply) {
|
|
|
46
79
|
result.failed.push({
|
|
47
80
|
id: target.id,
|
|
48
81
|
label,
|
|
49
|
-
error:
|
|
82
|
+
error: bulkFailureMessage(err),
|
|
50
83
|
});
|
|
51
84
|
}
|
|
52
85
|
}
|