vibe-forge 0.3.6 โ†’ 0.3.10

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.
@@ -113,6 +113,7 @@ safe_move_task() {
113
113
 
114
114
  notify() {
115
115
  local message="$1"
116
+ local urgency="${2:-normal}" # normal or urgent
116
117
  local timestamp
117
118
  timestamp=$(date -Iseconds)
118
119
 
@@ -125,7 +126,41 @@ notify() {
125
126
  # Terminal bell (works in most terminals)
126
127
  printf '\a'
127
128
 
128
- # Terminal-specific notifications could be added here
129
+ # System toast notification for urgent messages
130
+ if [[ "$urgency" == "urgent" ]]; then
131
+ send_system_notification "$message"
132
+ fi
133
+ }
134
+
135
+ # Send system-level notification (platform-specific)
136
+ send_system_notification() {
137
+ local message="$1"
138
+ local title="Vibe Forge"
139
+
140
+ case "$(uname -s)" in
141
+ MINGW*|MSYS*|CYGWIN*)
142
+ # Windows: Use PowerShell toast notification
143
+ powershell.exe -NoProfile -Command "
144
+ \$null = [Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime]
145
+ \$template = [Windows.UI.Notifications.ToastNotificationManager]::GetTemplateContent([Windows.UI.Notifications.ToastTemplateType]::ToastText02)
146
+ \$textNodes = \$template.GetElementsByTagName('text')
147
+ \$textNodes.Item(0).AppendChild(\$template.CreateTextNode('$title')) | Out-Null
148
+ \$textNodes.Item(1).AppendChild(\$template.CreateTextNode('$message')) | Out-Null
149
+ \$toast = [Windows.UI.Notifications.ToastNotification]::new(\$template)
150
+ [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier('Vibe Forge').Show(\$toast)
151
+ " 2>/dev/null &
152
+ ;;
153
+ Darwin)
154
+ # macOS: Use osascript
155
+ osascript -e "display notification \"$message\" with title \"$title\"" 2>/dev/null &
156
+ ;;
157
+ Linux)
158
+ # Linux: Use notify-send if available
159
+ if command -v notify-send &>/dev/null; then
160
+ notify-send "$title" "$message" 2>/dev/null &
161
+ fi
162
+ ;;
163
+ esac
129
164
  }
130
165
 
131
166
  check_new_pending_tasks() {
@@ -143,10 +178,10 @@ check_new_pending_tasks() {
143
178
  # Extract task info from frontmatter safely
144
179
  local task_id task_title assigned_to
145
180
 
146
- # Use head to limit read and tr to sanitize
147
- task_id=$(grep -m1 "^id:" "$task" 2>/dev/null | cut -d':' -f2 | tr -d ' "' | head -c 100)
148
- task_title=$(grep -m1 "^title:" "$task" 2>/dev/null | cut -d':' -f2- | tr -d '"' | head -c 200)
149
- assigned_to=$(grep -m1 "^assigned_to:" "$task" 2>/dev/null | cut -d':' -f2 | tr -d ' "' | head -c 50)
181
+ # Use head to limit read, tr to sanitize, and strip ANSI escape sequences
182
+ task_id=$(grep -m1 "^id:" "$task" 2>/dev/null | cut -d':' -f2 | tr -d ' "' | tr -d '\033' | sed 's/\[[0-9;]*m//g' | head -c 100)
183
+ task_title=$(grep -m1 "^title:" "$task" 2>/dev/null | cut -d':' -f2- | tr -d '"' | tr -d '\033' | sed 's/\[[0-9;]*m//g' | head -c 200)
184
+ assigned_to=$(grep -m1 "^assigned_to:" "$task" 2>/dev/null | cut -d':' -f2 | tr -d ' "' | tr -d '\033' | sed 's/\[[0-9;]*m//g' | head -c 50)
150
185
 
151
186
  # Use filename as fallback
152
187
  task_id="${task_id:-$filename}"
@@ -191,13 +226,46 @@ check_new_pending_tasks() {
191
226
  done
192
227
  }
193
228
 
229
+ check_attention_needed() {
230
+ # Check for workers needing attention (urgent notifications)
231
+ if [[ ! -d "$FORGE_ROOT/$TASKS_ATTENTION" ]]; then
232
+ return 0
233
+ fi
234
+
235
+ for attention_file in "$FORGE_ROOT/$TASKS_ATTENTION"/*.md; do
236
+ if [[ -f "$attention_file" && ! -L "$attention_file" ]]; then
237
+ local filename
238
+ filename=$(basename "$attention_file")
239
+ local notified_key="attention:$filename"
240
+
241
+ if ! grep -qF "$notified_key" "$NOTIFIED_FILE" 2>/dev/null; then
242
+ # Extract attention info
243
+ local agent issue
244
+ agent=$(grep -m1 "^agent:" "$attention_file" 2>/dev/null | cut -d':' -f2 | tr -d ' "' | head -c 50)
245
+ issue=$(grep -m1 "^##" "$attention_file" 2>/dev/null | sed 's/^## *//' | head -c 200)
246
+
247
+ agent="${agent:-Unknown}"
248
+ issue="${issue:-Needs attention}"
249
+
250
+ # Ring bell multiple times for attention
251
+ printf '\a\a\a'
252
+
253
+ # Send urgent notification with toast
254
+ notify "๐Ÿ”” $agent needs help: $issue" "urgent"
255
+
256
+ echo "$notified_key" >> "$NOTIFIED_FILE"
257
+ fi
258
+ fi
259
+ done
260
+ }
261
+
194
262
  # =============================================================================
195
263
  # Daemon Functions
196
264
  # =============================================================================
197
265
 
198
266
  update_state() {
199
267
  # Count tasks in each folder (using find with -maxdepth for safety)
200
- local pending in_progress completed review approved needs_changes merged
268
+ local pending in_progress completed review approved needs_changes merged attention
201
269
  pending=$(find "$FORGE_ROOT/$TASKS_PENDING" -maxdepth 1 -name "*.md" -type f 2>/dev/null | wc -l)
202
270
  in_progress=$(find "$FORGE_ROOT/$TASKS_IN_PROGRESS" -maxdepth 1 -name "*.md" -type f 2>/dev/null | wc -l)
203
271
  completed=$(find "$FORGE_ROOT/$TASKS_COMPLETED" -maxdepth 1 -name "*.md" -type f 2>/dev/null | wc -l)
@@ -205,9 +273,16 @@ update_state() {
205
273
  approved=$(find "$FORGE_ROOT/$TASKS_APPROVED" -maxdepth 1 -name "*.md" -type f 2>/dev/null | wc -l)
206
274
  needs_changes=$(find "$FORGE_ROOT/$TASKS_NEEDS_CHANGES" -maxdepth 1 -name "*.md" -type f 2>/dev/null | wc -l)
207
275
  merged=$(find "$FORGE_ROOT/$TASKS_MERGED" -maxdepth 1 -name "*.md" -type f 2>/dev/null | wc -l)
276
+ attention=$(find "$FORGE_ROOT/$TASKS_ATTENTION" -maxdepth 1 -name "*.md" -type f 2>/dev/null | wc -l)
208
277
 
209
278
  local blocked=0
210
279
 
280
+ # Build attention details if any workers need help
281
+ local attention_details=""
282
+ if [[ "$attention" -gt 0 ]]; then
283
+ attention_details=$(build_attention_details)
284
+ fi
285
+
211
286
  # Write state file atomically (write to temp, then move)
212
287
  local temp_state="${STATE_FILE}.tmp.$$"
213
288
  cat > "$temp_state" << EOF
@@ -228,12 +303,32 @@ tasks:
228
303
  needs_changes: $needs_changes
229
304
  merged: $merged
230
305
  blocked: $blocked
306
+ attention_needed: $attention
231
307
 
308
+ $attention_details
232
309
  last_updated: $(date -Iseconds)
233
310
  EOF
234
311
  mv "$temp_state" "$STATE_FILE"
235
312
  }
236
313
 
314
+ build_attention_details() {
315
+ echo "attention:"
316
+ for attention_file in "$FORGE_ROOT/$TASKS_ATTENTION"/*.md; do
317
+ if [[ -f "$attention_file" && ! -L "$attention_file" ]]; then
318
+ local agent created issue
319
+ agent=$(grep -m1 "^agent:" "$attention_file" 2>/dev/null | cut -d':' -f2 | tr -d ' "' | head -c 50)
320
+ created=$(grep -m1 "^created:" "$attention_file" 2>/dev/null | cut -d':' -f2- | tr -d ' ' | head -c 30)
321
+ # Get the issue line (first ## heading content or fallback)
322
+ issue=$(sed -n '/^## Issue/,/^##/p' "$attention_file" 2>/dev/null | grep -v "^##" | head -1 | tr -d '\n' | head -c 100)
323
+ issue="${issue:-Needs attention}"
324
+
325
+ echo " - agent: $agent"
326
+ echo " since: $created"
327
+ echo " issue: \"$issue\""
328
+ fi
329
+ done
330
+ }
331
+
237
332
  route_completed_to_review() {
238
333
  # Move completed tasks to review queue
239
334
  for task in "$FORGE_ROOT/$TASKS_COMPLETED"/*.md; do
@@ -275,6 +370,9 @@ daemon_loop() {
275
370
  # Check for new tasks and notify
276
371
  check_new_pending_tasks
277
372
 
373
+ # Check for workers needing attention (urgent)
374
+ check_attention_needed
375
+
278
376
  # Route tasks
279
377
  route_completed_to_review
280
378
  route_approved_to_merged
@@ -334,6 +432,7 @@ cmd_start() {
334
432
  mkdir -p "$FORGE_ROOT/$TASKS_APPROVED"
335
433
  mkdir -p "$FORGE_ROOT/$TASKS_NEEDS_CHANGES"
336
434
  mkdir -p "$FORGE_ROOT/$TASKS_MERGED"
435
+ mkdir -p "$FORGE_ROOT/$TASKS_ATTENTION"
337
436
 
338
437
  # Start daemon in background
339
438
  daemon_loop &
@@ -394,11 +493,27 @@ cmd_status() {
394
493
 
395
494
  if [[ -f "$STATE_FILE" ]]; then
396
495
  echo "Task Counts:"
397
- grep -E "pending:|in_progress:|completed:|in_review:|approved:|needs_changes:|merged:" "$STATE_FILE" | sed 's/^/ /'
496
+ grep -E "pending:|in_progress:|completed:|in_review:|approved:|needs_changes:|merged:|attention_needed:" "$STATE_FILE" | sed 's/^/ /'
398
497
  fi
399
498
 
400
499
  echo ""
401
500
 
501
+ # Show attention-needed workers prominently
502
+ local attention_count
503
+ attention_count=$(find "$FORGE_ROOT/$TASKS_ATTENTION" -maxdepth 1 -name "*.md" -type f 2>/dev/null | wc -l)
504
+ if [[ "$attention_count" -gt 0 ]]; then
505
+ echo -e "${RED}๐Ÿ”” ATTENTION NEEDED:${NC}"
506
+ for attention_file in "$FORGE_ROOT/$TASKS_ATTENTION"/*.md; do
507
+ if [[ -f "$attention_file" && ! -L "$attention_file" ]]; then
508
+ local agent issue
509
+ agent=$(grep -m1 "^agent:" "$attention_file" 2>/dev/null | cut -d':' -f2 | tr -d ' "' | head -c 50)
510
+ issue=$(sed -n '/^## Issue/,/^##/p' "$attention_file" 2>/dev/null | grep -v "^##" | head -1 | head -c 80)
511
+ echo -e " ${YELLOW}$agent${NC}: $issue"
512
+ fi
513
+ done
514
+ echo ""
515
+ fi
516
+
402
517
  # Show recent notifications
403
518
  if [[ -f "$NOTIFY_FILE" ]]; then
404
519
  local notify_count
@@ -304,6 +304,48 @@ configure_daemon() {
304
304
  fi
305
305
  }
306
306
 
307
+ # =============================================================================
308
+ # STEP 7b: Configure Worker Loop (Persistent Mode)
309
+ # =============================================================================
310
+
311
+ configure_worker_loop() {
312
+ echo ""
313
+ echo "Worker Loop Configuration"
314
+ echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”"
315
+ echo ""
316
+ echo "Worker Loop keeps agents running continuously."
317
+ echo "When a worker finishes a task, it automatically checks for new work"
318
+ echo "instead of exiting. Great for longer coding sessions."
319
+ echo ""
320
+
321
+ local enable_worker_loop="n"
322
+
323
+ if [[ "$NON_INTERACTIVE" == "false" ]]; then
324
+ read -p "Enable persistent worker mode? (y/N): " choice
325
+ if [[ "$choice" == "y" || "$choice" == "Y" ]]; then
326
+ enable_worker_loop="y"
327
+ fi
328
+ fi
329
+
330
+ # Update config with worker loop preference
331
+ local config_file="$FORGE_ROOT/.forge/config.json"
332
+ if [[ -f "$config_file" ]]; then
333
+ # Add worker_loop_enabled to config
334
+ sed -i 's/"daemon_enabled": \(true\|false\)/"daemon_enabled": \1,\n "worker_loop_enabled": '"$( [[ "$enable_worker_loop" == "y" ]] && echo "true" || echo "false" )"'/' "$config_file" 2>/dev/null || true
335
+ fi
336
+
337
+ if [[ "$enable_worker_loop" == "y" ]]; then
338
+ echo ""
339
+ echo -e "${GREEN}โœ… Worker Loop enabled${NC}"
340
+ echo " Workers will keep running and check for new tasks"
341
+ else
342
+ echo ""
343
+ echo -e "${YELLOW}โ„น๏ธ Worker Loop disabled${NC}"
344
+ echo " Workers will exit after completing their tasks"
345
+ echo " Enable later with: forge config worker-loop on"
346
+ fi
347
+ }
348
+
307
349
  # =============================================================================
308
350
  # STEP 8: Install Slash Command
309
351
  # =============================================================================
@@ -476,6 +518,7 @@ main() {
476
518
  if validate_setup; then
477
519
  configure_terminal
478
520
  configure_daemon
521
+ configure_worker_loop
479
522
  install_slash_command
480
523
  create_project_context
481
524
  setup_complete
@@ -31,7 +31,9 @@ source "$SCRIPT_DIR/lib/agents.sh"
31
31
 
32
32
  # Load agent configuration from JSON if available
33
33
  if [[ -f "$FORGE_ROOT/$AGENTS_CONFIG" ]]; then
34
- load_agents_from_json "$FORGE_ROOT/$AGENTS_CONFIG" 2>/dev/null || true
34
+ if ! load_agents_from_json "$FORGE_ROOT/$AGENTS_CONFIG" 2>/dev/null; then
35
+ log_warn "Failed to load agents.json, using fallback defaults"
36
+ fi
35
37
  fi
36
38
 
37
39
  # =============================================================================
@@ -44,9 +46,17 @@ spawn_windows_terminal() {
44
46
  log_info "Spawning $agent in Windows Terminal..."
45
47
 
46
48
  if command -v wt &> /dev/null || command -v wt.exe &> /dev/null; then
47
- # Get display name for tab title
48
- local display_name
49
+ # Get display info for tab
50
+ local display_name icon tab_color
49
51
  display_name=$(get_agent_display_name "$agent")
52
+ icon=$(get_agent_icon "$agent")
53
+ tab_color=$(get_agent_tab_color "$agent")
54
+
55
+ # Build tab title with icon
56
+ local tab_title="$display_name"
57
+ if [[ -n "$icon" ]]; then
58
+ tab_title="$icon $display_name"
59
+ fi
50
60
 
51
61
  # Convert FORGE_ROOT to Windows path for wt.exe
52
62
  # Git Bash paths like /c/foo become C:/foo
@@ -57,15 +67,21 @@ spawn_windows_terminal() {
57
67
  win_forge_root="$FORGE_ROOT"
58
68
  fi
59
69
 
70
+ # Build wt command with optional tab color
71
+ local wt_args=(-w 0 new-tab --title "$tab_title")
72
+ if [[ -n "$tab_color" ]]; then
73
+ wt_args+=(--tabColor "$tab_color")
74
+ fi
75
+
60
76
  # Windows Terminal: open new tab with forge command
61
77
  # SECURITY: $agent has already been validated through resolve_agent
62
78
  if [[ -n "$GIT_BASH_PATH" ]]; then
63
79
  local bash_path="${GIT_BASH_PATH//\//\\}"
64
- wt.exe -w 0 new-tab --title "$display_name" "$bash_path" -c "cd \"$win_forge_root\" && ./bin/forge.sh start \"$agent\""
80
+ wt.exe "${wt_args[@]}" "$bash_path" -c "cd \"$win_forge_root\" && ./bin/forge.sh start \"$agent\""
65
81
  else
66
- wt.exe -w 0 new-tab --title "$display_name" bash -c "cd \"$win_forge_root\" && ./bin/forge.sh start \"$agent\""
82
+ wt.exe "${wt_args[@]}" bash -c "cd \"$win_forge_root\" && ./bin/forge.sh start \"$agent\""
67
83
  fi
68
- log_success "$display_name spawned in new Windows Terminal tab"
84
+ log_success "$tab_title spawned in new Windows Terminal tab"
69
85
  else
70
86
  log_error "wt command not found."
71
87
  echo "Make sure Windows Terminal is installed."
@@ -123,7 +139,9 @@ main() {
123
139
  require_forge_config "$FORGE_ROOT"
124
140
 
125
141
  echo ""
126
- log_header "๐Ÿ”ฅ Forge Spawn: $(get_agent_display_name "$resolved")"
142
+ local icon
143
+ icon=$(get_agent_icon "$resolved")
144
+ log_header "${icon:-๐Ÿ”ฅ} Forge Spawn: $(get_agent_display_name "$resolved")"
127
145
  if [[ "$agent" != "$resolved" ]]; then
128
146
  echo " (resolved from '$agent')"
129
147
  fi
package/bin/forge.sh CHANGED
@@ -41,7 +41,9 @@ source "$SCRIPT_DIR/lib/agents.sh"
41
41
  # Load agent configuration from JSON if available
42
42
  # This overwrites the fallback values in constants.sh
43
43
  if [[ -f "$FORGE_ROOT/$AGENTS_CONFIG" ]]; then
44
- load_agents_from_json "$FORGE_ROOT/$AGENTS_CONFIG" 2>/dev/null || true
44
+ if ! load_agents_from_json "$FORGE_ROOT/$AGENTS_CONFIG" 2>/dev/null; then
45
+ log_warn "Failed to load agents.json, using fallback defaults"
46
+ fi
45
47
  fi
46
48
 
47
49
  # =============================================================================
@@ -116,7 +118,13 @@ On startup, you MUST immediately display the team assembly welcome as shown in t
116
118
 
117
119
  # Startup Instructions
118
120
 
119
- On startup: Announce yourself (name, icon, role), check tasks/pending/ and tasks/needs-changes/ for your work, summarize what you find, and ask if you should begin.
121
+ On startup:
122
+ 1. Announce yourself briefly (icon, name, role)
123
+ 2. Check tasks/pending/ and tasks/needs-changes/ for tasks assigned to you
124
+ 3. If you find assigned tasks, IMMEDIATELY begin working on them (no confirmation needed)
125
+ 4. If no tasks found, announce you're idle and ready for work
126
+
127
+ You are autonomous - when assigned work exists, start it without asking permission.
120
128
  "
121
129
  fi
122
130
 
@@ -233,6 +241,73 @@ cmd_daemon() {
233
241
  esac
234
242
  }
235
243
 
244
+ cmd_config() {
245
+ local setting="${1:-}"
246
+ local value="${2:-}"
247
+ local config_file="$FORGE_ROOT/.forge/config.json"
248
+
249
+ if [[ ! -f "$config_file" ]]; then
250
+ log_error "Forge not initialized. Run 'forge init' first."
251
+ exit 1
252
+ fi
253
+
254
+ case "$setting" in
255
+ "worker-loop"|"loop")
256
+ case "$value" in
257
+ "on"|"true"|"1"|"enable"|"enabled")
258
+ # Enable worker loop
259
+ if jq -e '.worker_loop_enabled' "$config_file" > /dev/null 2>&1; then
260
+ jq '.worker_loop_enabled = true' "$config_file" > "$config_file.tmp" && mv "$config_file.tmp" "$config_file"
261
+ else
262
+ jq '. + {worker_loop_enabled: true}' "$config_file" > "$config_file.tmp" && mv "$config_file.tmp" "$config_file"
263
+ fi
264
+ log_success "Worker Loop enabled"
265
+ echo "Workers will now keep running to check for new tasks."
266
+ ;;
267
+ "off"|"false"|"0"|"disable"|"disabled")
268
+ # Disable worker loop
269
+ if jq -e '.worker_loop_enabled' "$config_file" > /dev/null 2>&1; then
270
+ jq '.worker_loop_enabled = false' "$config_file" > "$config_file.tmp" && mv "$config_file.tmp" "$config_file"
271
+ else
272
+ jq '. + {worker_loop_enabled: false}' "$config_file" > "$config_file.tmp" && mv "$config_file.tmp" "$config_file"
273
+ fi
274
+ log_success "Worker Loop disabled"
275
+ echo "Workers will exit after completing their tasks."
276
+ ;;
277
+ ""|"status")
278
+ # Show current status
279
+ local current
280
+ current=$(jq -r '.worker_loop_enabled // false' "$config_file")
281
+ if [[ "$current" == "true" ]]; then
282
+ echo "Worker Loop: enabled"
283
+ else
284
+ echo "Worker Loop: disabled"
285
+ fi
286
+ ;;
287
+ *)
288
+ echo "Usage: forge config worker-loop [on|off|status]"
289
+ ;;
290
+ esac
291
+ ;;
292
+ "")
293
+ # Show all config
294
+ echo ""
295
+ log_header "Forge Configuration"
296
+ echo ""
297
+ jq '.' "$config_file"
298
+ echo ""
299
+ ;;
300
+ *)
301
+ log_error "Unknown setting: $setting"
302
+ echo ""
303
+ echo "Available settings:"
304
+ echo " worker-loop Toggle persistent worker mode (on/off)"
305
+ echo ""
306
+ echo "Usage: forge config <setting> [value]"
307
+ ;;
308
+ esac
309
+ }
310
+
236
311
  cmd_help() {
237
312
  echo ""
238
313
  log_header "๐Ÿ”ฅ Vibe Forge"
@@ -247,17 +322,15 @@ cmd_help() {
247
322
  echo " status Show current forge status"
248
323
  echo " test Validate setup is working"
249
324
  echo " daemon <action> Manage background daemon (start|stop|status|notifications|clear)"
325
+ echo " config <setting> View or change configuration settings"
250
326
  echo " help Show this help message"
251
327
  echo ""
252
- echo "Agents (with aliases):"
253
- echo " anvil (frontend, ui, fe) - Frontend Developer"
254
- echo " furnace (backend, api, be) - Backend Developer"
255
- echo " crucible (test, testing, qa) - Tester / QA"
256
- echo " sentinel (review, reviewer, cr) - Code Reviewer"
257
- echo " scribe (docs, documentation) - Documentation"
258
- echo " herald (release, deploy) - Release Manager"
259
- echo " ember (devops, ops, infra) - DevOps"
260
- echo " aegis (security, sec, appsec) - Security"
328
+ show_available_agents
329
+ echo ""
330
+ echo "Configuration:"
331
+ echo " forge config Show all settings"
332
+ echo " forge config worker-loop on Enable persistent worker mode"
333
+ echo " forge config worker-loop off Disable persistent worker mode"
261
334
  echo ""
262
335
  echo "Examples:"
263
336
  echo " forge Start Planning Hub"
@@ -298,6 +371,10 @@ main() {
298
371
  shift
299
372
  cmd_daemon "$@"
300
373
  ;;
374
+ "config")
375
+ shift
376
+ cmd_config "$@"
377
+ ;;
301
378
  "help"|"--help"|"-h")
302
379
  cmd_help
303
380
  ;;
package/bin/lib/agents.sh CHANGED
@@ -155,3 +155,23 @@ get_agent_role() {
155
155
 
156
156
  echo "${AGENT_ROLES[$canonical]:-}"
157
157
  }
158
+
159
+ # get_agent_icon AGENT
160
+ # Returns the icon/emoji for an agent
161
+ get_agent_icon() {
162
+ local agent="$1"
163
+ local canonical
164
+ canonical=$(resolve_agent "$agent") || return 1
165
+
166
+ echo "${AGENT_ICONS[$canonical]:-}"
167
+ }
168
+
169
+ # get_agent_tab_color AGENT
170
+ # Returns the Windows Terminal tab color for an agent (hex format)
171
+ get_agent_tab_color() {
172
+ local agent="$1"
173
+ local canonical
174
+ canonical=$(resolve_agent "$agent") || return 1
175
+
176
+ echo "${AGENT_TAB_COLORS[$canonical]:-}"
177
+ }
package/bin/lib/config.sh CHANGED
@@ -78,6 +78,18 @@ load_agents_from_json() {
78
78
  console.log(`AGENT_PERSONALITY_FILES["${canonical}"]="${pfile}"`);
79
79
  }
80
80
 
81
+ // Output AGENT_ICONS assignments
82
+ for (const [canonical, info] of Object.entries(agents)) {
83
+ const icon = info.icon || "";
84
+ console.log(`AGENT_ICONS["${canonical}"]="${icon}"`);
85
+ }
86
+
87
+ // Output AGENT_TAB_COLORS assignments
88
+ for (const [canonical, info] of Object.entries(agents)) {
89
+ const tabColor = info.tab_color || "";
90
+ console.log(`AGENT_TAB_COLORS["${canonical}"]="${tabColor}"`);
91
+ }
92
+
81
93
  } catch (e) {
82
94
  console.error("Error parsing agents.json:", e.message);
83
95
  process.exit(1);
@@ -22,6 +22,7 @@ TASKS_REVIEW="$TASKS_DIR/review"
22
22
  TASKS_APPROVED="$TASKS_DIR/approved"
23
23
  TASKS_NEEDS_CHANGES="$TASKS_DIR/needs-changes"
24
24
  TASKS_MERGED="$TASKS_DIR/merged"
25
+ TASKS_ATTENTION="$TASKS_DIR/attention"
25
26
  CONTEXT_DIR="context"
26
27
  AGENTS_DIR="agents"
27
28
 
@@ -141,3 +142,29 @@ declare -A AGENT_PERSONALITY_FILES=(
141
142
  ["aegis"]="agents/aegis/personality.md"
142
143
  ["hub"]="agents/planning-hub/personality.md"
143
144
  )
145
+
146
+ # Agent icons
147
+ declare -A AGENT_ICONS=(
148
+ ["anvil"]="๐Ÿ”จ"
149
+ ["furnace"]="๐Ÿ”ฅ"
150
+ ["crucible"]="๐Ÿงช"
151
+ ["sentinel"]="๐Ÿ›ก๏ธ"
152
+ ["scribe"]="๐Ÿ“œ"
153
+ ["herald"]="๐Ÿ“ฏ"
154
+ ["ember"]="โš™๏ธ"
155
+ ["aegis"]="๐Ÿ”’"
156
+ ["hub"]="๐Ÿ”ฅ"
157
+ )
158
+
159
+ # Agent tab colors for Windows Terminal (hex format)
160
+ declare -A AGENT_TAB_COLORS=(
161
+ ["anvil"]="#3B82F6"
162
+ ["furnace"]="#EF4444"
163
+ ["crucible"]="#10B981"
164
+ ["sentinel"]="#8B5CF6"
165
+ ["scribe"]="#F59E0B"
166
+ ["herald"]="#EC4899"
167
+ ["ember"]="#F97316"
168
+ ["aegis"]="#06B6D4"
169
+ ["hub"]="#FF6B35"
170
+ )
@@ -6,7 +6,8 @@
6
6
  "role": "Frontend Developer",
7
7
  "aliases": ["frontend", "ui", "fe"],
8
8
  "type": "worker",
9
- "personality_file": "agents/anvil/personality.md"
9
+ "personality_file": "agents/anvil/personality.md",
10
+ "tab_color": "#3B82F6"
10
11
  },
11
12
  "furnace": {
12
13
  "name": "Furnace",
@@ -14,7 +15,8 @@
14
15
  "role": "Backend Developer",
15
16
  "aliases": ["backend", "api", "be"],
16
17
  "type": "worker",
17
- "personality_file": "agents/furnace/personality.md"
18
+ "personality_file": "agents/furnace/personality.md",
19
+ "tab_color": "#EF4444"
18
20
  },
19
21
  "crucible": {
20
22
  "name": "Crucible",
@@ -22,7 +24,8 @@
22
24
  "role": "Tester / QA",
23
25
  "aliases": ["test", "testing", "qa", "tester"],
24
26
  "type": "worker",
25
- "personality_file": "agents/crucible/personality.md"
27
+ "personality_file": "agents/crucible/personality.md",
28
+ "tab_color": "#10B981"
26
29
  },
27
30
  "sentinel": {
28
31
  "name": "Sentinel",
@@ -30,7 +33,8 @@
30
33
  "role": "Code Reviewer",
31
34
  "aliases": ["review", "reviewer", "cr"],
32
35
  "type": "core",
33
- "personality_file": "agents/sentinel/personality.md"
36
+ "personality_file": "agents/sentinel/personality.md",
37
+ "tab_color": "#8B5CF6"
34
38
  },
35
39
  "scribe": {
36
40
  "name": "Scribe",
@@ -38,7 +42,8 @@
38
42
  "role": "Documentation",
39
43
  "aliases": ["docs", "documentation", "doc"],
40
44
  "type": "worker",
41
- "personality_file": "agents/scribe/personality.md"
45
+ "personality_file": "agents/scribe/personality.md",
46
+ "tab_color": "#F59E0B"
42
47
  },
43
48
  "herald": {
44
49
  "name": "Herald",
@@ -46,7 +51,8 @@
46
51
  "role": "Release Manager",
47
52
  "aliases": ["release", "deploy", "deployment"],
48
53
  "type": "worker",
49
- "personality_file": "agents/herald/personality.md"
54
+ "personality_file": "agents/herald/personality.md",
55
+ "tab_color": "#EC4899"
50
56
  },
51
57
  "ember": {
52
58
  "name": "Ember",
@@ -54,7 +60,8 @@
54
60
  "role": "DevOps",
55
61
  "aliases": ["devops", "ops", "infra", "infrastructure"],
56
62
  "type": "specialist",
57
- "personality_file": "agents/ember/personality.md"
63
+ "personality_file": "agents/ember/personality.md",
64
+ "tab_color": "#F97316"
58
65
  },
59
66
  "aegis": {
60
67
  "name": "Aegis",
@@ -62,7 +69,8 @@
62
69
  "role": "Security",
63
70
  "aliases": ["security", "sec", "appsec"],
64
71
  "type": "specialist",
65
- "personality_file": "agents/aegis/personality.md"
72
+ "personality_file": "agents/aegis/personality.md",
73
+ "tab_color": "#06B6D4"
66
74
  },
67
75
  "hub": {
68
76
  "name": "Planning Hub",
@@ -70,7 +78,8 @@
70
78
  "role": "Planning & Coordination",
71
79
  "aliases": ["planning", "master", "forge-master"],
72
80
  "type": "core",
73
- "personality_file": "agents/planning-hub/personality.md"
81
+ "personality_file": "agents/planning-hub/personality.md",
82
+ "tab_color": "#FF6B35"
74
83
  }
75
84
  }
76
85
  }