adhdev 0.8.21 → 0.8.24

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 = [];
@@ -4184,6 +4572,24 @@ function isRecentDuplicateSend(key) {
4184
4572
  recentSendByTarget.set(key, now);
4185
4573
  return false;
4186
4574
  }
4575
+ function parseMaybeJson(value) {
4576
+ if (typeof value !== "string") return value;
4577
+ try {
4578
+ return JSON.parse(value);
4579
+ } catch {
4580
+ return value;
4581
+ }
4582
+ }
4583
+ function didProviderConfirmSend(result) {
4584
+ const parsed = parseMaybeJson(result);
4585
+ if (parsed === true) return true;
4586
+ if (typeof parsed === "string") {
4587
+ const normalized = parsed.trim().toLowerCase();
4588
+ return normalized === "ok" || normalized === "sent" || normalized === "success" || normalized === "true";
4589
+ }
4590
+ if (!parsed || typeof parsed !== "object") return false;
4591
+ return parsed.sent === true || parsed.success === true || parsed.ok === true || parsed.submitted === true || parsed.dispatched === true;
4592
+ }
4187
4593
  async function handleChatHistory(h, args) {
4188
4594
  const { agentType, offset, limit } = args;
4189
4595
  const historySessionId = getHistorySessionId(h, args);
@@ -4366,14 +4772,8 @@ async function handleSendChat(h, args) {
4366
4772
  try {
4367
4773
  const evalResult = await h.evaluateProviderScript("sendMessage", { MESSAGE: text }, 3e4);
4368
4774
  if (evalResult?.result) {
4369
- let parsed = evalResult.result;
4370
- if (typeof parsed === "string") {
4371
- try {
4372
- parsed = JSON.parse(parsed);
4373
- } catch {
4374
- }
4375
- }
4376
- if (parsed?.sent) {
4775
+ const parsed = parseMaybeJson(evalResult.result);
4776
+ if (didProviderConfirmSend(parsed)) {
4377
4777
  _log(`Extension script sent OK`);
4378
4778
  return _logSendSuccess("extension-script");
4379
4779
  }
@@ -4405,14 +4805,8 @@ async function handleSendChat(h, args) {
4405
4805
  if (sendScript) {
4406
4806
  try {
4407
4807
  const result = await targetCdp.evaluate(sendScript, 3e4);
4408
- let parsed = result;
4409
- if (typeof result === "string") {
4410
- try {
4411
- parsed = JSON.parse(result);
4412
- } catch {
4413
- }
4414
- }
4415
- if (parsed?.sent) {
4808
+ const parsed = parseMaybeJson(result);
4809
+ if (didProviderConfirmSend(parsed)) {
4416
4810
  _log(`sendMessage script OK`);
4417
4811
  return _logSendSuccess("script");
4418
4812
  }
@@ -4457,14 +4851,8 @@ async function handleSendChat(h, args) {
4457
4851
  const matchText = provider.webviewMatchText;
4458
4852
  const matchFn = matchText ? (body) => body.includes(matchText) : void 0;
4459
4853
  const wvResult = await targetCdp.evaluateInWebviewFrame(webviewScript, matchFn);
4460
- let wvParsed = wvResult;
4461
- if (typeof wvResult === "string") {
4462
- try {
4463
- wvParsed = JSON.parse(wvResult);
4464
- } catch {
4465
- }
4466
- }
4467
- if (wvParsed?.sent) {
4854
+ const wvParsed = parseMaybeJson(wvResult);
4855
+ if (didProviderConfirmSend(wvParsed)) {
4468
4856
  _log(`webviewSendMessage OK`);
4469
4857
  return _logSendSuccess("webview-script");
4470
4858
  }
@@ -4486,14 +4874,8 @@ async function handleSendChat(h, args) {
4486
4874
  const matchText = provider.webviewMatchText;
4487
4875
  const matchFn = matchText ? (body) => body.includes(matchText) : void 0;
4488
4876
  const wvResult = await targetCdp.evaluateInWebviewFrame(webviewScript, matchFn);
4489
- let wvParsed = wvResult;
4490
- if (typeof wvResult === "string") {
4491
- try {
4492
- wvParsed = JSON.parse(wvResult);
4493
- } catch {
4494
- }
4495
- }
4496
- if (wvParsed?.sent) {
4877
+ const wvParsed = parseMaybeJson(wvResult);
4878
+ if (didProviderConfirmSend(wvParsed)) {
4497
4879
  _log(`webviewSendMessage OK`);
4498
4880
  return _logSendSuccess("webview-script");
4499
4881
  }
@@ -5394,7 +5776,56 @@ function handleSetProviderSetting(h, args) {
5394
5776
  }
5395
5777
  return { success: false, error: `Failed to set ${providerType}.${key} \u2014 invalid key, value, or not a public setting` };
5396
5778
  }
5397
- async function handleExtensionScript(h, args, scriptName) {
5779
+ function normalizeProviderScriptArgs(args) {
5780
+ const normalizedArgs = { ...args || {} };
5781
+ for (const key of ["mode", "model", "message", "action", "button", "text", "sessionId", "value"]) {
5782
+ if (key in normalizedArgs && !(key.toUpperCase() in normalizedArgs)) {
5783
+ normalizedArgs[key.toUpperCase()] = normalizedArgs[key];
5784
+ }
5785
+ }
5786
+ return normalizedArgs;
5787
+ }
5788
+ function parseScriptResult(result) {
5789
+ if (typeof result === "string") {
5790
+ try {
5791
+ const parsed = JSON.parse(result);
5792
+ if (parsed && typeof parsed === "object" && parsed.success === false) {
5793
+ return { success: false, payload: parsed };
5794
+ }
5795
+ return { success: true, payload: parsed };
5796
+ } catch {
5797
+ return { success: true, payload: { result } };
5798
+ }
5799
+ }
5800
+ if (result && typeof result === "object" && result.success === false) {
5801
+ return { success: false, payload: result };
5802
+ }
5803
+ return { success: true, payload: result };
5804
+ }
5805
+ function getCliScriptCommand(payload) {
5806
+ if (!payload || typeof payload !== "object") return null;
5807
+ if (typeof payload.sendMessage === "string" && payload.sendMessage.trim()) {
5808
+ return { type: "send_message", text: payload.sendMessage.trim() };
5809
+ }
5810
+ const command = payload.command;
5811
+ if (!command || typeof command !== "object") return null;
5812
+ if (command.type !== "send_message") return null;
5813
+ const text = typeof command.text === "string" ? command.text.trim() : typeof command.message === "string" ? command.message.trim() : "";
5814
+ if (!text) return null;
5815
+ return { type: "send_message", text };
5816
+ }
5817
+ function applyProviderPatch(h, args, payload) {
5818
+ if (!payload || typeof payload !== "object") return;
5819
+ const targetSessionId = typeof args?.targetSessionId === "string" ? args.targetSessionId.trim() : "";
5820
+ const targetSession = targetSessionId ? h.ctx.sessionRegistry?.get(targetSessionId) : void 0;
5821
+ const instanceKey = targetSession?.instanceKey || targetSessionId;
5822
+ if (!instanceKey) return;
5823
+ h.ctx.instanceManager?.sendEvent(instanceKey, "provider_state_patch", {
5824
+ ...payload,
5825
+ extensionType: targetSession?.transport === "cdp-webview" ? targetSession.providerType : void 0
5826
+ });
5827
+ }
5828
+ async function executeProviderScript(h, args, scriptName) {
5398
5829
  const { agentType, ideType } = args || {};
5399
5830
  if (!agentType) return { success: false, error: "agentType is required" };
5400
5831
  const loader = h.ctx.providerLoader;
@@ -5407,13 +5838,29 @@ async function handleExtensionScript(h, args, scriptName) {
5407
5838
  if (!provider.scripts?.[actualScriptName]) {
5408
5839
  return { success: false, error: `Script '${actualScriptName}' not available for ${agentType}` };
5409
5840
  }
5410
- const scriptFn = provider.scripts[actualScriptName];
5411
- const normalizedArgs = { ...args };
5412
- for (const key of ["mode", "model", "message", "action", "button", "text", "sessionId"]) {
5413
- if (key in normalizedArgs && !(key.toUpperCase() in normalizedArgs)) {
5414
- normalizedArgs[key.toUpperCase()] = normalizedArgs[key];
5841
+ const normalizedArgs = normalizeProviderScriptArgs(args);
5842
+ if (provider.category === "cli") {
5843
+ const adapter = h.getCliAdapter(args?.targetSessionId || agentType);
5844
+ if (!adapter?.invokeScript) {
5845
+ return { success: false, error: `CLI adapter does not support script '${actualScriptName}'` };
5846
+ }
5847
+ try {
5848
+ const raw = await adapter.invokeScript(actualScriptName, normalizedArgs);
5849
+ const parsed = parseScriptResult(raw);
5850
+ if (!parsed.success) {
5851
+ return { success: false, ...parsed.payload || {} };
5852
+ }
5853
+ const cliCommand = getCliScriptCommand(parsed.payload);
5854
+ if (cliCommand?.type === "send_message" && cliCommand.text) {
5855
+ await adapter.sendMessage(cliCommand.text);
5856
+ }
5857
+ applyProviderPatch(h, args, parsed.payload);
5858
+ return { success: true, ...parsed.payload && typeof parsed.payload === "object" ? parsed.payload : { result: parsed.payload } };
5859
+ } catch (e) {
5860
+ return { success: false, error: `Script execution failed: ${e.message}` };
5415
5861
  }
5416
5862
  }
5863
+ const scriptFn = provider.scripts[actualScriptName];
5417
5864
  const scriptCode = scriptFn(normalizedArgs);
5418
5865
  if (!scriptCode) return { success: false, error: `Script '${actualScriptName}' returned null` };
5419
5866
  const cdpKey = provider.category === "ide" ? h.currentSession?.cdpManagerKey || h.currentManagerKey || agentType : h.currentSession?.cdpManagerKey || h.currentManagerKey || ideType;
@@ -5467,16 +5914,29 @@ async function handleExtensionScript(h, args, scriptName) {
5467
5914
  if (typeof result === "string") {
5468
5915
  try {
5469
5916
  const parsed = JSON.parse(result);
5917
+ applyProviderPatch(h, args, parsed);
5918
+ if (parsed && typeof parsed === "object" && parsed.success === false) {
5919
+ return { success: false, ...parsed };
5920
+ }
5470
5921
  return { success: true, ...parsed };
5471
5922
  } catch {
5472
5923
  return { success: true, result };
5473
5924
  }
5474
5925
  }
5926
+ applyProviderPatch(h, args, result);
5475
5927
  return { success: true, result };
5476
5928
  } catch (e) {
5477
5929
  return { success: false, error: `Script execution failed: ${e.message}` };
5478
5930
  }
5479
5931
  }
5932
+ async function handleExtensionScript(h, args, scriptName) {
5933
+ return executeProviderScript(h, args, scriptName);
5934
+ }
5935
+ async function handleProviderScript(h, args) {
5936
+ const scriptName = typeof args?.scriptName === "string" ? args.scriptName.trim() : "";
5937
+ if (!scriptName) return { success: false, error: "scriptName is required" };
5938
+ return executeProviderScript(h, args, scriptName);
5939
+ }
5480
5940
  function handleGetIdeExtensions(h, args) {
5481
5941
  const { ideType } = args || {};
5482
5942
  const loader = h.ctx.providerLoader;
@@ -6000,6 +6460,8 @@ var init_handler = __esm({
6000
6460
  case "set_ide_extension":
6001
6461
  return handleSetIdeExtension(this, args);
6002
6462
  // ─── Extension Model / Mode Control (stream-commands.ts) ──────────
6463
+ case "invoke_provider_script":
6464
+ return handleProviderScript(this, args);
6003
6465
  case "list_extension_models":
6004
6466
  return handleExtensionScript(this, args, "listModels");
6005
6467
  case "set_extension_model":
@@ -6684,6 +7146,53 @@ function stripTerminalNoise(str) {
6684
7146
  function sanitizeTerminalText(str) {
6685
7147
  return stripTerminalNoise(stripAnsi(str));
6686
7148
  }
7149
+ function splitCliScreenLines(text) {
7150
+ return String(text || "").replace(/\u0007/g, "").replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n").map((line) => line.replace(/\s+$/, ""));
7151
+ }
7152
+ function isPromptLikeCliLine(line) {
7153
+ const trimmed = String(line || "").trim();
7154
+ if (!trimmed) return false;
7155
+ return /^[❯›>]\s*(?:$|\S.*)$/.test(trimmed);
7156
+ }
7157
+ function buildCliScreenSnapshot(text) {
7158
+ const normalizedText = String(text || "").replace(/\r\n/g, "\n").replace(/\r/g, "\n");
7159
+ const rawLines = splitCliScreenLines(normalizedText);
7160
+ const lines = rawLines.map((line, index, arr) => {
7161
+ const trimmed = String(line || "").trim();
7162
+ return {
7163
+ index,
7164
+ fromTop: index,
7165
+ fromBottom: arr.length - index - 1,
7166
+ text: line,
7167
+ trimmed,
7168
+ isEmpty: trimmed.length === 0
7169
+ };
7170
+ });
7171
+ const nonEmptyLines = lines.filter((line) => !line.isEmpty);
7172
+ const firstNonEmptyLine = nonEmptyLines[0] ?? null;
7173
+ const lastNonEmptyLine = nonEmptyLines[nonEmptyLines.length - 1] ?? null;
7174
+ let promptLineIndex = -1;
7175
+ for (let i = lines.length - 1; i >= 0; i -= 1) {
7176
+ if (isPromptLikeCliLine(lines[i].text)) {
7177
+ promptLineIndex = i;
7178
+ break;
7179
+ }
7180
+ }
7181
+ return {
7182
+ text: normalizedText,
7183
+ lineCount: lines.length,
7184
+ lines,
7185
+ nonEmptyLines,
7186
+ firstNonEmptyLineIndex: firstNonEmptyLine?.index ?? -1,
7187
+ lastNonEmptyLineIndex: lastNonEmptyLine?.index ?? -1,
7188
+ firstNonEmptyLine,
7189
+ lastNonEmptyLine,
7190
+ promptLineIndex,
7191
+ promptLine: promptLineIndex >= 0 ? lines[promptLineIndex] : null,
7192
+ linesAbovePrompt: promptLineIndex >= 0 ? lines.slice(0, promptLineIndex) : [...lines],
7193
+ linesBelowPrompt: promptLineIndex >= 0 ? lines.slice(promptLineIndex + 1) : []
7194
+ };
7195
+ }
6687
7196
  function computeTerminalQueryTail(buffer) {
6688
7197
  const prefixes = ["\x1B[6n", "\x1B[?6n"];
6689
7198
  const maxLength = prefixes.reduce((n, value) => Math.max(n, value.length), 0) - 1;
@@ -6925,7 +7434,9 @@ var init_provider_cli_adapter = __esm({
6925
7434
  ready = false;
6926
7435
  startupBuffer = "";
6927
7436
  startupParseGate = false;
7437
+ startupSettleTimer = null;
6928
7438
  spawnAt = 0;
7439
+ startupFirstOutputAt = 0;
6929
7440
  // PTY I/O
6930
7441
  onPtyDataCallback = null;
6931
7442
  pendingOutputParseBuffer = "";
@@ -6966,6 +7477,7 @@ var init_provider_cli_adapter = __esm({
6966
7477
  statusHistory = [];
6967
7478
  // ─── CLI Scripts (script-based parsing) ───
6968
7479
  cliScripts;
7480
+ runtimeSettings = {};
6969
7481
  /** Full accumulated ANSI-stripped PTY output */
6970
7482
  accumulatedBuffer = "";
6971
7483
  /** Full accumulated raw PTY output (with ANSI) */
@@ -6980,14 +7492,15 @@ var init_provider_cli_adapter = __esm({
6980
7492
  traceSessionId = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
6981
7493
  static MAX_TRACE_ENTRIES = 250;
6982
7494
  providerResolutionMeta;
6983
- static IDLE_FINISH_CONFIRM_MS = 900;
7495
+ static IDLE_FINISH_CONFIRM_MS = 2e3;
7496
+ static STATUS_ACTIVITY_HOLD_MS = 2e3;
6984
7497
  static FINISH_RETRY_DELAY_MS = 300;
6985
7498
  static MAX_FINISH_RETRIES = 2;
6986
7499
  syncMessageViews() {
6987
7500
  this.messages = [...this.committedMessages];
6988
7501
  this.structuredMessages = [...this.committedMessages];
6989
7502
  }
6990
- normalizeParsedMessages(parsedMessages) {
7503
+ hydrateParsedMessages(parsedMessages, scope) {
6991
7504
  const referenceMessages = [...this.committedMessages];
6992
7505
  const usedReferenceIndexes = /* @__PURE__ */ new Set();
6993
7506
  const now = Date.now();
@@ -7020,13 +7533,30 @@ var init_provider_cli_adapter = __esm({
7020
7533
  const content = typeof message.content === "string" ? message.content : String(message.content || "");
7021
7534
  const parsedTimestamp = typeof message.timestamp === "number" && Number.isFinite(message.timestamp) ? message.timestamp : void 0;
7022
7535
  const referenceTimestamp = parsedTimestamp ?? findReferenceTimestamp(role, content, index);
7536
+ const fallbackTimestamp = role === "user" ? scope?.startedAt || now : this.lastOutputAt || scope?.startedAt || now;
7537
+ const timestamp = referenceTimestamp ?? fallbackTimestamp;
7023
7538
  return {
7539
+ ...message,
7024
7540
  role,
7025
7541
  content,
7026
- timestamp: referenceTimestamp ?? now
7542
+ timestamp,
7543
+ receivedAt: typeof message.receivedAt === "number" && Number.isFinite(message.receivedAt) ? message.receivedAt : timestamp
7027
7544
  };
7028
7545
  });
7029
7546
  }
7547
+ normalizeParsedMessages(parsedMessages, scope) {
7548
+ return this.hydrateParsedMessages(parsedMessages, scope).map((message) => ({
7549
+ role: message.role,
7550
+ content: message.content,
7551
+ timestamp: message.timestamp,
7552
+ receivedAt: message.receivedAt,
7553
+ kind: message.kind,
7554
+ id: message.id,
7555
+ index: message.index,
7556
+ meta: message.meta,
7557
+ senderName: message.senderName
7558
+ }));
7559
+ }
7030
7560
  sliceFromOffset(text, start) {
7031
7561
  if (!text) return "";
7032
7562
  if (!Number.isFinite(start) || start <= 0) return text;
@@ -7036,14 +7566,20 @@ var init_provider_cli_adapter = __esm({
7036
7566
  buildParseInput(baseMessages, partialResponse, scope) {
7037
7567
  const buffer = scope ? this.sliceFromOffset(this.accumulatedBuffer, scope.bufferStart) || this.accumulatedBuffer : this.accumulatedBuffer;
7038
7568
  const rawBuffer = scope ? this.sliceFromOffset(this.accumulatedRawBuffer, scope.rawBufferStart) || this.accumulatedRawBuffer : this.accumulatedRawBuffer;
7569
+ const screenText = this.terminalScreen.getText();
7570
+ const recentBuffer = buffer.slice(-1e3) || this.recentOutputBuffer;
7039
7571
  return {
7040
7572
  buffer,
7041
7573
  rawBuffer,
7042
- recentBuffer: buffer.slice(-1e3) || this.recentOutputBuffer,
7043
- screenText: this.terminalScreen.getText(),
7574
+ recentBuffer,
7575
+ screenText,
7576
+ screen: buildCliScreenSnapshot(screenText),
7577
+ bufferScreen: buildCliScreenSnapshot(buffer),
7578
+ recentScreen: buildCliScreenSnapshot(recentBuffer),
7044
7579
  messages: [...baseMessages],
7045
7580
  partialResponse,
7046
- promptText: scope?.prompt || ""
7581
+ promptText: scope?.prompt || "",
7582
+ settings: { ...this.runtimeSettings }
7047
7583
  };
7048
7584
  }
7049
7585
  setStatus(status, trigger) {
@@ -7149,6 +7685,9 @@ var init_provider_cli_adapter = __esm({
7149
7685
  const scriptNames = Object.keys(scripts).filter((k) => typeof scripts[k] === "function");
7150
7686
  LOG.info("CLI", `[${this.cliType}] CLI scripts injected: [${scriptNames.join(", ")}]`);
7151
7687
  }
7688
+ updateRuntimeSettings(settings) {
7689
+ this.runtimeSettings = { ...settings };
7690
+ }
7152
7691
  // ─── Lifecycle ─────────────────────────────────
7153
7692
  setServerConn(serverConn) {
7154
7693
  this.serverConn = serverConn;
@@ -7185,7 +7724,7 @@ var init_provider_cli_adapter = __esm({
7185
7724
  let shellArgs;
7186
7725
  const useShellUnix = !isWin && (!!spawnConfig.shell || !path7.isAbsolute(binaryPath) || isScriptBinary(binaryPath) || !looksLikeMachOOrElf(binaryPath));
7187
7726
  const isCmdShim = isWin && /\.(cmd|bat)$/i.test(binaryPath);
7188
- const useShellWin = isCmdShim || !path7.isAbsolute(binaryPath) || isScriptBinary(binaryPath);
7727
+ const useShellWin = !!spawnConfig.shell || isCmdShim || !path7.isAbsolute(binaryPath) || isScriptBinary(binaryPath);
7189
7728
  const useShell = isWin ? useShellWin : useShellUnix;
7190
7729
  if (useShell) {
7191
7730
  if (!spawnConfig.shell && !isWin) {
@@ -7283,6 +7822,11 @@ var init_provider_cli_adapter = __esm({
7283
7822
  this.spawnAt = Date.now();
7284
7823
  this.startupParseGate = true;
7285
7824
  this.startupBuffer = "";
7825
+ this.startupFirstOutputAt = 0;
7826
+ if (this.startupSettleTimer) {
7827
+ clearTimeout(this.startupSettleTimer);
7828
+ this.startupSettleTimer = null;
7829
+ }
7286
7830
  this.terminalScreen.reset(24, 80);
7287
7831
  this.pendingTerminalQueryTail = "";
7288
7832
  this.currentTurnScope = null;
@@ -7296,7 +7840,8 @@ var init_provider_cli_adapter = __esm({
7296
7840
  this.recordTrace("ready", {
7297
7841
  runtimeMeta: this.getRuntimeMetadata()
7298
7842
  });
7299
- this.setStatus("idle", "pty_ready");
7843
+ this.setStatus("starting", "pty_ready");
7844
+ this.scheduleStartupSettleCheck();
7300
7845
  this.onStatusChange?.();
7301
7846
  }
7302
7847
  // ─── Output Handling ────────────────────────────
@@ -7311,6 +7856,9 @@ var init_provider_cli_adapter = __esm({
7311
7856
  this.lastScreenSnapshot = normalizedScreenSnapshot;
7312
7857
  this.lastScreenChangeAt = now;
7313
7858
  }
7859
+ if (this.startupParseGate && !this.startupFirstOutputAt && (cleanData.trim() || normalizedScreenSnapshot.trim())) {
7860
+ this.startupFirstOutputAt = now;
7861
+ }
7314
7862
  if (this.idleFinishCandidate && (rawData.length > 0 || cleanData.length > 0)) {
7315
7863
  this.clearIdleFinishCandidate("new_output");
7316
7864
  }
@@ -7321,6 +7869,9 @@ var init_provider_cli_adapter = __esm({
7321
7869
  cleanPreview: this.summarizeTraceText(cleanData, 300),
7322
7870
  screenText: this.summarizeTraceText(this.terminalScreen.getText(), 1200)
7323
7871
  });
7872
+ if (this.startupParseGate) {
7873
+ this.scheduleStartupSettleCheck();
7874
+ }
7324
7875
  if (this.isWaitingForResponse && cleanData) {
7325
7876
  this.responseBuffer = (this.responseBuffer + cleanData).slice(-8e3);
7326
7877
  }
@@ -7334,27 +7885,51 @@ var init_provider_cli_adapter = __esm({
7334
7885
  this.recentOutputBuffer = (this.recentOutputBuffer + cleanData).slice(-1e3);
7335
7886
  this.accumulatedBuffer = (this.accumulatedBuffer + cleanData).slice(-_ProviderCliAdapter.MAX_ACCUMULATED_BUFFER);
7336
7887
  this.accumulatedRawBuffer = (this.accumulatedRawBuffer + rawData).slice(-_ProviderCliAdapter.MAX_ACCUMULATED_BUFFER);
7337
- if (this.startupParseGate) {
7338
- this.startupBuffer += cleanData;
7339
- const elapsed = Date.now() - this.spawnAt;
7340
- const screenText = this.terminalScreen.getText() || "";
7341
- const startupModal = this.getStartupConfirmationModal(screenText);
7342
- const scriptStatus = startupModal ? "waiting_approval" : this.runDetectStatus(this.startupBuffer);
7343
- const hasInteractivePrompt = this.looksLikeVisibleIdlePrompt(screenText);
7344
- const startupStableMs = this.lastScreenChangeAt ? now - this.lastScreenChangeAt : 0;
7345
- const isReady = (scriptStatus === "idle" || scriptStatus === "waiting_approval") && hasInteractivePrompt && startupStableMs >= 700 || !!startupModal && startupStableMs >= 700 || elapsed > 8e3 || this.startupBuffer.length > 12e3;
7346
- if (isReady) {
7347
- this.startupParseGate = false;
7348
- this.ready = true;
7349
- LOG.info(
7350
- "CLI",
7351
- `[${this.cliType}] Startup ready (${elapsed}ms, scriptStatus=${scriptStatus}, prompt=${hasInteractivePrompt}, stableMs=${startupStableMs}) providerDir=${this.providerResolutionMeta.providerDir || "-"} scriptDir=${this.providerResolutionMeta.scriptDir || "-"} scriptsPath=${this.providerResolutionMeta.scriptsPath || "-"}`
7352
- );
7353
- this.onStatusChange?.();
7354
- }
7355
- }
7888
+ this.resolveStartupState("output");
7356
7889
  this.scheduleSettle();
7357
7890
  }
7891
+ resolveStartupState(trigger) {
7892
+ if (!this.startupParseGate) return;
7893
+ const now = Date.now();
7894
+ const screenText = this.terminalScreen.getText() || "";
7895
+ const normalizedScreen = normalizeScreenSnapshot(screenText);
7896
+ const hasStartupOutput = !!this.startupFirstOutputAt || !!normalizedScreen.trim();
7897
+ if (!hasStartupOutput) return;
7898
+ const stableMs = this.lastScreenChangeAt ? now - this.lastScreenChangeAt : 0;
7899
+ if (stableMs < 2e3) return;
7900
+ const startupModal = this.getStartupConfirmationModal(screenText);
7901
+ this.startupParseGate = false;
7902
+ if (this.startupSettleTimer) {
7903
+ clearTimeout(this.startupSettleTimer);
7904
+ this.startupSettleTimer = null;
7905
+ }
7906
+ this.ready = true;
7907
+ if (startupModal) {
7908
+ this.activeModal = startupModal;
7909
+ this.setStatus("waiting_approval", `startup_ready:${trigger}`);
7910
+ } else {
7911
+ this.setStatus("idle", `startup_ready:${trigger}`);
7912
+ }
7913
+ LOG.info(
7914
+ "CLI",
7915
+ `[${this.cliType}] Startup settled (${trigger}, stableMs=${stableMs}, modal=${!!startupModal}) providerDir=${this.providerResolutionMeta.providerDir || "-"} scriptDir=${this.providerResolutionMeta.scriptDir || "-"} scriptsPath=${this.providerResolutionMeta.scriptsPath || "-"}`
7916
+ );
7917
+ this.onStatusChange?.();
7918
+ }
7919
+ scheduleStartupSettleCheck() {
7920
+ if (!this.startupParseGate) return;
7921
+ if (this.startupSettleTimer) clearTimeout(this.startupSettleTimer);
7922
+ const now = Date.now();
7923
+ const stableMs = this.lastScreenChangeAt ? now - this.lastScreenChangeAt : 0;
7924
+ const delayMs = Math.max(250, 2050 - stableMs);
7925
+ this.startupSettleTimer = setTimeout(() => {
7926
+ this.startupSettleTimer = null;
7927
+ this.resolveStartupState("startup_timer");
7928
+ if (this.startupParseGate && Date.now() - this.spawnAt < 1e4) {
7929
+ this.scheduleStartupSettleCheck();
7930
+ }
7931
+ }, delayMs);
7932
+ }
7358
7933
  scheduleSettle() {
7359
7934
  if (this.settleTimer) clearTimeout(this.settleTimer);
7360
7935
  const settleEpoch = this.responseEpoch;
@@ -7396,6 +7971,43 @@ var init_provider_cli_adapter = __esm({
7396
7971
  if (!text.trim()) return false;
7397
7972
  return /(^|\n)\s*[❯›>]\s*(?:\n|$)/m.test(text) || /⏎\s+send/i.test(text) || /\?\s*for\s*shortcuts/i.test(text) || /Type your message(?:\s+or\s+@path\/to\/file)?/i.test(text) || /workspace\s*\(\/directory\)/i.test(text) || /for\s*shortcuts/i.test(text);
7398
7973
  }
7974
+ findLastMatchingLineIndex(lines, predicate) {
7975
+ for (let index = lines.length - 1; index >= 0; index -= 1) {
7976
+ if (predicate(lines[index])) return index;
7977
+ }
7978
+ return -1;
7979
+ }
7980
+ looksLikeClaudeGeneratingLine(line) {
7981
+ const trimmed = String(line || "").trim();
7982
+ if (!trimmed) return false;
7983
+ if (/esc to (cancel|interrupt|stop)/i.test(trimmed)) return true;
7984
+ if (/^[✻✶✳✢✽⠂⠐⠒⠓⠦⠴⠶⠷⠿]+\s+\S+.*\b(?:thinking|thought for \d+s?)\b/i.test(trimmed)) return true;
7985
+ if (/^[✻✶✳✢✽⠂⠐⠒⠓⠦⠴⠶⠷⠿]+\s+[A-Z][A-Za-z-]{3,}ing\b.*(?:…|\.{3})/u.test(trimmed)) return true;
7986
+ if (/^[⏺•]\s+(?:Reading|Writing|Editing|Searching|Inspecting|Planning|Analyzing|Synthesizing|Drafting|Running|Listing|Scanning|Matching)\b.*(?:…|\.{3})/i.test(trimmed)) {
7987
+ return /ctrl\+o to expand/i.test(trimmed) || /\b\d+\s+(?:file|files|pattern|patterns|director(?:y|ies)|match|matches|result|results)\b/i.test(trimmed);
7988
+ }
7989
+ return false;
7990
+ }
7991
+ detectClaudeGeneratingOverride(screenText, tail) {
7992
+ if (this.cliType !== "claude-cli") return false;
7993
+ const source = sanitizeTerminalText(screenText || tail || "");
7994
+ if (!source.trim()) return false;
7995
+ const allLines = source.split(/\r\n|\n|\r/g).map((line) => line.trim()).filter(Boolean);
7996
+ if (allLines.length === 0) return false;
7997
+ const recentLines = allLines.slice(-12);
7998
+ const promptIndex = this.findLastMatchingLineIndex(recentLines, (line) => /^[❯›>]\s*$/.test(line));
7999
+ const activeRegion = promptIndex >= 0 ? recentLines.slice(Math.max(0, promptIndex - 2), promptIndex) : recentLines;
8000
+ if (activeRegion.length === 0) return false;
8001
+ return activeRegion.some((line) => this.looksLikeClaudeGeneratingLine(line));
8002
+ }
8003
+ refineDetectedStatus(status, tail, screenText) {
8004
+ if (this.startupParseGate) {
8005
+ return this.getStartupConfirmationModal(screenText || "") ? "waiting_approval" : "starting";
8006
+ }
8007
+ if (status === "waiting_approval") return status;
8008
+ if (this.detectClaudeGeneratingOverride(screenText || "", tail)) return "generating";
8009
+ return status;
8010
+ }
7399
8011
  looksLikeVisibleAssistantCandidate(screenText) {
7400
8012
  const lines = sanitizeTerminalText(String(screenText || "")).split(/\r\n|\n|\r/g);
7401
8013
  for (const line of lines) {
@@ -7430,6 +8042,11 @@ var init_provider_cli_adapter = __esm({
7430
8042
  const screenStableMs = this.lastScreenChangeAt ? now - this.lastScreenChangeAt : 0;
7431
8043
  return quietForMs < 1200 || screenStableMs < 1200 || !commitResult.hasAssistant;
7432
8044
  }
8045
+ hasRecentInteractiveActivity(now) {
8046
+ const quietForMs = this.lastNonEmptyOutputAt ? now - this.lastNonEmptyOutputAt : Number.MAX_SAFE_INTEGER;
8047
+ const screenStableMs = this.lastScreenChangeAt ? now - this.lastScreenChangeAt : Number.MAX_SAFE_INTEGER;
8048
+ return quietForMs < _ProviderCliAdapter.STATUS_ACTIVITY_HOLD_MS || screenStableMs < _ProviderCliAdapter.STATUS_ACTIVITY_HOLD_MS;
8049
+ }
7433
8050
  getStartupConfirmationModal(screenText) {
7434
8051
  const text = sanitizeTerminalText(String(screenText || ""));
7435
8052
  if (!text.trim()) return null;
@@ -7457,13 +8074,14 @@ var init_provider_cli_adapter = __esm({
7457
8074
  const startedAt = Date.now();
7458
8075
  let loggedWait = false;
7459
8076
  while (Date.now() - startedAt < maxWaitMs) {
8077
+ this.resolveStartupState("interactive_wait");
7460
8078
  const screenText = this.terminalScreen.getText() || "";
7461
8079
  const hasPrompt = this.looksLikeVisibleIdlePrompt(screenText);
7462
8080
  const stableMs = this.lastScreenChangeAt ? Date.now() - this.lastScreenChangeAt : 0;
7463
8081
  const recentlyOutput = this.lastNonEmptyOutputAt ? Date.now() - this.lastNonEmptyOutputAt : Number.MAX_SAFE_INTEGER;
7464
8082
  const status = this.runDetectStatus(this.recentOutputBuffer) || this.currentStatus;
7465
8083
  const startupLikelyActive = /Welcome back|Tips for getting|Recent activity|Claude Code v\d/i.test(screenText);
7466
- const interactiveReady = hasPrompt && stableMs >= 700 && recentlyOutput >= 350 && status !== "starting" && status !== "generating";
8084
+ const interactiveReady = hasPrompt && stableMs >= 700 && recentlyOutput >= 350 && status !== "generating";
7467
8085
  if (interactiveReady) {
7468
8086
  if (loggedWait) {
7469
8087
  LOG.info(
@@ -7502,6 +8120,10 @@ var init_provider_cli_adapter = __esm({
7502
8120
  }
7503
8121
  const tail = this.settledBuffer;
7504
8122
  const screenText = this.terminalScreen.getText() || "";
8123
+ this.resolveStartupState("settled");
8124
+ if (this.startupParseGate) {
8125
+ return;
8126
+ }
7505
8127
  const startupModal = this.getStartupConfirmationModal(screenText);
7506
8128
  const modal = this.runParseApproval(tail) || startupModal;
7507
8129
  const rawScriptStatus = this.runDetectStatus(tail);
@@ -7564,6 +8186,28 @@ var init_provider_cli_adapter = __esm({
7564
8186
  } else {
7565
8187
  clearPendingScriptStatus();
7566
8188
  }
8189
+ const recentInteractiveActivity = this.hasRecentInteractiveActivity(now);
8190
+ const shouldHoldGenerating = scriptStatus === "idle" && this.isWaitingForResponse && !modal && recentInteractiveActivity;
8191
+ if (shouldHoldGenerating) {
8192
+ this.clearIdleFinishCandidate("hold_generating_recent_activity");
8193
+ this.setStatus("generating", "recent_activity_hold");
8194
+ if (this.idleTimeout) clearTimeout(this.idleTimeout);
8195
+ this.idleTimeout = setTimeout(() => {
8196
+ if (this.isWaitingForResponse && this.currentStatus !== "waiting_approval") {
8197
+ this.finishResponse();
8198
+ }
8199
+ }, this.timeouts.generatingIdle);
8200
+ this.recordTrace("hold_generating_recent_activity", {
8201
+ scriptStatus,
8202
+ recentInteractiveActivity,
8203
+ lastNonEmptyOutputAt: this.lastNonEmptyOutputAt,
8204
+ lastScreenChangeAt: this.lastScreenChangeAt,
8205
+ holdMs: _ProviderCliAdapter.STATUS_ACTIVITY_HOLD_MS,
8206
+ ...this.buildTraceParseSnapshot(this.currentTurnScope, this.responseBuffer)
8207
+ });
8208
+ this.onStatusChange?.();
8209
+ return;
8210
+ }
7567
8211
  if (scriptStatus === "waiting_approval") {
7568
8212
  this.clearIdleFinishCandidate("waiting_approval");
7569
8213
  const inCooldown = this.lastApprovalResolvedAt && Date.now() - this.lastApprovalResolvedAt < this.timeouts.approvalCooldown;
@@ -7641,8 +8285,8 @@ var init_provider_cli_adapter = __esm({
7641
8285
  const screenStableMs = this.lastScreenChangeAt ? now - this.lastScreenChangeAt : 0;
7642
8286
  const hasAssistantTurn = !!lastParsedAssistant;
7643
8287
  const assistantLength = lastParsedAssistant?.content?.length || 0;
7644
- const idleQuietThresholdMs = Math.max(220, this.timeouts.outputSettle);
7645
- const idleStableThresholdMs = Math.max(120, Math.min(220, this.timeouts.outputSettle));
8288
+ const idleQuietThresholdMs = Math.max(2e3, this.timeouts.outputSettle);
8289
+ const idleStableThresholdMs = 2e3;
7646
8290
  const idleReady = visibleIdlePrompt && !modal && hasAssistantTurn && quietForMs >= idleQuietThresholdMs && screenStableMs >= idleStableThresholdMs;
7647
8291
  const candidate = this.idleFinishCandidate;
7648
8292
  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;
@@ -7756,7 +8400,7 @@ var init_provider_cli_adapter = __esm({
7756
8400
  this.currentTurnScope
7757
8401
  );
7758
8402
  if (parsed && Array.isArray(parsed.messages)) {
7759
- this.committedMessages = this.normalizeParsedMessages(parsed.messages);
8403
+ this.committedMessages = this.normalizeParsedMessages(parsed.messages, this.currentTurnScope);
7760
8404
  const promptForTrim = this.currentTurnScope?.prompt || getLastUserPromptText(this.committedMessages);
7761
8405
  if (promptForTrim) {
7762
8406
  const lastAssistantForTrim = [...this.committedMessages].reverse().find((message) => message.role === "assistant");
@@ -7793,11 +8437,15 @@ var init_provider_cli_adapter = __esm({
7793
8437
  runDetectStatus(text) {
7794
8438
  if (!this.cliScripts?.detectStatus) return null;
7795
8439
  try {
7796
- return this.cliScripts.detectStatus({
8440
+ const screenText = this.terminalScreen.getText();
8441
+ const status = this.cliScripts.detectStatus({
7797
8442
  tail: text.slice(-500),
7798
- screenText: this.terminalScreen.getText(),
7799
- rawBuffer: this.accumulatedRawBuffer
8443
+ screenText,
8444
+ rawBuffer: this.accumulatedRawBuffer,
8445
+ screen: buildCliScreenSnapshot(screenText),
8446
+ tailScreen: buildCliScreenSnapshot(text.slice(-500))
7800
8447
  });
8448
+ return this.refineDetectedStatus(status, text, screenText || "");
7801
8449
  } catch (e) {
7802
8450
  LOG.warn("CLI", `[${this.cliType}] detectStatus error: ${e.message}`);
7803
8451
  return null;
@@ -7806,11 +8454,16 @@ var init_provider_cli_adapter = __esm({
7806
8454
  runParseApproval(tail) {
7807
8455
  if (!this.cliScripts?.parseApproval) return null;
7808
8456
  try {
8457
+ const screenText = this.terminalScreen.getText();
8458
+ const buffer = screenText || this.accumulatedBuffer;
7809
8459
  return this.cliScripts.parseApproval({
7810
- buffer: this.terminalScreen.getText() || this.accumulatedBuffer,
7811
- screenText: this.terminalScreen.getText(),
8460
+ buffer,
8461
+ screenText,
7812
8462
  rawBuffer: this.accumulatedRawBuffer,
7813
- tail
8463
+ tail,
8464
+ screen: buildCliScreenSnapshot(screenText),
8465
+ bufferScreen: buildCliScreenSnapshot(buffer),
8466
+ tailScreen: buildCliScreenSnapshot(tail)
7814
8467
  });
7815
8468
  } catch (e) {
7816
8469
  LOG.warn("CLI", `[${this.cliType}] parseApproval error: ${e.message}`);
@@ -7826,6 +8479,21 @@ var init_provider_cli_adapter = __esm({
7826
8479
  activeModal: this.activeModal
7827
8480
  };
7828
8481
  }
8482
+ seedCommittedMessages(messages) {
8483
+ const normalized = (Array.isArray(messages) ? messages : []).filter((message) => message && (message.role === "user" || message.role === "assistant")).map((message) => ({
8484
+ role: message.role,
8485
+ content: typeof message.content === "string" ? message.content : String(message.content || ""),
8486
+ timestamp: typeof message.timestamp === "number" && Number.isFinite(message.timestamp) ? message.timestamp : void 0,
8487
+ receivedAt: typeof message.receivedAt === "number" && Number.isFinite(message.receivedAt) ? message.receivedAt : void 0,
8488
+ kind: typeof message.kind === "string" ? message.kind : void 0,
8489
+ id: typeof message.id === "string" ? message.id : void 0,
8490
+ index: typeof message.index === "number" ? message.index : void 0,
8491
+ meta: message.meta && typeof message.meta === "object" ? { ...message.meta } : void 0,
8492
+ senderName: typeof message.senderName === "string" ? message.senderName : void 0
8493
+ }));
8494
+ this.committedMessages = normalized;
8495
+ this.syncMessageViews();
8496
+ }
7829
8497
  /**
7830
8498
  * Script-based full parse — returns ReadChatResult.
7831
8499
  * Called by command handler / dashboard for rich content rendering.
@@ -7836,12 +8504,20 @@ var init_provider_cli_adapter = __esm({
7836
8504
  this.responseBuffer,
7837
8505
  this.currentTurnScope
7838
8506
  );
8507
+ const shouldPreferCommittedMessages = !this.currentTurnScope && this.currentStatus === "idle" && !this.activeModal;
7839
8508
  if (parsed && Array.isArray(parsed.messages)) {
8509
+ const hydratedMessages = shouldPreferCommittedMessages ? this.committedMessages.map((message, index) => ({
8510
+ ...message,
8511
+ id: message.id || `msg_${index}`,
8512
+ index: typeof message.index === "number" ? message.index : index,
8513
+ kind: message.kind || "standard",
8514
+ receivedAt: typeof message.receivedAt === "number" ? message.receivedAt : message.timestamp
8515
+ })) : this.hydrateParsedMessages(parsed.messages, this.currentTurnScope);
7840
8516
  return {
7841
8517
  id: parsed.id || "cli_session",
7842
8518
  status: parsed.status || this.currentStatus,
7843
8519
  title: parsed.title || this.cliName,
7844
- messages: parsed.messages,
8520
+ messages: hydratedMessages,
7845
8521
  activeModal: parsed.activeModal ?? this.activeModal,
7846
8522
  providerSessionId: typeof parsed.providerSessionId === "string" ? parsed.providerSessionId : void 0
7847
8523
  };
@@ -7862,11 +8538,30 @@ var init_provider_cli_adapter = __esm({
7862
8538
  activeModal: this.activeModal
7863
8539
  };
7864
8540
  }
8541
+ async invokeScript(scriptName, args) {
8542
+ const fn = this.cliScripts?.[scriptName];
8543
+ if (typeof fn !== "function") {
8544
+ throw new Error(`CLI script '${scriptName}' not available`);
8545
+ }
8546
+ const input = this.buildParseInput(
8547
+ this.committedMessages,
8548
+ this.responseBuffer,
8549
+ this.currentTurnScope
8550
+ );
8551
+ return await Promise.resolve(fn({
8552
+ ...input,
8553
+ args: args && typeof args === "object" ? { ...args } : {}
8554
+ }));
8555
+ }
7865
8556
  parseCurrentTranscript(baseMessages, partialResponse, scope) {
7866
8557
  if (!this.cliScripts?.parseOutput) return null;
7867
8558
  try {
7868
8559
  const input = this.buildParseInput(baseMessages, partialResponse, scope);
7869
8560
  const parsed = this.cliScripts.parseOutput(input);
8561
+ const refinedStatus = this.refineDetectedStatus(typeof parsed?.status === "string" ? parsed.status : null, input.recentBuffer, input.screenText);
8562
+ if (parsed && refinedStatus && parsed.status !== refinedStatus) {
8563
+ parsed.status = refinedStatus;
8564
+ }
7870
8565
  const promptForTrim = scope?.prompt || getLastUserPromptText(baseMessages);
7871
8566
  if (parsed && Array.isArray(parsed.messages) && promptForTrim) {
7872
8567
  const lastAssistant = [...parsed.messages].reverse().find((message) => message?.role === "assistant" && typeof message.content === "string");
@@ -7913,12 +8608,23 @@ ${data.message || ""}`.trim();
7913
8608
  if (this.startupParseGate) {
7914
8609
  const deadline = Date.now() + 1e4;
7915
8610
  while (this.startupParseGate && Date.now() < deadline) {
8611
+ this.resolveStartupState("send_wait");
7916
8612
  await new Promise((resolve13) => setTimeout(resolve13, 50));
7917
8613
  }
7918
8614
  }
8615
+ await this.waitForInteractivePrompt();
8616
+ if (!this.ready) {
8617
+ this.resolveStartupState("send_precheck");
8618
+ const screenText = this.terminalScreen.getText() || "";
8619
+ const hasPrompt = this.looksLikeVisibleIdlePrompt(screenText);
8620
+ if (hasPrompt && this.currentStatus === "idle") {
8621
+ this.ready = true;
8622
+ this.startupParseGate = false;
8623
+ LOG.info("CLI", `[${this.cliType}] sendMessage recovered idle prompt readiness`);
8624
+ }
8625
+ }
7919
8626
  if (!this.ready) throw new Error(`${this.cliName} not ready (status: ${this.currentStatus})`);
7920
8627
  if (this.isWaitingForResponse) return;
7921
- await this.waitForInteractivePrompt();
7922
8628
  const blockingModal = this.activeModal || this.getStartupConfirmationModal(this.terminalScreen.getText() || "");
7923
8629
  if (blockingModal || this.currentStatus === "waiting_approval") {
7924
8630
  throw new Error(`${this.cliName} is awaiting confirmation before it can accept a prompt`);
@@ -7962,8 +8668,6 @@ ${data.message || ""}`.trim();
7962
8668
  }
7963
8669
  this.responseEpoch += 1;
7964
8670
  this.responseSettleIgnoreUntil = Date.now() + submitDelayMs + this.timeouts.outputSettle + 250;
7965
- this.setStatus("generating", "sendMessage");
7966
- this.onStatusChange?.();
7967
8671
  const startResponseTimeout = () => {
7968
8672
  if (this.responseTimeout) clearTimeout(this.responseTimeout);
7969
8673
  this.responseTimeout = setTimeout(() => {
@@ -7982,7 +8686,7 @@ ${data.message || ""}`.trim();
7982
8686
  const retrySubmitIfStuck = (attempt) => {
7983
8687
  this.submitRetryTimer = null;
7984
8688
  if (!this.ptyProcess || !this.isWaitingForResponse || this.submitRetryUsed) return;
7985
- if (this.currentStatus !== "generating") return;
8689
+ if (this.currentStatus === "waiting_approval") return;
7986
8690
  if ((this.responseBuffer || "").trim()) return;
7987
8691
  const screenText = this.terminalScreen.getText();
7988
8692
  if (!promptLikelyVisible(screenText, normalizedPromptSnippet)) return;
@@ -8017,7 +8721,7 @@ ${data.message || ""}`.trim();
8017
8721
  this.submitRetryTimer = setTimeout(() => {
8018
8722
  this.submitRetryTimer = null;
8019
8723
  if (!this.ptyProcess || !this.isWaitingForResponse || this.submitRetryUsed) return;
8020
- if (this.currentStatus !== "generating") return;
8724
+ if (this.currentStatus === "waiting_approval") return;
8021
8725
  if ((this.responseBuffer || "").trim()) return;
8022
8726
  const screenText = this.terminalScreen.getText();
8023
8727
  if (!promptLikelyVisible(screenText, normalizedPromptSnippet)) return;
@@ -8449,6 +9153,7 @@ var init_cli_provider_instance = __esm({
8449
9153
  init_status_monitor();
8450
9154
  init_chat_history();
8451
9155
  init_logger();
9156
+ init_control_effects();
8452
9157
  CachedDatabaseSync = null;
8453
9158
  CliProviderInstance = class {
8454
9159
  constructor(provider, workingDir, cliArgs = [], instanceId, transportFactory, options) {
@@ -8477,6 +9182,8 @@ var init_cli_provider_instance = __esm({
8477
9182
  generatingDebounceTimer = null;
8478
9183
  generatingDebouncePending = null;
8479
9184
  lastApprovalEventAt = 0;
9185
+ controlValues = {};
9186
+ appliedEffectKeys = /* @__PURE__ */ new Set();
8480
9187
  historyWriter;
8481
9188
  runtimeMessages = [];
8482
9189
  instanceId;
@@ -8489,6 +9196,7 @@ var init_cli_provider_instance = __esm({
8489
9196
  async init(context) {
8490
9197
  this.context = context;
8491
9198
  this.settings = context.settings || {};
9199
+ this.adapter.updateRuntimeSettings?.(this.settings);
8492
9200
  this.monitor.updateConfig({
8493
9201
  approvalAlert: this.settings.approvalAlert !== false,
8494
9202
  longGeneratingAlert: this.settings.longGeneratingAlert !== false,
@@ -8504,6 +9212,21 @@ var init_cli_provider_instance = __esm({
8504
9212
  this.detectStatusTransition();
8505
9213
  });
8506
9214
  await this.adapter.spawn();
9215
+ if (this.providerSessionId) {
9216
+ const restoredHistory = readChatHistory(this.type, 0, 200, this.providerSessionId);
9217
+ if (restoredHistory.messages.length > 0) {
9218
+ this.adapter.seedCommittedMessages(
9219
+ restoredHistory.messages.map((message) => ({
9220
+ role: message.role,
9221
+ content: message.content,
9222
+ timestamp: message.receivedAt,
9223
+ receivedAt: message.receivedAt,
9224
+ kind: message.kind,
9225
+ senderName: message.senderName
9226
+ }))
9227
+ );
9228
+ }
9229
+ }
8507
9230
  if (this.providerSessionId && this.launchMode === "resume") {
8508
9231
  const resumedAt = Date.now();
8509
9232
  this.historyWriter.appendSystemMarker(
@@ -8584,6 +9307,12 @@ var init_cli_provider_instance = __esm({
8584
9307
  }
8585
9308
  const runtime = this.adapter.getRuntimeMetadata();
8586
9309
  const parsedMessages = Array.isArray(parsedStatus?.messages) ? parsedStatus.messages : [];
9310
+ const controlValues = extractProviderControlValues(this.provider.controls, parsedStatus);
9311
+ if (controlValues) {
9312
+ this.controlValues = controlValues;
9313
+ } else if (Object.keys(this.controlValues).length > 0) {
9314
+ this.controlValues = {};
9315
+ }
8587
9316
  const mergedMessages = this.mergeConversationMessages(parsedMessages);
8588
9317
  const dirName = this.workingDir.split("/").filter(Boolean).pop() || "session";
8589
9318
  if (parsedMessages.length > 0) {
@@ -8604,6 +9333,7 @@ var init_cli_provider_instance = __esm({
8604
9333
  );
8605
9334
  }
8606
9335
  }
9336
+ this.applyProviderResponse(parsedStatus, { phase: "immediate" });
8607
9337
  return {
8608
9338
  type: this.type,
8609
9339
  name: this.provider.name,
@@ -8633,8 +9363,7 @@ var init_cli_provider_instance = __esm({
8633
9363
  attachedClients: runtime.attachedClients || []
8634
9364
  } : void 0,
8635
9365
  resume: this.provider.resume,
8636
- controlValues: void 0,
8637
- // CLI controls not yet wired from stream
9366
+ controlValues: this.controlValues,
8638
9367
  providerControls: this.provider.controls
8639
9368
  };
8640
9369
  }
@@ -8645,6 +9374,15 @@ var init_cli_provider_instance = __esm({
8645
9374
  getPresentationMode() {
8646
9375
  return this.presentationMode;
8647
9376
  }
9377
+ updateSettings(newSettings) {
9378
+ this.settings = { ...newSettings };
9379
+ this.adapter.updateRuntimeSettings?.(this.settings);
9380
+ this.monitor.updateConfig({
9381
+ approvalAlert: this.settings.approvalAlert !== false,
9382
+ longGeneratingAlert: this.settings.longGeneratingAlert !== false,
9383
+ longGeneratingThresholdSec: this.settings.longGeneratingThresholdSec || 180
9384
+ });
9385
+ }
8648
9386
  onEvent(event, data) {
8649
9387
  if (event === "send_message" && data?.text) {
8650
9388
  void this.adapter.sendMessage(data.text).catch((e) => {
@@ -8656,22 +9394,27 @@ var init_cli_provider_instance = __esm({
8656
9394
  void this.adapter.resolveAction(data).catch((e) => {
8657
9395
  LOG.warn("CLI", `[${this.type}] resolve_action failed: ${e?.message || e}`);
8658
9396
  });
9397
+ } else if (event === "provider_state_patch" && data && typeof data === "object") {
9398
+ this.applyProviderResponse(data, { phase: "immediate" });
8659
9399
  }
8660
9400
  }
8661
9401
  dispose() {
8662
9402
  this.adapter.shutdown();
8663
9403
  this.monitor.reset();
9404
+ this.appliedEffectKeys.clear();
8664
9405
  }
8665
9406
  completedDebounceTimer = null;
8666
9407
  completedDebouncePending = null;
8667
9408
  detectStatusTransition() {
8668
9409
  const now = Date.now();
8669
9410
  const adapterStatus = this.adapter.getStatus();
9411
+ const parsedStatus = this.adapter.getScriptParsedStatus?.() || null;
8670
9412
  const newStatus = adapterStatus.status;
8671
9413
  const dirName = this.workingDir.split("/").filter(Boolean).pop() || "session";
8672
9414
  const chatTitle = `${this.provider.name} \xB7 ${dirName}`;
8673
9415
  const partial2 = this.adapter.getPartialResponse();
8674
9416
  const progressFingerprint = newStatus === "generating" ? `${partial2 || ""}::${adapterStatus.messages.at(-1)?.content || ""}`.slice(-2e3) : void 0;
9417
+ const previousStatus = this.lastStatus;
8675
9418
  if (newStatus !== this.lastStatus) {
8676
9419
  LOG.info("CLI", `[${this.type}] status: ${this.lastStatus} \u2192 ${newStatus}`);
8677
9420
  if (this.lastStatus === "idle" && newStatus === "generating") {
@@ -8764,6 +9507,9 @@ var init_cli_provider_instance = __esm({
8764
9507
  }
8765
9508
  this.lastStatus = newStatus;
8766
9509
  }
9510
+ this.applyProviderResponse(parsedStatus, {
9511
+ phase: newStatus === "idle" && (previousStatus === "generating" || previousStatus === "waiting_approval") ? "turn_completed" : "immediate"
9512
+ });
8767
9513
  const agentKey = `${this.type}:cli`;
8768
9514
  const monitorEvents = this.monitor.check(agentKey, newStatus, now, progressFingerprint);
8769
9515
  for (const me of monitorEvents) {
@@ -8779,6 +9525,88 @@ var init_cli_provider_instance = __esm({
8779
9525
  this.events = [];
8780
9526
  return events;
8781
9527
  }
9528
+ applyProviderResponse(data, options) {
9529
+ if (!data || typeof data !== "object") return;
9530
+ const controlValues = extractProviderControlValues(this.provider.controls, data);
9531
+ if (controlValues) {
9532
+ this.controlValues = { ...this.controlValues, ...controlValues };
9533
+ }
9534
+ const effects = normalizeProviderEffects(data);
9535
+ for (const effect of effects) {
9536
+ const effectWhen = effect.when || "immediate";
9537
+ if (effectWhen === "turn_completed" && options.phase !== "turn_completed") continue;
9538
+ if (effectWhen === "immediate" && options.phase === "turn_completed") continue;
9539
+ const effectKey = this.getEffectDedupKey(effect);
9540
+ if (this.appliedEffectKeys.has(effectKey)) continue;
9541
+ this.appliedEffectKeys.add(effectKey);
9542
+ if (effect.persist !== false) {
9543
+ const persisted = this.getPersistedEffectContent(effect);
9544
+ if (persisted) this.appendRuntimeSystemMessage(persisted, effectKey);
9545
+ }
9546
+ if (effect.type === "message" && effect.message) {
9547
+ const content = typeof effect.message.content === "string" ? effect.message.content : JSON.stringify(effect.message.content);
9548
+ this.pushEvent({
9549
+ event: "provider:message",
9550
+ timestamp: Date.now(),
9551
+ content,
9552
+ role: effect.message.role || "system",
9553
+ kind: effect.message.kind,
9554
+ senderName: effect.message.senderName
9555
+ });
9556
+ } else if (effect.type === "toast" && effect.toast) {
9557
+ this.pushEvent({
9558
+ event: "provider:toast",
9559
+ effectId: effect.id || effectKey,
9560
+ timestamp: Date.now(),
9561
+ message: effect.toast.message,
9562
+ level: effect.toast.level || "info"
9563
+ });
9564
+ } else if (effect.type === "notification" && effect.notification) {
9565
+ this.pushEvent({
9566
+ event: "provider:notification",
9567
+ effectId: effect.id || effectKey,
9568
+ timestamp: Date.now(),
9569
+ title: effect.notification.title,
9570
+ message: effect.notification.body,
9571
+ content: typeof effect.notification.bubbleContent === "string" ? effect.notification.bubbleContent : effect.notification.body,
9572
+ level: effect.notification.level || "info",
9573
+ channels: effect.notification.channels || ["toast"],
9574
+ preferenceKey: effect.notification.preferenceKey
9575
+ });
9576
+ }
9577
+ }
9578
+ if (this.appliedEffectKeys.size > 200) {
9579
+ this.appliedEffectKeys = new Set(Array.from(this.appliedEffectKeys).slice(-100));
9580
+ }
9581
+ }
9582
+ getEffectDedupKey(effect) {
9583
+ if (effect.id) return `provider_effect:${effect.id}`;
9584
+ if (effect.type === "message") {
9585
+ const content = typeof effect.message?.content === "string" ? effect.message.content : JSON.stringify(effect.message?.content || "");
9586
+ return `provider_effect:message:${content}`;
9587
+ }
9588
+ if (effect.type === "notification") {
9589
+ return `provider_effect:notification:${effect.notification?.title || ""}:${effect.notification?.body || ""}`;
9590
+ }
9591
+ return `provider_effect:toast:${effect.toast?.message || ""}`;
9592
+ }
9593
+ getPersistedEffectContent(effect) {
9594
+ if (effect.type === "message") {
9595
+ return typeof effect.message?.content === "string" ? effect.message.content : JSON.stringify(effect.message?.content || "");
9596
+ }
9597
+ if (effect.type === "toast") {
9598
+ return effect.toast?.message || null;
9599
+ }
9600
+ if (effect.type === "notification") {
9601
+ if (typeof effect.notification?.bubbleContent === "string") return effect.notification.bubbleContent;
9602
+ if (typeof effect.notification?.title === "string" && effect.notification.title.trim()) {
9603
+ return `${effect.notification.title}
9604
+ ${effect.notification.body || ""}`.trim();
9605
+ }
9606
+ return effect.notification?.body || null;
9607
+ }
9608
+ return null;
9609
+ }
8782
9610
  // ─── Adapter access (backward compat) ──────────────────
8783
9611
  getAdapter() {
8784
9612
  return this.adapter;
@@ -29633,6 +30461,22 @@ function getMacAppIdentifiers() {
29633
30461
  function getWinProcessNames() {
29634
30462
  return getProviderLoader().getWinProcessNames();
29635
30463
  }
30464
+ function getProviderMeta(ideId) {
30465
+ return getProviderLoader().getMeta(ideId);
30466
+ }
30467
+ function getPreferredLaunchMethod(ideId, platform11) {
30468
+ const prefer = getProviderMeta(ideId)?.launch?.prefer;
30469
+ const value = prefer?.[platform11];
30470
+ return value === "cli" || value === "app" || value === "auto" ? value : "auto";
30471
+ }
30472
+ function getCdpStartupTimeoutMs(ideId) {
30473
+ const value = getProviderMeta(ideId)?.launch?.cdpStartupTimeoutMs;
30474
+ if (typeof value !== "number" || !Number.isFinite(value)) return 15e3;
30475
+ return Math.max(1e3, Math.floor(value));
30476
+ }
30477
+ function escapeForAppleScript(value) {
30478
+ return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
30479
+ }
29636
30480
  async function findFreePort(ports) {
29637
30481
  for (const port2 of ports) {
29638
30482
  const free = await checkPortFree(port2);
@@ -29685,12 +30529,12 @@ async function killIdeProcess(ideId) {
29685
30529
  try {
29686
30530
  if (plat === "darwin" && appName) {
29687
30531
  try {
29688
- (0, import_child_process6.execSync)(`osascript -e 'tell application "${appName}" to quit' 2>/dev/null`, {
30532
+ (0, import_child_process6.execSync)(`osascript -e 'tell application "${escapeForAppleScript(appName)}" to quit' 2>/dev/null`, {
29689
30533
  timeout: 5e3
29690
30534
  });
29691
30535
  } catch {
29692
30536
  try {
29693
- (0, import_child_process6.execSync)(`pkill -f "${appName}" 2>/dev/null`);
30537
+ (0, import_child_process6.execSync)(`pkill -x "${appName}" 2>/dev/null`, { timeout: 5e3 });
29694
30538
  } catch {
29695
30539
  }
29696
30540
  }
@@ -29720,7 +30564,7 @@ async function killIdeProcess(ideId) {
29720
30564
  }
29721
30565
  if (plat === "darwin" && appName) {
29722
30566
  try {
29723
- (0, import_child_process6.execSync)(`pkill -9 -f "${appName}" 2>/dev/null`);
30567
+ (0, import_child_process6.execSync)(`pkill -9 -x "${appName}" 2>/dev/null`, { timeout: 5e3 });
29724
30568
  } catch {
29725
30569
  }
29726
30570
  } else if (plat === "win32" && winProcesses) {
@@ -29743,8 +30587,23 @@ function isIdeRunning(ideId) {
29743
30587
  if (plat === "darwin") {
29744
30588
  const appName = getMacAppIdentifiers()[ideId];
29745
30589
  if (!appName) return false;
29746
- const result = (0, import_child_process6.execSync)(`pgrep -f "${appName}" 2>/dev/null`, { encoding: "utf-8" });
29747
- return result.trim().length > 0;
30590
+ try {
30591
+ const result = (0, import_child_process6.execSync)(`pgrep -x "${appName}" 2>/dev/null`, {
30592
+ encoding: "utf-8",
30593
+ timeout: 3e3
30594
+ });
30595
+ return result.trim().length > 0;
30596
+ } catch {
30597
+ const result = (0, import_child_process6.execSync)(
30598
+ `osascript -e 'tell application "System Events" to count (every process whose name is "${escapeForAppleScript(appName)}")'`,
30599
+ {
30600
+ encoding: "utf-8",
30601
+ timeout: 3e3,
30602
+ stdio: ["pipe", "pipe", "pipe"]
30603
+ }
30604
+ );
30605
+ return Number.parseInt(result.trim() || "0", 10) > 0;
30606
+ }
29748
30607
  } else if (plat === "win32") {
29749
30608
  const winProcesses = getWinProcessNames()[ideId];
29750
30609
  if (!winProcesses) return false;
@@ -29893,7 +30752,8 @@ async function launchWithCdp(options = {}) {
29893
30752
  await launchLinux(targetIde, port, workspace, options.newWindow);
29894
30753
  }
29895
30754
  let cdpReady = false;
29896
- for (let i = 0; i < 30; i++) {
30755
+ const waitDeadline = Date.now() + getCdpStartupTimeoutMs(targetIde.id);
30756
+ while (Date.now() < waitDeadline) {
29897
30757
  await new Promise((r) => setTimeout(r, 500));
29898
30758
  if (await isCdpActive(port)) {
29899
30759
  cdpReady = true;
@@ -29922,14 +30782,18 @@ async function launchWithCdp(options = {}) {
29922
30782
  }
29923
30783
  async function launchMacOS(ide, port, workspace, newWindow) {
29924
30784
  const appName = getMacAppIdentifiers()[ide.id];
30785
+ const preferredMethod = getPreferredLaunchMethod(ide.id, "darwin");
29925
30786
  const args = ["--remote-debugging-port=" + port];
29926
30787
  if (newWindow) args.push("--new-window");
29927
30788
  if (workspace) args.push(workspace);
29928
- if (appName) {
30789
+ const canUseCli = !!ide.cliCommand;
30790
+ const canUseAppLauncher = !!appName;
30791
+ const useAppLauncher = preferredMethod === "app" ? canUseAppLauncher : preferredMethod === "cli" ? false : !canUseCli && canUseAppLauncher;
30792
+ if (!useAppLauncher && ide.cliCommand) {
30793
+ (0, import_child_process6.spawn)(ide.cliCommand, args, { detached: true, stdio: "ignore" }).unref();
30794
+ } else if (appName) {
29929
30795
  const openArgs = ["-a", appName, "--args", ...args];
29930
30796
  (0, import_child_process6.spawn)("open", openArgs, { detached: true, stdio: "ignore" }).unref();
29931
- } else if (ide.cliCommand) {
29932
- (0, import_child_process6.spawn)(ide.cliCommand, args, { detached: true, stdio: "ignore" }).unref();
29933
30797
  } else {
29934
30798
  throw new Error(`No app identifier or CLI for ${ide.displayName}`);
29935
30799
  }
@@ -30247,6 +31111,7 @@ function buildStatusSnapshot(options) {
30247
31111
  workspaces: wsState.workspaces,
30248
31112
  defaultWorkspaceId: wsState.defaultWorkspaceId,
30249
31113
  defaultWorkspacePath: wsState.defaultWorkspacePath,
31114
+ terminalSizingMode: cfg.terminalSizingMode || "measured",
30250
31115
  recentLaunches: buildRecentLaunches(recentActivity),
30251
31116
  terminalBackend,
30252
31117
  availableProviders: buildAvailableProviders(options.providerLoader)
@@ -31095,6 +31960,7 @@ var ProviderStreamAdapter;
31095
31960
  var init_provider_adapter = __esm({
31096
31961
  "../../oss/packages/daemon-core/src/agent-stream/provider-adapter.ts"() {
31097
31962
  "use strict";
31963
+ init_control_effects();
31098
31964
  ProviderStreamAdapter = class {
31099
31965
  agentType;
31100
31966
  agentName;
@@ -31154,19 +32020,10 @@ var init_provider_adapter = __esm({
31154
32020
  mode: data.mode,
31155
32021
  activeModal: data.activeModal
31156
32022
  };
31157
- if (this.provider.controls?.length) {
31158
- const cv = {};
31159
- for (const ctrl of this.provider.controls) {
31160
- if (!ctrl.readFrom) continue;
31161
- const val = data[ctrl.readFrom];
31162
- if (val !== void 0 && val !== null) {
31163
- cv[ctrl.id] = typeof val === "object" ? val.name || val.id || String(val) : val;
31164
- }
31165
- }
31166
- if (data.model && !cv["model"]) cv["model"] = data.model;
31167
- if (data.mode && !cv["mode"]) cv["mode"] = data.mode;
31168
- if (Object.keys(cv).length > 0) state.controlValues = cv;
31169
- }
32023
+ const controlValues = extractProviderControlValues(this.provider.controls, data);
32024
+ if (controlValues) state.controlValues = controlValues;
32025
+ const effects = normalizeProviderEffects(data);
32026
+ if (effects.length > 0) state.effects = effects;
31170
32027
  if (state.messages.length > 0) {
31171
32028
  this.lastSuccessState = state;
31172
32029
  }
@@ -31726,6 +32583,8 @@ function forwardAgentStreamsToIdeInstance(instanceManager, ideType, streams) {
31726
32583
  activeModal: stream.activeModal || null,
31727
32584
  model: stream.model || void 0,
31728
32585
  mode: stream.mode || void 0,
32586
+ controlValues: stream.controlValues || void 0,
32587
+ effects: stream.effects || void 0,
31729
32588
  sessionId: stream.sessionId || stream.instanceId || void 0,
31730
32589
  title: stream.title || stream.agentName || void 0,
31731
32590
  agentType: stream.agentType || void 0,
@@ -46409,7 +47268,7 @@ var init_adhdev_daemon = __esm({
46409
47268
  import_ws3 = require("ws");
46410
47269
  import_chalk2 = __toESM(require("chalk"));
46411
47270
  init_version();
46412
- pkgVersion = resolvePackageVersion({ injectedVersion: "0.8.21" });
47271
+ pkgVersion = resolvePackageVersion({ injectedVersion: "0.8.24" });
46413
47272
  DANGEROUS_PATTERNS = [
46414
47273
  /\brm\s+(-[a-z]*f|-[a-z]*r|--force|--recursive)/i,
46415
47274
  /\bsudo\b/i,