mixdog 0.9.97 → 0.9.99
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 +9 -2
- package/src/rules/agent/30-explorer.md +43 -32
- package/src/runtime/agent/orchestrator/agent-trace.mjs +19 -5
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +30 -3
- package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +5 -3
- package/src/runtime/agent/orchestrator/providers/provider-catalog-cache.mjs +23 -1
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +44 -6
- package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/store/serialize.mjs +3 -6
- package/src/runtime/agent/orchestrator/tools/builtin/read-image-resize.mjs +103 -12
- package/src/runtime/agent/orchestrator/tools/builtin/read-special-files.mjs +10 -30
- package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +27 -84
- package/src/runtime/attachments/pdf-extract.mjs +83 -0
- package/src/runtime/attachments/store.mjs +515 -0
- package/src/runtime/attachments/store.test.mjs +188 -0
- package/src/runtime/channels/lib/interaction-handlers.mjs +1 -191
- package/src/runtime/channels/lib/runtime-paths.mjs +1 -4
- package/src/runtime/channels/lib/worker-ipc.mjs +0 -66
- package/src/runtime/channels/lib/worker-main.mjs +1 -20
- package/src/runtime/memory/lib/http-wire.mjs +39 -4
- package/src/runtime/shared/json-metrics.mjs +94 -0
- package/src/runtime/shared/turn-snapshot.mjs +13 -0
- package/src/runtime/shared/turn-worktree-snapshot.mjs +19 -0
- package/src/session-runtime/provider-models.mjs +11 -1
- package/src/session-runtime/quick-model-rows.mjs +38 -7
- package/src/session-runtime/quick-search-models.mjs +15 -17
- package/src/session-runtime/runtime-core.mjs +9 -4
- package/src/session-runtime/runtime-tunables.mjs +9 -6
- package/src/session-runtime/session-title.mjs +27 -3
- package/src/standalone/channel-transport.mjs +28 -12
- package/src/standalone/daemon.mjs +3 -0
- package/src/standalone/session-protocol.mjs +1 -0
- package/src/standalone/session-service.mjs +23 -1
- package/src/standalone/session-transport.mjs +3 -2
- package/src/tui/app/prompt-submit.mjs +7 -9
- package/src/tui/app/use-prompt-handlers.mjs +7 -3
- package/src/tui/dist/index.mjs +249 -43
- package/src/tui/paste-attachments.mjs +13 -7
- package/src/tui/prompt-history-store.mjs +52 -4
- package/src/tui/session/agent-job-feed.mjs +5 -1
- package/src/tui/session/live-share.mjs +21 -7
- package/src/tui/session/queue-helpers.mjs +6 -6
- package/src/tui/session/session-api-ext.mjs +12 -4
- package/src/tui/session/session-api.mjs +10 -1
- package/src/tui/session/session-flow.mjs +4 -2
- package/src/tui/session-local.mjs +15 -5
|
@@ -299,14 +299,20 @@ export function createLiveShare({
|
|
|
299
299
|
try { socket.destroy(); } catch { /* already gone */ }
|
|
300
300
|
});
|
|
301
301
|
attachLineReader(socket, (frame) => {
|
|
302
|
-
if (frame.t === 'submit' &&
|
|
302
|
+
if (frame.t === 'submit' && (
|
|
303
|
+
(typeof frame.text === 'string' && frame.text.trim())
|
|
304
|
+
|| typeof frame.prompt === 'string'
|
|
305
|
+
|| Array.isArray(frame.prompt)
|
|
306
|
+
)) {
|
|
303
307
|
// Optional submission metadata (additive, old viewers omit it): the
|
|
304
308
|
// originating surface's submission id must survive the pipe so its
|
|
305
309
|
// optimistic user row releases when the SAME id settles in the
|
|
306
310
|
// owner's transcript (user: 방금 친 메세지가 2개 남는다).
|
|
307
311
|
const id = typeof frame.id === 'string' && frame.id.trim() ? frame.id : undefined;
|
|
308
312
|
const submittedAt = Number(frame.submittedAt);
|
|
309
|
-
const
|
|
313
|
+
const prompt = frame.prompt ?? frame.text;
|
|
314
|
+
const delivered = onRemoteSubmit(prompt, {
|
|
315
|
+
...(frame.options && typeof frame.options === 'object' ? frame.options : {}),
|
|
310
316
|
...(id ? { id } : {}),
|
|
311
317
|
...(Number.isFinite(submittedAt) && submittedAt > 0
|
|
312
318
|
? { submittedAt: Math.round(submittedAt) }
|
|
@@ -660,16 +666,21 @@ export function createLiveShare({
|
|
|
660
666
|
ensure: ensureShare,
|
|
661
667
|
viewerConnected: () => clientUp,
|
|
662
668
|
waitForViewerSync,
|
|
663
|
-
sendSubmit(
|
|
669
|
+
sendSubmit(prompt, meta = null) {
|
|
664
670
|
if (!clientUp || !client) return false;
|
|
665
671
|
const ackId = `ack-${process.pid}-${Date.now()}-${(submitAckSeq += 1)}`;
|
|
666
672
|
const onUndelivered = typeof meta?.onUndelivered === 'function' ? meta.onUndelivered : null;
|
|
667
673
|
try {
|
|
668
674
|
const id = meta && meta.id != null && String(meta.id).trim() ? String(meta.id) : undefined;
|
|
669
675
|
const submittedAt = Number(meta?.submittedAt);
|
|
676
|
+
const displayText = String(meta?.displayText
|
|
677
|
+
?? meta?.options?.displayText
|
|
678
|
+
?? (typeof prompt === 'string' ? prompt : ''));
|
|
670
679
|
client.write(frameLine({
|
|
671
680
|
t: 'submit',
|
|
672
|
-
text:
|
|
681
|
+
text: displayText,
|
|
682
|
+
prompt,
|
|
683
|
+
...(meta?.options && typeof meta.options === 'object' ? { options: meta.options } : {}),
|
|
673
684
|
ack: ackId,
|
|
674
685
|
...(id ? { id } : {}),
|
|
675
686
|
...(Number.isFinite(submittedAt) && submittedAt > 0
|
|
@@ -723,9 +734,10 @@ export function createLiveShare({
|
|
|
723
734
|
* the SAME identity and the owner reuses it for the settled user row — which is
|
|
724
735
|
* what releases the submitting surface's optimistic bubble.
|
|
725
736
|
*/
|
|
726
|
-
export function forwardViewerSubmit({ text, options = {}, share, spool, pid = process.pid }) {
|
|
737
|
+
export function forwardViewerSubmit({ prompt = null, text, options = {}, share, spool, pid = process.pid }) {
|
|
727
738
|
const value = String(text || '').trim();
|
|
728
|
-
|
|
739
|
+
const content = prompt ?? value;
|
|
740
|
+
if (!value && !(Array.isArray(content) && content.length > 0)) return false;
|
|
729
741
|
// Reconcile first so a session that became attachable this event-loop turn
|
|
730
742
|
// takes the instant pipe path instead of the durable detour.
|
|
731
743
|
try { share.ensure?.(); } catch { /* durable fallback below */ }
|
|
@@ -736,9 +748,11 @@ export function forwardViewerSubmit({ text, options = {}, share, spool, pid = pr
|
|
|
736
748
|
// A pipe WRITE is not a delivery: the owner may refuse the prompt and the
|
|
737
749
|
// socket may die between write and read, so an unacknowledged submit is
|
|
738
750
|
// re-delivered through the spool — late, never lost.
|
|
739
|
-
if (share.sendSubmit(
|
|
751
|
+
if (share.sendSubmit(content, {
|
|
740
752
|
id: submissionId,
|
|
741
753
|
submittedAt: options.submittedAt,
|
|
754
|
+
displayText: value,
|
|
755
|
+
options,
|
|
742
756
|
onUndelivered: deliverDurably,
|
|
743
757
|
})) return true;
|
|
744
758
|
return deliverDurably();
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
parseAgentJob,
|
|
10
10
|
parseSyntheticAgentMessage,
|
|
11
11
|
} from './agent-envelope.mjs';
|
|
12
|
+
import { attachmentTextForPart, isAttachmentReference } from '../../runtime/attachments/store.mjs';
|
|
12
13
|
|
|
13
14
|
const QUEUE_PRIORITY = { now: 0, next: 1, later: 2 };
|
|
14
15
|
|
|
@@ -79,7 +80,7 @@ export function promptContentText(content) {
|
|
|
79
80
|
if (Array.isArray(content)) {
|
|
80
81
|
return content.map((part) => {
|
|
81
82
|
if (typeof part === 'string') return part;
|
|
82
|
-
if (part?.type === 'text') return part.text || '';
|
|
83
|
+
if (part?.type === 'text') return isAttachmentReference(part) ? attachmentTextForPart(part) : (part.text || '');
|
|
83
84
|
if (part?.type === 'image') return '[Image]';
|
|
84
85
|
if (part?.type === 'file') return '[File]';
|
|
85
86
|
return part?.text || '';
|
|
@@ -151,10 +152,9 @@ export function mergePastedImages(entries) {
|
|
|
151
152
|
return Object.keys(out).length > 0 ? out : null;
|
|
152
153
|
}
|
|
153
154
|
|
|
154
|
-
// Byte-free image metadata for the user transcript item.
|
|
155
|
-
//
|
|
156
|
-
//
|
|
157
|
-
// name/mime/size so previews can render without bloating state.
|
|
155
|
+
// Byte-free image metadata for the user transcript item. Prompt content carries
|
|
156
|
+
// only a content-addressed attachment ref after daemon intake; transcript items
|
|
157
|
+
// and desktop snapshots carry just name/mime/size.
|
|
158
158
|
export function promptContentImageMeta(content, pastedImages) {
|
|
159
159
|
const parts = Array.isArray(content) ? content.filter((part) => part?.type === 'image') : [];
|
|
160
160
|
if (parts.length === 0) return null;
|
|
@@ -168,7 +168,7 @@ export function promptContentImageMeta(content, pastedImages) {
|
|
|
168
168
|
id: meta?.id ?? null,
|
|
169
169
|
name: String(meta?.filename || `Image ${index + 1}`),
|
|
170
170
|
mimeType: String(part.mimeType || part.mediaType || meta?.mediaType || 'image/png'),
|
|
171
|
-
bytes: data.length,
|
|
171
|
+
bytes: Number(part.sizeBytes) || data.length,
|
|
172
172
|
};
|
|
173
173
|
});
|
|
174
174
|
}
|
|
@@ -584,9 +584,8 @@ export function createSessionApiB(bag) {
|
|
|
584
584
|
}
|
|
585
585
|
},
|
|
586
586
|
// Desktop composer image attach: run the SAME optional-sharp resize
|
|
587
|
-
// pipeline the TUI paste path uses
|
|
588
|
-
//
|
|
589
|
-
// payloads to the provider.
|
|
587
|
+
// pipeline the TUI paste path uses. The current provider selects the
|
|
588
|
+
// Anthropic 2000px/5MB profile or OpenAI 2048px/1536-patch profile.
|
|
590
589
|
resizeImage: async ({ data, mimeType = 'image/png', filename = '' } = {}) => {
|
|
591
590
|
const base64 = String(data || '');
|
|
592
591
|
if (!base64) throw new Error('resizeImage: image payload is required');
|
|
@@ -595,7 +594,10 @@ export function createSessionApiB(bag) {
|
|
|
595
594
|
const attachment = await imageAttachmentFromBuffer(
|
|
596
595
|
Buffer.from(base64, 'base64'),
|
|
597
596
|
String(mimeType || 'image/png'),
|
|
598
|
-
{
|
|
597
|
+
{
|
|
598
|
+
filename: String(filename || 'Pasted image'),
|
|
599
|
+
provider: routeState().provider || '',
|
|
600
|
+
},
|
|
599
601
|
);
|
|
600
602
|
return {
|
|
601
603
|
data: attachment.content,
|
|
@@ -647,6 +649,12 @@ export function createSessionApiB(bag) {
|
|
|
647
649
|
getTurnReviewDiff: async (options = {}) => {
|
|
648
650
|
return (await runtime.getTurnReviewDiff?.(options)) ?? { supported: false, files: [], patch: '' };
|
|
649
651
|
},
|
|
652
|
+
revertTurnReview: async () => {
|
|
653
|
+
if (typeof runtime.revertTurnReview !== 'function') {
|
|
654
|
+
throw new Error('Turn review revert is unavailable');
|
|
655
|
+
}
|
|
656
|
+
return await runtime.revertTurnReview();
|
|
657
|
+
},
|
|
650
658
|
revertTurnReviewFile: async (file) => {
|
|
651
659
|
if (typeof runtime.revertTurnReviewFile !== 'function') {
|
|
652
660
|
throw new Error('Turn review revert is unavailable');
|
|
@@ -8,6 +8,7 @@ import { createSessionApiB } from './session-api-ext.mjs';
|
|
|
8
8
|
import { buildDoctorReport } from '../app/doctor.mjs';
|
|
9
9
|
import { recomputePromptHistory } from './prompt-history.mjs';
|
|
10
10
|
import { appendPromptHistory, buildMergedPromptHistory, loadPromptHistory, promptHistoryKey } from '../prompt-history-store.mjs';
|
|
11
|
+
import { hydratePastedAttachments } from '../../runtime/attachments/store.mjs';
|
|
11
12
|
|
|
12
13
|
export function createSessionApi(bag) {
|
|
13
14
|
const api = { ...createSessionApiA(bag), ...createSessionApiB(bag) };
|
|
@@ -736,7 +737,15 @@ export function createSessionApiA(bag) {
|
|
|
736
737
|
} catch { /* best-effort */ }
|
|
737
738
|
}, 150);
|
|
738
739
|
pendingAfterAbortKick.unref?.();
|
|
739
|
-
|
|
740
|
+
const restored = hydratePastedAttachments(restorePastedImages, restorePastedTexts);
|
|
741
|
+
return {
|
|
742
|
+
aborted,
|
|
743
|
+
restoreText,
|
|
744
|
+
pastedImages: restored.pastedImages,
|
|
745
|
+
discardPastedImages,
|
|
746
|
+
pastedTexts: restored.pastedTexts,
|
|
747
|
+
discardPastedTexts,
|
|
748
|
+
};
|
|
740
749
|
},
|
|
741
750
|
};
|
|
742
751
|
}
|
|
@@ -6,6 +6,7 @@ import { resetAllStreamingMarkdownStablePrefixes } from '../markdown/streaming-m
|
|
|
6
6
|
import { createSessionStats } from './session-stats.mjs';
|
|
7
7
|
import { queuePriorityValue, defaultQueuePriority, isQueuedEntryEditable, isQueuedEntryVisible, isSlashQueuedEntry, notificationDisplayText, sessionActivityTimestamp, promptDisplayText, promptContentImageMeta, mergePromptContents, mergePastedImages, mergePastedTexts, callCommitCallbacks, STEERING_SUPPRESSED_DISPLAY } from './queue-helpers.mjs';
|
|
8
8
|
import { appendTuiSteeringPersist, dropTuiSteeringPersist, drainTuiSteeringPersist } from './tui-steering-persist.mjs';
|
|
9
|
+
import { hydratePastedAttachments } from '../../runtime/attachments/store.mjs';
|
|
9
10
|
|
|
10
11
|
export function createSessionFlow(bag) {
|
|
11
12
|
const {
|
|
@@ -560,12 +561,13 @@ export function createSessionFlow(bag) {
|
|
|
560
561
|
removeQueuedEntries(queued);
|
|
561
562
|
const queuedText = queued.map((item) => item.text).filter((text) => String(text || '').trim()).join('\n');
|
|
562
563
|
const combinedText = [queuedText, String(currentText || '')].filter((text) => text.trim()).join('\n');
|
|
564
|
+
const hydrated = hydratePastedAttachments(mergePastedImages(queued), mergePastedTexts(queued));
|
|
563
565
|
return {
|
|
564
566
|
count: queued.length,
|
|
565
567
|
ids: queued.map((item) => String(item.id || '')).filter(Boolean),
|
|
566
568
|
text: combinedText,
|
|
567
|
-
pastedImages:
|
|
568
|
-
pastedTexts:
|
|
569
|
+
pastedImages: hydrated.pastedImages,
|
|
570
|
+
pastedTexts: hydrated.pastedTexts,
|
|
569
571
|
};
|
|
570
572
|
}
|
|
571
573
|
|
|
@@ -111,8 +111,12 @@ import { createSessionApi } from './session/session-api.mjs';
|
|
|
111
111
|
import { createFrameBatchedStorePublisher } from './session/frame-batched-store.mjs';
|
|
112
112
|
import { createLiveShare, forwardViewerSubmit, liveSharePipePath } from './session/live-share.mjs';
|
|
113
113
|
import { displayModelName } from '../ui/model-display.mjs';
|
|
114
|
+
import { prewarmImageResizer } from '../runtime/agent/orchestrator/tools/builtin/read-image-resize.mjs';
|
|
114
115
|
|
|
115
116
|
const SESSION_RUNTIME_MODULE = '../mixdog-session-runtime.mjs';
|
|
117
|
+
// Start the native binding load with daemon startup rather than making the
|
|
118
|
+
// user's first image attachment pay the cold dynamic-import cost.
|
|
119
|
+
void prewarmImageResizer();
|
|
116
120
|
|
|
117
121
|
// The runtime graph is imported lazily, but that import (measured ~250ms) used
|
|
118
122
|
// to land inside the FIRST session runtime creation, which desktop performs while it
|
|
@@ -869,9 +873,13 @@ export async function createLocalSessionRuntime({
|
|
|
869
873
|
if (injected.length === 0) return;
|
|
870
874
|
for (const item of injected) {
|
|
871
875
|
if (!item || typeof item !== 'object' || (item.text == null && item.content == null)) continue;
|
|
872
|
-
const
|
|
876
|
+
const content = item.content ?? item.text;
|
|
873
877
|
const id = typeof item.id === 'string' && item.id.trim() ? item.id.trim() : undefined;
|
|
874
|
-
bag.enqueue(
|
|
878
|
+
bag.enqueue(content, {
|
|
879
|
+
...(item.options && typeof item.options === 'object' ? item.options : {}),
|
|
880
|
+
...(item.text ? { displayText: item.text } : {}),
|
|
881
|
+
...(id ? { id } : {}),
|
|
882
|
+
});
|
|
875
883
|
}
|
|
876
884
|
void bag.drain();
|
|
877
885
|
})().catch(() => { /* the watch/tick pair retries */ });
|
|
@@ -889,7 +897,7 @@ export async function createLocalSessionRuntime({
|
|
|
889
897
|
socketPathFor: (id) => liveSharePipePath(id, sessionPath(id)),
|
|
890
898
|
getPublishedState: () => publishedState,
|
|
891
899
|
listeners,
|
|
892
|
-
onRemoteSubmit: (
|
|
900
|
+
onRemoteSubmit: (prompt, meta) => {
|
|
893
901
|
// A refusal must be REPORTED, never silent. This session runtime can be unable to
|
|
894
902
|
// take a foreign prompt (disposed, or it became an attached viewer
|
|
895
903
|
// itself); swallowing it here is what made a submitted message vanish
|
|
@@ -900,8 +908,8 @@ export async function createLocalSessionRuntime({
|
|
|
900
908
|
// the settled user item then carry the id the submitting surface used
|
|
901
909
|
// for its optimistic row, so that row releases instead of duplicating.
|
|
902
910
|
const queued = bag.enqueue(
|
|
903
|
-
|
|
904
|
-
meta && meta
|
|
911
|
+
prompt,
|
|
912
|
+
meta && typeof meta === 'object' ? meta : {},
|
|
905
913
|
) !== false;
|
|
906
914
|
void bag.drain();
|
|
907
915
|
return queued;
|
|
@@ -951,6 +959,7 @@ export async function createLocalSessionRuntime({
|
|
|
951
959
|
if (!text) return { accepted: false };
|
|
952
960
|
return {
|
|
953
961
|
accepted: forwardViewerSubmit({
|
|
962
|
+
prompt,
|
|
954
963
|
text,
|
|
955
964
|
options,
|
|
956
965
|
share: liveShare,
|
|
@@ -960,6 +969,7 @@ export async function createLocalSessionRuntime({
|
|
|
960
969
|
content: prompt,
|
|
961
970
|
text,
|
|
962
971
|
id: submissionId,
|
|
972
|
+
options,
|
|
963
973
|
}) === true,
|
|
964
974
|
}),
|
|
965
975
|
};
|