@adhdev/daemon-core 0.8.64 → 0.8.66

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/index.js CHANGED
@@ -458,6 +458,231 @@ var init_logger = __esm({
458
458
  }
459
459
  });
460
460
 
461
+ // src/providers/io-contracts.ts
462
+ function normalizeInputEnvelope(input) {
463
+ const normalized = normalizeInputEnvelopePayload(input);
464
+ const textFallback = normalized.textFallback ?? flattenInputParts(normalized.parts);
465
+ return {
466
+ parts: normalized.parts,
467
+ textFallback,
468
+ ...normalized.metadata ? { metadata: normalized.metadata } : {}
469
+ };
470
+ }
471
+ function normalizeMessageParts(content) {
472
+ if (typeof content === "string") return [{ type: "text", text: content }];
473
+ if (!Array.isArray(content)) {
474
+ if (content && typeof content === "object" && typeof content.text === "string") {
475
+ return [{ type: "text", text: String(content.text) }];
476
+ }
477
+ return [];
478
+ }
479
+ const parts = [];
480
+ for (const raw of content) {
481
+ if (typeof raw === "string") {
482
+ parts.push({ type: "text", text: raw });
483
+ continue;
484
+ }
485
+ if (!raw || typeof raw !== "object") continue;
486
+ const part = normalizeMessagePartObject(raw);
487
+ if (part) parts.push(part);
488
+ }
489
+ return parts;
490
+ }
491
+ function flattenMessageParts(parts) {
492
+ return parts.map((part) => {
493
+ if (part.type === "text") return part.text;
494
+ if (part.type === "resource") return part.resource.text || "";
495
+ return "";
496
+ }).filter((value) => value.length > 0).join("\n");
497
+ }
498
+ function normalizeInputEnvelopePayload(input) {
499
+ if (typeof input === "string") {
500
+ return { parts: [{ type: "text", text: input }], textFallback: input };
501
+ }
502
+ if (!input || typeof input !== "object") {
503
+ return { parts: [], textFallback: "" };
504
+ }
505
+ const record = input;
506
+ const nestedInput = record.input;
507
+ if (nestedInput && typeof nestedInput === "object") {
508
+ const nested = nestedInput;
509
+ return {
510
+ parts: normalizeInputParts(nested.parts ?? nested.prompt),
511
+ textFallback: typeof nested.textFallback === "string" ? nested.textFallback : void 0,
512
+ metadata: normalizeInputMetadata(nested.metadata)
513
+ };
514
+ }
515
+ const directText = typeof record.text === "string" ? record.text : typeof record.message === "string" ? record.message : void 0;
516
+ if (directText !== void 0) {
517
+ return { parts: [{ type: "text", text: directText }], textFallback: directText };
518
+ }
519
+ const directParts = normalizeInputParts(record.parts ?? record.prompt);
520
+ return {
521
+ parts: directParts,
522
+ textFallback: typeof record.textFallback === "string" ? record.textFallback : void 0,
523
+ metadata: normalizeInputMetadata(record.metadata)
524
+ };
525
+ }
526
+ function normalizeInputMetadata(value) {
527
+ if (!value || typeof value !== "object") return void 0;
528
+ const record = value;
529
+ const metadata = {};
530
+ if (record.source === "dashboard" || record.source === "shortcut_api" || record.source === "provider_script" || record.source === "session_replay") {
531
+ metadata.source = record.source;
532
+ }
533
+ if (typeof record.clientTimestamp === "number" && Number.isFinite(record.clientTimestamp)) {
534
+ metadata.clientTimestamp = record.clientTimestamp;
535
+ }
536
+ return Object.keys(metadata).length > 0 ? metadata : void 0;
537
+ }
538
+ function normalizeInputParts(value) {
539
+ if (!Array.isArray(value)) return [];
540
+ const parts = [];
541
+ for (const raw of value) {
542
+ if (typeof raw === "string") {
543
+ parts.push({ type: "text", text: raw });
544
+ continue;
545
+ }
546
+ if (!raw || typeof raw !== "object") continue;
547
+ const part = normalizeInputPartObject(raw);
548
+ if (part) parts.push(part);
549
+ }
550
+ return parts;
551
+ }
552
+ function normalizeInputPartObject(raw) {
553
+ const type = raw.type;
554
+ if (type === "text" && typeof raw.text === "string") {
555
+ return { type, text: raw.text };
556
+ }
557
+ if (type === "image" && typeof raw.mimeType === "string") {
558
+ return {
559
+ type,
560
+ mimeType: raw.mimeType,
561
+ ...typeof raw.uri === "string" ? { uri: raw.uri } : {},
562
+ ...typeof raw.data === "string" ? { data: raw.data } : {},
563
+ ...typeof raw.alt === "string" ? { alt: raw.alt } : {}
564
+ };
565
+ }
566
+ if (type === "audio" && typeof raw.mimeType === "string") {
567
+ return {
568
+ type,
569
+ mimeType: raw.mimeType,
570
+ ...typeof raw.uri === "string" ? { uri: raw.uri } : {},
571
+ ...typeof raw.data === "string" ? { data: raw.data } : {},
572
+ ...typeof raw.transcript === "string" ? { transcript: raw.transcript } : {}
573
+ };
574
+ }
575
+ if (type === "video" && typeof raw.mimeType === "string") {
576
+ return {
577
+ type,
578
+ mimeType: raw.mimeType,
579
+ ...typeof raw.uri === "string" ? { uri: raw.uri } : {},
580
+ ...typeof raw.data === "string" ? { data: raw.data } : {},
581
+ ...typeof raw.posterUri === "string" ? { posterUri: raw.posterUri } : {}
582
+ };
583
+ }
584
+ if (type === "resource" && typeof raw.uri === "string") {
585
+ return {
586
+ type,
587
+ uri: raw.uri,
588
+ ...typeof raw.mimeType === "string" ? { mimeType: raw.mimeType } : {},
589
+ ...typeof raw.name === "string" ? { name: raw.name } : {},
590
+ ...typeof raw.text === "string" ? { text: raw.text } : {},
591
+ ...typeof raw.data === "string" ? { data: raw.data } : {}
592
+ };
593
+ }
594
+ if (type === "resource_link" && typeof raw.uri === "string") {
595
+ return {
596
+ type: "resource",
597
+ uri: raw.uri,
598
+ ...typeof raw.mimeType === "string" ? { mimeType: raw.mimeType } : {},
599
+ ...typeof raw.name === "string" ? { name: raw.name } : {}
600
+ };
601
+ }
602
+ return null;
603
+ }
604
+ function normalizeMessagePartObject(raw) {
605
+ const type = raw.type;
606
+ if (type === "text" && typeof raw.text === "string") {
607
+ return { type, text: raw.text };
608
+ }
609
+ if (type === "image" && typeof raw.mimeType === "string") {
610
+ return {
611
+ type,
612
+ mimeType: raw.mimeType,
613
+ ...typeof raw.uri === "string" ? { uri: raw.uri } : {},
614
+ ...typeof raw.data === "string" ? { data: raw.data } : {}
615
+ };
616
+ }
617
+ if (type === "audio" && typeof raw.mimeType === "string") {
618
+ return {
619
+ type,
620
+ mimeType: raw.mimeType,
621
+ ...typeof raw.uri === "string" ? { uri: raw.uri } : {},
622
+ ...typeof raw.data === "string" ? { data: raw.data } : {},
623
+ ...typeof raw.transcript === "string" ? { transcript: raw.transcript } : {}
624
+ };
625
+ }
626
+ if (type === "video" && typeof raw.mimeType === "string") {
627
+ return {
628
+ type,
629
+ mimeType: raw.mimeType,
630
+ ...typeof raw.uri === "string" ? { uri: raw.uri } : {},
631
+ ...typeof raw.data === "string" ? { data: raw.data } : {},
632
+ ...typeof raw.posterUri === "string" ? { posterUri: raw.posterUri } : {}
633
+ };
634
+ }
635
+ if (type === "resource_link" && typeof raw.uri === "string" && typeof raw.name === "string") {
636
+ return {
637
+ type,
638
+ uri: raw.uri,
639
+ name: raw.name,
640
+ ...typeof raw.mimeType === "string" ? { mimeType: raw.mimeType } : {},
641
+ ...typeof raw.size === "number" ? { size: raw.size } : {}
642
+ };
643
+ }
644
+ if (type === "resource" && raw.resource && typeof raw.resource === "object") {
645
+ const resource = raw.resource;
646
+ if (typeof resource.uri !== "string") return null;
647
+ return {
648
+ type,
649
+ resource: {
650
+ uri: resource.uri,
651
+ ...typeof resource.mimeType === "string" || resource.mimeType === null ? { mimeType: resource.mimeType } : {},
652
+ ...typeof resource.text === "string" ? { text: resource.text } : {},
653
+ ...typeof resource.blob === "string" ? { blob: resource.blob } : {}
654
+ }
655
+ };
656
+ }
657
+ return null;
658
+ }
659
+ function flattenInputParts(parts) {
660
+ return parts.map((part) => {
661
+ if (part.type === "text") return part.text;
662
+ if (part.type === "audio") return part.transcript || "";
663
+ if (part.type === "resource") return part.text || "";
664
+ return "";
665
+ }).filter((value) => value.length > 0).join("\n");
666
+ }
667
+ var init_io_contracts = __esm({
668
+ "src/providers/io-contracts.ts"() {
669
+ "use strict";
670
+ }
671
+ });
672
+
673
+ // src/providers/contracts.ts
674
+ function flattenContent(content) {
675
+ if (typeof content === "string") return content;
676
+ return flattenMessageParts(normalizeMessageParts(content));
677
+ }
678
+ var init_contracts = __esm({
679
+ "src/providers/contracts.ts"() {
680
+ "use strict";
681
+ init_io_contracts();
682
+ init_io_contracts();
683
+ }
684
+ });
685
+
461
686
  // src/providers/chat-message-normalization.ts
462
687
  function canonicalizeKindHint(value) {
463
688
  return value.trim().toLowerCase().replace(/[\s-]+/g, "_");
@@ -608,39 +833,161 @@ var init_chat_message_normalization = __esm({
608
833
  }
609
834
  });
610
835
 
611
- // src/cli-adapters/terminal-backends/ghostty-vt-backend.ts
612
- function isModuleNotFoundError(error, ref) {
613
- if (!(error instanceof Error)) return false;
614
- const message = error.message || "";
615
- const code = "code" in error ? error.code : void 0;
616
- return code === "MODULE_NOT_FOUND" && message.includes(ref);
836
+ // src/providers/read-chat-contract.ts
837
+ function isPlainObject3(value) {
838
+ return !!value && typeof value === "object" && !Array.isArray(value);
617
839
  }
618
- function normalizeBinding(mod, ref) {
619
- const binding = mod?.default?.createTerminal ? mod.default : mod?.createTerminal ? mod : null;
620
- if (!binding) {
621
- throw new Error(`Ghostty VT binding "${ref}" does not export createTerminal()`);
840
+ function isFiniteNumber(value) {
841
+ return typeof value === "number" && Number.isFinite(value);
842
+ }
843
+ function validateStatus(status, source) {
844
+ if (typeof status !== "string" || !VALID_STATUSES.includes(status)) {
845
+ throw new Error(`${source}: status must be one of ${VALID_STATUSES.join(", ")}`);
622
846
  }
623
- return binding;
847
+ return status;
624
848
  }
625
- function getBindingCandidates() {
626
- const explicit = process.env.ADHDEV_GHOSTTY_VT_BINDING?.trim();
627
- return explicit ? [explicit] : DEFAULT_BINDING_CANDIDATES;
849
+ function validateRole(role, source, index) {
850
+ if (typeof role !== "string" || !VALID_ROLES.includes(role)) {
851
+ throw new Error(`${source}: messages[${index}].role must be one of ${VALID_ROLES.join(", ")}`);
852
+ }
853
+ return role;
628
854
  }
629
- function loadGhosttyVtBinding(required) {
630
- if (cachedBinding !== void 0) {
631
- if (!cachedBinding && required && cachedBindingError) {
632
- throw cachedBindingError;
855
+ function validateMessageContent(content, source, index) {
856
+ if (typeof content === "string") return content;
857
+ if (Array.isArray(content)) return normalizeMessageParts(content);
858
+ throw new Error(`${source}: messages[${index}].content must be a string or structured content array`);
859
+ }
860
+ function validateMessage(message, source, index) {
861
+ if (!isPlainObject3(message)) {
862
+ throw new Error(`${source}: messages[${index}] must be an object`);
863
+ }
864
+ const normalized = {
865
+ role: validateRole(message.role, source, index),
866
+ content: validateMessageContent(message.content, source, index)
867
+ };
868
+ if (typeof message.kind === "string") normalized.kind = message.kind;
869
+ if (typeof message.id === "string") normalized.id = message.id;
870
+ if (isFiniteNumber(message.index)) normalized.index = message.index;
871
+ if (isFiniteNumber(message.timestamp)) normalized.timestamp = message.timestamp;
872
+ if (isFiniteNumber(message.receivedAt)) normalized.receivedAt = message.receivedAt;
873
+ if (Array.isArray(message.toolCalls)) normalized.toolCalls = message.toolCalls;
874
+ if (isPlainObject3(message.meta)) normalized.meta = message.meta;
875
+ if (typeof message.senderName === "string") normalized.senderName = message.senderName;
876
+ if (typeof message._type === "string") normalized._type = message._type;
877
+ if (typeof message._sub === "string") normalized._sub = message._sub;
878
+ return normalized;
879
+ }
880
+ function validateModal(activeModal, status, source) {
881
+ if (activeModal == null) {
882
+ if (status === "waiting_approval") {
883
+ throw new Error(`${source}: waiting_approval status requires activeModal with buttons`);
633
884
  }
634
- return cachedBinding;
885
+ return activeModal === null ? null : void 0;
635
886
  }
636
- const errors = [];
637
- for (const ref of getBindingCandidates()) {
638
- try {
639
- const mod = require(ref);
640
- cachedBinding = normalizeBinding(mod, ref);
641
- cachedBindingError = null;
642
- return cachedBinding;
643
- } catch (error) {
887
+ if (!isPlainObject3(activeModal)) {
888
+ throw new Error(`${source}: activeModal must be an object when provided`);
889
+ }
890
+ if (typeof activeModal.message !== "string") {
891
+ throw new Error(`${source}: activeModal.message must be a string`);
892
+ }
893
+ if (!Array.isArray(activeModal.buttons) || activeModal.buttons.some((button) => typeof button !== "string" || !button.trim())) {
894
+ throw new Error(`${source}: activeModal.buttons must be a non-empty string array`);
895
+ }
896
+ const normalized = {
897
+ message: activeModal.message,
898
+ buttons: activeModal.buttons.map((button) => button.trim())
899
+ };
900
+ if (isFiniteNumber(activeModal.width)) normalized.width = activeModal.width;
901
+ if (isFiniteNumber(activeModal.height)) normalized.height = activeModal.height;
902
+ return normalized;
903
+ }
904
+ function validateControlValues(controlValues, source) {
905
+ if (controlValues === void 0) return void 0;
906
+ if (!isPlainObject3(controlValues)) {
907
+ throw new Error(`${source}: controlValues must be an object when provided`);
908
+ }
909
+ const normalized = {};
910
+ for (const [key, value] of Object.entries(controlValues)) {
911
+ if (typeof value !== "string" && typeof value !== "number" && typeof value !== "boolean") {
912
+ throw new Error(`${source}: controlValues.${key} must be string, number, or boolean`);
913
+ }
914
+ normalized[key] = value;
915
+ }
916
+ return normalized;
917
+ }
918
+ function validateReadChatResultPayload(raw, source = "read_chat") {
919
+ if (!isPlainObject3(raw)) {
920
+ throw new Error(`${source}: payload must be an object`);
921
+ }
922
+ const status = validateStatus(raw.status, source);
923
+ if (!Array.isArray(raw.messages)) {
924
+ throw new Error(`${source}: messages must be an array`);
925
+ }
926
+ const messages = raw.messages.map((message, index) => validateMessage(message, source, index));
927
+ const activeModal = validateModal(raw.activeModal, status, source);
928
+ const controlValues = validateControlValues(raw.controlValues, source);
929
+ const normalized = {
930
+ status,
931
+ messages
932
+ };
933
+ if (activeModal !== void 0) normalized.activeModal = activeModal;
934
+ if (typeof raw.id === "string") normalized.id = raw.id;
935
+ if (typeof raw.title === "string") normalized.title = raw.title;
936
+ if (typeof raw.agentType === "string") normalized.agentType = raw.agentType;
937
+ if (typeof raw.agentName === "string") normalized.agentName = raw.agentName;
938
+ if (typeof raw.extensionId === "string") normalized.extensionId = raw.extensionId;
939
+ if (typeof raw.inputContent === "string") normalized.inputContent = raw.inputContent;
940
+ if (typeof raw.isVisible === "boolean") normalized.isVisible = raw.isVisible;
941
+ if (typeof raw.isWelcomeScreen === "boolean") normalized.isWelcomeScreen = raw.isWelcomeScreen;
942
+ if (controlValues) normalized.controlValues = controlValues;
943
+ if (raw.summaryMetadata !== void 0) normalized.summaryMetadata = raw.summaryMetadata;
944
+ if (Array.isArray(raw.effects)) normalized.effects = raw.effects;
945
+ if (typeof raw.providerSessionId === "string") normalized.providerSessionId = raw.providerSessionId;
946
+ return normalized;
947
+ }
948
+ var VALID_STATUSES, VALID_ROLES;
949
+ var init_read_chat_contract = __esm({
950
+ "src/providers/read-chat-contract.ts"() {
951
+ "use strict";
952
+ init_contracts();
953
+ VALID_STATUSES = ["idle", "generating", "waiting_approval", "error", "panel_hidden", "streaming", "long_generating"];
954
+ VALID_ROLES = ["user", "assistant", "system", "human"];
955
+ }
956
+ });
957
+
958
+ // src/cli-adapters/terminal-backends/ghostty-vt-backend.ts
959
+ function isModuleNotFoundError(error, ref) {
960
+ if (!(error instanceof Error)) return false;
961
+ const message = error.message || "";
962
+ const code = "code" in error ? error.code : void 0;
963
+ return code === "MODULE_NOT_FOUND" && message.includes(ref);
964
+ }
965
+ function normalizeBinding(mod, ref) {
966
+ const binding = mod?.default?.createTerminal ? mod.default : mod?.createTerminal ? mod : null;
967
+ if (!binding) {
968
+ throw new Error(`Ghostty VT binding "${ref}" does not export createTerminal()`);
969
+ }
970
+ return binding;
971
+ }
972
+ function getBindingCandidates() {
973
+ const explicit = process.env.ADHDEV_GHOSTTY_VT_BINDING?.trim();
974
+ return explicit ? [explicit] : DEFAULT_BINDING_CANDIDATES;
975
+ }
976
+ function loadGhosttyVtBinding(required) {
977
+ if (cachedBinding !== void 0) {
978
+ if (!cachedBinding && required && cachedBindingError) {
979
+ throw cachedBindingError;
980
+ }
981
+ return cachedBinding;
982
+ }
983
+ const errors = [];
984
+ for (const ref of getBindingCandidates()) {
985
+ try {
986
+ const mod = require(ref);
987
+ cachedBinding = normalizeBinding(mod, ref);
988
+ cachedBindingError = null;
989
+ return cachedBinding;
990
+ } catch (error) {
644
991
  if (isModuleNotFoundError(error, ref)) {
645
992
  errors.push(`${ref}: module not found`);
646
993
  continue;
@@ -1463,6 +1810,7 @@ var init_provider_cli_adapter = __esm({
1463
1810
  init_pty_transport();
1464
1811
  init_provider_cli_shared();
1465
1812
  init_chat_message_normalization();
1813
+ init_read_chat_contract();
1466
1814
  init_provider_cli_parse();
1467
1815
  init_provider_cli_config();
1468
1816
  init_provider_cli_runtime();
@@ -2608,6 +2956,9 @@ var init_provider_cli_adapter = __esm({
2608
2956
  runtimeSettings: this.runtimeSettings
2609
2957
  });
2610
2958
  const parsed = this.cliScripts.parseOutput(input);
2959
+ if (parsed && typeof parsed === "object") {
2960
+ Object.assign(parsed, validateReadChatResultPayload(parsed, `${this.cliType} parseOutput`));
2961
+ }
2611
2962
  const refinedStatus = this.refineDetectedStatus(typeof parsed?.status === "string" ? parsed.status : null, input.recentBuffer, input.screenText);
2612
2963
  if (parsed && refinedStatus && parsed.status !== refinedStatus) {
2613
2964
  parsed.status = refinedStatus;
@@ -5199,351 +5550,142 @@ var CdpDomHandlers = class {
5199
5550
  * args:
5200
5551
  * ideType?: string — IDE type hint
5201
5552
  * sessionId?: string — agent webview session ID
5202
- */
5203
- async handleDomDebug(args) {
5204
- if (!this.getCdp()?.isConnected) return { success: false, error: "CDP not connected" };
5205
- const sessionId = args?.sessionId;
5206
- const expression = `(() => {
5207
- const result = {
5208
- url: location.href,
5209
- title: document.title,
5210
- viewport: { w: window.innerWidth, h: window.innerHeight },
5211
-
5212
- // Input field info
5213
- inputs: [],
5214
- // Textarea info
5215
- textareas: [],
5216
- // Contenteditable info
5217
- editables: [],
5218
- // Buttons (send, submit etc)
5219
- buttons: [],
5220
- // iframes (agent webviews)
5221
- iframes: [],
5222
- // role="textbox" info
5223
- textboxes: [],
5224
- };
5225
-
5226
- // Input fields
5227
- document.querySelectorAll('input[type="text"], input:not([type])').forEach((el, i) => {
5228
- if (i >= 10) return;
5229
- result.inputs.push({
5230
- tag: 'input',
5231
- id: el.id || null,
5232
- class: (el.className || '').toString().slice(0, 150),
5233
- placeholder: el.getAttribute('placeholder') || null,
5234
- name: el.name || null,
5235
- value: el.value?.slice(0, 100) || null,
5236
- visible: el.offsetParent !== null,
5237
- rect: (() => { try { const r = el.getBoundingClientRect(); return { x: Math.round(r.x), y: Math.round(r.y), w: Math.round(r.width), h: Math.round(r.height) }; } catch { return null; } })(),
5238
- });
5239
- });
5240
-
5241
- // textarea
5242
- document.querySelectorAll('textarea').forEach((el, i) => {
5243
- if (i >= 10) return;
5244
- result.textareas.push({
5245
- id: el.id || null,
5246
- class: (el.className || '').toString().slice(0, 150),
5247
- placeholder: el.getAttribute('placeholder') || null,
5248
- rows: el.rows,
5249
- value: el.value?.slice(0, 100) || null,
5250
- visible: el.offsetParent !== null,
5251
- rect: (() => { try { const r = el.getBoundingClientRect(); return { x: Math.round(r.x), y: Math.round(r.y), w: Math.round(r.width), h: Math.round(r.height) }; } catch { return null; } })(),
5252
- });
5253
- });
5254
-
5255
- // contenteditable
5256
- document.querySelectorAll('[contenteditable="true"]').forEach((el, i) => {
5257
- if (i >= 10) return;
5258
- result.editables.push({
5259
- tag: el.tagName?.toLowerCase(),
5260
- id: el.id || null,
5261
- class: (el.className || '').toString().slice(0, 150),
5262
- role: el.getAttribute('role') || null,
5263
- text: (el.textContent || '').trim().slice(0, 100),
5264
- visible: el.offsetParent !== null,
5265
- rect: (() => { try { const r = el.getBoundingClientRect(); return { x: Math.round(r.x), y: Math.round(r.y), w: Math.round(r.width), h: Math.round(r.height) }; } catch { return null; } })(),
5266
- });
5267
- });
5268
-
5269
- // role="textbox"
5270
- document.querySelectorAll('[role="textbox"]').forEach((el, i) => {
5271
- if (i >= 10) return;
5272
- result.textboxes.push({
5273
- tag: el.tagName?.toLowerCase(),
5274
- id: el.id || null,
5275
- class: (el.className || '').toString().slice(0, 150),
5276
- 'aria-label': el.getAttribute('aria-label') || null,
5277
- text: (el.textContent || '').trim().slice(0, 100),
5278
- visible: el.offsetParent !== null,
5279
- rect: (() => { try { const r = el.getBoundingClientRect(); return { x: Math.round(r.x), y: Math.round(r.y), w: Math.round(r.width), h: Math.round(r.height) }; } catch { return null; } })(),
5280
- });
5281
- });
5282
-
5283
- // Buttons (send, submit, accept, reject, approve etc)
5284
- const btnKeywords = /send|submit|accept|reject|approve|deny|cancel|confirm|run|execute|apply/i;
5285
- document.querySelectorAll('button, [role="button"], input[type="submit"]').forEach((el, i) => {
5286
- const text = (el.textContent || el.getAttribute('aria-label') || '').trim();
5287
- if (i < 30 && (text.length < 30 || btnKeywords.test(text))) {
5288
- result.buttons.push({
5289
- tag: el.tagName?.toLowerCase(),
5290
- id: el.id || null,
5291
- class: (el.className || '').toString().slice(0, 150),
5292
- text: text.slice(0, 80),
5293
- 'aria-label': el.getAttribute('aria-label') || null,
5294
- disabled: el.disabled || el.getAttribute('disabled') !== null,
5295
- visible: el.offsetParent !== null,
5296
- rect: (() => { try { const r = el.getBoundingClientRect(); return { x: Math.round(r.x), y: Math.round(r.y), w: Math.round(r.width), h: Math.round(r.height) }; } catch { return null; } })(),
5297
- });
5298
- }
5299
- });
5300
-
5301
- // iframes
5302
- document.querySelectorAll('iframe, webview').forEach((el, i) => {
5303
- if (i >= 20) return;
5304
- result.iframes.push({
5305
- tag: el.tagName?.toLowerCase(),
5306
- id: el.id || null,
5307
- class: (el.className || '').toString().slice(0, 150),
5308
- src: el.getAttribute('src')?.slice(0, 200) || null,
5309
- title: el.getAttribute('title') || null,
5310
- visible: el.offsetParent !== null,
5311
- rect: (() => { try { const r = el.getBoundingClientRect(); return { x: Math.round(r.x), y: Math.round(r.y), w: Math.round(r.width), h: Math.round(r.height) }; } catch { return null; } })(),
5312
- });
5313
- });
5314
-
5315
- return JSON.stringify(result);
5316
- })()`;
5317
- try {
5318
- let raw;
5319
- if (sessionId) {
5320
- raw = await this.getCdp().evaluateInSessionFrame(sessionId, expression);
5321
- } else {
5322
- raw = await this.getCdp().evaluate(expression, 3e4);
5323
- }
5324
- const parsed = typeof raw === "string" ? JSON.parse(raw) : raw;
5325
- return { success: true, ...parsed };
5326
- } catch (e) {
5327
- return { success: false, error: e.message };
5328
- }
5329
- }
5330
- };
5331
-
5332
- // src/providers/ide-provider-instance.ts
5333
- var crypto2 = __toESM(require("crypto"));
5334
-
5335
- // src/providers/io-contracts.ts
5336
- function normalizeInputEnvelope(input) {
5337
- const normalized = normalizeInputEnvelopePayload(input);
5338
- const textFallback = normalized.textFallback ?? flattenInputParts(normalized.parts);
5339
- return {
5340
- parts: normalized.parts,
5341
- textFallback,
5342
- ...normalized.metadata ? { metadata: normalized.metadata } : {}
5343
- };
5344
- }
5345
- function normalizeMessageParts(content) {
5346
- if (typeof content === "string") return [{ type: "text", text: content }];
5347
- if (!Array.isArray(content)) {
5348
- if (content && typeof content === "object" && typeof content.text === "string") {
5349
- return [{ type: "text", text: String(content.text) }];
5350
- }
5351
- return [];
5352
- }
5353
- const parts = [];
5354
- for (const raw of content) {
5355
- if (typeof raw === "string") {
5356
- parts.push({ type: "text", text: raw });
5357
- continue;
5358
- }
5359
- if (!raw || typeof raw !== "object") continue;
5360
- const part = normalizeMessagePartObject(raw);
5361
- if (part) parts.push(part);
5362
- }
5363
- return parts;
5364
- }
5365
- function flattenMessageParts(parts) {
5366
- return parts.map((part) => {
5367
- if (part.type === "text") return part.text;
5368
- if (part.type === "resource") return part.resource.text || "";
5369
- return "";
5370
- }).filter((value) => value.length > 0).join("\n");
5371
- }
5372
- function normalizeInputEnvelopePayload(input) {
5373
- if (typeof input === "string") {
5374
- return { parts: [{ type: "text", text: input }], textFallback: input };
5375
- }
5376
- if (!input || typeof input !== "object") {
5377
- return { parts: [], textFallback: "" };
5378
- }
5379
- const record = input;
5380
- const nestedInput = record.input;
5381
- if (nestedInput && typeof nestedInput === "object") {
5382
- const nested = nestedInput;
5383
- return {
5384
- parts: normalizeInputParts(nested.parts ?? nested.prompt),
5385
- textFallback: typeof nested.textFallback === "string" ? nested.textFallback : void 0,
5386
- metadata: normalizeInputMetadata(nested.metadata)
5387
- };
5388
- }
5389
- const directText = typeof record.text === "string" ? record.text : typeof record.message === "string" ? record.message : void 0;
5390
- if (directText !== void 0) {
5391
- return { parts: [{ type: "text", text: directText }], textFallback: directText };
5392
- }
5393
- const directParts = normalizeInputParts(record.parts ?? record.prompt);
5394
- return {
5395
- parts: directParts,
5396
- textFallback: typeof record.textFallback === "string" ? record.textFallback : void 0,
5397
- metadata: normalizeInputMetadata(record.metadata)
5398
- };
5399
- }
5400
- function normalizeInputMetadata(value) {
5401
- if (!value || typeof value !== "object") return void 0;
5402
- const record = value;
5403
- const metadata = {};
5404
- if (record.source === "dashboard" || record.source === "shortcut_api" || record.source === "provider_script" || record.source === "session_replay") {
5405
- metadata.source = record.source;
5406
- }
5407
- if (typeof record.clientTimestamp === "number" && Number.isFinite(record.clientTimestamp)) {
5408
- metadata.clientTimestamp = record.clientTimestamp;
5409
- }
5410
- return Object.keys(metadata).length > 0 ? metadata : void 0;
5411
- }
5412
- function normalizeInputParts(value) {
5413
- if (!Array.isArray(value)) return [];
5414
- const parts = [];
5415
- for (const raw of value) {
5416
- if (typeof raw === "string") {
5417
- parts.push({ type: "text", text: raw });
5418
- continue;
5419
- }
5420
- if (!raw || typeof raw !== "object") continue;
5421
- const part = normalizeInputPartObject(raw);
5422
- if (part) parts.push(part);
5423
- }
5424
- return parts;
5425
- }
5426
- function normalizeInputPartObject(raw) {
5427
- const type = raw.type;
5428
- if (type === "text" && typeof raw.text === "string") {
5429
- return { type, text: raw.text };
5430
- }
5431
- if (type === "image" && typeof raw.mimeType === "string") {
5432
- return {
5433
- type,
5434
- mimeType: raw.mimeType,
5435
- ...typeof raw.uri === "string" ? { uri: raw.uri } : {},
5436
- ...typeof raw.data === "string" ? { data: raw.data } : {},
5437
- ...typeof raw.alt === "string" ? { alt: raw.alt } : {}
5438
- };
5439
- }
5440
- if (type === "audio" && typeof raw.mimeType === "string") {
5441
- return {
5442
- type,
5443
- mimeType: raw.mimeType,
5444
- ...typeof raw.uri === "string" ? { uri: raw.uri } : {},
5445
- ...typeof raw.data === "string" ? { data: raw.data } : {},
5446
- ...typeof raw.transcript === "string" ? { transcript: raw.transcript } : {}
5447
- };
5448
- }
5449
- if (type === "video" && typeof raw.mimeType === "string") {
5450
- return {
5451
- type,
5452
- mimeType: raw.mimeType,
5453
- ...typeof raw.uri === "string" ? { uri: raw.uri } : {},
5454
- ...typeof raw.data === "string" ? { data: raw.data } : {},
5455
- ...typeof raw.posterUri === "string" ? { posterUri: raw.posterUri } : {}
5456
- };
5457
- }
5458
- if (type === "resource" && typeof raw.uri === "string") {
5459
- return {
5460
- type,
5461
- uri: raw.uri,
5462
- ...typeof raw.mimeType === "string" ? { mimeType: raw.mimeType } : {},
5463
- ...typeof raw.name === "string" ? { name: raw.name } : {},
5464
- ...typeof raw.text === "string" ? { text: raw.text } : {},
5465
- ...typeof raw.data === "string" ? { data: raw.data } : {}
5466
- };
5467
- }
5468
- if (type === "resource_link" && typeof raw.uri === "string") {
5469
- return {
5470
- type: "resource",
5471
- uri: raw.uri,
5472
- ...typeof raw.mimeType === "string" ? { mimeType: raw.mimeType } : {},
5473
- ...typeof raw.name === "string" ? { name: raw.name } : {}
5474
- };
5475
- }
5476
- return null;
5477
- }
5478
- function normalizeMessagePartObject(raw) {
5479
- const type = raw.type;
5480
- if (type === "text" && typeof raw.text === "string") {
5481
- return { type, text: raw.text };
5482
- }
5483
- if (type === "image" && typeof raw.mimeType === "string") {
5484
- return {
5485
- type,
5486
- mimeType: raw.mimeType,
5487
- ...typeof raw.uri === "string" ? { uri: raw.uri } : {},
5488
- ...typeof raw.data === "string" ? { data: raw.data } : {}
5489
- };
5490
- }
5491
- if (type === "audio" && typeof raw.mimeType === "string") {
5492
- return {
5493
- type,
5494
- mimeType: raw.mimeType,
5495
- ...typeof raw.uri === "string" ? { uri: raw.uri } : {},
5496
- ...typeof raw.data === "string" ? { data: raw.data } : {},
5497
- ...typeof raw.transcript === "string" ? { transcript: raw.transcript } : {}
5498
- };
5499
- }
5500
- if (type === "video" && typeof raw.mimeType === "string") {
5501
- return {
5502
- type,
5503
- mimeType: raw.mimeType,
5504
- ...typeof raw.uri === "string" ? { uri: raw.uri } : {},
5505
- ...typeof raw.data === "string" ? { data: raw.data } : {},
5506
- ...typeof raw.posterUri === "string" ? { posterUri: raw.posterUri } : {}
5507
- };
5508
- }
5509
- if (type === "resource_link" && typeof raw.uri === "string" && typeof raw.name === "string") {
5510
- return {
5511
- type,
5512
- uri: raw.uri,
5513
- name: raw.name,
5514
- ...typeof raw.mimeType === "string" ? { mimeType: raw.mimeType } : {},
5515
- ...typeof raw.size === "number" ? { size: raw.size } : {}
5516
- };
5517
- }
5518
- if (type === "resource" && raw.resource && typeof raw.resource === "object") {
5519
- const resource = raw.resource;
5520
- if (typeof resource.uri !== "string") return null;
5521
- return {
5522
- type,
5523
- resource: {
5524
- uri: resource.uri,
5525
- ...typeof resource.mimeType === "string" || resource.mimeType === null ? { mimeType: resource.mimeType } : {},
5526
- ...typeof resource.text === "string" ? { text: resource.text } : {},
5527
- ...typeof resource.blob === "string" ? { blob: resource.blob } : {}
5553
+ */
5554
+ async handleDomDebug(args) {
5555
+ if (!this.getCdp()?.isConnected) return { success: false, error: "CDP not connected" };
5556
+ const sessionId = args?.sessionId;
5557
+ const expression = `(() => {
5558
+ const result = {
5559
+ url: location.href,
5560
+ title: document.title,
5561
+ viewport: { w: window.innerWidth, h: window.innerHeight },
5562
+
5563
+ // Input field info
5564
+ inputs: [],
5565
+ // Textarea info
5566
+ textareas: [],
5567
+ // Contenteditable info
5568
+ editables: [],
5569
+ // Buttons (send, submit etc)
5570
+ buttons: [],
5571
+ // iframes (agent webviews)
5572
+ iframes: [],
5573
+ // role="textbox" info
5574
+ textboxes: [],
5575
+ };
5576
+
5577
+ // Input fields
5578
+ document.querySelectorAll('input[type="text"], input:not([type])').forEach((el, i) => {
5579
+ if (i >= 10) return;
5580
+ result.inputs.push({
5581
+ tag: 'input',
5582
+ id: el.id || null,
5583
+ class: (el.className || '').toString().slice(0, 150),
5584
+ placeholder: el.getAttribute('placeholder') || null,
5585
+ name: el.name || null,
5586
+ value: el.value?.slice(0, 100) || null,
5587
+ visible: el.offsetParent !== null,
5588
+ rect: (() => { try { const r = el.getBoundingClientRect(); return { x: Math.round(r.x), y: Math.round(r.y), w: Math.round(r.width), h: Math.round(r.height) }; } catch { return null; } })(),
5589
+ });
5590
+ });
5591
+
5592
+ // textarea
5593
+ document.querySelectorAll('textarea').forEach((el, i) => {
5594
+ if (i >= 10) return;
5595
+ result.textareas.push({
5596
+ id: el.id || null,
5597
+ class: (el.className || '').toString().slice(0, 150),
5598
+ placeholder: el.getAttribute('placeholder') || null,
5599
+ rows: el.rows,
5600
+ value: el.value?.slice(0, 100) || null,
5601
+ visible: el.offsetParent !== null,
5602
+ rect: (() => { try { const r = el.getBoundingClientRect(); return { x: Math.round(r.x), y: Math.round(r.y), w: Math.round(r.width), h: Math.round(r.height) }; } catch { return null; } })(),
5603
+ });
5604
+ });
5605
+
5606
+ // contenteditable
5607
+ document.querySelectorAll('[contenteditable="true"]').forEach((el, i) => {
5608
+ if (i >= 10) return;
5609
+ result.editables.push({
5610
+ tag: el.tagName?.toLowerCase(),
5611
+ id: el.id || null,
5612
+ class: (el.className || '').toString().slice(0, 150),
5613
+ role: el.getAttribute('role') || null,
5614
+ text: (el.textContent || '').trim().slice(0, 100),
5615
+ visible: el.offsetParent !== null,
5616
+ rect: (() => { try { const r = el.getBoundingClientRect(); return { x: Math.round(r.x), y: Math.round(r.y), w: Math.round(r.width), h: Math.round(r.height) }; } catch { return null; } })(),
5617
+ });
5618
+ });
5619
+
5620
+ // role="textbox"
5621
+ document.querySelectorAll('[role="textbox"]').forEach((el, i) => {
5622
+ if (i >= 10) return;
5623
+ result.textboxes.push({
5624
+ tag: el.tagName?.toLowerCase(),
5625
+ id: el.id || null,
5626
+ class: (el.className || '').toString().slice(0, 150),
5627
+ 'aria-label': el.getAttribute('aria-label') || null,
5628
+ text: (el.textContent || '').trim().slice(0, 100),
5629
+ visible: el.offsetParent !== null,
5630
+ rect: (() => { try { const r = el.getBoundingClientRect(); return { x: Math.round(r.x), y: Math.round(r.y), w: Math.round(r.width), h: Math.round(r.height) }; } catch { return null; } })(),
5631
+ });
5632
+ });
5633
+
5634
+ // Buttons (send, submit, accept, reject, approve etc)
5635
+ const btnKeywords = /send|submit|accept|reject|approve|deny|cancel|confirm|run|execute|apply/i;
5636
+ document.querySelectorAll('button, [role="button"], input[type="submit"]').forEach((el, i) => {
5637
+ const text = (el.textContent || el.getAttribute('aria-label') || '').trim();
5638
+ if (i < 30 && (text.length < 30 || btnKeywords.test(text))) {
5639
+ result.buttons.push({
5640
+ tag: el.tagName?.toLowerCase(),
5641
+ id: el.id || null,
5642
+ class: (el.className || '').toString().slice(0, 150),
5643
+ text: text.slice(0, 80),
5644
+ 'aria-label': el.getAttribute('aria-label') || null,
5645
+ disabled: el.disabled || el.getAttribute('disabled') !== null,
5646
+ visible: el.offsetParent !== null,
5647
+ rect: (() => { try { const r = el.getBoundingClientRect(); return { x: Math.round(r.x), y: Math.round(r.y), w: Math.round(r.width), h: Math.round(r.height) }; } catch { return null; } })(),
5648
+ });
5649
+ }
5650
+ });
5651
+
5652
+ // iframes
5653
+ document.querySelectorAll('iframe, webview').forEach((el, i) => {
5654
+ if (i >= 20) return;
5655
+ result.iframes.push({
5656
+ tag: el.tagName?.toLowerCase(),
5657
+ id: el.id || null,
5658
+ class: (el.className || '').toString().slice(0, 150),
5659
+ src: el.getAttribute('src')?.slice(0, 200) || null,
5660
+ title: el.getAttribute('title') || null,
5661
+ visible: el.offsetParent !== null,
5662
+ rect: (() => { try { const r = el.getBoundingClientRect(); return { x: Math.round(r.x), y: Math.round(r.y), w: Math.round(r.width), h: Math.round(r.height) }; } catch { return null; } })(),
5663
+ });
5664
+ });
5665
+
5666
+ return JSON.stringify(result);
5667
+ })()`;
5668
+ try {
5669
+ let raw;
5670
+ if (sessionId) {
5671
+ raw = await this.getCdp().evaluateInSessionFrame(sessionId, expression);
5672
+ } else {
5673
+ raw = await this.getCdp().evaluate(expression, 3e4);
5528
5674
  }
5529
- };
5675
+ const parsed = typeof raw === "string" ? JSON.parse(raw) : raw;
5676
+ return { success: true, ...parsed };
5677
+ } catch (e) {
5678
+ return { success: false, error: e.message };
5679
+ }
5530
5680
  }
5531
- return null;
5532
- }
5533
- function flattenInputParts(parts) {
5534
- return parts.map((part) => {
5535
- if (part.type === "text") return part.text;
5536
- if (part.type === "audio") return part.transcript || "";
5537
- if (part.type === "resource") return part.text || "";
5538
- return "";
5539
- }).filter((value) => value.length > 0).join("\n");
5540
- }
5681
+ };
5541
5682
 
5542
- // src/providers/contracts.ts
5543
- function flattenContent(content) {
5544
- if (typeof content === "string") return content;
5545
- return flattenMessageParts(normalizeMessageParts(content));
5546
- }
5683
+ // src/providers/ide-provider-instance.ts
5684
+ var crypto2 = __toESM(require("crypto"));
5685
+ init_contracts();
5686
+
5687
+ // src/providers/extension-provider-instance.ts
5688
+ init_contracts();
5547
5689
 
5548
5690
  // src/providers/status-monitor.ts
5549
5691
  var DEFAULT_MONITOR_CONFIG = {
@@ -5658,6 +5800,7 @@ var StatusMonitor = class {
5658
5800
  };
5659
5801
 
5660
5802
  // src/providers/control-effects.ts
5803
+ init_contracts();
5661
5804
  init_chat_message_normalization();
5662
5805
  function extractProviderControlValues(controls, data) {
5663
5806
  if (!data || typeof data !== "object") return void 0;
@@ -5779,38 +5922,37 @@ function buildPersistedProviderEffectMessage(effect) {
5779
5922
  return null;
5780
5923
  }
5781
5924
  function normalizeControlListResult(data) {
5782
- if (data && typeof data === "object" && Array.isArray(data.options)) {
5783
- return {
5784
- options: normalizeControlOptions(data.options),
5785
- ...isScalarControlValue(data.currentValue) ? { currentValue: data.currentValue } : {},
5786
- ...typeof data.error === "string" ? { error: data.error } : {}
5787
- };
5925
+ if (!data || typeof data !== "object" || !Array.isArray(data.options)) {
5926
+ throw new Error("Provider control list results must use the typed shape { options, currentValue?, error? }");
5788
5927
  }
5789
- const rawOptions = Array.isArray(data?.models) ? data.models : Array.isArray(data?.modes) ? data.modes : Array.isArray(data?.options) ? data.options : [];
5790
- const options = normalizeControlOptions(rawOptions);
5791
5928
  return {
5792
- options,
5793
- ...isScalarControlValue(data?.current) ? { currentValue: data.current } : {},
5794
- ...isScalarControlValue(data?.currentValue) ? { currentValue: data.currentValue } : {},
5795
- ...typeof data?.error === "string" ? { error: data.error } : {}
5929
+ options: normalizeControlOptions(data.options),
5930
+ ...isScalarControlValue(data.currentValue) ? { currentValue: data.currentValue } : {},
5931
+ ...typeof data.error === "string" ? { error: data.error } : {}
5796
5932
  };
5797
5933
  }
5798
5934
  function normalizeControlSetResult(data) {
5799
- const currentValue = isScalarControlValue(data?.currentValue) ? data.currentValue : isScalarControlValue(data?.value) ? data.value : void 0;
5935
+ if (!data || typeof data !== "object" || typeof data.ok !== "boolean") {
5936
+ throw new Error("Provider control set results must use the typed shape { ok, currentValue?, effects?, error? }");
5937
+ }
5938
+ const currentValue = isScalarControlValue(data.currentValue) ? data.currentValue : isScalarControlValue(data.value) ? data.value : void 0;
5800
5939
  return {
5801
- ok: data?.ok === true || data?.success === true,
5940
+ ok: data.ok,
5802
5941
  ...currentValue !== void 0 ? { currentValue } : {},
5803
- ...Array.isArray(data?.effects) ? { effects: normalizeProviderEffects(data) } : {},
5804
- ...typeof data?.error === "string" ? { error: data.error } : {}
5942
+ ...Array.isArray(data.effects) ? { effects: normalizeProviderEffects(data) } : {},
5943
+ ...typeof data.error === "string" ? { error: data.error } : {}
5805
5944
  };
5806
5945
  }
5807
5946
  function normalizeControlInvokeResult(data) {
5808
- const currentValue = isScalarControlValue(data?.currentValue) ? data.currentValue : isScalarControlValue(data?.value) ? data.value : void 0;
5947
+ if (!data || typeof data !== "object" || typeof data.ok !== "boolean") {
5948
+ throw new Error("Provider control invoke results must use the typed shape { ok, currentValue?, effects?, error? }");
5949
+ }
5950
+ const currentValue = isScalarControlValue(data.currentValue) ? data.currentValue : isScalarControlValue(data.value) ? data.value : void 0;
5809
5951
  return {
5810
- ok: data?.ok === true || data?.success === true,
5952
+ ok: data.ok,
5811
5953
  ...currentValue !== void 0 ? { currentValue } : {},
5812
- ...Array.isArray(data?.effects) ? { effects: normalizeProviderEffects(data) } : {},
5813
- ...typeof data?.error === "string" ? { error: data.error } : {}
5954
+ ...Array.isArray(data.effects) ? { effects: normalizeProviderEffects(data) } : {},
5955
+ ...typeof data.error === "string" ? { error: data.error } : {}
5814
5956
  };
5815
5957
  }
5816
5958
  function normalizeControlOptions(options) {
@@ -6701,19 +6843,37 @@ var ExtensionProviderInstance = class {
6701
6843
  );
6702
6844
  }
6703
6845
  }
6846
+ buildSyntheticTurnKey(message, occurrence) {
6847
+ const role = typeof message?.role === "string" ? message.role : "";
6848
+ const kind = typeof message?.kind === "string" ? message.kind : "";
6849
+ const senderName = typeof message?.senderName === "string" ? message.senderName : "";
6850
+ const content = flattenContent(message?.content).replace(/\s+/g, " ").trim().slice(0, 500);
6851
+ return `${role}|${kind}|${senderName}|${content}|${occurrence}`;
6852
+ }
6704
6853
  /**
6705
- * Assign stable receivedAt to extension messages.
6706
- * Same pattern as IdeProviderInstance.readChat() prevByHash
6707
- * preserves first-seen timestamp across polling cycles.
6854
+ * Assign stable receivedAt / synthetic _turnKey to extension messages.
6855
+ * Same transcript should keep the same identity across polling cycles and
6856
+ * stream resets, while repeated identical text later in the transcript still
6857
+ * produces a distinct completion marker via the occurrence suffix.
6708
6858
  */
6709
6859
  assignReceivedAt(messages) {
6710
6860
  const now = Date.now();
6711
6861
  const nextHashes = /* @__PURE__ */ new Map();
6862
+ const occurrenceByBaseKey = /* @__PURE__ */ new Map();
6712
6863
  for (const msg of messages) {
6713
- const hash = `${msg.role}:${(msg.content || "").slice(0, 100)}`;
6714
- const prevTime = this.prevMessageHashes.get(hash);
6864
+ const explicitTurnKey = typeof msg?._turnKey === "string" && msg._turnKey.trim() ? msg._turnKey.trim() : "";
6865
+ const explicitId = typeof msg?.id === "string" && msg.id.trim() ? `id:${msg.id.trim()}` : "";
6866
+ const explicitIndex = typeof msg?.index === "number" && Number.isFinite(msg.index) ? `idx:${msg.index}` : "";
6867
+ const baseKey = explicitTurnKey || explicitId || explicitIndex || `${msg?.role || ""}:${flattenContent(msg?.content || "").slice(0, 500)}`;
6868
+ const occurrence = (occurrenceByBaseKey.get(baseKey) || 0) + 1;
6869
+ occurrenceByBaseKey.set(baseKey, occurrence);
6870
+ const syntheticTurnKey = explicitTurnKey || explicitId || explicitIndex || this.buildSyntheticTurnKey(msg, occurrence);
6871
+ if (!explicitTurnKey && !explicitId && !explicitIndex) {
6872
+ msg._turnKey = syntheticTurnKey;
6873
+ }
6874
+ const prevTime = this.prevMessageHashes.get(syntheticTurnKey);
6715
6875
  msg.receivedAt = prevTime || now;
6716
- nextHashes.set(hash, msg.receivedAt);
6876
+ nextHashes.set(syntheticTurnKey, msg.receivedAt);
6717
6877
  }
6718
6878
  this.prevMessageHashes = nextHashes;
6719
6879
  return normalizeChatMessages(messages);
@@ -6790,6 +6950,7 @@ ${effect.notification.body || ""}`.trim();
6790
6950
 
6791
6951
  // src/providers/ide-provider-instance.ts
6792
6952
  init_logger();
6953
+ init_read_chat_contract();
6793
6954
 
6794
6955
  // src/providers/approval-utils.ts
6795
6956
  var DEFAULT_APPROVAL_POSITIVE_HINTS = [
@@ -7071,7 +7232,7 @@ var IdeProviderInstance = class {
7071
7232
  }
7072
7233
  }
7073
7234
  if (!raw || typeof raw !== "object") return;
7074
- const chat = raw;
7235
+ const chat = validateReadChatResultPayload(raw, `${this.type} readChat`);
7075
7236
  let { activeModal } = chat;
7076
7237
  if (activeModal) {
7077
7238
  const w = activeModal.width ?? Infinity;
@@ -8216,6 +8377,65 @@ function reconcileIdeRuntimeSessions(instanceManager, sessionRegistry) {
8216
8377
  init_logger();
8217
8378
 
8218
8379
  // src/commands/chat-commands.ts
8380
+ init_contracts();
8381
+
8382
+ // src/providers/provider-input-support.ts
8383
+ var VALID_INPUT_MEDIA_TYPES = /* @__PURE__ */ new Set(["text", "image", "audio", "video", "resource"]);
8384
+ function getProviderLabel(provider) {
8385
+ return provider?.name || provider?.type || "This provider";
8386
+ }
8387
+ function hasNonEmptyFallbackText(input) {
8388
+ return typeof input.textFallback === "string" && input.textFallback.trim().length > 0;
8389
+ }
8390
+ function getRequestedInputMediaTypes(input) {
8391
+ const types = /* @__PURE__ */ new Set();
8392
+ if (hasNonEmptyFallbackText(input) && !input.parts.some((part) => part.type === "text")) {
8393
+ types.add("text");
8394
+ }
8395
+ for (const part of input.parts) {
8396
+ if (VALID_INPUT_MEDIA_TYPES.has(part.type)) {
8397
+ types.add(part.type);
8398
+ }
8399
+ }
8400
+ return Array.from(types);
8401
+ }
8402
+ function getEffectiveSemanticPartCount(input) {
8403
+ let count = input.parts.length;
8404
+ if (hasNonEmptyFallbackText(input) && !input.parts.some((part) => part.type === "text")) {
8405
+ count += 1;
8406
+ }
8407
+ return count;
8408
+ }
8409
+ function assertTextOnlyInput(provider, input) {
8410
+ const unsupported = getRequestedInputMediaTypes(input).filter((type) => type !== "text");
8411
+ if (unsupported.length === 0) return;
8412
+ const label = getProviderLabel(provider);
8413
+ const suffix = unsupported.length === 1 ? "" : "s";
8414
+ throw new Error(`${label} only supports text input; unsupported input type${suffix}: ${unsupported.join(", ")}`);
8415
+ }
8416
+ function getDeclaredProviderInputSupport(provider) {
8417
+ const rawMediaTypes = Array.isArray(provider?.capabilities?.input?.mediaTypes) ? provider?.capabilities?.input?.mediaTypes.filter((type) => VALID_INPUT_MEDIA_TYPES.has(type)) : [];
8418
+ return {
8419
+ multipart: provider?.capabilities?.input?.multipart === true,
8420
+ mediaTypes: new Set(rawMediaTypes.length > 0 ? rawMediaTypes : ["text"])
8421
+ };
8422
+ }
8423
+ function assertProviderSupportsDeclaredInput(provider, input) {
8424
+ const label = getProviderLabel(provider);
8425
+ const support = getDeclaredProviderInputSupport(provider);
8426
+ const requestedTypes = getRequestedInputMediaTypes(input);
8427
+ const unsupported = requestedTypes.filter((type) => !support.mediaTypes.has(type));
8428
+ if (unsupported.length > 0) {
8429
+ const suffix = unsupported.length === 1 ? "" : "s";
8430
+ throw new Error(`${label} does not support input type${suffix}: ${unsupported.join(", ")}`);
8431
+ }
8432
+ if (getEffectiveSemanticPartCount(input) > 1 && !support.multipart) {
8433
+ throw new Error(`${label} does not support multipart input`);
8434
+ }
8435
+ }
8436
+
8437
+ // src/commands/chat-commands.ts
8438
+ init_read_chat_contract();
8219
8439
  init_logger();
8220
8440
 
8221
8441
  // src/logging/debug-config.ts
@@ -8397,10 +8617,15 @@ function isCliLikeTransport(transport) {
8397
8617
  function isExtensionTransport(transport) {
8398
8618
  return transport === "cdp-webview";
8399
8619
  }
8400
- function buildRecentSendKey(h, args, provider, text) {
8620
+ function buildRecentSendKey(h, args, provider, signature) {
8401
8621
  const transport = getTargetTransport(h, provider) || "unknown";
8402
8622
  const target = args?.targetSessionId || args?.agentType || h.currentSession?.providerType || h.currentProviderType || h.currentManagerKey || "unknown";
8403
- return `${transport}:${target}:${text.trim()}`;
8623
+ return `${transport}:${target}:${signature.trim()}`;
8624
+ }
8625
+ function buildSendInputSignature(input) {
8626
+ const text = typeof input.textFallback === "string" ? input.textFallback.trim() : "";
8627
+ if (text) return text;
8628
+ return JSON.stringify(input.parts || []);
8404
8629
  }
8405
8630
  function getSendChatInputEnvelope(args) {
8406
8631
  return normalizeInputEnvelope(args?.input ? { input: args.input } : args);
@@ -8553,14 +8778,20 @@ function computeReadChatSync(messages, cursor) {
8553
8778
  };
8554
8779
  }
8555
8780
  function buildReadChatCommandResult(payload, args) {
8556
- const messages = normalizeReadChatMessages(payload);
8781
+ let validatedPayload;
8782
+ try {
8783
+ validatedPayload = validateReadChatResultPayload(payload, "read_chat command result");
8784
+ } catch (error) {
8785
+ return { success: false, error: error?.message || String(error) };
8786
+ }
8787
+ const messages = normalizeReadChatMessages(validatedPayload);
8557
8788
  const cursor = normalizeReadChatCursor(args);
8558
8789
  if (!cursor.knownMessageCount && !cursor.lastMessageSignature && cursor.tailLimit > 0 && messages.length > cursor.tailLimit) {
8559
8790
  const tailMessages = messages.slice(-cursor.tailLimit);
8560
8791
  const lastMessageSignature = getChatMessageSignature(tailMessages[tailMessages.length - 1]);
8561
8792
  return {
8562
8793
  success: true,
8563
- ...payload,
8794
+ ...validatedPayload,
8564
8795
  messages: tailMessages,
8565
8796
  syncMode: "full",
8566
8797
  replaceFrom: 0,
@@ -8571,7 +8802,7 @@ function buildReadChatCommandResult(payload, args) {
8571
8802
  const sync = computeReadChatSync(messages, cursor);
8572
8803
  return {
8573
8804
  success: true,
8574
- ...payload,
8805
+ ...validatedPayload,
8575
8806
  messages: sync.messages,
8576
8807
  syncMode: sync.syncMode,
8577
8808
  replaceFrom: sync.replaceFrom,
@@ -8650,12 +8881,18 @@ async function handleReadChat(h, args) {
8650
8881
  const adapter = getTargetedCliAdapter(h, args, provider?.type);
8651
8882
  if (adapter) {
8652
8883
  _log(`${transport} adapter: ${adapter.cliType}`);
8653
- const status = adapter.getStatus();
8884
+ const parsedStatus = typeof adapter.getScriptParsedStatus === "function" ? parseMaybeJson(adapter.getScriptParsedStatus()) : null;
8885
+ const parsedRecord = parsedStatus && typeof parsedStatus === "object" ? parsedStatus : null;
8886
+ const status = parsedRecord || adapter.getStatus();
8887
+ const title = typeof parsedRecord?.title === "string" ? parsedRecord.title : void 0;
8888
+ const providerSessionId = typeof parsedRecord?.providerSessionId === "string" ? parsedRecord.providerSessionId : void 0;
8654
8889
  if (status) {
8655
8890
  return buildReadChatCommandResult({
8656
8891
  messages: status.messages || [],
8657
8892
  status: status.status,
8658
- activeModal: status.activeModal
8893
+ activeModal: status.activeModal,
8894
+ ...title ? { title } : {},
8895
+ ...providerSessionId ? { providerSessionId } : {}
8659
8896
  }, args);
8660
8897
  }
8661
8898
  }
@@ -8673,25 +8910,26 @@ async function handleReadChat(h, args) {
8673
8910
  }
8674
8911
  }
8675
8912
  if (parsed && typeof parsed === "object") {
8676
- _log(`Extension OK: ${parsed.messages?.length || 0} msgs`);
8913
+ const validated = validateReadChatResultPayload(parsed, "extension read_chat");
8914
+ _log(`Extension OK: ${validated.messages?.length || 0} msgs`);
8677
8915
  traceProviderEvent(args, "provider", "extension.read_chat.success", {
8678
8916
  h,
8679
8917
  provider,
8680
8918
  payload: {
8681
8919
  method: "evaluateProviderScript",
8682
8920
  result: evalResult.result,
8683
- parsed,
8684
- messageCount: Array.isArray(parsed.messages) ? parsed.messages.length : 0
8921
+ parsed: validated,
8922
+ messageCount: Array.isArray(validated.messages) ? validated.messages.length : 0
8685
8923
  }
8686
8924
  });
8687
8925
  h.historyWriter.appendNewMessages(
8688
8926
  provider?.type || "unknown_extension",
8689
- toHistoryPersistedMessages(normalizeReadChatMessages(parsed)),
8690
- parsed.title,
8927
+ toHistoryPersistedMessages(normalizeReadChatMessages(validated)),
8928
+ validated.title,
8691
8929
  args?.targetSessionId,
8692
8930
  historySessionId
8693
8931
  );
8694
- return buildReadChatCommandResult(parsed, args);
8932
+ return buildReadChatCommandResult(validated, args);
8695
8933
  }
8696
8934
  }
8697
8935
  } catch (e) {
@@ -8746,15 +8984,16 @@ async function handleReadChat(h, args) {
8746
8984
  }
8747
8985
  }
8748
8986
  if (parsed && typeof parsed === "object") {
8749
- _log(`Webview OK: ${parsed.messages?.length || 0} msgs`);
8987
+ const validated = validateReadChatResultPayload(parsed, "webview read_chat");
8988
+ _log(`Webview OK: ${validated.messages?.length || 0} msgs`);
8750
8989
  h.historyWriter.appendNewMessages(
8751
8990
  provider?.type || getCurrentProviderType(h, "unknown_webview"),
8752
- toHistoryPersistedMessages(normalizeReadChatMessages(parsed)),
8753
- parsed.title,
8991
+ toHistoryPersistedMessages(normalizeReadChatMessages(validated)),
8992
+ validated.title,
8754
8993
  args?.targetSessionId,
8755
8994
  historySessionId
8756
8995
  );
8757
- return buildReadChatCommandResult(parsed, args);
8996
+ return buildReadChatCommandResult(validated, args);
8758
8997
  }
8759
8998
  }
8760
8999
  } catch (e) {
@@ -8775,25 +9014,26 @@ async function handleReadChat(h, args) {
8775
9014
  }
8776
9015
  }
8777
9016
  if (parsed && typeof parsed === "object" && parsed.messages?.length > 0) {
8778
- _log(`OK: ${parsed.messages?.length} msgs`);
9017
+ const validated = validateReadChatResultPayload(parsed, "ide read_chat");
9018
+ _log(`OK: ${validated.messages?.length} msgs`);
8779
9019
  traceProviderEvent(args, "provider", "ide.read_chat.success", {
8780
9020
  h,
8781
9021
  provider,
8782
9022
  payload: {
8783
9023
  method: "evaluate",
8784
9024
  result: evalResult.result,
8785
- parsed,
8786
- messageCount: Array.isArray(parsed.messages) ? parsed.messages.length : 0
9025
+ parsed: validated,
9026
+ messageCount: Array.isArray(validated.messages) ? validated.messages.length : 0
8787
9027
  }
8788
9028
  });
8789
9029
  h.historyWriter.appendNewMessages(
8790
9030
  provider?.type || getCurrentProviderType(h, "unknown_ide"),
8791
- toHistoryPersistedMessages(normalizeReadChatMessages(parsed)),
8792
- parsed.title,
9031
+ toHistoryPersistedMessages(normalizeReadChatMessages(validated)),
9032
+ validated.title,
8793
9033
  args?.targetSessionId,
8794
9034
  historySessionId
8795
9035
  );
8796
- return buildReadChatCommandResult(parsed, args);
9036
+ return buildReadChatCommandResult(validated, args);
8797
9037
  }
8798
9038
  }
8799
9039
  } catch (e) {
@@ -8811,11 +9051,12 @@ async function handleReadChat(h, args) {
8811
9051
  async function handleSendChat(h, args) {
8812
9052
  const input = getSendChatInputEnvelope(args);
8813
9053
  const text = input.textFallback;
8814
- if (!text) return { success: false, error: "text required" };
9054
+ const hasInput = input.parts.length > 0 || typeof text === "string" && text.trim().length > 0;
9055
+ if (!hasInput) return { success: false, error: "input required" };
8815
9056
  const _log = (msg) => LOG.debug("Command", `[send_chat] ${msg}`);
8816
9057
  const provider = h.getProvider(args?.agentType);
8817
9058
  const transport = getTargetTransport(h, provider);
8818
- const dedupeKey = buildRecentSendKey(h, args, provider, text);
9059
+ const dedupeKey = buildRecentSendKey(h, args, provider, buildSendInputSignature(input));
8819
9060
  const _logSendSuccess = (method, targetAgent) => {
8820
9061
  return { success: true, sent: true, method, targetAgent };
8821
9062
  };
@@ -8823,11 +9064,26 @@ async function handleSendChat(h, args) {
8823
9064
  _log(`Suppressed duplicate send for ${dedupeKey}`);
8824
9065
  return { success: true, sent: false, deduplicated: true };
8825
9066
  }
8826
- if (isCliLikeTransport(transport)) {
9067
+ if (transport === "acp") {
9068
+ const target = getTargetInstance(h, args);
9069
+ if (!target || target.category !== "acp") {
9070
+ return { success: false, error: `ACP instance not found for ${provider?.type || args?.agentType || "unknown"}` };
9071
+ }
9072
+ try {
9073
+ assertProviderSupportsDeclaredInput(provider, input);
9074
+ target.onEvent("send_message", { input });
9075
+ return _logSendSuccess("acp-instance", target.type);
9076
+ } catch (e) {
9077
+ return { success: false, error: `acp send failed: ${e.message}` };
9078
+ }
9079
+ }
9080
+ if (transport === "pty") {
8827
9081
  const adapter = getTargetedCliAdapter(h, args, provider?.type);
8828
9082
  if (adapter) {
8829
9083
  _log(`${transport} adapter: ${adapter.cliType}`);
8830
9084
  try {
9085
+ assertTextOnlyInput(provider, input);
9086
+ if (!text) return { success: false, error: "text required for PTY send" };
8831
9087
  await adapter.sendMessage(text);
8832
9088
  return _logSendSuccess(`${transport}-adapter`, adapter.cliType);
8833
9089
  } catch (e) {
@@ -8835,6 +9091,8 @@ async function handleSendChat(h, args) {
8835
9091
  }
8836
9092
  }
8837
9093
  }
9094
+ assertTextOnlyInput(provider, input);
9095
+ if (!text) return { success: false, error: "text required" };
8838
9096
  if (isExtensionTransport(transport)) {
8839
9097
  _log(`Extension: ${provider?.type || "unknown_extension"}`);
8840
9098
  try {
@@ -9999,14 +10257,14 @@ function normalizeProviderScriptArgs(args, scriptName) {
9999
10257
  }
10000
10258
  function buildControlScriptResult(scriptName, payload) {
10001
10259
  if (!payload || typeof payload !== "object") return {};
10002
- if (Array.isArray(payload.options) || Array.isArray(payload.models) || Array.isArray(payload.modes)) {
10260
+ if (Array.isArray(payload.options)) {
10003
10261
  return { controlResult: normalizeControlListResult(payload) };
10004
10262
  }
10005
10263
  const looksLikeValueMutation = /^set|^change/i.test(scriptName) || payload.currentValue !== void 0 || payload.value !== void 0;
10006
10264
  if (looksLikeValueMutation) {
10007
10265
  return { controlResult: normalizeControlSetResult(payload) };
10008
10266
  }
10009
- if (payload.ok !== void 0 || payload.success !== void 0 || Array.isArray(payload.effects)) {
10267
+ if (payload.ok !== void 0 || Array.isArray(payload.effects) || typeof payload.error === "string") {
10010
10268
  return { controlResult: normalizeControlInvokeResult(payload) };
10011
10269
  }
10012
10270
  return {};
@@ -10809,7 +11067,7 @@ var DaemonCommandHandler = class {
10809
11067
 
10810
11068
  // src/commands/cli-manager.ts
10811
11069
  var os12 = __toESM(require("os"));
10812
- var path13 = __toESM(require("path"));
11070
+ var path12 = __toESM(require("path"));
10813
11071
  var crypto4 = __toESM(require("crypto"));
10814
11072
  var import_chalk = __toESM(require("chalk"));
10815
11073
  init_provider_cli_adapter();
@@ -10821,6 +11079,7 @@ var path11 = __toESM(require("path"));
10821
11079
  var crypto3 = __toESM(require("crypto"));
10822
11080
  var fs5 = __toESM(require("fs"));
10823
11081
  var import_node_module = require("module");
11082
+ init_contracts();
10824
11083
  init_provider_cli_adapter();
10825
11084
  init_logger();
10826
11085
  init_chat_message_normalization();
@@ -11119,6 +11378,7 @@ var CliProviderInstance = class {
11119
11378
  onEvent(event, data) {
11120
11379
  if (event === "send_message") {
11121
11380
  const input = normalizeInputEnvelope(data);
11381
+ assertTextOnlyInput(this.provider, input);
11122
11382
  if (input.textFallback) {
11123
11383
  void this.adapter.sendMessage(input.textFallback).catch((e) => {
11124
11384
  LOG.warn("CLI", `[${this.type}] send_message failed: ${e?.message || e}`);
@@ -11556,10 +11816,10 @@ ${effect.notification.body || ""}`.trim();
11556
11816
  };
11557
11817
 
11558
11818
  // src/providers/acp-provider-instance.ts
11559
- var path12 = __toESM(require("path"));
11560
11819
  var import_stream = require("stream");
11561
11820
  var import_child_process5 = require("child_process");
11562
11821
  var import_sdk = require("@agentclientprotocol/sdk");
11822
+ init_contracts();
11563
11823
  init_chat_message_normalization();
11564
11824
  init_logger();
11565
11825
  function getPromptCapabilityFlags(agentCapabilities) {
@@ -11570,25 +11830,6 @@ function getPromptCapabilityFlags(agentCapabilities) {
11570
11830
  embeddedContext: prompt.embeddedContext === true
11571
11831
  };
11572
11832
  }
11573
- function getResourceNameFromUri(uri, fallback) {
11574
- try {
11575
- if (uri.startsWith("file://")) {
11576
- return path12.basename(new URL(uri).pathname) || fallback;
11577
- }
11578
- return path12.basename(uri) || fallback;
11579
- } catch {
11580
- return fallback;
11581
- }
11582
- }
11583
- function inputPartToResourceLink(part, fallbackName) {
11584
- if (!part.uri) return null;
11585
- return {
11586
- type: "resource_link",
11587
- uri: part.uri,
11588
- name: getResourceNameFromUri(part.uri, fallbackName),
11589
- ...part.mimeType ? { mimeType: part.mimeType } : {}
11590
- };
11591
- }
11592
11833
  function appendPromptText(promptParts, text) {
11593
11834
  const normalized = typeof text === "string" ? text.trim() : "";
11594
11835
  if (!normalized) return;
@@ -11605,55 +11846,60 @@ function buildAcpPromptParts(input, agentCapabilities) {
11605
11846
  continue;
11606
11847
  }
11607
11848
  if (part.type === "image") {
11608
- if (caps.image && part.data) {
11609
- promptParts.push({
11610
- type: "image",
11611
- data: part.data,
11612
- mimeType: part.mimeType,
11613
- ...part.uri ? { uri: part.uri } : {}
11614
- });
11615
- continue;
11849
+ if (!caps.image) {
11850
+ throw new Error("ACP agent does not support input type: image");
11851
+ }
11852
+ if (!part.data) {
11853
+ throw new Error("ACP image input requires inline image data");
11616
11854
  }
11617
- const fallback = inputPartToResourceLink(part, "image");
11618
- if (fallback) promptParts.push(fallback);
11619
- appendPromptText(promptParts, part.alt || (!part.uri ? `Attached image (${part.mimeType})` : void 0));
11855
+ promptParts.push({
11856
+ type: "image",
11857
+ data: part.data,
11858
+ mimeType: part.mimeType,
11859
+ ...part.uri ? { uri: part.uri } : {}
11860
+ });
11620
11861
  continue;
11621
11862
  }
11622
11863
  if (part.type === "audio") {
11623
- if (caps.audio && part.data) {
11624
- promptParts.push({
11625
- type: "audio",
11626
- data: part.data,
11627
- mimeType: part.mimeType
11628
- });
11629
- continue;
11864
+ if (!caps.audio) {
11865
+ throw new Error("ACP agent does not support input type: audio");
11866
+ }
11867
+ if (!part.data) {
11868
+ throw new Error("ACP audio input requires inline audio data");
11630
11869
  }
11631
- const fallback = inputPartToResourceLink(part, "audio");
11632
- if (fallback) promptParts.push(fallback);
11633
- appendPromptText(promptParts, part.transcript || (!part.uri ? `Attached audio (${part.mimeType})` : void 0));
11870
+ promptParts.push({
11871
+ type: "audio",
11872
+ data: part.data,
11873
+ mimeType: part.mimeType
11874
+ });
11634
11875
  continue;
11635
11876
  }
11636
11877
  if (part.type === "resource") {
11637
- if (caps.embeddedContext && (part.text || part.data)) {
11878
+ if (!caps.embeddedContext) {
11879
+ throw new Error("ACP agent does not support input type: resource");
11880
+ }
11881
+ if (part.text) {
11638
11882
  promptParts.push({
11639
11883
  type: "resource",
11640
- resource: part.text ? { uri: part.uri, text: part.text, mimeType: part.mimeType ?? null } : { uri: part.uri, blob: part.data || "", mimeType: part.mimeType ?? null }
11884
+ resource: { uri: part.uri, text: part.text, mimeType: part.mimeType ?? null }
11641
11885
  });
11642
11886
  continue;
11643
11887
  }
11644
- const fallback = inputPartToResourceLink(part, part.name || "resource");
11645
- if (fallback) promptParts.push(fallback);
11646
- appendPromptText(promptParts, part.text || (!part.uri && part.name ? part.name : void 0));
11647
- continue;
11888
+ if (part.data) {
11889
+ promptParts.push({
11890
+ type: "resource",
11891
+ resource: { uri: part.uri, blob: part.data, mimeType: part.mimeType ?? null }
11892
+ });
11893
+ continue;
11894
+ }
11895
+ throw new Error("ACP resource input requires embedded text or binary data");
11648
11896
  }
11649
11897
  if (part.type === "video") {
11650
- const fallback = inputPartToResourceLink(part, "video");
11651
- if (fallback) promptParts.push(fallback);
11652
- appendPromptText(promptParts, !part.uri ? `Attached video (${part.mimeType})` : void 0);
11898
+ throw new Error("ACP agent does not support input type: video");
11653
11899
  }
11654
11900
  }
11655
11901
  if (!promptParts.some((part) => part.type === "text") && input.textFallback) {
11656
- promptParts.unshift({ type: "text", text: input.textFallback });
11902
+ appendPromptText(promptParts, input.textFallback);
11657
11903
  }
11658
11904
  return promptParts;
11659
11905
  }
@@ -11784,6 +12030,7 @@ var AcpProviderInstance = class {
11784
12030
  onEvent(event, data) {
11785
12031
  if (event === "send_message") {
11786
12032
  const input = normalizeInputEnvelope(data);
12033
+ assertProviderSupportsDeclaredInput(this.provider, input);
11787
12034
  const promptParts = buildAcpPromptParts(input, this.agentCapabilities);
11788
12035
  this.sendPrompt(input.textFallback, promptParts.length > 0 ? promptParts : void 0).catch(
11789
12036
  (e) => this.log.warn(`[${this.type}] sendPrompt error: ${e?.message}`)
@@ -12717,6 +12964,7 @@ ${rawInput}` : rawInput;
12717
12964
  };
12718
12965
 
12719
12966
  // src/commands/cli-manager.ts
12967
+ init_contracts();
12720
12968
  init_logger();
12721
12969
 
12722
12970
  // src/commands/hosted-runtime-restore.ts
@@ -12983,7 +13231,7 @@ var DaemonCliManager = class {
12983
13231
  async startSession(cliType, workingDir, cliArgs, initialModel, options) {
12984
13232
  const trimmed = (workingDir || "").trim();
12985
13233
  if (!trimmed) throw new Error("working directory required");
12986
- const resolvedDir = trimmed.startsWith("~") ? trimmed.replace(/^~/, os12.homedir()) : path13.resolve(trimmed);
13234
+ const resolvedDir = trimmed.startsWith("~") ? trimmed.replace(/^~/, os12.homedir()) : path12.resolve(trimmed);
12987
13235
  const normalizedType = this.providerLoader.resolveAlias(cliType);
12988
13236
  const provider = this.providerLoader.getByAlias(cliType);
12989
13237
  const key = crypto4.randomUUID();
@@ -13442,6 +13690,12 @@ Run 'adhdev doctor' for detailed diagnostics.`
13442
13690
  const { adapter, key } = found;
13443
13691
  if (action === "send_chat") {
13444
13692
  const input = normalizeInputEnvelope(args?.input ? { input: args.input } : args);
13693
+ const provider = this.providerLoader.resolve(agentType) || this.providerLoader.getMeta(agentType);
13694
+ if (provider?.category === "acp") {
13695
+ assertProviderSupportsDeclaredInput(provider, input);
13696
+ } else {
13697
+ assertTextOnlyInput(provider, input);
13698
+ }
13445
13699
  const message = input.textFallback;
13446
13700
  if (!message) throw new Error("message required for send_chat");
13447
13701
  await adapter.sendMessage(message);
@@ -13464,16 +13718,17 @@ Run 'adhdev doctor' for detailed diagnostics.`
13464
13718
  var import_child_process6 = require("child_process");
13465
13719
  var net = __toESM(require("net"));
13466
13720
  var os14 = __toESM(require("os"));
13467
- var path15 = __toESM(require("path"));
13721
+ var path14 = __toESM(require("path"));
13468
13722
 
13469
13723
  // src/providers/provider-loader.ts
13470
13724
  var fs6 = __toESM(require("fs"));
13471
- var path14 = __toESM(require("path"));
13725
+ var path13 = __toESM(require("path"));
13472
13726
  var os13 = __toESM(require("os"));
13473
13727
  var chokidar = __toESM(require("chokidar"));
13474
13728
  init_logger();
13475
13729
 
13476
13730
  // src/providers/provider-schema.ts
13731
+ var VALID_CAPABILITY_MEDIA_TYPES = /* @__PURE__ */ new Set(["text", "image", "audio", "video", "resource"]);
13477
13732
  var KNOWN_PROVIDER_FIELDS = /* @__PURE__ */ new Set([
13478
13733
  "type",
13479
13734
  "name",
@@ -13524,6 +13779,7 @@ var KNOWN_PROVIDER_FIELDS = /* @__PURE__ */ new Set([
13524
13779
  "sendDelayMs",
13525
13780
  "sendKey",
13526
13781
  "submitStrategy",
13782
+ "timeouts",
13527
13783
  "disableUpstream"
13528
13784
  ]);
13529
13785
  var VALUE_CONTROL_TYPES = /* @__PURE__ */ new Set(["select", "toggle", "cycle", "slider"]);
@@ -13550,6 +13806,7 @@ function validateProviderDefinition(raw) {
13550
13806
  warnings.push("disableUpstream is deprecated in provider definitions; use machine-level provider source policy instead");
13551
13807
  }
13552
13808
  const category = provider.category;
13809
+ const controls = Array.isArray(provider.controls) ? provider.controls : [];
13553
13810
  if (category === "cli" || category === "acp") {
13554
13811
  const spawn4 = provider.spawn;
13555
13812
  const command = spawn4 && typeof spawn4 === "object" ? spawn4.command : void 0;
@@ -13567,11 +13824,61 @@ function validateProviderDefinition(raw) {
13567
13824
  if (category === "extension" && !provider.extensionId) {
13568
13825
  warnings.push("Extension providers should have extensionId");
13569
13826
  }
13570
- for (const control of Array.isArray(provider.controls) ? provider.controls : []) {
13827
+ validateCapabilities(provider, controls, errors);
13828
+ for (const control of controls) {
13571
13829
  validateControl(control, errors);
13572
13830
  }
13573
13831
  return { errors, warnings };
13574
13832
  }
13833
+ function validateCapabilities(provider, controls, errors) {
13834
+ const capabilities = provider.capabilities;
13835
+ if (provider.contractVersion === 2) {
13836
+ if (!capabilities || typeof capabilities !== "object") {
13837
+ errors.push("contractVersion 2 providers must declare capabilities");
13838
+ return;
13839
+ }
13840
+ }
13841
+ if (!capabilities || typeof capabilities !== "object") {
13842
+ return;
13843
+ }
13844
+ const input = capabilities.input;
13845
+ if (!input || typeof input !== "object") {
13846
+ errors.push("capabilities.input is required");
13847
+ } else {
13848
+ if (typeof input.multipart !== "boolean") {
13849
+ errors.push("capabilities.input.multipart must be boolean");
13850
+ }
13851
+ if (!Array.isArray(input.mediaTypes) || input.mediaTypes.length === 0) {
13852
+ errors.push("capabilities.input.mediaTypes must be a non-empty array");
13853
+ } else if (input.mediaTypes.some((type) => typeof type !== "string" || !VALID_CAPABILITY_MEDIA_TYPES.has(type))) {
13854
+ errors.push(`capabilities.input.mediaTypes must only include: ${Array.from(VALID_CAPABILITY_MEDIA_TYPES).join(", ")}`);
13855
+ }
13856
+ }
13857
+ const output = capabilities.output;
13858
+ if (!output || typeof output !== "object") {
13859
+ errors.push("capabilities.output is required");
13860
+ } else {
13861
+ if (typeof output.richContent !== "boolean") {
13862
+ errors.push("capabilities.output.richContent must be boolean");
13863
+ }
13864
+ if (!Array.isArray(output.mediaTypes) || output.mediaTypes.length === 0) {
13865
+ errors.push("capabilities.output.mediaTypes must be a non-empty array");
13866
+ } else if (output.mediaTypes.some((type) => typeof type !== "string" || !VALID_CAPABILITY_MEDIA_TYPES.has(type))) {
13867
+ errors.push(`capabilities.output.mediaTypes must only include: ${Array.from(VALID_CAPABILITY_MEDIA_TYPES).join(", ")}`);
13868
+ }
13869
+ }
13870
+ const controlCapabilities = capabilities.controls;
13871
+ if (!controlCapabilities || typeof controlCapabilities !== "object") {
13872
+ errors.push("capabilities.controls is required");
13873
+ return;
13874
+ }
13875
+ if (typeof controlCapabilities.typedResults !== "boolean") {
13876
+ errors.push("capabilities.controls.typedResults must be boolean");
13877
+ }
13878
+ if (controls.length > 0 && controlCapabilities.typedResults !== true) {
13879
+ errors.push("providers declaring controls must set capabilities.controls.typedResults=true");
13880
+ }
13881
+ }
13575
13882
  function validateControl(control, errors) {
13576
13883
  if (!control || typeof control !== "object") {
13577
13884
  errors.push("controls: each control must be an object");
@@ -13626,9 +13933,9 @@ var ProviderLoader = class _ProviderLoader {
13626
13933
  static META_FILE = ".meta.json";
13627
13934
  constructor(options) {
13628
13935
  this.logFn = options?.logFn || LOG.forComponent("Provider").asLogFn();
13629
- this.defaultProvidersDir = path14.join(os13.homedir(), ".adhdev", "providers");
13936
+ this.defaultProvidersDir = path13.join(os13.homedir(), ".adhdev", "providers");
13630
13937
  this.userDir = this.defaultProvidersDir;
13631
- this.upstreamDir = path14.join(this.defaultProvidersDir, ".upstream");
13938
+ this.upstreamDir = path13.join(this.defaultProvidersDir, ".upstream");
13632
13939
  this.disableUpstream = false;
13633
13940
  this.applySourceConfig({
13634
13941
  userDir: options?.userDir,
@@ -13676,7 +13983,7 @@ var ProviderLoader = class _ProviderLoader {
13676
13983
  }
13677
13984
  this.sourceMode = nextSourceMode;
13678
13985
  this.userDir = this.explicitProviderDir || this.defaultProvidersDir;
13679
- this.upstreamDir = path14.join(this.defaultProvidersDir, ".upstream");
13986
+ this.upstreamDir = path13.join(this.defaultProvidersDir, ".upstream");
13680
13987
  this.disableUpstream = this.sourceMode === "no-upstream";
13681
13988
  if (this.explicitProviderDir) {
13682
13989
  this.log(`Config 'providerDir' applied: ${this.userDir}`);
@@ -13690,7 +13997,7 @@ var ProviderLoader = class _ProviderLoader {
13690
13997
  * Canonical provider directory shape for a given root.
13691
13998
  */
13692
13999
  getProviderDir(root, category, type) {
13693
- return path14.join(root, category, type);
14000
+ return path13.join(root, category, type);
13694
14001
  }
13695
14002
  /**
13696
14003
  * Canonical user override directory for a provider.
@@ -13717,7 +14024,7 @@ var ProviderLoader = class _ProviderLoader {
13717
14024
  resolveProviderFile(type, ...segments) {
13718
14025
  const dir = this.findProviderDirInternal(type);
13719
14026
  if (!dir) return null;
13720
- return path14.join(dir, ...segments);
14027
+ return path13.join(dir, ...segments);
13721
14028
  }
13722
14029
  /**
13723
14030
  * Load all providers (3-tier priority)
@@ -13756,7 +14063,7 @@ var ProviderLoader = class _ProviderLoader {
13756
14063
  if (!fs6.existsSync(this.upstreamDir)) return false;
13757
14064
  try {
13758
14065
  return fs6.readdirSync(this.upstreamDir).some(
13759
- (d) => fs6.statSync(path14.join(this.upstreamDir, d)).isDirectory()
14066
+ (d) => fs6.statSync(path13.join(this.upstreamDir, d)).isDirectory()
13760
14067
  );
13761
14068
  } catch {
13762
14069
  return false;
@@ -14071,8 +14378,8 @@ var ProviderLoader = class _ProviderLoader {
14071
14378
  resolved._resolvedScriptDir = entry.scriptDir;
14072
14379
  resolved._resolvedScriptsSource = `compatibility:${entry.ideVersion}`;
14073
14380
  if (providerDir) {
14074
- const fullDir = path14.join(providerDir, entry.scriptDir);
14075
- resolved._resolvedScriptsPath = fs6.existsSync(path14.join(fullDir, "scripts.js")) ? path14.join(fullDir, "scripts.js") : fullDir;
14381
+ const fullDir = path13.join(providerDir, entry.scriptDir);
14382
+ resolved._resolvedScriptsPath = fs6.existsSync(path13.join(fullDir, "scripts.js")) ? path13.join(fullDir, "scripts.js") : fullDir;
14076
14383
  }
14077
14384
  matched = true;
14078
14385
  }
@@ -14087,8 +14394,8 @@ var ProviderLoader = class _ProviderLoader {
14087
14394
  resolved._resolvedScriptDir = base.defaultScriptDir;
14088
14395
  resolved._resolvedScriptsSource = "defaultScriptDir:version_miss";
14089
14396
  if (providerDir) {
14090
- const fullDir = path14.join(providerDir, base.defaultScriptDir);
14091
- resolved._resolvedScriptsPath = fs6.existsSync(path14.join(fullDir, "scripts.js")) ? path14.join(fullDir, "scripts.js") : fullDir;
14397
+ const fullDir = path13.join(providerDir, base.defaultScriptDir);
14398
+ resolved._resolvedScriptsPath = fs6.existsSync(path13.join(fullDir, "scripts.js")) ? path13.join(fullDir, "scripts.js") : fullDir;
14092
14399
  }
14093
14400
  }
14094
14401
  resolved._versionWarning = `Version ${currentVersion} not in compatibility matrix. Using default scripts.`;
@@ -14105,8 +14412,8 @@ var ProviderLoader = class _ProviderLoader {
14105
14412
  resolved._resolvedScriptDir = dirOverride;
14106
14413
  resolved._resolvedScriptsSource = `versions:${range}`;
14107
14414
  if (providerDir) {
14108
- const fullDir = path14.join(providerDir, dirOverride);
14109
- resolved._resolvedScriptsPath = fs6.existsSync(path14.join(fullDir, "scripts.js")) ? path14.join(fullDir, "scripts.js") : fullDir;
14415
+ const fullDir = path13.join(providerDir, dirOverride);
14416
+ resolved._resolvedScriptsPath = fs6.existsSync(path13.join(fullDir, "scripts.js")) ? path13.join(fullDir, "scripts.js") : fullDir;
14110
14417
  }
14111
14418
  }
14112
14419
  } else if (override.scripts) {
@@ -14122,8 +14429,8 @@ var ProviderLoader = class _ProviderLoader {
14122
14429
  resolved._resolvedScriptDir = base.defaultScriptDir;
14123
14430
  resolved._resolvedScriptsSource = "defaultScriptDir:no_version";
14124
14431
  if (providerDir) {
14125
- const fullDir = path14.join(providerDir, base.defaultScriptDir);
14126
- resolved._resolvedScriptsPath = fs6.existsSync(path14.join(fullDir, "scripts.js")) ? path14.join(fullDir, "scripts.js") : fullDir;
14432
+ const fullDir = path13.join(providerDir, base.defaultScriptDir);
14433
+ resolved._resolvedScriptsPath = fs6.existsSync(path13.join(fullDir, "scripts.js")) ? path13.join(fullDir, "scripts.js") : fullDir;
14127
14434
  }
14128
14435
  }
14129
14436
  }
@@ -14148,14 +14455,14 @@ var ProviderLoader = class _ProviderLoader {
14148
14455
  this.log(` [loadScriptsFromDir] ${type}: providerDir not found`);
14149
14456
  return null;
14150
14457
  }
14151
- const dir = path14.join(providerDir, scriptDir);
14458
+ const dir = path13.join(providerDir, scriptDir);
14152
14459
  if (!fs6.existsSync(dir)) {
14153
14460
  this.log(` [loadScriptsFromDir] ${type}: dir not found: ${dir}`);
14154
14461
  return null;
14155
14462
  }
14156
14463
  const cached = this.scriptsCache.get(dir);
14157
14464
  if (cached) return cached;
14158
- const scriptsJs = path14.join(dir, "scripts.js");
14465
+ const scriptsJs = path13.join(dir, "scripts.js");
14159
14466
  if (fs6.existsSync(scriptsJs)) {
14160
14467
  try {
14161
14468
  delete require.cache[require.resolve(scriptsJs)];
@@ -14197,7 +14504,7 @@ var ProviderLoader = class _ProviderLoader {
14197
14504
  return;
14198
14505
  }
14199
14506
  if (filePath.endsWith(".js") || filePath.endsWith(".json")) {
14200
- this.log(`File changed: ${path14.basename(filePath)}, reloading...`);
14507
+ this.log(`File changed: ${path13.basename(filePath)}, reloading...`);
14201
14508
  this.reload();
14202
14509
  }
14203
14510
  };
@@ -14252,7 +14559,7 @@ var ProviderLoader = class _ProviderLoader {
14252
14559
  }
14253
14560
  const https = require("https");
14254
14561
  const { execSync: execSync7 } = require("child_process");
14255
- const metaPath = path14.join(this.upstreamDir, _ProviderLoader.META_FILE);
14562
+ const metaPath = path13.join(this.upstreamDir, _ProviderLoader.META_FILE);
14256
14563
  let prevEtag = "";
14257
14564
  let prevTimestamp = 0;
14258
14565
  try {
@@ -14312,17 +14619,17 @@ var ProviderLoader = class _ProviderLoader {
14312
14619
  return { updated: false };
14313
14620
  }
14314
14621
  this.log("Downloading latest providers from GitHub...");
14315
- const tmpTar = path14.join(os13.tmpdir(), `adhdev-providers-${Date.now()}.tar.gz`);
14316
- const tmpExtract = path14.join(os13.tmpdir(), `adhdev-providers-extract-${Date.now()}`);
14622
+ const tmpTar = path13.join(os13.tmpdir(), `adhdev-providers-${Date.now()}.tar.gz`);
14623
+ const tmpExtract = path13.join(os13.tmpdir(), `adhdev-providers-extract-${Date.now()}`);
14317
14624
  await this.downloadFile(_ProviderLoader.GITHUB_TARBALL_URL, tmpTar);
14318
14625
  fs6.mkdirSync(tmpExtract, { recursive: true });
14319
14626
  execSync7(`tar -xzf "${tmpTar}" -C "${tmpExtract}"`, { timeout: 3e4 });
14320
14627
  const extracted = fs6.readdirSync(tmpExtract);
14321
14628
  const rootDir = extracted.find(
14322
- (d) => fs6.statSync(path14.join(tmpExtract, d)).isDirectory() && d.startsWith("adhdev-providers")
14629
+ (d) => fs6.statSync(path13.join(tmpExtract, d)).isDirectory() && d.startsWith("adhdev-providers")
14323
14630
  );
14324
14631
  if (!rootDir) throw new Error("Unexpected tarball structure");
14325
- const sourceDir = path14.join(tmpExtract, rootDir);
14632
+ const sourceDir = path13.join(tmpExtract, rootDir);
14326
14633
  const backupDir = this.upstreamDir + ".bak";
14327
14634
  if (fs6.existsSync(this.upstreamDir)) {
14328
14635
  if (fs6.existsSync(backupDir)) fs6.rmSync(backupDir, { recursive: true, force: true });
@@ -14397,8 +14704,8 @@ var ProviderLoader = class _ProviderLoader {
14397
14704
  copyDirRecursive(src, dest) {
14398
14705
  fs6.mkdirSync(dest, { recursive: true });
14399
14706
  for (const entry of fs6.readdirSync(src, { withFileTypes: true })) {
14400
- const srcPath = path14.join(src, entry.name);
14401
- const destPath = path14.join(dest, entry.name);
14707
+ const srcPath = path13.join(src, entry.name);
14708
+ const destPath = path13.join(dest, entry.name);
14402
14709
  if (entry.isDirectory()) {
14403
14710
  this.copyDirRecursive(srcPath, destPath);
14404
14711
  } else {
@@ -14409,7 +14716,7 @@ var ProviderLoader = class _ProviderLoader {
14409
14716
  /** .meta.json save */
14410
14717
  writeMeta(metaPath, etag, timestamp) {
14411
14718
  try {
14412
- fs6.mkdirSync(path14.dirname(metaPath), { recursive: true });
14719
+ fs6.mkdirSync(path13.dirname(metaPath), { recursive: true });
14413
14720
  fs6.writeFileSync(metaPath, JSON.stringify({
14414
14721
  etag,
14415
14722
  timestamp,
@@ -14426,7 +14733,7 @@ var ProviderLoader = class _ProviderLoader {
14426
14733
  const scan = (d) => {
14427
14734
  try {
14428
14735
  for (const entry of fs6.readdirSync(d, { withFileTypes: true })) {
14429
- if (entry.isDirectory()) scan(path14.join(d, entry.name));
14736
+ if (entry.isDirectory()) scan(path13.join(d, entry.name));
14430
14737
  else if (entry.name === "provider.json") count++;
14431
14738
  }
14432
14739
  } catch {
@@ -14611,17 +14918,17 @@ var ProviderLoader = class _ProviderLoader {
14611
14918
  for (const root of searchRoots) {
14612
14919
  if (!fs6.existsSync(root)) continue;
14613
14920
  const candidate = this.getProviderDir(root, cat, type);
14614
- if (fs6.existsSync(path14.join(candidate, "provider.json"))) return candidate;
14615
- const catDir = path14.join(root, cat);
14921
+ if (fs6.existsSync(path13.join(candidate, "provider.json"))) return candidate;
14922
+ const catDir = path13.join(root, cat);
14616
14923
  if (fs6.existsSync(catDir)) {
14617
14924
  try {
14618
14925
  for (const entry of fs6.readdirSync(catDir, { withFileTypes: true })) {
14619
14926
  if (!entry.isDirectory()) continue;
14620
- const jsonPath = path14.join(catDir, entry.name, "provider.json");
14927
+ const jsonPath = path13.join(catDir, entry.name, "provider.json");
14621
14928
  if (fs6.existsSync(jsonPath)) {
14622
14929
  try {
14623
14930
  const data = JSON.parse(fs6.readFileSync(jsonPath, "utf-8"));
14624
- if (data.type === type) return path14.join(catDir, entry.name);
14931
+ if (data.type === type) return path13.join(catDir, entry.name);
14625
14932
  } catch {
14626
14933
  }
14627
14934
  }
@@ -14638,7 +14945,7 @@ var ProviderLoader = class _ProviderLoader {
14638
14945
  * (template substitution is NOT applied here — scripts.js handles that)
14639
14946
  */
14640
14947
  buildScriptWrappersFromDir(dir) {
14641
- const scriptsJs = path14.join(dir, "scripts.js");
14948
+ const scriptsJs = path13.join(dir, "scripts.js");
14642
14949
  if (fs6.existsSync(scriptsJs)) {
14643
14950
  try {
14644
14951
  delete require.cache[require.resolve(scriptsJs)];
@@ -14652,7 +14959,7 @@ var ProviderLoader = class _ProviderLoader {
14652
14959
  for (const file of fs6.readdirSync(dir)) {
14653
14960
  if (!file.endsWith(".js")) continue;
14654
14961
  const scriptName = toCamel(file.replace(".js", ""));
14655
- const filePath = path14.join(dir, file);
14962
+ const filePath = path13.join(dir, file);
14656
14963
  result[scriptName] = (...args) => {
14657
14964
  try {
14658
14965
  let content = fs6.readFileSync(filePath, "utf-8");
@@ -14712,7 +15019,7 @@ var ProviderLoader = class _ProviderLoader {
14712
15019
  }
14713
15020
  const hasJson = entries.some((e) => e.name === "provider.json");
14714
15021
  if (hasJson) {
14715
- const jsonPath = path14.join(d, "provider.json");
15022
+ const jsonPath = path13.join(d, "provider.json");
14716
15023
  try {
14717
15024
  const raw = fs6.readFileSync(jsonPath, "utf-8");
14718
15025
  const mod = JSON.parse(raw);
@@ -14733,7 +15040,7 @@ var ProviderLoader = class _ProviderLoader {
14733
15040
  this.log(`\u26A0 Invalid provider at ${jsonPath}: ${validation.errors.join("; ")}`);
14734
15041
  } else {
14735
15042
  const hasCompatibility = Array.isArray(normalizedProvider.compatibility);
14736
- const scriptsPath = path14.join(d, "scripts.js");
15043
+ const scriptsPath = path13.join(d, "scripts.js");
14737
15044
  if (!hasCompatibility && fs6.existsSync(scriptsPath)) {
14738
15045
  try {
14739
15046
  delete require.cache[require.resolve(scriptsPath)];
@@ -14759,7 +15066,7 @@ var ProviderLoader = class _ProviderLoader {
14759
15066
  if (!entry.isDirectory()) continue;
14760
15067
  if (entry.name.startsWith("_") || entry.name.startsWith(".")) continue;
14761
15068
  if (excludeDirs && d === dir && excludeDirs.includes(entry.name)) continue;
14762
- scan(path14.join(d, entry.name));
15069
+ scan(path13.join(d, entry.name));
14763
15070
  }
14764
15071
  }
14765
15072
  };
@@ -15017,8 +15324,8 @@ function detectCurrentWorkspace(ideId) {
15017
15324
  const appNameMap = getMacAppIdentifiers();
15018
15325
  const appName = appNameMap[ideId];
15019
15326
  if (appName) {
15020
- const storagePath = path15.join(
15021
- process.env.APPDATA || path15.join(os14.homedir(), "AppData", "Roaming"),
15327
+ const storagePath = path14.join(
15328
+ process.env.APPDATA || path14.join(os14.homedir(), "AppData", "Roaming"),
15022
15329
  appName,
15023
15330
  "storage.json"
15024
15331
  );
@@ -15196,9 +15503,9 @@ init_logger();
15196
15503
 
15197
15504
  // src/logging/command-log.ts
15198
15505
  var fs7 = __toESM(require("fs"));
15199
- var path16 = __toESM(require("path"));
15506
+ var path15 = __toESM(require("path"));
15200
15507
  var os15 = __toESM(require("os"));
15201
- var LOG_DIR2 = process.platform === "win32" ? path16.join(process.env.LOCALAPPDATA || process.env.APPDATA || path16.join(os15.homedir(), "AppData", "Local"), "adhdev", "logs") : process.platform === "darwin" ? path16.join(os15.homedir(), "Library", "Logs", "adhdev") : path16.join(os15.homedir(), ".local", "share", "adhdev", "logs");
15508
+ var LOG_DIR2 = process.platform === "win32" ? path15.join(process.env.LOCALAPPDATA || process.env.APPDATA || path15.join(os15.homedir(), "AppData", "Local"), "adhdev", "logs") : process.platform === "darwin" ? path15.join(os15.homedir(), "Library", "Logs", "adhdev") : path15.join(os15.homedir(), ".local", "share", "adhdev", "logs");
15202
15509
  var MAX_FILE_SIZE = 5 * 1024 * 1024;
15203
15510
  var MAX_DAYS = 7;
15204
15511
  try {
@@ -15236,13 +15543,13 @@ function getDateStr2() {
15236
15543
  return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
15237
15544
  }
15238
15545
  var currentDate2 = getDateStr2();
15239
- var currentFile = path16.join(LOG_DIR2, `commands-${currentDate2}.jsonl`);
15546
+ var currentFile = path15.join(LOG_DIR2, `commands-${currentDate2}.jsonl`);
15240
15547
  var writeCount2 = 0;
15241
15548
  function checkRotation() {
15242
15549
  const today = getDateStr2();
15243
15550
  if (today !== currentDate2) {
15244
15551
  currentDate2 = today;
15245
- currentFile = path16.join(LOG_DIR2, `commands-${currentDate2}.jsonl`);
15552
+ currentFile = path15.join(LOG_DIR2, `commands-${currentDate2}.jsonl`);
15246
15553
  cleanOldFiles();
15247
15554
  }
15248
15555
  }
@@ -15256,7 +15563,7 @@ function cleanOldFiles() {
15256
15563
  const dateMatch = file.match(/commands-(\d{4}-\d{2}-\d{2})/);
15257
15564
  if (dateMatch && dateMatch[1] < cutoffStr) {
15258
15565
  try {
15259
- fs7.unlinkSync(path16.join(LOG_DIR2, file));
15566
+ fs7.unlinkSync(path15.join(LOG_DIR2, file));
15260
15567
  } catch {
15261
15568
  }
15262
15569
  }
@@ -15669,13 +15976,13 @@ var import_child_process7 = require("child_process");
15669
15976
  var import_child_process8 = require("child_process");
15670
15977
  var fs8 = __toESM(require("fs"));
15671
15978
  var os17 = __toESM(require("os"));
15672
- var path17 = __toESM(require("path"));
15979
+ var path16 = __toESM(require("path"));
15673
15980
  var UPGRADE_HELPER_ENV = "ADHDEV_DAEMON_UPGRADE_HELPER";
15674
15981
  function getUpgradeLogPath() {
15675
15982
  const home = os17.homedir();
15676
- const dir = path17.join(home, ".adhdev");
15983
+ const dir = path16.join(home, ".adhdev");
15677
15984
  fs8.mkdirSync(dir, { recursive: true });
15678
- return path17.join(dir, "daemon-upgrade.log");
15985
+ return path16.join(dir, "daemon-upgrade.log");
15679
15986
  }
15680
15987
  function appendUpgradeLog(message) {
15681
15988
  const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] ${message}
@@ -15715,7 +16022,7 @@ async function waitForPidExit(pid, timeoutMs) {
15715
16022
  }
15716
16023
  }
15717
16024
  function stopSessionHostProcesses(appName) {
15718
- const pidFile = path17.join(os17.homedir(), ".adhdev", `${appName}-session-host.pid`);
16025
+ const pidFile = path16.join(os17.homedir(), ".adhdev", `${appName}-session-host.pid`);
15719
16026
  try {
15720
16027
  if (fs8.existsSync(pidFile)) {
15721
16028
  const pid = Number.parseInt(fs8.readFileSync(pidFile, "utf8").trim(), 10);
@@ -15744,7 +16051,7 @@ function stopSessionHostProcesses(appName) {
15744
16051
  }
15745
16052
  }
15746
16053
  function removeDaemonPidFile() {
15747
- const pidFile = path17.join(os17.homedir(), ".adhdev", "daemon.pid");
16054
+ const pidFile = path16.join(os17.homedir(), ".adhdev", "daemon.pid");
15748
16055
  try {
15749
16056
  fs8.unlinkSync(pidFile);
15750
16057
  } catch {
@@ -15755,7 +16062,7 @@ function cleanupStaleGlobalInstallDirs(pkgName) {
15755
16062
  const npmRoot = (0, import_child_process7.execFileSync)(getNpmExecutable(), ["root", "-g"], { encoding: "utf8", ...npmExecOpts }).trim();
15756
16063
  if (!npmRoot) return;
15757
16064
  const npmPrefix = (0, import_child_process7.execFileSync)(getNpmExecutable(), ["prefix", "-g"], { encoding: "utf8", ...npmExecOpts }).trim();
15758
- const binDir = process.platform === "win32" ? npmPrefix : path17.join(npmPrefix, "bin");
16065
+ const binDir = process.platform === "win32" ? npmPrefix : path16.join(npmPrefix, "bin");
15759
16066
  const packageBaseName = pkgName.startsWith("@") ? pkgName.split("/")[1] : pkgName;
15760
16067
  const binNames = /* @__PURE__ */ new Set([packageBaseName]);
15761
16068
  if (pkgName === "@adhdev/daemon-standalone") {
@@ -15763,25 +16070,25 @@ function cleanupStaleGlobalInstallDirs(pkgName) {
15763
16070
  }
15764
16071
  if (pkgName.startsWith("@")) {
15765
16072
  const [scope, name] = pkgName.split("/");
15766
- const scopeDir = path17.join(npmRoot, scope);
16073
+ const scopeDir = path16.join(npmRoot, scope);
15767
16074
  if (!fs8.existsSync(scopeDir)) return;
15768
16075
  for (const entry of fs8.readdirSync(scopeDir)) {
15769
16076
  if (!entry.startsWith(`.${name}-`)) continue;
15770
- fs8.rmSync(path17.join(scopeDir, entry), { recursive: true, force: true });
15771
- appendUpgradeLog(`Removed stale scoped staging dir: ${path17.join(scopeDir, entry)}`);
16077
+ fs8.rmSync(path16.join(scopeDir, entry), { recursive: true, force: true });
16078
+ appendUpgradeLog(`Removed stale scoped staging dir: ${path16.join(scopeDir, entry)}`);
15772
16079
  }
15773
16080
  } else {
15774
16081
  for (const entry of fs8.readdirSync(npmRoot)) {
15775
16082
  if (!entry.startsWith(`.${pkgName}-`)) continue;
15776
- fs8.rmSync(path17.join(npmRoot, entry), { recursive: true, force: true });
15777
- appendUpgradeLog(`Removed stale staging dir: ${path17.join(npmRoot, entry)}`);
16083
+ fs8.rmSync(path16.join(npmRoot, entry), { recursive: true, force: true });
16084
+ appendUpgradeLog(`Removed stale staging dir: ${path16.join(npmRoot, entry)}`);
15778
16085
  }
15779
16086
  }
15780
16087
  if (fs8.existsSync(binDir)) {
15781
16088
  for (const entry of fs8.readdirSync(binDir)) {
15782
16089
  if (![...binNames].some((name) => entry.startsWith(`.${name}-`))) continue;
15783
- fs8.rmSync(path17.join(binDir, entry), { recursive: true, force: true });
15784
- appendUpgradeLog(`Removed stale bin staging entry: ${path17.join(binDir, entry)}`);
16090
+ fs8.rmSync(path16.join(binDir, entry), { recursive: true, force: true });
16091
+ appendUpgradeLog(`Removed stale bin staging entry: ${path16.join(binDir, entry)}`);
15785
16092
  }
15786
16093
  }
15787
16094
  }
@@ -16811,6 +17118,7 @@ var DEFAULT_DAEMON_PORT = 19222;
16811
17118
  var DAEMON_WS_PATH = "/ipc";
16812
17119
 
16813
17120
  // src/agent-stream/provider-adapter.ts
17121
+ init_read_chat_contract();
16814
17122
  init_chat_message_normalization();
16815
17123
  var ProviderStreamAdapter = class {
16816
17124
  agentType;
@@ -16916,26 +17224,29 @@ var ProviderStreamAdapter = class {
16916
17224
  }
16917
17225
  return state2;
16918
17226
  }
17227
+ const validated = validateReadChatResultPayload(data, `${this.agentType} readChat`);
17228
+ const validatedStatus = validated.status;
17229
+ const streamStatus = validatedStatus === "generating" || validatedStatus === "long_generating" ? "streaming" : validatedStatus;
16919
17230
  const state = {
16920
17231
  agentType: this.agentType,
16921
17232
  agentName: this.agentName,
16922
17233
  extensionId: this.extensionId,
16923
- status: data.status || "idle",
16924
- messages: normalizeChatMessages(Array.isArray(data.messages) ? data.messages : []),
16925
- inputContent: data.inputContent || "",
16926
- activeModal: data.activeModal
17234
+ status: streamStatus,
17235
+ messages: normalizeChatMessages(validated.messages),
17236
+ inputContent: typeof validated.inputContent === "string" ? validated.inputContent : "",
17237
+ ...validated.activeModal ? { activeModal: validated.activeModal } : {}
16927
17238
  };
16928
- if (typeof data.title === "string" && data.title.trim()) {
16929
- state.title = data.title.trim();
17239
+ if (typeof validated.title === "string" && validated.title.trim()) {
17240
+ state.title = validated.title.trim();
16930
17241
  }
16931
- const controlValues = extractProviderControlValues(this.provider.controls, data);
17242
+ const controlValues = extractProviderControlValues(this.provider.controls, validated);
16932
17243
  const surface = resolveProviderStateSurface({
16933
17244
  controlValues,
16934
- summaryMetadata: data.summaryMetadata
17245
+ summaryMetadata: validated.summaryMetadata
16935
17246
  });
16936
17247
  if (surface.controlValues) state.controlValues = surface.controlValues;
16937
17248
  if (surface.summaryMetadata) state.summaryMetadata = surface.summaryMetadata;
16938
- const effects = normalizeProviderEffects(data);
17249
+ const effects = normalizeProviderEffects(validated);
16939
17250
  if (effects.length > 0) state.effects = effects;
16940
17251
  if (state.messages.length > 0) {
16941
17252
  this.lastSuccessState = state;
@@ -17774,15 +18085,16 @@ var ProviderInstanceManager = class {
17774
18085
  };
17775
18086
 
17776
18087
  // src/index.ts
18088
+ init_io_contracts();
17777
18089
  init_chat_message_normalization();
17778
18090
 
17779
18091
  // src/providers/version-archive.ts
17780
18092
  var fs10 = __toESM(require("fs"));
17781
- var path18 = __toESM(require("path"));
18093
+ var path17 = __toESM(require("path"));
17782
18094
  var os18 = __toESM(require("os"));
17783
18095
  var import_child_process9 = require("child_process");
17784
18096
  var import_os3 = require("os");
17785
- var ARCHIVE_PATH = path18.join(os18.homedir(), ".adhdev", "version-history.json");
18097
+ var ARCHIVE_PATH = path17.join(os18.homedir(), ".adhdev", "version-history.json");
17786
18098
  var MAX_ENTRIES_PER_PROVIDER = 20;
17787
18099
  var VersionArchive = class {
17788
18100
  history = {};
@@ -17829,7 +18141,7 @@ var VersionArchive = class {
17829
18141
  }
17830
18142
  save() {
17831
18143
  try {
17832
- fs10.mkdirSync(path18.dirname(ARCHIVE_PATH), { recursive: true });
18144
+ fs10.mkdirSync(path17.dirname(ARCHIVE_PATH), { recursive: true });
17833
18145
  fs10.writeFileSync(ARCHIVE_PATH, JSON.stringify(this.history, null, 2));
17834
18146
  } catch {
17835
18147
  }
@@ -17886,7 +18198,7 @@ function checkPathExists2(paths) {
17886
18198
  for (const p of paths) {
17887
18199
  if (p.includes("*")) {
17888
18200
  const home = os18.homedir();
17889
- const resolved = p.replace(/\*/g, home.split(path18.sep).pop() || "");
18201
+ const resolved = p.replace(/\*/g, home.split(path17.sep).pop() || "");
17890
18202
  if (fs10.existsSync(resolved)) return resolved;
17891
18203
  } else {
17892
18204
  if (fs10.existsSync(p)) return p;
@@ -17896,7 +18208,7 @@ function checkPathExists2(paths) {
17896
18208
  }
17897
18209
  function getMacAppVersion(appPath) {
17898
18210
  if ((0, import_os3.platform)() !== "darwin" || !appPath.endsWith(".app")) return null;
17899
- const plistPath = path18.join(appPath, "Contents", "Info.plist");
18211
+ const plistPath = path17.join(appPath, "Contents", "Info.plist");
17900
18212
  if (!fs10.existsSync(plistPath)) return null;
17901
18213
  const raw = runCommand(`/usr/libexec/PlistBuddy -c "Print CFBundleShortVersionString" "${plistPath}"`);
17902
18214
  return raw || null;
@@ -17922,7 +18234,7 @@ async function detectAllVersions(loader, archive) {
17922
18234
  const cliBin = provider.cli ? findBinary2(provider.cli) : null;
17923
18235
  let resolvedBin = cliBin;
17924
18236
  if (!resolvedBin && appPath && currentOs === "darwin") {
17925
- const bundled = path18.join(appPath, "Contents", "Resources", "app", "bin", provider.cli || "");
18237
+ const bundled = path17.join(appPath, "Contents", "Resources", "app", "bin", provider.cli || "");
17926
18238
  if (provider.cli && fs10.existsSync(bundled)) resolvedBin = bundled;
17927
18239
  }
17928
18240
  info.installed = !!(appPath || resolvedBin);
@@ -17963,7 +18275,7 @@ async function detectAllVersions(loader, archive) {
17963
18275
  // src/daemon/dev-server.ts
17964
18276
  var http2 = __toESM(require("http"));
17965
18277
  var fs14 = __toESM(require("fs"));
17966
- var path22 = __toESM(require("path"));
18278
+ var path21 = __toESM(require("path"));
17967
18279
  init_config();
17968
18280
 
17969
18281
  // src/daemon/scaffold-template.ts
@@ -18307,7 +18619,7 @@ init_logger();
18307
18619
 
18308
18620
  // src/daemon/dev-cdp-handlers.ts
18309
18621
  var fs11 = __toESM(require("fs"));
18310
- var path19 = __toESM(require("path"));
18622
+ var path18 = __toESM(require("path"));
18311
18623
  init_logger();
18312
18624
  async function handleCdpEvaluate(ctx, req, res) {
18313
18625
  const body = await ctx.readBody(req);
@@ -18486,17 +18798,17 @@ async function handleScriptHints(ctx, type, _req, res) {
18486
18798
  return;
18487
18799
  }
18488
18800
  let scriptsPath = "";
18489
- const directScripts = path19.join(dir, "scripts.js");
18801
+ const directScripts = path18.join(dir, "scripts.js");
18490
18802
  if (fs11.existsSync(directScripts)) {
18491
18803
  scriptsPath = directScripts;
18492
18804
  } else {
18493
- const scriptsDir = path19.join(dir, "scripts");
18805
+ const scriptsDir = path18.join(dir, "scripts");
18494
18806
  if (fs11.existsSync(scriptsDir)) {
18495
18807
  const versions = fs11.readdirSync(scriptsDir).filter((d) => {
18496
- return fs11.statSync(path19.join(scriptsDir, d)).isDirectory();
18808
+ return fs11.statSync(path18.join(scriptsDir, d)).isDirectory();
18497
18809
  }).sort().reverse();
18498
18810
  for (const ver of versions) {
18499
- const p = path19.join(scriptsDir, ver, "scripts.js");
18811
+ const p = path18.join(scriptsDir, ver, "scripts.js");
18500
18812
  if (fs11.existsSync(p)) {
18501
18813
  scriptsPath = p;
18502
18814
  break;
@@ -19325,7 +19637,7 @@ async function handleDomContext(ctx, type, req, res) {
19325
19637
 
19326
19638
  // src/daemon/dev-cli-debug.ts
19327
19639
  var fs12 = __toESM(require("fs"));
19328
- var path20 = __toESM(require("path"));
19640
+ var path19 = __toESM(require("path"));
19329
19641
  function slugifyFixtureName(value) {
19330
19642
  const normalized = String(value || "").trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
19331
19643
  return normalized || `fixture-${Date.now()}`;
@@ -19335,11 +19647,11 @@ function getCliFixtureDir(ctx, type) {
19335
19647
  if (!providerDir) {
19336
19648
  throw new Error(`Provider directory not found for '${type}'`);
19337
19649
  }
19338
- return path20.join(providerDir, "fixtures");
19650
+ return path19.join(providerDir, "fixtures");
19339
19651
  }
19340
19652
  function readCliFixture(ctx, type, name) {
19341
19653
  const fixtureDir = getCliFixtureDir(ctx, type);
19342
- const filePath = path20.join(fixtureDir, `${name}.json`);
19654
+ const filePath = path19.join(fixtureDir, `${name}.json`);
19343
19655
  if (!fs12.existsSync(filePath)) {
19344
19656
  throw new Error(`Fixture not found: ${filePath}`);
19345
19657
  }
@@ -20106,7 +20418,7 @@ async function handleCliFixtureCapture(ctx, req, res) {
20106
20418
  },
20107
20419
  notes: typeof body?.notes === "string" ? body.notes : void 0
20108
20420
  };
20109
- const filePath = path20.join(fixtureDir, `${name}.json`);
20421
+ const filePath = path19.join(fixtureDir, `${name}.json`);
20110
20422
  fs12.writeFileSync(filePath, JSON.stringify(fixture, null, 2));
20111
20423
  ctx.json(res, 200, {
20112
20424
  saved: true,
@@ -20130,7 +20442,7 @@ async function handleCliFixtureList(ctx, type, _req, res) {
20130
20442
  return;
20131
20443
  }
20132
20444
  const fixtures = fs12.readdirSync(fixtureDir).filter((file) => file.endsWith(".json")).sort((a, b) => b.localeCompare(a, void 0, { numeric: true, sensitivity: "base" })).map((file) => {
20133
- const fullPath = path20.join(fixtureDir, file);
20445
+ const fullPath = path19.join(fixtureDir, file);
20134
20446
  try {
20135
20447
  const raw = JSON.parse(fs12.readFileSync(fullPath, "utf-8"));
20136
20448
  return {
@@ -20266,7 +20578,7 @@ async function handleCliRaw(ctx, req, res) {
20266
20578
 
20267
20579
  // src/daemon/dev-auto-implement.ts
20268
20580
  var fs13 = __toESM(require("fs"));
20269
- var path21 = __toESM(require("path"));
20581
+ var path20 = __toESM(require("path"));
20270
20582
  var os19 = __toESM(require("os"));
20271
20583
  function getAutoImplPid(ctx) {
20272
20584
  const pid = ctx.autoImplProcess?.pid;
@@ -20323,22 +20635,22 @@ function getLatestScriptVersionDir(scriptsDir) {
20323
20635
  if (!fs13.existsSync(scriptsDir)) return null;
20324
20636
  const versions = fs13.readdirSync(scriptsDir).filter((d) => {
20325
20637
  try {
20326
- return fs13.statSync(path21.join(scriptsDir, d)).isDirectory();
20638
+ return fs13.statSync(path20.join(scriptsDir, d)).isDirectory();
20327
20639
  } catch {
20328
20640
  return false;
20329
20641
  }
20330
20642
  }).sort((a, b) => b.localeCompare(a, void 0, { numeric: true, sensitivity: "base" }));
20331
20643
  if (versions.length === 0) return null;
20332
- return path21.join(scriptsDir, versions[0]);
20644
+ return path20.join(scriptsDir, versions[0]);
20333
20645
  }
20334
20646
  function resolveAutoImplWritableProviderDir(ctx, category, type, requestedDir) {
20335
- const canonicalUserDir = path21.resolve(ctx.providerLoader.getUserProviderDir(category, type));
20336
- const desiredDir = requestedDir ? path21.resolve(requestedDir) : canonicalUserDir;
20337
- const upstreamRoot = path21.resolve(ctx.providerLoader.getUpstreamDir());
20338
- if (desiredDir === upstreamRoot || desiredDir.startsWith(`${upstreamRoot}${path21.sep}`)) {
20647
+ const canonicalUserDir = path20.resolve(ctx.providerLoader.getUserProviderDir(category, type));
20648
+ const desiredDir = requestedDir ? path20.resolve(requestedDir) : canonicalUserDir;
20649
+ const upstreamRoot = path20.resolve(ctx.providerLoader.getUpstreamDir());
20650
+ if (desiredDir === upstreamRoot || desiredDir.startsWith(`${upstreamRoot}${path20.sep}`)) {
20339
20651
  return { dir: null, reason: `Refusing to write into upstream provider directory: ${desiredDir}` };
20340
20652
  }
20341
- if (path21.basename(desiredDir) !== type) {
20653
+ if (path20.basename(desiredDir) !== type) {
20342
20654
  return { dir: null, reason: `Requested writable provider directory must end with '${type}': ${desiredDir}` };
20343
20655
  }
20344
20656
  const sourceDir = ctx.findProviderDir(type);
@@ -20346,11 +20658,11 @@ function resolveAutoImplWritableProviderDir(ctx, category, type, requestedDir) {
20346
20658
  return { dir: null, reason: `Provider source directory not found for '${type}'` };
20347
20659
  }
20348
20660
  if (!fs13.existsSync(desiredDir)) {
20349
- fs13.mkdirSync(path21.dirname(desiredDir), { recursive: true });
20661
+ fs13.mkdirSync(path20.dirname(desiredDir), { recursive: true });
20350
20662
  fs13.cpSync(sourceDir, desiredDir, { recursive: true });
20351
20663
  ctx.log(`Auto-implement writable copy created: ${desiredDir}`);
20352
20664
  }
20353
- const providerJson = path21.join(desiredDir, "provider.json");
20665
+ const providerJson = path20.join(desiredDir, "provider.json");
20354
20666
  if (!fs13.existsSync(providerJson)) {
20355
20667
  return { dir: null, reason: `provider.json not found in writable provider directory: ${desiredDir}` };
20356
20668
  }
@@ -20361,13 +20673,13 @@ function loadAutoImplReferenceScripts(ctx, referenceType) {
20361
20673
  const refDir = ctx.findProviderDir(referenceType);
20362
20674
  if (!refDir || !fs13.existsSync(refDir)) return {};
20363
20675
  const referenceScripts = {};
20364
- const scriptsDir = path21.join(refDir, "scripts");
20676
+ const scriptsDir = path20.join(refDir, "scripts");
20365
20677
  const latestDir = getLatestScriptVersionDir(scriptsDir);
20366
20678
  if (!latestDir) return referenceScripts;
20367
20679
  for (const file of fs13.readdirSync(latestDir)) {
20368
20680
  if (!file.endsWith(".js")) continue;
20369
20681
  try {
20370
- referenceScripts[file] = fs13.readFileSync(path21.join(latestDir, file), "utf-8");
20682
+ referenceScripts[file] = fs13.readFileSync(path20.join(latestDir, file), "utf-8");
20371
20683
  } catch {
20372
20684
  }
20373
20685
  }
@@ -20475,9 +20787,9 @@ async function handleAutoImplement(ctx, type, req, res) {
20475
20787
  });
20476
20788
  const referenceScripts = loadAutoImplReferenceScripts(ctx, resolvedReference);
20477
20789
  const prompt = buildAutoImplPrompt(ctx, type, provider, providerDir, functions, domContext, referenceScripts, comment, resolvedReference, verification);
20478
- const tmpDir = path21.join(os19.tmpdir(), "adhdev-autoimpl");
20790
+ const tmpDir = path20.join(os19.tmpdir(), "adhdev-autoimpl");
20479
20791
  if (!fs13.existsSync(tmpDir)) fs13.mkdirSync(tmpDir, { recursive: true });
20480
- const promptFile = path21.join(tmpDir, `prompt-${type}-${Date.now()}.md`);
20792
+ const promptFile = path20.join(tmpDir, `prompt-${type}-${Date.now()}.md`);
20481
20793
  fs13.writeFileSync(promptFile, prompt, "utf-8");
20482
20794
  ctx.log(`Auto-implement prompt written to ${promptFile} (${prompt.length} chars)`);
20483
20795
  const agentProvider = ctx.providerLoader.resolve(agent) || ctx.providerLoader.getMeta(agent);
@@ -20914,7 +21226,7 @@ function buildAutoImplPrompt(ctx, type, provider, providerDir, functions, domCon
20914
21226
  setMode: "set_mode.js"
20915
21227
  };
20916
21228
  const targetFileNames = new Set(functions.map((fn) => funcToFile[fn]).filter(Boolean));
20917
- const scriptsDir = path21.join(providerDir, "scripts");
21229
+ const scriptsDir = path20.join(providerDir, "scripts");
20918
21230
  const latestScriptsDir = getLatestScriptVersionDir(scriptsDir);
20919
21231
  if (latestScriptsDir) {
20920
21232
  lines.push(`Scripts version directory: \`${latestScriptsDir}\``);
@@ -20925,7 +21237,7 @@ function buildAutoImplPrompt(ctx, type, provider, providerDir, functions, domCon
20925
21237
  for (const file of fs13.readdirSync(latestScriptsDir)) {
20926
21238
  if (file.endsWith(".js") && targetFileNames.has(file)) {
20927
21239
  try {
20928
- const content = fs13.readFileSync(path21.join(latestScriptsDir, file), "utf-8");
21240
+ const content = fs13.readFileSync(path20.join(latestScriptsDir, file), "utf-8");
20929
21241
  lines.push(`### \`${file}\` \u270F\uFE0F EDIT`);
20930
21242
  lines.push("```javascript");
20931
21243
  lines.push(content);
@@ -20942,7 +21254,7 @@ function buildAutoImplPrompt(ctx, type, provider, providerDir, functions, domCon
20942
21254
  lines.push("");
20943
21255
  for (const file of refFiles) {
20944
21256
  try {
20945
- const content = fs13.readFileSync(path21.join(latestScriptsDir, file), "utf-8");
21257
+ const content = fs13.readFileSync(path20.join(latestScriptsDir, file), "utf-8");
20946
21258
  lines.push(`### \`${file}\` \u{1F512}`);
20947
21259
  lines.push("```javascript");
20948
21260
  lines.push(content);
@@ -20983,10 +21295,10 @@ function buildAutoImplPrompt(ctx, type, provider, providerDir, functions, domCon
20983
21295
  lines.push("");
20984
21296
  }
20985
21297
  }
20986
- const docsDir = path21.join(providerDir, "../../docs");
21298
+ const docsDir = path20.join(providerDir, "../../docs");
20987
21299
  const loadGuide = (name) => {
20988
21300
  try {
20989
- const p = path21.join(docsDir, name);
21301
+ const p = path20.join(docsDir, name);
20990
21302
  if (fs13.existsSync(p)) return fs13.readFileSync(p, "utf-8");
20991
21303
  } catch {
20992
21304
  }
@@ -21223,7 +21535,7 @@ function buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, ref
21223
21535
  parseApproval: "parse_approval.js"
21224
21536
  };
21225
21537
  const targetFileNames = new Set(functions.map((fn) => funcToFile[fn]).filter(Boolean));
21226
- const scriptsDir = path21.join(providerDir, "scripts");
21538
+ const scriptsDir = path20.join(providerDir, "scripts");
21227
21539
  const latestScriptsDir = getLatestScriptVersionDir(scriptsDir);
21228
21540
  if (latestScriptsDir) {
21229
21541
  lines.push(`Scripts version directory: \`${latestScriptsDir}\``);
@@ -21235,7 +21547,7 @@ function buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, ref
21235
21547
  if (!file.endsWith(".js")) continue;
21236
21548
  if (!targetFileNames.has(file)) continue;
21237
21549
  try {
21238
- const content = fs13.readFileSync(path21.join(latestScriptsDir, file), "utf-8");
21550
+ const content = fs13.readFileSync(path20.join(latestScriptsDir, file), "utf-8");
21239
21551
  lines.push(`### \`${file}\` \u270F\uFE0F EDIT`);
21240
21552
  lines.push("```javascript");
21241
21553
  lines.push(content);
@@ -21251,7 +21563,7 @@ function buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, ref
21251
21563
  lines.push("");
21252
21564
  for (const file of refFiles) {
21253
21565
  try {
21254
- const content = fs13.readFileSync(path21.join(latestScriptsDir, file), "utf-8");
21566
+ const content = fs13.readFileSync(path20.join(latestScriptsDir, file), "utf-8");
21255
21567
  lines.push(`### \`${file}\` \u{1F512}`);
21256
21568
  lines.push("```javascript");
21257
21569
  lines.push(content);
@@ -21284,10 +21596,10 @@ function buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, ref
21284
21596
  lines.push("");
21285
21597
  }
21286
21598
  }
21287
- const docsDir = path21.join(providerDir, "../../docs");
21599
+ const docsDir = path20.join(providerDir, "../../docs");
21288
21600
  const loadGuide = (name) => {
21289
21601
  try {
21290
- const p = path21.join(docsDir, name);
21602
+ const p = path20.join(docsDir, name);
21291
21603
  if (fs13.existsSync(p)) return fs13.readFileSync(p, "utf-8");
21292
21604
  } catch {
21293
21605
  }
@@ -21734,8 +22046,8 @@ var DevServer = class _DevServer {
21734
22046
  }
21735
22047
  getEndpointList() {
21736
22048
  return this.routes.map((r) => {
21737
- const path23 = typeof r.pattern === "string" ? r.pattern : r.pattern.source.replace(/\\\//g, "/").replace(/\(\[.*?\]\+\)/g, ":type").replace(/[\^$]/g, "");
21738
- return `${r.method.padEnd(5)} ${path23}`;
22049
+ const path22 = typeof r.pattern === "string" ? r.pattern : r.pattern.source.replace(/\\\//g, "/").replace(/\(\[.*?\]\+\)/g, ":type").replace(/[\^$]/g, "");
22050
+ return `${r.method.padEnd(5)} ${path22}`;
21739
22051
  });
21740
22052
  }
21741
22053
  async start(port = DEV_SERVER_PORT) {
@@ -22016,12 +22328,12 @@ var DevServer = class _DevServer {
22016
22328
  // ─── DevConsole SPA ───
22017
22329
  getConsoleDistDir() {
22018
22330
  const candidates = [
22019
- path22.resolve(__dirname, "../../web-devconsole/dist"),
22020
- path22.resolve(__dirname, "../../../web-devconsole/dist"),
22021
- path22.join(process.cwd(), "packages/web-devconsole/dist")
22331
+ path21.resolve(__dirname, "../../web-devconsole/dist"),
22332
+ path21.resolve(__dirname, "../../../web-devconsole/dist"),
22333
+ path21.join(process.cwd(), "packages/web-devconsole/dist")
22022
22334
  ];
22023
22335
  for (const dir of candidates) {
22024
- if (fs14.existsSync(path22.join(dir, "index.html"))) return dir;
22336
+ if (fs14.existsSync(path21.join(dir, "index.html"))) return dir;
22025
22337
  }
22026
22338
  return null;
22027
22339
  }
@@ -22031,7 +22343,7 @@ var DevServer = class _DevServer {
22031
22343
  this.json(res, 500, { error: "DevConsole not found. Run: npm run build -w packages/web-devconsole" });
22032
22344
  return;
22033
22345
  }
22034
- const htmlPath = path22.join(distDir, "index.html");
22346
+ const htmlPath = path21.join(distDir, "index.html");
22035
22347
  try {
22036
22348
  const html = fs14.readFileSync(htmlPath, "utf-8");
22037
22349
  res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
@@ -22056,15 +22368,15 @@ var DevServer = class _DevServer {
22056
22368
  this.json(res, 404, { error: "Not found" });
22057
22369
  return;
22058
22370
  }
22059
- const safePath = path22.normalize(pathname).replace(/^\.\.\//, "");
22060
- const filePath = path22.join(distDir, safePath);
22371
+ const safePath = path21.normalize(pathname).replace(/^\.\.\//, "");
22372
+ const filePath = path21.join(distDir, safePath);
22061
22373
  if (!filePath.startsWith(distDir)) {
22062
22374
  this.json(res, 403, { error: "Forbidden" });
22063
22375
  return;
22064
22376
  }
22065
22377
  try {
22066
22378
  const content = fs14.readFileSync(filePath);
22067
- const ext = path22.extname(filePath);
22379
+ const ext = path21.extname(filePath);
22068
22380
  const contentType = _DevServer.MIME_MAP[ext] || "application/octet-stream";
22069
22381
  res.writeHead(200, { "Content-Type": contentType, "Cache-Control": "public, max-age=31536000, immutable" });
22070
22382
  res.end(content);
@@ -22177,9 +22489,9 @@ var DevServer = class _DevServer {
22177
22489
  const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
22178
22490
  if (entry.isDirectory()) {
22179
22491
  files.push({ path: rel, size: 0, type: "dir" });
22180
- scan(path22.join(d, entry.name), rel);
22492
+ scan(path21.join(d, entry.name), rel);
22181
22493
  } else {
22182
- const stat = fs14.statSync(path22.join(d, entry.name));
22494
+ const stat = fs14.statSync(path21.join(d, entry.name));
22183
22495
  files.push({ path: rel, size: stat.size, type: "file" });
22184
22496
  }
22185
22497
  }
@@ -22202,7 +22514,7 @@ var DevServer = class _DevServer {
22202
22514
  this.json(res, 404, { error: `Provider directory not found: ${type}` });
22203
22515
  return;
22204
22516
  }
22205
- const fullPath = path22.resolve(dir, path22.normalize(filePath));
22517
+ const fullPath = path21.resolve(dir, path21.normalize(filePath));
22206
22518
  if (!fullPath.startsWith(dir)) {
22207
22519
  this.json(res, 403, { error: "Forbidden" });
22208
22520
  return;
@@ -22227,14 +22539,14 @@ var DevServer = class _DevServer {
22227
22539
  this.json(res, 404, { error: `Provider directory not found: ${type}` });
22228
22540
  return;
22229
22541
  }
22230
- const fullPath = path22.resolve(dir, path22.normalize(filePath));
22542
+ const fullPath = path21.resolve(dir, path21.normalize(filePath));
22231
22543
  if (!fullPath.startsWith(dir)) {
22232
22544
  this.json(res, 403, { error: "Forbidden" });
22233
22545
  return;
22234
22546
  }
22235
22547
  try {
22236
22548
  if (fs14.existsSync(fullPath)) fs14.copyFileSync(fullPath, fullPath + ".bak");
22237
- fs14.mkdirSync(path22.dirname(fullPath), { recursive: true });
22549
+ fs14.mkdirSync(path21.dirname(fullPath), { recursive: true });
22238
22550
  fs14.writeFileSync(fullPath, content, "utf-8");
22239
22551
  this.log(`File saved: ${fullPath} (${content.length} chars)`);
22240
22552
  this.providerLoader.reload();
@@ -22251,7 +22563,7 @@ var DevServer = class _DevServer {
22251
22563
  return;
22252
22564
  }
22253
22565
  for (const name of ["scripts.js", "provider.json"]) {
22254
- const p = path22.join(dir, name);
22566
+ const p = path21.join(dir, name);
22255
22567
  if (fs14.existsSync(p)) {
22256
22568
  const source = fs14.readFileSync(p, "utf-8");
22257
22569
  this.json(res, 200, { type, path: p, source, lines: source.split("\n").length });
@@ -22272,8 +22584,8 @@ var DevServer = class _DevServer {
22272
22584
  this.json(res, 404, { error: `Provider not found: ${type}` });
22273
22585
  return;
22274
22586
  }
22275
- const target = fs14.existsSync(path22.join(dir, "scripts.js")) ? "scripts.js" : "provider.json";
22276
- const targetPath = path22.join(dir, target);
22587
+ const target = fs14.existsSync(path21.join(dir, "scripts.js")) ? "scripts.js" : "provider.json";
22588
+ const targetPath = path21.join(dir, target);
22277
22589
  try {
22278
22590
  if (fs14.existsSync(targetPath)) fs14.copyFileSync(targetPath, targetPath + ".bak");
22279
22591
  fs14.writeFileSync(targetPath, source, "utf-8");
@@ -22420,7 +22732,7 @@ var DevServer = class _DevServer {
22420
22732
  }
22421
22733
  let targetDir;
22422
22734
  targetDir = this.providerLoader.getUserProviderDir(category, type);
22423
- const jsonPath = path22.join(targetDir, "provider.json");
22735
+ const jsonPath = path21.join(targetDir, "provider.json");
22424
22736
  if (fs14.existsSync(jsonPath)) {
22425
22737
  this.json(res, 409, { error: `Provider already exists at ${targetDir}`, path: targetDir });
22426
22738
  return;
@@ -22432,8 +22744,8 @@ var DevServer = class _DevServer {
22432
22744
  const createdFiles = ["provider.json"];
22433
22745
  if (result.files) {
22434
22746
  for (const [relPath, content] of Object.entries(result.files)) {
22435
- const fullPath = path22.join(targetDir, relPath);
22436
- fs14.mkdirSync(path22.dirname(fullPath), { recursive: true });
22747
+ const fullPath = path21.join(targetDir, relPath);
22748
+ fs14.mkdirSync(path21.dirname(fullPath), { recursive: true });
22437
22749
  fs14.writeFileSync(fullPath, content, "utf-8");
22438
22750
  createdFiles.push(relPath);
22439
22751
  }
@@ -22486,22 +22798,22 @@ var DevServer = class _DevServer {
22486
22798
  if (!fs14.existsSync(scriptsDir)) return null;
22487
22799
  const versions = fs14.readdirSync(scriptsDir).filter((d) => {
22488
22800
  try {
22489
- return fs14.statSync(path22.join(scriptsDir, d)).isDirectory();
22801
+ return fs14.statSync(path21.join(scriptsDir, d)).isDirectory();
22490
22802
  } catch {
22491
22803
  return false;
22492
22804
  }
22493
22805
  }).sort((a, b) => b.localeCompare(a, void 0, { numeric: true, sensitivity: "base" }));
22494
22806
  if (versions.length === 0) return null;
22495
- return path22.join(scriptsDir, versions[0]);
22807
+ return path21.join(scriptsDir, versions[0]);
22496
22808
  }
22497
22809
  resolveAutoImplWritableProviderDir(category, type, requestedDir) {
22498
- const canonicalUserDir = path22.resolve(this.providerLoader.getUserProviderDir(category, type));
22499
- const desiredDir = requestedDir ? path22.resolve(requestedDir) : canonicalUserDir;
22500
- const upstreamRoot = path22.resolve(this.providerLoader.getUpstreamDir());
22501
- if (desiredDir === upstreamRoot || desiredDir.startsWith(`${upstreamRoot}${path22.sep}`)) {
22810
+ const canonicalUserDir = path21.resolve(this.providerLoader.getUserProviderDir(category, type));
22811
+ const desiredDir = requestedDir ? path21.resolve(requestedDir) : canonicalUserDir;
22812
+ const upstreamRoot = path21.resolve(this.providerLoader.getUpstreamDir());
22813
+ if (desiredDir === upstreamRoot || desiredDir.startsWith(`${upstreamRoot}${path21.sep}`)) {
22502
22814
  return { dir: null, reason: `Refusing to write into upstream provider directory: ${desiredDir}` };
22503
22815
  }
22504
- if (path22.basename(desiredDir) !== type) {
22816
+ if (path21.basename(desiredDir) !== type) {
22505
22817
  return { dir: null, reason: `Requested writable provider directory must end with '${type}': ${desiredDir}` };
22506
22818
  }
22507
22819
  const sourceDir = this.findProviderDir(type);
@@ -22509,11 +22821,11 @@ var DevServer = class _DevServer {
22509
22821
  return { dir: null, reason: `Provider source directory not found for '${type}'` };
22510
22822
  }
22511
22823
  if (!fs14.existsSync(desiredDir)) {
22512
- fs14.mkdirSync(path22.dirname(desiredDir), { recursive: true });
22824
+ fs14.mkdirSync(path21.dirname(desiredDir), { recursive: true });
22513
22825
  fs14.cpSync(sourceDir, desiredDir, { recursive: true });
22514
22826
  this.log(`Auto-implement writable copy created: ${desiredDir}`);
22515
22827
  }
22516
- const providerJson = path22.join(desiredDir, "provider.json");
22828
+ const providerJson = path21.join(desiredDir, "provider.json");
22517
22829
  if (!fs14.existsSync(providerJson)) {
22518
22830
  return { dir: null, reason: `provider.json not found in writable provider directory: ${desiredDir}` };
22519
22831
  }
@@ -22549,7 +22861,7 @@ var DevServer = class _DevServer {
22549
22861
  setMode: "set_mode.js"
22550
22862
  };
22551
22863
  const targetFileNames = new Set(functions.map((fn) => funcToFile[fn]).filter(Boolean));
22552
- const scriptsDir = path22.join(providerDir, "scripts");
22864
+ const scriptsDir = path21.join(providerDir, "scripts");
22553
22865
  const latestScriptsDir = this.getLatestScriptVersionDir(scriptsDir);
22554
22866
  if (latestScriptsDir) {
22555
22867
  lines.push(`Scripts version directory: \`${latestScriptsDir}\``);
@@ -22560,7 +22872,7 @@ var DevServer = class _DevServer {
22560
22872
  for (const file of fs14.readdirSync(latestScriptsDir)) {
22561
22873
  if (file.endsWith(".js") && targetFileNames.has(file)) {
22562
22874
  try {
22563
- const content = fs14.readFileSync(path22.join(latestScriptsDir, file), "utf-8");
22875
+ const content = fs14.readFileSync(path21.join(latestScriptsDir, file), "utf-8");
22564
22876
  lines.push(`### \`${file}\` \u270F\uFE0F EDIT`);
22565
22877
  lines.push("```javascript");
22566
22878
  lines.push(content);
@@ -22577,7 +22889,7 @@ var DevServer = class _DevServer {
22577
22889
  lines.push("");
22578
22890
  for (const file of refFiles) {
22579
22891
  try {
22580
- const content = fs14.readFileSync(path22.join(latestScriptsDir, file), "utf-8");
22892
+ const content = fs14.readFileSync(path21.join(latestScriptsDir, file), "utf-8");
22581
22893
  lines.push(`### \`${file}\` \u{1F512}`);
22582
22894
  lines.push("```javascript");
22583
22895
  lines.push(content);
@@ -22618,10 +22930,10 @@ var DevServer = class _DevServer {
22618
22930
  lines.push("");
22619
22931
  }
22620
22932
  }
22621
- const docsDir = path22.join(providerDir, "../../docs");
22933
+ const docsDir = path21.join(providerDir, "../../docs");
22622
22934
  const loadGuide = (name) => {
22623
22935
  try {
22624
- const p = path22.join(docsDir, name);
22936
+ const p = path21.join(docsDir, name);
22625
22937
  if (fs14.existsSync(p)) return fs14.readFileSync(p, "utf-8");
22626
22938
  } catch {
22627
22939
  }
@@ -22795,7 +23107,7 @@ var DevServer = class _DevServer {
22795
23107
  parseApproval: "parse_approval.js"
22796
23108
  };
22797
23109
  const targetFileNames = new Set(functions.map((fn) => funcToFile[fn]).filter(Boolean));
22798
- const scriptsDir = path22.join(providerDir, "scripts");
23110
+ const scriptsDir = path21.join(providerDir, "scripts");
22799
23111
  const latestScriptsDir = this.getLatestScriptVersionDir(scriptsDir);
22800
23112
  if (latestScriptsDir) {
22801
23113
  lines.push(`Scripts version directory: \`${latestScriptsDir}\``);
@@ -22807,7 +23119,7 @@ var DevServer = class _DevServer {
22807
23119
  if (!file.endsWith(".js")) continue;
22808
23120
  if (!targetFileNames.has(file)) continue;
22809
23121
  try {
22810
- const content = fs14.readFileSync(path22.join(latestScriptsDir, file), "utf-8");
23122
+ const content = fs14.readFileSync(path21.join(latestScriptsDir, file), "utf-8");
22811
23123
  lines.push(`### \`${file}\` \u270F\uFE0F EDIT`);
22812
23124
  lines.push("```javascript");
22813
23125
  lines.push(content);
@@ -22823,7 +23135,7 @@ var DevServer = class _DevServer {
22823
23135
  lines.push("");
22824
23136
  for (const file of refFiles) {
22825
23137
  try {
22826
- const content = fs14.readFileSync(path22.join(latestScriptsDir, file), "utf-8");
23138
+ const content = fs14.readFileSync(path21.join(latestScriptsDir, file), "utf-8");
22827
23139
  lines.push(`### \`${file}\` \u{1F512}`);
22828
23140
  lines.push("```javascript");
22829
23141
  lines.push(content);
@@ -22856,10 +23168,10 @@ var DevServer = class _DevServer {
22856
23168
  lines.push("");
22857
23169
  }
22858
23170
  }
22859
- const docsDir = path22.join(providerDir, "../../docs");
23171
+ const docsDir = path21.join(providerDir, "../../docs");
22860
23172
  const loadGuide = (name) => {
22861
23173
  try {
22862
- const p = path22.join(docsDir, name);
23174
+ const p = path21.join(docsDir, name);
22863
23175
  if (fs14.existsSync(p)) return fs14.readFileSync(p, "utf-8");
22864
23176
  } catch {
22865
23177
  }