acstack 0.50.2 → 0.50.4
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/assets/terminal-workspace/lib/apply-herdr.sh +4 -1
- package/assets/terminal-workspace/lib/apply-yazi.sh +25 -0
- 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/cli/package.json +1 -1
- package/docs/terminal-workspace-keys.md +1 -0
- package/docs/terminal-workspace.md +8 -0
- package/package.json +1 -1
- package/skills/terminal-workspace/SKILL.md +1 -0
|
@@ -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.4",
|
|
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
|
|
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
|
}
|
|
@@ -288,11 +288,36 @@ def drop_claimed(t):
|
|
|
288
288
|
return "\n".join(out)
|
|
289
289
|
|
|
290
290
|
|
|
291
|
+
def inspect_available(t):
|
|
292
|
+
# Preserve custom bindings, including sequences beginning with i.
|
|
293
|
+
if "i" in CLAIMED or tomllib is None:
|
|
294
|
+
return False
|
|
295
|
+
try:
|
|
296
|
+
data = tomllib.loads(t)
|
|
297
|
+
except ValueError:
|
|
298
|
+
return False
|
|
299
|
+
for section in ("mgr", "manager"):
|
|
300
|
+
for name in ("prepend_keymap", "keymap", "append_keymap"):
|
|
301
|
+
for binding in data.get(section, {}).get(name, []):
|
|
302
|
+
on = binding.get("on", [])
|
|
303
|
+
if on == "i" or isinstance(on, list) and on[:1] == ["i"]:
|
|
304
|
+
return False
|
|
305
|
+
return True
|
|
306
|
+
|
|
307
|
+
|
|
291
308
|
parts = []
|
|
292
309
|
if not claimed(text):
|
|
293
310
|
# A binding conceded below is one this script never writes, so the user's
|
|
294
311
|
# own must survive. Only strip when the bindings are actually going in.
|
|
312
|
+
inspect = inspect_available(text)
|
|
295
313
|
text = drop_claimed(text)
|
|
314
|
+
if inspect:
|
|
315
|
+
parts.append(
|
|
316
|
+
"[[mgr.prepend_keymap]]\n"
|
|
317
|
+
'on = "i"\n'
|
|
318
|
+
"""run = 'shell --block -- case %h in *.[mM][dD]|*.[mM][aA][rR][kK][dD][oO][wW][nN]) frogmouth -- %h ;; esac'\n"""
|
|
319
|
+
'desc = "Inspect Markdown in Frogmouth"'
|
|
320
|
+
)
|
|
296
321
|
# %h is the hovered file. $0 and $@ are not: yazi runs the template through
|
|
297
322
|
# a shell, where $0 names the shell itself and $@ is empty - both measured,
|
|
298
323
|
# and both would open the browser on nothing at all.
|
|
@@ -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 \
|
package/cli/package.json
CHANGED
|
@@ -74,6 +74,7 @@ Add `shift` to any of these to open it in a full tab instead of a popup.
|
|
|
74
74
|
| Key | Does |
|
|
75
75
|
|---|---|
|
|
76
76
|
| `e` | edit in `$EDITOR` |
|
|
77
|
+
| `i` | inspect Markdown in Frogmouth (install with `pipx install frogmouth`; existing `i` bindings take precedence) |
|
|
77
78
|
| `o` | open by rule, which for markdown is the reader |
|
|
78
79
|
| `b` | open in the browser you are sitting at |
|
|
79
80
|
| `D` | send the file to your laptop over Taildrop |
|
|
@@ -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
|
@@ -79,6 +79,7 @@ The two files this skill works with:
|
|
|
79
79
|
- **Config**: `~/.config/acstack/terminal-workspace.yaml`, copied from `assets/terminal-workspace/config.example.yaml`
|
|
80
80
|
- **Script**: `assets/terminal-workspace/setup.sh` in this plugin
|
|
81
81
|
|
|
82
|
+
Yazi's `i` opens Markdown in Frogmouth (`pipx install frogmouth`); existing `i` bindings take precedence, and `e` keeps editing in `$EDITOR`.
|
|
82
83
|
The keys, on one page: [docs/terminal-workspace-keys.md](../../docs/terminal-workspace-keys.md).
|
|
83
84
|
|
|
84
85
|
Background and why each piece is there:
|