arcane-cli 1.19.0 → 1.20.0
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/README.md +2 -2
- package/dist/assets/.arcane/governance/spell-authoring-standards.md +34 -22
- package/dist/assets/.arcane/governance/threat-model.md +4 -4
- package/dist/assets/.arcane/spells/_fragments/action-required.md +27 -0
- package/dist/assets/.arcane/spells/_fragments/presence-report.md +1 -1
- package/dist/assets/.arcane/spells/spell-address-review.md +21 -9
- package/dist/assets/.arcane/spells/spell-adopt-docs.md +21 -9
- package/dist/assets/.arcane/spells/spell-app-store-accounts.md +22 -10
- package/dist/assets/.arcane/spells/spell-architect.md +21 -9
- package/dist/assets/.arcane/spells/spell-close-session.md +26 -14
- package/dist/assets/.arcane/spells/spell-commit-work.md +22 -10
- package/dist/assets/.arcane/spells/spell-create-pull-request.md +22 -10
- package/dist/assets/.arcane/spells/spell-eas-store-deploy.md +21 -9
- package/dist/assets/.arcane/spells/spell-full-cycle.md +21 -9
- package/dist/assets/.arcane/spells/spell-implement.md +1 -1
- package/dist/assets/.arcane/spells/spell-make-discoverable.md +21 -9
- package/dist/assets/.arcane/spells/spell-open-session.md +23 -11
- package/dist/assets/.arcane/spells/spell-parallel-workers.md +23 -11
- package/dist/assets/.arcane/spells/spell-plan.md +21 -9
- package/dist/assets/.arcane/spells/spell-review-batch.md +21 -9
- package/dist/assets/.arcane/spells/spell-review.md +22 -10
- package/dist/assets/.arcane/spells/spell-scope.md +21 -9
- package/dist/assets/.arcane/spells/spell-ship.md +21 -9
- package/dist/assets/.arcane/spells/spell-sync-pull-request.md +22 -10
- package/dist/assets/ai-context/system-prompt-context.md +1 -1
- package/dist/assets/schemas/presence.schema.json +28 -9
- package/dist/index.js +34 -28
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/assets/.arcane/spells/_fragments/needs-you.md +0 -15
|
@@ -252,23 +252,35 @@ If `external_provider=jira` or `external_provider=other`, add explicit TODOs in
|
|
|
252
252
|
|
|
253
253
|
### 9. Present and Confirm
|
|
254
254
|
|
|
255
|
-
<!-- fragment:
|
|
256
|
-
**
|
|
255
|
+
<!-- fragment:action-required:start -->
|
|
256
|
+
**Action required — what only the operator can do.** When this run leaves an action that only the operator can take, report it in one block at the very top of the final report, before every other section and before any optional next step. The block exists because these are the operator's decisions to make, not because the agent is stuck: write it as a hand-off of authority, never as a plea for help. Frame it with a horizontal rule above and below, so it stands apart in a terminal as well as on a rendered page:
|
|
257
257
|
|
|
258
258
|
```markdown
|
|
259
|
-
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
## 🚧 Action required
|
|
262
|
+
|
|
263
|
+
**Now**
|
|
264
|
+
|
|
265
|
+
- <exactly what to do: the command, the link, or the question> — <why only the operator can do it>
|
|
260
266
|
|
|
261
|
-
|
|
267
|
+
**When you're at your desk**
|
|
268
|
+
|
|
269
|
+
- <exactly what to do> — <why only the operator can do it>
|
|
270
|
+
|
|
271
|
+
---
|
|
262
272
|
```
|
|
263
273
|
|
|
264
|
-
- **What belongs in it:** an action
|
|
265
|
-
- **
|
|
274
|
+
- **What belongs in it:** an action only the operator can take — approving or merging a pull request, accepting a decision record, answering a question the work is waiting on, confirming a `guarded` push, running an interactive command such as `spell unblock-push`, or a step on an external platform the agent cannot reach. An optional suggestion, a next spell to run, or anything the agent could do itself is not a required action and stays out of the block.
|
|
275
|
+
- **Two tiers, one block.** **Now** holds what the work is not correct or complete without. **When you're at your desk** holds what only the operator can do but that can wait: a permission rule, a leftover remote branch, a credential to set up. Leave out a tier that has nothing in it; never split the tiers into two blocks.
|
|
276
|
+
- **One line per action.** Say exactly what to do, then, after the dash, why only the operator can do it. Write the command, link or question out in full; never point elsewhere with "see above".
|
|
266
277
|
- **Never only among optional steps.** An action required for correctness is never listed only under next steps, suggestions, `Carry Forward` or `What's Next?`. It may be repeated there, but it always appears in this block.
|
|
267
|
-
- **
|
|
278
|
+
- **Done items stay, struck through.** Within one session, once an item has been listed, keep it in every later report of the block; when the operator has done it, strike it through (`~~<the line>~~`) rather than dropping it, so progress shows and nothing disappears silently. The session's close carries only the open items forward.
|
|
279
|
+
- **Omit it when empty.** When nothing is left for the operator and nothing was struck through this session, leave the block out entirely: no heading, no rules and no "None" line.
|
|
268
280
|
- **Every final report.** The block belongs in whatever report ends the run, including a report written when the spell stops early.
|
|
269
|
-
- **Report
|
|
281
|
+
- **Report the Now tier to the presence channel.** When the **Now** tier has open items, also run `spell presence report --status waiting --required-action "<line 1>" --required-action "<line 2>"`, one flag per open line of that tier; the other tier is not blocking and is not reported. With no presence channel configured on this machine the command prints nothing and exits 0, so the block itself and the rest of the run are unchanged.
|
|
270
282
|
- Enforcement: explicitly advisory prose (ARC-023) — no check reads a spell's actual output for this block; it holds by these instructions alone.
|
|
271
|
-
<!-- fragment:
|
|
283
|
+
<!-- fragment:action-required:end -->
|
|
272
284
|
|
|
273
285
|
Present the execution plan to the user. Ask:
|
|
274
286
|
1. Does the epic split make sense?
|
|
@@ -97,23 +97,35 @@ Workflow:
|
|
|
97
97
|
- OR: BLOCK — [reason]
|
|
98
98
|
```
|
|
99
99
|
|
|
100
|
-
<!-- fragment:
|
|
101
|
-
**
|
|
100
|
+
<!-- fragment:action-required:start -->
|
|
101
|
+
**Action required — what only the operator can do.** When this run leaves an action that only the operator can take, report it in one block at the very top of the final report, before every other section and before any optional next step. The block exists because these are the operator's decisions to make, not because the agent is stuck: write it as a hand-off of authority, never as a plea for help. Frame it with a horizontal rule above and below, so it stands apart in a terminal as well as on a rendered page:
|
|
102
102
|
|
|
103
103
|
```markdown
|
|
104
|
-
|
|
104
|
+
---
|
|
105
105
|
|
|
106
|
-
|
|
106
|
+
## 🚧 Action required
|
|
107
|
+
|
|
108
|
+
**Now**
|
|
109
|
+
|
|
110
|
+
- <exactly what to do: the command, the link, or the question> — <why only the operator can do it>
|
|
111
|
+
|
|
112
|
+
**When you're at your desk**
|
|
113
|
+
|
|
114
|
+
- <exactly what to do> — <why only the operator can do it>
|
|
115
|
+
|
|
116
|
+
---
|
|
107
117
|
```
|
|
108
118
|
|
|
109
|
-
- **What belongs in it:** an action
|
|
110
|
-
- **
|
|
119
|
+
- **What belongs in it:** an action only the operator can take — approving or merging a pull request, accepting a decision record, answering a question the work is waiting on, confirming a `guarded` push, running an interactive command such as `spell unblock-push`, or a step on an external platform the agent cannot reach. An optional suggestion, a next spell to run, or anything the agent could do itself is not a required action and stays out of the block.
|
|
120
|
+
- **Two tiers, one block.** **Now** holds what the work is not correct or complete without. **When you're at your desk** holds what only the operator can do but that can wait: a permission rule, a leftover remote branch, a credential to set up. Leave out a tier that has nothing in it; never split the tiers into two blocks.
|
|
121
|
+
- **One line per action.** Say exactly what to do, then, after the dash, why only the operator can do it. Write the command, link or question out in full; never point elsewhere with "see above".
|
|
111
122
|
- **Never only among optional steps.** An action required for correctness is never listed only under next steps, suggestions, `Carry Forward` or `What's Next?`. It may be repeated there, but it always appears in this block.
|
|
112
|
-
- **
|
|
123
|
+
- **Done items stay, struck through.** Within one session, once an item has been listed, keep it in every later report of the block; when the operator has done it, strike it through (`~~<the line>~~`) rather than dropping it, so progress shows and nothing disappears silently. The session's close carries only the open items forward.
|
|
124
|
+
- **Omit it when empty.** When nothing is left for the operator and nothing was struck through this session, leave the block out entirely: no heading, no rules and no "None" line.
|
|
113
125
|
- **Every final report.** The block belongs in whatever report ends the run, including a report written when the spell stops early.
|
|
114
|
-
- **Report
|
|
126
|
+
- **Report the Now tier to the presence channel.** When the **Now** tier has open items, also run `spell presence report --status waiting --required-action "<line 1>" --required-action "<line 2>"`, one flag per open line of that tier; the other tier is not blocking and is not reported. With no presence channel configured on this machine the command prints nothing and exits 0, so the block itself and the rest of the run are unchanged.
|
|
115
127
|
- Enforcement: explicitly advisory prose (ARC-023) — no check reads a spell's actual output for this block; it holds by these instructions alone.
|
|
116
|
-
<!-- fragment:
|
|
128
|
+
<!-- fragment:action-required:end -->
|
|
117
129
|
|
|
118
130
|
7. **Request human approval** — present the ship report and wait for explicit approval.
|
|
119
131
|
|
|
@@ -110,7 +110,7 @@ then classify **each conflicted hunk independently** — a single file can conta
|
|
|
110
110
|
that one side's content is a strict superset of the other's. **STOP.** Do not guess, do not pick a
|
|
111
111
|
side, do not attempt a "reasonable-looking" merge of the two. Abort the in-progress rebase/merge
|
|
112
112
|
(`git rebase --abort` / `git merge --abort`) so the branch is left exactly as it was, then report:
|
|
113
|
-
- First, the `##
|
|
113
|
+
- First, the `## 🚧 Action required` block described in Step 6, naming the manual resolution as the action.
|
|
114
114
|
- The exact conflicting file(s) and hunk(s).
|
|
115
115
|
- Both sides' conflicting content, verbatim.
|
|
116
116
|
- That the recoverable ref from Step 1 still points at the pre-sync state.
|
|
@@ -172,23 +172,35 @@ report the discrepancy rather than declaring the sync done.
|
|
|
172
172
|
|
|
173
173
|
## Step 6 — Report
|
|
174
174
|
|
|
175
|
-
<!-- fragment:
|
|
176
|
-
**
|
|
175
|
+
<!-- fragment:action-required:start -->
|
|
176
|
+
**Action required — what only the operator can do.** When this run leaves an action that only the operator can take, report it in one block at the very top of the final report, before every other section and before any optional next step. The block exists because these are the operator's decisions to make, not because the agent is stuck: write it as a hand-off of authority, never as a plea for help. Frame it with a horizontal rule above and below, so it stands apart in a terminal as well as on a rendered page:
|
|
177
177
|
|
|
178
178
|
```markdown
|
|
179
|
-
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## 🚧 Action required
|
|
182
|
+
|
|
183
|
+
**Now**
|
|
184
|
+
|
|
185
|
+
- <exactly what to do: the command, the link, or the question> — <why only the operator can do it>
|
|
180
186
|
|
|
181
|
-
|
|
187
|
+
**When you're at your desk**
|
|
188
|
+
|
|
189
|
+
- <exactly what to do> — <why only the operator can do it>
|
|
190
|
+
|
|
191
|
+
---
|
|
182
192
|
```
|
|
183
193
|
|
|
184
|
-
- **What belongs in it:** an action
|
|
185
|
-
- **
|
|
194
|
+
- **What belongs in it:** an action only the operator can take — approving or merging a pull request, accepting a decision record, answering a question the work is waiting on, confirming a `guarded` push, running an interactive command such as `spell unblock-push`, or a step on an external platform the agent cannot reach. An optional suggestion, a next spell to run, or anything the agent could do itself is not a required action and stays out of the block.
|
|
195
|
+
- **Two tiers, one block.** **Now** holds what the work is not correct or complete without. **When you're at your desk** holds what only the operator can do but that can wait: a permission rule, a leftover remote branch, a credential to set up. Leave out a tier that has nothing in it; never split the tiers into two blocks.
|
|
196
|
+
- **One line per action.** Say exactly what to do, then, after the dash, why only the operator can do it. Write the command, link or question out in full; never point elsewhere with "see above".
|
|
186
197
|
- **Never only among optional steps.** An action required for correctness is never listed only under next steps, suggestions, `Carry Forward` or `What's Next?`. It may be repeated there, but it always appears in this block.
|
|
187
|
-
- **
|
|
198
|
+
- **Done items stay, struck through.** Within one session, once an item has been listed, keep it in every later report of the block; when the operator has done it, strike it through (`~~<the line>~~`) rather than dropping it, so progress shows and nothing disappears silently. The session's close carries only the open items forward.
|
|
199
|
+
- **Omit it when empty.** When nothing is left for the operator and nothing was struck through this session, leave the block out entirely: no heading, no rules and no "None" line.
|
|
188
200
|
- **Every final report.** The block belongs in whatever report ends the run, including a report written when the spell stops early.
|
|
189
|
-
- **Report
|
|
201
|
+
- **Report the Now tier to the presence channel.** When the **Now** tier has open items, also run `spell presence report --status waiting --required-action "<line 1>" --required-action "<line 2>"`, one flag per open line of that tier; the other tier is not blocking and is not reported. With no presence channel configured on this machine the command prints nothing and exits 0, so the block itself and the rest of the run are unchanged.
|
|
190
202
|
- Enforcement: explicitly advisory prose (ARC-023) — no check reads a spell's actual output for this block; it holds by these instructions alone.
|
|
191
|
-
<!-- fragment:
|
|
203
|
+
<!-- fragment:action-required:end -->
|
|
192
204
|
|
|
193
205
|
```
|
|
194
206
|
✓ PR synced: <URL>
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
- **Branch:** main
|
|
34
34
|
- **Blockers:** None.
|
|
35
35
|
- **Pending Verification:** None.
|
|
36
|
-
- **
|
|
36
|
+
- **Action required:** None.
|
|
37
37
|
- **Notes:** —
|
|
38
38
|
|
|
39
39
|
> ✓ Consumed: YYYY-MM-DD — scaffold placeholder, not a real session; ships pre-consumed so the first
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"task",
|
|
16
16
|
"message",
|
|
17
17
|
"status",
|
|
18
|
-
"
|
|
18
|
+
"required_actions",
|
|
19
19
|
"started_at",
|
|
20
20
|
"reported_at",
|
|
21
21
|
"ended_at",
|
|
@@ -73,11 +73,11 @@
|
|
|
73
73
|
],
|
|
74
74
|
"description": "working: a spell is running; waiting: the session asked the operator for something; blocked: cannot proceed for an external reason; idle: open, no spell running; done: ended (set by end only)."
|
|
75
75
|
},
|
|
76
|
-
"
|
|
76
|
+
"required_actions": {
|
|
77
77
|
"type": "array",
|
|
78
78
|
"maxItems": 20,
|
|
79
79
|
"items": {
|
|
80
|
-
"$ref": "#/$defs/
|
|
80
|
+
"$ref": "#/$defs/requiredAction"
|
|
81
81
|
}
|
|
82
82
|
},
|
|
83
83
|
"started_at": {
|
|
@@ -216,7 +216,7 @@
|
|
|
216
216
|
}
|
|
217
217
|
}
|
|
218
218
|
},
|
|
219
|
-
"
|
|
219
|
+
"requiredAction": {
|
|
220
220
|
"type": "object",
|
|
221
221
|
"additionalProperties": false,
|
|
222
222
|
"required": [
|
|
@@ -227,7 +227,7 @@
|
|
|
227
227
|
"action": {
|
|
228
228
|
"type": "string",
|
|
229
229
|
"maxLength": 256,
|
|
230
|
-
"description": "Exactly what the operator must do: one line of a '##
|
|
230
|
+
"description": "Exactly what the operator must do: one line of a '## 🚧 Action required' block.",
|
|
231
231
|
"minLength": 1
|
|
232
232
|
},
|
|
233
233
|
"reason": {
|
|
@@ -390,14 +390,33 @@
|
|
|
390
390
|
"maxLength": 256,
|
|
391
391
|
"description": "Absent keeps the stored value; null clears it."
|
|
392
392
|
},
|
|
393
|
-
"
|
|
393
|
+
"required_actions": {
|
|
394
394
|
"type": "array",
|
|
395
395
|
"maxItems": 20,
|
|
396
396
|
"items": {
|
|
397
|
-
"$ref": "#/$defs/
|
|
397
|
+
"$ref": "#/$defs/requiredAction"
|
|
398
398
|
},
|
|
399
399
|
"description": "Replaces the stored list on every report; absent means []. More than 20 entries is refused with 400, never cut."
|
|
400
|
+
},
|
|
401
|
+
"needs_you": {
|
|
402
|
+
"type": "array",
|
|
403
|
+
"maxItems": 20,
|
|
404
|
+
"items": {
|
|
405
|
+
"$ref": "#/$defs/requiredAction"
|
|
406
|
+
},
|
|
407
|
+
"deprecated": true,
|
|
408
|
+
"description": "The name of required_actions before 1.20 (ARC-060 decision 14), accepted as required_actions for the 1.20 series. Never sent together with required_actions: a body with both is refused with 400 naming needs_you."
|
|
400
409
|
}
|
|
410
|
+
},
|
|
411
|
+
"not": {
|
|
412
|
+
"properties": {
|
|
413
|
+
"required_actions": true,
|
|
414
|
+
"needs_you": true
|
|
415
|
+
},
|
|
416
|
+
"required": [
|
|
417
|
+
"required_actions",
|
|
418
|
+
"needs_you"
|
|
419
|
+
]
|
|
401
420
|
}
|
|
402
421
|
},
|
|
403
422
|
"end": {
|
|
@@ -528,7 +547,7 @@
|
|
|
528
547
|
}
|
|
529
548
|
},
|
|
530
549
|
"event": {
|
|
531
|
-
"description": "One change on GET /v0/events: the event name and the full record after the change. Exactly one event per accepted report, by the precedence started > resumed >
|
|
550
|
+
"description": "One change on GET /v0/events: the event name and the full record after the change. Exactly one event per accepted report, by the precedence started > resumed > action_required > updated (architecture D5). The record is the truth and the event name is why it was sent: a consumer that alerts on 'action required' reads the record's required_actions, because a first or a resumed report can carry lines under another event name.",
|
|
532
551
|
"type": "object",
|
|
533
552
|
"additionalProperties": false,
|
|
534
553
|
"required": [
|
|
@@ -540,7 +559,7 @@
|
|
|
540
559
|
"enum": [
|
|
541
560
|
"session.started",
|
|
542
561
|
"session.updated",
|
|
543
|
-
"session.
|
|
562
|
+
"session.action_required",
|
|
544
563
|
"session.stale",
|
|
545
564
|
"session.resumed",
|
|
546
565
|
"session.ended",
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import { createRequire as __createRequire } from "node:module";
|
|
|
3
3
|
const require = __createRequire(import.meta.url);
|
|
4
4
|
|
|
5
5
|
// src/index.ts
|
|
6
|
-
import { Command } from "commander";
|
|
6
|
+
import { Command, Option } from "commander";
|
|
7
7
|
import { fileURLToPath } from "url";
|
|
8
8
|
import { dirname as dirname15, join as join31 } from "path";
|
|
9
9
|
|
|
@@ -8419,7 +8419,7 @@ var PRESENCE_ENV = { port: "ARCANE_PRESENCE_PORT", token: "ARCANE_PRESENCE_TOKEN
|
|
|
8419
8419
|
var LOOPBACK_HOST = "127.0.0.1";
|
|
8420
8420
|
var MAX_BODY_BYTES = 65536;
|
|
8421
8421
|
var MAX_LINE_CHARS = 256;
|
|
8422
|
-
var
|
|
8422
|
+
var MAX_REQUIRED_ACTIONS = 20;
|
|
8423
8423
|
var MAX_SUBSCRIBERS = 32;
|
|
8424
8424
|
var MAX_SUBSCRIBER_BUFFER_BYTES = 1048576;
|
|
8425
8425
|
var MAX_CONNECTIONS = 128;
|
|
@@ -8438,7 +8438,7 @@ var ALIAS_CONTEXTS = ["openclaw", "slug"];
|
|
|
8438
8438
|
var SESSION_EVENTS = [
|
|
8439
8439
|
"session.started",
|
|
8440
8440
|
"session.updated",
|
|
8441
|
-
"session.
|
|
8441
|
+
"session.action_required",
|
|
8442
8442
|
"session.stale",
|
|
8443
8443
|
"session.resumed",
|
|
8444
8444
|
"session.ended",
|
|
@@ -11255,11 +11255,11 @@ function detectHarness(env) {
|
|
|
11255
11255
|
}
|
|
11256
11256
|
|
|
11257
11257
|
// src/modules/presence/reporter.ts
|
|
11258
|
-
var
|
|
11259
|
-
function
|
|
11260
|
-
const at = line2.indexOf(
|
|
11258
|
+
var REQUIRED_ACTION_SEPARATOR = " \u2014 ";
|
|
11259
|
+
function parseRequiredAction(line2) {
|
|
11260
|
+
const at = line2.indexOf(REQUIRED_ACTION_SEPARATOR);
|
|
11261
11261
|
if (at < 0) return { action: line2, reason: null };
|
|
11262
|
-
return { action: line2.slice(0, at), reason: line2.slice(at +
|
|
11262
|
+
return { action: line2.slice(0, at), reason: line2.slice(at + REQUIRED_ACTION_SEPARATOR.length) };
|
|
11263
11263
|
}
|
|
11264
11264
|
async function quiet(fn) {
|
|
11265
11265
|
try {
|
|
@@ -11315,7 +11315,7 @@ async function sendReport(cwd, flags, deps = {}) {
|
|
|
11315
11315
|
agent: agent2,
|
|
11316
11316
|
repository: located.repository,
|
|
11317
11317
|
checkout: located.checkout,
|
|
11318
|
-
|
|
11318
|
+
required_actions: (flags.requiredActions ?? []).map(parseRequiredAction)
|
|
11319
11319
|
};
|
|
11320
11320
|
if (flags.spell !== void 0) body["spell"] = flags.spell;
|
|
11321
11321
|
if (flags.task !== void 0) body["task"] = flags.task;
|
|
@@ -11364,7 +11364,7 @@ var PRESENCE_TOOLS = [
|
|
|
11364
11364
|
task: { ...LINE, description: "What the session is doing, in one line." },
|
|
11365
11365
|
spell: { ...LINE, description: "The spell running, e.g. spell-implement." },
|
|
11366
11366
|
message: { ...LINE, description: "A free one-line note; an empty string clears it." },
|
|
11367
|
-
|
|
11367
|
+
required_actions: { type: "array", maxItems: 20, items: { type: "string" }, description: "Lines of the Now tier of an Action required block, '<action> \u2014 <reason>'." },
|
|
11368
11368
|
agent: { type: "string", description: "The roster agent (a slug or an alias); default: the orchestrator." },
|
|
11369
11369
|
harness: { type: "string", enum: [...HARNESSES] },
|
|
11370
11370
|
cwd: { type: "string", description: "The checkout to report for; default: the server's working directory." }
|
|
@@ -11421,16 +11421,16 @@ function serveMcp(options) {
|
|
|
11421
11421
|
const report = async (args) => {
|
|
11422
11422
|
const status = stringArg(args, "status");
|
|
11423
11423
|
if (status === void 0) return text("status is required: one of working, waiting, blocked, idle.", true);
|
|
11424
|
-
const needs = args["
|
|
11424
|
+
const needs = args["required_actions"];
|
|
11425
11425
|
if (needs !== void 0 && (!Array.isArray(needs) || needs.some((n) => typeof n !== "string"))) {
|
|
11426
|
-
return text("
|
|
11426
|
+
return text("required_actions must be a list of strings.", true);
|
|
11427
11427
|
}
|
|
11428
11428
|
const flags = { status };
|
|
11429
11429
|
for (const key of ["task", "spell", "message", "agent", "harness"]) {
|
|
11430
11430
|
const v = stringArg(args, key);
|
|
11431
11431
|
if (v !== void 0) flags[key] = v;
|
|
11432
11432
|
}
|
|
11433
|
-
if (Array.isArray(needs)) flags.
|
|
11433
|
+
if (Array.isArray(needs)) flags.requiredActions = needs;
|
|
11434
11434
|
if (resolveEndpoint(deps) === null) return text(NOT_CONFIGURED_TEXT);
|
|
11435
11435
|
const where = stringArg(args, "cwd") ?? cwd;
|
|
11436
11436
|
const outcome = await sendReport(where, flags, deps);
|
|
@@ -11816,13 +11816,14 @@ function checkout(value) {
|
|
|
11816
11816
|
}
|
|
11817
11817
|
return { path: nonEmpty(c["path"], "checkout.path"), branch: line(c["branch"], "checkout.branch", true), linked_worktree: c["linked_worktree"] };
|
|
11818
11818
|
}
|
|
11819
|
-
|
|
11820
|
-
|
|
11821
|
-
if (value
|
|
11822
|
-
|
|
11819
|
+
var LEGACY_REQUIRED_ACTIONS_KEY = "needs_you";
|
|
11820
|
+
function requiredActions(value, key = "required_actions") {
|
|
11821
|
+
if (!Array.isArray(value)) throw new Refusal("invalid_field", key, `${key} must be a list`);
|
|
11822
|
+
if (value.length > MAX_REQUIRED_ACTIONS) {
|
|
11823
|
+
throw new Refusal("too_many_lines", key, `${key} carries at most ${MAX_REQUIRED_ACTIONS} lines; ${value.length} were sent`);
|
|
11823
11824
|
}
|
|
11824
11825
|
return value.map((item, i) => {
|
|
11825
|
-
const field =
|
|
11826
|
+
const field = `${key}.${i}`;
|
|
11826
11827
|
const n = closed(item, field, ["action", "reason"]);
|
|
11827
11828
|
return { action: nonEmptyLine(n["action"], `${field}.action`), reason: line(n["reason"], `${field}.reason`, true) };
|
|
11828
11829
|
});
|
|
@@ -11839,7 +11840,7 @@ function refuse(fn) {
|
|
|
11839
11840
|
}
|
|
11840
11841
|
function validateReport(value) {
|
|
11841
11842
|
return refuse(() => {
|
|
11842
|
-
const b = closed(value, void 0, ["status", "harness", "agent", "repository", "checkout"], ["spell", "task", "message", "
|
|
11843
|
+
const b = closed(value, void 0, ["status", "harness", "agent", "repository", "checkout"], ["spell", "task", "message", "required_actions", LEGACY_REQUIRED_ACTIONS_KEY]);
|
|
11843
11844
|
if (b["status"] === "done") {
|
|
11844
11845
|
throw new Refusal("invalid_field", "status", "status done is set by ending a session: use POST /v0/sessions/end (spell presence end)");
|
|
11845
11846
|
}
|
|
@@ -11853,7 +11854,11 @@ function validateReport(value) {
|
|
|
11853
11854
|
for (const key of ["spell", "task", "message"]) {
|
|
11854
11855
|
if (key in b) out[key] = line(b[key], key, true);
|
|
11855
11856
|
}
|
|
11856
|
-
if ("
|
|
11857
|
+
if ("required_actions" in b && LEGACY_REQUIRED_ACTIONS_KEY in b) {
|
|
11858
|
+
throw new Refusal("invalid_field", LEGACY_REQUIRED_ACTIONS_KEY, "send required_actions only: needs_you is its name before 1.20");
|
|
11859
|
+
}
|
|
11860
|
+
if ("required_actions" in b) out.required_actions = requiredActions(b["required_actions"]);
|
|
11861
|
+
else if (LEGACY_REQUIRED_ACTIONS_KEY in b) out.required_actions = requiredActions(b[LEGACY_REQUIRED_ACTIONS_KEY], LEGACY_REQUIRED_ACTIONS_KEY);
|
|
11857
11862
|
return out;
|
|
11858
11863
|
});
|
|
11859
11864
|
}
|
|
@@ -12095,7 +12100,7 @@ data: ${JSON.stringify(hello)}
|
|
|
12095
12100
|
function copy(record) {
|
|
12096
12101
|
return structuredClone(record);
|
|
12097
12102
|
}
|
|
12098
|
-
function
|
|
12103
|
+
function sameRequiredActions(a, b) {
|
|
12099
12104
|
if (a.length !== b.length) return false;
|
|
12100
12105
|
return a.every((item, i) => item.action === b[i]?.action && item.reason === b[i]?.reason);
|
|
12101
12106
|
}
|
|
@@ -12120,7 +12125,7 @@ function createSessionTable(options) {
|
|
|
12120
12125
|
const ms = now();
|
|
12121
12126
|
const at = new Date(ms).toISOString();
|
|
12122
12127
|
const key = body.checkout.path;
|
|
12123
|
-
const
|
|
12128
|
+
const requiredActions2 = (body.required_actions ?? []).map((n) => ({ action: n.action, reason: n.reason }));
|
|
12124
12129
|
const existing = entries.get(key);
|
|
12125
12130
|
if (existing === void 0) {
|
|
12126
12131
|
const record2 = {
|
|
@@ -12133,7 +12138,7 @@ function createSessionTable(options) {
|
|
|
12133
12138
|
task: body.task ?? null,
|
|
12134
12139
|
message: body.message ?? null,
|
|
12135
12140
|
status: body.status,
|
|
12136
|
-
|
|
12141
|
+
required_actions: requiredActions2,
|
|
12137
12142
|
started_at: at,
|
|
12138
12143
|
reported_at: at,
|
|
12139
12144
|
ended_at: null,
|
|
@@ -12145,7 +12150,7 @@ function createSessionTable(options) {
|
|
|
12145
12150
|
}
|
|
12146
12151
|
const record = existing.record;
|
|
12147
12152
|
const wasStale = record.stale;
|
|
12148
|
-
const needsChanged = !
|
|
12153
|
+
const needsChanged = !sameRequiredActions(record.required_actions, requiredActions2);
|
|
12149
12154
|
record.agent = structuredClone(body.agent);
|
|
12150
12155
|
record.harness = body.harness;
|
|
12151
12156
|
record.repository = structuredClone(body.repository);
|
|
@@ -12154,11 +12159,11 @@ function createSessionTable(options) {
|
|
|
12154
12159
|
if (body.task !== void 0) record.task = body.task;
|
|
12155
12160
|
if (body.message !== void 0) record.message = body.message;
|
|
12156
12161
|
record.status = body.status;
|
|
12157
|
-
record.
|
|
12162
|
+
record.required_actions = requiredActions2;
|
|
12158
12163
|
record.reported_at = at;
|
|
12159
12164
|
record.stale = false;
|
|
12160
12165
|
existing.reportedMs = ms;
|
|
12161
|
-
const event = wasStale ? "session.resumed" : needsChanged ? "session.
|
|
12166
|
+
const event = wasStale ? "session.resumed" : needsChanged ? "session.action_required" : "session.updated";
|
|
12162
12167
|
emit(event, record);
|
|
12163
12168
|
return { record: copy(record), created: false, event };
|
|
12164
12169
|
},
|
|
@@ -12380,7 +12385,7 @@ function renderSessionsTable(sessions, now) {
|
|
|
12380
12385
|
const out = [line2(header)];
|
|
12381
12386
|
sessions.forEach((s, i) => {
|
|
12382
12387
|
out.push(line2(rows[i]));
|
|
12383
|
-
for (const n of s.
|
|
12388
|
+
for (const n of s.required_actions) out.push(` action required: ${n.reason === null ? n.action : `${n.action} \u2014 ${n.reason}`}`);
|
|
12384
12389
|
});
|
|
12385
12390
|
return out;
|
|
12386
12391
|
}
|
|
@@ -12705,8 +12710,9 @@ function collectLine(value, previous) {
|
|
|
12705
12710
|
return [...previous, value];
|
|
12706
12711
|
}
|
|
12707
12712
|
var exitZero = () => process.exit(0);
|
|
12708
|
-
presenceCommand.command("report").description("Report this checkout's session to the presence service. Silent, exit 0, when no channel is configured; never blocks").requiredOption("--status <status>", "working | waiting | blocked | idle").option("--task <line>", "What the session is doing, in one line").option("--spell <id>", "The spell running, e.g. spell-implement").option("--message <line>", "A free one-line note; an empty value clears it").option("--
|
|
12709
|
-
const
|
|
12713
|
+
presenceCommand.command("report").description("Report this checkout's session to the presence service. Silent, exit 0, when no channel is configured; never blocks").requiredOption("--status <status>", "working | waiting | blocked | idle").option("--task <line>", "What the session is doing, in one line").option("--spell <id>", "The spell running, e.g. spell-implement").option("--message <line>", "A free one-line note; an empty value clears it").option("--required-action <line>", "One open line of the Now tier of an Action required block, '<action> \u2014 <reason>'; repeat per line", collectLine, []).addOption(new Option("--needs-you <line>").argParser(collectLine).default([]).hideHelp()).option("--agent <slug>", "The roster agent this session reports as (a slug or an alias); default: the orchestrator").option("--harness <id>", "claude-code | copilot | codex | openclaw | cron | other; default: detected, else other").exitOverride(exitZero).action(async (opts) => {
|
|
12714
|
+
const { requiredAction, needsYou, ...rest } = opts;
|
|
12715
|
+
const run = await runPresenceReport(process.cwd(), { ...rest, requiredActions: [...requiredAction, ...needsYou] });
|
|
12710
12716
|
process.exitCode = 0;
|
|
12711
12717
|
if (run.timedOut) process.exit(0);
|
|
12712
12718
|
});
|