mixdog 0.9.96 → 0.9.97
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/README.md +0 -17
- package/package.json +8 -7
- package/scripts/build-tui.mjs +6 -6
- package/scripts/{channel-daemon-stub.mjs → daemon-stub.mjs} +18 -12
- package/src/defaults/skills/setup/SKILL.md +1 -1
- package/src/lib/keychain-cjs.cjs +1 -1
- package/src/lib/mixdog-debug.cjs +2 -3
- package/src/rules/lead/01-general.md +1 -2
- package/src/rules/lead/lead-tool.md +0 -3
- package/src/rules/shared/01-tool.md +11 -10
- package/src/runtime/agent/orchestrator/agent-trace-io.mjs +4 -13
- package/src/runtime/agent/orchestrator/mcp/client.mjs +81 -33
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +3 -6
- package/src/runtime/agent/orchestrator/providers/registry.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/agent-loop.mjs +8 -6
- package/src/runtime/agent/orchestrator/session/compact.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +22 -4
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +20 -2
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +4 -3
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +40 -25
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +2 -3
- package/src/runtime/agent/orchestrator/session/manager/prefetch-bridge.mjs +12 -6
- package/src/runtime/agent/orchestrator/session/manager/session-lock.mjs +38 -2
- package/src/runtime/agent/orchestrator/session/store/summary-cache.mjs +10 -3
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +59 -42
- package/src/runtime/agent/orchestrator/session/tool-batch.mjs +3 -2
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +12 -22
- package/src/runtime/agent/orchestrator/tools/builtin/binary-file.mjs +92 -19
- package/src/runtime/agent/orchestrator/tools/builtin/fs-reachability.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +2 -0
- package/src/runtime/agent/orchestrator/tools/builtin/read-image.mjs +4 -4
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +80 -30
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +41 -22
- package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +6 -2
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +55 -11
- package/src/runtime/agent/orchestrator/tools/code-graph/search-references.mjs +12 -1
- package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +55 -18
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +1 -0
- package/src/runtime/agent/orchestrator/tools/env-scrub.mjs +1 -2
- package/src/runtime/agent/orchestrator/tools/lib/pwsh-standby-pool.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +67 -21
- package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +2 -1
- package/src/runtime/agent/orchestrator/tools/patch.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +7 -1
- package/src/runtime/channels/lib/config.mjs +33 -33
- package/src/runtime/channels/lib/inbound-handler.mjs +7 -7
- package/src/runtime/channels/lib/interaction-handlers.mjs +6 -6
- package/src/runtime/channels/lib/memory-client.mjs +31 -157
- package/src/runtime/channels/lib/output-forwarder.mjs +16 -16
- package/src/runtime/channels/lib/owned-runtime.mjs +60 -60
- package/src/runtime/channels/lib/owner-heartbeat.mjs +1 -1
- package/src/runtime/channels/lib/{backend-dispatch.mjs → provider-dispatch.mjs} +6 -6
- package/src/runtime/channels/lib/runtime-paths.mjs +4 -30
- package/src/runtime/channels/lib/status-snapshot.mjs +3 -3
- package/src/runtime/channels/lib/tool-dispatch.mjs +4 -4
- package/src/runtime/channels/lib/worker-bootstrap.mjs +1 -1
- package/src/runtime/channels/lib/worker-ipc.mjs +7 -7
- package/src/runtime/channels/lib/worker-main.mjs +38 -38
- package/src/runtime/channels/{backends → providers}/discord-access.mjs +1 -1
- package/src/runtime/channels/{backends → providers}/discord-gateway.mjs +1 -1
- package/src/runtime/channels/{backends → providers}/discord.mjs +3 -3
- package/src/runtime/channels/{backends → providers}/telegram.mjs +12 -12
- package/src/runtime/channels/tool-defs.mjs +1 -1
- package/src/runtime/memory/index.mjs +72 -211
- package/src/runtime/memory/lib/agent-ipc.mjs +9 -8
- package/src/runtime/memory/lib/cycle-llm-adapters.mjs +2 -2
- package/src/runtime/memory/lib/http-router.mjs +1 -6
- package/src/runtime/memory/lib/memory-config-flags.mjs +1 -1
- package/src/runtime/memory/lib/memory-cycle2-shared.mjs +4 -1
- package/src/runtime/memory/lib/memory-daemon-lifecycle.mjs +5 -0
- package/src/runtime/memory/lib/memory-log.mjs +1 -1
- package/src/runtime/memory/lib/memory-process-lock.mjs +5 -60
- package/src/runtime/memory/lib/memory.mjs +1 -1
- package/src/runtime/memory/lib/session-ingest.mjs +1 -1
- package/src/runtime/search/index.mjs +2 -2
- package/src/runtime/shared/abort-race.mjs +70 -0
- package/src/runtime/shared/child-spawn-gate.mjs +101 -63
- package/src/runtime/shared/config.mjs +11 -24
- package/src/runtime/shared/llm/index.mjs +1 -1
- package/src/runtime/shared/owner-fair-gate.mjs +134 -0
- package/src/runtime/shared/resource-admission.mjs +6 -5
- package/src/runtime/shared/tool-execution-owner.mjs +12 -0
- package/src/runtime/shared/tool-result-summary.mjs +1 -1
- package/src/runtime/shared/tool-surface.mjs +91 -22
- package/src/runtime/shared/tool-workload-gates.mjs +29 -0
- package/src/runtime/shared/turn-snapshot.mjs +17 -2
- package/src/session-runtime/channel-config-api.mjs +5 -5
- package/src/session-runtime/config-lifecycle.mjs +27 -27
- package/src/session-runtime/lifecycle-api.mjs +22 -3
- package/src/session-runtime/memory-runtime-prewarm.mjs +15 -0
- package/src/session-runtime/prewarm.mjs +1 -1
- package/src/session-runtime/provider-models.mjs +1 -1
- package/src/session-runtime/remote-control.mjs +6 -7
- package/src/session-runtime/runtime-core.mjs +52 -28
- package/src/session-runtime/self-update.mjs +1 -1
- package/src/session-runtime/session-lifecycle.mjs +36 -15
- package/src/session-runtime/session-title.mjs +1 -1
- package/src/session-runtime/session-turn-api.mjs +55 -15
- package/src/session-runtime/settings-api.mjs +7 -7
- package/src/standalone/agent-dispatch-broker.mjs +1 -1
- package/src/standalone/channel-admin.mjs +42 -53
- package/src/standalone/{channel-daemon-client.mjs → channel-client.mjs} +16 -18
- package/src/standalone/{channel-daemon-transport.mjs → channel-transport.mjs} +45 -46
- package/src/standalone/channel-worker.mjs +124 -647
- package/src/standalone/{backend-daemon.mjs → daemon.mjs} +187 -132
- package/src/standalone/memory-runtime-proxy.mjs +58 -1
- package/src/standalone/{engine-daemon-client.mjs → session-client.mjs} +151 -86
- package/src/standalone/session-protocol.mjs +8 -4
- package/src/standalone/session-runtime-pool.mjs +393 -0
- package/src/standalone/session-runtime-record.mjs +20 -0
- package/src/standalone/session-runtime-worker.mjs +203 -0
- package/src/standalone/{engine-daemon-service.mjs → session-service.mjs} +170 -157
- package/src/standalone/{engine-daemon-transport.mjs → session-transport.mjs} +139 -61
- package/src/standalone/session-wire.mjs +55 -0
- package/src/tui/App.jsx +4 -4
- package/src/tui/app/app-format.mjs +1 -1
- package/src/tui/app/channel-pickers.mjs +41 -41
- package/src/tui/app/input-parsers.mjs +2 -2
- package/src/tui/app/onboarding-steps.mjs +2 -2
- package/src/tui/app/project-picker.mjs +8 -8
- package/src/tui/app/prompt-submit.mjs +8 -8
- package/src/tui/app/settings-picker.mjs +25 -25
- package/src/tui/app/use-prompt-queue-history.mjs +1 -1
- package/src/tui/app/use-transcript-scroll.mjs +1 -1
- package/src/tui/components/ToolExecution.jsx +1 -1
- package/src/tui/components/prompt-input/restore-policy.mjs +1 -1
- package/src/tui/components/tool-execution/surface-detail.mjs +1 -1
- package/src/tui/dist/index.mjs +154 -127
- package/src/tui/hooks/useSession.mjs +16 -0
- package/src/tui/index.jsx +11 -11
- package/src/tui/{engine → session}/agent-envelope.mjs +3 -3
- package/src/tui/{engine → session}/agent-job-feed.mjs +4 -4
- package/src/tui/{engine → session}/boot-profile.mjs +2 -2
- package/src/tui/{engine → session}/context-state.mjs +3 -3
- package/src/tui/{engine → session}/labels.mjs +2 -2
- package/src/tui/{engine → session}/live-share.mjs +7 -7
- package/src/tui/{engine → session}/notice-text.mjs +2 -2
- package/src/tui/{engine → session}/notification-plan.mjs +3 -3
- package/src/tui/session/oauth-flows.mjs +140 -0
- package/src/tui/{engine → session}/prompt-history.mjs +3 -3
- package/src/tui/{engine → session}/queue-helpers.mjs +2 -2
- package/src/tui/{engine → session}/render-timing.mjs +2 -2
- package/src/tui/{engine → session}/session-api-ext.mjs +30 -17
- package/src/tui/{engine → session}/session-api.mjs +7 -60
- package/src/tui/{engine → session}/session-flow.mjs +1 -1
- package/src/tui/{engine → session}/session-stats.mjs +3 -3
- package/src/tui/{engine → session}/tool-approval.mjs +3 -3
- package/src/tui/{engine → session}/tool-call-fields.mjs +2 -2
- package/src/tui/{engine → session}/tool-card-results.mjs +4 -4
- package/src/tui/{engine → session}/tool-result-status.mjs +5 -5
- package/src/tui/{engine → session}/tool-result-text.mjs +3 -3
- package/src/tui/{engine → session}/transcript-spill.mjs +34 -34
- package/src/tui/{engine → session}/turn.mjs +29 -107
- package/src/tui/{engine-local-session.mjs → session-local.mjs} +59 -75
- package/src/tui/session.mjs +22 -0
- package/src/ui/statusline-segments.mjs +1 -1
- package/src/ui/statusline.mjs +1 -1
- package/vendor/ink/build/components/App.js +10 -1
- package/src/session-runtime/memory-daemon-probe.mjs +0 -61
- package/src/standalone/engine-daemon-protocol.mjs +0 -32
- package/src/tui/engine/turn-watchdog.mjs +0 -92
- package/src/tui/engine.mjs +0 -22
- package/src/tui/hooks/useEngine.mjs +0 -16
- /package/src/runtime/agent/orchestrator/session/compact/{engine.mjs → runner.mjs} +0 -0
- /package/src/runtime/channels/{backends → providers}/discord-attachments.mjs +0 -0
- /package/src/tui/{engine → session}/agent-response-tail.mjs +0 -0
- /package/src/tui/{engine → session}/frame-batched-store.mjs +0 -0
- /package/src/tui/{engine → session}/tui-steering-persist.mjs +0 -0
|
@@ -144,8 +144,8 @@ function restorePatchRollbackState(snapshots, readStateScope) {
|
|
|
144
144
|
}
|
|
145
145
|
|
|
146
146
|
// Apply one "wave" (a set of unique-target parsed entries) via the native
|
|
147
|
-
// (+ JS out-of-base) split. Returns {
|
|
148
|
-
// {
|
|
147
|
+
// (+ JS out-of-base) split. Returns { executor, text } on success or
|
|
148
|
+
// { executor, error } so the caller decides whether earlier waves already
|
|
149
149
|
// committed to disk. Extracted verbatim from the inline applyWave closure so
|
|
150
150
|
// both the default wave loop and sequence mode share identical apply
|
|
151
151
|
// semantics.
|
|
@@ -156,7 +156,7 @@ async function applyParsedWave({ parsed: wparsed, entries: wentries, headerRewri
|
|
|
156
156
|
const parsedInside = (wparsed || []).filter(
|
|
157
157
|
(entry) => !isResolvedPathOutsideBase(parsedEntryResolvedPath(entry, basePath), basePath),
|
|
158
158
|
);
|
|
159
|
-
const
|
|
159
|
+
const executor = outsideEntries.length > 0
|
|
160
160
|
? (insideEntries.length > 0 ? 'native+js-patch' : 'js-patch')
|
|
161
161
|
: 'native-patch';
|
|
162
162
|
const resultParts = [];
|
|
@@ -173,7 +173,7 @@ async function applyParsedWave({ parsed: wparsed, entries: wentries, headerRewri
|
|
|
173
173
|
signal: abortSignal,
|
|
174
174
|
parsed: parsedInside,
|
|
175
175
|
});
|
|
176
|
-
if (isPatchErrorText(nativeResult)) return {
|
|
176
|
+
if (isPatchErrorText(nativeResult)) return { executor, error: nativeResult };
|
|
177
177
|
resultParts.push(nativeResult);
|
|
178
178
|
}
|
|
179
179
|
if (outsideEntries.length > 0) {
|
|
@@ -187,10 +187,10 @@ async function applyParsedWave({ parsed: wparsed, entries: wentries, headerRewri
|
|
|
187
187
|
fuzzy,
|
|
188
188
|
readStateScope,
|
|
189
189
|
});
|
|
190
|
-
if (isPatchErrorText(jsResult)) return {
|
|
190
|
+
if (isPatchErrorText(jsResult)) return { executor, error: jsResult };
|
|
191
191
|
resultParts.push(jsResult);
|
|
192
192
|
}
|
|
193
|
-
return {
|
|
193
|
+
return { executor, text: resultParts.join('\n') };
|
|
194
194
|
}
|
|
195
195
|
|
|
196
196
|
// Default ordered section mode. Apply each file section in listed order,
|
|
@@ -384,7 +384,7 @@ async function applyPatchSequence(patchStr, requestedFormat, basePath, ctx) {
|
|
|
384
384
|
const skipped = [];
|
|
385
385
|
let failed = null;
|
|
386
386
|
let failedIndex = -1;
|
|
387
|
-
let
|
|
387
|
+
let executor = 'native-patch';
|
|
388
388
|
for (let i = 0; i < units.length; i++) {
|
|
389
389
|
const unit = units[i];
|
|
390
390
|
if (failed) { skipped.push(unit.displayPath); continue; }
|
|
@@ -421,7 +421,7 @@ async function applyPatchSequence(patchStr, requestedFormat, basePath, ctx) {
|
|
|
421
421
|
}
|
|
422
422
|
}
|
|
423
423
|
const res = await applyParsedWave(wave, basePath, waveOpts);
|
|
424
|
-
|
|
424
|
+
executor = res.executor;
|
|
425
425
|
if (res.error) {
|
|
426
426
|
failed = { displayPath: unit.displayPath, error: res.error };
|
|
427
427
|
failedIndex = i;
|
|
@@ -459,7 +459,7 @@ async function applyPatchSequence(patchStr, requestedFormat, basePath, ctx) {
|
|
|
459
459
|
}
|
|
460
460
|
const head = `apply_patch: ${verb} ${units.length} section(s)`;
|
|
461
461
|
const body = (appliedTexts ? `${head}\n${appliedTexts}` : head) + dryNote + rejectedTail;
|
|
462
|
-
return wrapPatchMutationOutput(body, mutationPlan, {
|
|
462
|
+
return wrapPatchMutationOutput(body, mutationPlan, { executor });
|
|
463
463
|
}
|
|
464
464
|
const failMsg = failed.error.replace(/^Error:\s*/, '');
|
|
465
465
|
const rollbackErrors = (!dryRun && rollbackOnFailure)
|
|
@@ -499,7 +499,7 @@ async function applyPatchSequence(patchStr, requestedFormat, basePath, ctx) {
|
|
|
499
499
|
if (skipped.length > 0) {
|
|
500
500
|
lines.push(`--- skipped (not attempted): ${skipped.join(', ')} ---`);
|
|
501
501
|
}
|
|
502
|
-
return wrapPatchMutationOutput(lines.join('\n') + dryNote + rejectedTail, mutationPlan, {
|
|
502
|
+
return wrapPatchMutationOutput(lines.join('\n') + dryNote + rejectedTail, mutationPlan, { executor });
|
|
503
503
|
}));
|
|
504
504
|
}
|
|
505
505
|
|
|
@@ -723,9 +723,41 @@ function salvageShatteredV4APatchArgs(args) {
|
|
|
723
723
|
return cleaned;
|
|
724
724
|
}
|
|
725
725
|
|
|
726
|
+
function extractInlinePatchRoot(patchText) {
|
|
727
|
+
const text = String(patchText || '');
|
|
728
|
+
const match = /^(\*\*\* Begin Patch\r?\n)\*\*\* Root:[ \t]*(.*?)\r?\n/.exec(text);
|
|
729
|
+
if (!match) return { patch: text, root: null };
|
|
730
|
+
const root = String(match[2] || '').trim();
|
|
731
|
+
if (!root) throw new Error('apply_patch: "*** Root:" requires a containing directory');
|
|
732
|
+
return {
|
|
733
|
+
patch: `${match[1]}${text.slice(match[0].length)}`,
|
|
734
|
+
root,
|
|
735
|
+
};
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
function isFilesystemRootSpecifier(value, cwd) {
|
|
739
|
+
const raw = String(value || '').trim();
|
|
740
|
+
if (!raw) return false;
|
|
741
|
+
// Keep Windows semantics testable on every host and reject the ambiguous
|
|
742
|
+
// drive-relative spelling (`C:`) alongside an actual drive root.
|
|
743
|
+
if (/^[A-Za-z]:(?:[\\/]?)$/.test(raw)) return true;
|
|
744
|
+
const resolved = resolveBasePath(cwd, raw);
|
|
745
|
+
return pathDirname(resolved) === resolved;
|
|
746
|
+
}
|
|
747
|
+
|
|
726
748
|
async function apply_patch(args, cwd, options = {}) {
|
|
727
749
|
args = salvageShatteredV4APatchArgs(args);
|
|
728
|
-
|
|
750
|
+
let patchStr = salvageV4AOpening((typeof args?.patch === 'string' ? args.patch : '').replace(/^\uFEFF/, ''));
|
|
751
|
+
const inlineRoot = extractInlinePatchRoot(patchStr);
|
|
752
|
+
patchStr = inlineRoot.patch;
|
|
753
|
+
if (inlineRoot.root) {
|
|
754
|
+
const argumentRoot = typeof args?.root === 'string' ? args.root.trim() : '';
|
|
755
|
+
if (argumentRoot
|
|
756
|
+
&& pathResolve(resolveBasePath(cwd, argumentRoot)) !== pathResolve(resolveBasePath(cwd, inlineRoot.root))) {
|
|
757
|
+
throw new Error('apply_patch: root argument conflicts with the "*** Root:" directive');
|
|
758
|
+
}
|
|
759
|
+
args = { ...(args || {}), root: inlineRoot.root };
|
|
760
|
+
}
|
|
729
761
|
if (!patchStr.trim()) {
|
|
730
762
|
throw new Error('apply_patch: "patch" is required (unified diff or V4A patch string)');
|
|
731
763
|
}
|
|
@@ -761,7 +793,21 @@ async function apply_patch(args, cwd, options = {}) {
|
|
|
761
793
|
// a mis-scoped working directory can never silently rewrite another tree.
|
|
762
794
|
// Editing outside stays possible, but only as a deliberate act: name the
|
|
763
795
|
// root that contains those targets in the same call.
|
|
764
|
-
const
|
|
796
|
+
const explicitRoot = typeof args?.root === 'string' && args.root.trim() ? args.root.trim() : null;
|
|
797
|
+
if (explicitRoot && isFilesystemRootSpecifier(explicitRoot, cwd)) {
|
|
798
|
+
throw new Error(`apply_patch: refusing filesystem root as write root: ${normalizeOutputPath(explicitRoot)}`);
|
|
799
|
+
}
|
|
800
|
+
const writeRoot = resolveBasePath(cwd, explicitRoot ?? args?.base_path ?? null);
|
|
801
|
+
if (explicitRoot) {
|
|
802
|
+
try {
|
|
803
|
+
await assertPathReachable(writeRoot);
|
|
804
|
+
if (!statSync(writeRoot).isDirectory()) {
|
|
805
|
+
throw new Error(`not a directory — ${normalizeOutputPath(writeRoot)}`);
|
|
806
|
+
}
|
|
807
|
+
} catch (err) {
|
|
808
|
+
throw new Error(`apply_patch: invalid write root ${normalizeOutputPath(writeRoot)}: ${err?.message || String(err)}`);
|
|
809
|
+
}
|
|
810
|
+
}
|
|
765
811
|
const outsideTargets = [];
|
|
766
812
|
for (const rel of patchTargetPaths(patchStr, basePath)) {
|
|
767
813
|
const abs = isAbsolute(rel) ? pathResolve(rel) : pathResolve(basePath, rel);
|
|
@@ -773,7 +819,7 @@ async function apply_patch(args, cwd, options = {}) {
|
|
|
773
819
|
const more = shown.length > 3 ? ` (+${shown.length - 3} more)` : '';
|
|
774
820
|
throw new Error(
|
|
775
821
|
`apply_patch: ${shown.length} target(s) fall outside the write root ${normalizeOutputPath(writeRoot)}: ${head}${more}. `
|
|
776
|
-
+ 'Check the paths first; if
|
|
822
|
+
+ 'Check the paths first; if intended, set root (JSON) or add "*** Root: <containing directory>" after "*** Begin Patch" (freeform).',
|
|
777
823
|
);
|
|
778
824
|
}
|
|
779
825
|
const rejectPartial = args?.reject_partial !== false;
|
|
@@ -917,17 +963,17 @@ async function apply_patch(args, cwd, options = {}) {
|
|
|
917
963
|
if (v4aRenameOnly) {
|
|
918
964
|
const lines = formatV4ARenameSuccessLines(v4aRenameResults);
|
|
919
965
|
if (lines.length === 0) return 'Error: patch contained no applicable file sections';
|
|
920
|
-
return wrapPatchMutationOutput(`${lines.join('\n')}\n`, mutationPlan, {
|
|
966
|
+
return wrapPatchMutationOutput(`${lines.join('\n')}\n`, mutationPlan, { executor: 'v4a-rename' });
|
|
921
967
|
}
|
|
922
968
|
// Apply one wave (a set of unique targets) via applyParsedWave (native +
|
|
923
|
-
// JS split). Returns {
|
|
969
|
+
// JS split). Returns { executor, text } on success or { executor, error }.
|
|
924
970
|
const applyWave = (wave) => applyParsedWave(wave, basePath, { fuzz, rejectPartial, dryRun, fuzzy, readStateScope, abortSignal });
|
|
925
971
|
|
|
926
972
|
// Duplicate-target blocks were split into contiguous sequential groups
|
|
927
973
|
// (listed order preserved); apply them in order, each against the prior
|
|
928
974
|
// group's on-disk result.
|
|
929
975
|
const waveTexts = [];
|
|
930
|
-
let
|
|
976
|
+
let executor = 'native-patch';
|
|
931
977
|
// dry_run never writes, so a later group would validate against unchanged
|
|
932
978
|
// disk and false-fail on any block that depends on an earlier edit. Only
|
|
933
979
|
// the first group is validated under dry_run; the rest are reported as
|
|
@@ -935,9 +981,9 @@ async function apply_patch(args, cwd, options = {}) {
|
|
|
935
981
|
const groupCount = (dryRun && waveDispatch.length > 1) ? 1 : waveDispatch.length;
|
|
936
982
|
for (let w = 0; w < groupCount; w++) {
|
|
937
983
|
const res = await applyWave(waveDispatch[w]);
|
|
938
|
-
|
|
984
|
+
executor = res.executor;
|
|
939
985
|
if (res.error) {
|
|
940
|
-
if (w === 0) return wrapPatchMutationOutput(res.error, mutationPlan, {
|
|
986
|
+
if (w === 0) return wrapPatchMutationOutput(res.error, mutationPlan, { executor });
|
|
941
987
|
// A later group failed. rejectPartial makes each group all-or-nothing,
|
|
942
988
|
// so every block in groups 1..w is fully committed to disk and left in
|
|
943
989
|
// place. List them all so the caller knows the true on-disk state.
|
|
@@ -948,7 +994,7 @@ async function apply_patch(args, cwd, options = {}) {
|
|
|
948
994
|
'--- failing block ---',
|
|
949
995
|
failMsg,
|
|
950
996
|
].join('\n');
|
|
951
|
-
return wrapPatchMutationOutput(note, mutationPlan, {
|
|
997
|
+
return wrapPatchMutationOutput(note, mutationPlan, { executor });
|
|
952
998
|
}
|
|
953
999
|
waveTexts.push(res.text);
|
|
954
1000
|
}
|
|
@@ -970,9 +1016,9 @@ async function apply_patch(args, cwd, options = {}) {
|
|
|
970
1016
|
`hunk-level rejected (rejectPartial=false, V4A): ${rejectedV4AHunks.length}`,
|
|
971
1017
|
...rejectedV4AHunks.map((r) => ` REJECT ${r.file || '(unknown)'} — ${String(r.reason || '').split(';')[0].trim()}`),
|
|
972
1018
|
];
|
|
973
|
-
return wrapPatchMutationOutput(`${combined}\n${tail.join('\n')}`, mutationPlan, {
|
|
1019
|
+
return wrapPatchMutationOutput(`${combined}\n${tail.join('\n')}`, mutationPlan, { executor });
|
|
974
1020
|
}
|
|
975
|
-
return wrapPatchMutationOutput(combined, mutationPlan, {
|
|
1021
|
+
return wrapPatchMutationOutput(combined, mutationPlan, { executor });
|
|
976
1022
|
};
|
|
977
1023
|
|
|
978
1024
|
return withBuiltinPathLocks(_lockPaths, () =>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
const APPLY_PATCH_LARK_GRAMMAR = `start: begin_patch hunk+ end_patch
|
|
1
|
+
const APPLY_PATCH_LARK_GRAMMAR = `start: begin_patch root_line? hunk+ end_patch
|
|
2
2
|
begin_patch: "*** Begin Patch" LF
|
|
3
|
+
root_line: "*** Root: " filename LF
|
|
3
4
|
end_patch: "*** End Patch" LF?
|
|
4
5
|
|
|
5
6
|
hunk: add_hunk | delete_hunk | update_hunk
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
// ./patch/orchestrator.mjs — apply_patch orchestration + executePatchTool
|
|
21
21
|
// + replay capture + UI-diff side-channel.
|
|
22
22
|
//
|
|
23
|
-
//
|
|
23
|
+
// Executor is unchanged: NATIVE-ONLY dispatch to the mixdog-patch Rust engine,
|
|
24
24
|
// no JS apply fallback. Public exports below match the pre-split surface.
|
|
25
25
|
|
|
26
26
|
// Side-effect import: schedules the native-patch prewarm on module load, as
|
|
@@ -258,6 +258,7 @@ export function execShellCommand({
|
|
|
258
258
|
ownerSessionId,
|
|
259
259
|
backgroundOnTimeout,
|
|
260
260
|
promotedTimeoutMs = 0,
|
|
261
|
+
backgroundDeadlineMs = 0,
|
|
261
262
|
admission = resourceAdmission,
|
|
262
263
|
}) {
|
|
263
264
|
return new Promise(async (resolve) => {
|
|
@@ -791,7 +792,12 @@ export function execShellCommand({
|
|
|
791
792
|
const stdoutPath = taskOutput.spilled ? taskOutput.stdoutPath : null;
|
|
792
793
|
const stderrPath = taskOutput.spilled ? taskOutput.stderrPath : null;
|
|
793
794
|
let job = null;
|
|
794
|
-
const
|
|
795
|
+
const elapsedMs = Math.max(0, Date.now() - _startMs);
|
|
796
|
+
const adoptedTimeoutMs = reason === 'timeout'
|
|
797
|
+
? promotedTimeoutMs
|
|
798
|
+
: (backgroundDeadlineMs > 0
|
|
799
|
+
? Math.max(1, backgroundDeadlineMs - elapsedMs)
|
|
800
|
+
: 0);
|
|
795
801
|
try {
|
|
796
802
|
job = adoptForegroundShellJob({
|
|
797
803
|
command,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readFileSync, mkdirSync } from "fs";
|
|
2
2
|
import { join } from "path";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { DiscordProvider } from "../providers/discord.mjs";
|
|
4
|
+
import { TelegramProvider } from "../providers/telegram.mjs";
|
|
5
5
|
import {
|
|
6
6
|
readSection,
|
|
7
7
|
updateSection,
|
|
@@ -22,7 +22,7 @@ const DEFAULT_ACCESS = {
|
|
|
22
22
|
channels: {}
|
|
23
23
|
};
|
|
24
24
|
const DEFAULT_CONFIG = {
|
|
25
|
-
|
|
25
|
+
provider: "discord",
|
|
26
26
|
discord: { token: "" },
|
|
27
27
|
telegram: { token: "" },
|
|
28
28
|
access: DEFAULT_ACCESS,
|
|
@@ -40,18 +40,18 @@ function applyDefaults(config) {
|
|
|
40
40
|
return out;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
function
|
|
44
|
-
if (
|
|
45
|
-
return String(entry?.telegramChatId ||
|
|
43
|
+
function channelIdForProvider(entry = {}, provider = "discord") {
|
|
44
|
+
if (provider === "telegram") {
|
|
45
|
+
return String(entry?.telegramChatId || "");
|
|
46
46
|
}
|
|
47
|
-
return String(entry?.discordChannelId ||
|
|
47
|
+
return String(entry?.discordChannelId || "");
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
// Resolve the single active-
|
|
50
|
+
// Resolve the single active-provider channel id from the config's `channel`
|
|
51
51
|
// section. Disk config carries a single `channel` object only.
|
|
52
|
-
function resolveChannelId(raw = {},
|
|
52
|
+
function resolveChannelId(raw = {}, provider = "discord") {
|
|
53
53
|
const channel = raw.channel && typeof raw.channel === "object" ? raw.channel : null;
|
|
54
|
-
if (channel) return
|
|
54
|
+
if (channel) return channelIdForProvider(channel, provider);
|
|
55
55
|
return "";
|
|
56
56
|
}
|
|
57
57
|
|
|
@@ -88,20 +88,20 @@ async function loadConfig({ freshSecrets = false } = {}) {
|
|
|
88
88
|
if (discordTokenProblem) {
|
|
89
89
|
process.stderr.write(`mixdog: discord token ignored: ${discordTokenProblem}\n`);
|
|
90
90
|
}
|
|
91
|
-
// Single-
|
|
91
|
+
// Single-provider select: config.provider picks ONE of discord|telegram.
|
|
92
92
|
// Anything else falls back to the discord default.
|
|
93
|
-
const
|
|
93
|
+
const provider = raw.provider === "telegram" ? "telegram" : "discord";
|
|
94
94
|
const telegramToken = getTelegramToken();
|
|
95
|
-
const channelId = resolveChannelId(raw,
|
|
95
|
+
const channelId = resolveChannelId(raw, provider);
|
|
96
96
|
// The runtime reads only the resolved `channelId`; disk carries a single
|
|
97
97
|
// `channel` object, so spread `raw` directly.
|
|
98
98
|
return applyDefaults({
|
|
99
99
|
...DEFAULT_CONFIG,
|
|
100
100
|
...raw,
|
|
101
|
-
|
|
101
|
+
provider,
|
|
102
102
|
channelId,
|
|
103
103
|
discord: { ...DEFAULT_CONFIG.discord, ...(({ token: _, ...rest }) => rest)(raw.discord || {}), ...(discordToken && !discordTokenProblem ? { token: discordToken } : {}) },
|
|
104
|
-
// Merge the keychain-resolved telegram token (harmless when
|
|
104
|
+
// Merge the keychain-resolved telegram token (harmless when provider is
|
|
105
105
|
// discord; the secret never lands in the on-disk config either way).
|
|
106
106
|
telegram: { ...DEFAULT_CONFIG.telegram, ...(({ token: _t, ...rest }) => rest)(raw.telegram || {}), ...(telegramToken ? { token: telegramToken } : {}) },
|
|
107
107
|
access: {
|
|
@@ -117,7 +117,7 @@ async function loadConfig({ freshSecrets = false } = {}) {
|
|
|
117
117
|
updateSection("channels", () => DEFAULT_CONFIG);
|
|
118
118
|
process.stderr.write(
|
|
119
119
|
`mixdog: default channels config created in ${MIXDOG_CONFIG_PATH}
|
|
120
|
-
edit the active
|
|
120
|
+
edit the active provider channel id to connect.
|
|
121
121
|
`
|
|
122
122
|
);
|
|
123
123
|
return applyDefaults(DEFAULT_CONFIG);
|
|
@@ -125,7 +125,7 @@ async function loadConfig({ freshSecrets = false } = {}) {
|
|
|
125
125
|
throw err;
|
|
126
126
|
}
|
|
127
127
|
}
|
|
128
|
-
const
|
|
128
|
+
const HEADLESS_PROVIDER = {
|
|
129
129
|
name: "headless",
|
|
130
130
|
MAX_MESSAGE_LENGTH: 2000,
|
|
131
131
|
formatOutgoing(t) {
|
|
@@ -156,31 +156,31 @@ const HEADLESS_BACKEND = {
|
|
|
156
156
|
on() {
|
|
157
157
|
}
|
|
158
158
|
};
|
|
159
|
-
function
|
|
159
|
+
function createProvider(config) {
|
|
160
160
|
// Channels-module toggle is MESSAGING-ONLY: automation (scheduler/webhooks)
|
|
161
|
-
// keeps the worker alive, so a disabled module runs the headless
|
|
161
|
+
// keeps the worker alive, so a disabled module runs the headless provider
|
|
162
162
|
// instead of blocking the whole runtime.
|
|
163
163
|
if (config.enabled === false) {
|
|
164
164
|
process.stderr.write("mixdog: channels messaging disabled; channel runtime running in headless mode\n");
|
|
165
|
-
return
|
|
165
|
+
return HEADLESS_PROVIDER;
|
|
166
166
|
}
|
|
167
|
-
// Single-
|
|
168
|
-
// config.
|
|
169
|
-
if (config.
|
|
167
|
+
// Single-provider select: exactly one provider is constructed based on
|
|
168
|
+
// config.provider (discord|telegram). The two are mutually exclusive.
|
|
169
|
+
if (config.provider === "telegram") {
|
|
170
170
|
const telegramToken = getTelegramToken();
|
|
171
171
|
if (!telegramToken) {
|
|
172
172
|
process.stderr.write("mixdog: telegram bot not configured; channel runtime running in headless mode\n");
|
|
173
|
-
return
|
|
173
|
+
return HEADLESS_PROVIDER;
|
|
174
174
|
}
|
|
175
175
|
const tgStateDir = config.telegram?.stateDir ?? join(DATA_DIR, "telegram");
|
|
176
176
|
mkdirSync(tgStateDir, { recursive: true });
|
|
177
|
-
return new
|
|
177
|
+
return new TelegramProvider({
|
|
178
178
|
...config.telegram,
|
|
179
179
|
configPath: CONFIG_FILE,
|
|
180
180
|
access: config.access,
|
|
181
181
|
// Single-source channel setup: the main chat is auto-allowed inside
|
|
182
|
-
//
|
|
183
|
-
//
|
|
182
|
+
// TelegramProvider.loadAccess() so the configured Telegram target is
|
|
183
|
+
// enough for both inbound gating and outbound.
|
|
184
184
|
mainChannelId: config.channelId
|
|
185
185
|
}, tgStateDir);
|
|
186
186
|
}
|
|
@@ -189,19 +189,19 @@ function createBackend(config) {
|
|
|
189
189
|
if (discordTokenProblem) {
|
|
190
190
|
process.stderr.write(`mixdog: discord token ignored: ${discordTokenProblem}\n`);
|
|
191
191
|
}
|
|
192
|
-
if (config.
|
|
192
|
+
if (config.provider !== "discord" || !discordToken || discordTokenProblem) {
|
|
193
193
|
process.stderr.write("mixdog: discord bot not configured; channel runtime running in headless mode\n");
|
|
194
|
-
return
|
|
194
|
+
return HEADLESS_PROVIDER;
|
|
195
195
|
}
|
|
196
196
|
const stateDir = config.discord.stateDir ?? join(DATA_DIR, "discord");
|
|
197
197
|
mkdirSync(stateDir, { recursive: true });
|
|
198
|
-
return new
|
|
198
|
+
return new DiscordProvider({
|
|
199
199
|
...config.discord,
|
|
200
200
|
configPath: CONFIG_FILE,
|
|
201
201
|
access: config.access,
|
|
202
202
|
// Single-source channel setup: the main channel is auto-allowed inside
|
|
203
|
-
//
|
|
204
|
-
//
|
|
203
|
+
// DiscordProvider.loadAccess() so the configured Discord target is enough
|
|
204
|
+
// for both inbound and outbound — no separate access.channels entry.
|
|
205
205
|
mainChannelId: config.channelId
|
|
206
206
|
}, stateDir);
|
|
207
207
|
}
|
|
@@ -215,7 +215,7 @@ function loadProfileConfig() {
|
|
|
215
215
|
}
|
|
216
216
|
export {
|
|
217
217
|
DATA_DIR,
|
|
218
|
-
|
|
218
|
+
createProvider,
|
|
219
219
|
getDiscordToken,
|
|
220
220
|
getTelegramToken,
|
|
221
221
|
loadConfig,
|
|
@@ -7,14 +7,14 @@ import {
|
|
|
7
7
|
import { refreshActiveInstance } from "./runtime-paths.mjs";
|
|
8
8
|
import { isNetworkError, retryOnNetwork } from "./network-retry.mjs";
|
|
9
9
|
// Inbound message pipeline extracted from channels/index.mjs (behavior-
|
|
10
|
-
// preserving): serial inbound queue,
|
|
10
|
+
// preserving): serial inbound queue, provider.onMessage transcript (re)bind +
|
|
11
11
|
// steal logic, and handleInbound voice-transcription + parent notify. Bound to
|
|
12
12
|
// live runtime getters and shared primitives.
|
|
13
13
|
function isImageAttachment(contentType) {
|
|
14
14
|
return typeof contentType === "string" && contentType.toLowerCase().startsWith("image/");
|
|
15
15
|
}
|
|
16
16
|
export function createInboundHandler({
|
|
17
|
-
|
|
17
|
+
getProvider,
|
|
18
18
|
getConfig,
|
|
19
19
|
getBridgeRuntimeConnected,
|
|
20
20
|
getChannelBridgeActive,
|
|
@@ -55,7 +55,7 @@ const inboundQueue = (() => {
|
|
|
55
55
|
};
|
|
56
56
|
})();
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
getProvider().onMessage = (msg) => {
|
|
59
59
|
const receivedAtMs = Number.isFinite(msg.receivedAtMs) ? msg.receivedAtMs : Date.now();
|
|
60
60
|
const onMessageAtMs = Date.now();
|
|
61
61
|
const gateBlocked = (!getBridgeRuntimeConnected() || !getBridgeOwnershipSnapshot().owned)
|
|
@@ -72,7 +72,7 @@ getBackend().onMessage = (msg) => {
|
|
|
72
72
|
msg.__bridgeGateRetries = attempt;
|
|
73
73
|
msg.receivedAtMs = receivedAtMs;
|
|
74
74
|
setTimeout(() => {
|
|
75
|
-
try {
|
|
75
|
+
try { getProvider().onMessage?.(msg); } catch { /* best-effort */ }
|
|
76
76
|
}, 2000 * attempt)?.unref?.();
|
|
77
77
|
} else {
|
|
78
78
|
try { process.stderr.write(`mixdog: inbound message dropped after ${attempt - 1} ${gateBlocked} retries (channel=${msg.chatId}, id=${msg.messageId})\n`); } catch {}
|
|
@@ -242,7 +242,7 @@ getBackend().onMessage = (msg) => {
|
|
|
242
242
|
});
|
|
243
243
|
void (async () => {
|
|
244
244
|
try {
|
|
245
|
-
await
|
|
245
|
+
await getProvider().react(msg.chatId, msg.messageId, "\u{1F914}");
|
|
246
246
|
} catch {
|
|
247
247
|
}
|
|
248
248
|
await bindingDone;
|
|
@@ -271,7 +271,7 @@ async function handleInbound(msg, route, options = {}) {
|
|
|
271
271
|
const files = await retryOnNetwork(
|
|
272
272
|
// Short per-attempt timeout (vs the 180s default) bounds worst-case
|
|
273
273
|
// blockage of the serial inboundQueue on a bad voice message.
|
|
274
|
-
() =>
|
|
274
|
+
() => getProvider().downloadAttachment(msg.chatId, msg.messageId, { timeoutMs: 20_000 }),
|
|
275
275
|
{ label: "voice.download" }
|
|
276
276
|
);
|
|
277
277
|
// concurrency handled inside transcribeVoice queue; loop is sequential so last att wins
|
|
@@ -312,7 +312,7 @@ async function handleInbound(msg, route, options = {}) {
|
|
|
312
312
|
if (imageAtts.length > 0) {
|
|
313
313
|
try {
|
|
314
314
|
const files = (await retryOnNetwork(
|
|
315
|
-
() =>
|
|
315
|
+
() => getProvider().downloadAttachment(msg.chatId, msg.messageId, {
|
|
316
316
|
timeoutMs: 20_000,
|
|
317
317
|
filter: (a) => isImageAttachment(a.contentType),
|
|
318
318
|
}),
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import * as fs from "fs";
|
|
2
2
|
import * as path from "path";
|
|
3
3
|
// Discord permission-prompt + interaction/modal routing extracted from
|
|
4
|
-
// channels/index.mjs (behavior-preserving). Installs
|
|
4
|
+
// channels/index.mjs (behavior-preserving). Installs provider.onInteraction /
|
|
5
5
|
// onModalRequest handlers and the terminal tool-exec signal watcher; returns the
|
|
6
6
|
// pending-permission map + marker refresher for the worker IPC handler.
|
|
7
7
|
export function createInteractionHandlers({
|
|
8
|
-
|
|
8
|
+
getProvider,
|
|
9
9
|
getConfig,
|
|
10
10
|
getBridgeRuntimeConnected,
|
|
11
11
|
instanceId,
|
|
@@ -30,18 +30,18 @@ export function createInteractionHandlers({
|
|
|
30
30
|
RUNTIME_ROOT,
|
|
31
31
|
}) {
|
|
32
32
|
function editDiscordMessage(channelId, messageId, label) {
|
|
33
|
-
// Behavior-preserving: route through the
|
|
33
|
+
// Behavior-preserving: route through the getProvider() abstraction (which uses
|
|
34
34
|
// discord.js under the hood) instead of issuing a raw REST PATCH. Errors
|
|
35
35
|
// are swallowed to stderr to match the prior fire-and-forget shape — the
|
|
36
36
|
// call site never awaited the HTTPS request either.
|
|
37
37
|
if (!getDiscordToken()) return;
|
|
38
38
|
const text = `\u{1F510} **Permission Request** \u2014 ${label}`;
|
|
39
|
-
void
|
|
39
|
+
void getProvider().editMessage(channelId, messageId, text, { components: [] }).catch((err) => {
|
|
40
40
|
process.stderr.write(`mixdog: editDiscordMessage failed: ${err}
|
|
41
41
|
`);
|
|
42
42
|
});
|
|
43
43
|
}
|
|
44
|
-
|
|
44
|
+
getProvider().onModalRequest = async (rawInteraction) => {
|
|
45
45
|
if (!getBridgeRuntimeConnected() || !getBridgeOwnershipSnapshot().owned) {
|
|
46
46
|
refreshBridgeOwnershipSafe();
|
|
47
47
|
return;
|
|
@@ -155,7 +155,7 @@ try {
|
|
|
155
155
|
try { process.stderr.write(`mixdog channels: tool-exec signal watcher setup failed: ${err && err.message || err}\n`); } catch {}
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
-
|
|
158
|
+
getProvider().onInteraction = (interaction) => {
|
|
159
159
|
// Channel-route permission reply. Custom_id format: perm-ch-{request_id}-{allow|session|deny}.
|
|
160
160
|
// request_id is the 5-letter short ID CC generates via shortRequestId().
|
|
161
161
|
// Emit notifications/claude/channel/permission back to the MCP host; the race
|