instar 0.28.46 → 0.28.48

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 (57) hide show
  1. package/README.md +3 -0
  2. package/dist/commands/init.d.ts.map +1 -1
  3. package/dist/commands/init.js +75 -144
  4. package/dist/commands/init.js.map +1 -1
  5. package/dist/commands/server.js +1 -1
  6. package/dist/commands/server.js.map +1 -1
  7. package/dist/core/Config.d.ts.map +1 -1
  8. package/dist/core/Config.js +3 -0
  9. package/dist/core/Config.js.map +1 -1
  10. package/dist/core/PostUpdateMigrator.d.ts +18 -0
  11. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  12. package/dist/core/PostUpdateMigrator.js +134 -46
  13. package/dist/core/PostUpdateMigrator.js.map +1 -1
  14. package/dist/core/SessionManager.d.ts.map +1 -1
  15. package/dist/core/SessionManager.js +14 -2
  16. package/dist/core/SessionManager.js.map +1 -1
  17. package/dist/core/types.d.ts +6 -0
  18. package/dist/core/types.d.ts.map +1 -1
  19. package/dist/core/types.js.map +1 -1
  20. package/dist/memory/TopicMemory.d.ts +18 -3
  21. package/dist/memory/TopicMemory.d.ts.map +1 -1
  22. package/dist/memory/TopicMemory.js +71 -11
  23. package/dist/memory/TopicMemory.js.map +1 -1
  24. package/dist/messaging/imessage/IMessageAdapter.d.ts +35 -0
  25. package/dist/messaging/imessage/IMessageAdapter.d.ts.map +1 -1
  26. package/dist/messaging/imessage/IMessageAdapter.js +121 -1
  27. package/dist/messaging/imessage/IMessageAdapter.js.map +1 -1
  28. package/dist/messaging/imessage/types.d.ts +33 -0
  29. package/dist/messaging/imessage/types.d.ts.map +1 -1
  30. package/dist/scheduler/JobScheduler.d.ts.map +1 -1
  31. package/dist/scheduler/JobScheduler.js +8 -0
  32. package/dist/scheduler/JobScheduler.js.map +1 -1
  33. package/dist/server/AgentServer.d.ts.map +1 -1
  34. package/dist/server/AgentServer.js +16 -1
  35. package/dist/server/AgentServer.js.map +1 -1
  36. package/dist/server/middleware.d.ts +11 -1
  37. package/dist/server/middleware.d.ts.map +1 -1
  38. package/dist/server/middleware.js +25 -1
  39. package/dist/server/middleware.js.map +1 -1
  40. package/dist/server/routes.d.ts.map +1 -1
  41. package/dist/server/routes.js +9 -3
  42. package/dist/server/routes.js.map +1 -1
  43. package/package.json +1 -1
  44. package/scripts/attachments-sync/go.mod +8 -0
  45. package/scripts/attachments-sync/go.sum +4 -0
  46. package/scripts/attachments-sync/main.go +253 -0
  47. package/scripts/setup-imessage-hardlink.sh +73 -0
  48. package/src/data/builtin-manifest.json +93 -93
  49. package/src/templates/scripts/slack-reply.sh +10 -0
  50. package/src/templates/scripts/telegram-reply.sh +12 -0
  51. package/src/templates/scripts/whatsapp-reply.sh +10 -0
  52. package/upgrades/0.28.47.md +51 -0
  53. package/upgrades/0.28.48.md +52 -0
  54. package/upgrades/side-effects/0.28.47.md +88 -0
  55. package/upgrades/side-effects/0.28.48.md +90 -0
  56. package/upgrades/side-effects/outbound-timeout-408-ambiguous.md +146 -0
  57. /package/upgrades/side-effects/{echo-prevention-self-session-exclusion.md → 0.28.46.md} +0 -0
package/README.md CHANGED
@@ -213,6 +213,9 @@ iMessage support lets your agent send and receive iMessages on macOS. Messages a
213
213
  ```
214
214
  4. **Automation permission** for Messages.app — macOS will prompt on first send
215
215
 
216
+ > **Photo attachments:** If you want your agent to process images and files sent via iMessage, the `instar-attachments-sync` binary must also be running with Full Disk Access granted to it. It mirrors attachments from the Messages sandbox to a readable location. See [docs/LAUNCHDAEMON-SETUP.md#3-imessage-photo-attachments-optional](docs/LAUNCHDAEMON-SETUP.md#3-imessage-photo-attachments-optional) for setup.
217
+
218
+ For running as a LaunchDaemon (always-on, survives reboots), see [docs/LAUNCHDAEMON-SETUP.md](docs/LAUNCHDAEMON-SETUP.md).
216
219
  ### Configuration
217
220
 
218
221
  Add to your `.instar/config.json`:
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AA+CH,UAAU,WAAW;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,4DAA4D;IAC5D,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,yFAAyF;IACzF,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED;;GAEG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAqBrE;AAkzCD;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CA2iC1E;AA6jBD;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAuBlF"}
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AA+CH,UAAU,WAAW;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,4DAA4D;IAC5D,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,yFAAyF;IACzF,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED;;GAEG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAqBrE;AAkzCD;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CA2iC1E;AAulBD;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAuBlF"}
@@ -2505,7 +2505,33 @@ function getDefaultJobs(port) {
2505
2505
  enabled: true,
2506
2506
  execute: {
2507
2507
  type: 'prompt',
2508
- value: 'Review what has happened in the last 4 hours by reading recent activity logs. If there are any learnings, patterns, or insights worth remembering, update .instar/MEMORY.md. If nothing significant happened, do nothing.',
2508
+ value: `AUTH=$(python3 -c "import json; print(json.load(open('.instar/config.json')).get('authToken',''))" 2>/dev/null)
2509
+
2510
+ # Read recent activity logs to understand what happened in last 4 hours
2511
+ RECENT_LOGS=$(ls -t .instar/logs/activity-*.jsonl 2>/dev/null | head -1)
2512
+ if [ -z "$RECENT_LOGS" ]; then
2513
+ RECENT_LOGS=".instar/logs/activity-$(date +%Y-%m-%d).jsonl"
2514
+ fi
2515
+
2516
+ # Extract recent session activity and key events (filter out noise, keep significant events)
2517
+ echo "=== RECENT ACTIVITY (Last 4 Hours) ==="
2518
+ tail -500 "$RECENT_LOGS" 2>/dev/null | jq -r 'select(.type != "job-start" and .type != "job-queued") | "\(.timestamp) [\(.type)] \(.message // .title // .session_name // .slug // "")"' 2>/dev/null | tail -100
2519
+
2520
+ echo ""
2521
+ echo "=== YOUR TASK ==="
2522
+ echo "Analyze the activity above. Identify any learnings, patterns, or insights worth preserving in MEMORY.md:"
2523
+ echo "- Session patterns or repeated issues"
2524
+ echo "- Completed commitments or action items"
2525
+ echo "- Gaps between intended behavior and actual behavior"
2526
+ echo "- Unexpected interactions or failure modes"
2527
+ echo "- Process improvements or capability gaps"
2528
+ echo ""
2529
+ echo "If you find genuine learnings:"
2530
+ echo "1. Update .instar/MEMORY.md with the insight (append to the file)"
2531
+ echo "2. Be specific: include what was learned, why it matters, and how it should guide future work"
2532
+ echo "3. Signal completion: curl -s -X POST http://localhost:${port}/reflection/record -H 'Content-Type: application/json' -d '{\"type\":\"quick\"}'"
2533
+ echo ""
2534
+ echo "If nothing significant, do nothing. Silence means continuity is working as expected."`,
2509
2535
  },
2510
2536
  tags: ['cat:learning'],
2511
2537
  },
@@ -2549,7 +2575,7 @@ function getDefaultJobs(port) {
2549
2575
  expectedDurationMinutes: 3,
2550
2576
  model: 'opus',
2551
2577
  enabled: true,
2552
- gate: `curl -sf http://localhost:${port}/evolution/learnings?applied=false 2>/dev/null | python3 -c "import sys,json; d=json.load(sys.stdin); exit(0 if len(d.get('learnings',[])) > 0 else 1)"`,
2578
+ gate: `curl -sf -H "Authorization: Bearer $INSTAR_AUTH_TOKEN" http://localhost:${port}/evolution/learnings?applied=false 2>/dev/null | python3 -c "import sys,json; d=json.load(sys.stdin); exit(0 if len(d.get('learnings',[])) > 0 else 1)"`,
2553
2579
  execute: {
2554
2580
  type: 'prompt',
2555
2581
  value: `Harvest and synthesize learnings: curl -s http://localhost:${port}/evolution/learnings?applied=false
@@ -2580,7 +2606,7 @@ If no actionable patterns found, exit silently.`,
2580
2606
  expectedDurationMinutes: 2,
2581
2607
  model: 'haiku',
2582
2608
  enabled: true,
2583
- gate: `curl -sf http://localhost:${port}/evolution/actions/overdue 2>/dev/null | python3 -c "import sys,json; d=json.load(sys.stdin); exit(0 if len(d.get('overdue',[])) > 0 else 1)"`,
2609
+ gate: `curl -sf -H "Authorization: Bearer $INSTAR_AUTH_TOKEN" http://localhost:${port}/evolution/actions/overdue 2>/dev/null | python3 -c "import sys,json; d=json.load(sys.stdin); exit(0 if len(d.get('overdue',[])) > 0 else 1)"`,
2584
2610
  execute: {
2585
2611
  type: 'prompt',
2586
2612
  value: `Check for overdue commitments: curl -s http://localhost:${port}/evolution/actions/overdue
@@ -2738,7 +2764,7 @@ If no overdue or stale items, exit silently.`,
2738
2764
  expectedDurationMinutes: 5,
2739
2765
  model: 'haiku',
2740
2766
  enabled: true,
2741
- gate: 'bash .claude/scripts/git-sync-gate.sh',
2767
+ gate: 'bash ${CLAUDE_PROJECT_DIR}/.claude/scripts/git-sync-gate.sh',
2742
2768
  execute: {
2743
2769
  type: 'skill',
2744
2770
  value: 'git-sync',
@@ -2821,7 +2847,7 @@ If everything is coherent and no reflection is needed, exit silently. Only repor
2821
2847
  expectedDurationMinutes: 3,
2822
2848
  model: 'sonnet',
2823
2849
  enabled: true,
2824
- gate: `curl -sf http://localhost:${port}/evolution/proposals?status=proposed 2>/dev/null | python3 -c "import sys,json; d=json.load(sys.stdin); exit(0 if len(d.get('proposals',[])) > 0 else 1)"`,
2850
+ gate: `curl -sf -H "Authorization: Bearer $INSTAR_AUTH_TOKEN" http://localhost:${port}/evolution/proposals?status=proposed 2>/dev/null | python3 -c "import sys,json; d=json.load(sys.stdin); exit(0 if len(d.get('proposals',[])) > 0 else 1)"`,
2825
2851
  execute: {
2826
2852
  type: 'prompt',
2827
2853
  value: `Review pending evolution proposals: curl -s http://localhost:${port}/evolution/proposals?status=proposed\n\nFor each proposal:\n1. Read the title, description, type, and source\n2. Evaluate: Is this a genuine improvement? Is the effort worth the impact? Does it align with our goals?\n3. If approved, update status: curl -s -X PATCH http://localhost:${port}/evolution/proposals/EVO-XXX -H 'Content-Type: application/json' -d '{"status":"approved"}'\n4. If rejected or deferred, update with reason.\n\nDo NOT implement approved proposals — that's handled by the paired evolution-proposal-implement job.\n\nAlso check the dashboard: curl -s http://localhost:${port}/evolution — report any highlights to the user if they seem important.\n\nIf no proposals need attention, exit silently.`,
@@ -2837,7 +2863,7 @@ If everything is coherent and no reflection is needed, exit silently. Only repor
2837
2863
  expectedDurationMinutes: 10,
2838
2864
  model: 'opus',
2839
2865
  enabled: true,
2840
- gate: `curl -sf http://localhost:${port}/evolution/proposals?status=approved 2>/dev/null | python3 -c "import sys,json; d=json.load(sys.stdin); exit(0 if len(d.get('proposals',[])) > 0 else 1)"`,
2866
+ gate: `curl -sf -H "Authorization: Bearer $INSTAR_AUTH_TOKEN" http://localhost:${port}/evolution/proposals?status=approved 2>/dev/null | python3 -c "import sys,json; d=json.load(sys.stdin); exit(0 if len(d.get('proposals',[])) > 0 else 1)"`,
2841
2867
  execute: {
2842
2868
  type: 'prompt',
2843
2869
  value: `Implement approved evolution proposals: curl -s http://localhost:${port}/evolution/proposals?status=approved\n\nFor each approved proposal:\n1. Read the full description and understand what needs to be built\n2. Implement it: create the skill/hook/job/config change described\n3. After implementation, mark complete: curl -s -X PATCH http://localhost:${port}/evolution/proposals/EVO-XXX -H 'Content-Type: application/json' -d '{"status":"implemented","resolution":"What was done"}'\n\nIf no approved proposals exist, exit silently.`,
@@ -3129,6 +3155,33 @@ function refreshScripts(projectDir, stateDir) {
3129
3155
  // Always install serendipity-capture.sh
3130
3156
  installSerendipityCapture(projectDir);
3131
3157
  }
3158
+ /**
3159
+ * Load a messaging-relay script template from src/templates/scripts/,
3160
+ * substituting the agent's configured port into the INSTAR_PORT fallback.
3161
+ *
3162
+ * The canonical templates live at src/templates/scripts/*-reply.sh. Keeping
3163
+ * a single source of truth (vs. duplicated inlined bash) eliminates a class
3164
+ * of bugs where scaffold-time and migration-time versions drift out of sync.
3165
+ * PostUpdateMigrator.getTelegramReplyScript() uses the same loading pattern.
3166
+ */
3167
+ function loadRelayTemplate(filename, port) {
3168
+ const modDir = path.dirname(new URL(import.meta.url).pathname);
3169
+ const candidates = [
3170
+ // dev: src/commands → ../templates/scripts
3171
+ path.resolve(modDir, '..', 'templates', 'scripts', filename),
3172
+ // dist: dist/commands → ../../src/templates/scripts
3173
+ path.resolve(modDir, '..', '..', 'src', 'templates', 'scripts', filename),
3174
+ ];
3175
+ for (const candidate of candidates) {
3176
+ if (fs.existsSync(candidate)) {
3177
+ const content = fs.readFileSync(candidate, 'utf-8');
3178
+ // Template defaults to port 4040; bake the agent's actual port in so
3179
+ // the script works even without INSTAR_PORT set in the environment.
3180
+ return content.replace('${INSTAR_PORT:-4040}', `\${INSTAR_PORT:-${port}}`);
3181
+ }
3182
+ }
3183
+ throw new Error(`Relay template not found: ${filename}`);
3184
+ }
3132
3185
  /**
3133
3186
  * Install the Telegram relay script that Claude uses to send responses
3134
3187
  * back to Telegram topics via the instar server API.
@@ -3136,62 +3189,8 @@ function refreshScripts(projectDir, stateDir) {
3136
3189
  function installTelegramRelay(projectDir, port) {
3137
3190
  const scriptsDir = path.join(projectDir, '.claude', 'scripts');
3138
3191
  fs.mkdirSync(scriptsDir, { recursive: true });
3139
- const scriptContent = `#!/bin/bash
3140
- # telegram-reply.sh — Send a message back to a Telegram topic via instar server.
3141
- #
3142
- # Usage:
3143
- # .claude/scripts/telegram-reply.sh TOPIC_ID "message text"
3144
- # echo "message text" | .claude/scripts/telegram-reply.sh TOPIC_ID
3145
- # cat <<'EOF' | .claude/scripts/telegram-reply.sh TOPIC_ID
3146
- # Multi-line message here
3147
- # EOF
3148
-
3149
- TOPIC_ID="$1"
3150
- shift
3151
-
3152
- if [ -z "$TOPIC_ID" ]; then
3153
- echo "Usage: telegram-reply.sh TOPIC_ID [message]" >&2
3154
- exit 1
3155
- fi
3156
-
3157
- # Read message from args or stdin
3158
- if [ $# -gt 0 ]; then
3159
- MSG="$*"
3160
- else
3161
- MSG="$(cat)"
3162
- fi
3163
-
3164
- if [ -z "$MSG" ]; then
3165
- echo "No message provided" >&2
3166
- exit 1
3167
- fi
3168
-
3169
- PORT="\${INSTAR_PORT:-${port}}"
3170
-
3171
- # Escape for JSON
3172
- JSON_MSG=$(printf '%s' "$MSG" | python3 -c 'import sys,json; print(json.dumps(sys.stdin.read()))' 2>/dev/null)
3173
- if [ -z "$JSON_MSG" ]; then
3174
- # Fallback if python3 not available: basic escape
3175
- JSON_MSG="$(printf '%s' "$MSG" | sed 's/\\\\\\\\/\\\\\\\\\\\\\\\\/g; s/"/\\\\\\\\"/g' | sed ':a;N;$!ba;s/\\\\n/\\\\\\\\n/g')"
3176
- JSON_MSG="\\"$JSON_MSG\\""
3177
- fi
3178
-
3179
- RESPONSE=$(curl -s -w "\\n%{http_code}" -X POST "http://localhost:\${PORT}/telegram/reply/\${TOPIC_ID}" \\
3180
- -H 'Content-Type: application/json' \\
3181
- -d "{\\"text\\":\${JSON_MSG}}")
3182
-
3183
- HTTP_CODE=$(echo "$RESPONSE" | tail -1)
3184
- BODY=$(echo "$RESPONSE" | sed '$d')
3185
-
3186
- if [ "$HTTP_CODE" = "200" ]; then
3187
- echo "Sent $(echo "$MSG" | wc -c | tr -d ' ') chars to topic $TOPIC_ID"
3188
- else
3189
- echo "Failed (HTTP $HTTP_CODE): $BODY" >&2
3190
- exit 1
3191
- fi
3192
- `;
3193
3192
  const scriptPath = path.join(scriptsDir, 'telegram-reply.sh');
3194
- fs.writeFileSync(scriptPath, scriptContent, { mode: 0o755 });
3193
+ fs.writeFileSync(scriptPath, loadRelayTemplate('telegram-reply.sh', port), { mode: 0o755 });
3195
3194
  }
3196
3195
  /**
3197
3196
  * Install the WhatsApp relay script that Claude uses to send responses
@@ -3200,76 +3199,8 @@ fi
3200
3199
  function installWhatsAppRelay(projectDir, port) {
3201
3200
  const scriptsDir = path.join(projectDir, '.instar', 'scripts');
3202
3201
  fs.mkdirSync(scriptsDir, { recursive: true });
3203
- const scriptContent = `#!/bin/bash
3204
- # whatsapp-reply.sh — Send a message back to a WhatsApp JID via instar server.
3205
- #
3206
- # Usage:
3207
- # .instar/scripts/whatsapp-reply.sh JID "message text"
3208
- # echo "message text" | .instar/scripts/whatsapp-reply.sh JID
3209
- # cat <<'EOF' | .instar/scripts/whatsapp-reply.sh JID
3210
- # Multi-line message here
3211
- # EOF
3212
- #
3213
- # JID format: phone@s.whatsapp.net (e.g., 12345678901@s.whatsapp.net)
3214
-
3215
- JID="$1"
3216
- shift
3217
-
3218
- if [ -z "$JID" ]; then
3219
- echo "Usage: whatsapp-reply.sh JID [message]" >&2
3220
- exit 1
3221
- fi
3222
-
3223
- # Read message from args or stdin
3224
- if [ $# -gt 0 ]; then
3225
- MSG="$*"
3226
- else
3227
- MSG="$(cat)"
3228
- fi
3229
-
3230
- if [ -z "$MSG" ]; then
3231
- echo "No message provided" >&2
3232
- exit 1
3233
- fi
3234
-
3235
- PORT="\${INSTAR_PORT:-${port}}"
3236
-
3237
- # Read auth token from config (if present)
3238
- AUTH_TOKEN=""
3239
- if [ -f ".instar/config.json" ]; then
3240
- AUTH_TOKEN=$(python3 -c "import json; print(json.load(open('.instar/config.json')).get('authToken',''))" 2>/dev/null)
3241
- fi
3242
-
3243
- # Escape for JSON
3244
- JSON_MSG=$(printf '%s' "$MSG" | python3 -c 'import sys,json; print(json.dumps(sys.stdin.read()))' 2>/dev/null)
3245
- if [ -z "$JSON_MSG" ]; then
3246
- JSON_MSG="$(printf '%s' "$MSG" | sed 's/\\\\\\\\/\\\\\\\\\\\\\\\\/g; s/"/\\\\\\\\"/g' | sed ':a;N;$!ba;s/\\\\n/\\\\\\\\n/g')"
3247
- JSON_MSG="\\"$JSON_MSG\\""
3248
- fi
3249
-
3250
- if [ -n "$AUTH_TOKEN" ]; then
3251
- RESPONSE=$(curl -s -w "\\n%{http_code}" -X POST "http://localhost:\${PORT}/whatsapp/send/\${JID}" \\
3252
- -H 'Content-Type: application/json' \\
3253
- -H "Authorization: Bearer \${AUTH_TOKEN}" \\
3254
- -d "{\\"text\\":\${JSON_MSG}}")
3255
- else
3256
- RESPONSE=$(curl -s -w "\\n%{http_code}" -X POST "http://localhost:\${PORT}/whatsapp/send/\${JID}" \\
3257
- -H 'Content-Type: application/json' \\
3258
- -d "{\\"text\\":\${JSON_MSG}}")
3259
- fi
3260
-
3261
- HTTP_CODE=$(echo "$RESPONSE" | tail -1)
3262
- BODY=$(echo "$RESPONSE" | sed '$d')
3263
-
3264
- if [ "$HTTP_CODE" = "200" ]; then
3265
- echo "Sent $(echo "$MSG" | wc -c | tr -d ' ') chars to $JID"
3266
- else
3267
- echo "Failed (HTTP $HTTP_CODE): $BODY" >&2
3268
- exit 1
3269
- fi
3270
- `;
3271
3202
  const scriptPath = path.join(scriptsDir, 'whatsapp-reply.sh');
3272
- fs.writeFileSync(scriptPath, scriptContent, { mode: 0o755 });
3203
+ fs.writeFileSync(scriptPath, loadRelayTemplate('whatsapp-reply.sh', port), { mode: 0o755 });
3273
3204
  }
3274
3205
  /**
3275
3206
  * Append missing sections to CLAUDE.md without overwriting user customizations.
@@ -3983,27 +3914,27 @@ function installClaudeSettings(projectDir, serverPort) {
3983
3914
  const instarBashHooks = [
3984
3915
  {
3985
3916
  type: 'command',
3986
- command: 'bash .instar/hooks/instar/dangerous-command-guard.sh "$TOOL_INPUT"',
3917
+ command: 'bash ${CLAUDE_PROJECT_DIR}/.instar/hooks/instar/dangerous-command-guard.sh "$TOOL_INPUT"',
3987
3918
  blocking: true,
3988
3919
  },
3989
3920
  {
3990
3921
  type: 'command',
3991
- command: 'bash .instar/hooks/instar/grounding-before-messaging.sh "$TOOL_INPUT"',
3922
+ command: 'bash ${CLAUDE_PROJECT_DIR}/.instar/hooks/instar/grounding-before-messaging.sh "$TOOL_INPUT"',
3992
3923
  blocking: false,
3993
3924
  },
3994
3925
  {
3995
3926
  type: 'command',
3996
- command: 'node .instar/hooks/instar/deferral-detector.js',
3927
+ command: 'node ${CLAUDE_PROJECT_DIR}/.instar/hooks/instar/deferral-detector.js',
3997
3928
  timeout: 5000,
3998
3929
  },
3999
3930
  {
4000
3931
  type: 'command',
4001
- command: 'node .instar/hooks/instar/external-communication-guard.js',
3932
+ command: 'node ${CLAUDE_PROJECT_DIR}/.instar/hooks/instar/external-communication-guard.js',
4002
3933
  timeout: 5000,
4003
3934
  },
4004
3935
  {
4005
3936
  type: 'command',
4006
- command: 'node .instar/hooks/instar/post-action-reflection.js',
3937
+ command: 'node ${CLAUDE_PROJECT_DIR}/.instar/hooks/instar/post-action-reflection.js',
4007
3938
  timeout: 5000,
4008
3939
  },
4009
3940
  ];
@@ -4011,7 +3942,7 @@ function installClaudeSettings(projectDir, serverPort) {
4011
3942
  const instarMcpHooks = [
4012
3943
  {
4013
3944
  type: 'command',
4014
- command: 'node .instar/hooks/instar/external-operation-gate.js',
3945
+ command: 'node ${CLAUDE_PROJECT_DIR}/.instar/hooks/instar/external-operation-gate.js',
4015
3946
  blocking: true,
4016
3947
  timeout: 5000,
4017
3948
  },
@@ -4060,7 +3991,7 @@ function installClaudeSettings(projectDir, serverPort) {
4060
3991
  // The session-start.sh hook handles event routing internally via CLAUDE_HOOK_MATCHER
4061
3992
  const sessionStartHook = {
4062
3993
  type: 'command',
4063
- command: 'bash .instar/hooks/instar/session-start.sh',
3994
+ command: 'bash ${CLAUDE_PROJECT_DIR}/.instar/hooks/instar/session-start.sh',
4064
3995
  timeout: 5,
4065
3996
  };
4066
3997
  if (!hooks.SessionStart) {
@@ -4115,7 +4046,7 @@ function installClaudeSettings(projectDir, serverPort) {
4115
4046
  // PostToolUse: scope coherence collector tracks implementation depth
4116
4047
  const scopeCollectorHook = {
4117
4048
  type: 'command',
4118
- command: 'node .instar/hooks/instar/scope-coherence-collector.js',
4049
+ command: 'node ${CLAUDE_PROJECT_DIR}/.instar/hooks/instar/scope-coherence-collector.js',
4119
4050
  timeout: 5000,
4120
4051
  };
4121
4052
  if (!hooks.PostToolUse) {
@@ -4126,7 +4057,7 @@ function installClaudeSettings(projectDir, serverPort) {
4126
4057
  // (scope collector also added to Read and Skill)
4127
4058
  const claimInterceptHook = {
4128
4059
  type: 'command',
4129
- command: 'node .instar/hooks/instar/claim-intercept.js',
4060
+ command: 'node ${CLAUDE_PROJECT_DIR}/.instar/hooks/instar/claim-intercept.js',
4130
4061
  timeout: 5000,
4131
4062
  };
4132
4063
  for (const matcher of ['Edit', 'Write', 'Bash', 'Read', 'Skill']) {
@@ -4151,19 +4082,19 @@ function installClaudeSettings(projectDir, serverPort) {
4151
4082
  // Stop: response review pipeline — Coherence Gate LLM-powered review
4152
4083
  const responseReviewHook = {
4153
4084
  type: 'command',
4154
- command: 'node .instar/hooks/instar/response-review.js',
4085
+ command: 'node ${CLAUDE_PROJECT_DIR}/.instar/hooks/instar/response-review.js',
4155
4086
  timeout: 10000,
4156
4087
  };
4157
4088
  // Stop: scope coherence checkpoint fires the zoom-out prompt
4158
4089
  const scopeCheckpointHook = {
4159
4090
  type: 'command',
4160
- command: 'node .instar/hooks/instar/scope-coherence-checkpoint.js',
4091
+ command: 'node ${CLAUDE_PROJECT_DIR}/.instar/hooks/instar/scope-coherence-checkpoint.js',
4161
4092
  timeout: 10000,
4162
4093
  };
4163
4094
  // Stop: claim intercept response checks direct text for false claims
4164
4095
  const claimInterceptResponseHook = {
4165
4096
  type: 'command',
4166
- command: 'node .instar/hooks/instar/claim-intercept-response.js',
4097
+ command: 'node ${CLAUDE_PROJECT_DIR}/.instar/hooks/instar/claim-intercept-response.js',
4167
4098
  timeout: 10000,
4168
4099
  };
4169
4100
  if (!hooks.Stop) {
@@ -4191,7 +4122,7 @@ function installClaudeSettings(projectDir, serverPort) {
4191
4122
  if (!hasAutonomousHook) {
4192
4123
  hooks.Stop.unshift({ matcher: '', hooks: [{
4193
4124
  type: 'command',
4194
- command: 'bash .claude/skills/autonomous/hooks/autonomous-stop-hook.sh',
4125
+ command: 'bash ${CLAUDE_PROJECT_DIR}/.claude/skills/autonomous/hooks/autonomous-stop-hook.sh',
4195
4126
  timeout: 10000,
4196
4127
  }] });
4197
4128
  }
@@ -4207,7 +4138,7 @@ function installClaudeSettings(projectDir, serverPort) {
4207
4138
  matcher: '',
4208
4139
  hooks: [{
4209
4140
  type: 'command',
4210
- command: 'node .instar/hooks/instar/auto-approve-permissions.js',
4141
+ command: 'node ${CLAUDE_PROJECT_DIR}/.instar/hooks/instar/auto-approve-permissions.js',
4211
4142
  timeout: 5000,
4212
4143
  }],
4213
4144
  });