@cortexkit/opencode-magic-context 0.49.18 → 0.49.19
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/dist/auto-search-worker.js +6 -2
- package/dist/chunk-embedding-write-worker.js +2 -2
- package/dist/{context-m897gfp9.js → context-j31m2dv7.js} +16 -12
- package/dist/features/magic-context/dreamer/classify.d.ts +3 -0
- package/dist/features/magic-context/dreamer/classify.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/hidden-single-shot.d.ts +11 -0
- package/dist/features/magic-context/dreamer/hidden-single-shot.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/map-memories.d.ts +3 -0
- package/dist/features/magic-context/dreamer/map-memories.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/verify.d.ts +3 -0
- package/dist/features/magic-context/dreamer/verify.d.ts.map +1 -1
- package/dist/features/magic-context/migrations.d.ts.map +1 -1
- package/dist/features/magic-context/storage-clone.d.ts.map +1 -1
- package/dist/features/magic-context/storage-meta-persisted.d.ts +13 -0
- package/dist/features/magic-context/storage-meta-persisted.d.ts.map +1 -1
- package/dist/hooks/magic-context/auto-search-worker-evaluation.fixture.d.ts +2 -0
- package/dist/hooks/magic-context/auto-search-worker-evaluation.fixture.d.ts.map +1 -0
- package/dist/hooks/magic-context/hook.d.ts.map +1 -1
- package/dist/hooks/magic-context/lkg-recovery-prefix.d.ts +13 -0
- package/dist/hooks/magic-context/lkg-recovery-prefix.d.ts.map +1 -1
- package/dist/hooks/magic-context/system-date-refresh.d.ts +15 -2
- package/dist/hooks/magic-context/system-date-refresh.d.ts.map +1 -1
- package/dist/hooks/magic-context/system-prompt-hash.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform-postprocess-phase.d.ts +2 -0
- package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform.d.ts.map +1 -1
- package/dist/{index-936py2wm.js → index-3esx8tkf.js} +60 -26
- package/dist/{index-v8awdgvd.js → index-3h4dp9wb.js} +1 -1
- package/dist/{index-24jbw52d.js → index-4911rn0b.js} +3 -3
- package/dist/{index-qn7xstt2.js → index-63k48rv9.js} +1 -2
- package/dist/{index-rmwykbbb.js → index-9zcsy3hh.js} +16 -1
- package/dist/{index-r4nkrs05.js → index-c1p88z1y.js} +3 -3
- package/dist/{index-s7j8tvhv.js → index-dxmq29t1.js} +3 -3
- package/dist/{index-6s5941tm.js → index-edmpdxcw.js} +1 -1
- package/dist/{index-e4xv8890.js → index-jhghdnsm.js} +26 -12
- package/dist/{index-fgncb0ng.js → index-jrr131e0.js} +2 -2
- package/dist/{index-s1ex9y6p.js → index-rpxdjkm0.js} +165 -27
- package/dist/{index-x4njr8df.js → index-se4m2g6w.js} +1 -1
- package/dist/index.js +20 -16
- package/dist/{message-index-bounded-n1frdn85.js → message-index-bounded-zyc8h32y.js} +2 -2
- package/dist/{message-index-66jv6fh4.js → message-index-gtpqnj8w.js} +2 -2
- package/dist/migration-worker.js +2 -2
- package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
- package/dist/plugin/messages-transform.d.ts.map +1 -1
- package/dist/plugin/tool-registry.d.ts.map +1 -1
- package/dist/raw-ordinal-warmup-worker.js +3 -3
- package/dist/{read-session-chunk-v06g3vka.js → read-session-chunk-jjb48bt9.js} +1 -1
- package/dist/{recomp-orchestrator-ghpk2dtq.js → recomp-orchestrator-999jj4bc.js} +7 -7
- package/dist/{search-archive-4d53y23e.js → search-archive-6vm0e2f5.js} +2 -2
- package/dist/{search-nkf271d4.js → search-xsqk1bj4.js} +5 -5
- package/dist/shared/model-suggestion-retry.d.ts +30 -1
- package/dist/shared/model-suggestion-retry.d.ts.map +1 -1
- package/dist/shared/prompt-async-transport.d.ts.map +1 -1
- package/dist/tools/ctx-expand/rust-mode-tag.test-support.d.ts +15 -0
- package/dist/tools/ctx-expand/rust-mode-tag.test-support.d.ts.map +1 -0
- package/dist/tools/ctx-expand/tools.d.ts +11 -0
- package/dist/tools/ctx-expand/tools.d.ts.map +1 -1
- package/dist/{transform-postprocess-phase-zpgav0h7.js → transform-postprocess-phase-jj99r7z2.js} +7 -7
- package/dist/v2/auto-search-worker.js +6 -2
- package/dist/v2/chunk-embedding-write-worker.js +2 -2
- package/dist/v2/{context-f2xh7ba4.js → context-y5yamxak.js} +85 -33
- package/dist/v2/hooks/tools.d.ts.map +1 -1
- package/dist/v2/{message-index-48f7hw7a.js → message-index-8vz2pq76.js} +2 -2
- package/dist/v2/{message-index-bounded-w3hfm29z.js → message-index-bounded-spd8r7pj.js} +2 -2
- package/dist/v2/migration-worker.js +2 -2
- package/dist/v2/raw-ordinal-warmup-worker.js +3 -3
- package/dist/v2/{read-session-chunk-zpgmxwte.js → read-session-chunk-cwkxrmns.js} +1 -1
- package/dist/v2/{recomp-orchestrator-4vfxej9t.js → recomp-orchestrator-f6m9jr7p.js} +6 -6
- package/dist/v2/{search-archive-vv48s2aa.js → search-archive-wve3tq1k.js} +2 -2
- package/dist/v2/{search-kza414f0.js → search-hwg7jfbj.js} +5 -5
- package/dist/v2/{server-2ehj0bw7.js → server-2ar62dyx.js} +1 -1
- package/dist/v2/{server-qv7f0hhe.js → server-45q69hkq.js} +1 -1
- package/dist/v2/{server-bfpda1tb.js → server-6vdzn1td.js} +165 -27
- package/dist/v2/{server-xhab8jx8.js → server-9hx7g5jp.js} +16 -1
- package/dist/v2/{server-a2eqhkxr.js → server-bmymkx73.js} +2 -2
- package/dist/v2/{server-1nyybqan.js → server-gsxjv2q2.js} +3 -3
- package/dist/v2/{server-my7k4eds.js → server-hm38q7r0.js} +1 -1
- package/dist/v2/{server-gpvay0xz.js → server-xq7dfjke.js} +1 -2
- package/dist/v2/server.js +2 -2
- package/dist/v2/{wrapup-orchestrator-psqz7yh0.js → wrapup-orchestrator-embdz8gj.js} +6 -6
- package/dist/{wrapup-orchestrator-s9zc5b00.js → wrapup-orchestrator-whhbjmaz.js} +8 -8
- package/package.json +1 -1
- package/src/shared/model-suggestion-retry.test.ts +166 -0
- package/src/shared/model-suggestion-retry.ts +165 -14
- package/src/shared/prompt-async-transport.test.ts +107 -0
- package/src/shared/prompt-async-transport.ts +39 -0
|
@@ -645,3 +645,110 @@ test("session errors belonging to another child do not fail the run", async () =
|
|
|
645
645
|
await promptAsyncAndWaitForIdle(host.client, request(), { pollIntervalMs: 1 });
|
|
646
646
|
expect(host.polls()).toBe(1);
|
|
647
647
|
});
|
|
648
|
+
|
|
649
|
+
describe("prompt_async transport and the first-output deadline", () => {
|
|
650
|
+
/**
|
|
651
|
+
* A child session where each model answers its own way. "dead" leaves the
|
|
652
|
+
* empty assistant row the host writes before the provider responds and stays
|
|
653
|
+
* busy forever; "partial" streams a text part and then stays busy; "answer"
|
|
654
|
+
* settles with a final assistant message on the next poll.
|
|
655
|
+
*/
|
|
656
|
+
function modelHost(behaviours: Record<string, "dead" | "partial" | "answer">) {
|
|
657
|
+
const messages: Message[] = [];
|
|
658
|
+
let current: "dead" | "partial" | "answer" | null = null;
|
|
659
|
+
const sent: string[] = [];
|
|
660
|
+
const promptAsync = mock(
|
|
661
|
+
async (request: { body: { model?: { providerID: string; modelID: string } } }) => {
|
|
662
|
+
const model = request.body.model
|
|
663
|
+
? `${request.body.model.providerID}/${request.body.model.modelID}`
|
|
664
|
+
: "primary";
|
|
665
|
+
sent.push(model);
|
|
666
|
+
current = behaviours[model] ?? "answer";
|
|
667
|
+
messages.push({
|
|
668
|
+
info: { id: `msg_user_${messages.length}`, role: "user", time: { created: 1 } },
|
|
669
|
+
parts: [{ type: "text", text: "go" }],
|
|
670
|
+
});
|
|
671
|
+
messages.push({
|
|
672
|
+
info: {
|
|
673
|
+
id: `msg_assistant_${messages.length}`,
|
|
674
|
+
role: "assistant",
|
|
675
|
+
time: { created: 2 },
|
|
676
|
+
},
|
|
677
|
+
parts: current === "partial" ? [{ type: "text", text: "<classify>" }] : [],
|
|
678
|
+
});
|
|
679
|
+
return { data: undefined };
|
|
680
|
+
},
|
|
681
|
+
);
|
|
682
|
+
const abort = mock(async () => {
|
|
683
|
+
current = null;
|
|
684
|
+
return { data: true };
|
|
685
|
+
});
|
|
686
|
+
const status = mock(async () => {
|
|
687
|
+
if (current === "dead" || current === "partial")
|
|
688
|
+
return { data: { "ses-child": { type: "busy" } } };
|
|
689
|
+
if (current === "answer") {
|
|
690
|
+
const last = messages.at(-1);
|
|
691
|
+
if (last && !last.info.time.completed) {
|
|
692
|
+
last.info.time.completed = 3;
|
|
693
|
+
last.info.finish = "stop";
|
|
694
|
+
last.parts = [{ type: "text", text: "<classify></classify>" }];
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
return { data: {} };
|
|
698
|
+
});
|
|
699
|
+
const list = mock(async () => ({ data: messages.map((message) => ({ ...message })) }));
|
|
700
|
+
return {
|
|
701
|
+
client: { session: { promptAsync, abort, status, messages: list } } as never,
|
|
702
|
+
sent,
|
|
703
|
+
abort,
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
async function runChain(
|
|
708
|
+
host: ReturnType<typeof modelHost>,
|
|
709
|
+
timeoutMs: number,
|
|
710
|
+
firstOutputTimeoutMs: number,
|
|
711
|
+
) {
|
|
712
|
+
const transport = createPromptAsyncTransport(host.client, "ses-child", {
|
|
713
|
+
pollIntervalMs: 5,
|
|
714
|
+
});
|
|
715
|
+
return promptSyncWithValidatedOutputRetry(
|
|
716
|
+
host.client,
|
|
717
|
+
{
|
|
718
|
+
...request(),
|
|
719
|
+
body: {
|
|
720
|
+
model: { providerID: "opencode", modelID: "free-model" },
|
|
721
|
+
parts: [{ type: "text", text: "go" }],
|
|
722
|
+
},
|
|
723
|
+
},
|
|
724
|
+
{
|
|
725
|
+
transport,
|
|
726
|
+
timeoutMs,
|
|
727
|
+
firstOutputTimeoutMs,
|
|
728
|
+
fallbackModels: ["anthropic/claude-sonnet-5-5"],
|
|
729
|
+
fetchOutput: async () => "<classify></classify>",
|
|
730
|
+
validateOutput: (output: string) => output,
|
|
731
|
+
},
|
|
732
|
+
);
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
test("a model that never answers is cut at the deadline and the fallback completes", async () => {
|
|
736
|
+
const host = modelHost({ "opencode/free-model": "dead" });
|
|
737
|
+
const startedAt = Date.now();
|
|
738
|
+
|
|
739
|
+
const run = await runChain(host, 5_000, 60);
|
|
740
|
+
|
|
741
|
+
expect(run.attempt.label).toBe("anthropic/claude-sonnet-5-5");
|
|
742
|
+
expect(host.sent).toEqual(["opencode/free-model", "anthropic/claude-sonnet-5-5"]);
|
|
743
|
+
// The dead model's run is stopped on the host before the fallback is sent.
|
|
744
|
+
expect(host.abort).toHaveBeenCalledWith({ path: { id: "ses-child" } });
|
|
745
|
+
expect(Date.now() - startedAt).toBeLessThan(2_000);
|
|
746
|
+
});
|
|
747
|
+
|
|
748
|
+
test("a model that streamed part of an answer keeps its timeout and does not fall back", async () => {
|
|
749
|
+
const host = modelHost({ "opencode/free-model": "partial" });
|
|
750
|
+
|
|
751
|
+
await expect(runChain(host, 200, 40)).rejects.toThrow("prompt timed out after 200ms");
|
|
752
|
+
expect(host.sent).toEqual(["opencode/free-model"]);
|
|
753
|
+
});
|
|
754
|
+
});
|
|
@@ -11,6 +11,7 @@ import { sumTokensFromChildMessages } from "../features/magic-context/subagent-t
|
|
|
11
11
|
import { describeAssistantError } from "./assistant-message-extractor";
|
|
12
12
|
import { sessionLog } from "./logger";
|
|
13
13
|
import type { PromptArgs, PromptTransport } from "./model-suggestion-retry";
|
|
14
|
+
import { noteModelActivityMonitored, noteModelOutputReceived } from "./model-suggestion-retry";
|
|
14
15
|
|
|
15
16
|
type Client = ReturnType<typeof createOpencodeClient> | undefined;
|
|
16
17
|
|
|
@@ -202,6 +203,29 @@ function isTerminalAssistant(message: unknown): boolean {
|
|
|
202
203
|
);
|
|
203
204
|
}
|
|
204
205
|
|
|
206
|
+
/**
|
|
207
|
+
* True when an assistant row shows the model has answered: any token usage, or a
|
|
208
|
+
* non-empty text or reasoning part, or a tool call. A row the host creates before
|
|
209
|
+
* the provider responds carries none of these.
|
|
210
|
+
*/
|
|
211
|
+
function showsModelOutput(message: unknown): boolean {
|
|
212
|
+
if (infoOf(message).role !== "assistant") return false;
|
|
213
|
+
const tokens = sumTokensFromChildMessages([message]);
|
|
214
|
+
if (tokens.input + tokens.output + tokens.cacheRead + tokens.cacheWrite > 0) return true;
|
|
215
|
+
const parts = (message as { parts?: unknown } | null)?.parts;
|
|
216
|
+
if (!Array.isArray(parts)) return false;
|
|
217
|
+
return parts.some((part) => {
|
|
218
|
+
if (!part || typeof part !== "object") return false;
|
|
219
|
+
const { type, text } = part as { type?: unknown; text?: unknown };
|
|
220
|
+
if (type === "tool") return true;
|
|
221
|
+
return (
|
|
222
|
+
(type === "text" || type === "reasoning") &&
|
|
223
|
+
typeof text === "string" &&
|
|
224
|
+
text.trim().length > 0
|
|
225
|
+
);
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
205
229
|
function unwrapData(response: unknown): unknown {
|
|
206
230
|
if (response && typeof response === "object" && "data" in response) {
|
|
207
231
|
return (response as { data?: unknown }).data;
|
|
@@ -324,6 +348,10 @@ export async function promptAsyncAndWaitForIdle(
|
|
|
324
348
|
}
|
|
325
349
|
let sentAt = Date.now();
|
|
326
350
|
let sawBusy = false;
|
|
351
|
+
// From here on this wait reports whether the model has produced anything,
|
|
352
|
+
// which lets the model chain tell a silent model from a slow answer.
|
|
353
|
+
noteModelActivityMonitored(signal);
|
|
354
|
+
let sawOutput = false;
|
|
327
355
|
|
|
328
356
|
for (;;) {
|
|
329
357
|
await sleep(pollIntervalMs, signal);
|
|
@@ -341,6 +369,17 @@ export async function promptAsyncAndWaitForIdle(
|
|
|
341
369
|
}
|
|
342
370
|
const status = await readStatus(session, sessionId, dir, signal);
|
|
343
371
|
const messages = await readMessages(session, sessionId, dir, signal);
|
|
372
|
+
if (
|
|
373
|
+
!sawOutput &&
|
|
374
|
+
messages.some((message) => {
|
|
375
|
+
const id = messageId(message);
|
|
376
|
+
// Rows from before this send belong to an earlier attempt.
|
|
377
|
+
return id !== null && !initialBaseline.has(id) && showsModelOutput(message);
|
|
378
|
+
})
|
|
379
|
+
) {
|
|
380
|
+
sawOutput = true;
|
|
381
|
+
noteModelOutputReceived(signal);
|
|
382
|
+
}
|
|
344
383
|
if (budget) {
|
|
345
384
|
const latest = messages.at(-1);
|
|
346
385
|
const latestId = messageId(latest);
|