acstack 0.50.1 → 0.50.3
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/.claude-plugin/plugin.json +1 -1
- package/README.md +3 -0
- package/assets/terminal-workspace/lib/apply-herdr.sh +4 -1
- package/assets/terminal-workspace/lib/commands.sh +1 -0
- package/assets/terminal-workspace/lib/patch-herdr-pi.py +86 -0
- package/assets/terminal-workspace/lib/pi-packages.sh +2 -0
- package/assets/terminal-workspace/lib/pi-state.sh +6 -0
- package/assets/terminal-workspace/lib/versions.sh +6 -2
- package/assets/terminal-workspace/pi/acstack-question-blocked.ts +13 -25
- package/cli/package.json +1 -1
- package/docs/terminal-workspace.md +14 -3
- package/package.json +3 -2
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "acstack",
|
|
3
3
|
"description": "Agentic coding stack where planning grows only as far as needed: a map of decisions, tasks, checks, and handoffs behind a pluggable tracker.",
|
|
4
|
-
"version": "0.50.
|
|
4
|
+
"version": "0.50.3",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "ac8318740"
|
|
7
7
|
},
|
package/README.md
CHANGED
|
@@ -91,6 +91,9 @@ Then, in your project, run `/acstack:setup`.
|
|
|
91
91
|
- Freshness uses `/acstack:handoff` when a fresh window or independent read helps more
|
|
92
92
|
- With no thresholds set, the first nudge comes at 200k tokens or half the window, whichever is lower
|
|
93
93
|
- The urgent nudge comes at 500k tokens or 80% of the window, whichever is lower
|
|
94
|
+
- ACStack includes the question-status bridge automatically
|
|
95
|
+
- With `@juicesharp/rpiv-ask-user-question` and Herdr's Pi integration installed, Herdr shows blocked while Pi waits for an answer
|
|
96
|
+
- Existing setup-installed bridges can stay installed without counting the same wait twice
|
|
94
97
|
- Both subagent packages find the four agents through the `pi.subagents` field in `package.json`
|
|
95
98
|
- `@tintinweb/pi-subagents` exposes `Agent`; `pi-subagents` exposes `subagent`
|
|
96
99
|
- The agents use your subagent package's default model
|
|
@@ -303,6 +303,9 @@ PY
|
|
|
303
303
|
|
|
304
304
|
local integ; integ=$(cfg_get herdr.integration none)
|
|
305
305
|
if [ "$integ" != "none" ] && [ -n "$integ" ]; then
|
|
306
|
-
herdr integration install "$integ" >/dev/null 2>&1
|
|
306
|
+
if herdr integration install "$integ" >/dev/null 2>&1; then
|
|
307
|
+
say "herdr $integ state hook installed"
|
|
308
|
+
patch_herdr_pi_status
|
|
309
|
+
fi
|
|
307
310
|
fi
|
|
308
311
|
}
|
|
@@ -189,6 +189,7 @@ PY
|
|
|
189
189
|
say "delta unset as git pager"
|
|
190
190
|
say "now set delta.enabled: false in $CFG so apply does not restore it" ;;
|
|
191
191
|
herdr)
|
|
192
|
+
patch_herdr_pi_status --remove
|
|
192
193
|
py "$HERDR_CFG" "$BEGIN" "$END" <<'PY'
|
|
193
194
|
import re, sys, os
|
|
194
195
|
p, b, e = sys.argv[1:4]
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""Keep herdr's single Pi status writer aware of background subagents."""
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
import tempfile
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
path = Path(sys.argv[1])
|
|
8
|
+
remove = sys.argv[2:] == ["--remove"]
|
|
9
|
+
if not path.exists():
|
|
10
|
+
sys.exit(0)
|
|
11
|
+
if path.is_symlink() or not path.is_file():
|
|
12
|
+
sys.exit(" pi: herdr status patch skipped: reporter is not a regular file")
|
|
13
|
+
|
|
14
|
+
begin = "// >>> acstack terminal-workspace: background status >>>"
|
|
15
|
+
end = "// <<< acstack terminal-workspace: background status <<<"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def marked(text):
|
|
19
|
+
return f" {begin}\n{text} {end}\n"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
replacements = [
|
|
23
|
+
(" let agentActive = false;\n", " let agentActive = false;\n" + marked(''' // shortcut: no workflow/reload replay; extend when Tintin exposes active-run snapshots.
|
|
24
|
+
const backgroundAgents = new Set<string>();
|
|
25
|
+
let busyCount = 0;
|
|
26
|
+
''')),
|
|
27
|
+
(" if (agentActive) {\n", marked(''' // blocked > active parent or children > idle
|
|
28
|
+
if (agentActive || backgroundAgents.size > 0 || busyCount > 0) {
|
|
29
|
+
''')),
|
|
30
|
+
(' pi.events.on("herdr:blocked", (data) => {\n', marked(''' // Tintin's in-process children share this bus, but never own the pane status.
|
|
31
|
+
pi.events.on("subagents:started", (data) => {
|
|
32
|
+
if (!rootSession || typeof data?.id !== "string" || !data.id) return;
|
|
33
|
+
backgroundAgents.add(data.id);
|
|
34
|
+
publishState();
|
|
35
|
+
});
|
|
36
|
+
for (const event of ["subagents:completed", "subagents:failed"]) {
|
|
37
|
+
pi.events.on(event, (data) => {
|
|
38
|
+
if (!rootSession || typeof data?.id !== "string") return;
|
|
39
|
+
backgroundAgents.delete(data.id);
|
|
40
|
+
publishState();
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
// nicobailon/pi-subagents uses balanced active intervals instead of IDs.
|
|
44
|
+
pi.events.on("herdr:busy", (data) => {
|
|
45
|
+
if (!rootSession || typeof data?.active !== "boolean") return;
|
|
46
|
+
busyCount = Math.max(0, busyCount + (data.active ? 1 : -1));
|
|
47
|
+
publishState();
|
|
48
|
+
});
|
|
49
|
+
''') + ' pi.events.on("herdr:blocked", (data) => {\n'),
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
text = path.read_text()
|
|
53
|
+
if not text.startswith("// installed by herdr\n") or "HERDR_INTEGRATION_ID=pi\n" not in text:
|
|
54
|
+
sys.exit(" pi: herdr status patch skipped: unrecognised reporter")
|
|
55
|
+
# These lifecycle guards must still exist before adding shared-bus listeners.
|
|
56
|
+
required = [
|
|
57
|
+
' pi.on("agent_settled", (_event, ctx) => {',
|
|
58
|
+
' if (ctx?.mode !== "tui") {',
|
|
59
|
+
' if (!rootSession || ctx?.isIdle?.() !== true) {',
|
|
60
|
+
]
|
|
61
|
+
if any(text.count(anchor) != 1 for anchor in required):
|
|
62
|
+
sys.exit(" pi: herdr status patch skipped: upstream lifecycle changed")
|
|
63
|
+
patched = begin in text
|
|
64
|
+
if patched == (not remove):
|
|
65
|
+
# Validate every managed block before treating an existing patch as ours.
|
|
66
|
+
if patched and any(text.count(new) != 1 for _, new in replacements):
|
|
67
|
+
sys.exit(" pi: herdr status patch skipped: managed blocks were changed")
|
|
68
|
+
sys.exit(0)
|
|
69
|
+
|
|
70
|
+
for old, new in replacements:
|
|
71
|
+
if text.count(new if remove else old) != 1:
|
|
72
|
+
sys.exit(" pi: herdr status patch skipped: upstream layout changed")
|
|
73
|
+
for old, new in replacements:
|
|
74
|
+
text = text.replace(new, old, 1) if remove else text.replace(old, new, 1)
|
|
75
|
+
|
|
76
|
+
# Validate all matches first, then replace atomically so a failed patch leaves no partial writer.
|
|
77
|
+
fd, temporary = tempfile.mkstemp(dir=path.parent, prefix=".acstack-herdr-")
|
|
78
|
+
try:
|
|
79
|
+
with os.fdopen(fd, "w") as stream:
|
|
80
|
+
stream.write(text)
|
|
81
|
+
os.chmod(temporary, path.stat().st_mode)
|
|
82
|
+
os.replace(temporary, path)
|
|
83
|
+
finally:
|
|
84
|
+
if os.path.exists(temporary):
|
|
85
|
+
os.unlink(temporary)
|
|
86
|
+
print(f" pi: herdr background status patch {'removed' if remove else 'applied'}")
|
|
@@ -301,6 +301,7 @@ apply_pi() {
|
|
|
301
301
|
pi_state merge "$PI_SUBAGENT_CFG" subagent '{"toolActivation": "eager"}'
|
|
302
302
|
pi_state md write
|
|
303
303
|
patch_pi_win32_paste
|
|
304
|
+
patch_herdr_pi_status
|
|
304
305
|
|
|
305
306
|
# Logging in is interactive and stores a credential, so it stays the user's.
|
|
306
307
|
[ -f "$PI_DIR/auth.json" ] || say "pi: not logged in yet. Run pi, then /login"
|
|
@@ -311,6 +312,7 @@ apply_pi() {
|
|
|
311
312
|
# and leaves the pi binary, its logins and its sessions alone.
|
|
312
313
|
remove_pi() {
|
|
313
314
|
find_pi
|
|
315
|
+
patch_herdr_pi_status --remove
|
|
314
316
|
pi_question_bridge remove
|
|
315
317
|
if pi_state parses "$PI_SETTINGS"; then
|
|
316
318
|
pi_unpin "$PI_ACSTACK"
|
|
@@ -404,6 +404,12 @@ find_pi() {
|
|
|
404
404
|
}
|
|
405
405
|
|
|
406
406
|
|
|
407
|
+
# Keep herdr's own writer as the sole pane authority; reapply after it is replaced.
|
|
408
|
+
patch_herdr_pi_status() {
|
|
409
|
+
python3 "$SCRIPT_DIR/lib/patch-herdr-pi.py" "$PI_DIR/extensions/herdr-agent-state.ts" "$@" \
|
|
410
|
+
|| say "pi: herdr background status patch was not applied; reporter left unchanged"
|
|
411
|
+
}
|
|
412
|
+
|
|
407
413
|
# Pi's bundled TUI does not always receive Windows Terminal paste as plain
|
|
408
414
|
# text. Through Herdr and ConPTY it can arrive as Win32 input records such as
|
|
409
415
|
# ESC[13;28;13;1;0;1_. Patch the installed bundle after install or update.
|
|
@@ -204,8 +204,12 @@ upgrade_one() { # upgrade_one <name>
|
|
|
204
204
|
find_pi
|
|
205
205
|
fi
|
|
206
206
|
case "$1" in
|
|
207
|
-
herdr) herdr update >/dev/null 2>&1
|
|
208
|
-
|
|
207
|
+
herdr) if herdr update >/dev/null 2>&1; then
|
|
208
|
+
say "herdr updated"
|
|
209
|
+
patch_herdr_pi_status
|
|
210
|
+
else
|
|
211
|
+
say "herdr update failed"
|
|
212
|
+
fi ;;
|
|
209
213
|
gh-dash) gh extension upgrade dlvhdr/gh-dash >/dev/null 2>&1 \
|
|
210
214
|
&& say "gh-dash upgraded" || say "gh-dash upgrade failed" ;;
|
|
211
215
|
harlequin) uv tool upgrade harlequin >/dev/null 2>&1 \
|
|
@@ -1,31 +1,19 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
let pending: { active: boolean; native: boolean } | undefined;
|
|
5
|
-
let forwarding = false;
|
|
6
|
-
|
|
7
|
-
pi.events.on("herdr:blocked", (event: unknown) => {
|
|
8
|
-
if (!forwarding && pending && (event as { active?: unknown } | null)?.active === pending.active) {
|
|
9
|
-
pending.native = true;
|
|
10
|
-
pending = undefined;
|
|
11
|
-
}
|
|
12
|
-
});
|
|
3
|
+
const TAKEOVER = "acstack:question-blocked:takeover";
|
|
13
4
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
if (
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
} finally {
|
|
27
|
-
forwarding = false;
|
|
28
|
-
}
|
|
5
|
+
export default function (pi: ExtensionAPI) {
|
|
6
|
+
pi.on("session_start", () => {
|
|
7
|
+
pi.events.emit(TAKEOVER, {});
|
|
8
|
+
// Register after legacy factories so our synchronous relay suppresses their queued copy.
|
|
9
|
+
// shortcut: legacy compatibility excludes recursively emitted public events; remove the legacy copy if needed
|
|
10
|
+
const stopRelay = pi.events.on("rpiv:ask-user:blocked", (event: unknown) => {
|
|
11
|
+
const active = (event as { active?: unknown } | null)?.active;
|
|
12
|
+
if (typeof active === "boolean") pi.events.emit("herdr:blocked", { active });
|
|
13
|
+
});
|
|
14
|
+
const stopTakeover = pi.events.on(TAKEOVER, () => {
|
|
15
|
+
stopRelay();
|
|
16
|
+
stopTakeover();
|
|
29
17
|
});
|
|
30
18
|
});
|
|
31
19
|
}
|
package/cli/package.json
CHANGED
|
@@ -365,11 +365,14 @@ What `apply` does:
|
|
|
365
365
|
- ACStack uses it because decisions can include markdown, code and artifact previews
|
|
366
366
|
- It accepts freeform answers and notes, and needs no full-event telemetry environment variable
|
|
367
367
|
- This setup still keeps pi's install telemetry disabled through `pi.settings.install_telemetry: false`
|
|
368
|
-
-
|
|
368
|
+
- ACStack's Pi package loads the question bridge automatically, including installations made without terminal-workspace setup
|
|
369
|
+
- Setup also installs a fallback copy under pi's agent directory, honoring `PI_CODING_AGENT_DIR`
|
|
370
|
+
- The packaged bridge and fallback copy count each wait once when both load
|
|
369
371
|
- The bridge translates `rpiv:ask-user:blocked` into `herdr:blocked`, so herdr shows the question's input wait
|
|
370
372
|
- Answer, cancellation, and error completion release the question's blocker while other herdr blockers stay active
|
|
371
|
-
-
|
|
372
|
-
-
|
|
373
|
+
- Anonymous native Herdr events remain independent from question events, including simultaneous waits
|
|
374
|
+
- A producer emitting both event types contributes two balanced blockers, which clear when both sources finish
|
|
375
|
+
- Older fallback copies work unless another extension recursively emits public question events; remove the older copy in that case
|
|
373
376
|
- Reapply refreshes the owned bridge, but preserves a pre-existing file at the same path
|
|
374
377
|
- `pi.question_tool: false`, `disable pi`, and `uninstall` remove only the owned bridge and leave user workarounds alone
|
|
375
378
|
- Set `pi.question_tool: false` to leave the package out, or remove it later if this setup installed it
|
|
@@ -393,6 +396,14 @@ What `apply` does:
|
|
|
393
396
|
- It writes the four keys under `pi.settings` into `~/.pi/agent/settings.json`
|
|
394
397
|
- It patches pi's bundled TUI so Windows Terminal Win32 input records paste as text
|
|
395
398
|
- `pi update` can replace the bundle, so `setup.sh upgrade pi` reapplies the patch
|
|
399
|
+
- It patches an existing herdr Pi reporter to keep the parent working while background subagents run
|
|
400
|
+
- Questions still show blocked, even when children are active
|
|
401
|
+
- The patch handles `pi-subagents` busy events and `@tintinweb/pi-subagents` Agent lifecycle events
|
|
402
|
+
- Tintin workflows and children already running during an extension reload lack lifecycle replay and remain outside this tracking
|
|
403
|
+
- `apply`, herdr integration installation, and `setup.sh upgrade herdr` reapply the patch
|
|
404
|
+
- Direct `herdr integration install pi` overwrites the patch; run `apply` afterward, then restart Pi or use `/reload`
|
|
405
|
+
- Setup leaves unknown reporter layouts unchanged and reports the skipped patch
|
|
406
|
+
- `disable pi`, `disable herdr`, and `uninstall` remove unchanged managed patch blocks
|
|
396
407
|
|
|
397
408
|
How it treats pi's files:
|
|
398
409
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "acstack",
|
|
3
|
-
"version": "0.50.
|
|
3
|
+
"version": "0.50.3",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Agentic coding stack with first-class Claude Code and pi support: skills, orchestrator instructions, and verification agents.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -38,7 +38,8 @@
|
|
|
38
38
|
],
|
|
39
39
|
"pi": {
|
|
40
40
|
"extensions": [
|
|
41
|
-
"./pi/extension.ts"
|
|
41
|
+
"./pi/extension.ts",
|
|
42
|
+
"./assets/terminal-workspace/pi/acstack-question-blocked.ts"
|
|
42
43
|
],
|
|
43
44
|
"subagents": {
|
|
44
45
|
"agents": [
|