@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.
Files changed (174) hide show
  1. package/bin/dev-fallback.js +167 -4
  2. package/dist/base-command.js +46 -2
  3. package/dist/commands/agents/actions/create.js +18 -7
  4. package/dist/commands/agents/actions/delete.d.ts +1 -0
  5. package/dist/commands/agents/actions/delete.js +7 -0
  6. package/dist/commands/agents/actions/update.js +5 -3
  7. package/dist/commands/agents/api-actions/add.js +5 -2
  8. package/dist/commands/agents/api-actions/index.d.ts +11 -0
  9. package/dist/commands/agents/api-actions/index.js +27 -1
  10. package/dist/commands/agents/avatar/default/index.d.ts +11 -0
  11. package/dist/commands/agents/avatar/default/index.js +17 -0
  12. package/dist/commands/agents/avatar/default/set.d.ts +2 -1
  13. package/dist/commands/agents/avatar/default/set.js +32 -7
  14. package/dist/commands/agents/avatar/upload.js +7 -1
  15. package/dist/commands/agents/categories/delete.js +8 -0
  16. package/dist/commands/agents/categories/index.d.ts +21 -5
  17. package/dist/commands/agents/categories/index.js +68 -15
  18. package/dist/commands/agents/chat.d.ts +21 -0
  19. package/dist/commands/agents/chat.js +94 -7
  20. package/dist/commands/agents/create.js +11 -4
  21. package/dist/commands/agents/list.d.ts +15 -0
  22. package/dist/commands/agents/list.js +31 -1
  23. package/dist/commands/agents/scene-background/upload.d.ts +30 -0
  24. package/dist/commands/agents/scene-background/upload.js +97 -0
  25. package/dist/commands/agents/show.js +13 -0
  26. package/dist/commands/agents/tools/calls.d.ts +23 -5
  27. package/dist/commands/agents/tools/calls.js +81 -18
  28. package/dist/commands/agents/update.js +15 -5
  29. package/dist/commands/code/{oversky-agent.d.ts → skrr-agent.d.ts} +3 -3
  30. package/dist/commands/code/{oversky-agent.js → skrr-agent.js} +11 -8
  31. package/dist/commands/convos/messages.d.ts +13 -0
  32. package/dist/commands/convos/messages.js +40 -16
  33. package/dist/commands/daemon/index.js +8 -1
  34. package/dist/commands/daemon/logs.d.ts +26 -0
  35. package/dist/commands/daemon/logs.js +56 -0
  36. package/dist/commands/daemon/uninstall.js +1 -1
  37. package/dist/commands/followups/act.js +3 -2
  38. package/dist/commands/followups/cancel.d.ts +6 -3
  39. package/dist/commands/followups/cancel.js +16 -6
  40. package/dist/commands/followups/observability.d.ts +23 -0
  41. package/dist/commands/followups/observability.js +114 -0
  42. package/dist/commands/followups/reschedule.js +1 -1
  43. package/dist/commands/followups/resolve.js +3 -3
  44. package/dist/commands/followups/show.js +2 -1
  45. package/dist/commands/followups/watch.js +9 -5
  46. package/dist/commands/harnesses/forget.js +28 -0
  47. package/dist/commands/harnesses/install.d.ts +41 -0
  48. package/dist/commands/harnesses/install.js +165 -0
  49. package/dist/commands/harnesses/installers.d.ts +45 -0
  50. package/dist/commands/harnesses/installers.js +155 -0
  51. package/dist/commands/harnesses/list.d.ts +82 -1
  52. package/dist/commands/harnesses/list.js +104 -12
  53. package/dist/commands/harnesses/models.js +10 -1
  54. package/dist/commands/harnesses/show.d.ts +15 -0
  55. package/dist/commands/harnesses/show.js +44 -3
  56. package/dist/commands/harnesses/update.js +1 -1
  57. package/dist/commands/harnesses/usage.d.ts +25 -0
  58. package/dist/commands/harnesses/usage.js +24 -2
  59. package/dist/commands/instructions/status.js +13 -1
  60. package/dist/commands/machines/dedicated/catalog.js +4 -15
  61. package/dist/commands/machines/dedicated/create.d.ts +8 -0
  62. package/dist/commands/machines/dedicated/create.js +58 -1
  63. package/dist/commands/machines/dedicated/destroy.js +18 -4
  64. package/dist/commands/machines/dedicated/list.js +9 -6
  65. package/dist/commands/machines/hosted/list.js +12 -0
  66. package/dist/commands/machines/list.js +11 -0
  67. package/dist/commands/machines/show.d.ts +1 -0
  68. package/dist/commands/machines/show.js +40 -11
  69. package/dist/commands/profiles/list.d.ts +23 -0
  70. package/dist/commands/profiles/list.js +96 -0
  71. package/dist/commands/search/skills.d.ts +11 -1
  72. package/dist/commands/search/skills.js +26 -8
  73. package/dist/commands/skills/details.d.ts +2 -0
  74. package/dist/commands/skills/details.js +24 -1
  75. package/dist/commands/skills/import-as-actions.d.ts +32 -0
  76. package/dist/commands/skills/import-as-actions.js +175 -0
  77. package/dist/commands/skills/install.d.ts +2 -0
  78. package/dist/commands/skills/install.js +24 -1
  79. package/dist/commands/skills/invocable.js +6 -2
  80. package/dist/commands/skills/local.d.ts +40 -0
  81. package/dist/commands/skills/local.js +160 -0
  82. package/dist/commands/skills/search.d.ts +11 -1
  83. package/dist/commands/skills/search.js +29 -8
  84. package/dist/commands/spaces/actions/add.js +23 -5
  85. package/dist/commands/spaces/actions/available.js +3 -0
  86. package/dist/commands/spaces/actions/list.d.ts +5 -0
  87. package/dist/commands/spaces/actions/list.js +49 -3
  88. package/dist/commands/spaces/actions/run.d.ts +28 -0
  89. package/dist/commands/spaces/actions/run.js +146 -0
  90. package/dist/commands/spaces/actions/update.js +27 -5
  91. package/dist/commands/spaces/label-summaries.d.ts +27 -0
  92. package/dist/commands/spaces/label-summaries.js +64 -0
  93. package/dist/commands/spaces/task-rollups.d.ts +5 -0
  94. package/dist/commands/spaces/task-rollups.js +15 -4
  95. package/dist/commands/spaces/workflows/lifecycle-diagnostics.js +63 -3
  96. package/dist/commands/tasks/actions/attach.js +32 -7
  97. package/dist/commands/tasks/complete.js +13 -0
  98. package/dist/commands/tasks/create.js +1 -1
  99. package/dist/commands/tasks/events/append.js +29 -6
  100. package/dist/commands/tasks/expectations/assess.d.ts +1 -0
  101. package/dist/commands/tasks/expectations/assess.js +43 -5
  102. package/dist/commands/tasks/expectations.js +77 -16
  103. package/dist/commands/tasks/follow-up.js +2 -2
  104. package/dist/commands/tasks/lifecycle/definitions.js +21 -2
  105. package/dist/commands/tasks/lifecycle/detach.d.ts +7 -0
  106. package/dist/commands/tasks/lifecycle/detach.js +43 -3
  107. package/dist/commands/tasks/lifecycle/migrate.d.ts +2 -0
  108. package/dist/commands/tasks/lifecycle/migrate.js +42 -3
  109. package/dist/commands/tasks/lifecycle/reopen.d.ts +2 -0
  110. package/dist/commands/tasks/lifecycle/reopen.js +40 -3
  111. package/dist/commands/tasks/lifecycle/transition.js +33 -2
  112. package/dist/commands/tasks/list.js +16 -9
  113. package/dist/commands/tasks/move.js +11 -13
  114. package/dist/commands/tasks/review-queue.d.ts +15 -0
  115. package/dist/commands/tasks/review-queue.js +180 -31
  116. package/dist/commands/tasks/show.d.ts +11 -0
  117. package/dist/commands/tasks/show.js +105 -12
  118. package/dist/commands/tasks/trust.js +7 -2
  119. package/dist/commands/tasks/update.js +46 -10
  120. package/dist/commands/tasks/updates/add.js +1 -1
  121. package/dist/commands/token/list.d.ts +22 -0
  122. package/dist/commands/token/list.js +68 -0
  123. package/dist/commands/token/revoke.d.ts +23 -0
  124. package/dist/commands/token/revoke.js +53 -0
  125. package/dist/lib/agent-actions.d.ts +57 -2
  126. package/dist/lib/agent-actions.js +113 -21
  127. package/dist/lib/agent-pin-mute.d.ts +10 -5
  128. package/dist/lib/agent-pin-mute.js +17 -4
  129. package/dist/lib/agent-visual-refs.d.ts +27 -0
  130. package/dist/lib/agent-visual-refs.js +127 -2
  131. package/dist/lib/agentic-stream.d.ts +6 -0
  132. package/dist/lib/agentic-stream.js +34 -3
  133. package/dist/lib/assignee-resolver.d.ts +1 -1
  134. package/dist/lib/assignee-resolver.js +8 -0
  135. package/dist/lib/bulk-task-targets.d.ts +12 -0
  136. package/dist/lib/bulk-task-targets.js +34 -1
  137. package/dist/lib/cli-installers.d.ts +130 -0
  138. package/dist/lib/cli-installers.js +145 -0
  139. package/dist/lib/command-miss.js +55 -7
  140. package/dist/lib/dedicated-lease-command.d.ts +13 -0
  141. package/dist/lib/dedicated-lease-command.js +22 -1
  142. package/dist/lib/dedicated-machines.d.ts +95 -1
  143. package/dist/lib/dedicated-machines.js +236 -2
  144. package/dist/lib/failed-lookup.d.ts +3 -0
  145. package/dist/lib/failed-lookup.js +40 -0
  146. package/dist/lib/first-party-harness-agent.js +9 -2
  147. package/dist/lib/first-party-harness-doctor.d.ts +15 -0
  148. package/dist/lib/first-party-harness-doctor.js +126 -27
  149. package/dist/lib/followups.d.ts +14 -6
  150. package/dist/lib/followups.js +31 -6
  151. package/dist/lib/harnesses.d.ts +8 -0
  152. package/dist/lib/instruction-input.d.ts +19 -1
  153. package/dist/lib/instruction-input.js +52 -1
  154. package/dist/lib/label-scope.d.ts +15 -0
  155. package/dist/lib/label-scope.js +25 -5
  156. package/dist/lib/local-skills.d.ts +98 -0
  157. package/dist/lib/local-skills.js +195 -0
  158. package/dist/lib/machines.d.ts +10 -10
  159. package/dist/lib/machines.js +23 -1
  160. package/dist/lib/search-query.d.ts +19 -0
  161. package/dist/lib/search-query.js +35 -0
  162. package/dist/lib/space-custom-actions.d.ts +72 -0
  163. package/dist/lib/space-custom-actions.js +117 -0
  164. package/dist/lib/space-resolver.d.ts +1 -21
  165. package/dist/lib/space-resolver.js +19 -10
  166. package/dist/lib/task-closure.d.ts +3 -2
  167. package/dist/lib/task-closure.js +18 -20
  168. package/dist/lib/task-lifecycle-output.js +117 -9
  169. package/dist/lib/task-ref-resolver.d.ts +1 -17
  170. package/dist/lib/task-ref-resolver.js +18 -8
  171. package/dist/lib/tasks.js +9 -3
  172. package/dist/node_modules/@skrr-ai/data-provider/index.js +5076 -4947
  173. package/oclif.manifest.json +26635 -25649
  174. 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
- exports.BUILTIN_ACTION_TYPES = [
21
- 'slack_send_message',
22
- 'slack_tool',
23
- 'group_chat_message',
24
- 'space_create_task',
25
- 'space_post_update',
26
- 'task_create',
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
- function resolveAgentActionScopeFlags(flags) {
117
- const labelIds = (flags.label ?? []).map((value) => value.trim()).filter(Boolean);
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' : labelIds.length ? 'label' : undefined);
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 (!labelIds.length)
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 read first and the toggle is sent only when it
12
- * differs. That makes each command idempotent and its name true. The read is not
13
- * a lock: two concurrent invocations can still race, which is a real limit of a
14
- * toggle endpoint and not something the CLI can fix from out here — the
15
- * returned state is always what the server says, never what was asked for.
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 = await this.ask(`${question.header ?? 'Question'}: ${question.question}\n> `);
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({ type: 'question.answered', questionId: event.questionId, answers });
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: err instanceof Error ? err.message : String(err),
82
+ error: bulkFailureMessage(err),
50
83
  });
51
84
  }
52
85
  }