adhdev 0.8.22 → 0.8.25

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
@@ -84,7 +84,8 @@ function normalizeConfig(raw) {
84
84
  providerSettings: isPlainObject(parsed.providerSettings) ? parsed.providerSettings : {},
85
85
  ideSettings: isPlainObject(parsed.ideSettings) ? parsed.ideSettings : {},
86
86
  disableUpstream: asBoolean(parsed.disableUpstream, DEFAULT_CONFIG.disableUpstream ?? false),
87
- providerDir: asOptionalString(parsed.providerDir)
87
+ providerDir: asOptionalString(parsed.providerDir),
88
+ terminalSizingMode: parsed.terminalSizingMode === "fit" ? "fit" : "measured"
88
89
  };
89
90
  }
90
91
  function generateMachineId() {
@@ -232,7 +233,8 @@ var init_config = __esm({
232
233
  registeredMachineId: void 0,
233
234
  providerSettings: {},
234
235
  ideSettings: {},
235
- disableUpstream: false
236
+ disableUpstream: false,
237
+ terminalSizingMode: "measured"
236
238
  };
237
239
  MACHINE_ID_PREFIX = "mach_";
238
240
  }
@@ -2457,204 +2459,97 @@ var init_status_monitor = __esm({
2457
2459
  }
2458
2460
  });
2459
2461
 
2460
- // ../../oss/packages/daemon-core/src/providers/extension-provider-instance.ts
2461
- var ExtensionProviderInstance;
2462
- var init_extension_provider_instance = __esm({
2463
- "../../oss/packages/daemon-core/src/providers/extension-provider-instance.ts"() {
2464
- "use strict";
2465
- init_status_monitor();
2466
- ExtensionProviderInstance = class {
2467
- type;
2468
- category = "extension";
2469
- provider;
2470
- context = null;
2471
- settings = {};
2472
- events = [];
2473
- // status
2474
- currentStatus = "idle";
2475
- agentStreams = [];
2476
- messages = [];
2477
- activeModal = null;
2478
- currentModel = "";
2479
- currentMode = "";
2480
- controlValues = {};
2481
- lastAgentStatus = "idle";
2482
- generatingStartedAt = 0;
2483
- monitor;
2484
- // meta
2485
- instanceId;
2486
- ideType = "";
2487
- chatId = null;
2488
- chatTitle = null;
2489
- agentName = "";
2490
- extensionId = "";
2491
- constructor(provider) {
2492
- this.type = provider.type;
2493
- this.provider = provider;
2494
- this.instanceId = crypto.randomUUID();
2495
- this.monitor = new StatusMonitor();
2496
- }
2497
- // ─── Lifecycle ──────────────────────────────────
2498
- async init(context) {
2499
- this.context = context;
2500
- this.settings = context.settings || {};
2501
- this.monitor.updateConfig({
2502
- approvalAlert: this.settings.approvalAlert !== false,
2503
- longGeneratingAlert: this.settings.longGeneratingAlert !== false,
2504
- longGeneratingThresholdSec: this.settings.longGeneratingThresholdSec || 180
2505
- });
2506
- }
2507
- async onTick() {
2508
- if (!this.context?.cdp?.isConnected) return;
2509
- }
2510
- getState() {
2511
- return {
2512
- type: this.type,
2513
- name: this.provider.name,
2514
- category: "extension",
2515
- status: this.currentStatus,
2516
- activeChat: this.messages.length > 0 ? {
2517
- id: this.chatId || this.instanceId,
2518
- title: this.chatTitle || this.agentName || this.provider.name,
2519
- status: this.currentStatus,
2520
- messages: this.messages,
2521
- activeModal: this.activeModal,
2522
- inputContent: ""
2523
- } : null,
2524
- currentModel: this.currentModel || void 0,
2525
- currentPlan: this.currentMode || void 0,
2526
- controlValues: this.controlValues,
2527
- providerControls: this.provider.controls,
2528
- agentStreams: this.agentStreams,
2529
- instanceId: this.instanceId,
2530
- lastUpdated: Date.now(),
2531
- settings: this.settings,
2532
- pendingEvents: this.flushEvents()
2533
- };
2534
- }
2535
- onEvent(event, data) {
2536
- if (event === "stream_update") {
2537
- if (data?.streams) this.agentStreams = data.streams;
2538
- if (data?.messages) this.messages = data.messages;
2539
- if (data?.activeModal !== void 0) this.activeModal = data.activeModal;
2540
- if (data?.model) this.currentModel = data.model;
2541
- if (data?.mode) this.currentMode = data.mode;
2542
- if (data?.controlValues) this.controlValues = data.controlValues;
2543
- if (typeof data?.sessionId === "string" && data.sessionId.trim()) this.chatId = data.sessionId;
2544
- if (typeof data?.title === "string" && data.title.trim()) this.chatTitle = data.title;
2545
- if (typeof data?.agentName === "string" && data.agentName.trim()) this.agentName = data.agentName;
2546
- if (typeof data?.extensionId === "string" && data.extensionId.trim()) this.extensionId = data.extensionId;
2547
- if (data?.status) {
2548
- const newStatus = data.status;
2549
- this.detectTransition(newStatus, data);
2550
- this.currentStatus = newStatus;
2551
- }
2552
- } else if (event === "stream_reset") {
2553
- this.resetStreamState();
2554
- } else if (event === "extension_connected") {
2555
- this.ideType = data?.ideType || "";
2462
+ // ../../oss/packages/daemon-core/src/providers/control-effects.ts
2463
+ function extractProviderControlValues(controls, data) {
2464
+ if (!data || typeof data !== "object") return void 0;
2465
+ const values = {};
2466
+ const explicit = data.controlValues;
2467
+ if (explicit && typeof explicit === "object") {
2468
+ for (const [key, value] of Object.entries(explicit)) {
2469
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
2470
+ values[key] = value;
2471
+ }
2472
+ }
2473
+ }
2474
+ for (const ctrl of controls || []) {
2475
+ if (!ctrl.readFrom) continue;
2476
+ const rawValue = data[ctrl.readFrom];
2477
+ if (rawValue === void 0 || rawValue === null) continue;
2478
+ values[ctrl.id] = normalizeControlValue(rawValue);
2479
+ }
2480
+ if (data.model !== void 0 && values.model === void 0) values.model = normalizeControlValue(data.model);
2481
+ if (data.mode !== void 0 && values.mode === void 0) values.mode = normalizeControlValue(data.mode);
2482
+ return Object.keys(values).length > 0 ? values : void 0;
2483
+ }
2484
+ function normalizeProviderEffects(data) {
2485
+ const rawEffects = Array.isArray(data?.effects) ? data.effects : [];
2486
+ const effects = [];
2487
+ for (const raw of rawEffects) {
2488
+ if (!raw || typeof raw !== "object") continue;
2489
+ const type = raw.type;
2490
+ if (type === "message" && raw.message && typeof raw.message === "object") {
2491
+ const content = raw.message.content;
2492
+ if (typeof content !== "string" && !Array.isArray(content)) continue;
2493
+ effects.push({
2494
+ type: "message",
2495
+ id: typeof raw.id === "string" ? raw.id : void 0,
2496
+ when: raw.when === "turn_completed" ? "turn_completed" : "immediate",
2497
+ persist: raw.persist !== false,
2498
+ message: {
2499
+ role: raw.message.role === "assistant" || raw.message.role === "user" ? raw.message.role : "system",
2500
+ content,
2501
+ kind: typeof raw.message.kind === "string" ? raw.message.kind : void 0,
2502
+ senderName: typeof raw.message.senderName === "string" ? raw.message.senderName : void 0
2556
2503
  }
2557
- }
2558
- dispose() {
2559
- this.agentStreams = [];
2560
- this.messages = [];
2561
- this.monitor.reset();
2562
- }
2563
- /** Query UUID instanceId */
2564
- getInstanceId() {
2565
- return this.instanceId;
2566
- }
2567
- // ─── status transition detect ──────────────────────────────
2568
- detectTransition(newStatus, data) {
2569
- const now = Date.now();
2570
- const agentStatus = newStatus === "streaming" || newStatus === "generating" ? "generating" : newStatus === "waiting_approval" ? "waiting_approval" : "idle";
2571
- const lastMsg = Array.isArray(data?.messages) && data.messages.length > 0 ? data.messages[data.messages.length - 1] : null;
2572
- const progressFingerprint = agentStatus === "generating" ? `${lastMsg?.role || ""}:${typeof lastMsg?.content === "string" ? lastMsg.content : JSON.stringify(lastMsg?.content || "")}`.slice(-2e3) : void 0;
2573
- if (agentStatus !== this.lastAgentStatus) {
2574
- if (this.lastAgentStatus === "idle" && agentStatus === "generating") {
2575
- this.generatingStartedAt = now;
2576
- this.pushEvent({
2577
- event: "agent:generating_started",
2578
- chatTitle: this.resolveChatTitle(data),
2579
- timestamp: now,
2580
- ideType: this.ideType || this.type,
2581
- agentType: this.type,
2582
- agentName: this.agentName || this.provider.name,
2583
- extensionId: this.extensionId || this.type
2584
- });
2585
- } else if (agentStatus === "waiting_approval") {
2586
- if (!this.generatingStartedAt) this.generatingStartedAt = now;
2587
- this.pushEvent({
2588
- event: "agent:waiting_approval",
2589
- chatTitle: this.resolveChatTitle(data),
2590
- timestamp: now,
2591
- ideType: this.ideType || this.type,
2592
- agentType: this.type,
2593
- agentName: this.agentName || this.provider.name,
2594
- extensionId: this.extensionId || this.type,
2595
- modalMessage: data?.activeModal?.message,
2596
- modalButtons: data?.activeModal?.buttons
2597
- });
2598
- } else if (agentStatus === "idle" && (this.lastAgentStatus === "generating" || this.lastAgentStatus === "waiting_approval")) {
2599
- const duration3 = this.generatingStartedAt ? Math.round((now - this.generatingStartedAt) / 1e3) : 0;
2600
- this.pushEvent({
2601
- event: "agent:generating_completed",
2602
- chatTitle: this.resolveChatTitle(data),
2603
- duration: duration3,
2604
- timestamp: now,
2605
- ideType: this.ideType || this.type,
2606
- agentType: this.type,
2607
- agentName: this.agentName || this.provider.name,
2608
- extensionId: this.extensionId || this.type
2609
- });
2610
- this.generatingStartedAt = 0;
2611
- }
2612
- this.lastAgentStatus = agentStatus;
2613
- }
2614
- const agentKey = `${this.type}:ext`;
2615
- const monitorEvents = this.monitor.check(agentKey, agentStatus, now, progressFingerprint);
2616
- for (const me of monitorEvents) {
2617
- this.pushEvent({ event: me.type, agentKey: me.agentKey, message: me.message, elapsedSec: me.elapsedSec, timestamp: me.timestamp });
2504
+ });
2505
+ continue;
2506
+ }
2507
+ if (type === "toast" && raw.toast && typeof raw.toast.message === "string") {
2508
+ effects.push({
2509
+ type: "toast",
2510
+ id: typeof raw.id === "string" ? raw.id : void 0,
2511
+ when: raw.when === "turn_completed" ? "turn_completed" : "immediate",
2512
+ persist: raw.persist !== false,
2513
+ toast: {
2514
+ level: raw.toast.level === "success" || raw.toast.level === "warning" ? raw.toast.level : "info",
2515
+ message: raw.toast.message
2618
2516
  }
2619
- }
2620
- pushEvent(event) {
2621
- this.events.push(event);
2622
- if (this.events.length > 50) this.events = this.events.slice(-50);
2623
- }
2624
- flushEvents() {
2625
- const events = [...this.events];
2626
- this.events = [];
2627
- return events;
2628
- }
2629
- resolveChatTitle(data) {
2630
- const title = typeof data?.title === "string" && data.title.trim() ? data.title.trim() : this.chatTitle;
2631
- return title || this.agentName || this.provider.name;
2632
- }
2633
- resetStreamState() {
2634
- if (this.currentStatus !== "idle") {
2635
- this.detectTransition("idle", {
2636
- title: this.chatTitle,
2637
- agentName: this.agentName,
2638
- extensionId: this.extensionId,
2639
- messages: this.messages
2640
- });
2517
+ });
2518
+ continue;
2519
+ }
2520
+ if (type === "notification" && raw.notification && typeof raw.notification.body === "string") {
2521
+ effects.push({
2522
+ type: "notification",
2523
+ id: typeof raw.id === "string" ? raw.id : void 0,
2524
+ when: raw.when === "turn_completed" ? "turn_completed" : "immediate",
2525
+ persist: raw.persist !== false,
2526
+ notification: {
2527
+ title: typeof raw.notification.title === "string" ? raw.notification.title : void 0,
2528
+ body: raw.notification.body,
2529
+ level: raw.notification.level === "success" || raw.notification.level === "warning" ? raw.notification.level : "info",
2530
+ channels: Array.isArray(raw.notification.channels) ? raw.notification.channels.filter((channel) => channel === "bubble" || channel === "toast" || channel === "browser") : void 0,
2531
+ preferenceKey: raw.notification.preferenceKey === "disconnect" || raw.notification.preferenceKey === "completion" || raw.notification.preferenceKey === "approval" || raw.notification.preferenceKey === "browser" ? raw.notification.preferenceKey : void 0,
2532
+ bubbleContent: typeof raw.notification.bubbleContent === "string" || Array.isArray(raw.notification.bubbleContent) ? raw.notification.bubbleContent : void 0
2641
2533
  }
2642
- this.agentStreams = [];
2643
- this.messages = [];
2644
- this.activeModal = null;
2645
- this.currentModel = "";
2646
- this.currentMode = "";
2647
- this.controlValues = {};
2648
- this.currentStatus = "idle";
2649
- this.chatId = null;
2650
- this.chatTitle = null;
2651
- this.agentName = "";
2652
- this.extensionId = "";
2653
- this.lastAgentStatus = "idle";
2654
- this.generatingStartedAt = 0;
2655
- this.monitor.reset();
2656
- }
2657
- };
2534
+ });
2535
+ }
2536
+ }
2537
+ return effects;
2538
+ }
2539
+ function normalizeControlValue(value) {
2540
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
2541
+ return value;
2542
+ }
2543
+ if (value && typeof value === "object") {
2544
+ if (typeof value.label === "string") return value.label;
2545
+ if (typeof value.name === "string") return value.name;
2546
+ if (typeof value.id === "string") return value.id;
2547
+ }
2548
+ return String(value);
2549
+ }
2550
+ var init_control_effects = __esm({
2551
+ "../../oss/packages/daemon-core/src/providers/control-effects.ts"() {
2552
+ "use strict";
2658
2553
  }
2659
2554
  });
2660
2555
 
@@ -2938,6 +2833,344 @@ var init_chat_history = __esm({
2938
2833
  }
2939
2834
  });
2940
2835
 
2836
+ // ../../oss/packages/daemon-core/src/providers/extension-provider-instance.ts
2837
+ var ExtensionProviderInstance;
2838
+ var init_extension_provider_instance = __esm({
2839
+ "../../oss/packages/daemon-core/src/providers/extension-provider-instance.ts"() {
2840
+ "use strict";
2841
+ init_status_monitor();
2842
+ init_control_effects();
2843
+ init_chat_history();
2844
+ ExtensionProviderInstance = class {
2845
+ type;
2846
+ category = "extension";
2847
+ provider;
2848
+ context = null;
2849
+ settings = {};
2850
+ events = [];
2851
+ // status
2852
+ currentStatus = "idle";
2853
+ agentStreams = [];
2854
+ messages = [];
2855
+ activeModal = null;
2856
+ currentModel = "";
2857
+ currentMode = "";
2858
+ controlValues = {};
2859
+ appliedEffectKeys = /* @__PURE__ */ new Set();
2860
+ runtimeMessages = [];
2861
+ lastAgentStatus = "idle";
2862
+ generatingStartedAt = 0;
2863
+ monitor;
2864
+ historyWriter;
2865
+ // meta
2866
+ instanceId;
2867
+ ideType = "";
2868
+ chatId = null;
2869
+ chatTitle = null;
2870
+ agentName = "";
2871
+ extensionId = "";
2872
+ constructor(provider) {
2873
+ this.type = provider.type;
2874
+ this.provider = provider;
2875
+ this.instanceId = crypto.randomUUID();
2876
+ this.monitor = new StatusMonitor();
2877
+ this.historyWriter = new ChatHistoryWriter();
2878
+ }
2879
+ // ─── Lifecycle ──────────────────────────────────
2880
+ async init(context) {
2881
+ this.context = context;
2882
+ this.settings = context.settings || {};
2883
+ this.monitor.updateConfig({
2884
+ approvalAlert: this.settings.approvalAlert !== false,
2885
+ longGeneratingAlert: this.settings.longGeneratingAlert !== false,
2886
+ longGeneratingThresholdSec: this.settings.longGeneratingThresholdSec || 180
2887
+ });
2888
+ }
2889
+ async onTick() {
2890
+ if (!this.context?.cdp?.isConnected) return;
2891
+ }
2892
+ getState() {
2893
+ return {
2894
+ type: this.type,
2895
+ name: this.provider.name,
2896
+ category: "extension",
2897
+ status: this.currentStatus,
2898
+ activeChat: this.messages.length > 0 || this.runtimeMessages.length > 0 ? {
2899
+ id: this.chatId || this.instanceId,
2900
+ title: this.chatTitle || this.agentName || this.provider.name,
2901
+ status: this.currentStatus,
2902
+ messages: this.mergeConversationMessages(this.messages),
2903
+ activeModal: this.activeModal,
2904
+ inputContent: ""
2905
+ } : null,
2906
+ currentModel: this.currentModel || void 0,
2907
+ currentPlan: this.currentMode || void 0,
2908
+ controlValues: this.controlValues,
2909
+ providerControls: this.provider.controls,
2910
+ agentStreams: this.agentStreams,
2911
+ instanceId: this.instanceId,
2912
+ lastUpdated: Date.now(),
2913
+ settings: this.settings,
2914
+ pendingEvents: this.flushEvents()
2915
+ };
2916
+ }
2917
+ onEvent(event, data) {
2918
+ if (event === "stream_update") {
2919
+ if (data?.streams) this.agentStreams = data.streams;
2920
+ if (data?.messages) this.messages = data.messages;
2921
+ if (data?.activeModal !== void 0) this.activeModal = data.activeModal;
2922
+ if (data?.model) this.currentModel = data.model;
2923
+ if (data?.mode) this.currentMode = data.mode;
2924
+ const controlValues = extractProviderControlValues(this.provider.controls, data) || data?.controlValues;
2925
+ if (controlValues) this.controlValues = controlValues;
2926
+ if (typeof data?.sessionId === "string" && data.sessionId.trim()) this.chatId = data.sessionId;
2927
+ if (typeof data?.title === "string" && data.title.trim()) this.chatTitle = data.title;
2928
+ if (typeof data?.agentName === "string" && data.agentName.trim()) this.agentName = data.agentName;
2929
+ if (typeof data?.extensionId === "string" && data.extensionId.trim()) this.extensionId = data.extensionId;
2930
+ if (data?.status) {
2931
+ const newStatus = data.status;
2932
+ this.detectTransition(newStatus, data);
2933
+ this.currentStatus = newStatus;
2934
+ }
2935
+ } else if (event === "stream_reset") {
2936
+ this.resetStreamState();
2937
+ } else if (event === "extension_connected") {
2938
+ this.ideType = data?.ideType || "";
2939
+ } else if (event === "provider_state_patch" && data && typeof data === "object") {
2940
+ this.applyProviderResponse(data, { phase: "immediate" });
2941
+ }
2942
+ }
2943
+ dispose() {
2944
+ this.agentStreams = [];
2945
+ this.messages = [];
2946
+ this.monitor.reset();
2947
+ this.appliedEffectKeys.clear();
2948
+ this.runtimeMessages = [];
2949
+ }
2950
+ updateSettings(newSettings) {
2951
+ this.settings = { ...newSettings };
2952
+ this.monitor.updateConfig({
2953
+ approvalAlert: this.settings.approvalAlert !== false,
2954
+ longGeneratingAlert: this.settings.longGeneratingAlert !== false,
2955
+ longGeneratingThresholdSec: this.settings.longGeneratingThresholdSec || 180
2956
+ });
2957
+ }
2958
+ /** Query UUID instanceId */
2959
+ getInstanceId() {
2960
+ return this.instanceId;
2961
+ }
2962
+ // ─── status transition detect ──────────────────────────────
2963
+ detectTransition(newStatus, data) {
2964
+ const now = Date.now();
2965
+ const agentStatus = newStatus === "streaming" || newStatus === "generating" ? "generating" : newStatus === "waiting_approval" ? "waiting_approval" : "idle";
2966
+ const lastMsg = Array.isArray(data?.messages) && data.messages.length > 0 ? data.messages[data.messages.length - 1] : null;
2967
+ const progressFingerprint = agentStatus === "generating" ? `${lastMsg?.role || ""}:${typeof lastMsg?.content === "string" ? lastMsg.content : JSON.stringify(lastMsg?.content || "")}`.slice(-2e3) : void 0;
2968
+ const previousStatus = this.lastAgentStatus;
2969
+ if (agentStatus !== this.lastAgentStatus) {
2970
+ if (this.lastAgentStatus === "idle" && agentStatus === "generating") {
2971
+ this.generatingStartedAt = now;
2972
+ this.pushEvent({
2973
+ event: "agent:generating_started",
2974
+ chatTitle: this.resolveChatTitle(data),
2975
+ timestamp: now,
2976
+ ideType: this.ideType || this.type,
2977
+ agentType: this.type,
2978
+ agentName: this.agentName || this.provider.name,
2979
+ extensionId: this.extensionId || this.type
2980
+ });
2981
+ } else if (agentStatus === "waiting_approval") {
2982
+ if (!this.generatingStartedAt) this.generatingStartedAt = now;
2983
+ this.pushEvent({
2984
+ event: "agent:waiting_approval",
2985
+ chatTitle: this.resolveChatTitle(data),
2986
+ timestamp: now,
2987
+ ideType: this.ideType || this.type,
2988
+ agentType: this.type,
2989
+ agentName: this.agentName || this.provider.name,
2990
+ extensionId: this.extensionId || this.type,
2991
+ modalMessage: data?.activeModal?.message,
2992
+ modalButtons: data?.activeModal?.buttons
2993
+ });
2994
+ } else if (agentStatus === "idle" && (this.lastAgentStatus === "generating" || this.lastAgentStatus === "waiting_approval")) {
2995
+ const duration3 = this.generatingStartedAt ? Math.round((now - this.generatingStartedAt) / 1e3) : 0;
2996
+ this.pushEvent({
2997
+ event: "agent:generating_completed",
2998
+ chatTitle: this.resolveChatTitle(data),
2999
+ duration: duration3,
3000
+ timestamp: now,
3001
+ ideType: this.ideType || this.type,
3002
+ agentType: this.type,
3003
+ agentName: this.agentName || this.provider.name,
3004
+ extensionId: this.extensionId || this.type
3005
+ });
3006
+ this.generatingStartedAt = 0;
3007
+ }
3008
+ this.lastAgentStatus = agentStatus;
3009
+ }
3010
+ this.applyProviderResponse(data, {
3011
+ phase: agentStatus === "idle" && (previousStatus === "generating" || previousStatus === "waiting_approval") ? "turn_completed" : "immediate"
3012
+ });
3013
+ const agentKey = `${this.type}:ext`;
3014
+ const monitorEvents = this.monitor.check(agentKey, agentStatus, now, progressFingerprint);
3015
+ for (const me of monitorEvents) {
3016
+ this.pushEvent({ event: me.type, agentKey: me.agentKey, message: me.message, elapsedSec: me.elapsedSec, timestamp: me.timestamp });
3017
+ }
3018
+ }
3019
+ pushEvent(event) {
3020
+ this.events.push(event);
3021
+ if (this.events.length > 50) this.events = this.events.slice(-50);
3022
+ }
3023
+ applyProviderResponse(data, options) {
3024
+ if (!data || typeof data !== "object") return;
3025
+ const controlValues = extractProviderControlValues(this.provider.controls, data);
3026
+ if (controlValues) this.controlValues = { ...this.controlValues, ...controlValues };
3027
+ const effects = normalizeProviderEffects(data);
3028
+ for (const effect of effects) {
3029
+ const effectWhen = effect.when || "immediate";
3030
+ if (effectWhen === "turn_completed" && options.phase !== "turn_completed") continue;
3031
+ if (effectWhen === "immediate" && options.phase === "turn_completed") continue;
3032
+ const effectKey = this.getEffectDedupKey(effect);
3033
+ if (this.appliedEffectKeys.has(effectKey)) continue;
3034
+ this.appliedEffectKeys.add(effectKey);
3035
+ if (effect.persist !== false) {
3036
+ const persisted = this.getPersistedEffectContent(effect);
3037
+ if (persisted) this.appendRuntimeSystemMessage(persisted, effectKey);
3038
+ }
3039
+ if (effect.type === "message" && effect.message) {
3040
+ this.pushEvent({
3041
+ event: "provider:message",
3042
+ timestamp: Date.now(),
3043
+ content: typeof effect.message.content === "string" ? effect.message.content : JSON.stringify(effect.message.content),
3044
+ role: effect.message.role || "system",
3045
+ kind: effect.message.kind,
3046
+ senderName: effect.message.senderName
3047
+ });
3048
+ } else if (effect.type === "toast" && effect.toast) {
3049
+ this.pushEvent({
3050
+ event: "provider:toast",
3051
+ effectId: effect.id || effectKey,
3052
+ timestamp: Date.now(),
3053
+ message: effect.toast.message,
3054
+ level: effect.toast.level || "info"
3055
+ });
3056
+ } else if (effect.type === "notification" && effect.notification) {
3057
+ this.pushEvent({
3058
+ event: "provider:notification",
3059
+ effectId: effect.id || effectKey,
3060
+ timestamp: Date.now(),
3061
+ title: effect.notification.title,
3062
+ message: effect.notification.body,
3063
+ content: typeof effect.notification.bubbleContent === "string" ? effect.notification.bubbleContent : effect.notification.body,
3064
+ level: effect.notification.level || "info",
3065
+ channels: effect.notification.channels || ["toast"],
3066
+ preferenceKey: effect.notification.preferenceKey
3067
+ });
3068
+ }
3069
+ }
3070
+ }
3071
+ appendRuntimeSystemMessage(content, dedupKey, receivedAt = Date.now()) {
3072
+ const normalizedContent = String(content || "").trim();
3073
+ if (!normalizedContent) return;
3074
+ if (this.runtimeMessages.some((entry) => entry.key === dedupKey)) return;
3075
+ this.runtimeMessages.push({
3076
+ key: dedupKey,
3077
+ message: {
3078
+ role: "system",
3079
+ senderName: "System",
3080
+ content: normalizedContent,
3081
+ receivedAt,
3082
+ timestamp: receivedAt
3083
+ }
3084
+ });
3085
+ if (this.runtimeMessages.length > 50) this.runtimeMessages = this.runtimeMessages.slice(-50);
3086
+ this.historyWriter.appendNewMessages(
3087
+ this.type,
3088
+ [{
3089
+ role: "system",
3090
+ senderName: "System",
3091
+ content: normalizedContent,
3092
+ kind: "system",
3093
+ receivedAt,
3094
+ historyDedupKey: dedupKey
3095
+ }],
3096
+ this.chatTitle || this.agentName || this.provider.name,
3097
+ this.instanceId,
3098
+ this.chatId || this.instanceId
3099
+ );
3100
+ }
3101
+ mergeConversationMessages(messages) {
3102
+ if (this.runtimeMessages.length === 0) return messages;
3103
+ return [...messages, ...this.runtimeMessages.map((entry) => entry.message)].map((message, index) => ({ message, index })).sort((a, b) => {
3104
+ const aTime = a.message.receivedAt || a.message.timestamp || 0;
3105
+ const bTime = b.message.receivedAt || b.message.timestamp || 0;
3106
+ if (aTime !== bTime) return aTime - bTime;
3107
+ return a.index - b.index;
3108
+ }).map((entry) => entry.message);
3109
+ }
3110
+ getPersistedEffectContent(effect) {
3111
+ if (effect.type === "message") {
3112
+ return typeof effect.message?.content === "string" ? effect.message.content : JSON.stringify(effect.message?.content || "");
3113
+ }
3114
+ if (effect.type === "toast") {
3115
+ return effect.toast?.message || null;
3116
+ }
3117
+ if (effect.type === "notification") {
3118
+ if (typeof effect.notification?.bubbleContent === "string") return effect.notification.bubbleContent;
3119
+ if (typeof effect.notification?.title === "string" && effect.notification.title.trim()) {
3120
+ return `${effect.notification.title}
3121
+ ${effect.notification.body || ""}`.trim();
3122
+ }
3123
+ return effect.notification?.body || null;
3124
+ }
3125
+ return null;
3126
+ }
3127
+ getEffectDedupKey(effect) {
3128
+ if (effect.id) return `provider_effect:${effect.id}`;
3129
+ if (effect.type === "message") {
3130
+ return `provider_effect:message:${typeof effect.message?.content === "string" ? effect.message.content : JSON.stringify(effect.message?.content || "")}`;
3131
+ }
3132
+ if (effect.type === "notification") {
3133
+ return `provider_effect:notification:${effect.notification?.title || ""}:${effect.notification?.body || ""}`;
3134
+ }
3135
+ return `provider_effect:toast:${effect.toast?.message || ""}`;
3136
+ }
3137
+ flushEvents() {
3138
+ const events = [...this.events];
3139
+ this.events = [];
3140
+ return events;
3141
+ }
3142
+ resolveChatTitle(data) {
3143
+ const title = typeof data?.title === "string" && data.title.trim() ? data.title.trim() : this.chatTitle;
3144
+ return title || this.agentName || this.provider.name;
3145
+ }
3146
+ resetStreamState() {
3147
+ if (this.currentStatus !== "idle") {
3148
+ this.detectTransition("idle", {
3149
+ title: this.chatTitle,
3150
+ agentName: this.agentName,
3151
+ extensionId: this.extensionId,
3152
+ messages: this.messages
3153
+ });
3154
+ }
3155
+ this.agentStreams = [];
3156
+ this.messages = [];
3157
+ this.activeModal = null;
3158
+ this.currentModel = "";
3159
+ this.currentMode = "";
3160
+ this.controlValues = {};
3161
+ this.currentStatus = "idle";
3162
+ this.chatId = null;
3163
+ this.chatTitle = null;
3164
+ this.agentName = "";
3165
+ this.extensionId = "";
3166
+ this.lastAgentStatus = "idle";
3167
+ this.generatingStartedAt = 0;
3168
+ this.monitor.reset();
3169
+ }
3170
+ };
3171
+ }
3172
+ });
3173
+
2941
3174
  // ../../oss/packages/daemon-core/src/providers/ide-provider-instance.ts
2942
3175
  var crypto2, IdeProviderInstance;
2943
3176
  var init_ide_provider_instance = __esm({
@@ -2948,6 +3181,7 @@ var init_ide_provider_instance = __esm({
2948
3181
  init_status_monitor();
2949
3182
  init_chat_history();
2950
3183
  init_logger();
3184
+ init_control_effects();
2951
3185
  IdeProviderInstance = class {
2952
3186
  type;
2953
3187
  category = "ide";
@@ -2965,6 +3199,8 @@ var init_ide_provider_instance = __esm({
2965
3199
  monitor;
2966
3200
  historyWriter;
2967
3201
  autoApproveBusy = false;
3202
+ appliedEffectKeys = /* @__PURE__ */ new Set();
3203
+ runtimeMessages = [];
2968
3204
  // IDE meta
2969
3205
  ideVersion = "";
2970
3206
  instanceId;
@@ -3025,7 +3261,7 @@ var init_ide_provider_instance = __esm({
3025
3261
  id: this.cachedChat.id || "active_session",
3026
3262
  title: this.cachedChat.title || this.type,
3027
3263
  status: this.cachedChat.status || this.currentStatus,
3028
- messages: this.cachedChat.messages || [],
3264
+ messages: this.mergeConversationMessages(this.cachedChat.messages || []),
3029
3265
  activeModal: this.cachedChat.activeModal || null,
3030
3266
  inputContent: this.cachedChat.inputContent || ""
3031
3267
  } : null,
@@ -3065,6 +3301,13 @@ var init_ide_provider_instance = __esm({
3065
3301
  for (const ext of this.extensions.values()) {
3066
3302
  ext.onEvent("stream_reset");
3067
3303
  }
3304
+ } else if (event === "provider_state_patch" && data && typeof data === "object") {
3305
+ const extType = typeof data.extensionType === "string" ? data.extensionType : "";
3306
+ if (extType && this.extensions.has(extType)) {
3307
+ this.extensions.get(extType).onEvent("provider_state_patch", data);
3308
+ } else {
3309
+ this.applyProviderResponse(data, { phase: "immediate" });
3310
+ }
3068
3311
  }
3069
3312
  }
3070
3313
  dispose() {
@@ -3072,11 +3315,21 @@ var init_ide_provider_instance = __esm({
3072
3315
  this.lastAgentStatuses.clear();
3073
3316
  this.generatingStartedAt.clear();
3074
3317
  this.monitor.reset();
3318
+ this.appliedEffectKeys.clear();
3319
+ this.runtimeMessages = [];
3075
3320
  for (const ext of this.extensions.values()) {
3076
3321
  ext.dispose();
3077
3322
  }
3078
3323
  this.extensions.clear();
3079
3324
  }
3325
+ updateSettings(newSettings) {
3326
+ this.settings = { ...newSettings };
3327
+ this.monitor.updateConfig({
3328
+ approvalAlert: this.settings.approvalAlert !== false,
3329
+ longGeneratingAlert: this.settings.longGeneratingAlert !== false,
3330
+ longGeneratingThresholdSec: this.settings.longGeneratingThresholdSec || 180
3331
+ });
3332
+ }
3080
3333
  // ─── Extension manage ─────────────────────────────
3081
3334
  /** Extension Instance add */
3082
3335
  async addExtension(provider, settings) {
@@ -3189,6 +3442,8 @@ var init_ide_provider_instance = __esm({
3189
3442
  raw.messages = raw.messages.filter((m) => !hiddenKinds.has(m.kind));
3190
3443
  }
3191
3444
  }
3445
+ const controlValues = extractProviderControlValues(this.provider.controls, raw);
3446
+ if (controlValues) raw.controlValues = controlValues;
3192
3447
  this.cachedChat = { ...raw, activeModal };
3193
3448
  this.detectAgentTransitions(raw, now);
3194
3449
  if (raw.messages?.length > 0) {
@@ -3255,6 +3510,9 @@ var init_ide_provider_instance = __esm({
3255
3510
  }
3256
3511
  this.lastAgentStatuses.set(agentKey, agentStatus);
3257
3512
  }
3513
+ this.applyProviderResponse(chatData, {
3514
+ phase: agentStatus === "idle" && (lastStatus === "generating" || lastStatus === "waiting_approval") ? "turn_completed" : "immediate"
3515
+ });
3258
3516
  if (agentStatus === "waiting_approval" && this.settings.autoApprove && !this.autoApproveBusy) {
3259
3517
  this.autoApproveViaScript(chatData);
3260
3518
  }
@@ -3267,6 +3525,136 @@ var init_ide_provider_instance = __esm({
3267
3525
  this.events.push(event);
3268
3526
  if (this.events.length > 50) this.events = this.events.slice(-50);
3269
3527
  }
3528
+ applyProviderResponse(data, options) {
3529
+ if (!data || typeof data !== "object") return;
3530
+ const controlValues = extractProviderControlValues(this.provider.controls, data);
3531
+ if (controlValues) {
3532
+ this.cachedChat = {
3533
+ ...this.cachedChat || {},
3534
+ ...data,
3535
+ controlValues: { ...this.cachedChat?.controlValues || {}, ...controlValues }
3536
+ };
3537
+ }
3538
+ const effects = normalizeProviderEffects(data);
3539
+ for (const effect of effects) {
3540
+ const effectWhen = effect.when || "immediate";
3541
+ if (effectWhen === "turn_completed" && options.phase !== "turn_completed") continue;
3542
+ if (effectWhen === "immediate" && options.phase === "turn_completed") continue;
3543
+ const effectKey = this.getEffectDedupKey(effect);
3544
+ if (this.appliedEffectKeys.has(effectKey)) continue;
3545
+ this.appliedEffectKeys.add(effectKey);
3546
+ if (effect.persist !== false) {
3547
+ const persisted = this.getPersistedEffectContent(effect);
3548
+ if (persisted) this.appendRuntimeSystemMessage(persisted, effectKey);
3549
+ }
3550
+ if (effect.type === "message" && effect.message) {
3551
+ this.pushEvent({
3552
+ event: "provider:message",
3553
+ timestamp: Date.now(),
3554
+ content: typeof effect.message.content === "string" ? effect.message.content : JSON.stringify(effect.message.content),
3555
+ role: effect.message.role || "system",
3556
+ kind: effect.message.kind,
3557
+ senderName: effect.message.senderName
3558
+ });
3559
+ } else if (effect.type === "toast" && effect.toast) {
3560
+ this.pushEvent({
3561
+ event: "provider:toast",
3562
+ effectId: effect.id || effectKey,
3563
+ timestamp: Date.now(),
3564
+ message: effect.toast.message,
3565
+ level: effect.toast.level || "info"
3566
+ });
3567
+ } else if (effect.type === "notification" && effect.notification) {
3568
+ this.pushEvent({
3569
+ event: "provider:notification",
3570
+ effectId: effect.id || effectKey,
3571
+ timestamp: Date.now(),
3572
+ title: effect.notification.title,
3573
+ message: effect.notification.body,
3574
+ content: typeof effect.notification.bubbleContent === "string" ? effect.notification.bubbleContent : effect.notification.body,
3575
+ level: effect.notification.level || "info",
3576
+ channels: effect.notification.channels || ["toast"],
3577
+ preferenceKey: effect.notification.preferenceKey
3578
+ });
3579
+ }
3580
+ }
3581
+ }
3582
+ appendRuntimeSystemMessage(content, dedupKey, receivedAt = Date.now()) {
3583
+ const normalizedContent = String(content || "").trim();
3584
+ if (!normalizedContent) return;
3585
+ if (this.runtimeMessages.some((entry) => entry.key === dedupKey)) return;
3586
+ if (!this.cachedChat) {
3587
+ this.cachedChat = {
3588
+ id: "active_session",
3589
+ title: this.provider.name,
3590
+ status: this.currentStatus,
3591
+ messages: [],
3592
+ activeModal: null,
3593
+ inputContent: ""
3594
+ };
3595
+ }
3596
+ this.runtimeMessages.push({
3597
+ key: dedupKey,
3598
+ message: {
3599
+ role: "system",
3600
+ senderName: "System",
3601
+ content: normalizedContent,
3602
+ receivedAt,
3603
+ timestamp: receivedAt
3604
+ }
3605
+ });
3606
+ if (this.runtimeMessages.length > 50) this.runtimeMessages = this.runtimeMessages.slice(-50);
3607
+ this.historyWriter.appendNewMessages(
3608
+ this.type,
3609
+ [{
3610
+ role: "system",
3611
+ senderName: "System",
3612
+ content: normalizedContent,
3613
+ kind: "system",
3614
+ receivedAt,
3615
+ historyDedupKey: dedupKey
3616
+ }],
3617
+ this.cachedChat?.title || this.provider.name,
3618
+ this.instanceId,
3619
+ this.cachedChat?.id || this.instanceId
3620
+ );
3621
+ }
3622
+ mergeConversationMessages(messages) {
3623
+ if (this.runtimeMessages.length === 0) return messages;
3624
+ return [...messages, ...this.runtimeMessages.map((entry) => entry.message)].map((message, index) => ({ message, index })).sort((a, b) => {
3625
+ const aTime = a.message.receivedAt || a.message.timestamp || 0;
3626
+ const bTime = b.message.receivedAt || b.message.timestamp || 0;
3627
+ if (aTime !== bTime) return aTime - bTime;
3628
+ return a.index - b.index;
3629
+ }).map((entry) => entry.message);
3630
+ }
3631
+ getPersistedEffectContent(effect) {
3632
+ if (effect.type === "message") {
3633
+ return typeof effect.message?.content === "string" ? effect.message.content : JSON.stringify(effect.message?.content || "");
3634
+ }
3635
+ if (effect.type === "toast") {
3636
+ return effect.toast?.message || null;
3637
+ }
3638
+ if (effect.type === "notification") {
3639
+ if (typeof effect.notification?.bubbleContent === "string") return effect.notification.bubbleContent;
3640
+ if (typeof effect.notification?.title === "string" && effect.notification.title.trim()) {
3641
+ return `${effect.notification.title}
3642
+ ${effect.notification.body || ""}`.trim();
3643
+ }
3644
+ return effect.notification?.body || null;
3645
+ }
3646
+ return null;
3647
+ }
3648
+ getEffectDedupKey(effect) {
3649
+ if (effect.id) return `provider_effect:${effect.id}`;
3650
+ if (effect.type === "message") {
3651
+ return `provider_effect:message:${typeof effect.message?.content === "string" ? effect.message.content : JSON.stringify(effect.message?.content || "")}`;
3652
+ }
3653
+ if (effect.type === "notification") {
3654
+ return `provider_effect:notification:${effect.notification?.title || ""}:${effect.notification?.body || ""}`;
3655
+ }
3656
+ return `provider_effect:toast:${effect.toast?.message || ""}`;
3657
+ }
3270
3658
  flushEvents() {
3271
3659
  const events = [...this.events];
3272
3660
  this.events = [];
@@ -4122,6 +4510,56 @@ var init_builders = __esm({
4122
4510
  }
4123
4511
  });
4124
4512
 
4513
+ // ../../oss/packages/daemon-core/src/sessions/reconcile.ts
4514
+ function upsertSessionTarget(sessionRegistry, target) {
4515
+ const existing = sessionRegistry.get(target.sessionId);
4516
+ if (existing && existing.parentSessionId === target.parentSessionId && existing.providerType === target.providerType && existing.transport === target.transport && existing.cdpManagerKey === target.cdpManagerKey && existing.instanceKey === target.instanceKey) {
4517
+ return;
4518
+ }
4519
+ sessionRegistry.register(target);
4520
+ }
4521
+ function reconcileIdeRuntimeSessions(instanceManager, sessionRegistry) {
4522
+ if (!instanceManager || !sessionRegistry) return;
4523
+ for (const instanceKey of instanceManager.listInstanceIds()) {
4524
+ if (!instanceKey.startsWith("ide:")) continue;
4525
+ const ideInstance = instanceManager.getInstance(instanceKey);
4526
+ if (!ideInstance || ideInstance.category !== "ide" || typeof ideInstance.getInstanceId !== "function") {
4527
+ continue;
4528
+ }
4529
+ const managerKey = instanceKey.slice(4);
4530
+ const ideType = typeof ideInstance.type === "string" && ideInstance.type.trim() ? ideInstance.type.trim() : managerKey.split("_")[0];
4531
+ const parentSessionId = ideInstance.getInstanceId();
4532
+ if (!parentSessionId) continue;
4533
+ upsertSessionTarget(sessionRegistry, {
4534
+ sessionId: parentSessionId,
4535
+ parentSessionId: null,
4536
+ providerType: ideType,
4537
+ transport: "cdp-page",
4538
+ cdpManagerKey: managerKey,
4539
+ instanceKey
4540
+ });
4541
+ const extensions = ideInstance.getExtensionInstances?.() || [];
4542
+ for (const ext of extensions) {
4543
+ const extType = typeof ext?.type === "string" ? ext.type.trim() : "";
4544
+ const extSessionId = ext?.getInstanceId?.();
4545
+ if (!extType || !extSessionId) continue;
4546
+ upsertSessionTarget(sessionRegistry, {
4547
+ sessionId: extSessionId,
4548
+ parentSessionId,
4549
+ providerType: extType,
4550
+ transport: "cdp-webview",
4551
+ cdpManagerKey: managerKey,
4552
+ instanceKey
4553
+ });
4554
+ }
4555
+ }
4556
+ }
4557
+ var init_reconcile = __esm({
4558
+ "../../oss/packages/daemon-core/src/sessions/reconcile.ts"() {
4559
+ "use strict";
4560
+ }
4561
+ });
4562
+
4125
4563
  // ../../oss/packages/daemon-core/src/commands/chat-commands.ts
4126
4564
  function getCurrentProviderType(h, fallback = "") {
4127
4565
  return h.currentSession?.providerType || h.currentProviderType || fallback;
@@ -5388,7 +5826,56 @@ function handleSetProviderSetting(h, args) {
5388
5826
  }
5389
5827
  return { success: false, error: `Failed to set ${providerType}.${key} \u2014 invalid key, value, or not a public setting` };
5390
5828
  }
5391
- async function handleExtensionScript(h, args, scriptName) {
5829
+ function normalizeProviderScriptArgs(args) {
5830
+ const normalizedArgs = { ...args || {} };
5831
+ for (const key of ["mode", "model", "message", "action", "button", "text", "sessionId", "value"]) {
5832
+ if (key in normalizedArgs && !(key.toUpperCase() in normalizedArgs)) {
5833
+ normalizedArgs[key.toUpperCase()] = normalizedArgs[key];
5834
+ }
5835
+ }
5836
+ return normalizedArgs;
5837
+ }
5838
+ function parseScriptResult(result) {
5839
+ if (typeof result === "string") {
5840
+ try {
5841
+ const parsed = JSON.parse(result);
5842
+ if (parsed && typeof parsed === "object" && parsed.success === false) {
5843
+ return { success: false, payload: parsed };
5844
+ }
5845
+ return { success: true, payload: parsed };
5846
+ } catch {
5847
+ return { success: true, payload: { result } };
5848
+ }
5849
+ }
5850
+ if (result && typeof result === "object" && result.success === false) {
5851
+ return { success: false, payload: result };
5852
+ }
5853
+ return { success: true, payload: result };
5854
+ }
5855
+ function getCliScriptCommand(payload) {
5856
+ if (!payload || typeof payload !== "object") return null;
5857
+ if (typeof payload.sendMessage === "string" && payload.sendMessage.trim()) {
5858
+ return { type: "send_message", text: payload.sendMessage.trim() };
5859
+ }
5860
+ const command = payload.command;
5861
+ if (!command || typeof command !== "object") return null;
5862
+ if (command.type !== "send_message") return null;
5863
+ const text = typeof command.text === "string" ? command.text.trim() : typeof command.message === "string" ? command.message.trim() : "";
5864
+ if (!text) return null;
5865
+ return { type: "send_message", text };
5866
+ }
5867
+ function applyProviderPatch(h, args, payload) {
5868
+ if (!payload || typeof payload !== "object") return;
5869
+ const targetSessionId = typeof args?.targetSessionId === "string" ? args.targetSessionId.trim() : "";
5870
+ const targetSession = targetSessionId ? h.ctx.sessionRegistry?.get(targetSessionId) : void 0;
5871
+ const instanceKey = targetSession?.instanceKey || targetSessionId;
5872
+ if (!instanceKey) return;
5873
+ h.ctx.instanceManager?.sendEvent(instanceKey, "provider_state_patch", {
5874
+ ...payload,
5875
+ extensionType: targetSession?.transport === "cdp-webview" ? targetSession.providerType : void 0
5876
+ });
5877
+ }
5878
+ async function executeProviderScript(h, args, scriptName) {
5392
5879
  const { agentType, ideType } = args || {};
5393
5880
  if (!agentType) return { success: false, error: "agentType is required" };
5394
5881
  const loader = h.ctx.providerLoader;
@@ -5401,13 +5888,29 @@ async function handleExtensionScript(h, args, scriptName) {
5401
5888
  if (!provider.scripts?.[actualScriptName]) {
5402
5889
  return { success: false, error: `Script '${actualScriptName}' not available for ${agentType}` };
5403
5890
  }
5404
- const scriptFn = provider.scripts[actualScriptName];
5405
- const normalizedArgs = { ...args };
5406
- for (const key of ["mode", "model", "message", "action", "button", "text", "sessionId"]) {
5407
- if (key in normalizedArgs && !(key.toUpperCase() in normalizedArgs)) {
5408
- normalizedArgs[key.toUpperCase()] = normalizedArgs[key];
5891
+ const normalizedArgs = normalizeProviderScriptArgs(args);
5892
+ if (provider.category === "cli") {
5893
+ const adapter = h.getCliAdapter(args?.targetSessionId || agentType);
5894
+ if (!adapter?.invokeScript) {
5895
+ return { success: false, error: `CLI adapter does not support script '${actualScriptName}'` };
5896
+ }
5897
+ try {
5898
+ const raw = await adapter.invokeScript(actualScriptName, normalizedArgs);
5899
+ const parsed = parseScriptResult(raw);
5900
+ if (!parsed.success) {
5901
+ return { success: false, ...parsed.payload || {} };
5902
+ }
5903
+ const cliCommand = getCliScriptCommand(parsed.payload);
5904
+ if (cliCommand?.type === "send_message" && cliCommand.text) {
5905
+ await adapter.sendMessage(cliCommand.text);
5906
+ }
5907
+ applyProviderPatch(h, args, parsed.payload);
5908
+ return { success: true, ...parsed.payload && typeof parsed.payload === "object" ? parsed.payload : { result: parsed.payload } };
5909
+ } catch (e) {
5910
+ return { success: false, error: `Script execution failed: ${e.message}` };
5409
5911
  }
5410
5912
  }
5913
+ const scriptFn = provider.scripts[actualScriptName];
5411
5914
  const scriptCode = scriptFn(normalizedArgs);
5412
5915
  if (!scriptCode) return { success: false, error: `Script '${actualScriptName}' returned null` };
5413
5916
  const cdpKey = provider.category === "ide" ? h.currentSession?.cdpManagerKey || h.currentManagerKey || agentType : h.currentSession?.cdpManagerKey || h.currentManagerKey || ideType;
@@ -5461,16 +5964,29 @@ async function handleExtensionScript(h, args, scriptName) {
5461
5964
  if (typeof result === "string") {
5462
5965
  try {
5463
5966
  const parsed = JSON.parse(result);
5967
+ applyProviderPatch(h, args, parsed);
5968
+ if (parsed && typeof parsed === "object" && parsed.success === false) {
5969
+ return { success: false, ...parsed };
5970
+ }
5464
5971
  return { success: true, ...parsed };
5465
5972
  } catch {
5466
5973
  return { success: true, result };
5467
5974
  }
5468
5975
  }
5976
+ applyProviderPatch(h, args, result);
5469
5977
  return { success: true, result };
5470
5978
  } catch (e) {
5471
5979
  return { success: false, error: `Script execution failed: ${e.message}` };
5472
5980
  }
5473
5981
  }
5982
+ async function handleExtensionScript(h, args, scriptName) {
5983
+ return executeProviderScript(h, args, scriptName);
5984
+ }
5985
+ async function handleProviderScript(h, args) {
5986
+ const scriptName = typeof args?.scriptName === "string" ? args.scriptName.trim() : "";
5987
+ if (!scriptName) return { success: false, error: "scriptName is required" };
5988
+ return executeProviderScript(h, args, scriptName);
5989
+ }
5474
5990
  function handleGetIdeExtensions(h, args) {
5475
5991
  const { ideType } = args || {};
5476
5992
  const loader = h.ctx.providerLoader;
@@ -5700,6 +6216,7 @@ var init_handler = __esm({
5700
6216
  init_devtools();
5701
6217
  init_builders();
5702
6218
  init_chat_history();
6219
+ init_reconcile();
5703
6220
  init_logger();
5704
6221
  init_chat_commands();
5705
6222
  init_cdp_commands();
@@ -5834,17 +6351,27 @@ var init_handler = __esm({
5834
6351
  return key.split("_")[0];
5835
6352
  }
5836
6353
  resolveRoute(args) {
5837
- const session = this._ctx.sessionRegistry?.get(args?.targetSessionId);
5838
- const managerKey = this.extractIdeType(args);
5839
- const providerType = args?.agentType || args?.providerType || session?.providerType || this.inferProviderType(managerKey);
5840
- return { session, managerKey, providerType };
6354
+ const targetSessionId = typeof args?.targetSessionId === "string" ? args.targetSessionId.trim() : "";
6355
+ let session = targetSessionId ? this._ctx.sessionRegistry?.get(targetSessionId) : void 0;
6356
+ if (targetSessionId && !session) {
6357
+ reconcileIdeRuntimeSessions(this._ctx.instanceManager, this._ctx.sessionRegistry);
6358
+ session = this._ctx.sessionRegistry?.get(targetSessionId);
6359
+ }
6360
+ const sessionLookupFailed = !!targetSessionId && !session;
6361
+ const managerKey = this.extractIdeType(args, sessionLookupFailed);
6362
+ let providerType;
6363
+ if (!sessionLookupFailed) {
6364
+ providerType = session?.providerType || args?.agentType || args?.providerType || this.inferProviderType(managerKey);
6365
+ }
6366
+ return { session, managerKey, providerType, sessionLookupFailed };
5841
6367
  }
5842
6368
  /** Extract CDP scope key from target session or explicit ideType */
5843
- extractIdeType(args) {
6369
+ extractIdeType(args, sessionLookupFailed = false) {
5844
6370
  if (args?.targetSessionId) {
5845
6371
  const target = this._ctx.sessionRegistry?.get(args.targetSessionId);
5846
6372
  if (target?.cdpManagerKey) return target.cdpManagerKey;
5847
6373
  if (this._ctx.cdpManagers.has(args.targetSessionId)) return args.targetSessionId;
6374
+ if (sessionLookupFailed) return void 0;
5848
6375
  }
5849
6376
  if (args?.ideType) {
5850
6377
  const target = this._ctx.sessionRegistry?.get(args.ideType);
@@ -5891,6 +6418,33 @@ var init_handler = __esm({
5891
6418
  this._currentRoute = this.resolveRoute(args);
5892
6419
  const startedAt = Date.now();
5893
6420
  this.logCommandStart(cmd, args);
6421
+ const sessionScopedCommands = /* @__PURE__ */ new Set([
6422
+ "read_chat",
6423
+ "send_chat",
6424
+ "list_chats",
6425
+ "new_chat",
6426
+ "switch_chat",
6427
+ "set_mode",
6428
+ "change_model",
6429
+ "set_thought_level",
6430
+ "resolve_action",
6431
+ "focus_session",
6432
+ "pty_input",
6433
+ "pty_resize",
6434
+ "invoke_provider_script",
6435
+ "list_extension_models",
6436
+ "set_extension_model",
6437
+ "list_extension_modes",
6438
+ "set_extension_mode"
6439
+ ]);
6440
+ if (this._currentRoute.sessionLookupFailed && sessionScopedCommands.has(cmd)) {
6441
+ const result2 = {
6442
+ success: false,
6443
+ error: `Live session not found for targetSessionId: ${String(args?.targetSessionId || "").trim() || "unknown"}`
6444
+ };
6445
+ this.logCommandEnd(cmd, result2, startedAt);
6446
+ return result2;
6447
+ }
5894
6448
  let result;
5895
6449
  if (!this._currentRoute.session && !this._currentRoute.managerKey && !this._currentRoute.providerType) {
5896
6450
  const cdpCommands = ["send_chat", "read_chat", "list_chats", "new_chat", "switch_chat", "set_mode", "change_model", "set_thought_level", "resolve_action"];
@@ -5994,6 +6548,8 @@ var init_handler = __esm({
5994
6548
  case "set_ide_extension":
5995
6549
  return handleSetIdeExtension(this, args);
5996
6550
  // ─── Extension Model / Mode Control (stream-commands.ts) ──────────
6551
+ case "invoke_provider_script":
6552
+ return handleProviderScript(this, args);
5997
6553
  case "list_extension_models":
5998
6554
  return handleExtensionScript(this, args, "listModels");
5999
6555
  case "set_extension_model":
@@ -6678,6 +7234,53 @@ function stripTerminalNoise(str) {
6678
7234
  function sanitizeTerminalText(str) {
6679
7235
  return stripTerminalNoise(stripAnsi(str));
6680
7236
  }
7237
+ function splitCliScreenLines(text) {
7238
+ return String(text || "").replace(/\u0007/g, "").replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n").map((line) => line.replace(/\s+$/, ""));
7239
+ }
7240
+ function isPromptLikeCliLine(line) {
7241
+ const trimmed = String(line || "").trim();
7242
+ if (!trimmed) return false;
7243
+ return /^[❯›>]\s*(?:$|\S.*)$/.test(trimmed);
7244
+ }
7245
+ function buildCliScreenSnapshot(text) {
7246
+ const normalizedText = String(text || "").replace(/\r\n/g, "\n").replace(/\r/g, "\n");
7247
+ const rawLines = splitCliScreenLines(normalizedText);
7248
+ const lines = rawLines.map((line, index, arr) => {
7249
+ const trimmed = String(line || "").trim();
7250
+ return {
7251
+ index,
7252
+ fromTop: index,
7253
+ fromBottom: arr.length - index - 1,
7254
+ text: line,
7255
+ trimmed,
7256
+ isEmpty: trimmed.length === 0
7257
+ };
7258
+ });
7259
+ const nonEmptyLines = lines.filter((line) => !line.isEmpty);
7260
+ const firstNonEmptyLine = nonEmptyLines[0] ?? null;
7261
+ const lastNonEmptyLine = nonEmptyLines[nonEmptyLines.length - 1] ?? null;
7262
+ let promptLineIndex = -1;
7263
+ for (let i = lines.length - 1; i >= 0; i -= 1) {
7264
+ if (isPromptLikeCliLine(lines[i].text)) {
7265
+ promptLineIndex = i;
7266
+ break;
7267
+ }
7268
+ }
7269
+ return {
7270
+ text: normalizedText,
7271
+ lineCount: lines.length,
7272
+ lines,
7273
+ nonEmptyLines,
7274
+ firstNonEmptyLineIndex: firstNonEmptyLine?.index ?? -1,
7275
+ lastNonEmptyLineIndex: lastNonEmptyLine?.index ?? -1,
7276
+ firstNonEmptyLine,
7277
+ lastNonEmptyLine,
7278
+ promptLineIndex,
7279
+ promptLine: promptLineIndex >= 0 ? lines[promptLineIndex] : null,
7280
+ linesAbovePrompt: promptLineIndex >= 0 ? lines.slice(0, promptLineIndex) : [...lines],
7281
+ linesBelowPrompt: promptLineIndex >= 0 ? lines.slice(promptLineIndex + 1) : []
7282
+ };
7283
+ }
6681
7284
  function computeTerminalQueryTail(buffer) {
6682
7285
  const prefixes = ["\x1B[6n", "\x1B[?6n"];
6683
7286
  const maxLength = prefixes.reduce((n, value) => Math.max(n, value.length), 0) - 1;
@@ -6919,7 +7522,9 @@ var init_provider_cli_adapter = __esm({
6919
7522
  ready = false;
6920
7523
  startupBuffer = "";
6921
7524
  startupParseGate = false;
7525
+ startupSettleTimer = null;
6922
7526
  spawnAt = 0;
7527
+ startupFirstOutputAt = 0;
6923
7528
  // PTY I/O
6924
7529
  onPtyDataCallback = null;
6925
7530
  pendingOutputParseBuffer = "";
@@ -6960,6 +7565,7 @@ var init_provider_cli_adapter = __esm({
6960
7565
  statusHistory = [];
6961
7566
  // ─── CLI Scripts (script-based parsing) ───
6962
7567
  cliScripts;
7568
+ runtimeSettings = {};
6963
7569
  /** Full accumulated ANSI-stripped PTY output */
6964
7570
  accumulatedBuffer = "";
6965
7571
  /** Full accumulated raw PTY output (with ANSI) */
@@ -6974,14 +7580,15 @@ var init_provider_cli_adapter = __esm({
6974
7580
  traceSessionId = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
6975
7581
  static MAX_TRACE_ENTRIES = 250;
6976
7582
  providerResolutionMeta;
6977
- static IDLE_FINISH_CONFIRM_MS = 900;
7583
+ static IDLE_FINISH_CONFIRM_MS = 2e3;
7584
+ static STATUS_ACTIVITY_HOLD_MS = 2e3;
6978
7585
  static FINISH_RETRY_DELAY_MS = 300;
6979
7586
  static MAX_FINISH_RETRIES = 2;
6980
7587
  syncMessageViews() {
6981
7588
  this.messages = [...this.committedMessages];
6982
7589
  this.structuredMessages = [...this.committedMessages];
6983
7590
  }
6984
- normalizeParsedMessages(parsedMessages) {
7591
+ hydrateParsedMessages(parsedMessages, scope) {
6985
7592
  const referenceMessages = [...this.committedMessages];
6986
7593
  const usedReferenceIndexes = /* @__PURE__ */ new Set();
6987
7594
  const now = Date.now();
@@ -7014,13 +7621,30 @@ var init_provider_cli_adapter = __esm({
7014
7621
  const content = typeof message.content === "string" ? message.content : String(message.content || "");
7015
7622
  const parsedTimestamp = typeof message.timestamp === "number" && Number.isFinite(message.timestamp) ? message.timestamp : void 0;
7016
7623
  const referenceTimestamp = parsedTimestamp ?? findReferenceTimestamp(role, content, index);
7624
+ const fallbackTimestamp = role === "user" ? scope?.startedAt || now : this.lastOutputAt || scope?.startedAt || now;
7625
+ const timestamp = referenceTimestamp ?? fallbackTimestamp;
7017
7626
  return {
7627
+ ...message,
7018
7628
  role,
7019
7629
  content,
7020
- timestamp: referenceTimestamp ?? now
7630
+ timestamp,
7631
+ receivedAt: typeof message.receivedAt === "number" && Number.isFinite(message.receivedAt) ? message.receivedAt : timestamp
7021
7632
  };
7022
7633
  });
7023
7634
  }
7635
+ normalizeParsedMessages(parsedMessages, scope) {
7636
+ return this.hydrateParsedMessages(parsedMessages, scope).map((message) => ({
7637
+ role: message.role,
7638
+ content: message.content,
7639
+ timestamp: message.timestamp,
7640
+ receivedAt: message.receivedAt,
7641
+ kind: message.kind,
7642
+ id: message.id,
7643
+ index: message.index,
7644
+ meta: message.meta,
7645
+ senderName: message.senderName
7646
+ }));
7647
+ }
7024
7648
  sliceFromOffset(text, start) {
7025
7649
  if (!text) return "";
7026
7650
  if (!Number.isFinite(start) || start <= 0) return text;
@@ -7030,14 +7654,20 @@ var init_provider_cli_adapter = __esm({
7030
7654
  buildParseInput(baseMessages, partialResponse, scope) {
7031
7655
  const buffer = scope ? this.sliceFromOffset(this.accumulatedBuffer, scope.bufferStart) || this.accumulatedBuffer : this.accumulatedBuffer;
7032
7656
  const rawBuffer = scope ? this.sliceFromOffset(this.accumulatedRawBuffer, scope.rawBufferStart) || this.accumulatedRawBuffer : this.accumulatedRawBuffer;
7657
+ const screenText = this.terminalScreen.getText();
7658
+ const recentBuffer = buffer.slice(-1e3) || this.recentOutputBuffer;
7033
7659
  return {
7034
7660
  buffer,
7035
7661
  rawBuffer,
7036
- recentBuffer: buffer.slice(-1e3) || this.recentOutputBuffer,
7037
- screenText: this.terminalScreen.getText(),
7662
+ recentBuffer,
7663
+ screenText,
7664
+ screen: buildCliScreenSnapshot(screenText),
7665
+ bufferScreen: buildCliScreenSnapshot(buffer),
7666
+ recentScreen: buildCliScreenSnapshot(recentBuffer),
7038
7667
  messages: [...baseMessages],
7039
7668
  partialResponse,
7040
- promptText: scope?.prompt || ""
7669
+ promptText: scope?.prompt || "",
7670
+ settings: { ...this.runtimeSettings }
7041
7671
  };
7042
7672
  }
7043
7673
  setStatus(status, trigger) {
@@ -7143,6 +7773,9 @@ var init_provider_cli_adapter = __esm({
7143
7773
  const scriptNames = Object.keys(scripts).filter((k) => typeof scripts[k] === "function");
7144
7774
  LOG.info("CLI", `[${this.cliType}] CLI scripts injected: [${scriptNames.join(", ")}]`);
7145
7775
  }
7776
+ updateRuntimeSettings(settings) {
7777
+ this.runtimeSettings = { ...settings };
7778
+ }
7146
7779
  // ─── Lifecycle ─────────────────────────────────
7147
7780
  setServerConn(serverConn) {
7148
7781
  this.serverConn = serverConn;
@@ -7277,6 +7910,11 @@ var init_provider_cli_adapter = __esm({
7277
7910
  this.spawnAt = Date.now();
7278
7911
  this.startupParseGate = true;
7279
7912
  this.startupBuffer = "";
7913
+ this.startupFirstOutputAt = 0;
7914
+ if (this.startupSettleTimer) {
7915
+ clearTimeout(this.startupSettleTimer);
7916
+ this.startupSettleTimer = null;
7917
+ }
7280
7918
  this.terminalScreen.reset(24, 80);
7281
7919
  this.pendingTerminalQueryTail = "";
7282
7920
  this.currentTurnScope = null;
@@ -7290,7 +7928,8 @@ var init_provider_cli_adapter = __esm({
7290
7928
  this.recordTrace("ready", {
7291
7929
  runtimeMeta: this.getRuntimeMetadata()
7292
7930
  });
7293
- this.setStatus("idle", "pty_ready");
7931
+ this.setStatus("starting", "pty_ready");
7932
+ this.scheduleStartupSettleCheck();
7294
7933
  this.onStatusChange?.();
7295
7934
  }
7296
7935
  // ─── Output Handling ────────────────────────────
@@ -7305,6 +7944,9 @@ var init_provider_cli_adapter = __esm({
7305
7944
  this.lastScreenSnapshot = normalizedScreenSnapshot;
7306
7945
  this.lastScreenChangeAt = now;
7307
7946
  }
7947
+ if (this.startupParseGate && !this.startupFirstOutputAt && (cleanData.trim() || normalizedScreenSnapshot.trim())) {
7948
+ this.startupFirstOutputAt = now;
7949
+ }
7308
7950
  if (this.idleFinishCandidate && (rawData.length > 0 || cleanData.length > 0)) {
7309
7951
  this.clearIdleFinishCandidate("new_output");
7310
7952
  }
@@ -7315,6 +7957,9 @@ var init_provider_cli_adapter = __esm({
7315
7957
  cleanPreview: this.summarizeTraceText(cleanData, 300),
7316
7958
  screenText: this.summarizeTraceText(this.terminalScreen.getText(), 1200)
7317
7959
  });
7960
+ if (this.startupParseGate) {
7961
+ this.scheduleStartupSettleCheck();
7962
+ }
7318
7963
  if (this.isWaitingForResponse && cleanData) {
7319
7964
  this.responseBuffer = (this.responseBuffer + cleanData).slice(-8e3);
7320
7965
  }
@@ -7328,27 +7973,51 @@ var init_provider_cli_adapter = __esm({
7328
7973
  this.recentOutputBuffer = (this.recentOutputBuffer + cleanData).slice(-1e3);
7329
7974
  this.accumulatedBuffer = (this.accumulatedBuffer + cleanData).slice(-_ProviderCliAdapter.MAX_ACCUMULATED_BUFFER);
7330
7975
  this.accumulatedRawBuffer = (this.accumulatedRawBuffer + rawData).slice(-_ProviderCliAdapter.MAX_ACCUMULATED_BUFFER);
7331
- if (this.startupParseGate) {
7332
- this.startupBuffer += cleanData;
7333
- const elapsed = Date.now() - this.spawnAt;
7334
- const screenText = this.terminalScreen.getText() || "";
7335
- const startupModal = this.getStartupConfirmationModal(screenText);
7336
- const scriptStatus = startupModal ? "waiting_approval" : this.runDetectStatus(this.startupBuffer);
7337
- const hasInteractivePrompt = this.looksLikeVisibleIdlePrompt(screenText);
7338
- const startupStableMs = this.lastScreenChangeAt ? now - this.lastScreenChangeAt : 0;
7339
- const isReady = (scriptStatus === "idle" || scriptStatus === "waiting_approval") && hasInteractivePrompt && startupStableMs >= 700 || !!startupModal && startupStableMs >= 700 || elapsed > 8e3 || this.startupBuffer.length > 12e3;
7340
- if (isReady) {
7341
- this.startupParseGate = false;
7342
- this.ready = true;
7343
- LOG.info(
7344
- "CLI",
7345
- `[${this.cliType}] Startup ready (${elapsed}ms, scriptStatus=${scriptStatus}, prompt=${hasInteractivePrompt}, stableMs=${startupStableMs}) providerDir=${this.providerResolutionMeta.providerDir || "-"} scriptDir=${this.providerResolutionMeta.scriptDir || "-"} scriptsPath=${this.providerResolutionMeta.scriptsPath || "-"}`
7346
- );
7347
- this.onStatusChange?.();
7348
- }
7349
- }
7976
+ this.resolveStartupState("output");
7350
7977
  this.scheduleSettle();
7351
7978
  }
7979
+ resolveStartupState(trigger) {
7980
+ if (!this.startupParseGate) return;
7981
+ const now = Date.now();
7982
+ const screenText = this.terminalScreen.getText() || "";
7983
+ const normalizedScreen = normalizeScreenSnapshot(screenText);
7984
+ const hasStartupOutput = !!this.startupFirstOutputAt || !!normalizedScreen.trim();
7985
+ if (!hasStartupOutput) return;
7986
+ const stableMs = this.lastScreenChangeAt ? now - this.lastScreenChangeAt : 0;
7987
+ if (stableMs < 2e3) return;
7988
+ const startupModal = this.getStartupConfirmationModal(screenText);
7989
+ this.startupParseGate = false;
7990
+ if (this.startupSettleTimer) {
7991
+ clearTimeout(this.startupSettleTimer);
7992
+ this.startupSettleTimer = null;
7993
+ }
7994
+ this.ready = true;
7995
+ if (startupModal) {
7996
+ this.activeModal = startupModal;
7997
+ this.setStatus("waiting_approval", `startup_ready:${trigger}`);
7998
+ } else {
7999
+ this.setStatus("idle", `startup_ready:${trigger}`);
8000
+ }
8001
+ LOG.info(
8002
+ "CLI",
8003
+ `[${this.cliType}] Startup settled (${trigger}, stableMs=${stableMs}, modal=${!!startupModal}) providerDir=${this.providerResolutionMeta.providerDir || "-"} scriptDir=${this.providerResolutionMeta.scriptDir || "-"} scriptsPath=${this.providerResolutionMeta.scriptsPath || "-"}`
8004
+ );
8005
+ this.onStatusChange?.();
8006
+ }
8007
+ scheduleStartupSettleCheck() {
8008
+ if (!this.startupParseGate) return;
8009
+ if (this.startupSettleTimer) clearTimeout(this.startupSettleTimer);
8010
+ const now = Date.now();
8011
+ const stableMs = this.lastScreenChangeAt ? now - this.lastScreenChangeAt : 0;
8012
+ const delayMs = Math.max(250, 2050 - stableMs);
8013
+ this.startupSettleTimer = setTimeout(() => {
8014
+ this.startupSettleTimer = null;
8015
+ this.resolveStartupState("startup_timer");
8016
+ if (this.startupParseGate && Date.now() - this.spawnAt < 1e4) {
8017
+ this.scheduleStartupSettleCheck();
8018
+ }
8019
+ }, delayMs);
8020
+ }
7352
8021
  scheduleSettle() {
7353
8022
  if (this.settleTimer) clearTimeout(this.settleTimer);
7354
8023
  const settleEpoch = this.responseEpoch;
@@ -7415,11 +8084,14 @@ var init_provider_cli_adapter = __esm({
7415
8084
  if (allLines.length === 0) return false;
7416
8085
  const recentLines = allLines.slice(-12);
7417
8086
  const promptIndex = this.findLastMatchingLineIndex(recentLines, (line) => /^[❯›>]\s*$/.test(line));
7418
- const activeRegion = promptIndex >= 0 ? recentLines.slice(promptIndex + 1) : recentLines;
8087
+ const activeRegion = promptIndex >= 0 ? recentLines.slice(Math.max(0, promptIndex - 2), promptIndex) : recentLines;
7419
8088
  if (activeRegion.length === 0) return false;
7420
8089
  return activeRegion.some((line) => this.looksLikeClaudeGeneratingLine(line));
7421
8090
  }
7422
8091
  refineDetectedStatus(status, tail, screenText) {
8092
+ if (this.startupParseGate) {
8093
+ return this.getStartupConfirmationModal(screenText || "") ? "waiting_approval" : "starting";
8094
+ }
7423
8095
  if (status === "waiting_approval") return status;
7424
8096
  if (this.detectClaudeGeneratingOverride(screenText || "", tail)) return "generating";
7425
8097
  return status;
@@ -7458,6 +8130,11 @@ var init_provider_cli_adapter = __esm({
7458
8130
  const screenStableMs = this.lastScreenChangeAt ? now - this.lastScreenChangeAt : 0;
7459
8131
  return quietForMs < 1200 || screenStableMs < 1200 || !commitResult.hasAssistant;
7460
8132
  }
8133
+ hasRecentInteractiveActivity(now) {
8134
+ const quietForMs = this.lastNonEmptyOutputAt ? now - this.lastNonEmptyOutputAt : Number.MAX_SAFE_INTEGER;
8135
+ const screenStableMs = this.lastScreenChangeAt ? now - this.lastScreenChangeAt : Number.MAX_SAFE_INTEGER;
8136
+ return quietForMs < _ProviderCliAdapter.STATUS_ACTIVITY_HOLD_MS || screenStableMs < _ProviderCliAdapter.STATUS_ACTIVITY_HOLD_MS;
8137
+ }
7461
8138
  getStartupConfirmationModal(screenText) {
7462
8139
  const text = sanitizeTerminalText(String(screenText || ""));
7463
8140
  if (!text.trim()) return null;
@@ -7485,13 +8162,14 @@ var init_provider_cli_adapter = __esm({
7485
8162
  const startedAt = Date.now();
7486
8163
  let loggedWait = false;
7487
8164
  while (Date.now() - startedAt < maxWaitMs) {
8165
+ this.resolveStartupState("interactive_wait");
7488
8166
  const screenText = this.terminalScreen.getText() || "";
7489
8167
  const hasPrompt = this.looksLikeVisibleIdlePrompt(screenText);
7490
8168
  const stableMs = this.lastScreenChangeAt ? Date.now() - this.lastScreenChangeAt : 0;
7491
8169
  const recentlyOutput = this.lastNonEmptyOutputAt ? Date.now() - this.lastNonEmptyOutputAt : Number.MAX_SAFE_INTEGER;
7492
8170
  const status = this.runDetectStatus(this.recentOutputBuffer) || this.currentStatus;
7493
8171
  const startupLikelyActive = /Welcome back|Tips for getting|Recent activity|Claude Code v\d/i.test(screenText);
7494
- const interactiveReady = hasPrompt && stableMs >= 700 && recentlyOutput >= 350 && status !== "starting" && status !== "generating";
8172
+ const interactiveReady = hasPrompt && stableMs >= 700 && recentlyOutput >= 350 && status !== "generating";
7495
8173
  if (interactiveReady) {
7496
8174
  if (loggedWait) {
7497
8175
  LOG.info(
@@ -7530,6 +8208,10 @@ var init_provider_cli_adapter = __esm({
7530
8208
  }
7531
8209
  const tail = this.settledBuffer;
7532
8210
  const screenText = this.terminalScreen.getText() || "";
8211
+ this.resolveStartupState("settled");
8212
+ if (this.startupParseGate) {
8213
+ return;
8214
+ }
7533
8215
  const startupModal = this.getStartupConfirmationModal(screenText);
7534
8216
  const modal = this.runParseApproval(tail) || startupModal;
7535
8217
  const rawScriptStatus = this.runDetectStatus(tail);
@@ -7592,6 +8274,28 @@ var init_provider_cli_adapter = __esm({
7592
8274
  } else {
7593
8275
  clearPendingScriptStatus();
7594
8276
  }
8277
+ const recentInteractiveActivity = this.hasRecentInteractiveActivity(now);
8278
+ const shouldHoldGenerating = scriptStatus === "idle" && this.isWaitingForResponse && !modal && recentInteractiveActivity;
8279
+ if (shouldHoldGenerating) {
8280
+ this.clearIdleFinishCandidate("hold_generating_recent_activity");
8281
+ this.setStatus("generating", "recent_activity_hold");
8282
+ if (this.idleTimeout) clearTimeout(this.idleTimeout);
8283
+ this.idleTimeout = setTimeout(() => {
8284
+ if (this.isWaitingForResponse && this.currentStatus !== "waiting_approval") {
8285
+ this.finishResponse();
8286
+ }
8287
+ }, this.timeouts.generatingIdle);
8288
+ this.recordTrace("hold_generating_recent_activity", {
8289
+ scriptStatus,
8290
+ recentInteractiveActivity,
8291
+ lastNonEmptyOutputAt: this.lastNonEmptyOutputAt,
8292
+ lastScreenChangeAt: this.lastScreenChangeAt,
8293
+ holdMs: _ProviderCliAdapter.STATUS_ACTIVITY_HOLD_MS,
8294
+ ...this.buildTraceParseSnapshot(this.currentTurnScope, this.responseBuffer)
8295
+ });
8296
+ this.onStatusChange?.();
8297
+ return;
8298
+ }
7595
8299
  if (scriptStatus === "waiting_approval") {
7596
8300
  this.clearIdleFinishCandidate("waiting_approval");
7597
8301
  const inCooldown = this.lastApprovalResolvedAt && Date.now() - this.lastApprovalResolvedAt < this.timeouts.approvalCooldown;
@@ -7669,8 +8373,8 @@ var init_provider_cli_adapter = __esm({
7669
8373
  const screenStableMs = this.lastScreenChangeAt ? now - this.lastScreenChangeAt : 0;
7670
8374
  const hasAssistantTurn = !!lastParsedAssistant;
7671
8375
  const assistantLength = lastParsedAssistant?.content?.length || 0;
7672
- const idleQuietThresholdMs = Math.max(220, this.timeouts.outputSettle);
7673
- const idleStableThresholdMs = Math.max(120, Math.min(220, this.timeouts.outputSettle));
8376
+ const idleQuietThresholdMs = Math.max(2e3, this.timeouts.outputSettle);
8377
+ const idleStableThresholdMs = 2e3;
7674
8378
  const idleReady = visibleIdlePrompt && !modal && hasAssistantTurn && quietForMs >= idleQuietThresholdMs && screenStableMs >= idleStableThresholdMs;
7675
8379
  const candidate = this.idleFinishCandidate;
7676
8380
  const candidateQuiet = !!candidate && candidate.responseEpoch === this.responseEpoch && candidate.lastOutputAt === this.lastOutputAt && candidate.lastScreenChangeAt === this.lastScreenChangeAt && assistantLength >= candidate.assistantLength && now - candidate.armedAt >= _ProviderCliAdapter.IDLE_FINISH_CONFIRM_MS;
@@ -7784,7 +8488,7 @@ var init_provider_cli_adapter = __esm({
7784
8488
  this.currentTurnScope
7785
8489
  );
7786
8490
  if (parsed && Array.isArray(parsed.messages)) {
7787
- this.committedMessages = this.normalizeParsedMessages(parsed.messages);
8491
+ this.committedMessages = this.normalizeParsedMessages(parsed.messages, this.currentTurnScope);
7788
8492
  const promptForTrim = this.currentTurnScope?.prompt || getLastUserPromptText(this.committedMessages);
7789
8493
  if (promptForTrim) {
7790
8494
  const lastAssistantForTrim = [...this.committedMessages].reverse().find((message) => message.role === "assistant");
@@ -7825,7 +8529,9 @@ var init_provider_cli_adapter = __esm({
7825
8529
  const status = this.cliScripts.detectStatus({
7826
8530
  tail: text.slice(-500),
7827
8531
  screenText,
7828
- rawBuffer: this.accumulatedRawBuffer
8532
+ rawBuffer: this.accumulatedRawBuffer,
8533
+ screen: buildCliScreenSnapshot(screenText),
8534
+ tailScreen: buildCliScreenSnapshot(text.slice(-500))
7829
8535
  });
7830
8536
  return this.refineDetectedStatus(status, text, screenText || "");
7831
8537
  } catch (e) {
@@ -7836,11 +8542,16 @@ var init_provider_cli_adapter = __esm({
7836
8542
  runParseApproval(tail) {
7837
8543
  if (!this.cliScripts?.parseApproval) return null;
7838
8544
  try {
8545
+ const screenText = this.terminalScreen.getText();
8546
+ const buffer = screenText || this.accumulatedBuffer;
7839
8547
  return this.cliScripts.parseApproval({
7840
- buffer: this.terminalScreen.getText() || this.accumulatedBuffer,
7841
- screenText: this.terminalScreen.getText(),
8548
+ buffer,
8549
+ screenText,
7842
8550
  rawBuffer: this.accumulatedRawBuffer,
7843
- tail
8551
+ tail,
8552
+ screen: buildCliScreenSnapshot(screenText),
8553
+ bufferScreen: buildCliScreenSnapshot(buffer),
8554
+ tailScreen: buildCliScreenSnapshot(tail)
7844
8555
  });
7845
8556
  } catch (e) {
7846
8557
  LOG.warn("CLI", `[${this.cliType}] parseApproval error: ${e.message}`);
@@ -7856,6 +8567,21 @@ var init_provider_cli_adapter = __esm({
7856
8567
  activeModal: this.activeModal
7857
8568
  };
7858
8569
  }
8570
+ seedCommittedMessages(messages) {
8571
+ const normalized = (Array.isArray(messages) ? messages : []).filter((message) => message && (message.role === "user" || message.role === "assistant")).map((message) => ({
8572
+ role: message.role,
8573
+ content: typeof message.content === "string" ? message.content : String(message.content || ""),
8574
+ timestamp: typeof message.timestamp === "number" && Number.isFinite(message.timestamp) ? message.timestamp : void 0,
8575
+ receivedAt: typeof message.receivedAt === "number" && Number.isFinite(message.receivedAt) ? message.receivedAt : void 0,
8576
+ kind: typeof message.kind === "string" ? message.kind : void 0,
8577
+ id: typeof message.id === "string" ? message.id : void 0,
8578
+ index: typeof message.index === "number" ? message.index : void 0,
8579
+ meta: message.meta && typeof message.meta === "object" ? { ...message.meta } : void 0,
8580
+ senderName: typeof message.senderName === "string" ? message.senderName : void 0
8581
+ }));
8582
+ this.committedMessages = normalized;
8583
+ this.syncMessageViews();
8584
+ }
7859
8585
  /**
7860
8586
  * Script-based full parse — returns ReadChatResult.
7861
8587
  * Called by command handler / dashboard for rich content rendering.
@@ -7866,12 +8592,20 @@ var init_provider_cli_adapter = __esm({
7866
8592
  this.responseBuffer,
7867
8593
  this.currentTurnScope
7868
8594
  );
8595
+ const shouldPreferCommittedMessages = !this.currentTurnScope && this.currentStatus === "idle" && !this.activeModal;
7869
8596
  if (parsed && Array.isArray(parsed.messages)) {
8597
+ const hydratedMessages = shouldPreferCommittedMessages ? this.committedMessages.map((message, index) => ({
8598
+ ...message,
8599
+ id: message.id || `msg_${index}`,
8600
+ index: typeof message.index === "number" ? message.index : index,
8601
+ kind: message.kind || "standard",
8602
+ receivedAt: typeof message.receivedAt === "number" ? message.receivedAt : message.timestamp
8603
+ })) : this.hydrateParsedMessages(parsed.messages, this.currentTurnScope);
7870
8604
  return {
7871
8605
  id: parsed.id || "cli_session",
7872
8606
  status: parsed.status || this.currentStatus,
7873
8607
  title: parsed.title || this.cliName,
7874
- messages: parsed.messages,
8608
+ messages: hydratedMessages,
7875
8609
  activeModal: parsed.activeModal ?? this.activeModal,
7876
8610
  providerSessionId: typeof parsed.providerSessionId === "string" ? parsed.providerSessionId : void 0
7877
8611
  };
@@ -7892,6 +8626,21 @@ var init_provider_cli_adapter = __esm({
7892
8626
  activeModal: this.activeModal
7893
8627
  };
7894
8628
  }
8629
+ async invokeScript(scriptName, args) {
8630
+ const fn = this.cliScripts?.[scriptName];
8631
+ if (typeof fn !== "function") {
8632
+ throw new Error(`CLI script '${scriptName}' not available`);
8633
+ }
8634
+ const input = this.buildParseInput(
8635
+ this.committedMessages,
8636
+ this.responseBuffer,
8637
+ this.currentTurnScope
8638
+ );
8639
+ return await Promise.resolve(fn({
8640
+ ...input,
8641
+ args: args && typeof args === "object" ? { ...args } : {}
8642
+ }));
8643
+ }
7895
8644
  parseCurrentTranscript(baseMessages, partialResponse, scope) {
7896
8645
  if (!this.cliScripts?.parseOutput) return null;
7897
8646
  try {
@@ -7947,12 +8696,23 @@ ${data.message || ""}`.trim();
7947
8696
  if (this.startupParseGate) {
7948
8697
  const deadline = Date.now() + 1e4;
7949
8698
  while (this.startupParseGate && Date.now() < deadline) {
8699
+ this.resolveStartupState("send_wait");
7950
8700
  await new Promise((resolve13) => setTimeout(resolve13, 50));
7951
8701
  }
7952
8702
  }
8703
+ await this.waitForInteractivePrompt();
8704
+ if (!this.ready) {
8705
+ this.resolveStartupState("send_precheck");
8706
+ const screenText = this.terminalScreen.getText() || "";
8707
+ const hasPrompt = this.looksLikeVisibleIdlePrompt(screenText);
8708
+ if (hasPrompt && this.currentStatus === "idle") {
8709
+ this.ready = true;
8710
+ this.startupParseGate = false;
8711
+ LOG.info("CLI", `[${this.cliType}] sendMessage recovered idle prompt readiness`);
8712
+ }
8713
+ }
7953
8714
  if (!this.ready) throw new Error(`${this.cliName} not ready (status: ${this.currentStatus})`);
7954
8715
  if (this.isWaitingForResponse) return;
7955
- await this.waitForInteractivePrompt();
7956
8716
  const blockingModal = this.activeModal || this.getStartupConfirmationModal(this.terminalScreen.getText() || "");
7957
8717
  if (blockingModal || this.currentStatus === "waiting_approval") {
7958
8718
  throw new Error(`${this.cliName} is awaiting confirmation before it can accept a prompt`);
@@ -7996,8 +8756,6 @@ ${data.message || ""}`.trim();
7996
8756
  }
7997
8757
  this.responseEpoch += 1;
7998
8758
  this.responseSettleIgnoreUntil = Date.now() + submitDelayMs + this.timeouts.outputSettle + 250;
7999
- this.setStatus("generating", "sendMessage");
8000
- this.onStatusChange?.();
8001
8759
  const startResponseTimeout = () => {
8002
8760
  if (this.responseTimeout) clearTimeout(this.responseTimeout);
8003
8761
  this.responseTimeout = setTimeout(() => {
@@ -8016,7 +8774,7 @@ ${data.message || ""}`.trim();
8016
8774
  const retrySubmitIfStuck = (attempt) => {
8017
8775
  this.submitRetryTimer = null;
8018
8776
  if (!this.ptyProcess || !this.isWaitingForResponse || this.submitRetryUsed) return;
8019
- if (this.currentStatus !== "generating") return;
8777
+ if (this.currentStatus === "waiting_approval") return;
8020
8778
  if ((this.responseBuffer || "").trim()) return;
8021
8779
  const screenText = this.terminalScreen.getText();
8022
8780
  if (!promptLikelyVisible(screenText, normalizedPromptSnippet)) return;
@@ -8051,7 +8809,7 @@ ${data.message || ""}`.trim();
8051
8809
  this.submitRetryTimer = setTimeout(() => {
8052
8810
  this.submitRetryTimer = null;
8053
8811
  if (!this.ptyProcess || !this.isWaitingForResponse || this.submitRetryUsed) return;
8054
- if (this.currentStatus !== "generating") return;
8812
+ if (this.currentStatus === "waiting_approval") return;
8055
8813
  if ((this.responseBuffer || "").trim()) return;
8056
8814
  const screenText = this.terminalScreen.getText();
8057
8815
  if (!promptLikelyVisible(screenText, normalizedPromptSnippet)) return;
@@ -8483,6 +9241,7 @@ var init_cli_provider_instance = __esm({
8483
9241
  init_status_monitor();
8484
9242
  init_chat_history();
8485
9243
  init_logger();
9244
+ init_control_effects();
8486
9245
  CachedDatabaseSync = null;
8487
9246
  CliProviderInstance = class {
8488
9247
  constructor(provider, workingDir, cliArgs = [], instanceId, transportFactory, options) {
@@ -8511,6 +9270,8 @@ var init_cli_provider_instance = __esm({
8511
9270
  generatingDebounceTimer = null;
8512
9271
  generatingDebouncePending = null;
8513
9272
  lastApprovalEventAt = 0;
9273
+ controlValues = {};
9274
+ appliedEffectKeys = /* @__PURE__ */ new Set();
8514
9275
  historyWriter;
8515
9276
  runtimeMessages = [];
8516
9277
  instanceId;
@@ -8523,6 +9284,7 @@ var init_cli_provider_instance = __esm({
8523
9284
  async init(context) {
8524
9285
  this.context = context;
8525
9286
  this.settings = context.settings || {};
9287
+ this.adapter.updateRuntimeSettings?.(this.settings);
8526
9288
  this.monitor.updateConfig({
8527
9289
  approvalAlert: this.settings.approvalAlert !== false,
8528
9290
  longGeneratingAlert: this.settings.longGeneratingAlert !== false,
@@ -8538,6 +9300,21 @@ var init_cli_provider_instance = __esm({
8538
9300
  this.detectStatusTransition();
8539
9301
  });
8540
9302
  await this.adapter.spawn();
9303
+ if (this.providerSessionId) {
9304
+ const restoredHistory = readChatHistory(this.type, 0, 200, this.providerSessionId);
9305
+ if (restoredHistory.messages.length > 0) {
9306
+ this.adapter.seedCommittedMessages(
9307
+ restoredHistory.messages.map((message) => ({
9308
+ role: message.role,
9309
+ content: message.content,
9310
+ timestamp: message.receivedAt,
9311
+ receivedAt: message.receivedAt,
9312
+ kind: message.kind,
9313
+ senderName: message.senderName
9314
+ }))
9315
+ );
9316
+ }
9317
+ }
8541
9318
  if (this.providerSessionId && this.launchMode === "resume") {
8542
9319
  const resumedAt = Date.now();
8543
9320
  this.historyWriter.appendSystemMarker(
@@ -8618,6 +9395,12 @@ var init_cli_provider_instance = __esm({
8618
9395
  }
8619
9396
  const runtime = this.adapter.getRuntimeMetadata();
8620
9397
  const parsedMessages = Array.isArray(parsedStatus?.messages) ? parsedStatus.messages : [];
9398
+ const controlValues = extractProviderControlValues(this.provider.controls, parsedStatus);
9399
+ if (controlValues) {
9400
+ this.controlValues = controlValues;
9401
+ } else if (Object.keys(this.controlValues).length > 0) {
9402
+ this.controlValues = {};
9403
+ }
8621
9404
  const mergedMessages = this.mergeConversationMessages(parsedMessages);
8622
9405
  const dirName = this.workingDir.split("/").filter(Boolean).pop() || "session";
8623
9406
  if (parsedMessages.length > 0) {
@@ -8638,6 +9421,7 @@ var init_cli_provider_instance = __esm({
8638
9421
  );
8639
9422
  }
8640
9423
  }
9424
+ this.applyProviderResponse(parsedStatus, { phase: "immediate" });
8641
9425
  return {
8642
9426
  type: this.type,
8643
9427
  name: this.provider.name,
@@ -8667,8 +9451,7 @@ var init_cli_provider_instance = __esm({
8667
9451
  attachedClients: runtime.attachedClients || []
8668
9452
  } : void 0,
8669
9453
  resume: this.provider.resume,
8670
- controlValues: void 0,
8671
- // CLI controls not yet wired from stream
9454
+ controlValues: this.controlValues,
8672
9455
  providerControls: this.provider.controls
8673
9456
  };
8674
9457
  }
@@ -8679,6 +9462,15 @@ var init_cli_provider_instance = __esm({
8679
9462
  getPresentationMode() {
8680
9463
  return this.presentationMode;
8681
9464
  }
9465
+ updateSettings(newSettings) {
9466
+ this.settings = { ...newSettings };
9467
+ this.adapter.updateRuntimeSettings?.(this.settings);
9468
+ this.monitor.updateConfig({
9469
+ approvalAlert: this.settings.approvalAlert !== false,
9470
+ longGeneratingAlert: this.settings.longGeneratingAlert !== false,
9471
+ longGeneratingThresholdSec: this.settings.longGeneratingThresholdSec || 180
9472
+ });
9473
+ }
8682
9474
  onEvent(event, data) {
8683
9475
  if (event === "send_message" && data?.text) {
8684
9476
  void this.adapter.sendMessage(data.text).catch((e) => {
@@ -8690,22 +9482,27 @@ var init_cli_provider_instance = __esm({
8690
9482
  void this.adapter.resolveAction(data).catch((e) => {
8691
9483
  LOG.warn("CLI", `[${this.type}] resolve_action failed: ${e?.message || e}`);
8692
9484
  });
9485
+ } else if (event === "provider_state_patch" && data && typeof data === "object") {
9486
+ this.applyProviderResponse(data, { phase: "immediate" });
8693
9487
  }
8694
9488
  }
8695
9489
  dispose() {
8696
9490
  this.adapter.shutdown();
8697
9491
  this.monitor.reset();
9492
+ this.appliedEffectKeys.clear();
8698
9493
  }
8699
9494
  completedDebounceTimer = null;
8700
9495
  completedDebouncePending = null;
8701
9496
  detectStatusTransition() {
8702
9497
  const now = Date.now();
8703
9498
  const adapterStatus = this.adapter.getStatus();
9499
+ const parsedStatus = this.adapter.getScriptParsedStatus?.() || null;
8704
9500
  const newStatus = adapterStatus.status;
8705
9501
  const dirName = this.workingDir.split("/").filter(Boolean).pop() || "session";
8706
9502
  const chatTitle = `${this.provider.name} \xB7 ${dirName}`;
8707
9503
  const partial2 = this.adapter.getPartialResponse();
8708
9504
  const progressFingerprint = newStatus === "generating" ? `${partial2 || ""}::${adapterStatus.messages.at(-1)?.content || ""}`.slice(-2e3) : void 0;
9505
+ const previousStatus = this.lastStatus;
8709
9506
  if (newStatus !== this.lastStatus) {
8710
9507
  LOG.info("CLI", `[${this.type}] status: ${this.lastStatus} \u2192 ${newStatus}`);
8711
9508
  if (this.lastStatus === "idle" && newStatus === "generating") {
@@ -8798,6 +9595,9 @@ var init_cli_provider_instance = __esm({
8798
9595
  }
8799
9596
  this.lastStatus = newStatus;
8800
9597
  }
9598
+ this.applyProviderResponse(parsedStatus, {
9599
+ phase: newStatus === "idle" && (previousStatus === "generating" || previousStatus === "waiting_approval") ? "turn_completed" : "immediate"
9600
+ });
8801
9601
  const agentKey = `${this.type}:cli`;
8802
9602
  const monitorEvents = this.monitor.check(agentKey, newStatus, now, progressFingerprint);
8803
9603
  for (const me of monitorEvents) {
@@ -8813,6 +9613,88 @@ var init_cli_provider_instance = __esm({
8813
9613
  this.events = [];
8814
9614
  return events;
8815
9615
  }
9616
+ applyProviderResponse(data, options) {
9617
+ if (!data || typeof data !== "object") return;
9618
+ const controlValues = extractProviderControlValues(this.provider.controls, data);
9619
+ if (controlValues) {
9620
+ this.controlValues = { ...this.controlValues, ...controlValues };
9621
+ }
9622
+ const effects = normalizeProviderEffects(data);
9623
+ for (const effect of effects) {
9624
+ const effectWhen = effect.when || "immediate";
9625
+ if (effectWhen === "turn_completed" && options.phase !== "turn_completed") continue;
9626
+ if (effectWhen === "immediate" && options.phase === "turn_completed") continue;
9627
+ const effectKey = this.getEffectDedupKey(effect);
9628
+ if (this.appliedEffectKeys.has(effectKey)) continue;
9629
+ this.appliedEffectKeys.add(effectKey);
9630
+ if (effect.persist !== false) {
9631
+ const persisted = this.getPersistedEffectContent(effect);
9632
+ if (persisted) this.appendRuntimeSystemMessage(persisted, effectKey);
9633
+ }
9634
+ if (effect.type === "message" && effect.message) {
9635
+ const content = typeof effect.message.content === "string" ? effect.message.content : JSON.stringify(effect.message.content);
9636
+ this.pushEvent({
9637
+ event: "provider:message",
9638
+ timestamp: Date.now(),
9639
+ content,
9640
+ role: effect.message.role || "system",
9641
+ kind: effect.message.kind,
9642
+ senderName: effect.message.senderName
9643
+ });
9644
+ } else if (effect.type === "toast" && effect.toast) {
9645
+ this.pushEvent({
9646
+ event: "provider:toast",
9647
+ effectId: effect.id || effectKey,
9648
+ timestamp: Date.now(),
9649
+ message: effect.toast.message,
9650
+ level: effect.toast.level || "info"
9651
+ });
9652
+ } else if (effect.type === "notification" && effect.notification) {
9653
+ this.pushEvent({
9654
+ event: "provider:notification",
9655
+ effectId: effect.id || effectKey,
9656
+ timestamp: Date.now(),
9657
+ title: effect.notification.title,
9658
+ message: effect.notification.body,
9659
+ content: typeof effect.notification.bubbleContent === "string" ? effect.notification.bubbleContent : effect.notification.body,
9660
+ level: effect.notification.level || "info",
9661
+ channels: effect.notification.channels || ["toast"],
9662
+ preferenceKey: effect.notification.preferenceKey
9663
+ });
9664
+ }
9665
+ }
9666
+ if (this.appliedEffectKeys.size > 200) {
9667
+ this.appliedEffectKeys = new Set(Array.from(this.appliedEffectKeys).slice(-100));
9668
+ }
9669
+ }
9670
+ getEffectDedupKey(effect) {
9671
+ if (effect.id) return `provider_effect:${effect.id}`;
9672
+ if (effect.type === "message") {
9673
+ const content = typeof effect.message?.content === "string" ? effect.message.content : JSON.stringify(effect.message?.content || "");
9674
+ return `provider_effect:message:${content}`;
9675
+ }
9676
+ if (effect.type === "notification") {
9677
+ return `provider_effect:notification:${effect.notification?.title || ""}:${effect.notification?.body || ""}`;
9678
+ }
9679
+ return `provider_effect:toast:${effect.toast?.message || ""}`;
9680
+ }
9681
+ getPersistedEffectContent(effect) {
9682
+ if (effect.type === "message") {
9683
+ return typeof effect.message?.content === "string" ? effect.message.content : JSON.stringify(effect.message?.content || "");
9684
+ }
9685
+ if (effect.type === "toast") {
9686
+ return effect.toast?.message || null;
9687
+ }
9688
+ if (effect.type === "notification") {
9689
+ if (typeof effect.notification?.bubbleContent === "string") return effect.notification.bubbleContent;
9690
+ if (typeof effect.notification?.title === "string" && effect.notification.title.trim()) {
9691
+ return `${effect.notification.title}
9692
+ ${effect.notification.body || ""}`.trim();
9693
+ }
9694
+ return effect.notification?.body || null;
9695
+ }
9696
+ return null;
9697
+ }
8816
9698
  // ─── Adapter access (backward compat) ──────────────────
8817
9699
  getAdapter() {
8818
9700
  return this.adapter;
@@ -29667,6 +30549,22 @@ function getMacAppIdentifiers() {
29667
30549
  function getWinProcessNames() {
29668
30550
  return getProviderLoader().getWinProcessNames();
29669
30551
  }
30552
+ function getProviderMeta(ideId) {
30553
+ return getProviderLoader().getMeta(ideId);
30554
+ }
30555
+ function getPreferredLaunchMethod(ideId, platform11) {
30556
+ const prefer = getProviderMeta(ideId)?.launch?.prefer;
30557
+ const value = prefer?.[platform11];
30558
+ return value === "cli" || value === "app" || value === "auto" ? value : "auto";
30559
+ }
30560
+ function getCdpStartupTimeoutMs(ideId) {
30561
+ const value = getProviderMeta(ideId)?.launch?.cdpStartupTimeoutMs;
30562
+ if (typeof value !== "number" || !Number.isFinite(value)) return 15e3;
30563
+ return Math.max(1e3, Math.floor(value));
30564
+ }
30565
+ function escapeForAppleScript(value) {
30566
+ return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
30567
+ }
29670
30568
  async function findFreePort(ports) {
29671
30569
  for (const port2 of ports) {
29672
30570
  const free = await checkPortFree(port2);
@@ -29719,12 +30617,12 @@ async function killIdeProcess(ideId) {
29719
30617
  try {
29720
30618
  if (plat === "darwin" && appName) {
29721
30619
  try {
29722
- (0, import_child_process6.execSync)(`osascript -e 'tell application "${appName}" to quit' 2>/dev/null`, {
30620
+ (0, import_child_process6.execSync)(`osascript -e 'tell application "${escapeForAppleScript(appName)}" to quit' 2>/dev/null`, {
29723
30621
  timeout: 5e3
29724
30622
  });
29725
30623
  } catch {
29726
30624
  try {
29727
- (0, import_child_process6.execSync)(`pkill -f "${appName}" 2>/dev/null`);
30625
+ (0, import_child_process6.execSync)(`pkill -x "${appName}" 2>/dev/null`, { timeout: 5e3 });
29728
30626
  } catch {
29729
30627
  }
29730
30628
  }
@@ -29754,7 +30652,7 @@ async function killIdeProcess(ideId) {
29754
30652
  }
29755
30653
  if (plat === "darwin" && appName) {
29756
30654
  try {
29757
- (0, import_child_process6.execSync)(`pkill -9 -f "${appName}" 2>/dev/null`);
30655
+ (0, import_child_process6.execSync)(`pkill -9 -x "${appName}" 2>/dev/null`, { timeout: 5e3 });
29758
30656
  } catch {
29759
30657
  }
29760
30658
  } else if (plat === "win32" && winProcesses) {
@@ -29777,8 +30675,23 @@ function isIdeRunning(ideId) {
29777
30675
  if (plat === "darwin") {
29778
30676
  const appName = getMacAppIdentifiers()[ideId];
29779
30677
  if (!appName) return false;
29780
- const result = (0, import_child_process6.execSync)(`pgrep -f "${appName}" 2>/dev/null`, { encoding: "utf-8" });
29781
- return result.trim().length > 0;
30678
+ try {
30679
+ const result = (0, import_child_process6.execSync)(`pgrep -x "${appName}" 2>/dev/null`, {
30680
+ encoding: "utf-8",
30681
+ timeout: 3e3
30682
+ });
30683
+ return result.trim().length > 0;
30684
+ } catch {
30685
+ const result = (0, import_child_process6.execSync)(
30686
+ `osascript -e 'tell application "System Events" to count (every process whose name is "${escapeForAppleScript(appName)}")'`,
30687
+ {
30688
+ encoding: "utf-8",
30689
+ timeout: 3e3,
30690
+ stdio: ["pipe", "pipe", "pipe"]
30691
+ }
30692
+ );
30693
+ return Number.parseInt(result.trim() || "0", 10) > 0;
30694
+ }
29782
30695
  } else if (plat === "win32") {
29783
30696
  const winProcesses = getWinProcessNames()[ideId];
29784
30697
  if (!winProcesses) return false;
@@ -29927,7 +30840,8 @@ async function launchWithCdp(options = {}) {
29927
30840
  await launchLinux(targetIde, port, workspace, options.newWindow);
29928
30841
  }
29929
30842
  let cdpReady = false;
29930
- for (let i = 0; i < 30; i++) {
30843
+ const waitDeadline = Date.now() + getCdpStartupTimeoutMs(targetIde.id);
30844
+ while (Date.now() < waitDeadline) {
29931
30845
  await new Promise((r) => setTimeout(r, 500));
29932
30846
  if (await isCdpActive(port)) {
29933
30847
  cdpReady = true;
@@ -29956,14 +30870,18 @@ async function launchWithCdp(options = {}) {
29956
30870
  }
29957
30871
  async function launchMacOS(ide, port, workspace, newWindow) {
29958
30872
  const appName = getMacAppIdentifiers()[ide.id];
30873
+ const preferredMethod = getPreferredLaunchMethod(ide.id, "darwin");
29959
30874
  const args = ["--remote-debugging-port=" + port];
29960
30875
  if (newWindow) args.push("--new-window");
29961
30876
  if (workspace) args.push(workspace);
29962
- if (appName) {
30877
+ const canUseCli = !!ide.cliCommand;
30878
+ const canUseAppLauncher = !!appName;
30879
+ const useAppLauncher = preferredMethod === "app" ? canUseAppLauncher : preferredMethod === "cli" ? false : !canUseCli && canUseAppLauncher;
30880
+ if (!useAppLauncher && ide.cliCommand) {
30881
+ (0, import_child_process6.spawn)(ide.cliCommand, args, { detached: true, stdio: "ignore" }).unref();
30882
+ } else if (appName) {
29963
30883
  const openArgs = ["-a", appName, "--args", ...args];
29964
30884
  (0, import_child_process6.spawn)("open", openArgs, { detached: true, stdio: "ignore" }).unref();
29965
- } else if (ide.cliCommand) {
29966
- (0, import_child_process6.spawn)(ide.cliCommand, args, { detached: true, stdio: "ignore" }).unref();
29967
30885
  } else {
29968
30886
  throw new Error(`No app identifier or CLI for ${ide.displayName}`);
29969
30887
  }
@@ -30281,6 +31199,7 @@ function buildStatusSnapshot(options) {
30281
31199
  workspaces: wsState.workspaces,
30282
31200
  defaultWorkspaceId: wsState.defaultWorkspaceId,
30283
31201
  defaultWorkspacePath: wsState.defaultWorkspacePath,
31202
+ terminalSizingMode: cfg.terminalSizingMode || "measured",
30284
31203
  recentLaunches: buildRecentLaunches(recentActivity),
30285
31204
  terminalBackend,
30286
31205
  availableProviders: buildAvailableProviders(options.providerLoader)
@@ -31129,6 +32048,7 @@ var ProviderStreamAdapter;
31129
32048
  var init_provider_adapter = __esm({
31130
32049
  "../../oss/packages/daemon-core/src/agent-stream/provider-adapter.ts"() {
31131
32050
  "use strict";
32051
+ init_control_effects();
31132
32052
  ProviderStreamAdapter = class {
31133
32053
  agentType;
31134
32054
  agentName;
@@ -31188,19 +32108,10 @@ var init_provider_adapter = __esm({
31188
32108
  mode: data.mode,
31189
32109
  activeModal: data.activeModal
31190
32110
  };
31191
- if (this.provider.controls?.length) {
31192
- const cv = {};
31193
- for (const ctrl of this.provider.controls) {
31194
- if (!ctrl.readFrom) continue;
31195
- const val = data[ctrl.readFrom];
31196
- if (val !== void 0 && val !== null) {
31197
- cv[ctrl.id] = typeof val === "object" ? val.name || val.id || String(val) : val;
31198
- }
31199
- }
31200
- if (data.model && !cv["model"]) cv["model"] = data.model;
31201
- if (data.mode && !cv["mode"]) cv["mode"] = data.mode;
31202
- if (Object.keys(cv).length > 0) state.controlValues = cv;
31203
- }
32111
+ const controlValues = extractProviderControlValues(this.provider.controls, data);
32112
+ if (controlValues) state.controlValues = controlValues;
32113
+ const effects = normalizeProviderEffects(data);
32114
+ if (effects.length > 0) state.effects = effects;
31204
32115
  if (state.messages.length > 0) {
31205
32116
  this.lastSuccessState = state;
31206
32117
  }
@@ -31592,6 +32503,7 @@ var init_poller = __esm({
31592
32503
  "../../oss/packages/daemon-core/src/agent-stream/poller.ts"() {
31593
32504
  "use strict";
31594
32505
  init_setup();
32506
+ init_reconcile();
31595
32507
  init_logger();
31596
32508
  AgentStreamPoller = class {
31597
32509
  deps;
@@ -31631,6 +32543,7 @@ var init_poller = __esm({
31631
32543
  sessionRegistry
31632
32544
  } = this.deps;
31633
32545
  if (!agentStreamManager || cdpManagers.size === 0) return;
32546
+ reconcileIdeRuntimeSessions(instanceManager, sessionRegistry);
31634
32547
  for (const [ideType, cdp] of cdpManagers) {
31635
32548
  registerExtensionProviders(providerLoader, cdp, ideType);
31636
32549
  const ideInstance = instanceManager.getInstance(`ide:${ideType}`);
@@ -31760,6 +32673,8 @@ function forwardAgentStreamsToIdeInstance(instanceManager, ideType, streams) {
31760
32673
  activeModal: stream.activeModal || null,
31761
32674
  model: stream.model || void 0,
31762
32675
  mode: stream.mode || void 0,
32676
+ controlValues: stream.controlValues || void 0,
32677
+ effects: stream.effects || void 0,
31763
32678
  sessionId: stream.sessionId || stream.instanceId || void 0,
31764
32679
  title: stream.title || stream.agentName || void 0,
31765
32680
  agentType: stream.agentType || void 0,
@@ -46443,7 +47358,7 @@ var init_adhdev_daemon = __esm({
46443
47358
  import_ws3 = require("ws");
46444
47359
  import_chalk2 = __toESM(require("chalk"));
46445
47360
  init_version();
46446
- pkgVersion = resolvePackageVersion({ injectedVersion: "0.8.22" });
47361
+ pkgVersion = resolvePackageVersion({ injectedVersion: "0.8.25" });
46447
47362
  DANGEROUS_PATTERNS = [
46448
47363
  /\brm\s+(-[a-z]*f|-[a-z]*r|--force|--recursive)/i,
46449
47364
  /\bsudo\b/i,