instar 1.3.774 → 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.
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +95 -1
- package/dist/commands/server.js.map +1 -1
- package/dist/config/ConfigDefaults.d.ts.map +1 -1
- package/dist/config/ConfigDefaults.js +7 -0
- package/dist/config/ConfigDefaults.js.map +1 -1
- package/dist/core/CoherenceJournal.d.ts +1 -1
- package/dist/core/CoherenceJournal.d.ts.map +1 -1
- package/dist/core/CoherenceJournal.js +9 -2
- package/dist/core/CoherenceJournal.js.map +1 -1
- package/dist/core/PostUpdateMigrator.d.ts +1 -0
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +175 -0
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/WorkingSetArtifactManager.d.ts +104 -0
- package/dist/core/WorkingSetArtifactManager.d.ts.map +1 -0
- package/dist/core/WorkingSetArtifactManager.js +174 -0
- package/dist/core/WorkingSetArtifactManager.js.map +1 -0
- package/dist/core/WorkingSetArtifactReplicatedStore.d.ts +203 -0
- package/dist/core/WorkingSetArtifactReplicatedStore.d.ts.map +1 -0
- package/dist/core/WorkingSetArtifactReplicatedStore.js +384 -0
- package/dist/core/WorkingSetArtifactReplicatedStore.js.map +1 -0
- package/dist/core/WorkingSetManifest.d.ts +5 -0
- package/dist/core/WorkingSetManifest.d.ts.map +1 -1
- package/dist/core/WorkingSetManifest.js +10 -0
- package/dist/core/WorkingSetManifest.js.map +1 -1
- package/dist/core/WorkingSetPull.d.ts +5 -0
- package/dist/core/WorkingSetPull.d.ts.map +1 -1
- package/dist/core/WorkingSetPull.js +1 -0
- package/dist/core/WorkingSetPull.js.map +1 -1
- package/dist/core/WriteDomainRegistry.d.ts.map +1 -1
- package/dist/core/WriteDomainRegistry.js +12 -0
- package/dist/core/WriteDomainRegistry.js.map +1 -1
- package/dist/server/AgentServer.d.ts +1 -0
- package/dist/server/AgentServer.d.ts.map +1 -1
- package/dist/server/AgentServer.js +1 -0
- package/dist/server/AgentServer.js.map +1 -1
- package/dist/server/routes.d.ts +7 -0
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +104 -0
- package/dist/server/routes.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +65 -65
- package/src/templates/hooks/settings-template.json +10 -0
- package/upgrades/1.3.775.md +44 -0
- 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() {
|