acstack 0.50.2 → 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.
@@ -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.2",
4
+ "version": "0.50.3",
5
5
  "author": {
6
6
  "name": "ac8318740"
7
7
  },
@@ -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 && say "herdr $integ state hook installed"
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
- && say "herdr updated" || say "herdr update failed" ;;
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 \
package/cli/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "acstack-cli",
3
- "version": "0.50.2",
3
+ "version": "0.50.3",
4
4
  "description": "CLI for the ACStack agentic coding stack",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -396,6 +396,14 @@ What `apply` does:
396
396
  - It writes the four keys under `pi.settings` into `~/.pi/agent/settings.json`
397
397
  - It patches pi's bundled TUI so Windows Terminal Win32 input records paste as text
398
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
399
407
 
400
408
  How it treats pi's files:
401
409
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "acstack",
3
- "version": "0.50.2",
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",