@imdeadpool/guardex 7.0.41 → 7.1.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.
Files changed (118) hide show
  1. package/README.md +94 -13
  2. package/package.json +3 -1
  3. package/skills/gitguardex/SKILL.md +13 -0
  4. package/skills/guardex-merge-skills-to-dev/SKILL.md +59 -0
  5. package/skills/gx-act/SKILL.md +82 -0
  6. package/src/agents/cleanup-sessions.js +126 -0
  7. package/src/agents/finish.js +172 -0
  8. package/src/agents/inspect.js +202 -0
  9. package/src/agents/launch.js +249 -0
  10. package/src/agents/registry.js +133 -0
  11. package/src/agents/selection-panel.js +571 -0
  12. package/src/agents/sessions.js +151 -0
  13. package/src/agents/start.js +591 -0
  14. package/src/agents/status.js +146 -0
  15. package/src/agents/terminal.js +152 -0
  16. package/src/budget/index.js +344 -0
  17. package/src/ci-init/index.js +265 -0
  18. package/src/cli/args.js +357 -3
  19. package/src/cli/commands/agents.js +364 -0
  20. package/src/cli/commands/bootstrap.js +92 -0
  21. package/src/cli/commands/branch.js +127 -0
  22. package/src/cli/commands/claude.js +674 -0
  23. package/src/cli/commands/doctor.js +268 -0
  24. package/src/cli/commands/finish.js +26 -0
  25. package/src/cli/commands/mcp.js +122 -0
  26. package/src/cli/commands/misc.js +304 -0
  27. package/src/cli/commands/pr.js +439 -0
  28. package/src/cli/commands/prompt.js +92 -0
  29. package/src/cli/commands/release.js +305 -0
  30. package/src/cli/commands/report.js +244 -0
  31. package/src/cli/commands/review.js +32 -0
  32. package/src/cli/commands/setup.js +242 -0
  33. package/src/cli/commands/status.js +338 -0
  34. package/src/cli/commands/watch.js +234 -0
  35. package/src/cli/main.js +85 -3613
  36. package/src/cli/shared/repo-env.js +161 -0
  37. package/src/cli/shared/sandbox.js +417 -0
  38. package/src/cli/shared/scaffolding.js +535 -0
  39. package/src/cli/shared/toolchain-shims.js +420 -0
  40. package/src/cockpit/action-runner.js +3 -0
  41. package/src/cockpit/actions.js +80 -0
  42. package/src/cockpit/control.js +1121 -0
  43. package/src/cockpit/index.js +426 -0
  44. package/src/cockpit/kitty-layout.js +549 -0
  45. package/src/cockpit/kitty-tree.js +144 -0
  46. package/src/cockpit/logs-reader.js +182 -0
  47. package/src/cockpit/menu.js +204 -0
  48. package/src/cockpit/pane-actions.js +597 -0
  49. package/src/cockpit/pane-menu.js +387 -0
  50. package/src/cockpit/projects-finder.js +178 -0
  51. package/src/cockpit/render.js +215 -0
  52. package/src/cockpit/settings-render.js +128 -0
  53. package/src/cockpit/settings.js +124 -0
  54. package/src/cockpit/shortcuts.js +24 -0
  55. package/src/cockpit/sidebar.js +311 -0
  56. package/src/cockpit/state.js +72 -0
  57. package/src/cockpit/theme.js +128 -0
  58. package/src/cockpit/welcome.js +266 -0
  59. package/src/context.js +304 -43
  60. package/src/core/runtime.js +6 -1
  61. package/src/doctor/index.js +45 -15
  62. package/src/finish/index.js +186 -7
  63. package/src/finish/preflight.js +177 -0
  64. package/src/finish/review-gate.js +182 -0
  65. package/src/git/index.js +511 -4
  66. package/src/hooks/index.js +0 -64
  67. package/src/kitty/command.js +101 -0
  68. package/src/kitty/runtime.js +250 -0
  69. package/src/mcp/collect.js +370 -0
  70. package/src/mcp/server.js +157 -0
  71. package/src/output/index.js +68 -2
  72. package/src/pr-review.js +264 -0
  73. package/src/pr.js +381 -0
  74. package/src/sandbox/index.js +13 -2
  75. package/src/scaffold/agent-worktree-prep.js +213 -0
  76. package/src/scaffold/index.js +127 -10
  77. package/src/speckit/index.js +226 -0
  78. package/src/submodule/index.js +288 -0
  79. package/src/terminal/index.js +45 -0
  80. package/src/terminal/kitty.js +622 -0
  81. package/src/terminal/tmux.js +125 -0
  82. package/src/tmux/command.js +27 -0
  83. package/src/tmux/session.js +89 -0
  84. package/src/toolchain/index.js +20 -0
  85. package/templates/AGENTS.monorepo-apps.md +26 -0
  86. package/templates/AGENTS.multiagent-safety.md +63 -323
  87. package/templates/AGENTS.multiagent-safety.min.md +11 -0
  88. package/templates/codex/skills/gitguardex/SKILL.md +2 -0
  89. package/templates/codex/skills/gx-act/SKILL.md +82 -0
  90. package/templates/githooks/pre-commit +44 -20
  91. package/templates/github/workflows/README.md +87 -0
  92. package/templates/github/workflows/ci-full.yml +55 -0
  93. package/templates/github/workflows/ci.yml +56 -0
  94. package/templates/github/workflows/cr.yml +20 -1
  95. package/templates/scripts/agent-branch-finish.sh +519 -23
  96. package/templates/scripts/agent-branch-merge.sh +4 -1
  97. package/templates/scripts/agent-branch-start.sh +176 -24
  98. package/templates/scripts/agent-preflight.sh +115 -0
  99. package/templates/scripts/agent-worktree-prune.sh +96 -5
  100. package/templates/scripts/codex-agent.sh +41 -97
  101. package/templates/scripts/openspec/init-plan-workspace.sh +43 -0
  102. package/templates/scripts/review-bot-watch.sh +31 -2
  103. package/templates/scripts/agent-session-state.js +0 -171
  104. package/templates/scripts/install-vscode-active-agents-extension.js +0 -135
  105. package/templates/vscode/guardex-active-agents/README.md +0 -34
  106. package/templates/vscode/guardex-active-agents/extension.js +0 -3782
  107. package/templates/vscode/guardex-active-agents/fileicons/gitguardex-fileicons.json +0 -54
  108. package/templates/vscode/guardex-active-agents/fileicons/icons/agent.svg +0 -5
  109. package/templates/vscode/guardex-active-agents/fileicons/icons/branch.svg +0 -7
  110. package/templates/vscode/guardex-active-agents/fileicons/icons/config.svg +0 -4
  111. package/templates/vscode/guardex-active-agents/fileicons/icons/hook.svg +0 -4
  112. package/templates/vscode/guardex-active-agents/fileicons/icons/openspec.svg +0 -5
  113. package/templates/vscode/guardex-active-agents/fileicons/icons/plan.svg +0 -4
  114. package/templates/vscode/guardex-active-agents/fileicons/icons/spec.svg +0 -5
  115. package/templates/vscode/guardex-active-agents/icon.png +0 -0
  116. package/templates/vscode/guardex-active-agents/media/active-agents-hivemind.svg +0 -14
  117. package/templates/vscode/guardex-active-agents/package.json +0 -169
  118. package/templates/vscode/guardex-active-agents/session-schema.js +0 -1348
@@ -6,6 +6,7 @@ AGENT_NAME="${GUARDEX_AGENT_NAME:-agent}"
6
6
  BASE_BRANCH="${GUARDEX_BASE_BRANCH:-}"
7
7
  BASE_BRANCH_EXPLICIT=0
8
8
  CODEX_BIN="${GUARDEX_CODEX_BIN:-codex}"
9
+ CODEX_APPROVAL_POLICY="${GUARDEX_CODEX_APPROVAL_POLICY-never}"
9
10
  NODE_BIN="${GUARDEX_NODE_BIN:-node}"
10
11
  CLI_ENTRY="${GUARDEX_CLI_ENTRY:-}"
11
12
  AUTO_FINISH_RAW="${GUARDEX_CODEX_AUTO_FINISH:-true}"
@@ -67,6 +68,29 @@ normalize_tier() {
67
68
  esac
68
69
  }
69
70
 
71
+ codex_args_include_approval_policy() {
72
+ local arg
73
+ for arg in "$@"; do
74
+ case "$arg" in
75
+ -a|--ask-for-approval|--approval-policy|--dangerously-bypass-approvals-and-sandbox)
76
+ return 0
77
+ ;;
78
+ --ask-for-approval=*|--approval-policy=*)
79
+ return 0
80
+ ;;
81
+ esac
82
+ done
83
+ return 1
84
+ }
85
+
86
+ build_codex_launch_args() {
87
+ CODEX_LAUNCH_ARGS=()
88
+ if [[ -n "$CODEX_APPROVAL_POLICY" ]] && ! codex_args_include_approval_policy "$@"; then
89
+ CODEX_LAUNCH_ARGS+=("-a" "$CODEX_APPROVAL_POLICY")
90
+ fi
91
+ CODEX_LAUNCH_ARGS+=("$@")
92
+ }
93
+
70
94
  string_contains_any() {
71
95
  local haystack="$1"
72
96
  shift
@@ -300,7 +324,6 @@ if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
300
324
  exit 1
301
325
  fi
302
326
  repo_root="$(git rev-parse --show-toplevel)"
303
- active_session_state_script="${repo_root}/scripts/agent-session-state.js"
304
327
 
305
328
  guardex_env_helper="${repo_root}/scripts/guardex-env.sh"
306
329
  if [[ -f "$guardex_env_helper" ]]; then
@@ -614,78 +637,6 @@ has_origin_remote() {
614
637
  git -C "$repo_root" remote get-url origin >/dev/null 2>&1
615
638
  }
616
639
 
617
- run_active_session_state() {
618
- local action="$1"
619
- shift
620
-
621
- if [[ ! -f "$active_session_state_script" ]]; then
622
- return 0
623
- fi
624
- if ! command -v "$NODE_BIN" >/dev/null 2>&1; then
625
- return 0
626
- fi
627
-
628
- "$NODE_BIN" "$active_session_state_script" "$action" "$@" >/dev/null 2>&1 || true
629
- }
630
-
631
- record_active_session_state() {
632
- local wt="$1"
633
- local branch="$2"
634
-
635
- run_active_session_state \
636
- start \
637
- --repo "$repo_root" \
638
- --branch "$branch" \
639
- --task "$TASK_NAME" \
640
- --agent "$AGENT_NAME" \
641
- --worktree "$wt" \
642
- --pid "$$" \
643
- --cli "$CODEX_BIN" \
644
- --task-mode "$TASK_MODE" \
645
- --openspec-tier "$OPENSPEC_TIER" \
646
- --routing-reason "$TASK_ROUTING_REASON"
647
- }
648
-
649
- clear_active_session_state() {
650
- local branch="$1"
651
- run_active_session_state stop --repo "$repo_root" --branch "$branch"
652
- }
653
-
654
- heartbeat_active_session_state() {
655
- local branch="$1"
656
- run_active_session_state heartbeat --repo "$repo_root" --branch "$branch" --state working
657
- }
658
-
659
- normalize_heartbeat_interval_seconds() {
660
- local raw="${GUARDEX_ACTIVE_SESSION_HEARTBEAT_INTERVAL_SECONDS:-15}"
661
- if [[ "$raw" =~ ^[0-9]+$ ]] && [[ "$raw" -ge 1 ]]; then
662
- printf '%s' "$raw"
663
- return 0
664
- fi
665
- printf '15'
666
- }
667
-
668
- start_active_session_heartbeat() {
669
- local branch="$1"
670
- local interval
671
- interval="$(normalize_heartbeat_interval_seconds)"
672
- (
673
- while true; do
674
- sleep "$interval" || break
675
- heartbeat_active_session_state "$branch"
676
- done
677
- ) &
678
- active_session_heartbeat_pid="$!"
679
- }
680
-
681
- stop_active_session_heartbeat() {
682
- if [[ -n "${active_session_heartbeat_pid:-}" ]]; then
683
- kill "$active_session_heartbeat_pid" >/dev/null 2>&1 || true
684
- wait "$active_session_heartbeat_pid" >/dev/null 2>&1 || true
685
- active_session_heartbeat_pid=""
686
- fi
687
- }
688
-
689
640
  origin_remote_supports_pr_finish() {
690
641
  local origin_url
691
642
  origin_url="$(git -C "$repo_root" remote get-url origin 2>/dev/null || true)"
@@ -735,9 +686,14 @@ print_takeover_prompt() {
735
686
 
736
687
  finish_cmd="gx branch finish --branch \"${branch}\" --base ${base_branch} --via-pr --wait-for-merge --cleanup"
737
688
 
738
- echo "[codex-agent] Takeover sandbox: ${wt}"
739
- echo "[codex-agent] Takeover routing: $(describe_task_routing) (${TASK_ROUTING_REASON})"
740
- echo "[codex-agent] Takeover prompt: Continue \`${change_slug}\` on branch \`${branch}\`. Work inside \`${wt}\`, review \`${change_artifact}\`, continue from the current state instead of creating a new sandbox, and when the work is done run \`${finish_cmd}\`."
689
+ echo "[codex-agent] Resume this sandbox:"
690
+ echo " change: ${change_slug}"
691
+ echo " branch: ${branch}"
692
+ echo " worktree: ${wt}"
693
+ echo " spec: ${change_artifact}"
694
+ echo " routing: $(describe_task_routing) (${TASK_ROUTING_REASON})"
695
+ echo " rule: continue current state; do not create a new sandbox"
696
+ echo " finish: ${finish_cmd}"
741
697
  }
742
698
 
743
699
  sync_worktree_with_base() {
@@ -1034,7 +990,8 @@ run_finish_flow() {
1034
990
  (
1035
991
  cd "$wt"
1036
992
  set +e
1037
- "$CODEX_BIN" "$review_prompt"
993
+ build_codex_launch_args "$review_prompt"
994
+ "$CODEX_BIN" "${CODEX_LAUNCH_ARGS[@]}"
1038
995
  review_exit="$?"
1039
996
  set -e
1040
997
  if [[ "$review_exit" -ne 0 ]]; then
@@ -1073,35 +1030,18 @@ fi
1073
1030
 
1074
1031
  echo "[codex-agent] Task routing: $(describe_task_routing) (${TASK_ROUTING_REASON})"
1075
1032
 
1076
- active_session_recorded=0
1077
- active_session_heartbeat_pid=""
1078
- cleanup_active_session_state_on_exit() {
1079
- set +e
1080
- if [[ "${active_session_recorded:-0}" -eq 1 && -n "${worktree_branch:-}" && "${worktree_branch:-}" != "HEAD" ]]; then
1081
- stop_active_session_heartbeat
1082
- clear_active_session_state "$worktree_branch"
1083
- active_session_recorded=0
1084
- fi
1085
- }
1086
-
1087
- record_active_session_state "$worktree_path" "$worktree_branch"
1088
- active_session_recorded=1
1089
- start_active_session_heartbeat "$worktree_branch"
1090
- trap cleanup_active_session_state_on_exit EXIT INT TERM
1091
-
1092
1033
  echo "[codex-agent] Launching ${CODEX_BIN} in sandbox: $worktree_path"
1093
1034
  cd "$worktree_path"
1094
1035
  set +e
1036
+ build_codex_launch_args "$@"
1095
1037
  GUARDEX_TASK_MODE="$TASK_MODE" \
1096
1038
  GUARDEX_OPENSPEC_TIER="$OPENSPEC_TIER" \
1097
1039
  GUARDEX_TASK_ROUTING_REASON="$TASK_ROUTING_REASON" \
1098
- "$CODEX_BIN" "$@"
1040
+ "$CODEX_BIN" "${CODEX_LAUNCH_ARGS[@]}"
1099
1041
  codex_exit="$?"
1100
1042
  set -e
1101
1043
 
1102
1044
  cd "$repo_root"
1103
- cleanup_active_session_state_on_exit
1104
- trap - EXIT INT TERM
1105
1045
  final_exit="$codex_exit"
1106
1046
  auto_finish_completed=0
1107
1047
 
@@ -1161,7 +1101,11 @@ else
1161
1101
  echo "[codex-agent] Branch kept intentionally. Cleanup on demand: gx cleanup --branch \"${worktree_branch}\""
1162
1102
  else
1163
1103
  print_takeover_prompt "$worktree_path" "$worktree_branch"
1164
- echo "[codex-agent] If finished, merge with: gx branch finish --branch \"${worktree_branch}\" --base dev --via-pr --wait-for-merge"
1104
+ finish_base_branch="$(resolve_worktree_base_branch "$worktree_path")"
1105
+ if [[ -z "$finish_base_branch" ]]; then
1106
+ finish_base_branch="dev"
1107
+ fi
1108
+ echo "[codex-agent] If finished, merge with: gx branch finish --branch \"${worktree_branch}\" --base ${finish_base_branch} --via-pr --wait-for-merge --cleanup"
1165
1109
  echo "[codex-agent] Cleanup on demand: gx cleanup --branch \"${worktree_branch}\""
1166
1110
  fi
1167
1111
  fi
@@ -84,6 +84,7 @@ if [[ ! -f "$PLAN_DIR/README.md" ]]; then
84
84
  echo
85
85
  echo "Each role folder contains OpenSpec-style artifacts:"
86
86
  echo "- \`.openspec.yaml\`"
87
+ echo "- \`prompt.md\` (copy/paste role prompt)"
87
88
  echo "- \`proposal.md\`"
88
89
  echo "- \`tasks.md\` (Spec / Tests / Implementation / Checkpoints checklists)"
89
90
  echo "- \`specs/<role>/spec.md\`"
@@ -108,6 +109,7 @@ Drive this plan from draft to execution-ready status with strict checkpoint disc
108
109
  - \`openspec/plan/${PLAN_SLUG}/checkpoints.md\`
109
110
  - \`openspec/plan/${PLAN_SLUG}/open-questions.md\`
110
111
  - \`openspec/plan/${PLAN_SLUG}/planner/plan.md\`
112
+ - role \`prompt.md\` files for copy/paste helper startup
111
113
  - role \`tasks.md\` files for planner/architect/critic/executor/writer/verifier
112
114
 
113
115
  ## Coordinator responsibilities
@@ -282,6 +284,7 @@ Role workspace for \`${role}\`.
282
284
 
283
285
  Default artifacts:
284
286
  - \`.openspec.yaml\`
287
+ - \`prompt.md\`
285
288
  - \`proposal.md\`
286
289
  - \`tasks.md\`
287
290
  - \`specs/<role>/spec.md\`
@@ -302,12 +305,52 @@ plan: ${PLAN_SLUG}
302
305
  role: ${role}
303
306
  status: draft
304
307
  artifacts:
308
+ prompt: prompt.md
305
309
  proposal: proposal.md
306
310
  tasks: tasks.md
307
311
  spec: specs/${ROLE_SPEC_SLUG}/spec.md
308
312
  ROLEYAMLEOF
309
313
  fi
310
314
 
315
+ if [[ ! -f "$ROLE_DIR/prompt.md" ]]; then
316
+ cat > "$ROLE_DIR/prompt.md" <<ROLEPROMPTEOF
317
+ # ${role} Prompt
318
+
319
+ You are the \`${role}\` role for OpenSpec plan \`${PLAN_SLUG}\`.
320
+
321
+ ## Objective
322
+
323
+ Complete only this role's assigned checklist and leave compact evidence for the coordinator.
324
+
325
+ ## Source of truth
326
+
327
+ - \`openspec/plan/${PLAN_SLUG}/summary.md\`
328
+ - \`openspec/plan/${PLAN_SLUG}/checkpoints.md\`
329
+ - \`openspec/plan/${PLAN_SLUG}/open-questions.md\`
330
+ - \`openspec/plan/${PLAN_SLUG}/${role}/tasks.md\`
331
+ - \`openspec/plan/${PLAN_SLUG}/${role}/proposal.md\`
332
+
333
+ ## Before edits
334
+
335
+ 1. Confirm branch/worktree with \`git status --short --branch\`.
336
+ 2. Claim every touched file before editing:
337
+ - Prefer Colony \`task_claim_file\` when an active task exists.
338
+ - Otherwise run \`gx locks claim --branch <agent-branch> <file...>\`.
339
+ 3. Stay inside assigned files/modules; coordinate before touching shared paths.
340
+
341
+ ## Working rules
342
+
343
+ - Update \`${role}/tasks.md\` as each item completes.
344
+ - Record durable unresolved questions in \`open-questions.md\`.
345
+ - Keep handoffs short: files changed, behavior touched, verification, risks.
346
+ - Do not revert another agent's edits.
347
+
348
+ ## Cleanup
349
+
350
+ Only the owner/finalizer lane runs \`gx branch finish --branch <agent-branch> --base dev --via-pr --wait-for-merge --cleanup\`. If blocked, append \`BLOCKED:\` with branch, task, blocker, next, evidence.
351
+ ROLEPROMPTEOF
352
+ fi
353
+
311
354
  if [[ ! -f "$ROLE_DIR/proposal.md" ]]; then
312
355
  cat > "$ROLE_DIR/proposal.md" <<ROLEPROPOSALEOF
313
356
  # Proposal: ${role} (${PLAN_SLUG})
@@ -64,6 +64,36 @@ normalize_bool() {
64
64
  esac
65
65
  }
66
66
 
67
+ gh_api_probe_looks_like_network_failure() {
68
+ local probe_output="$1"
69
+ [[ "$probe_output" =~ error[[:space:]]connecting[[:space:]]to[[:space:]]api\.github\.com|Could[[:space:]]not[[:space:]]resolve[[:space:]]host|could[[:space:]]not[[:space:]]resolve[[:space:]]host|Failed[[:space:]]to[[:space:]]connect|failed[[:space:]]to[[:space:]]connect|Network[[:space:]]is[[:space:]]unreachable|network[[:space:]]is[[:space:]]unreachable|Connection[[:space:]]timed[[:space:]]out|connection[[:space:]]timed[[:space:]]out|Temporary[[:space:]]failure[[:space:]]in[[:space:]]name[[:space:]]resolution|temporary[[:space:]]failure[[:space:]]in[[:space:]]name[[:space:]]resolution ]]
70
+ }
71
+
72
+ ensure_gh_auth_or_explain() {
73
+ local auth_output probe_output
74
+ if auth_output="$(gh auth status 2>&1)"; then
75
+ return 0
76
+ fi
77
+
78
+ if probe_output="$(gh api user --jq .login 2>&1)"; then
79
+ echo "[review-bot-watch] gh auth status failed, but gh api user succeeded; continuing with usable GitHub auth." >&2
80
+ return 0
81
+ fi
82
+
83
+ if gh_api_probe_looks_like_network_failure "$probe_output"; then
84
+ echo "[review-bot-watch] GitHub API is unreachable, so gh cannot validate the stored token." >&2
85
+ echo "[review-bot-watch] This is a network or Codex sandbox connectivity problem, not proof that the token is invalid." >&2
86
+ echo "$probe_output" >&2
87
+ return 1
88
+ fi
89
+
90
+ echo "[review-bot-watch] gh is not authenticated. Run: gh auth login" >&2
91
+ if [[ -n "$auth_output" ]]; then
92
+ echo "$auth_output" >&2
93
+ fi
94
+ return 1
95
+ }
96
+
67
97
  ONCE=0
68
98
 
69
99
  while [[ $# -gt 0 ]]; do
@@ -153,8 +183,7 @@ if ! command -v codex >/dev/null 2>&1; then
153
183
  exit 127
154
184
  fi
155
185
 
156
- if ! gh auth status >/dev/null 2>&1; then
157
- echo "[review-bot-watch] gh is not authenticated. Run: gh auth login" >&2
186
+ if ! ensure_gh_auth_or_explain; then
158
187
  exit 1
159
188
  fi
160
189
 
@@ -1,171 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- const fs = require('node:fs');
4
- const path = require('node:path');
5
-
6
- function resolveSessionSchemaModule() {
7
- const candidates = [
8
- path.resolve(__dirname, '..', 'vscode', 'guardex-active-agents', 'session-schema.js'),
9
- path.resolve(__dirname, '..', 'templates', 'vscode', 'guardex-active-agents', 'session-schema.js'),
10
- ];
11
-
12
- for (const candidate of candidates) {
13
- if (fs.existsSync(candidate)) {
14
- return require(candidate);
15
- }
16
- }
17
-
18
- throw new Error('Could not resolve Guardex active-agent session schema module.');
19
- }
20
-
21
- const sessionSchema = resolveSessionSchemaModule();
22
-
23
- function usage() {
24
- return (
25
- 'Usage:\n' +
26
- ' node scripts/agent-session-state.js start --repo <path> --branch <name> --task <task> --agent <agent> --worktree <path> --pid <pid> --cli <name> [--task-mode <caveman|omx>] [--openspec-tier <T0|T1|T2|T3>] [--routing-reason <text>] [--state <working|thinking|idle>]\n' +
27
- ' node scripts/agent-session-state.js heartbeat --repo <path> --branch <name> [--state <working|thinking|idle>]\n' +
28
- ' node scripts/agent-session-state.js terminate --repo <path> --branch <name>\n' +
29
- ' node scripts/agent-session-state.js stop --repo <path> --branch <name>\n'
30
- );
31
- }
32
-
33
- function parseOptions(argv) {
34
- const options = {};
35
- for (let index = 0; index < argv.length; index += 1) {
36
- const token = argv[index];
37
- if (!token.startsWith('--')) {
38
- throw new Error(`Unexpected argument: ${token}`);
39
- }
40
- const key = token.slice(2);
41
- const value = argv[index + 1];
42
- if (!value || value.startsWith('--')) {
43
- throw new Error(`Missing value for --${key}`);
44
- }
45
- options[key] = value;
46
- index += 1;
47
- }
48
- return options;
49
- }
50
-
51
- function requireOption(options, key) {
52
- const value = options[key];
53
- if (!value) {
54
- throw new Error(`Missing required option --${key}`);
55
- }
56
- return value;
57
- }
58
-
59
- function writeSessionRecord(options) {
60
- const repoRoot = requireOption(options, 'repo');
61
- const branch = requireOption(options, 'branch');
62
- const record = sessionSchema.buildSessionRecord({
63
- repoRoot,
64
- branch,
65
- taskName: requireOption(options, 'task'),
66
- agentName: requireOption(options, 'agent'),
67
- worktreePath: requireOption(options, 'worktree'),
68
- pid: requireOption(options, 'pid'),
69
- cliName: requireOption(options, 'cli'),
70
- taskMode: options['task-mode'],
71
- openspecTier: options['openspec-tier'],
72
- taskRoutingReason: options['routing-reason'],
73
- state: options.state,
74
- });
75
-
76
- const targetPath = sessionSchema.sessionFilePathForBranch(repoRoot, branch);
77
- fs.mkdirSync(path.dirname(targetPath), { recursive: true });
78
- fs.writeFileSync(targetPath, `${JSON.stringify(record, null, 2)}\n`, 'utf8');
79
- }
80
-
81
- function refreshSessionRecord(options) {
82
- const repoRoot = requireOption(options, 'repo');
83
- const branch = requireOption(options, 'branch');
84
- const targetPath = sessionSchema.sessionFilePathForBranch(repoRoot, branch);
85
- if (!fs.existsSync(targetPath)) {
86
- return;
87
- }
88
-
89
- const parsed = JSON.parse(fs.readFileSync(targetPath, 'utf8'));
90
- const nextRecord = {
91
- ...parsed,
92
- lastHeartbeatAt: new Date().toISOString(),
93
- };
94
- if (options.state) {
95
- nextRecord.state = options.state;
96
- }
97
-
98
- fs.writeFileSync(targetPath, `${JSON.stringify(nextRecord, null, 2)}\n`, 'utf8');
99
- }
100
-
101
- function readSessionRecord(options) {
102
- const repoRoot = requireOption(options, 'repo');
103
- const branch = requireOption(options, 'branch');
104
- const targetPath = sessionSchema.sessionFilePathForBranch(repoRoot, branch);
105
- if (!fs.existsSync(targetPath)) {
106
- return null;
107
- }
108
- return JSON.parse(fs.readFileSync(targetPath, 'utf8'));
109
- }
110
-
111
- function terminateSessionProcess(options) {
112
- const record = readSessionRecord(options);
113
- const pid = Number(record?.pid);
114
- if (!Number.isInteger(pid) || pid <= 0) {
115
- throw new Error('No live pid recorded for branch.');
116
- }
117
-
118
- try {
119
- process.kill(pid, 'SIGTERM');
120
- } catch (error) {
121
- if (error?.code === 'ESRCH') {
122
- return;
123
- }
124
- throw error;
125
- }
126
- }
127
-
128
- function removeSessionRecord(options) {
129
- const repoRoot = requireOption(options, 'repo');
130
- const branch = requireOption(options, 'branch');
131
- const targetPath = sessionSchema.sessionFilePathForBranch(repoRoot, branch);
132
- if (fs.existsSync(targetPath)) {
133
- fs.unlinkSync(targetPath);
134
- }
135
- }
136
-
137
- function main() {
138
- const [command, ...rest] = process.argv.slice(2);
139
- if (!command || ['-h', '--help', 'help'].includes(command)) {
140
- process.stdout.write(usage());
141
- return;
142
- }
143
-
144
- const options = parseOptions(rest);
145
- if (command === 'start') {
146
- writeSessionRecord(options);
147
- return;
148
- }
149
- if (command === 'heartbeat') {
150
- refreshSessionRecord(options);
151
- return;
152
- }
153
- if (command === 'terminate') {
154
- terminateSessionProcess(options);
155
- return;
156
- }
157
- if (command === 'stop') {
158
- removeSessionRecord(options);
159
- return;
160
- }
161
-
162
- throw new Error(`Unknown subcommand: ${command}`);
163
- }
164
-
165
- try {
166
- main();
167
- } catch (error) {
168
- process.stderr.write(`[guardex-active-session] ${error.message}\n`);
169
- process.stderr.write(usage());
170
- process.exitCode = 1;
171
- }
@@ -1,135 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- const fs = require('node:fs');
4
- const os = require('node:os');
5
- const path = require('node:path');
6
-
7
- const PATCH_COMPATIBILITY_WINDOW = 20;
8
- const RETIRED_EXTENSION_IDS = [
9
- 'recodeee.gitguardex-active-agents',
10
- ];
11
-
12
- function parseOptions(argv) {
13
- const options = {};
14
- for (let index = 0; index < argv.length; index += 1) {
15
- const token = argv[index];
16
- if (!token.startsWith('--')) {
17
- throw new Error(`Unexpected argument: ${token}`);
18
- }
19
- const key = token.slice(2);
20
- const value = argv[index + 1];
21
- if (!value || value.startsWith('--')) {
22
- throw new Error(`Missing value for --${key}`);
23
- }
24
- options[key] = value;
25
- index += 1;
26
- }
27
- return options;
28
- }
29
-
30
- function resolveExtensionSource(repoRoot) {
31
- const candidates = [
32
- path.join(repoRoot, 'vscode', 'guardex-active-agents'),
33
- path.join(repoRoot, 'templates', 'vscode', 'guardex-active-agents'),
34
- ];
35
-
36
- for (const candidate of candidates) {
37
- if (fs.existsSync(path.join(candidate, 'package.json'))) {
38
- return candidate;
39
- }
40
- }
41
-
42
- throw new Error('Could not find the Guardex VS Code companion sources.');
43
- }
44
-
45
- function removeIfExists(targetPath) {
46
- if (fs.existsSync(targetPath)) {
47
- fs.rmSync(targetPath, { recursive: true, force: true });
48
- }
49
- }
50
-
51
- function parseSimpleSemver(version) {
52
- const parts = String(version || '').trim().split('.').map((part) => Number.parseInt(part, 10));
53
- if (parts.length !== 3 || parts.some((part) => Number.isNaN(part))) {
54
- throw new Error(`Expected simple semver for the Active Agents companion, received "${version}".`);
55
- }
56
- return parts;
57
- }
58
-
59
- function buildInstallTargets(extensionId, version, extensionsDir) {
60
- const [major, minor, patch] = parseSimpleSemver(version);
61
- const firstCompatiblePatch = Math.max(0, patch - PATCH_COMPATIBILITY_WINDOW);
62
- const targets = [path.join(extensionsDir, extensionId)];
63
-
64
- for (let compatiblePatch = firstCompatiblePatch; compatiblePatch <= patch; compatiblePatch += 1) {
65
- targets.push(path.join(extensionsDir, `${extensionId}-${major}.${minor}.${compatiblePatch}`));
66
- }
67
-
68
- return targets;
69
- }
70
-
71
- function isRetiredExtensionInstall(entryName, currentExtensionId) {
72
- return RETIRED_EXTENSION_IDS
73
- .filter((extensionId) => extensionId !== currentExtensionId)
74
- .some((extensionId) => entryName === extensionId || entryName.startsWith(`${extensionId}-`));
75
- }
76
-
77
- function main() {
78
- const repoRoot = path.resolve(__dirname, '..');
79
- const options = parseOptions(process.argv.slice(2));
80
- const sourceDir = resolveExtensionSource(repoRoot);
81
- const manifest = JSON.parse(fs.readFileSync(path.join(sourceDir, 'package.json'), 'utf8'));
82
- const extensionId = `${manifest.publisher}.${manifest.name}`;
83
- const extensionsDir = path.resolve(
84
- options['extensions-dir'] ||
85
- process.env.GUARDEX_VSCODE_EXTENSIONS_DIR ||
86
- process.env.VSCODE_EXTENSIONS_DIR ||
87
- path.join(os.homedir(), '.vscode', 'extensions'),
88
- );
89
-
90
- fs.mkdirSync(extensionsDir, { recursive: true });
91
- const targetDirs = buildInstallTargets(extensionId, manifest.version, extensionsDir);
92
- const canonicalTargetDir = targetDirs[0];
93
- const keepDirNames = new Set(targetDirs.map((targetDir) => path.basename(targetDir)));
94
- let retiredInstallCount = 0;
95
-
96
- for (const entry of fs.readdirSync(extensionsDir, { withFileTypes: true })) {
97
- if (!entry.isDirectory()) {
98
- continue;
99
- }
100
- if (isRetiredExtensionInstall(entry.name, extensionId)) {
101
- removeIfExists(path.join(extensionsDir, entry.name));
102
- retiredInstallCount += 1;
103
- continue;
104
- }
105
- if (keepDirNames.has(entry.name)) {
106
- continue;
107
- }
108
- if (entry.name === extensionId || entry.name.startsWith(`${extensionId}-`)) {
109
- removeIfExists(path.join(extensionsDir, entry.name));
110
- }
111
- }
112
-
113
- for (const targetDir of targetDirs) {
114
- removeIfExists(targetDir);
115
- fs.cpSync(sourceDir, targetDir, {
116
- recursive: true,
117
- force: true,
118
- preserveTimestamps: true,
119
- });
120
- }
121
-
122
- process.stdout.write(
123
- `[guardex-active-agents] Installed ${extensionId}@${manifest.version} to ${canonicalTargetDir}\n` +
124
- `[guardex-active-agents] Refreshed ${targetDirs.length - 1} recent patch compatibility path(s) for already-open windows.\n` +
125
- `[guardex-active-agents] Removed ${retiredInstallCount} retired extension install path(s).\n` +
126
- '[guardex-active-agents] Reload each already-open VS Code window to activate the newest Source Control companion.\n',
127
- );
128
- }
129
-
130
- try {
131
- main();
132
- } catch (error) {
133
- process.stderr.write(`[guardex-active-agents] ${error.message}\n`);
134
- process.exitCode = 1;
135
- }
@@ -1,34 +0,0 @@
1
- # GitGuardex Active Agents
2
-
3
- Local VS Code companion for Guardex-managed repos.
4
-
5
- ## Quick Start
6
-
7
- Use the dedicated Active Agents sidebar icon to create or inspect Guardex sandboxes quickly.
8
-
9
- 1. Install from a Guardex-wired repo:
10
-
11
- ```sh
12
- node scripts/install-vscode-active-agents-extension.js
13
- ```
14
-
15
- 2. Reload the VS Code window.
16
- 3. In the Activity Bar, open the dedicated `Active Agents` hive icon. Use `Start agent` to enter a task + agent name and launch the repo Guardex agent runner. The companion prefers `bash scripts/codex-agent.sh` when present, falls back to `npm run agent:codex --`, and only uses `gx branch start` as a last resort.
17
-
18
- What it does:
19
-
20
- - Bundles a local GitGuardex icon so repo installs show branded extension metadata inside VS Code.
21
- - Bundles the optional `GitGuardex File Icons` theme for OpenSpec, agent worktree, and hook files in Explorer.
22
- - Adds a dedicated `Active Agents` Activity Bar container with a hive icon and live badge count for active sessions.
23
- - Renders one repo node per live Guardex workspace with grouped `ACTIVE AGENTS` and `CHANGES` sections.
24
- - Splits live sessions inside `ACTIVE AGENTS` into `BLOCKED`, `WORKING NOW`, `THINKING`, `STALLED`, and `DEAD` groups so stuck, active, and inactive lanes stand out immediately.
25
- - Mirrors the same live state in the VS Code status bar so the selected session or active-agent count stays visible outside the tree.
26
- - Keeps the built-in Source Control view focused on real Git repositories; the Active Agents commit command prompts for a message from its own toolbar action.
27
- - Shows one row per live Guardex sandbox session inside those activity groups, with changed-file rows nested under sessions that are touching files.
28
- - Labels session rows with provider identity and snapshot context; snapshot-backed rows use a one-letter snapshot badge such as `N` for `nagyviktor@edixa.com`.
29
- - Shows raw agent branch groups with the `git-branch` icon instead of the generic folder icon.
30
- - Shows repo-root git changes in a sibling `CHANGES` section when the guarded repo itself is dirty.
31
- - Derives session state from dirty worktree status, git conflict markers, heartbeat freshness, PID liveness, and recent file mtimes, surfaces working/dead/conflict counts in the repo/header summary, and shows changed-file counts for active edits.
32
- - Uses distinct VS Code codicons for each session state, including animated `loading~spin` for `WORKING NOW`.
33
- - Reads repo-local presence files from `.omx/state/active-sessions/`, expects `lastHeartbeatAt` freshness, and falls back to managed worktree-root `AGENT.lock` telemetry when the launcher session file is absent.
34
- - Publishes `guardex.hasAgents` and `guardex.hasConflicts` context keys for other VS Code contributions.