instar 1.3.102 → 1.3.104

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 (44) hide show
  1. package/dist/commands/gate.d.ts.map +1 -1
  2. package/dist/commands/gate.js +8 -1
  3. package/dist/commands/gate.js.map +1 -1
  4. package/dist/commands/nuke.d.ts.map +1 -1
  5. package/dist/commands/nuke.js +19 -10
  6. package/dist/commands/nuke.js.map +1 -1
  7. package/dist/commands/server.d.ts.map +1 -1
  8. package/dist/commands/server.js +81 -0
  9. package/dist/commands/server.js.map +1 -1
  10. package/dist/core/PostUpdateMigrator.d.ts +33 -0
  11. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  12. package/dist/core/PostUpdateMigrator.js +155 -20
  13. package/dist/core/PostUpdateMigrator.js.map +1 -1
  14. package/dist/core/TopicPlacementPinStore.d.ts +37 -0
  15. package/dist/core/TopicPlacementPinStore.d.ts.map +1 -0
  16. package/dist/core/TopicPlacementPinStore.js +95 -0
  17. package/dist/core/TopicPlacementPinStore.js.map +1 -0
  18. package/dist/server/middleware.d.ts.map +1 -1
  19. package/dist/server/middleware.js +13 -0
  20. package/dist/server/middleware.js.map +1 -1
  21. package/dist/threadline/listener-daemon.d.ts.map +1 -1
  22. package/dist/threadline/listener-daemon.js +18 -8
  23. package/dist/threadline/listener-daemon.js.map +1 -1
  24. package/dist/threadline/mcp-stdio-entry.js +8 -3
  25. package/dist/threadline/mcp-stdio-entry.js.map +1 -1
  26. package/package.json +1 -1
  27. package/scripts/threadline-bridge-backfill.mjs +7 -1
  28. package/src/data/builtin-manifest.json +29 -29
  29. package/src/templates/hooks/compaction-recovery.sh +24 -9
  30. package/src/templates/hooks/session-start.sh +9 -2
  31. package/src/templates/hooks/slack-channel-context.sh +8 -1
  32. package/src/templates/hooks/telegram-topic-context.sh +14 -2
  33. package/src/templates/scripts/imessage-reply.sh +7 -3
  34. package/src/templates/scripts/instar-watchdog.sh +14 -5
  35. package/src/templates/scripts/secret-drop-retrieve.mjs +12 -4
  36. package/src/templates/scripts/serendipity-capture.sh +15 -9
  37. package/src/templates/scripts/slack-reply.sh +8 -1
  38. package/src/templates/scripts/telegram-reply.sh +11 -5
  39. package/src/templates/scripts/whatsapp-reply.sh +7 -4
  40. package/upgrades/1.3.103.md +77 -0
  41. package/upgrades/1.3.104.md +58 -0
  42. package/upgrades/NEXT.eli16.md +71 -0
  43. package/upgrades/side-effects/mesh-rpc-auth-and-transfer-activation.md +72 -0
  44. package/upgrades/side-effects/secret-externalization-hook-resolver-audit.md +48 -0
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "$schema": "./builtin-manifest.schema.json",
3
3
  "schemaVersion": 1,
4
- "generatedAt": "2026-05-29T23:18:48.200Z",
5
- "instarVersion": "1.3.102",
4
+ "generatedAt": "2026-05-30T00:17:16.098Z",
5
+ "instarVersion": "1.3.104",
6
6
  "entryCount": 193,
7
7
  "entries": {
8
8
  "hook:session-start": {
@@ -11,7 +11,7 @@
11
11
  "domain": "identity",
12
12
  "sourcePath": "src/core/PostUpdateMigrator.ts",
13
13
  "installedPath": ".instar/hooks/instar/session-start.sh",
14
- "contentHash": "7a002d7b539b987c142ef77985ff086a370571c1c6922a1a6e9b7119a3d652c3",
14
+ "contentHash": "a2f7941039c9bb8bfe6777412d3fee869077eb9a3d6e359c8f5efe52b3a693bb",
15
15
  "since": "2025-01-01"
16
16
  },
17
17
  "hook:dangerous-command-guard": {
@@ -20,7 +20,7 @@
20
20
  "domain": "safety",
21
21
  "sourcePath": "src/core/PostUpdateMigrator.ts",
22
22
  "installedPath": ".instar/hooks/instar/dangerous-command-guard.sh",
23
- "contentHash": "7a002d7b539b987c142ef77985ff086a370571c1c6922a1a6e9b7119a3d652c3",
23
+ "contentHash": "a2f7941039c9bb8bfe6777412d3fee869077eb9a3d6e359c8f5efe52b3a693bb",
24
24
  "since": "2025-01-01"
25
25
  },
26
26
  "hook:grounding-before-messaging": {
@@ -29,7 +29,7 @@
29
29
  "domain": "safety",
30
30
  "sourcePath": "src/core/PostUpdateMigrator.ts",
31
31
  "installedPath": ".instar/hooks/instar/grounding-before-messaging.sh",
32
- "contentHash": "7a002d7b539b987c142ef77985ff086a370571c1c6922a1a6e9b7119a3d652c3",
32
+ "contentHash": "a2f7941039c9bb8bfe6777412d3fee869077eb9a3d6e359c8f5efe52b3a693bb",
33
33
  "since": "2025-01-01"
34
34
  },
35
35
  "hook:compaction-recovery": {
@@ -38,7 +38,7 @@
38
38
  "domain": "identity",
39
39
  "sourcePath": "src/core/PostUpdateMigrator.ts",
40
40
  "installedPath": ".instar/hooks/instar/compaction-recovery.sh",
41
- "contentHash": "7a002d7b539b987c142ef77985ff086a370571c1c6922a1a6e9b7119a3d652c3",
41
+ "contentHash": "a2f7941039c9bb8bfe6777412d3fee869077eb9a3d6e359c8f5efe52b3a693bb",
42
42
  "since": "2025-01-01"
43
43
  },
44
44
  "hook:external-operation-gate": {
@@ -47,7 +47,7 @@
47
47
  "domain": "safety",
48
48
  "sourcePath": "src/core/PostUpdateMigrator.ts",
49
49
  "installedPath": ".instar/hooks/instar/external-operation-gate.js",
50
- "contentHash": "7a002d7b539b987c142ef77985ff086a370571c1c6922a1a6e9b7119a3d652c3",
50
+ "contentHash": "a2f7941039c9bb8bfe6777412d3fee869077eb9a3d6e359c8f5efe52b3a693bb",
51
51
  "since": "2025-01-01"
52
52
  },
53
53
  "hook:deferral-detector": {
@@ -56,7 +56,7 @@
56
56
  "domain": "safety",
57
57
  "sourcePath": "src/core/PostUpdateMigrator.ts",
58
58
  "installedPath": ".instar/hooks/instar/deferral-detector.js",
59
- "contentHash": "7a002d7b539b987c142ef77985ff086a370571c1c6922a1a6e9b7119a3d652c3",
59
+ "contentHash": "a2f7941039c9bb8bfe6777412d3fee869077eb9a3d6e359c8f5efe52b3a693bb",
60
60
  "since": "2025-01-01"
61
61
  },
62
62
  "hook:post-action-reflection": {
@@ -65,7 +65,7 @@
65
65
  "domain": "evolution",
66
66
  "sourcePath": "src/core/PostUpdateMigrator.ts",
67
67
  "installedPath": ".instar/hooks/instar/post-action-reflection.js",
68
- "contentHash": "7a002d7b539b987c142ef77985ff086a370571c1c6922a1a6e9b7119a3d652c3",
68
+ "contentHash": "a2f7941039c9bb8bfe6777412d3fee869077eb9a3d6e359c8f5efe52b3a693bb",
69
69
  "since": "2025-01-01"
70
70
  },
71
71
  "hook:external-communication-guard": {
@@ -74,7 +74,7 @@
74
74
  "domain": "safety",
75
75
  "sourcePath": "src/core/PostUpdateMigrator.ts",
76
76
  "installedPath": ".instar/hooks/instar/external-communication-guard.js",
77
- "contentHash": "7a002d7b539b987c142ef77985ff086a370571c1c6922a1a6e9b7119a3d652c3",
77
+ "contentHash": "a2f7941039c9bb8bfe6777412d3fee869077eb9a3d6e359c8f5efe52b3a693bb",
78
78
  "since": "2025-01-01"
79
79
  },
80
80
  "hook:scope-coherence-collector": {
@@ -83,7 +83,7 @@
83
83
  "domain": "coherence",
84
84
  "sourcePath": "src/core/PostUpdateMigrator.ts",
85
85
  "installedPath": ".instar/hooks/instar/scope-coherence-collector.js",
86
- "contentHash": "7a002d7b539b987c142ef77985ff086a370571c1c6922a1a6e9b7119a3d652c3",
86
+ "contentHash": "a2f7941039c9bb8bfe6777412d3fee869077eb9a3d6e359c8f5efe52b3a693bb",
87
87
  "since": "2025-01-01"
88
88
  },
89
89
  "hook:scope-coherence-checkpoint": {
@@ -92,7 +92,7 @@
92
92
  "domain": "coherence",
93
93
  "sourcePath": "src/core/PostUpdateMigrator.ts",
94
94
  "installedPath": ".instar/hooks/instar/scope-coherence-checkpoint.js",
95
- "contentHash": "7a002d7b539b987c142ef77985ff086a370571c1c6922a1a6e9b7119a3d652c3",
95
+ "contentHash": "a2f7941039c9bb8bfe6777412d3fee869077eb9a3d6e359c8f5efe52b3a693bb",
96
96
  "since": "2025-01-01"
97
97
  },
98
98
  "hook:free-text-guard": {
@@ -101,7 +101,7 @@
101
101
  "domain": "safety",
102
102
  "sourcePath": "src/core/PostUpdateMigrator.ts",
103
103
  "installedPath": ".instar/hooks/instar/free-text-guard.sh",
104
- "contentHash": "7a002d7b539b987c142ef77985ff086a370571c1c6922a1a6e9b7119a3d652c3",
104
+ "contentHash": "a2f7941039c9bb8bfe6777412d3fee869077eb9a3d6e359c8f5efe52b3a693bb",
105
105
  "since": "2025-01-01"
106
106
  },
107
107
  "hook:claim-intercept": {
@@ -110,7 +110,7 @@
110
110
  "domain": "coherence",
111
111
  "sourcePath": "src/core/PostUpdateMigrator.ts",
112
112
  "installedPath": ".instar/hooks/instar/claim-intercept.js",
113
- "contentHash": "7a002d7b539b987c142ef77985ff086a370571c1c6922a1a6e9b7119a3d652c3",
113
+ "contentHash": "a2f7941039c9bb8bfe6777412d3fee869077eb9a3d6e359c8f5efe52b3a693bb",
114
114
  "since": "2025-01-01"
115
115
  },
116
116
  "hook:claim-intercept-response": {
@@ -119,7 +119,7 @@
119
119
  "domain": "coherence",
120
120
  "sourcePath": "src/core/PostUpdateMigrator.ts",
121
121
  "installedPath": ".instar/hooks/instar/claim-intercept-response.js",
122
- "contentHash": "7a002d7b539b987c142ef77985ff086a370571c1c6922a1a6e9b7119a3d652c3",
122
+ "contentHash": "a2f7941039c9bb8bfe6777412d3fee869077eb9a3d6e359c8f5efe52b3a693bb",
123
123
  "since": "2025-01-01"
124
124
  },
125
125
  "hook:stop-gate-router": {
@@ -128,7 +128,7 @@
128
128
  "domain": "safety",
129
129
  "sourcePath": "src/core/PostUpdateMigrator.ts",
130
130
  "installedPath": ".instar/hooks/instar/stop-gate-router.js",
131
- "contentHash": "7a002d7b539b987c142ef77985ff086a370571c1c6922a1a6e9b7119a3d652c3",
131
+ "contentHash": "a2f7941039c9bb8bfe6777412d3fee869077eb9a3d6e359c8f5efe52b3a693bb",
132
132
  "since": "2025-01-01"
133
133
  },
134
134
  "hook:auto-approve-permissions": {
@@ -137,7 +137,7 @@
137
137
  "domain": "safety",
138
138
  "sourcePath": "src/core/PostUpdateMigrator.ts",
139
139
  "installedPath": ".instar/hooks/instar/auto-approve-permissions.js",
140
- "contentHash": "7a002d7b539b987c142ef77985ff086a370571c1c6922a1a6e9b7119a3d652c3",
140
+ "contentHash": "a2f7941039c9bb8bfe6777412d3fee869077eb9a3d6e359c8f5efe52b3a693bb",
141
141
  "since": "2025-01-01"
142
142
  },
143
143
  "job:health-check": {
@@ -1009,7 +1009,7 @@
1009
1009
  "type": "template",
1010
1010
  "domain": "safety",
1011
1011
  "sourcePath": "src/templates/hooks/compaction-recovery.sh",
1012
- "contentHash": "ca6a2a5c2ee03839654c9708912292bc03d81c61281dffbbb958d4877ee1dc91",
1012
+ "contentHash": "f65f4731a5142981cf021e948afa5b064dcc35ceadf6efd5354d749f2d85569f",
1013
1013
  "since": "2025-01-01"
1014
1014
  },
1015
1015
  "template:dangerous-command-guard.sh": {
@@ -1049,7 +1049,7 @@
1049
1049
  "type": "template",
1050
1050
  "domain": "safety",
1051
1051
  "sourcePath": "src/templates/hooks/session-start.sh",
1052
- "contentHash": "4d98736d3ee30fe8639f0851e5e492416ad05f4ae49f84e8695723faa4b9398e",
1052
+ "contentHash": "d8b20ab9e46aecc400171bde09a5a64f5a1eeb53e09017564cebc65e67fca062",
1053
1053
  "since": "2025-01-01"
1054
1054
  },
1055
1055
  "template:settings-template.json": {
@@ -1073,7 +1073,7 @@
1073
1073
  "type": "template",
1074
1074
  "domain": "safety",
1075
1075
  "sourcePath": "src/templates/hooks/slack-channel-context.sh",
1076
- "contentHash": "da9c34225af5d0a1f202d51263d864ac2ac8facf7f4e4b4d736e2b2b9b54d70a",
1076
+ "contentHash": "ebb2a74ce3819a148b1cfaace3d3467d317ed8cccd98db5cd7ffe0df3a2e9784",
1077
1077
  "since": "2025-01-01"
1078
1078
  },
1079
1079
  "template:telegram-topic-context.sh": {
@@ -1081,7 +1081,7 @@
1081
1081
  "type": "template",
1082
1082
  "domain": "safety",
1083
1083
  "sourcePath": "src/templates/hooks/telegram-topic-context.sh",
1084
- "contentHash": "57cfb744bbffcd21f6290c5f38726b105b93258bf09cc539c24d66aebed9c40e",
1084
+ "contentHash": "317b11084547271dab3f333be8bdf5e536a5af7239f50d51030c62990b4b7e59",
1085
1085
  "since": "2025-01-01"
1086
1086
  },
1087
1087
  "template:convergence-check.sh": {
@@ -1113,7 +1113,7 @@
1113
1113
  "type": "template",
1114
1114
  "domain": "operations",
1115
1115
  "sourcePath": "src/templates/scripts/imessage-reply.sh",
1116
- "contentHash": "aa34955027f094d5a78973762ac30c92816cc73399108b1ad0fb427e3ae70f1c",
1116
+ "contentHash": "e4ecc66feaaea92c5f65f008e5b82b1022c7fd61ee9e4f1ed1346106b4a63d6c",
1117
1117
  "since": "2025-01-01"
1118
1118
  },
1119
1119
  "template:instar-watchdog.sh": {
@@ -1121,7 +1121,7 @@
1121
1121
  "type": "template",
1122
1122
  "domain": "operations",
1123
1123
  "sourcePath": "src/templates/scripts/instar-watchdog.sh",
1124
- "contentHash": "d81ed54264fbb6091f2b29365c6dd318f491f2acfabbbb5ff20582336867f53d",
1124
+ "contentHash": "1f462b267e6724bea21692e617bd61a6b7cd4def7187321871cdc1fc178d1d1b",
1125
1125
  "since": "2025-01-01"
1126
1126
  },
1127
1127
  "template:instar-worktree-create.sh": {
@@ -1137,7 +1137,7 @@
1137
1137
  "type": "template",
1138
1138
  "domain": "operations",
1139
1139
  "sourcePath": "src/templates/scripts/secret-drop-retrieve.mjs",
1140
- "contentHash": "2acc881ce53c879047f0e98b9afd5c66f293c48046c62fff8587123078145c98",
1140
+ "contentHash": "3c99a8d8898868e31e88c4b729b57968c14cb723c9ffb6f903d36a1e18841156",
1141
1141
  "since": "2025-01-01"
1142
1142
  },
1143
1143
  "template:serendipity-capture.sh": {
@@ -1145,7 +1145,7 @@
1145
1145
  "type": "template",
1146
1146
  "domain": "operations",
1147
1147
  "sourcePath": "src/templates/scripts/serendipity-capture.sh",
1148
- "contentHash": "2e905386fd889bfc9246a44a3b2c58a6cb68f02e703aded8900107ab5188570b",
1148
+ "contentHash": "317bc0bbb09bf03c9755ab25ca0b8da940eccb8a74886fac60045163c6bdd091",
1149
1149
  "since": "2025-01-01"
1150
1150
  },
1151
1151
  "template:slack-reply.sh": {
@@ -1153,7 +1153,7 @@
1153
1153
  "type": "template",
1154
1154
  "domain": "operations",
1155
1155
  "sourcePath": "src/templates/scripts/slack-reply.sh",
1156
- "contentHash": "1813442032e435e4ecbf01467510e39a33c3feb3acb1407307c5569a65f30305",
1156
+ "contentHash": "ea698ef54e0e48233d82225c6cfcf886f58c7939101d966cfafc5e85edcac6a4",
1157
1157
  "since": "2025-01-01"
1158
1158
  },
1159
1159
  "template:smart-fetch.py": {
@@ -1169,7 +1169,7 @@
1169
1169
  "type": "template",
1170
1170
  "domain": "operations",
1171
1171
  "sourcePath": "src/templates/scripts/telegram-reply.sh",
1172
- "contentHash": "371d7e8f4f72146bf8bd07115873bdbbaaf32e851ac6e1318ba5b8929cd06e68",
1172
+ "contentHash": "0f6d27a522b123551871e6081774f8c89d1ad0ce248597af7dd60d8522871069",
1173
1173
  "since": "2025-01-01"
1174
1174
  },
1175
1175
  "template:whatsapp-reply.sh": {
@@ -1177,7 +1177,7 @@
1177
1177
  "type": "template",
1178
1178
  "domain": "operations",
1179
1179
  "sourcePath": "src/templates/scripts/whatsapp-reply.sh",
1180
- "contentHash": "a226d76025036d6b0f11ffcded50a748ff8d2102f2dbb06ce86599001b62ec23",
1180
+ "contentHash": "942712aa272235bca9f14ab6baa23bbe5ae0a8bf20f76edf87c963b181a7764c",
1181
1181
  "since": "2025-01-01"
1182
1182
  },
1183
1183
  "playbook-script:atomic_write.py": {
@@ -1489,7 +1489,7 @@
1489
1489
  "type": "subsystem",
1490
1490
  "domain": "updates",
1491
1491
  "sourcePath": "src/core/PostUpdateMigrator.ts",
1492
- "contentHash": "7a002d7b539b987c142ef77985ff086a370571c1c6922a1a6e9b7119a3d652c3",
1492
+ "contentHash": "a2f7941039c9bb8bfe6777412d3fee869077eb9a3d6e359c8f5efe52b3a693bb",
1493
1493
  "since": "2025-01-01"
1494
1494
  },
1495
1495
  "subsystem:scheduler": {
@@ -174,9 +174,12 @@ echo ""
174
174
  # Resolves Open Question #5 from the Consent & Discovery Framework spec:
175
175
  # lastSurfacedAt timestamps persist in SQLite regardless of context state.
176
176
  if [ -f "$CONFIG_FILE" ]; then
177
- PORT=$(grep -o '"port":[0-9]*' "$CONFIG_FILE" | head -1 | cut -d':' -f2)
177
+ PORT=$(grep -oE '"port"[[:space:]]*:[[:space:]]*[0-9]+' "$CONFIG_FILE" | head -1 | grep -oE '[0-9]+' | head -1)
178
178
  if [ -n "$PORT" ]; then
179
- AUTH_TOKEN=$(python3 -c "import json; print(json.load(open('$CONFIG_FILE')).get('authToken',''))" 2>/dev/null)
179
+ AUTH_TOKEN="${INSTAR_AUTH_TOKEN:-}"
180
+ if [ -z "$AUTH_TOKEN" ]; then
181
+ AUTH_TOKEN=$(python3 -c "import json; v=json.load(open('$CONFIG_FILE')).get('authToken',''); print(v if isinstance(v, str) else '')" 2>/dev/null)
182
+ fi
180
183
  FEATURES_JSON=$(curl -s -H "Authorization: Bearer ${AUTH_TOKEN}" \
181
184
  "http://localhost:${PORT}/features/summary" 2>/dev/null)
182
185
  if [ -n "$FEATURES_JSON" ]; then
@@ -235,7 +238,7 @@ fi
235
238
  # Server health reminder + recent Telegram context
236
239
  CONFIG_FILE="$INSTAR_DIR/config.json"
237
240
  if [ -f "$CONFIG_FILE" ]; then
238
- PORT=$(grep -o '"port":[0-9]*' "$CONFIG_FILE" | head -1 | cut -d':' -f2)
241
+ PORT=$(grep -oE '"port"[[:space:]]*:[[:space:]]*[0-9]+' "$CONFIG_FILE" | head -1 | grep -oE '[0-9]+' | head -1)
239
242
  if [ -n "$PORT" ]; then
240
243
  echo "Server: curl http://localhost:${PORT}/health | Capabilities: curl http://localhost:${PORT}/capabilities"
241
244
 
@@ -247,7 +250,10 @@ if [ -f "$CONFIG_FILE" ]; then
247
250
  # Slack channel context — if INSTAR_SLACK_CHANNEL is set, this is a Slack session
248
251
  SLACK_CHANNEL="${INSTAR_SLACK_CHANNEL:-}"
249
252
  if [ -n "$SLACK_CHANNEL" ]; then
250
- AUTH_TOKEN=$(python3 -c "import json; print(json.load(open('$CONFIG_FILE')).get('authToken',''))" 2>/dev/null)
253
+ AUTH_TOKEN="${INSTAR_AUTH_TOKEN:-}"
254
+ if [ -z "$AUTH_TOKEN" ]; then
255
+ AUTH_TOKEN=$(python3 -c "import json; v=json.load(open('$CONFIG_FILE')).get('authToken',''); print(v if isinstance(v, str) else '')" 2>/dev/null)
256
+ fi
251
257
  SLACK_MSGS=$(curl -s \
252
258
  -H "Authorization: Bearer ${AUTH_TOKEN}" \
253
259
  "http://localhost:${PORT}/slack/channels/${SLACK_CHANNEL}/messages?limit=20" 2>/dev/null)
@@ -346,7 +352,10 @@ except Exception:
346
352
  fi
347
353
 
348
354
  if [ -n "$TOPIC_FOR_CONTEXT" ]; then
349
- AUTH_TOKEN=$(python3 -c "import json; print(json.load(open('$CONFIG_FILE')).get('authToken',''))" 2>/dev/null)
355
+ AUTH_TOKEN="${INSTAR_AUTH_TOKEN:-}"
356
+ if [ -z "$AUTH_TOKEN" ]; then
357
+ AUTH_TOKEN=$(python3 -c "import json; v=json.load(open('$CONFIG_FILE')).get('authToken',''); print(v if isinstance(v, str) else '')" 2>/dev/null)
358
+ fi
350
359
  if [ -n "$AUTH_TOKEN" ]; then
351
360
  RECENT_MSGS=$(curl -s \
352
361
  -H "Authorization: Bearer ${AUTH_TOKEN}" \
@@ -418,9 +427,12 @@ fi
418
427
  # This surfaces what you already know that's relevant to the current context,
419
428
  # preventing you from re-deriving knowledge you've already accumulated.
420
429
  if [ -f "$CONFIG_FILE" ]; then
421
- PORT=$(grep -o '"port":[0-9]*' "$CONFIG_FILE" | head -1 | cut -d':' -f2)
430
+ PORT=$(grep -oE '"port"[[:space:]]*:[[:space:]]*[0-9]+' "$CONFIG_FILE" | head -1 | grep -oE '[0-9]+' | head -1)
422
431
  if [ -n "$PORT" ]; then
423
- AUTH_TOKEN=$(python3 -c "import json; print(json.load(open('$CONFIG_FILE')).get('authToken',''))" 2>/dev/null)
432
+ AUTH_TOKEN="${INSTAR_AUTH_TOKEN:-}"
433
+ if [ -z "$AUTH_TOKEN" ]; then
434
+ AUTH_TOKEN=$(python3 -c "import json; v=json.load(open('$CONFIG_FILE')).get('authToken',''); print(v if isinstance(v, str) else '')" 2>/dev/null)
435
+ fi
424
436
  WORKING_MEM=$(curl -s -H "Authorization: Bearer ${AUTH_TOKEN}" \
425
437
  "http://localhost:${PORT}/context/working-memory?prompt=compaction-recovery&limit=5" 2>/dev/null)
426
438
  if [ -n "$WORKING_MEM" ]; then
@@ -452,10 +464,13 @@ fi
452
464
  # so it gives us a guaranteed signal. The server will check for unanswered user
453
465
  # messages and re-inject them shortly after this hook completes.
454
466
  if [ -f "$CONFIG_FILE" ] && command -v tmux >/dev/null 2>&1; then
455
- PORT=$(grep -o '"port":[0-9]*' "$CONFIG_FILE" | head -1 | cut -d':' -f2)
467
+ PORT=$(grep -oE '"port"[[:space:]]*:[[:space:]]*[0-9]+' "$CONFIG_FILE" | head -1 | grep -oE '[0-9]+' | head -1)
456
468
  TMUX_SESSION=$(tmux display-message -p '#S' 2>/dev/null)
457
469
  if [ -n "$PORT" ] && [ -n "$TMUX_SESSION" ]; then
458
- AUTH_TOKEN=$(python3 -c "import json; print(json.load(open('$CONFIG_FILE')).get('authToken',''))" 2>/dev/null)
470
+ AUTH_TOKEN="${INSTAR_AUTH_TOKEN:-}"
471
+ if [ -z "$AUTH_TOKEN" ]; then
472
+ AUTH_TOKEN=$(python3 -c "import json; v=json.load(open('$CONFIG_FILE')).get('authToken',''); print(v if isinstance(v, str) else '')" 2>/dev/null)
473
+ fi
459
474
  curl -s -m 2 -X POST \
460
475
  -H "Authorization: Bearer ${AUTH_TOKEN}" \
461
476
  -H "Content-Type: application/json" \
@@ -21,12 +21,19 @@ if [ ! -f "$CONFIG_FILE" ]; then
21
21
  exit 0
22
22
  fi
23
23
 
24
- PORT=$(grep -o '"port":[0-9]*' "$CONFIG_FILE" | head -1 | cut -d':' -f2)
24
+ PORT=$(grep -oE '"port"[[:space:]]*:[[:space:]]*[0-9]+' "$CONFIG_FILE" | head -1 | grep -oE '[0-9]+' | head -1)
25
25
  if [ -z "$PORT" ]; then
26
26
  exit 0
27
27
  fi
28
28
 
29
- AUTH_TOKEN=$(python3 -c "import json; print(json.load(open('$CONFIG_FILE')).get('authToken',''))" 2>/dev/null)
29
+ # Resolve auth token: INSTAR_AUTH_TOKEN env first (SessionManager injects it per
30
+ # spawned session — survives secret-externalization), legacy plaintext-config
31
+ # fallback with string-type guard so the { "secret": true } placeholder produced
32
+ # by SecretMigrator cannot leak through as a bogus Bearer.
33
+ AUTH_TOKEN="${INSTAR_AUTH_TOKEN:-}"
34
+ if [ -z "$AUTH_TOKEN" ]; then
35
+ AUTH_TOKEN=$(python3 -c "import json; v=json.load(open('$CONFIG_FILE')).get('authToken',''); print(v if isinstance(v, str) else '')" 2>/dev/null)
36
+ fi
30
37
 
31
38
  # Check if server is alive
32
39
  HEALTH=$(curl -s -o /dev/null -w "%{http_code}" \
@@ -29,7 +29,14 @@ if [ -f ".instar/config.json" ]; then
29
29
  CONFIG_PORT=$(python3 -c "import json; print(json.load(open('.instar/config.json')).get('port',''))" 2>/dev/null)
30
30
  [ -n "$CONFIG_PORT" ] && PORT="$CONFIG_PORT"
31
31
  fi
32
- AUTH=$(python3 -c "import json; print(json.load(open('.instar/config.json')).get('authToken',''))" 2>/dev/null)
32
+ # Auth resolution: INSTAR_AUTH_TOKEN env first (SessionManager injects it per
33
+ # spawned session — survives secret-externalization), legacy plaintext-config
34
+ # fallback with string-type guard so the { "secret": true } placeholder
35
+ # produced by SecretMigrator cannot leak through as a bogus Bearer.
36
+ AUTH="${INSTAR_AUTH_TOKEN:-}"
37
+ if [ -z "$AUTH" ]; then
38
+ AUTH=$(python3 -c "import json; v=json.load(open('.instar/config.json')).get('authToken',''); print(v if isinstance(v, str) else '')" 2>/dev/null)
39
+ fi
33
40
  fi
34
41
 
35
42
  PORT="${PORT:-4042}"
@@ -43,7 +43,7 @@ if [ ! -f "$CONFIG_FILE" ]; then
43
43
  exit 0
44
44
  fi
45
45
 
46
- PORT=$(grep -o '"port":[0-9]*' "$CONFIG_FILE" | head -1 | cut -d':' -f2)
46
+ PORT=$(grep -oE '"port"[[:space:]]*:[[:space:]]*[0-9]+' "$CONFIG_FILE" | head -1 | grep -oE '[0-9]+' | head -1)
47
47
  if [ -z "$PORT" ]; then
48
48
  exit 0
49
49
  fi
@@ -54,8 +54,20 @@ if [ "$HEALTH" != "200" ]; then
54
54
  exit 0
55
55
  fi
56
56
 
57
+ # Resolve the auth token. INSTAR_AUTH_TOKEN env first — SessionManager injects
58
+ # this into every spawned Claude Code session, so hooks running inside a session
59
+ # always have it; survives the secret-externalization refactor that moved authToken
60
+ # out of config.json into the encrypted store. Legacy fallback reads config.json
61
+ # with a string-type guard: after externalization the value is the literal
62
+ # placeholder { "secret": true } — the guard rejects it and yields empty so we
63
+ # never send the placeholder as a Bearer token (which the server rejects with 403,
64
+ # silently breaking history injection — the 2026-05-29 incident this fix is for).
65
+ AUTH_TOKEN="${INSTAR_AUTH_TOKEN:-}"
66
+ if [ -z "$AUTH_TOKEN" ] && [ -f "$CONFIG_FILE" ]; then
67
+ AUTH_TOKEN=$(python3 -c "import json; v=json.load(open('$CONFIG_FILE')).get('authToken',''); print(v if isinstance(v, str) else '')" 2>/dev/null)
68
+ fi
69
+
57
70
  # Fetch recent messages for this topic
58
- AUTH_TOKEN=$(python3 -c "import json; print(json.load(open('$CONFIG_FILE')).get('authToken',''))" 2>/dev/null)
59
71
  if [ -n "$AUTH_TOKEN" ]; then
60
72
  RECENT_MSGS=$(curl -s \
61
73
  -H "Authorization: Bearer ${AUTH_TOKEN}" \
@@ -42,9 +42,13 @@ fi
42
42
 
43
43
  PORT="${INSTAR_PORT:-4042}"
44
44
 
45
- AUTH_TOKEN=""
46
- if [ -f ".instar/config.json" ]; then
47
- AUTH_TOKEN=$(python3 -c "import json; print(json.load(open('.instar/config.json')).get('authToken',''))" 2>/dev/null)
45
+ # Auth: env first (SessionManager injects it per spawned session and it
46
+ # survives secret-externalization), legacy plaintext-config fallback with
47
+ # string-type guard so the { "secret": true } placeholder produced by
48
+ # SecretMigrator cannot leak through as a bogus Bearer.
49
+ AUTH_TOKEN="${INSTAR_AUTH_TOKEN:-}"
50
+ if [ -z "$AUTH_TOKEN" ] && [ -f ".instar/config.json" ]; then
51
+ AUTH_TOKEN=$(python3 -c "import json; v=json.load(open('.instar/config.json')).get('authToken',''); print(v if isinstance(v, str) else '')" 2>/dev/null)
48
52
  fi
49
53
 
50
54
  # Escape message for JSON
@@ -324,7 +324,11 @@ escalate_via_peer() {
324
324
  node_bin=$(resolve_node || true)
325
325
  [ -z "$node_bin" ] && continue
326
326
  local peer_meta
327
- peer_meta=$("$node_bin" -e "const c=JSON.parse(require('fs').readFileSync(process.argv[1],'utf8'));process.stdout.write((c.port||'')+'\t'+(c.authToken||''))" "$peer_dir/.instar/config.json" 2>/dev/null || true)
327
+ # The string-type guard on authToken protects against the { secret: true }
328
+ # placeholder that SecretMigrator writes after externalizing the auth token:
329
+ # `c.authToken || ''` would otherwise return the object itself (truthy)
330
+ # which serializes to "[object Object]" — never a valid Bearer.
331
+ peer_meta=$("$node_bin" -e "const c=JSON.parse(require('fs').readFileSync(process.argv[1],'utf8'));const t=typeof c.authToken==='string'?c.authToken:'';process.stdout.write((c.port||'')+'\t'+t)" "$peer_dir/.instar/config.json" 2>/dev/null || true)
328
332
  peer_port="${peer_meta%% *}"
329
333
  peer_auth="${peer_meta##* }"
330
334
  [ -z "$peer_port" ] && continue
@@ -434,11 +438,16 @@ probe_server_identity() {
434
438
  node_bin=$(resolve_node || true)
435
439
  [ -z "$node_bin" ] && return 0
436
440
 
437
- # Read port + authToken in one node invocation. The authToken is required
438
- # because /health gates the `project` field behind authentication — without
439
- # it the probe can't verify identity and would silently fail-open.
441
+ # Read port + authToken in one node invocation. The authToken is needed
442
+ # because /health gates the `project` field behind authentication; without
443
+ # it the probe can still hit /health for a basic alive check but loses the
444
+ # identity disambiguation. INSTAR_AUTH_TOKEN env is preferred when set
445
+ # (preserves cross-version compatibility). The string-type guard rejects the
446
+ # { secret: true } placeholder SecretMigrator writes after externalizing the
447
+ # auth token — `String(c.authToken || '')` would otherwise yield
448
+ # "[object Object]" and the Bearer would be invalid.
440
449
  local meta
441
- meta=$("$node_bin" -e "const c=JSON.parse(require('fs').readFileSync(process.argv[1],'utf8'));process.stdout.write(String(c.port||'')+'\t'+String(c.authToken||''))" "$config_file" 2>/dev/null || true)
450
+ meta=$("$node_bin" -e "const c=JSON.parse(require('fs').readFileSync(process.argv[1],'utf8'));const t=process.env.INSTAR_AUTH_TOKEN||(typeof c.authToken==='string'?c.authToken:'');process.stdout.write(String(c.port||'')+'\t'+t)" "$config_file" 2>/dev/null || true)
442
451
  port="${meta%% *}"
443
452
  auth_token="${meta##* }"
444
453
  # Lifeline-only agents have no port; treat as healthy from probe POV.
@@ -47,15 +47,23 @@ if (!token || !mode || mode === '--consume') {
47
47
  process.exit(2);
48
48
  }
49
49
 
50
- let authToken;
50
+ // Auth-token resolution: INSTAR_AUTH_TOKEN env first (SessionManager and
51
+ // JobScheduler inject it for every spawned context; survives the
52
+ // secret-externalization refactor that moved authToken out of config.json into
53
+ // the encrypted store), legacy plaintext-config fallback with a string-type
54
+ // guard so the { "secret": true } placeholder produced by SecretMigrator
55
+ // cannot leak through as a bogus Bearer.
56
+ let authToken = process.env.INSTAR_AUTH_TOKEN || '';
51
57
  let port;
52
58
  try {
53
59
  const config = JSON.parse(fs.readFileSync('.instar/config.json', 'utf-8'));
54
- authToken = config.authToken;
60
+ if (!authToken && typeof config.authToken === 'string') authToken = config.authToken;
55
61
  port = config.port;
56
62
  } catch (e) {
57
- process.stderr.write('cannot read .instar/config.json: ' + e.message + '\n');
58
- process.exit(2);
63
+ if (!authToken) {
64
+ process.stderr.write('cannot read .instar/config.json: ' + e.message + '\n');
65
+ process.exit(2);
66
+ }
59
67
  }
60
68
 
61
69
  // Port resolution: env > config. Mirrors the telegram-reply.sh precedence
@@ -294,15 +294,21 @@ if patch_file or artifact_type:
294
294
  # Key derivation: HMAC-SHA256(authToken, "serendipity-v1:" + sessionId)
295
295
  signing_key = os.environ.get("SERENDIPITY_SIGNING_KEY", "")
296
296
  if not signing_key:
297
- # Derive from auth token + session
298
- config_path = os.environ.get("CONFIG_FILE", "")
299
- auth_token = ""
300
- if config_path and os.path.exists(config_path):
301
- try:
302
- cfg = json.load(open(config_path))
303
- auth_token = cfg.get("authToken", "")
304
- except:
305
- pass
297
+ # Derive from auth token + session. Token: INSTAR_AUTH_TOKEN env first
298
+ # (SessionManager injects it; survives secret-externalization), legacy
299
+ # plaintext-config fallback with a string-type guard so the
300
+ # { "secret": true } placeholder produced by SecretMigrator cannot leak.
301
+ auth_token = os.environ.get("INSTAR_AUTH_TOKEN", "")
302
+ if not auth_token:
303
+ config_path = os.environ.get("CONFIG_FILE", "")
304
+ if config_path and os.path.exists(config_path):
305
+ try:
306
+ cfg = json.load(open(config_path))
307
+ v = cfg.get("authToken", "")
308
+ if isinstance(v, str):
309
+ auth_token = v
310
+ except:
311
+ pass
306
312
  session_id = finding["source"]["sessionId"]
307
313
  key_material = f"serendipity-v1:{session_id}"
308
314
  signing_key = hmac.new(
@@ -44,7 +44,14 @@ if [ -f ".instar/config.json" ]; then
44
44
  CONFIG_PORT=$(python3 -c "import json; print(json.load(open('.instar/config.json')).get('port',''))" 2>/dev/null)
45
45
  [ -n "$CONFIG_PORT" ] && PORT="$CONFIG_PORT"
46
46
  fi
47
- AUTH=$(python3 -c "import json; print(json.load(open('.instar/config.json')).get('authToken',''))" 2>/dev/null)
47
+ # Auth: env first (SessionManager injects it into spawned sessions; survives
48
+ # secret-externalization), legacy plaintext-config fallback with string-type
49
+ # guard so the { "secret": true } placeholder produced by SecretMigrator
50
+ # cannot leak as a bogus Bearer.
51
+ AUTH="${INSTAR_AUTH_TOKEN:-}"
52
+ if [ -z "$AUTH" ]; then
53
+ AUTH=$(python3 -c "import json; v=json.load(open('.instar/config.json')).get('authToken',''); print(v if isinstance(v, str) else '')" 2>/dev/null)
54
+ fi
48
55
  fi
49
56
 
50
57
  PORT="${PORT:-4042}"
@@ -75,9 +75,13 @@ if [ -z "$MSG" ]; then
75
75
  fi
76
76
 
77
77
  # Resolve config-derived values from .instar/config.json (single python3
78
- # invocation). Env > config > 4040-warn for port; config-only for authToken
79
- # and agentId.
80
- AUTH_TOKEN=""
78
+ # invocation). Env > config > 4040-warn for port. Auth: INSTAR_AUTH_TOKEN env
79
+ # first (SessionManager injects it per spawned session; survives the
80
+ # secret-externalization refactor that moved authToken out of config.json into
81
+ # the encrypted store), legacy plaintext-config fallback with a string-type
82
+ # guard so the { "secret": true } placeholder produced by SecretMigrator
83
+ # cannot leak through as a bogus Bearer.
84
+ AUTH_TOKEN="${INSTAR_AUTH_TOKEN:-}"
81
85
  AGENT_ID=""
82
86
  CONFIG_PORT=""
83
87
  if [ -f ".instar/config.json" ]; then
@@ -87,11 +91,13 @@ try:
87
91
  c = json.load(open('.instar/config.json'))
88
92
  except Exception:
89
93
  sys.exit(0)
90
- print(c.get('authToken', ''))
94
+ v = c.get('authToken', '')
95
+ print(v if isinstance(v, str) else '')
91
96
  print(c.get('projectName', ''))
92
97
  print(c.get('port', ''))
93
98
  " 2>/dev/null)
94
- AUTH_TOKEN=$(printf '%s\n' "$CONFIG_VALUES" | sed -n '1p')
99
+ CONFIG_AUTH=$(printf '%s\n' "$CONFIG_VALUES" | sed -n '1p')
100
+ [ -z "$AUTH_TOKEN" ] && AUTH_TOKEN="$CONFIG_AUTH"
95
101
  AGENT_ID=$(printf '%s\n' "$CONFIG_VALUES" | sed -n '2p')
96
102
  CONFIG_PORT=$(printf '%s\n' "$CONFIG_VALUES" | sed -n '3p')
97
103
  fi
@@ -33,10 +33,13 @@ fi
33
33
 
34
34
  PORT="${INSTAR_PORT:-4040}"
35
35
 
36
- # Read auth token from config (if present)
37
- AUTH_TOKEN=""
38
- if [ -f ".instar/config.json" ]; then
39
- AUTH_TOKEN=$(python3 -c "import json; print(json.load(open('.instar/config.json')).get('authToken',''))" 2>/dev/null)
36
+ # Read auth token: env first (SessionManager injects it per spawned session and
37
+ # it survives secret-externalization), legacy plaintext-config fallback with
38
+ # string-type guard so the { "secret": true } placeholder produced by
39
+ # SecretMigrator cannot leak through as a bogus Bearer.
40
+ AUTH_TOKEN="${INSTAR_AUTH_TOKEN:-}"
41
+ if [ -z "$AUTH_TOKEN" ] && [ -f ".instar/config.json" ]; then
42
+ AUTH_TOKEN=$(python3 -c "import json; v=json.load(open('.instar/config.json')).get('authToken',''); print(v if isinstance(v, str) else '')" 2>/dev/null)
40
43
  fi
41
44
 
42
45
  # Escape for JSON