opencode-matrixx-v2 1.1.0 → 1.2.0

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.
@@ -26,5 +26,14 @@ export declare function evaluateContextModeToolCompat(toolNames: string[]): Chec
26
26
  * those sources instead of warning.
27
27
  */
28
28
  export declare function evaluateContextModeCwd(server: unknown, source?: "mcp" | "local" | "registry" | string): CheckResult;
29
+ export declare const CONTEXT_MODE_VERSION_FLOOR = "2.1.0";
30
+ /**
31
+ * Pure floor check: major.minor numeric compare against 2.1.
32
+ * Prerelease suffixes (e.g. 2.1.0-next) are stripped before parsing;
33
+ * null/unparsable versions fail-open to false (never blocks doctor).
34
+ */
35
+ export declare function isContextModeVersionBelowFloor(version: string | null | undefined): boolean;
36
+ /** Pure evaluator for the discipline version floor. Below 2.1.0 warns. */
37
+ export declare function evaluateContextModeVersion(version: string | null | undefined): CheckResult;
29
38
  export declare const contextModeCwdCheck: DoctorCheck;
30
39
  export declare const contextModeCheck: DoctorCheck;
package/dist/cli.js CHANGED
@@ -7235,6 +7235,22 @@ function loadContextModeMcpServer() {
7235
7235
  }
7236
7236
  return;
7237
7237
  }
7238
+ var CONTEXT_MODE_VERSION_FLOOR = "2.1.0";
7239
+ function isContextModeVersionBelowFloor(version) {
7240
+ if (typeof version !== "string" || version.length === 0)
7241
+ return false;
7242
+ const core = version.replace(/^[vV]/, "").split("-")[0] ?? "";
7243
+ const parts = core.split(".");
7244
+ const major = Number(parts[0]);
7245
+ const minor = parts.length > 1 ? Number(parts[1]) : 0;
7246
+ if (!Number.isFinite(major) || !Number.isFinite(minor))
7247
+ return false;
7248
+ if (major < 2)
7249
+ return true;
7250
+ if (major > 2)
7251
+ return false;
7252
+ return minor < 1;
7253
+ }
7238
7254
  var contextModeCwdCheck = {
7239
7255
  name: "context-mode-cwd",
7240
7256
  category: "integrations",
@@ -7269,13 +7285,16 @@ Cache dir: ${cacheDir} ${hasCache ? "(exists)" : "(not found \u2014 will be crea
7269
7285
  const substituteLine = discipline.path ? `Substitute-File: ${discipline.path}${discipline.version ? ` (v${discipline.version})` : ""}` : "Substitute-File: not found \u2014 fallback table in use; enforcer treats grep/glob as unblocked";
7270
7286
  const ctxTools = isCtxToolsAvailable();
7271
7287
  const ctxToolsLine = ctxTools ? "Ctx-Tools: available" : "Ctx-Tools: unavailable \u2014 enforcer fail-opens (warn-only)";
7288
+ const belowFloor = isContextModeVersionBelowFloor(discipline.version);
7289
+ const floorLine = belowFloor ? `Version-Floor: v${discipline.version} below supported floor v${CONTEXT_MODE_VERSION_FLOOR} \u2014 upgrade context-mode to >=${CONTEXT_MODE_VERSION_FLOOR} (any 2.1.x)` : null;
7272
7290
  return {
7273
7291
  name: "context-mode-integration",
7274
- status: !discipline.path || !ctxTools ? "warn" : "pass",
7275
- message: `context-mode plugin registered${hasCache ? ", cache present" : " (cache not yet created)"}${discipline.version ? `, discipline v${discipline.version}` : ""}`,
7292
+ status: !discipline.path || !ctxTools || belowFloor ? "warn" : "pass",
7293
+ message: `context-mode plugin registered${hasCache ? ", cache present" : " (cache not yet created)"}${discipline.version ? `, discipline v${discipline.version}` : ""}${belowFloor ? ` (below floor v${CONTEXT_MODE_VERSION_FLOOR})` : ""}`,
7276
7294
  detail: `Cache: ${cacheDir}
7277
7295
  ${substituteLine}
7278
- ${ctxToolsLine}
7296
+ ${ctxToolsLine}${floorLine ? `
7297
+ ${floorLine}` : ""}
7279
7298
  Plan tools: plan_create/read/update/list/delete (unconditional)`
7280
7299
  };
7281
7300
  }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Surface disabled-MCP startup failures as a user-visible warning on the
3
+ * session-created path. Silent when the startup state holds no failures.
4
+ */
5
+ export declare function showMcpStartupNoticeIfAny(): Promise<void>;
package/dist/index.js CHANGED
@@ -33784,13 +33784,17 @@ async function registerCommandTransform(command, entries, deps) {
33784
33784
  }
33785
33785
 
33786
33786
  // src/mcps/context7.ts
33787
- var context7 = {
33788
- type: "remote",
33789
- url: "https://mcp.context7.com/mcp",
33790
- enabled: true,
33791
- headers: process.env.CONTEXT7_API_KEY ? { Authorization: `Bearer ${process.env.CONTEXT7_API_KEY}` } : undefined,
33792
- oauth: false
33793
- };
33787
+ function buildContext7Entry(input = {}) {
33788
+ const key = input.context7ApiKey;
33789
+ return {
33790
+ type: "remote",
33791
+ url: "https://mcp.context7.com/mcp",
33792
+ enabled: true,
33793
+ headers: key ? { Authorization: `Bearer ${key}` } : undefined,
33794
+ oauth: false
33795
+ };
33796
+ }
33797
+ var context7 = buildContext7Entry();
33794
33798
 
33795
33799
  // src/mcps/document-reader.ts
33796
33800
  var document_reader = {
@@ -33810,7 +33814,7 @@ function buildBuiltinMcpEntries(input) {
33810
33814
  const entries = {};
33811
33815
  const failures = [];
33812
33816
  if (!disabledMcps.includes("context7")) {
33813
- entries.context7 = context7;
33817
+ entries.context7 = buildContext7Entry({ context7ApiKey: input.context7ApiKey || undefined });
33814
33818
  }
33815
33819
  if (!disabledMcps.includes("document_reader")) {
33816
33820
  if (input.uvxAvailable) {
@@ -37962,7 +37966,7 @@ What is the user asking about?
37962
37966
 
37963
37967
  // src/skills/skills/document-reader.ts
37964
37968
  var DOCUMENT_READER_SKILL_NAME = "document-reader";
37965
- var DOCUMENT_READER_SKILL_DESCRIPTION = "Use when reading PDFs, extracting content from documents, or analyzing office files (DOCX, XLSX, PPTX) \u2014 document conversion via document_reader MCP convert_to_markdown supporting file: and https: URIs for local and remote files. Related: ulw-research, dev-browser.";
37969
+ var DOCUMENT_READER_SKILL_DESCRIPTION = "Use when reading PDFs, extracting content from documents, or analyzing office files (DOCX, XLSX, PPTX) \u2014 document conversion via document_reader MCP document_reader__convert_to_markdown supporting file: and https: URIs for local and remote files. Related: ulw-research, dev-browser.";
37966
37970
  var documentReaderSkill = {
37967
37971
  name: DOCUMENT_READER_SKILL_NAME,
37968
37972
  description: DOCUMENT_READER_SKILL_DESCRIPTION,
@@ -38009,7 +38013,7 @@ uri: "https://example.com/data.xlsx"
38009
38013
 
38010
38014
  ## MUST NOT DO
38011
38015
 
38012
- - Do NOT guess file contents \u2014 always call \`convert_to_markdown\` first
38016
+ - Do NOT guess file contents \u2014 always call \`document_reader__convert_to_markdown\` first
38013
38017
  - Do NOT use the Read tool for PDFs or Office files \u2014 it cannot parse binary formats
38014
38018
  - Do NOT pass a relative path like \`file://./report.pdf\` \u2014 always use absolute paths
38015
38019
  `
@@ -47681,7 +47685,7 @@ task(
47681
47685
  Research documentation and knowledge base files related to: <CURRENT_LEAD>
47682
47686
 
47683
47687
  Read AGENTS.md files, README.md, doc/ directories, and inline documentation.
47684
- For library/framework docs delegate via task(operator) for context7 resolve\u2192query (resolve-library-id then query-docs); do NOT call context7 or websearch directly.
47688
+ For library/framework docs delegate via task(operator) for context7 resolve\u2192query (context7_resolve-library-id then context7_query-docs); do NOT call context7 or websearch directly.
47685
47689
 
47686
47690
  OUTPUT: Structured findings from docs with source attribution.
47687
47691
  Flag any claims that contradict other sources.
@@ -56639,7 +56643,30 @@ var startupFailures = [];
56639
56643
  function setMcpStartupFailures(failures) {
56640
56644
  startupFailures = failures;
56641
56645
  }
56646
+ function getMcpStartupFailures() {
56647
+ return startupFailures;
56648
+ }
56642
56649
 
56650
+ // src/mcps/mcp-startup-notice.ts
56651
+ var MCP_UVX_INSTALL_FIX = "curl -LsSf https://astral.sh/uv/install.sh | sh";
56652
+ var MCP_UVX_WARM_FIX = "uvx --from markitdown-mcp markitdown-mcp --help";
56653
+ function buildFailureLine(failure) {
56654
+ if (failure.name === "document_reader") {
56655
+ return "[mcp-startup] document_reader MCP is disabled: uvx (uv package manager) not found in PATH, " + `so document_reader__convert_to_markdown is unavailable.
56656
+ ` + `\u2192 fix: Install uv/uvx: ${MCP_UVX_INSTALL_FIX}
56657
+ ` + `Then warm cache: ${MCP_UVX_WARM_FIX}`;
56658
+ }
56659
+ return `[mcp-startup] ${failure.name} MCP is disabled: ${failure.error}`;
56660
+ }
56661
+ function buildMcpStartupNotice(failures) {
56662
+ if (failures.length === 0)
56663
+ return;
56664
+ return failures.map(buildFailureLine).join(`
56665
+ `);
56666
+ }
56667
+ function getMcpStartupNotice() {
56668
+ return buildMcpStartupNotice(getMcpStartupFailures());
56669
+ }
56643
56670
  // src/plugin-handlers/mcp-config-handler.ts
56644
56671
  function captureUserDisabledMcps(userMcp) {
56645
56672
  const disabled = new Set;
@@ -56656,9 +56683,11 @@ async function applyMcpConfig(params) {
56656
56683
  const disabledMcps = params.pluginConfig.disabled_mcps ?? [];
56657
56684
  const userMcp = params.config.mcp;
56658
56685
  const userDisabledMcps = captureUserDisabledMcps(userMcp);
56686
+ const context7ApiKey = process.env.CONTEXT7_API_KEY || undefined;
56659
56687
  const { entries: builtinMcps, failures } = buildBuiltinMcpEntries({
56660
56688
  disabledMcps,
56661
- uvxAvailable: isCommandAvailable("uvx")
56689
+ uvxAvailable: isCommandAvailable("uvx"),
56690
+ context7ApiKey
56662
56691
  });
56663
56692
  const merged = {
56664
56693
  ...builtinMcps,
@@ -56678,6 +56707,9 @@ async function applyMcpConfig(params) {
56678
56707
  const summary = failures.map((f) => `${f.name}: ${f.error}`).join("; ");
56679
56708
  log(`[mcp-config] MCP startup issues detected: ${summary}`, failures);
56680
56709
  setMcpStartupFailures(failures);
56710
+ const notice = buildMcpStartupNotice(failures);
56711
+ if (notice)
56712
+ log(notice);
56681
56713
  }
56682
56714
  params.config.mcp = merged;
56683
56715
  }
@@ -72286,88 +72318,6 @@ function describeLastMessageParts(messages) {
72286
72318
  return textOf(last).length > 0 ? "text" : "none";
72287
72319
  }
72288
72320
 
72289
- // src/tools/delegate-task/sync-result-fetcher.ts
72290
- function messageText(msg) {
72291
- const textParts = contentOf(msg).filter((p) => p.type === "text" || p.type === "reasoning");
72292
- const raw = textParts.map((p) => p.text ?? "").filter(Boolean).join(`
72293
- `);
72294
- return raw.replace(/<analysis>[\s\S]*?<\/analysis>/gi, "").trim();
72295
- }
72296
- function messageFinalText(msg) {
72297
- const text = messageText(msg);
72298
- const trimmed = text.trim();
72299
- if (trimmed.startsWith("```") && trimmed.endsWith("```")) {
72300
- const inner = trimmed.slice(3, -3).trim();
72301
- const firstNewline = inner.indexOf(`
72302
- `);
72303
- if (firstNewline > 0 && firstNewline < 100) {
72304
- return inner.slice(firstNewline + 1).trim();
72305
- }
72306
- return inner;
72307
- }
72308
- return trimmed;
72309
- }
72310
- function extractTaggedDeliverable(messages, tag) {
72311
- const tagStart = `<${tag}>`;
72312
- const tagEnd = `</${tag}>`;
72313
- for (let i = messages.length - 1;i >= 0; i--) {
72314
- const msg = messages[i];
72315
- if (!isAssistant(msg))
72316
- continue;
72317
- const text = messageText(msg);
72318
- const startIdx = text.indexOf(tagStart);
72319
- if (startIdx === -1)
72320
- continue;
72321
- const endIdx = text.indexOf(tagEnd, startIdx + tagStart.length);
72322
- if (endIdx === -1)
72323
- continue;
72324
- return {
72325
- message: msg,
72326
- text: text.slice(startIdx + tagStart.length, endIdx).trim()
72327
- };
72328
- }
72329
- return;
72330
- }
72331
- async function fetchSyncResult(client, sessionID, anchorMessageCount, options) {
72332
- const messages = await fetchSessionMessages(client, sessionID);
72333
- const messagesAfterAnchor = anchorMessageCount !== undefined ? messages.slice(anchorMessageCount) : messages;
72334
- if (anchorMessageCount !== undefined && messagesAfterAnchor.length === 0) {
72335
- return {
72336
- ok: false,
72337
- error: `Session completed with no new messages after the anchor. Observed: ${describeMessageShape(messages)}. ${DIAGNOSTIC_CAUSES}
72338
-
72339
- Session ID: ${sessionID}`
72340
- };
72341
- }
72342
- if (options?.deliverableTag) {
72343
- const tagged = extractTaggedDeliverable(messagesAfterAnchor, options.deliverableTag);
72344
- if (tagged) {
72345
- const textContent = options.finalTextOnly ? messageFinalText(tagged.message) : tagged.text;
72346
- return { ok: true, textContent };
72347
- }
72348
- }
72349
- const assistantMessages = messagesAfterAnchor.filter((m) => isAssistant(m)).sort((a, b) => timeOf(b) - timeOf(a));
72350
- const lastMessage = assistantMessages[0];
72351
- if (anchorMessageCount !== undefined && !lastMessage) {
72352
- return {
72353
- ok: false,
72354
- error: `Session completed with no new assistant message. Observed: ${describeMessageShape(messagesAfterAnchor)}. ${DIAGNOSTIC_CAUSES}
72355
-
72356
- Session ID: ${sessionID}`
72357
- };
72358
- }
72359
- if (!lastMessage) {
72360
- return {
72361
- ok: false,
72362
- error: `No assistant response found. Observed: ${describeMessageShape(messages)}. ${DIAGNOSTIC_CAUSES}
72363
-
72364
- Session ID: ${sessionID}`
72365
- };
72366
- }
72367
- const textContent = options?.finalTextOnly ? messageFinalText(lastMessage) : messageText(lastMessage);
72368
- return { ok: true, textContent };
72369
- }
72370
-
72371
72321
  // src/shared/poll-timeout-outcome.ts
72372
72322
  var POLL_TIMEOUT_PREFIX = "Poll timeout reached";
72373
72323
  var LAST_ASSISTANT_TEXT_CAP = 2000;
@@ -72417,6 +72367,26 @@ function createdAtOf(message) {
72417
72367
  const created = timeOf(message);
72418
72368
  return created === 0 ? undefined : created;
72419
72369
  }
72370
+ function completedOf(message) {
72371
+ const completed = message?.time?.completed;
72372
+ return typeof completed === "number" && completed > 0 ? completed : undefined;
72373
+ }
72374
+ function isStreamingAssistant(message, messages) {
72375
+ if (!message || !isAssistant(message))
72376
+ return false;
72377
+ const hostPublishesCompletion = messages.some((candidate) => isAssistant(candidate) && completedOf(candidate) !== undefined);
72378
+ if (!hostPublishesCompletion)
72379
+ return false;
72380
+ return completedOf(message) === undefined;
72381
+ }
72382
+ function strippedAssistantText(message) {
72383
+ return textOf(message).replace(/<analysis>[\s\S]*?<\/analysis>/gi, "").trim();
72384
+ }
72385
+ function lastAssistantHasText(message) {
72386
+ if (!message)
72387
+ return false;
72388
+ return strippedAssistantText(message).length > 0;
72389
+ }
72420
72390
  function isSessionComplete(messages) {
72421
72391
  let lastUser;
72422
72392
  let lastAssistant;
@@ -72434,6 +72404,8 @@ function isSessionComplete(messages) {
72434
72404
  return false;
72435
72405
  if (NON_TERMINAL_FINISH_REASONS.has(finish))
72436
72406
  return false;
72407
+ if (isStreamingAssistant(lastAssistant, messages))
72408
+ return false;
72437
72409
  if (!lastUser)
72438
72410
  return false;
72439
72411
  const userTime = createdAtOf(lastUser);
@@ -72473,12 +72445,7 @@ function hasToolResultAfterAssistant(messages) {
72473
72445
  return userId > assistantId;
72474
72446
  }
72475
72447
  function hasAssistantContent(messages) {
72476
- return messages.some((m) => {
72477
- if (!isAssistant(m))
72478
- return false;
72479
- const stripped = textOf(m).replace(/<analysis>[\s\S]*?<\/analysis>/gi, "").trim();
72480
- return stripped.length > 0;
72481
- });
72448
+ return messages.some((m) => isAssistant(m) && strippedAssistantText(m).length > 0);
72482
72449
  }
72483
72450
  async function pollSyncSession(ctx, client, input) {
72484
72451
  const syncTiming = getTimingConfig();
@@ -72531,6 +72498,12 @@ Session ID: ${input.sessionID}`;
72531
72498
  break;
72532
72499
  }
72533
72500
  const lastAssistant = [...msgs].reverse().find((m) => isAssistant(m));
72501
+ if (isStreamingAssistant(lastAssistant, msgs)) {
72502
+ idleStallCount = 0;
72503
+ lastMsgCount = 0;
72504
+ stableIdlePolls = 0;
72505
+ continue;
72506
+ }
72534
72507
  const hasText = hasAssistantContent(msgs);
72535
72508
  if (!finishOf(lastAssistant) && hasText) {
72536
72509
  const elapsed = Date.now() - pollStart;
@@ -72585,7 +72558,7 @@ Session ID: ${input.sessionID}`;
72585
72558
  break;
72586
72559
  }
72587
72560
  }
72588
- if (lastAssistant && msgs.length > 0 && Date.now() - pollStart >= syncTiming.MIN_STABILITY_TIME_MS) {
72561
+ if (lastAssistant && msgs.length > 0 && Date.now() - pollStart >= syncTiming.MIN_STABILITY_TIME_MS && !isStreamingAssistant(lastAssistant, msgs) && lastAssistantHasText(lastAssistant)) {
72589
72562
  const currentMsgCount = msgs.length;
72590
72563
  if (currentMsgCount === lastMsgCount) {
72591
72564
  stableIdlePolls++;
@@ -72613,7 +72586,7 @@ Session ID: ${input.sessionID}`;
72613
72586
  let lastAssistantText;
72614
72587
  try {
72615
72588
  const timeoutMsgs = await fetchSessionMessages(client, input.sessionID);
72616
- const lastAssistant = [...timeoutMsgs].reverse().find((m) => isAssistant(m));
72589
+ const lastAssistant = [...timeoutMsgs].reverse().find((m) => isAssistant(m) && !isStreamingAssistant(m, timeoutMsgs));
72617
72590
  const textParts = contentOf(lastAssistant).filter((p) => p.type === "text" && p.text).map((p) => p.text);
72618
72591
  if (textParts.length > 0)
72619
72592
  lastAssistantText = textParts.join(`
@@ -72629,6 +72602,99 @@ Session ID: ${input.sessionID}`;
72629
72602
  });
72630
72603
  }
72631
72604
 
72605
+ // src/tools/delegate-task/sync-result-fetcher.ts
72606
+ function messageText(msg) {
72607
+ const textParts = contentOf(msg).filter((p) => p.type === "text" || p.type === "reasoning");
72608
+ const raw = textParts.map((p) => p.text ?? "").filter(Boolean).join(`
72609
+ `);
72610
+ return raw.replace(/<analysis>[\s\S]*?<\/analysis>/gi, "").trim();
72611
+ }
72612
+ function messageFinalText(msg) {
72613
+ const text = messageText(msg);
72614
+ const trimmed = text.trim();
72615
+ if (trimmed.startsWith("```") && trimmed.endsWith("```")) {
72616
+ const inner = trimmed.slice(3, -3).trim();
72617
+ const firstNewline = inner.indexOf(`
72618
+ `);
72619
+ if (firstNewline > 0 && firstNewline < 100) {
72620
+ return inner.slice(firstNewline + 1).trim();
72621
+ }
72622
+ return inner;
72623
+ }
72624
+ return trimmed;
72625
+ }
72626
+ function extractTaggedDeliverable(messages, tag) {
72627
+ const tagStart = `<${tag}>`;
72628
+ const tagEnd = `</${tag}>`;
72629
+ for (let i = messages.length - 1;i >= 0; i--) {
72630
+ const msg = messages[i];
72631
+ if (!isAssistant(msg))
72632
+ continue;
72633
+ const text = messageText(msg);
72634
+ const startIdx = text.indexOf(tagStart);
72635
+ if (startIdx === -1)
72636
+ continue;
72637
+ const endIdx = text.indexOf(tagEnd, startIdx + tagStart.length);
72638
+ if (endIdx === -1)
72639
+ continue;
72640
+ return {
72641
+ message: msg,
72642
+ text: text.slice(startIdx + tagStart.length, endIdx).trim()
72643
+ };
72644
+ }
72645
+ return;
72646
+ }
72647
+ async function fetchSyncResult(client, sessionID, anchorMessageCount, options) {
72648
+ const messages = await fetchSessionMessages(client, sessionID);
72649
+ const messagesAfterAnchor = anchorMessageCount !== undefined ? messages.slice(anchorMessageCount) : messages;
72650
+ if (anchorMessageCount !== undefined && messagesAfterAnchor.length === 0) {
72651
+ return {
72652
+ ok: false,
72653
+ error: `Session completed with no new messages after the anchor. Observed: ${describeMessageShape(messages)}. ${DIAGNOSTIC_CAUSES}
72654
+
72655
+ Session ID: ${sessionID}`
72656
+ };
72657
+ }
72658
+ const assistantMessages = messagesAfterAnchor.filter((m) => isAssistant(m)).sort((a, b) => timeOf(b) - timeOf(a));
72659
+ const newestAssistant = assistantMessages[0];
72660
+ const hostPublishesCompletion = messages.some((message) => isAssistant(message) && completedOf(message) !== undefined);
72661
+ if (hostPublishesCompletion && isStreamingAssistant(newestAssistant, messages)) {
72662
+ return {
72663
+ ok: false,
72664
+ error: `Session is still streaming its newest assistant message; the result is not complete yet. ${DIAGNOSTIC_CAUSES}
72665
+
72666
+ Session ID: ${sessionID}`
72667
+ };
72668
+ }
72669
+ const candidates = hostPublishesCompletion ? assistantMessages.filter((m) => completedOf(m) !== undefined) : assistantMessages;
72670
+ if (options?.deliverableTag) {
72671
+ const tagged = extractTaggedDeliverable(candidates, options.deliverableTag);
72672
+ if (tagged) {
72673
+ const textContent = options.finalTextOnly ? messageFinalText(tagged.message) : tagged.text;
72674
+ return { ok: true, textContent };
72675
+ }
72676
+ }
72677
+ const lastMessage = candidates.find((m) => messageText(m).length > 0) ?? candidates[0];
72678
+ if (anchorMessageCount !== undefined && !lastMessage) {
72679
+ return {
72680
+ ok: false,
72681
+ error: `Session completed with no new assistant message. Observed: ${describeMessageShape(messagesAfterAnchor)}. ${DIAGNOSTIC_CAUSES}
72682
+
72683
+ Session ID: ${sessionID}`
72684
+ };
72685
+ }
72686
+ if (!lastMessage) {
72687
+ return {
72688
+ ok: false,
72689
+ error: `No assistant response found. Observed: ${describeMessageShape(messages)}. ${DIAGNOSTIC_CAUSES}
72690
+
72691
+ Session ID: ${sessionID}`
72692
+ };
72693
+ }
72694
+ const textContent = options?.finalTextOnly ? messageFinalText(lastMessage) : messageText(lastMessage);
72695
+ return { ok: true, textContent };
72696
+ }
72697
+
72632
72698
  // src/tools/delegate-task/sync-continuation-deps.ts
72633
72699
  var syncContinuationDeps = {
72634
72700
  pollSyncSession,
@@ -72727,11 +72793,20 @@ Session ID: ${continuationID}`;
72727
72793
  return result.error;
72728
72794
  }
72729
72795
  const duration = formatDuration(startTime);
72796
+ const textContent = result.textContent.trim();
72797
+ if (textContent.length === 0) {
72798
+ return `Task returned no readable output (empty assistant text).
72799
+
72800
+ Resume the same session instead of spawning a new one:
72801
+ task(session_id="${continuationID}", prompt="...")
72802
+
72803
+ Session ID: ${continuationID}`;
72804
+ }
72730
72805
  return `Task continued and completed in ${duration}.
72731
72806
 
72732
72807
  ---
72733
72808
 
72734
- ${result.textContent || "(No text output)"}
72809
+ ${textContent}
72735
72810
 
72736
72811
  <task_metadata>
72737
72812
  session_id: ${continuationID}
@@ -73031,13 +73106,22 @@ async function executeSyncTask(args, ctx, executorCtx, parentContext, agentToUse
73031
73106
  }
73032
73107
  didSucceed = true;
73033
73108
  const duration = formatDuration(startTime);
73109
+ const textContent = result.textContent.trim();
73110
+ if (textContent.length === 0) {
73111
+ return `Task returned no readable output (empty assistant text).
73112
+
73113
+ Resume the same session instead of spawning a new one:
73114
+ task(session_id="${sessionID}", prompt="...")
73115
+
73116
+ Session ID: ${sessionID}`;
73117
+ }
73034
73118
  return `Task completed in ${duration}.
73035
73119
 
73036
73120
  Agent: ${agentToUse}${args.category ? ` (category: ${args.category})` : ""}
73037
73121
 
73038
73122
  ---
73039
73123
 
73040
- ${result.textContent || "(No text output)"}
73124
+ ${textContent}
73041
73125
 
73042
73126
  <task_metadata>
73043
73127
  session_id: ${sessionID}
@@ -75958,6 +76042,14 @@ async function updateAndShowConnectedProvidersCacheStatus(_ctx) {
75958
76042
  }
75959
76043
  }
75960
76044
 
76045
+ // src/hooks/auto-update-checker/hook/mcp-startup-toast.ts
76046
+ async function showMcpStartupNoticeIfAny() {
76047
+ const notice = getMcpStartupNotice();
76048
+ if (!notice)
76049
+ return;
76050
+ notify("warn", notice);
76051
+ }
76052
+
75961
76053
  // src/hooks/auto-update-checker/hook/model-cache-warning.ts
75962
76054
  async function showModelCacheWarningIfNeeded(_ctx) {
75963
76055
  if (isModelCacheAvailable())
@@ -76010,6 +76102,9 @@ v${latestVersion} available. Restart OpenCode to apply.` : "OpenCode is now on S
76010
76102
  return;
76011
76103
  hasChecked = true;
76012
76104
  setTimeout(async () => {
76105
+ showMcpStartupNoticeIfAny().catch((err) => {
76106
+ log("[auto-update-checker] MCP startup notice failed:", err);
76107
+ });
76013
76108
  const cachedVersion = getCachedVersion();
76014
76109
  const localDevVersion = getLocalDevVersion(ctx.directory);
76015
76110
  const displayVersion = localDevVersion ?? cachedVersion;
@@ -79254,7 +79349,7 @@ var BLOCKED_PATTERNS = [
79254
79349
  /\bpython3?\b[^|&;]*\.(pdf|docx?|xlsx?|pptx?|jpe?g|png|gif|webp|mp3|wav)/,
79255
79350
  /\bmarkitdown\b/
79256
79351
  ];
79257
- var READ_BLOCK_MESSAGE = "[document-reader-guard] BLOCKED: Use document_reader__convert_to_markdown (document_reader MCP, Microsoft MarkItDown) for binary documents \u2014 generic Read cannot parse them. " + "Call convert_to_markdown with an absolute URI like file:///absolute/path/to/report.pdf, then summarize \u2014 do not dump raw output. " + "For context isolation delegate to the construct subagent so the main context stays clean.";
79352
+ var READ_BLOCK_MESSAGE = "[document-reader-guard] BLOCKED: Use document_reader__convert_to_markdown (document_reader MCP, Microsoft MarkItDown) for binary documents \u2014 generic Read cannot parse them. " + "Call document_reader__convert_to_markdown with an absolute URI like file:///absolute/path/to/report.pdf, then summarize \u2014 do not dump raw output. " + "For context isolation delegate to the construct subagent so the main context stays clean.";
79258
79353
  var BASH_BLOCK_MESSAGE = "[document-reader-guard] BLOCKED: Do not read documents via bash CLIs (pdftotext, pdfinfo, pandoc, libreoffice, cat/head/tail, python fitz/pypdf/pdfminer/pdfplumber/docx/openpyxl, markitdown CLI). " + "Use document_reader__convert_to_markdown with file:///absolute/path instead \u2014 delegate to construct for summarization.";
79259
79354
 
79260
79355
  // src/hooks/document-reader-guard/hook.ts
@@ -92503,6 +92598,13 @@ async function guard(label, fn) {
92503
92598
  function asRecord8(value) {
92504
92599
  return value !== null && typeof value === "object" ? value : {};
92505
92600
  }
92601
+ function hideNativeSubagentTool(editor) {
92602
+ try {
92603
+ editor.remove("subagent");
92604
+ } catch {
92605
+ log("[matrixx] native subagent tool not present, skipping hide");
92606
+ }
92607
+ }
92506
92608
  async function registerDomainTools(input) {
92507
92609
  const { ctx, bridge, directory, worktree } = input;
92508
92610
  const tools = bridge.tool;
@@ -92548,6 +92650,7 @@ async function registerDomainTools(input) {
92548
92650
  tool.input = override.parameters;
92549
92651
  });
92550
92652
  }
92653
+ hideNativeSubagentTool(editor);
92551
92654
  }));
92552
92655
  return registration ? [registration] : [];
92553
92656
  }
@@ -1,3 +1,15 @@
1
+ export interface BuildContext7EntryInput {
2
+ context7ApiKey?: string;
3
+ }
4
+ export declare function buildContext7Entry(input?: BuildContext7EntryInput): {
5
+ type: "remote";
6
+ url: string;
7
+ enabled: boolean;
8
+ headers: {
9
+ Authorization: string;
10
+ } | undefined;
11
+ oauth: false;
12
+ };
1
13
  export declare const context7: {
2
14
  type: "remote";
3
15
  url: string;
@@ -10,10 +10,13 @@ import { type BuiltinMcpEntry } from "./mcp.transform";
10
10
  import { type McpCreationFailure } from "./mcp-validator";
11
11
  export type { McpTransformDomain, McpTransformEditor, RegisterMcpTransformResult, } from "./mcp.transform";
12
12
  export { type BuiltinMcpEntry, buildBuiltinMcpEntries, registerMcpTransform, toV2McpEntry } from "./mcp.transform";
13
+ export { buildMcpStartupNotice, getMcpStartupNotice, MCP_UVX_INSTALL_FIX, MCP_UVX_WARM_FIX } from "./mcp-startup-notice";
13
14
  export type { McpCreationFailure } from "./mcp-validator";
14
15
  export { type McpName, McpNameSchema } from "./types";
15
16
  export interface CreateBuiltinMcpsOptions {
16
17
  isCommandAvailable?: (command: string) => boolean;
18
+ /** Overrides `process.env.CONTEXT7_API_KEY`; resolved at the edge, never in the builder. */
19
+ context7ApiKey?: string;
17
20
  }
18
21
  export interface CreateBuiltinMcpsResult {
19
22
  mcps: Record<string, BuiltinMcpEntry>;
@@ -0,0 +1,5 @@
1
+ import type { McpCreationFailure } from "./mcp-validator";
2
+ export declare const MCP_UVX_INSTALL_FIX = "curl -LsSf https://astral.sh/uv/install.sh | sh";
3
+ export declare const MCP_UVX_WARM_FIX = "uvx --from markitdown-mcp markitdown-mcp --help";
4
+ export declare function buildMcpStartupNotice(failures: McpCreationFailure[]): string | undefined;
5
+ export declare function getMcpStartupNotice(): string | undefined;
@@ -23,6 +23,8 @@ export interface BuildBuiltinMcpEntriesInput {
23
23
  disabledMcps?: string[];
24
24
  /** Resolved by the caller (e.g. `isCommandAvailable("uvx")`); never spawned here. */
25
25
  uvxAvailable: boolean;
26
+ /** Resolved by the caller from `process.env.CONTEXT7_API_KEY` at the edge; never read here. */
27
+ context7ApiKey?: string;
26
28
  }
27
29
  export interface BuildBuiltinMcpEntriesResult {
28
30
  entries: Record<string, BuiltinMcpEntry>;
@@ -104,6 +104,9 @@ export interface DomainRegistration {
104
104
  * the empty task response). Join text parts; non-text parts contribute nothing.
105
105
  */
106
106
  export declare function hookOutputText(content: unknown): string | undefined;
107
+ export declare function hideNativeSubagentTool(editor: {
108
+ remove(id: string): void;
109
+ }): void;
107
110
  /**
108
111
  * Applies the assembled `Hooks` onto the V2 context.
109
112
  * Returns the registration set whose `dispose` tears everything down.
@@ -1,5 +1,20 @@
1
1
  import type { SessionMessage } from "../../shared/session-message";
2
2
  import type { NativeClient, ToolContextWithMetadata } from "./types";
3
+ /**
4
+ * `time.completed` from the flat message shape. The shared `session-message`
5
+ * surface is frozen (see tests/shared/no-v1-shape.test.ts), so this accessor
6
+ * lives with its only consumer instead of graduating into `shared/`.
7
+ */
8
+ export declare function completedOf(message: unknown): number | undefined;
9
+ /**
10
+ * OpenCode V2 publishes `time.completed` on assistant messages: the row is
11
+ * created when a step starts and completed when it ends, so an assistant
12
+ * message without `time.completed` is still streaming. Older payloads (and
13
+ * test corpora) never carry the field — when no assistant message in the
14
+ * observed set carries it, the host does not expose completion and every
15
+ * message must be treated as settled.
16
+ */
17
+ export declare function isStreamingAssistant(message: SessionMessage | undefined, messages: SessionMessage[]): boolean;
3
18
  export declare function isSessionComplete(messages: SessionMessage[]): boolean;
4
19
  export declare function hasToolResultAfterAssistant(messages: SessionMessage[]): boolean;
5
20
  export declare function hasAssistantContent(messages: SessionMessage[]): boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-matrixx-v2",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "The Best AI Agent Harness - Batteries-Included OpenCode Plugin with Multi-Model Orchestration, Parallel Background Agents, and Crafted LSP/AST Tools",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -78,7 +78,7 @@
78
78
  "@code-yeongyu/comment-checker": "^0.6.1",
79
79
  "@cucumber/gherkin": "^34.0.0",
80
80
  "@modelcontextprotocol/sdk": "^1.25.1",
81
- "context-mode": "2.0.0-next.24",
81
+ "context-mode": "2.1.*",
82
82
  "js-yaml": "^4.1.1",
83
83
  "jsonc-parser": "^3.3.1",
84
84
  "picomatch": "^4.0.2",