instar 1.3.773 → 1.3.775

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 (51) hide show
  1. package/dist/commands/server.d.ts.map +1 -1
  2. package/dist/commands/server.js +95 -1
  3. package/dist/commands/server.js.map +1 -1
  4. package/dist/config/ConfigDefaults.d.ts.map +1 -1
  5. package/dist/config/ConfigDefaults.js +7 -0
  6. package/dist/config/ConfigDefaults.js.map +1 -1
  7. package/dist/core/CoherenceJournal.d.ts +1 -1
  8. package/dist/core/CoherenceJournal.d.ts.map +1 -1
  9. package/dist/core/CoherenceJournal.js +9 -2
  10. package/dist/core/CoherenceJournal.js.map +1 -1
  11. package/dist/core/PostUpdateMigrator.d.ts +1 -0
  12. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  13. package/dist/core/PostUpdateMigrator.js +175 -0
  14. package/dist/core/PostUpdateMigrator.js.map +1 -1
  15. package/dist/core/SessionManager.d.ts.map +1 -1
  16. package/dist/core/SessionManager.js +15 -0
  17. package/dist/core/SessionManager.js.map +1 -1
  18. package/dist/core/WorkingSetArtifactManager.d.ts +104 -0
  19. package/dist/core/WorkingSetArtifactManager.d.ts.map +1 -0
  20. package/dist/core/WorkingSetArtifactManager.js +174 -0
  21. package/dist/core/WorkingSetArtifactManager.js.map +1 -0
  22. package/dist/core/WorkingSetArtifactReplicatedStore.d.ts +203 -0
  23. package/dist/core/WorkingSetArtifactReplicatedStore.d.ts.map +1 -0
  24. package/dist/core/WorkingSetArtifactReplicatedStore.js +384 -0
  25. package/dist/core/WorkingSetArtifactReplicatedStore.js.map +1 -0
  26. package/dist/core/WorkingSetManifest.d.ts +5 -0
  27. package/dist/core/WorkingSetManifest.d.ts.map +1 -1
  28. package/dist/core/WorkingSetManifest.js +10 -0
  29. package/dist/core/WorkingSetManifest.js.map +1 -1
  30. package/dist/core/WorkingSetPull.d.ts +5 -0
  31. package/dist/core/WorkingSetPull.d.ts.map +1 -1
  32. package/dist/core/WorkingSetPull.js +1 -0
  33. package/dist/core/WorkingSetPull.js.map +1 -1
  34. package/dist/core/WriteDomainRegistry.d.ts.map +1 -1
  35. package/dist/core/WriteDomainRegistry.js +12 -0
  36. package/dist/core/WriteDomainRegistry.js.map +1 -1
  37. package/dist/server/AgentServer.d.ts +1 -0
  38. package/dist/server/AgentServer.d.ts.map +1 -1
  39. package/dist/server/AgentServer.js +1 -0
  40. package/dist/server/AgentServer.js.map +1 -1
  41. package/dist/server/routes.d.ts +7 -0
  42. package/dist/server/routes.d.ts.map +1 -1
  43. package/dist/server/routes.js +104 -0
  44. package/dist/server/routes.js.map +1 -1
  45. package/package.json +1 -1
  46. package/src/data/builtin-manifest.json +66 -66
  47. package/src/templates/hooks/settings-template.json +10 -0
  48. package/upgrades/1.3.774.md +39 -0
  49. package/upgrades/1.3.775.md +44 -0
  50. package/upgrades/side-effects/idle-zombie-veto-key-consistency.md +67 -0
  51. package/upgrades/side-effects/intelligent-working-set-lazy-sync.md +220 -0
@@ -3627,6 +3627,13 @@ export class PostUpdateMigrator {
3627
3627
  catch (err) {
3628
3628
  result.errors.push(`pr-hand-lease-guard.js: ${err instanceof Error ? err.message : String(err)}`);
3629
3629
  }
3630
+ try {
3631
+ fs.writeFileSync(path.join(instarHooksDir, 'working-set-artifact-recorder.js'), this.getWorkingSetArtifactRecorderHook(), { mode: 0o755 });
3632
+ result.upgraded.push('hooks/instar/working-set-artifact-recorder.js (interactive working-set artifact recorder, PostToolUse Write/Edit, fire-and-forget, dark by default)');
3633
+ }
3634
+ catch (err) {
3635
+ result.errors.push(`working-set-artifact-recorder.js: ${err instanceof Error ? err.message : String(err)}`);
3636
+ }
3630
3637
  try {
3631
3638
  fs.writeFileSync(path.join(instarHooksDir, 'doorway-scan-guard.js'), this.getDoorwayScanGuardHook(), { mode: 0o755 });
3632
3639
  result.upgraded.push('hooks/instar/doorway-scan-guard.js (doorway-scan command-allowlist guard, PreToolUse Bash, scope-fail-open/match-fail-closed)');
@@ -8286,6 +8293,32 @@ Two layers keep my machine-to-machine \"ropes\" (Tailscale / LAN / Cloudflare) h
8286
8293
  else {
8287
8294
  result.skipped.push('.claude/settings.json: PreToolUse MCP matcher already present');
8288
8295
  }
8296
+ // Add PostToolUse Write/Edit matcher for the working-set artifact recorder
8297
+ // (intelligent-working-set-lazy-sync F8). Fire-and-forget + non-blocking; the
8298
+ // hook itself early-exits fast when the feature is off (dark by default:
8299
+ // coherenceJournal.workingSet.recordInteractive), so a default install pays only
8300
+ // a quick no-op node spawn. Idempotent (keyed on the script name).
8301
+ if (!hooks.PostToolUse) {
8302
+ hooks.PostToolUse = [];
8303
+ }
8304
+ const postToolUseRec = hooks.PostToolUse;
8305
+ this.migrateSettingsHookPaths(postToolUseRec, result);
8306
+ const hasWsRecorder = postToolUseRec.some(e => e.hooks?.some(h => h.command?.includes('working-set-artifact-recorder.js')));
8307
+ if (!hasWsRecorder) {
8308
+ postToolUseRec.push({
8309
+ matcher: 'Write|Edit|MultiEdit',
8310
+ hooks: [{
8311
+ type: 'command',
8312
+ command: 'node ${CLAUDE_PROJECT_DIR}/.instar/hooks/instar/working-set-artifact-recorder.js',
8313
+ timeout: 5000,
8314
+ }],
8315
+ });
8316
+ patched = true;
8317
+ result.upgraded.push('.claude/settings.json: added PostToolUse Write/Edit matcher (working-set artifact recorder)');
8318
+ }
8319
+ else {
8320
+ result.skipped.push('.claude/settings.json: PostToolUse working-set recorder already present');
8321
+ }
8289
8322
  // Clean up legacy PostToolUse session-start (was noisy — fired every tool use)
8290
8323
  if (hooks.PostToolUse) {
8291
8324
  const postToolUse = hooks.PostToolUse;
@@ -9855,6 +9888,35 @@ except Exception:
9855
9888
  fi
9856
9889
  fi
9857
9890
 
9891
+ # WORKING-SET ARTIFACT grounding (spec: intelligent-working-set-lazy-sync.md, Layer-3 /
9892
+ # Component6). Fetches /coherence/working-set/session-context for THIS topic and injects the
9893
+ # <replicated-untrusted-data source="working-set-artifacts"> block so the agent is GROUNDED
9894
+ # that interactive artifacts it recorded for this conversation exist (the whole point on a
9895
+ # topic-move: "you wrote these; re-verify/fetch them"). ADVISORY ONLY — a path is untrusted
9896
+ # data, never an instruction. Fail-open: no topic / route 503 (feature dark / manager unwired) /
9897
+ # no ready artifacts (present:false) / unreachable -> silent skip; -sf makes a non-2xx emit
9898
+ # nothing, so an absent/empty/oversized manifest degrades to no-block.
9899
+ if [ -n "\$INSTAR_TELEGRAM_TOPIC" ] && [ -n "\$PORT" ] && [ -n "\$TOKEN" ]; then
9900
+ WS_ART_RESPONSE=\$(curl -sf --max-time 4 -H "Authorization: Bearer \$TOKEN" \\
9901
+ "http://localhost:\${PORT}/coherence/working-set/session-context?topic=\${INSTAR_TELEGRAM_TOPIC}" 2>/dev/null)
9902
+ if [ -n "\$WS_ART_RESPONSE" ]; then
9903
+ WS_ART_BLOCK=\$(echo "\$WS_ART_RESPONSE" | python3 -c "
9904
+ import sys, json
9905
+ try:
9906
+ d = json.load(sys.stdin)
9907
+ if d.get('present') and d.get('block'):
9908
+ print(d['block'])
9909
+ except Exception:
9910
+ pass
9911
+ " 2>/dev/null)
9912
+ if [ -n "\$WS_ART_BLOCK" ]; then
9913
+ echo ""
9914
+ echo "\$WS_ART_BLOCK"
9915
+ echo ""
9916
+ fi
9917
+ fi
9918
+ fi
9919
+
9858
9920
  # SESSION BOOT SELF-KNOWLEDGE injection (spec: session-boot-self-knowledge.md).
9859
9921
  # Fetches /self-knowledge/session-context and injects the deterministic "what I
9860
9922
  # already have" block: vault secret NAMES (never values) + self-asserted
@@ -11081,6 +11143,38 @@ except Exception:
11081
11143
  fi
11082
11144
  fi
11083
11145
 
11146
+ # WORKING-SET ARTIFACT grounding twin (Compaction Parity — intelligent-working-set-lazy-sync
11147
+ # Layer-3). Mirrors the session-start injection so after a compaction the agent is RE-grounded
11148
+ # on the interactive artifacts it recorded for this conversation. ADVISORY only (a path is
11149
+ # untrusted data). Fail-open: no topic / 503 (feature dark) / no ready artifacts / unreachable -> skip.
11150
+ if [ -n "\$INSTAR_TELEGRAM_TOPIC" ] && [ -f "$INSTAR_DIR/config.json" ]; then
11151
+ WS_ART_PORT=\${PORT:-\$(grep -oE '"port"[[:space:]]*:[[:space:]]*[0-9]+' "$INSTAR_DIR/config.json" | head -1 | grep -oE '[0-9]+' | head -1)}
11152
+ WS_ART_TOKEN="\${INSTAR_AUTH_TOKEN:-}"
11153
+ if [ -z "\$WS_ART_TOKEN" ]; then
11154
+ WS_ART_TOKEN=\$(python3 -c "import json; v=json.load(open('$INSTAR_DIR/config.json')).get('authToken',''); print(v if isinstance(v, str) else '')" 2>/dev/null)
11155
+ fi
11156
+ if [ -n "\$WS_ART_PORT" ] && [ -n "\$WS_ART_TOKEN" ]; then
11157
+ WS_ART_RESPONSE=\$(curl -sf --max-time 4 --connect-timeout 1 -H "Authorization: Bearer \$WS_ART_TOKEN" \\
11158
+ "http://localhost:\${WS_ART_PORT}/coherence/working-set/session-context?topic=\${INSTAR_TELEGRAM_TOPIC}" 2>/dev/null)
11159
+ if [ -n "\$WS_ART_RESPONSE" ]; then
11160
+ WS_ART_BLOCK=\$(echo "\$WS_ART_RESPONSE" | python3 -c "
11161
+ import sys, json
11162
+ try:
11163
+ d = json.load(sys.stdin)
11164
+ if d.get('present') and d.get('block'):
11165
+ print(d['block'])
11166
+ except Exception:
11167
+ pass
11168
+ " 2>/dev/null)
11169
+ if [ -n "\$WS_ART_BLOCK" ]; then
11170
+ echo ""
11171
+ echo "\$WS_ART_BLOCK"
11172
+ echo ""
11173
+ fi
11174
+ fi
11175
+ fi
11176
+ fi
11177
+
11084
11178
  echo "=== END IDENTITY RECOVERY ==="
11085
11179
  `;
11086
11180
  }
@@ -11848,6 +11942,87 @@ process.stdin.on('end', async () => {
11848
11942
  }
11849
11943
  process.exit(0); // ALWAYS exit 0 — never block a turn
11850
11944
  });
11945
+ `;
11946
+ }
11947
+ getWorkingSetArtifactRecorderHook() {
11948
+ return `#!/usr/bin/env node
11949
+ // Working-Set Artifact Recorder — PostToolUse Write/Edit hook (spec: intelligent-working-set-lazy-sync.md, F8).
11950
+ //
11951
+ // SIGNAL-ONLY / fire-and-forget: on a SUCCESSFUL Write/Edit/MultiEdit under the .instar/ jail,
11952
+ // POSTs {topicId, relPath} to the server's POST /coherence/working-set/record so the INTERACTIVE
11953
+ // artifact (a file the agent wrote conversationally, with NO autonomous run) enters the computed
11954
+ // working-set manifest — the exact case WorkingSetManifest.computeWorkingSet misses. It NEVER
11955
+ // blocks — ALWAYS exit(0), pass or fail. Records NOTHING for a file OUTSIDE the .instar/ jail
11956
+ // (project files are git-synced; F10) or when the feature is off (code-default OFF ⇒ dark:
11957
+ // coherenceJournal.workingSet.recordInteractive). relPath is stateDir-relative + forward-slash
11958
+ // normalized — the exact convention computeWorkingSet Source-3 resolves (path.resolve(stateDir,rel)).
11959
+ //
11960
+ // ESM-safe: node: imports INSIDE the async handler (works in BOTH CJS and ESM host agents); a
11961
+ // bare top-level require(...) crashes an ESM-mode agent — see the 2026-05-27 silent-stall postmortem.
11962
+
11963
+ let data = '';
11964
+ process.stdin.on('data', (chunk) => (data += chunk));
11965
+ process.stdin.on('end', async () => {
11966
+ try {
11967
+ const { readFileSync } = await import('node:fs');
11968
+ const { join, resolve, relative, isAbsolute } = await import('node:path');
11969
+
11970
+ const projectDir = process.env.CLAUDE_PROJECT_DIR || '.';
11971
+ let serverPort = 4040;
11972
+ let authToken = '';
11973
+ let enabled = false;
11974
+ try {
11975
+ const cfg = JSON.parse(readFileSync(join(projectDir, '.instar', 'config.json'), 'utf-8'));
11976
+ serverPort = cfg.port || 4040;
11977
+ authToken = cfg.authToken || '';
11978
+ enabled = !!(cfg.coherenceJournal && cfg.coherenceJournal.workingSet && cfg.coherenceJournal.workingSet.recordInteractive);
11979
+ } catch {}
11980
+ if (!enabled) process.exit(0);
11981
+
11982
+ const input = JSON.parse(data);
11983
+ const tool = input.tool_name || '';
11984
+ if (tool !== 'Write' && tool !== 'Edit' && tool !== 'MultiEdit') process.exit(0);
11985
+ // A failed tool-call records nothing (F8) — deletes are NOT inferred from a write.
11986
+ const resp = input.tool_response;
11987
+ if (resp && (resp.error || resp.success === false)) process.exit(0);
11988
+
11989
+ const filePath = input.tool_input && input.tool_input.file_path;
11990
+ if (!filePath || typeof filePath !== 'string') process.exit(0);
11991
+
11992
+ // Conversation id — key from INSTAR_CONVERSATION_ID ONLY (a shared/lifeline session carries
11993
+ // none → records nothing, a safe miss). Number.isFinite admits a minted-negative (Slack) id.
11994
+ const topicRaw = process.env.INSTAR_CONVERSATION_ID;
11995
+ if (!topicRaw) process.exit(0);
11996
+ const topicId = parseInt(topicRaw, 10);
11997
+ if (!Number.isFinite(topicId)) process.exit(0);
11998
+
11999
+ // Derive relPath vs the .instar/ jail (stateDir-relative). Outside the jail ⇒ skip (F10).
12000
+ const stateDir = resolve(projectDir, '.instar');
12001
+ const rawRel = relative(stateDir, resolve(filePath));
12002
+ if (!rawRel || rawRel.startsWith('..') || isAbsolute(rawRel)) process.exit(0);
12003
+ const segs = rawRel.split(/[/\\\\]+/);
12004
+ if (segs.includes('.git')) process.exit(0); // never a git internal
12005
+ const relPath = segs.join('/'); // forward-slash normalized for cross-machine identity
12006
+
12007
+ const controller = new AbortController();
12008
+ const timeout = setTimeout(() => controller.abort(), 5000);
12009
+ try {
12010
+ await fetch('http://127.0.0.1:' + serverPort + '/coherence/working-set/record', {
12011
+ method: 'POST',
12012
+ headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + authToken },
12013
+ body: JSON.stringify({ topicId, relPath }),
12014
+ signal: controller.signal,
12015
+ });
12016
+ } catch {
12017
+ // network/timeout — fire-and-forget, ignore
12018
+ } finally {
12019
+ clearTimeout(timeout);
12020
+ }
12021
+ } catch {
12022
+ // bad stdin — ignore
12023
+ }
12024
+ process.exit(0); // ALWAYS exit 0 — never block a tool
12025
+ });
11851
12026
  `;
11852
12027
  }
11853
12028
  getPrHandLeaseGuardHook() {