mixdog 0.9.0 → 0.9.1
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/package.json +3 -3
- package/scripts/session-ingest-smoke.mjs +2 -2
- package/src/headless-role.mjs +1 -1
- package/src/lib/mixdog-debug.cjs +0 -22
- package/src/lib/plugin-paths.cjs +1 -7
- package/src/lib/rules-builder.cjs +2 -2
- package/src/mixdog-session-runtime.mjs +0 -1
- package/src/repl.mjs +0 -2
- package/src/runtime/agent/orchestrator/internal-roles.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +27 -49
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +5 -20
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +8 -27
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +52 -182
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +8 -30
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +258 -0
- package/src/runtime/agent/orchestrator/tools/bash-session.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +0 -8
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +1 -44
- package/src/runtime/channels/index.mjs +0 -30
- package/src/runtime/channels/lib/cli-worker-host.mjs +1 -8
- package/src/runtime/channels/lib/config.mjs +0 -1
- package/src/runtime/channels/lib/drop-trace.mjs +1 -1
- package/src/runtime/channels/lib/executor.mjs +0 -3
- package/src/runtime/channels/lib/memory-client.mjs +0 -38
- package/src/runtime/channels/lib/output-forwarder.mjs +1 -8
- package/src/runtime/channels/lib/runtime-paths.mjs +0 -6
- package/src/runtime/channels/lib/session-discovery.mjs +0 -4
- package/src/runtime/channels/lib/tool-format.mjs +0 -1
- package/src/runtime/channels/lib/transcript-discovery.mjs +1 -10
- package/src/runtime/lib/keychain-cjs.cjs +0 -1
- package/src/runtime/memory/data/runtime-manifest.json +6 -7
- package/src/runtime/memory/index.mjs +3 -24
- package/src/runtime/memory/lib/llm-worker-host.mjs +0 -4
- package/src/runtime/memory/lib/memory-ops-policy.mjs +0 -1
- package/src/runtime/memory/lib/runtime-fetcher.mjs +43 -18
- package/src/runtime/memory/lib/session-ingest.mjs +9 -7
- package/src/runtime/search/index.mjs +2 -7
- package/src/runtime/search/lib/config.mjs +0 -4
- package/src/runtime/search/lib/state.mjs +1 -15
- package/src/runtime/search/lib/web-tools.mjs +0 -1
- package/src/runtime/shared/child-spawn-gate.mjs +0 -6
- package/src/standalone/seeds.mjs +1 -11
- package/src/tui/App.jsx +35 -12
- package/src/tui/components/PromptInput.jsx +63 -2
- package/src/tui/components/ToolExecution.jsx +7 -2
- package/src/tui/components/tool-output-format.mjs +156 -22
- package/src/tui/components/tool-output-format.test.mjs +93 -1
- package/src/tui/dist/index.mjs +473 -116
- package/src/tui/markdown/format-token.mjs +267 -108
- package/src/tui/markdown/format-token.test.mjs +105 -9
- package/src/tui/theme.mjs +10 -0
- package/src/vendor/statusline/bin/statusline-lib.mjs +0 -623
- package/vendor/ink/build/ink.js +54 -8
- package/src/hooks/lib/permission-rules.cjs +0 -170
- package/src/hooks/lib/settings-loader.cjs +0 -112
- package/src/lib/hook-pipe-path.cjs +0 -10
- package/src/runtime/channels/lib/hook-pipe-server.mjs +0 -671
package/vendor/ink/build/ink.js
CHANGED
|
@@ -181,6 +181,11 @@ export default class Ink {
|
|
|
181
181
|
// cells ({ x1, y1, x2, y2 } inclusive, normalized) or null. The App sets it
|
|
182
182
|
// via setSelection(); onRender forwards it to the renderer for highlighting.
|
|
183
183
|
selectionRect = null;
|
|
184
|
+
// [mixdog fork] Coalesce drag-selection repaints when a frame is pending.
|
|
185
|
+
isRendering = false;
|
|
186
|
+
selectionRepaintQueued = false;
|
|
187
|
+
selectionRepaintFlushPending = false;
|
|
188
|
+
selectionRepaintEpoch = 0;
|
|
184
189
|
// [mixdog fork] text under the current selection rect, refreshed every render
|
|
185
190
|
// from the output grid. Read back via getSelectionText() on drag-release.
|
|
186
191
|
selectedText = null;
|
|
@@ -318,9 +323,9 @@ export default class Ink {
|
|
|
318
323
|
this.log.setCursorPosition(position);
|
|
319
324
|
};
|
|
320
325
|
// [mixdog fork] Update the mouse drag-selection rectangle and repaint so the
|
|
321
|
-
// inverse highlight tracks the drag
|
|
326
|
+
// inverse highlight tracks the drag. Called by the App's mouse handler.
|
|
322
327
|
// A no-op-equal update is skipped to avoid redundant frames during motion.
|
|
323
|
-
setSelection = (rect) => {
|
|
328
|
+
setSelection = (rect, options = {}) => {
|
|
324
329
|
const a = this.selectionRect;
|
|
325
330
|
const same = a === rect ||
|
|
326
331
|
(a && rect &&
|
|
@@ -333,16 +338,47 @@ export default class Ink {
|
|
|
333
338
|
a.clipY2 === rect.clipY2 &&
|
|
334
339
|
a.captureText === rect.captureText);
|
|
335
340
|
if (same) {
|
|
336
|
-
|
|
341
|
+
if (!options.immediate) {
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
else {
|
|
346
|
+
this.selectionRect = rect ?? null;
|
|
337
347
|
}
|
|
338
|
-
this.selectionRect = rect ?? null;
|
|
339
348
|
if (!this.isUnmounted) {
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
349
|
+
if (options.immediate) {
|
|
350
|
+
this.selectionRepaintEpoch++;
|
|
351
|
+
this.selectionRepaintFlushPending = false;
|
|
352
|
+
this.rootNode.onImmediateRender();
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
this.scheduleSelectionRepaint();
|
|
344
356
|
}
|
|
345
357
|
};
|
|
358
|
+
scheduleSelectionRepaint = () => {
|
|
359
|
+
if (this.hasPendingThrottledRender) {
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
if (this.isRendering) {
|
|
363
|
+
this.selectionRepaintQueued = true;
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
if (this.selectionRepaintFlushPending) {
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
this.selectionRepaintFlushPending = true;
|
|
370
|
+
const epoch = ++this.selectionRepaintEpoch;
|
|
371
|
+
queueMicrotask(() => {
|
|
372
|
+
this.selectionRepaintFlushPending = false;
|
|
373
|
+
if (epoch !== this.selectionRepaintEpoch || this.isUnmounted) {
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
if (this.hasPendingThrottledRender) {
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
this.rootNode.onImmediateRender();
|
|
380
|
+
});
|
|
381
|
+
};
|
|
346
382
|
// [mixdog fork] Given a 0-based cell (x, y), return the inclusive rect of the
|
|
347
383
|
// word (maximal run of non-whitespace cells) on that single row, or null if
|
|
348
384
|
// the cell is whitespace/empty or out of range. Reuses the cached cell-value
|
|
@@ -401,6 +437,8 @@ export default class Ink {
|
|
|
401
437
|
this.nextRenderCommit.resolve();
|
|
402
438
|
this.nextRenderCommit = undefined;
|
|
403
439
|
}
|
|
440
|
+
this.isRendering = true;
|
|
441
|
+
try {
|
|
404
442
|
const startTime = performance.now();
|
|
405
443
|
const { output, outputHeight, staticOutput, cursor, selectedText, plainRows } = render(this.rootNode, this.isScreenReaderEnabled, this.selectionRect);
|
|
406
444
|
// [mixdog fork] Cache the text under the current selection rect so the App
|
|
@@ -488,6 +526,14 @@ export default class Ink {
|
|
|
488
526
|
this.fullStaticOutput += staticOutput;
|
|
489
527
|
}
|
|
490
528
|
this.renderInteractiveFrame(output, outputHeight, hasStaticOutput ? staticOutput : '');
|
|
529
|
+
}
|
|
530
|
+
finally {
|
|
531
|
+
this.isRendering = false;
|
|
532
|
+
if (this.selectionRepaintQueued) {
|
|
533
|
+
this.selectionRepaintQueued = false;
|
|
534
|
+
this.scheduleSelectionRepaint();
|
|
535
|
+
}
|
|
536
|
+
}
|
|
491
537
|
};
|
|
492
538
|
render(node) {
|
|
493
539
|
const tree = (React.createElement(AccessibilityContext.Provider, { value: { isScreenReaderEnabled: this.isScreenReaderEnabled } },
|
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
/**
|
|
3
|
-
* permission-rules.cjs
|
|
4
|
-
* Pattern matcher and priority evaluator. Priority order and Tool(content)
|
|
5
|
-
* parsing mirror Claude Code native logic; specifier matching uses a local
|
|
6
|
-
* path/command glob (native delegates per-tool content matching to each tool).
|
|
7
|
-
*
|
|
8
|
-
* Priority (highest → lowest): deny > ask > allow > mode default
|
|
9
|
-
*
|
|
10
|
-
* Pattern forms:
|
|
11
|
-
* mcp__* → toolName starts with "mcp__"
|
|
12
|
-
* ToolName → exact toolName match (no parens)
|
|
13
|
-
* ToolName(*) → toolName matches AND specifier glob matches any
|
|
14
|
-
* ToolName(specifier) → toolName matches AND specifier glob matches
|
|
15
|
-
* toolInput.path / .command / .file
|
|
16
|
-
* Glob chars: * (any chars within segment), ** (any segments)
|
|
17
|
-
*
|
|
18
|
-
* No external deps; no eval/Function.
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
// Read-only tool set (mixdog tool names).
|
|
22
|
-
const READ_ONLY_TOOLS = new Set([
|
|
23
|
-
'read', 'list', 'glob', 'grep', 'find_symbol', 'find_references', 'find_callers', 'explore',
|
|
24
|
-
'recall', 'search', 'fetch', 'web_fetch',
|
|
25
|
-
'schedule_status', 'list_models', 'job_wait', 'download_attachment',
|
|
26
|
-
]);
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Find the index of the first/last UNescaped occurrence of a char.
|
|
30
|
-
* A char is escaped when preceded by an odd number of backslashes.
|
|
31
|
-
* Mirrors native permissionRuleParser.findFirst/LastUnescapedChar.
|
|
32
|
-
*/
|
|
33
|
-
function findFirstUnescapedChar(str, char) {
|
|
34
|
-
for (let i = 0; i < str.length; i++) {
|
|
35
|
-
if (str[i] !== char) continue;
|
|
36
|
-
let bs = 0, j = i - 1;
|
|
37
|
-
while (j >= 0 && str[j] === '\\') { bs++; j--; }
|
|
38
|
-
if (bs % 2 === 0) return i;
|
|
39
|
-
}
|
|
40
|
-
return -1;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function findLastUnescapedChar(str, char) {
|
|
44
|
-
for (let i = str.length - 1; i >= 0; i--) {
|
|
45
|
-
if (str[i] !== char) continue;
|
|
46
|
-
let bs = 0, j = i - 1;
|
|
47
|
-
while (j >= 0 && str[j] === '\\') { bs++; j--; }
|
|
48
|
-
if (bs % 2 === 0) return i;
|
|
49
|
-
}
|
|
50
|
-
return -1;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/** Reverse escapeRuleContent: \( -> (, \) -> ), \\ -> \ (order matters). */
|
|
54
|
-
function unescapeRuleContent(content) {
|
|
55
|
-
return content
|
|
56
|
-
.replace(/\\\(/g, '(')
|
|
57
|
-
.replace(/\\\)/g, ')')
|
|
58
|
-
.replace(/\\\\/g, '\\');
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Convert a glob pattern string to a RegExp.
|
|
63
|
-
* Supports: * (non-slash wildcard), ** (any), ? (single char).
|
|
64
|
-
* Safe: no eval.
|
|
65
|
-
*/
|
|
66
|
-
function globToRegex(glob) {
|
|
67
|
-
let src = '';
|
|
68
|
-
let i = 0;
|
|
69
|
-
while (i < glob.length) {
|
|
70
|
-
const ch = glob[i];
|
|
71
|
-
if (ch === '*' && glob[i + 1] === '*') {
|
|
72
|
-
src += '.*';
|
|
73
|
-
i += 2;
|
|
74
|
-
if (glob[i] === '/') i++; // consume trailing slash
|
|
75
|
-
} else if (ch === '*') {
|
|
76
|
-
src += '[^/\\\\]*';
|
|
77
|
-
i++;
|
|
78
|
-
} else if (ch === '?') {
|
|
79
|
-
src += '[^/\\\\]';
|
|
80
|
-
i++;
|
|
81
|
-
} else {
|
|
82
|
-
// Escape regex metacharacters
|
|
83
|
-
src += ch.replace(/[.+^${}()|[\]\\]/g, '\\$&');
|
|
84
|
-
i++;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
return new RegExp('^' + src + '$', 'i');
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Match a single pattern entry against (toolName, toolInput).
|
|
92
|
-
* Returns true if the pattern applies.
|
|
93
|
-
*/
|
|
94
|
-
function matchesPattern(pattern, toolName, toolInput) {
|
|
95
|
-
if (typeof pattern !== 'string') return false;
|
|
96
|
-
|
|
97
|
-
// Detect Tool(specifier) form. Escape-aware, mirroring native
|
|
98
|
-
// permissionRuleValueFromString: only UNescaped parens delimit the
|
|
99
|
-
// specifier, and the close paren must be the final char.
|
|
100
|
-
const parenIdx = findFirstUnescapedChar(pattern, '(');
|
|
101
|
-
const closeIdx = findLastUnescapedChar(pattern, ')');
|
|
102
|
-
if (parenIdx > 0 && closeIdx === pattern.length - 1 && closeIdx > parenIdx) {
|
|
103
|
-
const namePart = pattern.slice(0, parenIdx);
|
|
104
|
-
const rawSpecifier = pattern.slice(parenIdx + 1, closeIdx);
|
|
105
|
-
|
|
106
|
-
if (toolName !== namePart) return false;
|
|
107
|
-
|
|
108
|
-
// Empty "()" or wildcard "(*)" specifier → tool-wide rule, match any
|
|
109
|
-
if (rawSpecifier === '' || rawSpecifier === '*') return true;
|
|
110
|
-
|
|
111
|
-
// Unescape \( \) before glob matching
|
|
112
|
-
const specifier = unescapeRuleContent(rawSpecifier);
|
|
113
|
-
// Match specifier against known path-bearing fields
|
|
114
|
-
const specRe = globToRegex(specifier);
|
|
115
|
-
const inp = toolInput || {};
|
|
116
|
-
const candidates = [...new Set([
|
|
117
|
-
inp.path, inp.command, inp.file, inp.file_path, inp.base_path, inp.cwd,
|
|
118
|
-
...(Array.isArray(inp.path) ? inp.path.map(p => (p && typeof p === 'object' ? p.path : p)) : []),
|
|
119
|
-
...(Array.isArray(inp.reads) ? inp.reads.map(r => r?.path) : []),
|
|
120
|
-
...(Array.isArray(inp.edits) ? inp.edits.flatMap(e => [e?.path, e?.file_path]) : []),
|
|
121
|
-
...(Array.isArray(inp.writes) ? inp.writes.flatMap(w => [w?.path, w?.file_path]) : []),
|
|
122
|
-
].filter(v => typeof v === 'string'))];
|
|
123
|
-
|
|
124
|
-
return candidates.some(c => specRe.test(c));
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// Prefix wildcard: e.g. "mcp__*"
|
|
128
|
-
if (pattern.endsWith('*')) {
|
|
129
|
-
const prefix = pattern.slice(0, -1);
|
|
130
|
-
return toolName.startsWith(prefix);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// Glob on toolName itself (may contain * or **)
|
|
134
|
-
if (pattern.includes('*') || pattern.includes('?')) {
|
|
135
|
-
return globToRegex(pattern).test(toolName);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// Exact match
|
|
139
|
-
return toolName === pattern;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Evaluate priority rules against a tool call.
|
|
144
|
-
*
|
|
145
|
-
* @param {string} toolName
|
|
146
|
-
* @param {object} toolInput
|
|
147
|
-
* @param {string[]} allowList
|
|
148
|
-
* @param {string[]} denyList
|
|
149
|
-
* @param {string[]} askList
|
|
150
|
-
* @returns {'deny'|'ask'|'allow'|null} null = no list matched (use mode default)
|
|
151
|
-
*/
|
|
152
|
-
function evaluateRules(toolName, toolInput, allowList, denyList, askList) {
|
|
153
|
-
if (denyList.some(p => matchesPattern(p, toolName, toolInput))) return 'deny';
|
|
154
|
-
if (askList.some(p => matchesPattern(p, toolName, toolInput))) return 'ask';
|
|
155
|
-
if (allowList.some(p => matchesPattern(p, toolName, toolInput))) return 'allow';
|
|
156
|
-
return null;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Determine whether a tool is considered read-only.
|
|
161
|
-
* For mcp__ tools: strip the mcp__ prefix chain and check the short tool name.
|
|
162
|
-
*/
|
|
163
|
-
function isReadOnlyTool(toolName) {
|
|
164
|
-
// Strip mcp__plugin_...__ prefix (one or more double-underscore segments)
|
|
165
|
-
const parts = toolName.split('__');
|
|
166
|
-
const shortName = parts[parts.length - 1];
|
|
167
|
-
return READ_ONLY_TOOLS.has(shortName);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
module.exports = { matchesPattern, evaluateRules, isReadOnlyTool, READ_ONLY_TOOLS };
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
/**
|
|
3
|
-
* settings-loader.cjs
|
|
4
|
-
* Loads and merges Mixdog settings from three tiers (lowest → highest):
|
|
5
|
-
* 1. User global: $MIXDOG_CONFIG_DIR/settings.json (or ~/.mixdog/settings.json)
|
|
6
|
-
* 2. Project: $MIXDOG_PROJECT_DIR/.mixdog/settings.json (or cwd/.mixdog/settings.json)
|
|
7
|
-
* 3. Project local: $MIXDOG_PROJECT_DIR/.mixdog/settings.local.json
|
|
8
|
-
*
|
|
9
|
-
* Only `permissions` sub-tree is returned; the rest is ignored.
|
|
10
|
-
* Pure fs/path — no external deps.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
const fs = require('fs');
|
|
14
|
-
const path = require('path');
|
|
15
|
-
const os = require('os');
|
|
16
|
-
|
|
17
|
-
// ── In-process mtime-driven cache ────────────────────────────────────────────
|
|
18
|
-
// Keyed by resolved file path. Each entry: { mtime: number|null, data: any }.
|
|
19
|
-
// Invalidated per-tier independently when mtime changes.
|
|
20
|
-
// TTL: never-expire by default (mtime-driven only).
|
|
21
|
-
const _fileCache = new Map();
|
|
22
|
-
|
|
23
|
-
function readJsonCached(filePath) {
|
|
24
|
-
let entry = _fileCache.get(filePath);
|
|
25
|
-
let mtime = null;
|
|
26
|
-
try { mtime = fs.statSync(filePath).mtimeMs; } catch { /* file absent */ }
|
|
27
|
-
if (entry && entry.mtime === mtime) return entry.data;
|
|
28
|
-
let data = null;
|
|
29
|
-
if (mtime !== null) {
|
|
30
|
-
try { data = JSON.parse(fs.readFileSync(filePath, 'utf8')); } catch { data = null; }
|
|
31
|
-
}
|
|
32
|
-
_fileCache.set(filePath, { mtime, data });
|
|
33
|
-
return data;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/** Clear the entire settings cache (for test isolation). */
|
|
37
|
-
function clearSettingsCache() {
|
|
38
|
-
_fileCache.clear();
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Merge two permissions objects (base then overlay).
|
|
43
|
-
* Arrays are concatenated and de-duped; scalar fields are overwritten.
|
|
44
|
-
*/
|
|
45
|
-
function mergePermissions(base, overlay) {
|
|
46
|
-
if (!overlay) return base || {};
|
|
47
|
-
if (!base) return overlay || {};
|
|
48
|
-
|
|
49
|
-
const merged = Object.assign({}, base);
|
|
50
|
-
|
|
51
|
-
for (const key of ['allow', 'deny', 'ask']) {
|
|
52
|
-
const b = Array.isArray(base[key]) ? base[key] : [];
|
|
53
|
-
const o = Array.isArray(overlay[key]) ? overlay[key] : [];
|
|
54
|
-
const combined = [...b, ...o];
|
|
55
|
-
if (combined.length) merged[key] = [...new Set(combined)];
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// scalar: overlay wins
|
|
59
|
-
if (overlay.defaultMode !== undefined) merged.defaultMode = overlay.defaultMode;
|
|
60
|
-
|
|
61
|
-
return merged;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Load and merge settings from all three tiers.
|
|
66
|
-
* Returns `{ allow: string[], deny: string[], ask: string[], defaultMode: string }`.
|
|
67
|
-
*/
|
|
68
|
-
function loadPermissions(projectDir) {
|
|
69
|
-
const userConfigDir = process.env.MIXDOG_CONFIG_DIR ||
|
|
70
|
-
path.join(os.homedir(), '.mixdog');
|
|
71
|
-
const projDir = projectDir ||
|
|
72
|
-
process.env.MIXDOG_PROJECT_DIR ||
|
|
73
|
-
process.cwd();
|
|
74
|
-
|
|
75
|
-
const userSettings = readJsonCached(path.join(userConfigDir, 'settings.json'));
|
|
76
|
-
const projectSettings = readJsonCached(path.join(projDir, '.mixdog', 'settings.json'));
|
|
77
|
-
const localSettings = readJsonCached(path.join(projDir, '.mixdog', 'settings.local.json'));
|
|
78
|
-
|
|
79
|
-
// Accept permissions from `settings.permissions` (canonical) OR top-level
|
|
80
|
-
// `allow`/`deny`/`ask`/`defaultMode` fields (common user shorthand).
|
|
81
|
-
function extractPerms(s) {
|
|
82
|
-
if (!s) return {};
|
|
83
|
-
const nested = (s.permissions && typeof s.permissions === 'object') ? s.permissions : {};
|
|
84
|
-
const topLevel = {};
|
|
85
|
-
for (const key of ['allow', 'deny', 'ask']) {
|
|
86
|
-
if (Array.isArray(s[key])) topLevel[key] = s[key];
|
|
87
|
-
}
|
|
88
|
-
if (typeof s.defaultMode === 'string') topLevel.defaultMode = s.defaultMode;
|
|
89
|
-
return mergePermissions(nested, topLevel);
|
|
90
|
-
}
|
|
91
|
-
const userPerms = extractPerms(userSettings);
|
|
92
|
-
const projectPerms = extractPerms(projectSettings);
|
|
93
|
-
const localPerms = extractPerms(localSettings);
|
|
94
|
-
|
|
95
|
-
let merged = mergePermissions({}, userPerms);
|
|
96
|
-
merged = mergePermissions(merged, projectPerms);
|
|
97
|
-
merged = mergePermissions(merged, localPerms);
|
|
98
|
-
|
|
99
|
-
const VALID_MODES = new Set(['default', 'acceptEdits', 'plan', 'dontAsk', 'bypassPermissions', 'auto']);
|
|
100
|
-
const rawMode = merged.defaultMode;
|
|
101
|
-
const resolvedMode = (typeof rawMode === 'string' && VALID_MODES.has(rawMode))
|
|
102
|
-
? rawMode
|
|
103
|
-
: 'default';
|
|
104
|
-
return {
|
|
105
|
-
allow: Array.isArray(merged.allow) ? merged.allow : [],
|
|
106
|
-
deny: Array.isArray(merged.deny) ? merged.deny : [],
|
|
107
|
-
ask: Array.isArray(merged.ask) ? merged.ask : [],
|
|
108
|
-
defaultMode: resolvedMode,
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
module.exports = { loadPermissions, clearSettingsCache };
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
// Single source for the hook IPC path. Centralizes an existing platform
|
|
4
|
-
// contract (not new heuristic). Rust mirror native/mixdog-shim/src/main.rs:34-43
|
|
5
|
-
// must stay in sync.
|
|
6
|
-
module.exports = function hookPipePath() {
|
|
7
|
-
return process.platform === 'win32'
|
|
8
|
-
? '\\\\.\\pipe\\mixdog-hooks'
|
|
9
|
-
: require('path').join(process.env.XDG_RUNTIME_DIR || '/tmp', 'mixdog-hooks.sock');
|
|
10
|
-
};
|