mixdog 0.9.135 → 0.9.136
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 -1
- package/scripts/tool-stress.mjs +8 -1
- package/src/agents/maintainer/agent.json +1 -1
- package/src/defaults/skills/setup/SKILL.md +8 -43
- package/src/help.mjs +0 -2
- package/src/rules/lead/01-general.md +0 -4
- package/src/rules/shared/01-tool.md +10 -4
- package/src/runtime/agent/orchestrator/agent-trace-format.mjs +30 -4
- package/src/runtime/agent/orchestrator/agent-trace-timing.test.mjs +57 -0
- package/src/runtime/agent/orchestrator/session/cache/prefetch-cache.mjs +3 -3
- package/src/runtime/agent/orchestrator/session/cache/prefetch-cache.test.mjs +40 -0
- package/src/runtime/agent/orchestrator/session/tool-batch.mjs +7 -0
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +0 -5
- package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +17 -4
- package/src/runtime/agent/orchestrator/tools/builtin/edit-sequential-occupation.test.mjs +40 -0
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +150 -41
- package/src/runtime/agent/orchestrator/tools/builtin/local-search-telemetry.mjs +4 -0
- package/src/runtime/agent/orchestrator/tools/builtin/native-search-client.mjs +34 -0
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +18 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-streaming.mjs +10 -9
- package/src/runtime/agent/orchestrator/tools/builtin/search-glob-tool.mjs +124 -18
- package/src/runtime/agent/orchestrator/tools/builtin/search-glob-top-k.test.mjs +39 -0
- package/src/runtime/agent/orchestrator/tools/builtin/search-grep-tool.mjs +7 -1
- package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +4 -1
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +82 -5
- package/src/runtime/agent/orchestrator/tools/code-graph/graph-binary.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/graph-manifest.json +11 -11
- package/src/runtime/agent/orchestrator/tools/lib/native-spawn-client.mjs +78 -12
- package/src/runtime/agent/orchestrator/tools/lib/shell-warm-standby.mjs +34 -3
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +12 -5
- package/src/runtime/agent/orchestrator/tools/shell-exec-output.mjs +87 -0
- package/src/runtime/agent/orchestrator/tools/spawn-manifest.json +11 -11
- package/src/runtime/channels/lib/config.mjs +14 -101
- package/src/runtime/channels/lib/index-drop-trace.mjs +2 -5
- package/src/runtime/channels/lib/owned-runtime.mjs +4 -80
- package/src/runtime/channels/lib/scheduler.mjs +75 -18
- package/src/runtime/channels/lib/status-snapshot.mjs +5 -68
- package/src/runtime/channels/lib/tool-dispatch.mjs +12 -55
- package/src/runtime/channels/lib/worker-ipc.mjs +1 -16
- package/src/runtime/channels/lib/worker-main.mjs +22 -307
- package/src/runtime/media/renditions.mjs +68 -5
- package/src/runtime/media/renditions.test.mjs +45 -0
- package/src/runtime/media/store.mjs +1 -0
- package/src/runtime/memory/index.mjs +4 -3
- package/src/runtime/memory/lib/cycle-scheduler.mjs +17 -6
- package/src/runtime/memory/lib/memory-cycle-packets.test.mjs +7 -1
- package/src/runtime/memory/lib/memory-cycle-requests.mjs +13 -0
- package/src/runtime/shared/config.mjs +8 -49
- package/src/runtime/shared/schedule-time.mjs +25 -0
- package/src/runtime/shared/schedule-time.test.mjs +29 -0
- package/src/runtime/shared/schedules-db.mjs +73 -1
- package/src/runtime/shared/tool-card-model.mjs +1 -1
- package/src/runtime/shared/transcript-writer.mjs +10 -17
- package/src/runtime/shared/update-checker.mjs +7 -2
- package/src/runtime/shared/update-checker.test.mjs +33 -0
- package/src/session-runtime/channel-config-api.mjs +3 -20
- package/src/session-runtime/config-lifecycle.mjs +1 -42
- package/src/session-runtime/lifecycle-api.mjs +1 -19
- package/src/session-runtime/model-route-api.mjs +0 -2
- package/src/session-runtime/model-settings-persist.test.mjs +0 -1
- package/src/session-runtime/prewarm.mjs +2 -7
- package/src/session-runtime/prewarm.test.mjs +0 -1
- package/src/session-runtime/runtime-core.mjs +6 -185
- package/src/session-runtime/session-lifecycle.mjs +4 -17
- package/src/session-runtime/session-transcript.mjs +54 -0
- package/src/session-runtime/session-turn-api.mjs +4 -44
- package/src/session-runtime/settings-api.mjs +0 -45
- package/src/session-runtime/tool-catalog-schema.mjs +1 -1
- package/src/standalone/channel-admin.mjs +8 -150
- package/src/standalone/daemon.mjs +5 -48
- package/src/standalone/seeds.mjs +18 -1
- package/src/standalone/session-protocol.mjs +0 -8
- package/src/standalone/session-service.mjs +1 -3
- package/src/tui/App.jsx +0 -5
- package/src/tui/app/app-view.jsx +1 -2
- package/src/tui/app/create-app-pickers.mjs +0 -20
- package/src/tui/app/doctor.mjs +2 -6
- package/src/tui/app/prompt-submit.mjs +2 -49
- package/src/tui/app/settings-picker.mjs +22 -96
- package/src/tui/app/slash-commands.mjs +0 -2
- package/src/tui/app/slash-dispatch.mjs +0 -17
- package/src/tui/app/use-prompt-handlers.mjs +4 -1
- package/src/tui/components/StatusLine.jsx +4 -4
- package/src/tui/dist/index.mjs +1003 -1461
- package/src/tui/session/context-state.mjs +0 -1
- package/src/tui/session/queue-helpers.mjs +1 -1
- package/src/tui/session/queue-helpers.test.mjs +10 -1
- package/src/tui/session/session-api-ext.mjs +0 -89
- package/src/tui/session/session-api.mjs +0 -2
- package/src/tui/session/tool-card-results.mjs +1 -1
- package/src/tui/session/tool-result-status.mjs +3 -3
- package/src/tui/session/tool-result-status.test.mjs +3 -3
- package/src/tui/session-local.mjs +0 -26
- package/src/workflows/default/WORKFLOW.md +13 -15
- package/src/workflows/solo/WORKFLOW.md +6 -7
- package/src/runtime/channels/lib/config-lock.mjs +0 -13
- package/src/runtime/channels/lib/drop-trace.mjs +0 -71
- package/src/runtime/channels/lib/format.mjs +0 -190
- package/src/runtime/channels/lib/inbound-handler.mjs +0 -387
- package/src/runtime/channels/lib/inbound-routing.mjs +0 -81
- package/src/runtime/channels/lib/interaction-handlers.mjs +0 -70
- package/src/runtime/channels/lib/interaction-workflows.mjs +0 -76
- package/src/runtime/channels/lib/memory-client.mjs +0 -289
- package/src/runtime/channels/lib/output-forwarder.mjs +0 -934
- package/src/runtime/channels/lib/session-control.mjs +0 -36
- package/src/runtime/channels/lib/session-discovery.mjs +0 -217
- package/src/runtime/channels/lib/session-discovery.test.mjs +0 -32
- package/src/runtime/channels/lib/telegram-format.mjs +0 -280
- package/src/runtime/channels/lib/tool-format.mjs +0 -125
- package/src/runtime/channels/lib/transcript-binding.mjs +0 -336
- package/src/runtime/channels/lib/transcript-discovery.mjs +0 -208
- package/src/runtime/channels/providers/discord-access.mjs +0 -30
- package/src/runtime/channels/providers/discord-attachments.mjs +0 -69
- package/src/runtime/channels/providers/discord-gateway.mjs +0 -230
- package/src/runtime/channels/providers/discord.mjs +0 -730
- package/src/runtime/channels/providers/telegram.mjs +0 -598
- package/src/runtime/channels/providers/telegram.test.mjs +0 -75
- package/src/session-runtime/remote-control.mjs +0 -126
- package/src/session-runtime/remote-transcript.mjs +0 -134
- package/src/session-runtime/remote-transition-queue.mjs +0 -14
- package/src/tui/app/channel-pickers.mjs +0 -384
|
@@ -520,7 +520,13 @@ export async function executeGrepTool(args, workDir, executeChildBuiltinTool, re
|
|
|
520
520
|
// Skipped for pattern arrays and when `-i` is already set.
|
|
521
521
|
const caseHintSuffix = async () => {
|
|
522
522
|
const caseInsensitive = args['-i'] === true;
|
|
523
|
-
|
|
523
|
+
// A broad no-match must not trigger a second whole-tree scan just to
|
|
524
|
+
// produce an advisory casing hint. The requested case-sensitive result
|
|
525
|
+
// is already complete; retain the hint only for explicit file scopes.
|
|
526
|
+
if (grepStat.isDirectory()
|
|
527
|
+
|| caseInsensitive
|
|
528
|
+
|| patterns.length !== 1
|
|
529
|
+
|| !/[A-Za-z]/.test(patterns[0])) return '';
|
|
524
530
|
try {
|
|
525
531
|
const probeArgs = buildGrepRgArgs({
|
|
526
532
|
patterns,
|
|
@@ -568,7 +568,10 @@ export function preflightPowerShellHygiene(command, { shellType, shellName } = {
|
|
|
568
568
|
// regex/search args are never mistaken for real syntax.
|
|
569
569
|
const masked = maskQuotedRegions(rewritten);
|
|
570
570
|
|
|
571
|
-
|
|
571
|
+
// Only command heads matter here. Scan the quote-masked copy so PowerShell
|
|
572
|
+
// literals use PowerShell escaping rules (`\` is literal, backtick escapes)
|
|
573
|
+
// and nested bash snippets remain opaque.
|
|
574
|
+
for (const segment of shellSplitSegments(masked)) {
|
|
572
575
|
for (const stage of shellSplitPipelineSegments(segment)) {
|
|
573
576
|
const tokens = stripShellProbeWrappers(shellTokenize(stage) || []);
|
|
574
577
|
if (tokens.length === 0) continue;
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// race), isCodeGraphTool. Extracted verbatim from code-graph.mjs.
|
|
4
4
|
import { resolve as pathResolve, isAbsolute, relative as pathRelative, basename as pathBasename, extname } from 'node:path';
|
|
5
5
|
import { homedir as osHomedir } from 'node:os';
|
|
6
|
+
import { createHash } from 'node:crypto';
|
|
6
7
|
import { existsSync, statSync } from 'node:fs';
|
|
7
8
|
import { readFile } from 'node:fs/promises';
|
|
8
9
|
import { normalizeInputPath, toDisplayPath } from '../builtin.mjs';
|
|
@@ -29,6 +30,8 @@ import {
|
|
|
29
30
|
_stripEmptyArgs,
|
|
30
31
|
} from './project-root.mjs';
|
|
31
32
|
import { buildCodeGraphAsync, prewarmCodeGraph, prewarmCodeGraphSymbols } from './build.mjs';
|
|
33
|
+
import { _fileInfoFromRustRecord, _runGraphFiles } from './graph-binary.mjs';
|
|
34
|
+
import { _attachGraphRuntimeCaches } from './graph-model.mjs';
|
|
32
35
|
import {
|
|
33
36
|
_isFilesystemRootPath,
|
|
34
37
|
collectTrustedCodeGraphRoots,
|
|
@@ -87,6 +90,74 @@ function _outlineLanguageForPath(file) {
|
|
|
87
90
|
return null;
|
|
88
91
|
}
|
|
89
92
|
|
|
93
|
+
const _exactFileGraphInflight = new Map();
|
|
94
|
+
const _exactFileGraphCache = new Map();
|
|
95
|
+
const EXACT_FILE_GRAPH_CACHE_MAX = 64;
|
|
96
|
+
|
|
97
|
+
async function _buildExactFileGraph(cwd, abs, signal = null) {
|
|
98
|
+
const root = pathResolve(cwd);
|
|
99
|
+
const file = pathResolve(abs);
|
|
100
|
+
const rel = _graphRel(file, root);
|
|
101
|
+
const key = `${root}\0${rel}`;
|
|
102
|
+
const sourceText = await readFile(file, { encoding: 'utf8', signal: signal || undefined });
|
|
103
|
+
const sourceHash = createHash('sha256').update(sourceText).digest('hex');
|
|
104
|
+
const cached = _exactFileGraphCache.get(key);
|
|
105
|
+
if (cached?.sourceHash === sourceHash) {
|
|
106
|
+
_exactFileGraphCache.delete(key);
|
|
107
|
+
_exactFileGraphCache.set(key, cached);
|
|
108
|
+
return cached.graph;
|
|
109
|
+
}
|
|
110
|
+
const existing = _exactFileGraphInflight.get(key);
|
|
111
|
+
if (existing?.sourceHash === sourceHash) return existing.promise;
|
|
112
|
+
const pending = (async () => {
|
|
113
|
+
const records = await _runGraphFiles(root, [rel], [], signal);
|
|
114
|
+
const record = records.find((item) => _graphRel(pathResolve(root, item.rel), root) === rel);
|
|
115
|
+
if (!record) return null;
|
|
116
|
+
const info = _fileInfoFromRustRecord(record, root);
|
|
117
|
+
const node = {
|
|
118
|
+
abs: info.abs,
|
|
119
|
+
rel: info.rel,
|
|
120
|
+
lang: info.lang,
|
|
121
|
+
fingerprint: info.fingerprint,
|
|
122
|
+
rawImports: info.rawImports,
|
|
123
|
+
resolvedImportsRel: [],
|
|
124
|
+
resolvedImports: [],
|
|
125
|
+
importedBy: [],
|
|
126
|
+
packageName: info.packageName,
|
|
127
|
+
namespaceName: info.namespaceName,
|
|
128
|
+
goPackageName: info.goPackageName,
|
|
129
|
+
topLevelTypes: info.topLevelTypes,
|
|
130
|
+
tokenSymbols: info.tokenSymbols,
|
|
131
|
+
symbols: Array.isArray(info.symbols) ? info.symbols : [],
|
|
132
|
+
};
|
|
133
|
+
const graph = _attachGraphRuntimeCaches({
|
|
134
|
+
cwd: root,
|
|
135
|
+
nodes: new Map([[node.rel, node]]),
|
|
136
|
+
reverse: new Map(),
|
|
137
|
+
builtAt: Date.now(),
|
|
138
|
+
signature: info.fingerprint || '',
|
|
139
|
+
truncated: false,
|
|
140
|
+
});
|
|
141
|
+
graph._sourceTextCache.set(node.rel, {
|
|
142
|
+
fingerprint: node.fingerprint || '',
|
|
143
|
+
text: sourceText,
|
|
144
|
+
});
|
|
145
|
+
_exactFileGraphCache.delete(key);
|
|
146
|
+
_exactFileGraphCache.set(key, { sourceHash, graph });
|
|
147
|
+
while (_exactFileGraphCache.size > EXACT_FILE_GRAPH_CACHE_MAX) {
|
|
148
|
+
_exactFileGraphCache.delete(_exactFileGraphCache.keys().next().value);
|
|
149
|
+
}
|
|
150
|
+
return graph;
|
|
151
|
+
})();
|
|
152
|
+
const inflight = { sourceHash, promise: pending };
|
|
153
|
+
_exactFileGraphInflight.set(key, inflight);
|
|
154
|
+
try {
|
|
155
|
+
return await pending;
|
|
156
|
+
} finally {
|
|
157
|
+
if (_exactFileGraphInflight.get(key) === inflight) _exactFileGraphInflight.delete(key);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
90
161
|
async function _mapWithConcurrency(values, mapper) {
|
|
91
162
|
const out = new Array(values.length);
|
|
92
163
|
let cursor = 0;
|
|
@@ -693,9 +764,17 @@ async function findSymbolTool(args, cwd, signal = null, options = {}) {
|
|
|
693
764
|
else prewarmCodeGraph(cwd);
|
|
694
765
|
return `prewarm scheduled: cwd=${cwd} symbols=${symbols.length}${symbols.length ? ` (${symbols.slice(0, 5).join(',')}${symbols.length > 5 ? `,+${symbols.length - 5}` : ''})` : ''}`;
|
|
695
766
|
}
|
|
696
|
-
const
|
|
697
|
-
|
|
698
|
-
|
|
767
|
+
const normFile = normalizeInputPath(args?.file);
|
|
768
|
+
const abs = normFile ? (isAbsolute(normFile) ? pathResolve(normFile) : pathResolve(cwd, normFile)) : null;
|
|
769
|
+
let exactFile = false;
|
|
770
|
+
if (abs && _outlineLanguageForPath(abs)) {
|
|
771
|
+
try { exactFile = statSync(abs).isFile(); } catch { exactFile = false; }
|
|
772
|
+
}
|
|
773
|
+
const graph = exactFile
|
|
774
|
+
? await _buildExactFileGraph(cwd, abs, signal)
|
|
775
|
+
: await buildCodeGraphAsync(cwd, signal, {
|
|
776
|
+
excludedProjectRoots: options?.excludedProjectRoots,
|
|
777
|
+
});
|
|
699
778
|
if (!graph) throw new Error(`find_symbol: cwd '${cwd}' is not an indexed/known project root or contains zero eligible files`);
|
|
700
779
|
if (options?.scopedCacheOutcome && graph.truncated) {
|
|
701
780
|
markScopedCacheIncomplete(options.scopedCacheOutcome);
|
|
@@ -703,8 +782,6 @@ async function findSymbolTool(args, cwd, signal = null, options = {}) {
|
|
|
703
782
|
const symbol = String(args?.symbol || '').trim();
|
|
704
783
|
const language = String(args?.language || '').trim() || null;
|
|
705
784
|
const limit = Math.max(1, Math.min(50, Number(args?.limit || 20)));
|
|
706
|
-
const normFile = normalizeInputPath(args?.file);
|
|
707
|
-
const abs = normFile ? (isAbsolute(normFile) ? pathResolve(normFile) : pathResolve(cwd, normFile)) : null;
|
|
708
785
|
const fileRel = abs ? _graphRel(abs, cwd) : null;
|
|
709
786
|
if (fileRel && !graph.nodes.get(fileRel)) {
|
|
710
787
|
return _appendSameBasenameHint(`Error: find_symbol: file not found in graph: ${normFile}`, normFile, graph);
|
|
@@ -248,7 +248,7 @@ export function _runGraphWalk(absRoot) { return _runGraphBinaryJsonl(absRoot, []
|
|
|
248
248
|
// topLevelTypes}. Rust builds the index + resolves over ALL nodes (fresh +
|
|
249
249
|
// reused) and emits fresh rels as full records, reused rels as lightweight
|
|
250
250
|
// {rel, resolvedImports, importedBy}.
|
|
251
|
-
export function _runGraphFiles(absRoot, rels, reusedMetas) {
|
|
251
|
+
export function _runGraphFiles(absRoot, rels, reusedMetas, signal = null) {
|
|
252
252
|
const lines = Array.isArray(reusedMetas)
|
|
253
253
|
? reusedMetas.map((m) => JSON.stringify({
|
|
254
254
|
rel: m.rel,
|
|
@@ -260,7 +260,7 @@ export function _runGraphFiles(absRoot, rels, reusedMetas) {
|
|
|
260
260
|
topLevelTypes: Array.isArray(m.topLevelTypes) ? m.topLevelTypes : [],
|
|
261
261
|
}))
|
|
262
262
|
: [];
|
|
263
|
-
return _runGraphBinaryJsonl(absRoot, ['--files', ...rels], lines);
|
|
263
|
+
return _runGraphBinaryJsonl(absRoot, ['--files', ...rels], lines, signal);
|
|
264
264
|
}
|
|
265
265
|
|
|
266
266
|
// Map a Rust FileRecord (rel/lang/fp/tokens/rawImports/resolvedImports/
|
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.1.
|
|
2
|
+
"version": "0.1.18",
|
|
3
3
|
"_comment": "Synced from immutable graph-v release assets.",
|
|
4
4
|
"assets": {
|
|
5
5
|
"darwin-arm64": {
|
|
6
|
-
"url": "https://github.com/tribgames/mixdog/releases/download/graph-v0.1.
|
|
7
|
-
"sha256": "
|
|
6
|
+
"url": "https://github.com/tribgames/mixdog/releases/download/graph-v0.1.18/mixdog-graph-darwin-arm64",
|
|
7
|
+
"sha256": "c89403d4a4cd08aa27427b65e9f0efe4885fb35cbe788527b81279e03edc82eb"
|
|
8
8
|
},
|
|
9
9
|
"darwin-x64": {
|
|
10
|
-
"url": "https://github.com/tribgames/mixdog/releases/download/graph-v0.1.
|
|
11
|
-
"sha256": "
|
|
10
|
+
"url": "https://github.com/tribgames/mixdog/releases/download/graph-v0.1.18/mixdog-graph-darwin-x64",
|
|
11
|
+
"sha256": "43765bb6598f0b7db3ba5df8348bb7221fc0e4fec5f8ad9d4d53887d4951e42f"
|
|
12
12
|
},
|
|
13
13
|
"linux-arm64": {
|
|
14
|
-
"url": "https://github.com/tribgames/mixdog/releases/download/graph-v0.1.
|
|
15
|
-
"sha256": "
|
|
14
|
+
"url": "https://github.com/tribgames/mixdog/releases/download/graph-v0.1.18/mixdog-graph-linux-arm64",
|
|
15
|
+
"sha256": "ada18e0d9ca3ab8fe65098b81c8856d01992eb2f33a2f2923e886280e2b635a4"
|
|
16
16
|
},
|
|
17
17
|
"linux-x64": {
|
|
18
|
-
"url": "https://github.com/tribgames/mixdog/releases/download/graph-v0.1.
|
|
19
|
-
"sha256": "
|
|
18
|
+
"url": "https://github.com/tribgames/mixdog/releases/download/graph-v0.1.18/mixdog-graph-linux-x64",
|
|
19
|
+
"sha256": "724d219ec72828662f34ba7b0edf00938e8f9f00df76cb6c41675551e2c0137e"
|
|
20
20
|
},
|
|
21
21
|
"win32-x64": {
|
|
22
|
-
"url": "https://github.com/tribgames/mixdog/releases/download/graph-v0.1.
|
|
23
|
-
"sha256": "
|
|
22
|
+
"url": "https://github.com/tribgames/mixdog/releases/download/graph-v0.1.18/mixdog-graph-win32-x64.exe",
|
|
23
|
+
"sha256": "c24f4ffb0beec3f355bc4e33c02adafce2ba966df34922ec0ff2ce2cde23fe65"
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
}
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
2
|
import { existsSync } from 'node:fs';
|
|
3
|
+
import { dirname, resolve as pathResolve } from 'node:path';
|
|
3
4
|
import { createInterface } from 'node:readline';
|
|
4
5
|
import { EventEmitter } from 'node:events';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
5
7
|
import { hiddenSpawnOpts } from '../../../../shared/spawn-flags.mjs';
|
|
6
8
|
import { packageNativeToolPath } from '../../../../shared/native-tool-paths.mjs';
|
|
7
9
|
import { getPluginData } from '../../config.mjs';
|
|
8
10
|
import { ensureSpawnBinary, findCachedSpawnBinary } from '../spawn-binary-fetcher.mjs';
|
|
9
11
|
|
|
10
12
|
const RESTART_BACKOFF_MS = 30_000;
|
|
13
|
+
const SERVER_READY_TIMEOUT_MS = 5_000;
|
|
11
14
|
|
|
12
15
|
let _server = null;
|
|
13
16
|
let _binaryPath = undefined;
|
|
@@ -57,22 +60,24 @@ function acceptTask(message) {
|
|
|
57
60
|
class LineStream extends EventEmitter {
|
|
58
61
|
constructor() {
|
|
59
62
|
super();
|
|
60
|
-
this._pending =
|
|
63
|
+
this._pending = [];
|
|
61
64
|
}
|
|
62
65
|
setEncoding() { return this; }
|
|
63
66
|
emit(event, ...args) {
|
|
64
67
|
if (event === 'data' && this.listenerCount('data') === 0) {
|
|
65
|
-
|
|
68
|
+
if (args[0] != null) this._pending.push(args[0]);
|
|
66
69
|
return false;
|
|
67
70
|
}
|
|
68
71
|
return super.emit(event, ...args);
|
|
69
72
|
}
|
|
70
73
|
on(event, listener) {
|
|
71
74
|
const ret = super.on(event, listener);
|
|
72
|
-
if (event === 'data' && this._pending) {
|
|
75
|
+
if (event === 'data' && this._pending.length > 0) {
|
|
73
76
|
const pending = this._pending;
|
|
74
|
-
this._pending =
|
|
75
|
-
queueMicrotask(() =>
|
|
77
|
+
this._pending = [];
|
|
78
|
+
queueMicrotask(() => {
|
|
79
|
+
for (const chunk of pending) listener(chunk);
|
|
80
|
+
});
|
|
76
81
|
}
|
|
77
82
|
return ret;
|
|
78
83
|
}
|
|
@@ -142,6 +147,17 @@ function _resolveBinary() {
|
|
|
142
147
|
_binaryPath = explicit;
|
|
143
148
|
return _binaryPath;
|
|
144
149
|
}
|
|
150
|
+
const moduleDir = dirname(fileURLToPath(import.meta.url));
|
|
151
|
+
const binName = process.platform === 'win32' ? 'mixdog-spawn.exe' : 'mixdog-spawn';
|
|
152
|
+
const localBuild = pathResolve(
|
|
153
|
+
moduleDir,
|
|
154
|
+
'../../../../../../native/mixdog-spawn/target/release',
|
|
155
|
+
binName,
|
|
156
|
+
);
|
|
157
|
+
if (existsSync(localBuild)) {
|
|
158
|
+
_binaryPath = localBuild;
|
|
159
|
+
return _binaryPath;
|
|
160
|
+
}
|
|
145
161
|
const installed = packageNativeToolPath('spawn');
|
|
146
162
|
if (existsSync(installed)) {
|
|
147
163
|
_binaryPath = installed;
|
|
@@ -243,13 +259,23 @@ function _ensureServer() {
|
|
|
243
259
|
_lastFailureAt = Date.now();
|
|
244
260
|
return null;
|
|
245
261
|
}
|
|
246
|
-
const server = { child, pending: new Map(), sequence: 0 };
|
|
262
|
+
const server = { child, pending: new Map(), sequence: 0, ready: false, caps: {} };
|
|
263
|
+
let resolveReady;
|
|
264
|
+
server.readyPromise = new Promise((resolve) => { resolveReady = resolve; });
|
|
265
|
+
const settleReady = (value) => {
|
|
266
|
+
if (!resolveReady) return;
|
|
267
|
+
const resolve = resolveReady;
|
|
268
|
+
resolveReady = null;
|
|
269
|
+
resolve(value);
|
|
270
|
+
};
|
|
247
271
|
const lines = createInterface({ input: child.stdout });
|
|
248
272
|
lines.on('line', (line) => {
|
|
249
273
|
let message;
|
|
250
274
|
try { message = JSON.parse(line); } catch { return; }
|
|
251
275
|
if (message?.ready === true) {
|
|
252
276
|
server.caps = (message.caps && typeof message.caps === 'object') ? message.caps : {};
|
|
277
|
+
server.ready = true;
|
|
278
|
+
settleReady(true);
|
|
253
279
|
return;
|
|
254
280
|
}
|
|
255
281
|
if (String(message?.event || '').startsWith('task_')) acceptTask(message);
|
|
@@ -257,8 +283,14 @@ function _ensureServer() {
|
|
|
257
283
|
if (!pending) return;
|
|
258
284
|
pending.onMessage(message);
|
|
259
285
|
});
|
|
260
|
-
child.on('error', (error) => {
|
|
261
|
-
|
|
286
|
+
child.on('error', (error) => {
|
|
287
|
+
settleReady(false);
|
|
288
|
+
if (_server === server) _teardown(error);
|
|
289
|
+
});
|
|
290
|
+
child.on('exit', () => {
|
|
291
|
+
settleReady(false);
|
|
292
|
+
if (_server === server) _teardown();
|
|
293
|
+
});
|
|
262
294
|
child.stdin.on('error', (error) => { if (_server === server) _teardown(error); });
|
|
263
295
|
_setServerReferenced(server, false);
|
|
264
296
|
_server = server;
|
|
@@ -274,17 +306,40 @@ export async function warmNativeSpawnServer() {
|
|
|
274
306
|
}
|
|
275
307
|
|
|
276
308
|
export async function ensureNativeSpawnServer() {
|
|
277
|
-
if (_server) return true;
|
|
309
|
+
if (_server?.ready) return true;
|
|
278
310
|
let binary = _resolveBinary();
|
|
279
311
|
if (!binary) {
|
|
280
312
|
binary = await ensureSpawnBinary(getPluginData());
|
|
281
313
|
_binaryPath = binary;
|
|
282
314
|
}
|
|
283
|
-
|
|
315
|
+
const server = _ensureServer();
|
|
316
|
+
if (!server) {
|
|
284
317
|
throw Object.assign(new Error('verified native spawn server could not be started'), {
|
|
285
318
|
code: 'NATIVE_SPAWN_UNAVAILABLE',
|
|
286
319
|
});
|
|
287
320
|
}
|
|
321
|
+
if (!server.ready) {
|
|
322
|
+
let timer;
|
|
323
|
+
let ready;
|
|
324
|
+
_setServerReferenced(server, true);
|
|
325
|
+
try {
|
|
326
|
+
ready = await Promise.race([
|
|
327
|
+
server.readyPromise,
|
|
328
|
+
new Promise((resolve) => {
|
|
329
|
+
timer = setTimeout(() => resolve(false), SERVER_READY_TIMEOUT_MS);
|
|
330
|
+
}),
|
|
331
|
+
]);
|
|
332
|
+
} finally {
|
|
333
|
+
if (timer) clearTimeout(timer);
|
|
334
|
+
if (_server === server) _recomputeServerRef(server);
|
|
335
|
+
}
|
|
336
|
+
if (!ready) {
|
|
337
|
+
if (_server === server) _teardown(new Error('native spawn server ready timeout'));
|
|
338
|
+
throw Object.assign(new Error('verified native spawn server did not become ready'), {
|
|
339
|
+
code: 'NATIVE_SPAWN_UNAVAILABLE',
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
}
|
|
288
343
|
return true;
|
|
289
344
|
}
|
|
290
345
|
|
|
@@ -311,6 +366,7 @@ export function tryNativeSpawn({ shell, argv, spawnOptions = {}, cwd } = {}) {
|
|
|
311
366
|
timeoutMs: Math.max(0, Number(spawnOptions.timeoutMs) || 0),
|
|
312
367
|
outputLimit: Math.max(0, Number(spawnOptions.outputLimit) || 0),
|
|
313
368
|
mergeStderr: spawnOptions.mergeStderr === true,
|
|
369
|
+
rawOutput: spawnOptions.rawOutput === true,
|
|
314
370
|
...(spawnOptions.stdinPipe === true ? { stdinPipe: true } : {}),
|
|
315
371
|
command: spawnOptions.command || undefined,
|
|
316
372
|
shellType: spawnOptions.shellType || undefined,
|
|
@@ -338,8 +394,18 @@ export function tryNativeSpawn({ shell, argv, spawnOptions = {}, cwd } = {}) {
|
|
|
338
394
|
fake.emit('spawn');
|
|
339
395
|
return;
|
|
340
396
|
}
|
|
341
|
-
if (event === 'stdout' && message.
|
|
342
|
-
|
|
397
|
+
if (event === 'stdout' && (message.dataBase64 || message.text)) {
|
|
398
|
+
const chunk = message.dataBase64
|
|
399
|
+
? Buffer.from(String(message.dataBase64), 'base64')
|
|
400
|
+
: String(message.text);
|
|
401
|
+
fake.stdout.emit('data', chunk);
|
|
402
|
+
}
|
|
403
|
+
else if (event === 'stderr' && (message.dataBase64 || message.text)) {
|
|
404
|
+
const chunk = message.dataBase64
|
|
405
|
+
? Buffer.from(String(message.dataBase64), 'base64')
|
|
406
|
+
: String(message.text);
|
|
407
|
+
fake.stderr.emit('data', chunk);
|
|
408
|
+
}
|
|
343
409
|
else if (event === 'root_exit') {
|
|
344
410
|
fake.exitCode = message.code == null ? null : Number(message.code);
|
|
345
411
|
fake.signalCode = message.signal || null;
|
|
@@ -14,9 +14,30 @@ import { SHELL_OUTPUT_DISK_CAP } from '../shell-exec-output.mjs';
|
|
|
14
14
|
// exactly like `-Command <script>`.
|
|
15
15
|
const STANDBY_BOOTSTRAP = "$null = . ([scriptblock]::Create('$null')); [Console]::InputEncoding=[System.Text.UTF8Encoding]::new($false); . ([scriptblock]::Create([Console]::In.ReadToEnd()))";
|
|
16
16
|
const STANDBY_ARGS = ['-NoLogo', '-NoProfile', '-NonInteractive', '-Command', STANDBY_BOOTSTRAP];
|
|
17
|
-
const
|
|
17
|
+
const _configuredIdleMs = Number(process.env.MIXDOG_SHELL_WARM_STANDBY_IDLE_MS);
|
|
18
|
+
const STANDBY_TTL_MS = Number.isFinite(_configuredIdleMs) && _configuredIdleMs >= 100
|
|
19
|
+
? Math.floor(_configuredIdleMs)
|
|
20
|
+
: 2 * 60_000;
|
|
18
21
|
|
|
19
22
|
let _slot = null; // { native, shell, envSig, createdAt }
|
|
23
|
+
let _slotIdleTimer = null;
|
|
24
|
+
|
|
25
|
+
function _clearSlotIdleTimer() {
|
|
26
|
+
if (!_slotIdleTimer) return;
|
|
27
|
+
clearTimeout(_slotIdleTimer);
|
|
28
|
+
_slotIdleTimer = null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function _armSlotIdleTimer(slot) {
|
|
32
|
+
_clearSlotIdleTimer();
|
|
33
|
+
_slotIdleTimer = setTimeout(() => {
|
|
34
|
+
_slotIdleTimer = null;
|
|
35
|
+
if (_slot !== slot) return;
|
|
36
|
+
_slot = null;
|
|
37
|
+
try { slot.native.child.kill(); } catch { /* best-effort */ }
|
|
38
|
+
}, STANDBY_TTL_MS);
|
|
39
|
+
_slotIdleTimer.unref?.();
|
|
40
|
+
}
|
|
20
41
|
|
|
21
42
|
function _disabled() {
|
|
22
43
|
return process.env.MIXDOG_SHELL_WARM_STANDBY === '0';
|
|
@@ -57,6 +78,7 @@ export function ensureWarmShellStandby({ shell, env }) {
|
|
|
57
78
|
env,
|
|
58
79
|
cwd: process.cwd(),
|
|
59
80
|
outputLimit: SHELL_OUTPUT_DISK_CAP,
|
|
81
|
+
rawOutput: true,
|
|
60
82
|
stdinPipe: true,
|
|
61
83
|
shellType: 'powershell',
|
|
62
84
|
command: '(warm shell standby)',
|
|
@@ -73,9 +95,15 @@ export function ensureWarmShellStandby({ shell, env }) {
|
|
|
73
95
|
envSig: envSignature(env),
|
|
74
96
|
createdAt: Date.now(),
|
|
75
97
|
};
|
|
76
|
-
|
|
77
|
-
|
|
98
|
+
const release = () => {
|
|
99
|
+
if (_slot !== slot) return;
|
|
100
|
+
_slot = null;
|
|
101
|
+
_clearSlotIdleTimer();
|
|
102
|
+
};
|
|
103
|
+
native.child.once('close', release);
|
|
104
|
+
native.child.once('error', release);
|
|
78
105
|
_slot = slot;
|
|
106
|
+
_armSlotIdleTimer(slot);
|
|
79
107
|
}
|
|
80
108
|
|
|
81
109
|
/** Take the parked standby for immediate use, or null on any mismatch.
|
|
@@ -98,12 +126,14 @@ export function takeWarmShellStandby({ shell, env, cwd }) {
|
|
|
98
126
|
|| Date.now() - slot.createdAt > STANDBY_TTL_MS
|
|
99
127
|
|| slot.envSig !== envSignature(env))) {
|
|
100
128
|
_slot = null;
|
|
129
|
+
_clearSlotIdleTimer();
|
|
101
130
|
try { slot.native.child.kill(); } catch { /* best-effort */ }
|
|
102
131
|
}
|
|
103
132
|
refill();
|
|
104
133
|
return null;
|
|
105
134
|
}
|
|
106
135
|
_slot = null;
|
|
136
|
+
_clearSlotIdleTimer();
|
|
107
137
|
// Back to active: the caller is about to run a real command on it and the
|
|
108
138
|
// host must stay alive until that command settles.
|
|
109
139
|
setNativeSpawnRequestIdle(slot.native.child, false);
|
|
@@ -125,6 +155,7 @@ export function takeWarmShellStandby({ shell, env, cwd }) {
|
|
|
125
155
|
export function _resetWarmShellStandbyForTest() {
|
|
126
156
|
const slot = _slot;
|
|
127
157
|
_slot = null;
|
|
158
|
+
_clearSlotIdleTimer();
|
|
128
159
|
if (slot?.native?.child) {
|
|
129
160
|
try { slot.native.child.kill(); } catch { /* best-effort */ }
|
|
130
161
|
}
|
|
@@ -35,7 +35,7 @@ export {
|
|
|
35
35
|
// renders a path marker instead of pasting the tail. Matches the
|
|
36
36
|
// SHELL_OUTPUT_MAX_CHARS used by the smart-truncate renderer in
|
|
37
37
|
// builtin.mjs so spilled output and inline output share the same boundary.
|
|
38
|
-
import { SHELL_OUTPUT_INLINE_CAP, SHELL_OUTPUT_DISK_CAP, stripAnsi, treeKill, TaskOutput, ExecResult } from './shell-exec-output.mjs';
|
|
38
|
+
import { SHELL_OUTPUT_INLINE_CAP, SHELL_OUTPUT_DISK_CAP, stripAnsi, treeKill, TaskOutput, ExecResult, ShellTextDecoder } from './shell-exec-output.mjs';
|
|
39
39
|
export { stripAnsi, ExecResult } from './shell-exec-output.mjs';
|
|
40
40
|
|
|
41
41
|
async function _execPolicyBlockMessage(command) {
|
|
@@ -221,6 +221,8 @@ export function execShellCommand({
|
|
|
221
221
|
};
|
|
222
222
|
const taskId = `shell_${randomUUID().slice(0, 8)}`;
|
|
223
223
|
const taskOutput = new TaskOutput(taskId);
|
|
224
|
+
const stdoutDecoder = new ShellTextDecoder();
|
|
225
|
+
const stderrDecoder = new ShellTextDecoder();
|
|
224
226
|
let timedOut = false;
|
|
225
227
|
let killed = false;
|
|
226
228
|
let killCause = null;
|
|
@@ -379,6 +381,7 @@ export function execShellCommand({
|
|
|
379
381
|
env,
|
|
380
382
|
cwd,
|
|
381
383
|
outputLimit: SHELL_OUTPUT_DISK_CAP,
|
|
384
|
+
rawOutput: true,
|
|
382
385
|
// NOTE (child-spawn-gate): the full command lifetime is intentionally
|
|
383
386
|
// NOT gated — bash/pwsh commands can run for minutes and would starve
|
|
384
387
|
// rg/code_graph. Only the spawn window above holds a slot.
|
|
@@ -429,21 +432,21 @@ export function execShellCommand({
|
|
|
429
432
|
}
|
|
430
433
|
|
|
431
434
|
const _stdoutData = (chunk) => {
|
|
432
|
-
|
|
435
|
+
const text = stdoutDecoder.write(chunk);
|
|
436
|
+
if (text) taskOutput.writeStdout(text);
|
|
433
437
|
if (taskOutput.binaryOutput && !settled && !autoBackgrounded) _treeKillForceSettle('binary-output');
|
|
434
438
|
if (taskOutput.writeError && !settled && !autoBackgrounded) _treeKillForceSettle('output-capture-error');
|
|
435
439
|
};
|
|
436
440
|
const _stderrData = (chunk) => {
|
|
437
|
-
|
|
441
|
+
const text = stderrDecoder.write(chunk);
|
|
442
|
+
if (text) taskOutput.writeStderr(text);
|
|
438
443
|
if (taskOutput.binaryOutput && !settled && !autoBackgrounded) _treeKillForceSettle('binary-output');
|
|
439
444
|
if (taskOutput.writeError && !settled && !autoBackgrounded) _treeKillForceSettle('output-capture-error');
|
|
440
445
|
};
|
|
441
446
|
if (child.stdout) {
|
|
442
|
-
child.stdout.setEncoding('utf-8');
|
|
443
447
|
child.stdout.on('data', _stdoutData);
|
|
444
448
|
}
|
|
445
449
|
if (child.stderr) {
|
|
446
|
-
child.stderr.setEncoding('utf-8');
|
|
447
450
|
child.stderr.on('data', _stderrData);
|
|
448
451
|
}
|
|
449
452
|
|
|
@@ -460,6 +463,10 @@ export function execShellCommand({
|
|
|
460
463
|
autoBgTimer = null;
|
|
461
464
|
}
|
|
462
465
|
detachAbortHandler();
|
|
466
|
+
const stdoutTail = stdoutDecoder.end();
|
|
467
|
+
const stderrTail = stderrDecoder.end();
|
|
468
|
+
if (stdoutTail) taskOutput.writeStdout(stdoutTail);
|
|
469
|
+
if (stderrTail) taskOutput.writeStderr(stderrTail);
|
|
463
470
|
// getStdout/getStderr can throw on a spilled-file read failure (EBADF
|
|
464
471
|
// after unlink race, EACCES). Without this catch the rejection bubbles
|
|
465
472
|
// up and leaves the outer settle promise unresolved, hanging the call.
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
statSync,
|
|
16
16
|
} from 'node:fs';
|
|
17
17
|
import { join } from 'node:path';
|
|
18
|
+
import { StringDecoder } from 'node:string_decoder';
|
|
18
19
|
import * as nodeUtil from 'node:util';
|
|
19
20
|
import { getPluginData } from '../config.mjs';
|
|
20
21
|
|
|
@@ -86,6 +87,92 @@ export function inspectShellTextChunk(value, channel = 'stdout') {
|
|
|
86
87
|
};
|
|
87
88
|
}
|
|
88
89
|
|
|
90
|
+
function detectShellTextEncoding(buffer, final = false) {
|
|
91
|
+
if (buffer.length >= 2) {
|
|
92
|
+
if (buffer[0] === 0xff && buffer[1] === 0xfe) return { encoding: 'utf16le', bom: 2 };
|
|
93
|
+
if (buffer[0] === 0xfe && buffer[1] === 0xff) return { encoding: 'utf16be', bom: 2 };
|
|
94
|
+
}
|
|
95
|
+
const pairs = Math.min(Math.floor(buffer.length / 2), 128);
|
|
96
|
+
if (pairs >= 4) {
|
|
97
|
+
let evenNuls = 0;
|
|
98
|
+
let oddNuls = 0;
|
|
99
|
+
let printablePartners = 0;
|
|
100
|
+
for (let i = 0; i < pairs * 2; i += 2) {
|
|
101
|
+
if (buffer[i] === 0) evenNuls++;
|
|
102
|
+
if (buffer[i + 1] === 0) oddNuls++;
|
|
103
|
+
const partner = buffer[i] === 0 ? buffer[i + 1] : buffer[i];
|
|
104
|
+
if (partner === 9 || partner === 10 || partner === 13 || (partner >= 0x20 && partner <= 0x7e)) {
|
|
105
|
+
printablePartners++;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const dominantNuls = Math.max(evenNuls, oddNuls);
|
|
109
|
+
const minorityNuls = Math.min(evenNuls, oddNuls);
|
|
110
|
+
if (
|
|
111
|
+
dominantNuls / pairs >= 0.6
|
|
112
|
+
&& minorityNuls / pairs <= 0.1
|
|
113
|
+
&& printablePartners / pairs >= 0.75
|
|
114
|
+
) {
|
|
115
|
+
return { encoding: oddNuls > evenNuls ? 'utf16le' : 'utf16be', bom: 0 };
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (!final && buffer.length < 4) return null;
|
|
119
|
+
return { encoding: 'utf8', bom: 0 };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export class ShellTextDecoder {
|
|
123
|
+
constructor() {
|
|
124
|
+
this.encoding = null;
|
|
125
|
+
this.decoder = null;
|
|
126
|
+
this.pending = Buffer.alloc(0);
|
|
127
|
+
this.byteRemainder = Buffer.alloc(0);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
_decode(buffer) {
|
|
131
|
+
if (this.encoding !== 'utf16be') return this.decoder.write(buffer);
|
|
132
|
+
const joined = this.byteRemainder.length > 0
|
|
133
|
+
? Buffer.concat([this.byteRemainder, buffer])
|
|
134
|
+
: buffer;
|
|
135
|
+
const evenLength = joined.length - (joined.length % 2);
|
|
136
|
+
this.byteRemainder = evenLength < joined.length ? joined.subarray(evenLength) : Buffer.alloc(0);
|
|
137
|
+
if (evenLength === 0) return '';
|
|
138
|
+
return this.decoder.write(Buffer.from(joined.subarray(0, evenLength)).swap16());
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
_start(final = false) {
|
|
142
|
+
const detected = detectShellTextEncoding(this.pending, final);
|
|
143
|
+
if (!detected) return '';
|
|
144
|
+
this.encoding = detected.encoding;
|
|
145
|
+
this.decoder = new StringDecoder(this.encoding === 'utf16be' ? 'utf16le' : this.encoding);
|
|
146
|
+
const buffered = this.pending.subarray(detected.bom);
|
|
147
|
+
this.pending = Buffer.alloc(0);
|
|
148
|
+
return this._decode(buffered);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
write(value) {
|
|
152
|
+
if (value == null || value.length === 0) return '';
|
|
153
|
+
if (typeof value === 'string') {
|
|
154
|
+
const prefix = this.encoding ? '' : this._start(true);
|
|
155
|
+
return prefix + value;
|
|
156
|
+
}
|
|
157
|
+
const buffer = Buffer.isBuffer(value) ? value : Buffer.from(value);
|
|
158
|
+
if (!this.encoding) {
|
|
159
|
+
this.pending = this.pending.length > 0 ? Buffer.concat([this.pending, buffer]) : buffer;
|
|
160
|
+
return this._start(false);
|
|
161
|
+
}
|
|
162
|
+
return this._decode(buffer);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
end() {
|
|
166
|
+
let text = this.encoding ? '' : this._start(true);
|
|
167
|
+
if (this.encoding === 'utf16be' && this.byteRemainder.length > 0) {
|
|
168
|
+
text += '\uFFFD';
|
|
169
|
+
this.byteRemainder = Buffer.alloc(0);
|
|
170
|
+
}
|
|
171
|
+
if (this.decoder) text += this.decoder.end();
|
|
172
|
+
return text;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
89
176
|
// Delegate process-tree termination to the native manager.
|
|
90
177
|
export function treeKill(child) {
|
|
91
178
|
if (!child?.__nativeSpawn || typeof child.kill !== 'function') return false;
|
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.1.
|
|
2
|
+
"version": "0.1.3",
|
|
3
3
|
"_comment": "Synced from immutable spawn-v release assets.",
|
|
4
4
|
"assets": {
|
|
5
5
|
"darwin-arm64": {
|
|
6
|
-
"url": "https://github.com/tribgames/mixdog/releases/download/spawn-v0.1.
|
|
7
|
-
"sha256": "
|
|
6
|
+
"url": "https://github.com/tribgames/mixdog/releases/download/spawn-v0.1.3/mixdog-spawn-darwin-arm64",
|
|
7
|
+
"sha256": "44b26d0a7bf2a1a32f3eaac9faff370540d1fa34bd1154e2dcbba283e6ee80da"
|
|
8
8
|
},
|
|
9
9
|
"darwin-x64": {
|
|
10
|
-
"url": "https://github.com/tribgames/mixdog/releases/download/spawn-v0.1.
|
|
11
|
-
"sha256": "
|
|
10
|
+
"url": "https://github.com/tribgames/mixdog/releases/download/spawn-v0.1.3/mixdog-spawn-darwin-x64",
|
|
11
|
+
"sha256": "bb20f01607aef01c62b7a7cac172b8f2493d7320aab1381ce1c18a366c9ace37"
|
|
12
12
|
},
|
|
13
13
|
"linux-arm64": {
|
|
14
|
-
"url": "https://github.com/tribgames/mixdog/releases/download/spawn-v0.1.
|
|
15
|
-
"sha256": "
|
|
14
|
+
"url": "https://github.com/tribgames/mixdog/releases/download/spawn-v0.1.3/mixdog-spawn-linux-arm64",
|
|
15
|
+
"sha256": "ba638ce7ada9f9602bc81ec2bc47095d978e97068fd659fbe33b281eab8c1993"
|
|
16
16
|
},
|
|
17
17
|
"linux-x64": {
|
|
18
|
-
"url": "https://github.com/tribgames/mixdog/releases/download/spawn-v0.1.
|
|
19
|
-
"sha256": "
|
|
18
|
+
"url": "https://github.com/tribgames/mixdog/releases/download/spawn-v0.1.3/mixdog-spawn-linux-x64",
|
|
19
|
+
"sha256": "4ca31eeba80af2073776ffbf62f9fb9e59d48d55e120cd25deab02023a586558"
|
|
20
20
|
},
|
|
21
21
|
"win32-x64": {
|
|
22
|
-
"url": "https://github.com/tribgames/mixdog/releases/download/spawn-v0.1.
|
|
23
|
-
"sha256": "
|
|
22
|
+
"url": "https://github.com/tribgames/mixdog/releases/download/spawn-v0.1.3/mixdog-spawn-win32-x64.exe",
|
|
23
|
+
"sha256": "dd8f47b94d877958e5522619d561f6286af02cc7ee7e60a3e7d4102a6c516f40"
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
}
|