wayfind 2.0.79 → 2.0.81
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/bin/connectors/llm.js
CHANGED
|
@@ -144,7 +144,7 @@ async function callAnthropic(config, systemPrompt, userContent) {
|
|
|
144
144
|
const payload = JSON.stringify({
|
|
145
145
|
model: config.model,
|
|
146
146
|
max_tokens: config.max_tokens || DEFAULT_MAX_TOKENS,
|
|
147
|
-
system: systemPrompt,
|
|
147
|
+
system: [{ type: 'text', text: systemPrompt, cache_control: { type: 'ephemeral' } }],
|
|
148
148
|
messages: [{ role: 'user', content: userContent }],
|
|
149
149
|
});
|
|
150
150
|
|
|
@@ -327,13 +327,35 @@ async function callWithTools(config, systemPrompt, userContent, tools, handleToo
|
|
|
327
327
|
const MAX_ITERATIONS = 10;
|
|
328
328
|
let messages = [{ role: 'user', content: userContent }];
|
|
329
329
|
|
|
330
|
+
// Breakpoint on system prompt and last tool definition: the system+tools prefix
|
|
331
|
+
// is stable across all iterations, so these hits are what pay for the loop.
|
|
332
|
+
const cachedSystem = [{ type: 'text', text: systemPrompt, cache_control: { type: 'ephemeral' } }];
|
|
333
|
+
const cachedTools = tools.map((t, idx) =>
|
|
334
|
+
idx === tools.length - 1 ? { ...t, cache_control: { type: 'ephemeral' } } : t
|
|
335
|
+
);
|
|
336
|
+
|
|
330
337
|
for (let i = 0; i < MAX_ITERATIONS; i++) {
|
|
338
|
+
// Breakpoint on the last message so the growing transcript also caches
|
|
339
|
+
// across iterations after the first.
|
|
340
|
+
const cachedMessages = messages.map((m, idx) => {
|
|
341
|
+
if (idx !== messages.length - 1) return m;
|
|
342
|
+
if (typeof m.content === 'string') {
|
|
343
|
+
return { ...m, content: [{ type: 'text', text: m.content, cache_control: { type: 'ephemeral' } }] };
|
|
344
|
+
}
|
|
345
|
+
if (Array.isArray(m.content) && m.content.length > 0) {
|
|
346
|
+
const last = m.content[m.content.length - 1];
|
|
347
|
+
const patched = { ...last, cache_control: { type: 'ephemeral' } };
|
|
348
|
+
return { ...m, content: [...m.content.slice(0, -1), patched] };
|
|
349
|
+
}
|
|
350
|
+
return m;
|
|
351
|
+
});
|
|
352
|
+
|
|
331
353
|
const payload = JSON.stringify({
|
|
332
354
|
model: config.model,
|
|
333
355
|
max_tokens: config.max_tokens || DEFAULT_MAX_TOKENS,
|
|
334
|
-
system:
|
|
335
|
-
messages,
|
|
336
|
-
tools,
|
|
356
|
+
system: cachedSystem,
|
|
357
|
+
messages: cachedMessages,
|
|
358
|
+
tools: cachedTools,
|
|
337
359
|
});
|
|
338
360
|
|
|
339
361
|
const res = await httpPost(ANTHROPIC_API_URL, headers, payload);
|
package/bin/team-context.js
CHANGED
|
@@ -2942,6 +2942,40 @@ function ensureStateWritePermissions() {
|
|
|
2942
2942
|
return added;
|
|
2943
2943
|
}
|
|
2944
2944
|
|
|
2945
|
+
/**
|
|
2946
|
+
* Ensure ~/.claude/settings.json has the PermissionRequest hook that auto-approves
|
|
2947
|
+
* Write calls to Wayfind state files. The permissions.allow list doesn't suppress
|
|
2948
|
+
* Claude Code's sensitive-file prompt for ~/.claude/ paths; a PermissionRequest hook
|
|
2949
|
+
* returning {"decision":"allow"} does.
|
|
2950
|
+
* Idempotent — no-ops if the hook command is already registered.
|
|
2951
|
+
* @returns {boolean} true if the hook was added
|
|
2952
|
+
*/
|
|
2953
|
+
function ensurePermissionRequestHook() {
|
|
2954
|
+
if (!HOME) return false;
|
|
2955
|
+
const settingsPath = path.join(HOME, '.claude', 'settings.json');
|
|
2956
|
+
const hookCommand = 'bash ~/.claude/hooks/allow-wayfind-writes.sh';
|
|
2957
|
+
|
|
2958
|
+
let settings = {};
|
|
2959
|
+
if (fs.existsSync(settingsPath)) {
|
|
2960
|
+
try { settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8')); } catch { return false; }
|
|
2961
|
+
}
|
|
2962
|
+
|
|
2963
|
+
if (!settings.hooks) settings.hooks = {};
|
|
2964
|
+
if (!Array.isArray(settings.hooks.PermissionRequest)) settings.hooks.PermissionRequest = [];
|
|
2965
|
+
|
|
2966
|
+
const alreadyRegistered = settings.hooks.PermissionRequest.some(entry =>
|
|
2967
|
+
Array.isArray(entry.hooks) && entry.hooks.some(h => h.command === hookCommand)
|
|
2968
|
+
);
|
|
2969
|
+
if (alreadyRegistered) return false;
|
|
2970
|
+
|
|
2971
|
+
settings.hooks.PermissionRequest.push({
|
|
2972
|
+
matcher: 'Write',
|
|
2973
|
+
hooks: [{ type: 'command', command: hookCommand, timeout: 5000 }],
|
|
2974
|
+
});
|
|
2975
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
2976
|
+
return true;
|
|
2977
|
+
}
|
|
2978
|
+
|
|
2945
2979
|
/**
|
|
2946
2980
|
* Re-sync hooks and commands from the installed Wayfind package to ~/.claude/.
|
|
2947
2981
|
* Copies hook scripts and slash-command files, overwriting stale copies.
|
|
@@ -3026,6 +3060,15 @@ function runUpdate() {
|
|
|
3026
3060
|
for (const p of addedPerms) console.log(` ✓ Allowlisted: ${p}`);
|
|
3027
3061
|
console.log(' (Prevents plan-mode prompts on journal and state-file writes)');
|
|
3028
3062
|
}
|
|
3063
|
+
|
|
3064
|
+
// Register PermissionRequest hook to suppress sensitive-file prompts for
|
|
3065
|
+
// ~/.claude/ paths, which the allowlist alone cannot suppress.
|
|
3066
|
+
const hookAdded = ensurePermissionRequestHook();
|
|
3067
|
+
if (hookAdded) {
|
|
3068
|
+
console.log('\n── PermissionRequest hook ──');
|
|
3069
|
+
console.log(' ✓ Registered allow-wayfind-writes.sh');
|
|
3070
|
+
console.log(' (Suppresses sensitive-file prompts for ~/.claude/ paths)');
|
|
3071
|
+
}
|
|
3029
3072
|
}
|
|
3030
3073
|
|
|
3031
3074
|
// ── Migrate to plugin ───────────────────────────────────────────────────────
|
package/package.json
CHANGED
|
@@ -146,10 +146,12 @@ Read the repo's `CLAUDE.md`. If it does NOT already contain "Session State Proto
|
|
|
146
146
|
|
|
147
147
|
If `CLAUDE.md` doesn't exist, create a minimal one with the repo name as a heading and the block above.
|
|
148
148
|
|
|
149
|
-
## Step 4.5: Patch Write Permissions in ~/.claude/settings.json
|
|
149
|
+
## Step 4.5: Patch Write Permissions and PermissionRequest Hook in ~/.claude/settings.json
|
|
150
150
|
|
|
151
151
|
Read `~/.claude/settings.json` (create it as `{}` if missing).
|
|
152
152
|
|
|
153
|
+
### Part A: Write allowlist
|
|
154
|
+
|
|
153
155
|
Ensure the following entries are present in `permissions.allow`. Add any that are missing — do NOT remove existing entries.
|
|
154
156
|
|
|
155
157
|
Each path needs two forms: absolute (for tools that resolve paths before the permission check) and literal (tilde or relative, for tools that pass the path as-is). Both are required — Claude Code matches against the literal `file_path` argument, not the resolved path.
|
|
@@ -169,9 +171,44 @@ Write(.claude/personal-state.md)
|
|
|
169
171
|
|
|
170
172
|
Where `<HOME>` is the user's actual home directory (e.g. `/home/greg` or `/Users/greg`).
|
|
171
173
|
|
|
172
|
-
|
|
174
|
+
### Part B: PermissionRequest hook
|
|
175
|
+
|
|
176
|
+
Claude Code has a hardcoded sensitive-file check for `~/.claude/` paths that fires **independently** of `permissions.allow` — the allowlist alone cannot suppress it. A `PermissionRequest` hook is required.
|
|
177
|
+
|
|
178
|
+
First verify `~/.claude/hooks/allow-wayfind-writes.sh` exists. If missing, create it:
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
#!/usr/bin/env bash
|
|
182
|
+
input=$(cat)
|
|
183
|
+
file_path=$(echo "$input" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
|
|
184
|
+
[ -z "$file_path" ] && exit 0
|
|
185
|
+
expanded="${file_path/#\~/$HOME}"
|
|
186
|
+
if [[ "$expanded" == "$HOME/.claude/memory/"* ]] || \
|
|
187
|
+
[[ "$expanded" == "$HOME/.claude/global-state.md" ]] || \
|
|
188
|
+
[[ "$expanded" == "$HOME/.claude/state.md" ]] || \
|
|
189
|
+
[[ "$expanded" == *"/.claude/team-state.md" ]] || \
|
|
190
|
+
[[ "$expanded" == *"/.claude/personal-state.md" ]]; then
|
|
191
|
+
echo '{"decision": "allow"}'
|
|
192
|
+
exit 0
|
|
193
|
+
fi
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Make it executable: `chmod +x ~/.claude/hooks/allow-wayfind-writes.sh`
|
|
197
|
+
|
|
198
|
+
Then ensure this entry is present in `hooks.PermissionRequest` in `~/.claude/settings.json`:
|
|
199
|
+
|
|
200
|
+
```json
|
|
201
|
+
{
|
|
202
|
+
"matcher": "Write",
|
|
203
|
+
"hooks": [{ "type": "command", "command": "bash ~/.claude/hooks/allow-wayfind-writes.sh", "timeout": 5000 }]
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Add it if missing — do NOT remove existing entries in the array.
|
|
208
|
+
|
|
209
|
+
**Why:** Without Part A, plan-mode users get prompted on every journal write. Without Part B, even bypass-mode users see "sensitive file" prompts for `~/.claude/` paths — which the allowlist cannot suppress.
|
|
173
210
|
|
|
174
|
-
Report: "Write permissions patched — N entries added" (or "already present" if nothing changed).
|
|
211
|
+
Report: "Write permissions patched — N entries added; PermissionRequest hook registered" (or "already present" for each if nothing changed).
|
|
175
212
|
|
|
176
213
|
## Step 5: Register State Files in Global Index
|
|
177
214
|
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Auto-approve Write permission requests for known-safe Wayfind state files.
|
|
3
|
+
# Runs as a PermissionRequest hook so it fires before Claude Code's sensitive-file
|
|
4
|
+
# prompt — the allowlist in settings.json doesn't suppress that prompt.
|
|
5
|
+
#
|
|
6
|
+
# Returns {"decision": "allow"} for matched paths, exits 0 with no output otherwise
|
|
7
|
+
# (Claude Code falls through to its normal permission handling).
|
|
8
|
+
|
|
9
|
+
input=$(cat)
|
|
10
|
+
file_path=$(echo "$input" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
|
|
11
|
+
|
|
12
|
+
[ -z "$file_path" ] && exit 0
|
|
13
|
+
|
|
14
|
+
# Expand leading tilde to $HOME
|
|
15
|
+
expanded="${file_path/#\~/$HOME}"
|
|
16
|
+
|
|
17
|
+
if [[ "$expanded" == "$HOME/.claude/memory/"* ]] || \
|
|
18
|
+
[[ "$expanded" == "$HOME/.claude/global-state.md" ]] || \
|
|
19
|
+
[[ "$expanded" == "$HOME/.claude/state.md" ]] || \
|
|
20
|
+
[[ "$expanded" == *"/.claude/team-state.md" ]] || \
|
|
21
|
+
[[ "$expanded" == *"/.claude/personal-state.md" ]]; then
|
|
22
|
+
echo '{"decision": "allow"}'
|
|
23
|
+
exit 0
|
|
24
|
+
fi
|