@orchestrator-claude/definitions 3.8.2 → 3.9.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.
@@ -1,14 +1,10 @@
1
1
  #!/bin/bash
2
- # Post-Phase Checkpoint Hook
3
- # Automatically creates git checkpoints when workflow phases complete
2
+ # Post-Phase Checkpoint Hook (v2 — REST API)
3
+ # Defense-in-depth: agents create checkpoints via MCP, this is a safety net.
4
+ # Creates git checkpoints when workflow phases complete.
4
5
  #
5
6
  # Triggered by: PostToolUse on Task tool (after agent completes)
6
- # Behavior: Detects phase transitions and creates git commits
7
- #
8
- # Requirements:
9
- # - jq (for JSON parsing)
10
- # - git (for checkpoint commits)
11
- #
7
+ # Requirements: git
12
8
  # Debug mode: ORCH_CHECKPOINT_DEBUG=1 to enable verbose logging
13
9
 
14
10
  set -e
@@ -16,18 +12,20 @@ set -e
16
12
  # Configuration
17
13
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
18
14
  PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
19
- INDEX_FILE="$PROJECT_ROOT/.orchestrator/orchestrator-index.json"
20
15
  STATE_DIR="$PROJECT_ROOT/.orchestrator/.state"
21
16
  LOG_FILE="$STATE_DIR/checkpoint-hook.log"
22
17
  LAST_PHASE_FILE="$STATE_DIR/last-checkpointed-phase"
23
18
 
24
- # Ensure state directory exists
19
+ # API Configuration
20
+ API_URL="${ORCHESTRATOR_API_URL:-http://localhost:3001}"
21
+ AUTH_EMAIL="${ORCHESTRATOR_AUTH_EMAIL:-admin@orchestrator.local}"
22
+ AUTH_PASSWORD="${ORCHESTRATOR_AUTH_PASSWORD:-admin123}"
23
+ PROJECT_ID="${ORCHESTRATOR_PROJECT_ID:-d001d65d-25f5-4d13-b1dc-d006873e949c}"
24
+
25
25
  mkdir -p "$STATE_DIR"
26
26
 
27
- # Debug mode
28
27
  DEBUG="${ORCH_CHECKPOINT_DEBUG:-0}"
29
28
 
30
- # Logging functions
31
29
  log_debug() {
32
30
  if [ "$DEBUG" = "1" ]; then
33
31
  echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] [DEBUG] $1" >> "$LOG_FILE"
@@ -42,120 +40,57 @@ log_info() {
42
40
  fi
43
41
  }
44
42
 
45
- log_warn() {
46
- echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] [WARN] $1" >> "$LOG_FILE"
47
- echo "[WARN] $1" >&2
48
- }
49
-
50
43
  log_error() {
51
44
  echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] [ERROR] $1" >> "$LOG_FILE"
52
45
  echo "[ERROR] $1" >&2
53
46
  }
54
47
 
55
- # Check prerequisites
56
- check_prerequisites() {
57
- # Check jq
58
- if ! command -v jq &> /dev/null; then
59
- log_error "jq is not installed. Checkpoints disabled."
60
- return 1
61
- fi
62
-
63
- # Check git
64
- if ! command -v git &> /dev/null; then
65
- log_error "git is not installed. Checkpoints disabled."
66
- return 1
67
- fi
68
-
69
- # Check if we're in a git repo
70
- if ! git -C "$PROJECT_ROOT" rev-parse --is-inside-work-tree &> /dev/null; then
71
- log_warn "Not a git repository. Checkpoints disabled."
72
- return 1
73
- fi
74
-
75
- # Check index file
76
- if [ ! -f "$INDEX_FILE" ]; then
77
- log_debug "No orchestrator-index.json found. Skipping."
78
- return 1
48
+ # Auth (shared with other hooks — cached JWT)
49
+ get_token() {
50
+ local cached="$STATE_DIR/jwt-token"
51
+ if [ -f "$cached" ]; then
52
+ local age=$(( $(date +%s) - $(stat -c%Y "$cached" 2>/dev/null || stat -f%m "$cached" 2>/dev/null || echo 0) ))
53
+ if [ "$age" -lt 3500 ]; then
54
+ cat "$cached"; return
55
+ fi
79
56
  fi
80
-
81
- return 0
82
- }
83
-
84
- # Get current phase from orchestrator-index.json
85
- get_current_phase() {
86
- local phase
87
- phase=$(jq -r '.activeWorkflow.currentPhase // empty' "$INDEX_FILE" 2>/dev/null)
88
- echo "$phase"
89
- }
90
-
91
- # Get workflow ID
92
- get_workflow_id() {
93
- local wf_id
94
- wf_id=$(jq -r '.activeWorkflow.id // empty' "$INDEX_FILE" 2>/dev/null)
95
- echo "$wf_id"
57
+ local resp
58
+ resp=$(curl -s -f -X POST "${API_URL}/api/v1/auth/login" \
59
+ -H "Content-Type: application/json" \
60
+ -d "{\"email\":\"${AUTH_EMAIL}\",\"password\":\"${AUTH_PASSWORD}\"}" 2>/dev/null) || return 1
61
+ local token
62
+ token=$(echo "$resp" | node -e "let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{try{const j=JSON.parse(d);console.log(j.data?.accessToken||j.accessToken||j.token||'')}catch{console.log('')}})" 2>/dev/null)
63
+ [ -n "$token" ] && { echo "$token" > "$cached"; echo "$token"; }
96
64
  }
97
65
 
98
- # Get workflow status
99
- get_workflow_status() {
100
- local status
101
- status=$(jq -r '.activeWorkflow.status // empty' "$INDEX_FILE" 2>/dev/null)
102
- echo "$status"
66
+ # Get workflow info from REST API
67
+ get_workflow_info() {
68
+ local token
69
+ token=$(get_token) || return 1
70
+ curl -s -f "${API_URL}/api/v1/workflows?status=in_progress" \
71
+ -H "Authorization: Bearer $token" \
72
+ -H "X-Project-ID: $PROJECT_ID" 2>/dev/null
103
73
  }
104
74
 
105
- # Get last checkpointed phase from checkpoints array (one with commitHash)
106
- get_last_checkpointed_phase() {
107
- local phase
108
- # Find the most recent checkpoint that has a commitHash (indicating it was git-committed)
109
- phase=$(jq -r '
110
- [.checkpoints[] | select(.commitHash != null and .commitHash != "")]
111
- | sort_by(.createdAt)
112
- | last
113
- | .phase // empty
114
- ' "$INDEX_FILE" 2>/dev/null)
115
- echo "$phase"
116
- }
117
-
118
- # Determine completed phase based on current phase
119
- # When we see currentPhase = PLAN, it means SPECIFY just completed
75
+ # Determine completed phase from current phase
76
+ # When currentPhase = PLAN, SPECIFY just completed; etc.
120
77
  get_completed_phase() {
121
- local current_phase="$1"
122
- local workflow_status="$2"
123
-
124
- case "$workflow_status" in
125
- "completed")
126
- echo "IMPLEMENT"
127
- return
128
- ;;
129
- esac
130
-
131
- case "$current_phase" in
132
- "PLAN")
133
- echo "SPECIFY"
134
- ;;
135
- "TASKS")
136
- echo "PLAN"
137
- ;;
138
- "IMPLEMENT")
139
- echo "TASKS"
140
- ;;
141
- *)
142
- echo ""
143
- ;;
78
+ case "$1" in
79
+ plan|PLAN) echo "specify" ;;
80
+ tasks|TASKS) echo "plan" ;;
81
+ implement|IMPLEMENT) echo "tasks" ;;
82
+ completed) echo "implement" ;;
83
+ *) echo "" ;;
144
84
  esac
145
85
  }
146
86
 
147
87
  # Create git checkpoint
148
88
  create_git_checkpoint() {
149
89
  local phase="$1"
150
- local commit_hash=""
151
90
 
152
91
  log_info "Creating git checkpoint for phase: $phase"
153
92
 
154
- # Stage all changes
155
- git -C "$PROJECT_ROOT" add -A 2>/dev/null || {
156
- log_warn "Failed to stage changes"
157
- return 1
158
- }
93
+ git -C "$PROJECT_ROOT" add -A 2>/dev/null || return 1
159
94
 
160
95
  # Check if there are staged changes
161
96
  if git -C "$PROJECT_ROOT" diff --cached --quiet 2>/dev/null; then
@@ -164,159 +99,83 @@ create_git_checkpoint() {
164
99
  return 0
165
100
  fi
166
101
 
167
- # Create commit with standard message
168
102
  local commit_msg="[orchestrator] ${phase}: Auto-checkpoint after ${phase} phase"
169
103
 
170
104
  if git -C "$PROJECT_ROOT" commit -m "$commit_msg" --no-verify 2>/dev/null; then
105
+ local commit_hash
171
106
  commit_hash=$(git -C "$PROJECT_ROOT" rev-parse HEAD 2>/dev/null)
172
107
  log_info "Checkpoint commit created: $commit_hash"
173
108
  echo "$commit_hash"
174
109
  else
175
- log_warn "Failed to create commit"
110
+ log_error "Failed to create commit"
176
111
  echo ""
177
112
  return 1
178
113
  fi
179
114
  }
180
115
 
181
- # Update orchestrator-index.json with checkpoint
182
- update_orchestrator_index() {
183
- local workflow_id="$1"
116
+ # Register checkpoint via REST API
117
+ register_checkpoint() {
118
+ local wf_id="$1"
184
119
  local phase="$2"
185
120
  local commit_hash="$3"
186
- local created_at
187
-
188
- created_at=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
189
- local checkpoint_id="cp_${workflow_id}_$(date +%s)"
190
- local description="Auto-checkpoint after ${phase} phase"
191
-
192
- log_debug "Updating index with checkpoint: $checkpoint_id"
193
-
194
- # Create temp file for atomic update
195
- local temp_file="${INDEX_FILE}.tmp.$$"
196
-
197
- # Use jq to add checkpoint and update statistics
198
- if jq --arg id "$checkpoint_id" \
199
- --arg wfId "$workflow_id" \
200
- --arg phase "$phase" \
201
- --arg hash "$commit_hash" \
202
- --arg date "$created_at" \
203
- --arg desc "$description" '
204
- # Add checkpoint to array
205
- .checkpoints += [{
206
- id: $id,
207
- workflowId: $wfId,
208
- phase: $phase,
209
- commitHash: $hash,
210
- createdAt: $date,
211
- description: $desc
212
- }] |
213
- # Update statistics
214
- .statistics.totalCheckpoints = (.checkpoints | length) |
215
- .statistics.lastActivity = $date
216
- ' "$INDEX_FILE" > "$temp_file" 2>/dev/null; then
217
-
218
- # Validate JSON
219
- if jq empty "$temp_file" 2>/dev/null; then
220
- mv "$temp_file" "$INDEX_FILE"
221
- log_info "Index updated with checkpoint: $checkpoint_id"
222
- return 0
223
- else
224
- log_error "Generated invalid JSON"
225
- rm -f "$temp_file"
226
- return 1
227
- fi
228
- else
229
- log_error "Failed to update index with jq"
230
- rm -f "$temp_file"
231
- return 1
232
- fi
233
- }
234
-
235
- # Save last checkpointed phase
236
- save_last_phase() {
237
- local phase="$1"
238
- echo "$phase" > "$LAST_PHASE_FILE"
239
- }
240
-
241
- # Get saved last phase (from state file, not index)
242
- get_saved_last_phase() {
243
- if [ -f "$LAST_PHASE_FILE" ]; then
244
- cat "$LAST_PHASE_FILE"
245
- else
246
- echo ""
247
- fi
121
+ local description="$4"
122
+ local token
123
+ token=$(get_token) || return 1
124
+ curl -s -f -X POST "${API_URL}/api/v1/checkpoints" \
125
+ -H "Content-Type: application/json" \
126
+ -H "Authorization: Bearer $token" \
127
+ -H "X-Project-ID: $PROJECT_ID" \
128
+ -d "{\"workflowId\":\"${wf_id}\",\"description\":\"${description}\",\"commitHash\":\"${commit_hash}\"}" >> "$LOG_FILE" 2>&1 || true
248
129
  }
249
130
 
250
- # Main execution
251
131
  main() {
252
132
  log_debug "Hook triggered"
253
133
 
254
- # Check prerequisites
255
- if ! check_prerequisites; then
256
- log_debug "Prerequisites not met, exiting"
257
- exit 0 # Don't fail the hook
258
- fi
134
+ # Prerequisites
135
+ command -v git &>/dev/null || { log_error "git not found"; exit 0; }
136
+ git -C "$PROJECT_ROOT" rev-parse --is-inside-work-tree &>/dev/null || { exit 0; }
259
137
 
260
- # Get current state
261
- local current_phase
262
- current_phase=$(get_current_phase)
138
+ # Get workflow from API
139
+ WORKFLOW_RESP=$(get_workflow_info) || { log_debug "No API access"; exit 0; }
263
140
 
264
- if [ -z "$current_phase" ]; then
265
- log_debug "No active workflow phase found"
266
- exit 0
267
- fi
141
+ WORKFLOW_DATA=$(echo "$WORKFLOW_RESP" | node -e "
142
+ let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{
143
+ try{
144
+ const j=JSON.parse(d);
145
+ const wfs=j.data||j;
146
+ const wf=Array.isArray(wfs)?wfs[0]:wfs;
147
+ if(!wf){console.log('{}');return;}
148
+ console.log(JSON.stringify({id:wf.id||'',phase:wf.currentPhase||wf.phase||'',status:wf.status||''}));
149
+ }catch{console.log('{}')}
150
+ });
151
+ " 2>/dev/null)
268
152
 
269
- local workflow_id
270
- workflow_id=$(get_workflow_id)
153
+ WF_ID=$(echo "$WORKFLOW_DATA" | node -e "let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{try{console.log(JSON.parse(d).id||'')}catch{console.log('')}})" 2>/dev/null)
154
+ CURRENT_PHASE=$(echo "$WORKFLOW_DATA" | node -e "let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{try{console.log(JSON.parse(d).phase||'')}catch{console.log('')}})" 2>/dev/null)
271
155
 
272
- local workflow_status
273
- workflow_status=$(get_workflow_status)
156
+ [ -z "$CURRENT_PHASE" ] && { log_debug "No active phase"; exit 0; }
274
157
 
275
- log_debug "Current phase: $current_phase, Status: $workflow_status, Workflow: $workflow_id"
158
+ COMPLETED_PHASE=$(get_completed_phase "$CURRENT_PHASE")
159
+ [ -z "$COMPLETED_PHASE" ] && { log_debug "No completed phase from: $CURRENT_PHASE"; exit 0; }
276
160
 
277
- # Determine which phase just completed
278
- local completed_phase
279
- completed_phase=$(get_completed_phase "$current_phase" "$workflow_status")
161
+ # Check if already checkpointed
162
+ SAVED=$(cat "$LAST_PHASE_FILE" 2>/dev/null || echo "")
163
+ [ "$COMPLETED_PHASE" = "$SAVED" ] && { log_debug "Already checkpointed: $COMPLETED_PHASE"; exit 0; }
280
164
 
281
- if [ -z "$completed_phase" ]; then
282
- log_debug "No completed phase detected from current phase: $current_phase"
283
- exit 0
284
- fi
165
+ log_debug "Current phase: $CURRENT_PHASE, Completed phase: $COMPLETED_PHASE, WF: $WF_ID"
285
166
 
286
- # Check if we already checkpointed this phase
287
- local last_checkpointed
288
- last_checkpointed=$(get_last_checkpointed_phase)
167
+ # Create git checkpoint
168
+ COMMIT_HASH=$(create_git_checkpoint "$COMPLETED_PHASE")
289
169
 
290
- local saved_last_phase
291
- saved_last_phase=$(get_saved_last_phase)
292
-
293
- log_debug "Completed phase: $completed_phase, Last checkpointed: $last_checkpointed, Saved: $saved_last_phase"
294
-
295
- # Skip if already checkpointed
296
- if [ "$completed_phase" = "$last_checkpointed" ] || [ "$completed_phase" = "$saved_last_phase" ]; then
297
- log_debug "Phase $completed_phase already checkpointed, skipping"
298
- exit 0
299
- fi
300
-
301
- # Create checkpoint
302
- local commit_hash
303
- commit_hash=$(create_git_checkpoint "$completed_phase")
304
-
305
- # Update index (even if no commit, to track the checkpoint)
306
- if [ -n "$commit_hash" ]; then
307
- if update_orchestrator_index "$workflow_id" "$completed_phase" "$commit_hash"; then
308
- save_last_phase "$completed_phase"
309
- log_info "Checkpoint complete for phase: $completed_phase (commit: $commit_hash)"
310
- else
311
- log_error "Failed to update index for phase: $completed_phase"
312
- fi
170
+ if [ -n "$COMMIT_HASH" ]; then
171
+ register_checkpoint "$WF_ID" "$COMPLETED_PHASE" "$COMMIT_HASH" "Auto-checkpoint after ${COMPLETED_PHASE} phase"
172
+ log_info "Checkpoint: phase=$COMPLETED_PHASE commit=$COMMIT_HASH"
313
173
  else
314
- log_info "No changes to checkpoint for phase: $completed_phase"
315
- save_last_phase "$completed_phase"
174
+ log_info "No changes for phase: $COMPLETED_PHASE"
316
175
  fi
317
176
 
177
+ echo "$COMPLETED_PHASE" > "$LAST_PHASE_FILE"
318
178
  exit 0
319
179
  }
320
180
 
321
- # Run main function
322
181
  main "$@"
@@ -1,6 +1,7 @@
1
1
  #!/bin/bash
2
- # Track Agent Invocation Hook
3
- # Registers agent invocations in orchestrator-index.json
2
+ # Track Agent Invocation Hook (v2 — REST API)
3
+ # Defense-in-depth: agents self-report via MCP, this hook is a safety net.
4
+ # Registers agent invocations via REST API (PostgreSQL).
4
5
  #
5
6
  # Usage (called by Claude Code hooks):
6
7
  # track-agent-invocation.sh start (reads stdin JSON)
@@ -13,11 +14,16 @@ set -e
13
14
 
14
15
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
15
16
  PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
16
- INDEX_FILE="$PROJECT_ROOT/.orchestrator/orchestrator-index.json"
17
17
  STATE_DIR="$PROJECT_ROOT/.orchestrator/.state"
18
18
  INVOCATION_FILE="$STATE_DIR/current-invocation"
19
19
  DEBUG_LOG="$STATE_DIR/hook-debug.log"
20
20
 
21
+ # API Configuration
22
+ API_URL="${ORCHESTRATOR_API_URL:-http://localhost:3001}"
23
+ AUTH_EMAIL="${ORCHESTRATOR_AUTH_EMAIL:-admin@orchestrator.local}"
24
+ AUTH_PASSWORD="${ORCHESTRATOR_AUTH_PASSWORD:-admin123}"
25
+ PROJECT_ID="${ORCHESTRATOR_PROJECT_ID:-d001d65d-25f5-4d13-b1dc-d006873e949c}"
26
+
21
27
  # Ensure state directory exists
22
28
  mkdir -p "$STATE_DIR"
23
29
 
@@ -32,6 +38,43 @@ if [ -f "$DEBUG_LOG" ]; then
32
38
  fi
33
39
  fi
34
40
 
41
+ log() {
42
+ echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] $*" >> "$DEBUG_LOG"
43
+ }
44
+
45
+ # Auth function: login and cache JWT
46
+ get_token() {
47
+ local cached="$STATE_DIR/jwt-token"
48
+ if [ -f "$cached" ]; then
49
+ local age=$(( $(date +%s) - $(stat -c%Y "$cached" 2>/dev/null || stat -f%m "$cached" 2>/dev/null || echo 0) ))
50
+ if [ "$age" -lt 3500 ]; then # Refresh if > ~58 min
51
+ cat "$cached"
52
+ return
53
+ fi
54
+ fi
55
+ local resp
56
+ resp=$(curl -s -f -X POST "${API_URL}/api/v1/auth/login" \
57
+ -H "Content-Type: application/json" \
58
+ -d "{\"email\":\"${AUTH_EMAIL}\",\"password\":\"${AUTH_PASSWORD}\"}" 2>/dev/null) || return 1
59
+ local token
60
+ token=$(echo "$resp" | node -e "let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{try{const j=JSON.parse(d);console.log(j.data?.accessToken||j.accessToken||j.token||'')}catch{console.log('')}})" 2>/dev/null)
61
+ if [ -n "$token" ]; then
62
+ echo "$token" > "$cached"
63
+ echo "$token"
64
+ fi
65
+ }
66
+
67
+ # Get active workflow ID from REST API
68
+ get_workflow_id() {
69
+ local token
70
+ token=$(get_token) || return 1
71
+ local resp
72
+ resp=$(curl -s -f "${API_URL}/api/v1/workflows?status=in_progress" \
73
+ -H "Authorization: Bearer $token" \
74
+ -H "X-Project-ID: $PROJECT_ID" 2>/dev/null) || return 1
75
+ echo "$resp" | node -e "let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{try{const j=JSON.parse(d);const wfs=j.data||j;const wf=Array.isArray(wfs)?wfs[0]:wfs;console.log(wf?.id||'')}catch{console.log('')}})" 2>/dev/null
76
+ }
77
+
35
78
  ACTION="${1:-}"
36
79
 
37
80
  # Read stdin into variable (Claude Code passes JSON via stdin)
@@ -45,7 +88,6 @@ case "$ACTION" in
45
88
  # Extract agent info from stdin JSON
46
89
  # Structure: { "tool_name": "Task", "tool_input": { "subagent_type": "...", ... } }
47
90
  if [ -n "$STDIN_DATA" ]; then
48
- # Debug: log received data
49
91
  echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] START stdin: $STDIN_DATA" >> "$DEBUG_LOG"
50
92
 
51
93
  AGENT_NAME=$(echo "$STDIN_DATA" | node -e "
@@ -54,7 +96,6 @@ case "$ACTION" in
54
96
  process.stdin.on('end', () => {
55
97
  try {
56
98
  const json = JSON.parse(data);
57
- // Try different structures
58
99
  const type = json.tool_input?.subagent_type
59
100
  || json.subagent_type
60
101
  || json.tool_input?.type
@@ -87,7 +128,6 @@ case "$ACTION" in
87
128
  });
88
129
  ")
89
130
  elif [ -n "$CLAUDE_TOOL_INPUT" ]; then
90
- # Fallback to environment variable
91
131
  echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] START env: $CLAUDE_TOOL_INPUT" >> "$DEBUG_LOG"
92
132
 
93
133
  AGENT_NAME=$(echo "$CLAUDE_TOOL_INPUT" | node -e "
@@ -108,124 +148,35 @@ case "$ACTION" in
108
148
  PHASE="unknown"
109
149
  fi
110
150
 
111
- # Generate invocation ID
112
- INVOCATION_ID="inv-$(date +%s)-$(head -c 4 /dev/urandom | xxd -p)"
113
- STARTED_AT=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
114
-
115
- # Get workflow ID from index
116
- WORKFLOW_ID=""
117
- if [ -f "$INDEX_FILE" ]; then
118
- WORKFLOW_ID=$(node -e "
119
- const fs = require('fs');
120
- try {
121
- const idx = JSON.parse(fs.readFileSync('$INDEX_FILE', 'utf8'));
122
- console.log(idx.activeWorkflow?.id || 'wf-standalone-' + Date.now());
123
- } catch { console.log('wf-standalone-' + Date.now()); }
124
- ")
125
- fi
151
+ TOKEN=$(get_token) || { log "ERROR: Auth failed"; exit 0; }
152
+ WORKFLOW_ID=$(get_workflow_id) || { log "SKIP: No active workflow"; exit 0; }
126
153
 
127
- # Save invocation state
154
+ STARTED_AT=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
155
+ INVOCATION_ID="inv-$(date +%s)-$(head -c 4 /dev/urandom | xxd -p)"
128
156
  echo "$INVOCATION_ID" > "$INVOCATION_FILE"
129
157
 
130
- # Update orchestrator-index.json
131
- if [ -f "$INDEX_FILE" ]; then
132
- node -e "
133
- const fs = require('fs');
134
- const indexPath = '$INDEX_FILE';
135
- const idx = JSON.parse(fs.readFileSync(indexPath, 'utf8'));
136
-
137
- if (!idx.agentInvocations) idx.agentInvocations = [];
138
-
139
- idx.agentInvocations.push({
140
- id: '$INVOCATION_ID',
141
- agentName: '$AGENT_NAME',
142
- phase: '$PHASE',
143
- workflowId: '$WORKFLOW_ID',
144
- startedAt: '$STARTED_AT',
145
- completedAt: null,
146
- status: 'running',
147
- durationMs: 0
148
- });
158
+ # Register via REST API
159
+ curl -s -f -X POST "${API_URL}/api/v1/agent-invocations" \
160
+ -H "Content-Type: application/json" \
161
+ -H "Authorization: Bearer $TOKEN" \
162
+ -H "X-Project-ID: $PROJECT_ID" \
163
+ -d "{\"agentType\":\"${AGENT_NAME}\",\"workflowId\":\"${WORKFLOW_ID}\",\"phase\":\"${PHASE}\",\"startedAt\":\"${STARTED_AT}\"}" >> "$DEBUG_LOG" 2>&1 || true
149
164
 
150
- fs.writeFileSync(indexPath, JSON.stringify(idx, null, 2));
151
- console.log(JSON.stringify({ invocationId: '$INVOCATION_ID', startedAt: '$STARTED_AT' }));
152
- "
153
- else
154
- echo '{"invocationId": "'$INVOCATION_ID'", "startedAt": "'$STARTED_AT'", "warning": "No index file found"}'
155
- fi
165
+ log "START: agent=$AGENT_NAME phase=$PHASE workflow=$WORKFLOW_ID"
156
166
  ;;
157
167
 
158
168
  complete)
159
- # Get invocation ID from state file
160
169
  INVOCATION_ID=""
161
170
  if [ -f "$INVOCATION_FILE" ]; then
162
171
  INVOCATION_ID=$(cat "$INVOCATION_FILE")
172
+ rm -f "$INVOCATION_FILE"
163
173
  fi
164
174
 
165
- if [ -z "$INVOCATION_ID" ]; then
166
- echo '{"success": false, "error": "No invocation ID found"}'
167
- exit 0 # Don't fail the hook
168
- fi
169
-
170
- # Debug log
171
175
  if [ -n "$STDIN_DATA" ]; then
172
176
  echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] COMPLETE stdin: $STDIN_DATA" >> "$DEBUG_LOG"
173
177
  fi
174
178
 
175
- # Determine status - default to success
176
- STATUS="success"
177
- SUMMARY="Completed"
178
-
179
- COMPLETED_AT=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
180
-
181
- # Update orchestrator-index.json
182
- if [ -f "$INDEX_FILE" ]; then
183
- node -e "
184
- const fs = require('fs');
185
- const indexPath = '$INDEX_FILE';
186
- const idx = JSON.parse(fs.readFileSync(indexPath, 'utf8'));
187
-
188
- const invocations = idx.agentInvocations || [];
189
- const invIdx = invocations.findIndex(i => i.id === '$INVOCATION_ID');
190
-
191
- if (invIdx === -1) {
192
- console.log(JSON.stringify({ success: false, error: 'Invocation not found' }));
193
- process.exit(0);
194
- }
195
-
196
- const inv = invocations[invIdx];
197
- const startTime = new Date(inv.startedAt).getTime();
198
- const endTime = new Date('$COMPLETED_AT').getTime();
199
- const durationMs = endTime - startTime;
200
-
201
- invocations[invIdx] = {
202
- ...inv,
203
- completedAt: '$COMPLETED_AT',
204
- status: '$STATUS' === 'success' ? 'completed' : 'failed',
205
- durationMs,
206
- result: {
207
- status: '$STATUS',
208
- summary: '$SUMMARY'
209
- }
210
- };
211
-
212
- idx.agentInvocations = invocations;
213
-
214
- // Update statistics
215
- if (idx.statistics) {
216
- idx.statistics.totalInvocations = invocations.length;
217
- idx.statistics.lastActivity = '$COMPLETED_AT';
218
- }
219
-
220
- fs.writeFileSync(indexPath, JSON.stringify(idx, null, 2));
221
- console.log(JSON.stringify({ success: true, durationMs }));
222
- "
223
- else
224
- echo '{"success": false, "error": "No index file found"}'
225
- fi
226
-
227
- # Clean up state file
228
- rm -f "$INVOCATION_FILE"
179
+ log "COMPLETE: invocation=$INVOCATION_ID (agent self-reports via MCP; hook is safety net)"
229
180
  ;;
230
181
 
231
182
  *)