conductor-remote 1.117.0 → 1.118.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (200) hide show
  1. package/README.md +10 -8
  2. package/dist/assets/{PierrePatch-D3_YBIAr.js → PierrePatch-D4YbKD8Q.js} +1 -1
  3. package/dist/assets/index-BRzsSW8g.css +1 -0
  4. package/dist/assets/index-BaS6qXUW.js +65 -0
  5. package/dist/index.html +2 -2
  6. package/dist/push-sw.js +2 -2
  7. package/dist/sw.js +1 -1
  8. package/dist-node/scripts/nosleep-setup.js +2 -2
  9. package/dist-node/scripts/nosleep.js +3 -3
  10. package/dist-node/scripts/service/commands.js +32 -0
  11. package/dist-node/scripts/service/configuration.js +205 -0
  12. package/dist-node/scripts/service/environment.js +59 -0
  13. package/dist-node/scripts/service/flags.js +45 -0
  14. package/dist-node/scripts/service/installation.js +91 -0
  15. package/dist-node/scripts/service/launch-agent.js +134 -0
  16. package/dist-node/scripts/service/network.js +244 -0
  17. package/dist-node/scripts/service/presentation.js +101 -0
  18. package/dist-node/scripts/service/voice.js +113 -0
  19. package/dist-node/scripts/service.js +5 -980
  20. package/dist-node/src/{agent-config.js → agents/agent-config.js} +1 -1
  21. package/dist-node/src/{model-cache.js → agents/model-cache.js} +1 -1
  22. package/dist-node/src/{roles.js → agents/roles.js} +3 -3
  23. package/dist-node/src/db.js +1 -1
  24. package/dist-node/src/{parked.js → delivery/parked.js} +1 -1
  25. package/dist-node/src/{dev-server.js → dev-server/controller.js} +8 -291
  26. package/dist-node/src/dev-server/ports.js +63 -0
  27. package/dist-node/src/dev-server/processes.js +73 -0
  28. package/dist-node/src/dev-server/proxy.js +158 -0
  29. package/dist-node/src/{run-activity.js → dev-server/run-activity.js} +2 -2
  30. package/dist-node/src/dev-server/types.js +1 -0
  31. package/dist-node/src/{file-preview.js → files/file-preview.js} +1 -1
  32. package/dist-node/src/{change-stats.js → git/change-stats.js} +1 -1
  33. package/dist-node/src/{git.js → git/diff.js} +1 -1
  34. package/dist-node/src/{autoupdate.js → host/autoupdate.js} +1 -1
  35. package/dist-node/src/{funnel-watchdog.js → host/funnel-watchdog.js} +4 -4
  36. package/dist-node/src/{logbuf.js → host/logbuf.js} +1 -1
  37. package/dist-node/src/{nosleep-helper.js → host/nosleep-helper.js} +1 -1
  38. package/dist-node/src/{tailscale.js → host/tailscale.js} +1 -1
  39. package/dist-node/src/http/router-types.js +2 -0
  40. package/dist-node/src/http/router.js +78 -0
  41. package/dist-node/src/http/routes/create-workspace.js +90 -0
  42. package/dist-node/src/http/routes/files.js +53 -0
  43. package/dist-node/src/http/routes/prompts.js +363 -0
  44. package/dist-node/src/http/routes/sessions.js +279 -0
  45. package/dist-node/src/http/routes/state.js +186 -0
  46. package/dist-node/src/http/routes/system.js +220 -0
  47. package/dist-node/src/http/routes/voice.js +117 -0
  48. package/dist-node/src/http/routes/workflows.js +248 -0
  49. package/dist-node/src/http/routes/workspaces.js +267 -0
  50. package/dist-node/src/http/services/base.js +252 -0
  51. package/dist-node/src/http/services/delegations.js +301 -0
  52. package/dist-node/src/http/services/delivery.js +433 -0
  53. package/dist-node/src/http/services/files.js +199 -0
  54. package/dist-node/src/http/services/mcp.js +94 -0
  55. package/dist-node/src/http/services/responses.js +120 -0
  56. package/dist-node/src/http/services/voice.js +147 -0
  57. package/dist-node/src/http/services/workflow-probes.js +215 -0
  58. package/dist-node/src/http/services/workflow-state.js +195 -0
  59. package/dist-node/src/http/services/workflow.js +366 -0
  60. package/dist-node/src/http/services.js +35 -0
  61. package/dist-node/src/mcp/arguments.js +14 -0
  62. package/dist-node/src/mcp/dispatcher.js +56 -0
  63. package/dist-node/src/mcp/formatters.js +110 -0
  64. package/dist-node/src/mcp/protocol.js +7 -0
  65. package/dist-node/src/mcp/registry.js +66 -0
  66. package/dist-node/src/mcp/tools/agent-options.js +122 -0
  67. package/dist-node/src/mcp/tools/chat-actions.js +194 -0
  68. package/dist-node/src/mcp/tools/chats.js +194 -0
  69. package/dist-node/src/mcp/tools/delegation.js +135 -0
  70. package/dist-node/src/mcp/tools/system.js +135 -0
  71. package/dist-node/src/mcp/tools/voice.js +136 -0
  72. package/dist-node/src/mcp/tools/workspaces.js +259 -0
  73. package/dist-node/src/mcp/types.js +1 -0
  74. package/dist-node/src/mcp.js +6 -4
  75. package/dist-node/src/{notify.js → notifications/notify.js} +5 -5
  76. package/dist-node/src/{webpush.js → notifications/webpush.js} +1 -1
  77. package/dist-node/src/orchestration/delegation/codec.js +201 -0
  78. package/dist-node/src/{delegation-intake.js → orchestration/delegation/intake.js} +2 -2
  79. package/dist-node/src/{delegation-prompt.js → orchestration/delegation/prompt.js} +1 -1
  80. package/dist-node/src/orchestration/delegation/queue.js +255 -0
  81. package/dist-node/src/orchestration/delegation/store.js +111 -0
  82. package/dist-node/src/orchestration/delegation/types.js +2 -0
  83. package/dist-node/src/orchestration/persistence/capabilities.js +109 -0
  84. package/dist-node/src/orchestration/persistence/codecs.js +148 -0
  85. package/dist-node/src/orchestration/persistence/connection.js +205 -0
  86. package/dist-node/src/orchestration/persistence/db.js +101 -0
  87. package/dist-node/src/orchestration/persistence/effect-recovery.js +227 -0
  88. package/dist-node/src/orchestration/persistence/effect-transitions.js +157 -0
  89. package/dist-node/src/orchestration/persistence/effects.js +109 -0
  90. package/dist-node/src/orchestration/persistence/errors.js +39 -0
  91. package/dist-node/src/orchestration/persistence/events.js +29 -0
  92. package/dist-node/src/orchestration/persistence/idempotency.js +82 -0
  93. package/dist-node/src/orchestration/persistence/job-attempts.js +114 -0
  94. package/dist-node/src/orchestration/persistence/jobs.js +159 -0
  95. package/dist-node/src/orchestration/persistence/projections.js +190 -0
  96. package/dist-node/src/orchestration/persistence/quarantine.js +46 -0
  97. package/dist-node/src/orchestration/persistence/records.js +115 -0
  98. package/dist-node/src/orchestration/persistence/relays.js +78 -0
  99. package/dist-node/src/orchestration/persistence/runs.js +156 -0
  100. package/dist-node/src/{orchestration-schema.js → orchestration/persistence/schema.js} +1 -1
  101. package/dist-node/src/orchestration/persistence/types.js +1 -0
  102. package/dist-node/src/orchestration/persistence/ui-lease.js +197 -0
  103. package/dist-node/src/orchestration/persistence/values.js +114 -0
  104. package/dist-node/src/orchestration/workflow/commands.js +309 -0
  105. package/dist-node/src/orchestration/workflow/coordinator.js +28 -0
  106. package/dist-node/src/orchestration/workflow/effect-recovery.js +125 -0
  107. package/dist-node/src/orchestration/workflow/effects.js +307 -0
  108. package/dist-node/src/orchestration/workflow/errors.js +13 -0
  109. package/dist-node/src/orchestration/workflow/helpers.js +154 -0
  110. package/dist-node/src/orchestration/workflow/job-dispatch.js +312 -0
  111. package/dist-node/src/orchestration/workflow/job-results.js +280 -0
  112. package/dist-node/src/{workflow.js → orchestration/workflow/prompts.js} +2 -2
  113. package/dist-node/src/orchestration/workflow/root.js +293 -0
  114. package/dist-node/src/orchestration/workflow/start.js +99 -0
  115. package/dist-node/src/orchestration/workflow/state.js +105 -0
  116. package/dist-node/src/orchestration/workflow/types.js +1 -0
  117. package/dist-node/src/orchestration/workflow/wake.js +164 -0
  118. package/dist-node/src/prefs.js +1 -1
  119. package/dist-node/src/{background-tasks.js → reads/background-tasks.js} +1 -1
  120. package/dist-node/src/reads/messages.js +362 -0
  121. package/dist-node/src/reads/repository.js +87 -0
  122. package/dist-node/src/reads/sessions.js +165 -0
  123. package/dist-node/src/reads/types.js +1 -0
  124. package/dist-node/src/reads/workspaces.js +230 -0
  125. package/dist-node/src/reads/worktrees.js +45 -0
  126. package/dist-node/src/routes.js +5 -5
  127. package/dist-node/src/{search.js → search/coordinator.js} +4 -4
  128. package/dist-node/src/{search-worker.js → search/worker.js} +3 -3
  129. package/dist-node/src/server.js +14 -3930
  130. package/dist-node/src/settings.js +1 -1
  131. package/dist-node/src/shared.js +10 -10
  132. package/dist-node/src/{transcript.js → transcript/parser.js} +4 -4
  133. package/dist-node/src/{tool-usage-worker.js → usage/tool-usage-worker.js} +1 -1
  134. package/dist-node/src/{tool-usage.js → usage/tool-usage.js} +1 -1
  135. package/dist-node/src/voice/brief.js +22 -50
  136. package/dist-node/src/voice/context.js +1 -1
  137. package/dist-node/src/voice/dates.js +38 -0
  138. package/dist-node/src/voice/history.js +14 -5
  139. package/dist-node/src/voice/prompt.js +10 -9
  140. package/dist-node/src/voice/recall.js +115 -0
  141. package/dist-node/src/{speech.js → voice/speech.js} +1 -1
  142. package/dist-node/src/voice/tools.js +92 -3
  143. package/dist-node/src/voice/webrtc.js +1 -1
  144. package/dist-node/src/wire.js +2 -2
  145. package/dist-node/src/writes/actuator.js +124 -0
  146. package/dist-node/src/writes/agent-options.js +151 -0
  147. package/dist-node/src/writes/applescript/agent-options.applescript +334 -0
  148. package/dist-node/src/writes/applescript/chat-targeting.applescript +149 -0
  149. package/dist-node/src/writes/applescript/composer.applescript +195 -0
  150. package/dist-node/src/writes/applescript/hotspot.applescript +155 -0
  151. package/dist-node/src/writes/applescript/lifecycle.applescript +266 -0
  152. package/dist-node/src/writes/applescript/run-tasks.applescript +208 -0
  153. package/dist-node/src/writes/applescript/source.js +52 -0
  154. package/dist-node/src/writes/applescript/window.applescript +436 -0
  155. package/dist-node/src/writes/applescript/workspace-status.applescript +177 -0
  156. package/dist-node/src/writes/applescript/workspace-targeting.applescript +359 -0
  157. package/dist-node/src/writes/chats.js +160 -0
  158. package/dist-node/src/writes/guards.js +77 -0
  159. package/dist-node/src/writes/runner.js +67 -0
  160. package/dist-node/src/{sidecar.js → writes/sidecar.js} +1 -1
  161. package/dist-node/src/writes/system.js +81 -0
  162. package/dist-node/src/writes/targeting.js +121 -0
  163. package/dist-node/src/writes/types.js +1 -0
  164. package/dist-node/src/writes/ui-lock.js +229 -0
  165. package/dist-node/src/writes/workspaces.js +233 -0
  166. package/docs/voice-setup.md +34 -12
  167. package/package.json +2 -2
  168. package/dist/assets/index-ByTwVInL.css +0 -1
  169. package/dist/assets/index-LAm9X7wv.js +0 -65
  170. package/dist-node/src/conductor.applescript +0 -2279
  171. package/dist-node/src/delegations.js +0 -557
  172. package/dist-node/src/mcp-tools.js +0 -1319
  173. package/dist-node/src/orchestration-db.js +0 -2186
  174. package/dist-node/src/reads.js +0 -787
  175. package/dist-node/src/workflow-coordinator.js +0 -2119
  176. package/dist-node/src/writes.js +0 -1223
  177. /package/dist-node/src/{conductor-settings.js → agents/conductor-settings.js} +0 -0
  178. /package/dist-node/src/{firstprompt.js → delivery/firstprompt.js} +0 -0
  179. /package/dist-node/src/{sendonce.js → delivery/sendonce.js} +0 -0
  180. /package/dist-node/src/{preview-urls.js → dev-server/preview-urls.js} +0 -0
  181. /package/dist-node/src/{run-configs.js → dev-server/run-configs.js} +0 -0
  182. /package/dist-node/src/{attachments.js → files/attachments.js} +0 -0
  183. /package/dist-node/src/{icons.js → files/icons.js} +0 -0
  184. /package/dist-node/src/{staged-attachments.js → files/staged-attachments.js} +0 -0
  185. /package/dist-node/src/{fork-workspace.js → git/fork-workspace.js} +0 -0
  186. /package/dist-node/src/{merge.js → git/merge.js} +0 -0
  187. /package/dist-node/src/{pr.js → git/pr.js} +0 -0
  188. /package/dist-node/src/{nosleep.js → host/nosleep.js} +0 -0
  189. /package/dist-node/src/{relay-processes.js → host/relay-processes.js} +0 -0
  190. /package/dist-node/src/{ui-lease-watchdog.js → host/ui-lease-watchdog.js} +0 -0
  191. /package/dist-node/src/{wifi.js → host/wifi.js} +0 -0
  192. /package/dist-node/src/{orchestration-schema.generated.js → orchestration/persistence/schema.generated.js} +0 -0
  193. /package/dist-node/src/{workflow-effect-runner.js → orchestration/workflow/effect-runner.js} +0 -0
  194. /package/dist-node/src/{workflow-http.js → orchestration/workflow/http.js} +0 -0
  195. /package/dist-node/src/{workflow-machine.js → orchestration/workflow/machine.js} +0 -0
  196. /package/dist-node/src/{session-poller.js → reads/session-poller.js} +0 -0
  197. /package/dist-node/src/{context-breakdown.js → transcript/context-breakdown.js} +0 -0
  198. /package/dist-node/src/{chat-cursor.js → transcript/cursor.js} +0 -0
  199. /package/dist-node/src/{plan-usage.js → usage/plan-usage.js} +0 -0
  200. /package/dist-node/src/{tool-usage-service.js → usage/tool-usage-service.js} +0 -0
@@ -0,0 +1,266 @@
1
+
2
+ on cancelAgent()
3
+ -- Stop the answer this chat is streaming: Conductor's own "Cancel agent"
4
+ -- command, Command-Shift-Delete (key code 51). Taken from its Keyboard
5
+ -- shortcuts dialog (Command-slash, section "Chat"), not guessed - and it is
6
+ -- the only lever there is. The menu bar carries no stop command and neither
7
+ -- does the palette, and the composer's stop BUTTON is unnamed to
8
+ -- Accessibility: no title, no description, no help, no identifier, telling
9
+ -- itself apart from the send button beside it only by its Tailwind classes.
10
+ -- Which is a distinction worth nothing at the moment it matters, because
11
+ -- while a chat is working with a draft in the composer BOTH buttons are on
12
+ -- screen - stop on the left, send on the right - so "press the last button"
13
+ -- steers the running agent with whatever was half-typed instead of stopping
14
+ -- it. A keystroke can't confuse the two.
15
+ --
16
+ -- The keystroke lands wherever focus is, so this asserts the pane first, the
17
+ -- same check a send makes before typing. Cancelling the wrong agent destroys
18
+ -- work that was running fine, which is the same class of damage as landing a
19
+ -- prompt in the wrong chat and gets the same answer: error out, never guess.
20
+ set strips to my tabGroups()
21
+ if (count of strips) is 0 then error "couldn't find the chat pane to cancel in"
22
+ my assertWorkspace(item 1 of strips)
23
+ -- Command-L ("Focus chat input") first, so the shortcut is delivered inside the
24
+ -- chat view rather than to a terminal panel that had focus, where the terminal
25
+ -- would swallow it. It also parks focus somewhere harmless: the composer keeps
26
+ -- its draft, since Conductor binds this chord to Cancel agent rather than to any
27
+ -- text-deletion the field would otherwise do with it.
28
+ tell application "System Events"
29
+ keystroke "l" using {command down}
30
+ delay 0.2
31
+ key code 51 using {command down, shift down}
32
+ end tell
33
+ delay 0.3
34
+ end cancelAgent
35
+
36
+ on closeChatTab()
37
+ -- Hide the selected chat through Conductor's own Close tab shortcut, Command-W.
38
+ -- The same chord closes a terminal tab when the terminal has focus, so Command-L
39
+ -- first is load-bearing: it moves focus into the chat whose workspace and tab were
40
+ -- asserted immediately above this handler.
41
+ set strips to my tabGroups()
42
+ if (count of strips) is 0 then error "couldn't find the chat pane to close from"
43
+ my assertWorkspace(item 1 of strips)
44
+ tell application "System Events"
45
+ keystroke "l" using {command down}
46
+ delay 0.2
47
+ keystroke "w" using {command down}
48
+ end tell
49
+
50
+ -- Conductor asks "Close anyway" when the chat's agent is still running. The
51
+ -- server normally catches that from sessions.status before touching the UI, but
52
+ -- a turn can start in the gap between that read and this keystroke. Detect the
53
+ -- live dialog too, and never infer permission to hide a running conversation.
54
+ set confirmEl to missing value
55
+ set checks to 1
56
+ if (system attribute "RELAY_CLOSE_RUNNING") is "1" then set checks to 4
57
+ repeat with attempt from 1 to checks
58
+ delay 0.25
59
+ set confirmEl to my closeConfirmButton()
60
+ if confirmEl is not missing value then exit repeat
61
+ end repeat
62
+ if confirmEl is missing value then return "closed"
63
+ if (system attribute "RELAY_CLOSE_RUNNING") is not "1" then
64
+ tell application "System Events" to key code 53
65
+ error "the agent started working before the tab closed - closing it now needs confirmation"
66
+ end if
67
+ tell application "System Events" to tell process "Conductor"
68
+ perform action "AXPress" of confirmEl
69
+ end tell
70
+ delay 0.5
71
+ return "closed"
72
+ end closeChatTab
73
+
74
+ on buttonsUnder(root, maxDepth)
75
+ -- Every AXButton at or below root, level-order and bounded. Depth is the whole
76
+ -- cost here: the transcript hangs off the same web area as anything Conductor
77
+ -- portals into it, so each extra level multiplies the round trips and the sweep
78
+ -- gets slower the longer the chat is. That is why the callers below pass the
79
+ -- shallow depth that fits the thing they are looking for rather than one big cap.
80
+ set acc to {}
81
+ set level to {root}
82
+ set depth to 0
83
+ tell application "System Events" to tell process "Conductor"
84
+ repeat while (count of level) > 0 and depth < maxDepth
85
+ set nextLevel to {}
86
+ repeat with entry in level
87
+ set node to contents of entry
88
+ try
89
+ set acc to acc & (get UI elements of node whose role is "AXButton")
90
+ set nextLevel to nextLevel & (get UI elements of node)
91
+ end try
92
+ end repeat
93
+ set level to nextLevel
94
+ set depth to depth + 1
95
+ end repeat
96
+ end tell
97
+ return acc
98
+ end buttonsUnder
99
+
100
+ on shallowButtonsNamed(root, maxDepth, wanted)
101
+ -- Exact-name buttons at the first depth that contains one. Continue lives in
102
+ -- Conductor's workspace action bar, while a chat transcript hangs much deeper
103
+ -- off the same web area. Stopping at the shallowest hit both avoids walking a
104
+ -- long transcript and keeps a similarly named control inside a message from
105
+ -- impersonating the workspace action.
106
+ set level to {root}
107
+ set depth to 0
108
+ tell application "System Events" to tell process "Conductor"
109
+ repeat while (count of level) > 0 and depth < maxDepth
110
+ set found to {}
111
+ set nextLevel to {}
112
+ repeat with entry in level
113
+ set node to contents of entry
114
+ try
115
+ repeat with candidate in (get UI elements of node whose role is "AXButton")
116
+ set controlEl to contents of candidate
117
+ if (my axName(controlEl)) is wanted then set end of found to controlEl
118
+ end repeat
119
+ set nextLevel to nextLevel & (get UI elements of node)
120
+ end try
121
+ end repeat
122
+ if (count of found) > 0 then return found
123
+ set level to nextLevel
124
+ set depth to depth + 1
125
+ end repeat
126
+ end tell
127
+ return {}
128
+ end shallowButtonsNamed
129
+
130
+ on hasSiblingButtonNamed(controlEl, wanted)
131
+ -- The live merged action is the pair Continue + Archive. Requiring that pair
132
+ -- prevents a generic Continue in a modal from impersonating it while the
133
+ -- asserted workspace pane remains visible behind the modal.
134
+ try
135
+ tell application "System Events" to tell process "Conductor"
136
+ set actionRow to value of attribute "AXParent" of controlEl
137
+ repeat with peer in (get UI elements of actionRow whose role is "AXButton")
138
+ if (my axName(contents of peer)) is wanted then return true
139
+ end repeat
140
+ end tell
141
+ end try
142
+ return false
143
+ end hasSiblingButtonNamed
144
+
145
+ on continueWorkspaceOnNewBranch()
146
+ -- Press Conductor's own merged-workspace action. Its handler owns all of the
147
+ -- state that cannot be reproduced safely from the relay: it checks out a fresh
148
+ -- branch at the PR's target, updates the workspace row, keeps every chat, and
149
+ -- stages Branch continued.md in the selected chat. The relay never writes the
150
+ -- database; server.ts watches `workspaces.branch` for the receipt.
151
+ set strips to my tabGroups()
152
+ if (count of strips) is 0 then error "couldn't find the chat pane to continue from"
153
+ my assertWorkspace(item 1 of strips)
154
+ set found to my shallowButtonsNamed(my webArea(), 10, "Continue")
155
+ if (count of found) is 0 then error "Conductor isn't offering Continue for this workspace - it appears after its pull request is merged and refreshed"
156
+ if (count of found) > 1 then error "more than one Continue button is visible - close any dialog and try again"
157
+ set continueEl to item 1 of found
158
+ if not (my hasSiblingButtonNamed(continueEl, "Archive")) then error "couldn't verify Conductor's merged-workspace Continue action - close any dialog and try again"
159
+ set canPress to true
160
+ try
161
+ tell application "System Events" to tell process "Conductor"
162
+ set canPress to (value of attribute "AXEnabled" of continueEl) as boolean
163
+ end tell
164
+ end try
165
+ if not canPress then error "Conductor's Continue button is still busy - try again shortly"
166
+ tell application "System Events" to tell process "Conductor"
167
+ perform action "AXPress" of continueEl
168
+ end tell
169
+ delay 0.4
170
+ end continueWorkspaceOnNewBranch
171
+
172
+ on dialogButtons()
173
+ -- Both places Conductor could be drawing a confirmation, each at the depth that
174
+ -- shape actually sits at. A real sheet hangs off the window a level or two down;
175
+ -- an in-webview dialog is a shallow portal near the top of the web area. Its
176
+ -- buttons are inside depth 3; going to 4 reaches the transcript and made an idle
177
+ -- close spend its whole 28-second budget proving that no dialog existed.
178
+ my requireWindow()
179
+ set fromWindow to {}
180
+ tell application "System Events" to tell process "Conductor"
181
+ set fromWindow to my buttonsUnder(window 1, 4)
182
+ end tell
183
+ return fromWindow & (my buttonsUnder(my webArea(), 3))
184
+ end dialogButtons
185
+
186
+ on closeConfirmButton()
187
+ -- The running-chat dialog has exactly Cancel + "Close anyway". Require the
188
+ -- pair so an unrelated destructive dialog can never donate a similarly named
189
+ -- button to this run.
190
+ set found to missing value
191
+ set sawCancel to false
192
+ repeat with entry in my dialogButtons()
193
+ set node to contents of entry
194
+ set label to my axName(node)
195
+ if label is "Cancel" then set sawCancel to true
196
+ if label starts with "Close anyway" then set found to node
197
+ end repeat
198
+ if sawCancel then return found
199
+ return missing value
200
+ end closeConfirmButton
201
+
202
+ on archiveConfirmButton()
203
+ -- Conductor asks before archiving a workspace whose agents are still running:
204
+ -- "Archive workspace?" over Cancel and "Stop agents and archive". Identified by
205
+ -- the *pair* - a button naming the verb, with a Cancel somewhere beside it -
206
+ -- rather than by the archive one alone, which is the same rule waitForMenuWith
207
+ -- follows: press nothing that has not proved which thing it belongs to.
208
+ -- "Unarchive" is excluded by name, since AppleScript's contains ignores case
209
+ -- and would otherwise read that as a confirmation of this.
210
+ set found to missing value
211
+ set sawCancel to false
212
+ repeat with entry in my dialogButtons()
213
+ set node to contents of entry
214
+ set label to my axName(node)
215
+ if label is "Cancel" then set sawCancel to true
216
+ if label contains "archive" and label does not contain "unarchive" then set found to node
217
+ end repeat
218
+ if sawCancel then return found
219
+ return missing value
220
+ end archiveConfirmButton
221
+
222
+ on archiveWorkspace()
223
+ -- Put this workspace away: Conductor's own "Archive workspace", Command-Shift-A.
224
+ -- A keystroke on purpose, like cancelAgent - the sidebar row menu carries an
225
+ -- Archive item, but reaching it costs a sidebar scan plus two menu waits, and
226
+ -- this chord acts on the workspace the pane is already showing.
227
+ --
228
+ -- Which is also the danger: it archives whatever is on screen, so the pane
229
+ -- assertion is the whole guard. Archiving the wrong workspace deletes a worktree
230
+ -- and kills a turn that was running fine, so a pane that cannot be checked is
231
+ -- refused rather than aimed at (writes.ts requires the branch for that reason).
232
+ --
233
+ -- Command-L ("Focus chat input") first, exactly as cancelAgent does: the chord
234
+ -- lands wherever focus is, and a focused terminal panel swallows it.
235
+ set strips to my tabGroups()
236
+ if (count of strips) is 0 then error "couldn't find the chat pane to archive from"
237
+ my assertWorkspace(item 1 of strips)
238
+ tell application "System Events"
239
+ keystroke "l" using {command down}
240
+ delay 0.2
241
+ keystroke "a" using {command down, shift down}
242
+ end tell
243
+ -- A workspace with agents still working draws a confirmation; an idle one is
244
+ -- archived by the chord alone. Both are live, so this waits a bounded moment for
245
+ -- the dialog and treats its absence as the second case - the DB is the receipt
246
+ -- either way (server.ts watches for state 'archived'), so nothing here has to
247
+ -- decide whether the archive landed.
248
+ set confirmEl to missing value
249
+ repeat with attempt from 1 to 5
250
+ delay 0.3
251
+ set confirmEl to my archiveConfirmButton()
252
+ if confirmEl is not missing value then exit repeat
253
+ end repeat
254
+ if confirmEl is missing value then return "archived"
255
+ -- The caller has to have opted into the damage before we press this: stopping
256
+ -- other people's agents is not something to infer from a tap that said Archive.
257
+ if (system attribute "RELAY_ARCHIVE_AGENTS") is not "1" then
258
+ tell application "System Events" to key code 53
259
+ error "agents are still running in this workspace - archiving would stop them"
260
+ end if
261
+ tell application "System Events" to tell process "Conductor"
262
+ perform action "AXPress" of confirmEl
263
+ end tell
264
+ delay 0.5
265
+ return "archived"
266
+ end archiveWorkspace
@@ -0,0 +1,208 @@
1
+
2
+ -- The workspace toolbar's run process. This is deliberately separate from the
3
+ -- chat-strip targeting above: the terminal strip is the sibling AXTabGroup whose
4
+ -- direct radio buttons include Setup / Run / Terminal, and its task button is
5
+ -- named "Run <task>" or "Stop <task>". Conductor may add more terminal tabs, so
6
+ -- identify the strip by the Run radio rather than by its position.
7
+ on runStrip()
8
+ set matches to {}
9
+ repeat with entry in my tabGroups()
10
+ set tg to contents of entry
11
+ set hasRunTab to false
12
+ repeat with r in my stripRadios(tg)
13
+ if (my axName(contents of r)) is "Run" then set hasRunTab to true
14
+ end repeat
15
+ if hasRunTab then set end of matches to tg
16
+ end repeat
17
+ if (count of matches) is 0 then error "couldn't find Conductor's Run panel"
18
+ if (count of matches) > 1 then error "can't tell which panel holds Conductor's Run task"
19
+ return item 1 of matches
20
+ end runStrip
21
+
22
+ on runTaskButton(tg)
23
+ set matches to {}
24
+ tell application "System Events" to tell process "Conductor"
25
+ repeat with entry in (get UI elements of tg whose role is "AXButton")
26
+ set node to contents of entry
27
+ set label to my axName(node)
28
+ if label starts with "Run " or label starts with "Stop " then set end of matches to node
29
+ end repeat
30
+ end tell
31
+ if (count of matches) is 0 then error "this workspace has no selected Run task"
32
+ if (count of matches) > 1 then error "Conductor exposed more than one selected Run task"
33
+ return item 1 of matches
34
+ end runTaskButton
35
+
36
+ on runTaskChooser(tg)
37
+ set found to {}
38
+ tell application "System Events" to tell process "Conductor"
39
+ repeat with entry in (get UI elements of tg whose role is "AXPopUpButton")
40
+ set node to contents of entry
41
+ if (my axName(node)) is "Select task" then set end of found to node
42
+ end repeat
43
+ end tell
44
+ if (count of found) is 0 then return missing value
45
+ if (count of found) > 1 then error "Conductor exposed more than one Run task chooser"
46
+ return item 1 of found
47
+ end runTaskChooser
48
+
49
+ on openRunTaskMenu(tg)
50
+ -- Multiple Run configs live behind a Radix pop-up. AXPress is advertised but
51
+ -- does nothing on this control; focusing it and pressing Space opens the real
52
+ -- AXMenu. Choosing an item starts the task immediately — there is no second
53
+ -- press on the primary Run button. "Configure" is Conductor's own trailing
54
+ -- command, not a task choice.
55
+ set chooser to my runTaskChooser(tg)
56
+ if chooser is missing value then error "this workspace has no Run config chooser"
57
+ tell application "System Events" to tell process "Conductor"
58
+ set value of attribute "AXFocused" of chooser to true
59
+ end tell
60
+ tell application "System Events" to key code 49
61
+ delay 0.4
62
+ set choices to {}
63
+ set wa to my webArea()
64
+ tell application "System Events" to tell process "Conductor"
65
+ repeat with m in (get UI elements of wa whose role is "AXMenu")
66
+ repeat with entry in (get UI elements of m whose role is "AXMenuItem")
67
+ set node to contents of entry
68
+ set label to my axName(node)
69
+ if label is not "" and label is not "Configure" then set end of choices to node
70
+ end repeat
71
+ end repeat
72
+ end tell
73
+ return choices
74
+ end openRunTaskMenu
75
+
76
+ on startRunTaskNamed(tg, wantedTask)
77
+ try
78
+ set matches to {}
79
+ repeat with entry in my openRunTaskMenu(tg)
80
+ set node to contents of entry
81
+ if (my axName(node)) is wantedTask then set end of matches to node
82
+ end repeat
83
+ if (count of matches) is 0 then error "no Run config named " & wantedTask
84
+ if (count of matches) > 1 then error "several Run configs match " & wantedTask
85
+ tell application "System Events" to tell process "Conductor"
86
+ perform action "AXPress" of item 1 of matches
87
+ end tell
88
+ on error errText number errNum
89
+ tell application "System Events" to key code 53
90
+ error errText number errNum
91
+ end try
92
+ end startRunTaskNamed
93
+
94
+ on startSoleRunTask(tg)
95
+ try
96
+ set choices to my openRunTaskMenu(tg)
97
+ if (count of choices) is 0 then error "Conductor's Run menu contains no tasks"
98
+ if (count of choices) > 1 then error "choose which Run config to start"
99
+ tell application "System Events" to tell process "Conductor"
100
+ perform action "AXPress" of item 1 of choices
101
+ end tell
102
+ on error errText number errNum
103
+ tell application "System Events" to key code 53
104
+ error errText number errNum
105
+ end try
106
+ end startSoleRunTask
107
+
108
+ on runTaskState(btn)
109
+ set label to my axName(btn)
110
+ if label starts with "Stop " then return "running"
111
+ if label starts with "Run " then return "stopped"
112
+ error "couldn't read Conductor's Run task state"
113
+ end runTaskState
114
+
115
+ on runTaskName(btn)
116
+ set label to my axName(btn)
117
+ if label starts with "Stop " then return text 6 thru -1 of label
118
+ if label starts with "Run " then return text 5 thru -1 of label
119
+ return label
120
+ end runTaskName
121
+
122
+ on linkDestination(controlEl)
123
+ tell application "System Events" to tell process "Conductor"
124
+ try
125
+ return value of attribute "AXURL" of controlEl as text
126
+ on error
127
+ return ""
128
+ end try
129
+ end tell
130
+ end linkDestination
131
+
132
+ on openRunTargets(tg)
133
+ set foundPorts to {}
134
+ set foundLinks to {}
135
+ tell application "System Events" to tell process "Conductor"
136
+ set controls to UI elements of tg
137
+ end tell
138
+ repeat with entry in controls
139
+ set node to contents of entry
140
+ tell application "System Events" to tell process "Conductor"
141
+ set nodeRole to role of node
142
+ end tell
143
+ if nodeRole is "AXButton" or nodeRole is "AXLink" then
144
+ set label to my axName(node)
145
+ if label starts with "Open" then
146
+ set destination to my linkDestination(node)
147
+ if destination starts with "http://" or destination starts with "https://" then set end of foundLinks to destination
148
+ if label starts with "Open :" then set end of foundPorts to text 7 thru -1 of label
149
+ end if
150
+ end if
151
+ end repeat
152
+ return {foundPorts, foundLinks}
153
+ end openRunTargets
154
+
155
+ on setRunTask(wantRunning, wantedTask)
156
+ my activateConductor()
157
+ my focusWorkspace()
158
+ set tg to my runStrip()
159
+ my assertWorkspace(tg)
160
+ set btn to my runTaskButton(tg)
161
+ set beforeState to my runTaskState(btn)
162
+ set taskName to my runTaskName(btn)
163
+ set wantedState to "stopped"
164
+ if wantRunning then set wantedState to "running"
165
+ set changed to "false"
166
+ if wantRunning and wantedTask is not "" and beforeState is "running" and taskName is not wantedTask then error taskName & " is already running; stop it before starting " & wantedTask
167
+
168
+ if beforeState is not wantedState then
169
+ if wantRunning and wantedTask is not "" then
170
+ my startRunTaskNamed(tg, wantedTask)
171
+ else if wantRunning and (my runTaskChooser(tg)) is not missing value then
172
+ -- Legacy settings carry no name to the relay. Preserve their one-tap
173
+ -- behavior only when the live menu independently confirms one choice.
174
+ my startSoleRunTask(tg)
175
+ else
176
+ tell application "System Events" to tell process "Conductor"
177
+ perform action "AXPress" of btn
178
+ end tell
179
+ end if
180
+ set changed to "true"
181
+ end if
182
+
183
+ -- The panel re-renders after the press, invalidating every old AX handle.
184
+ -- Re-find it on each poll and require the opposite label before reporting the
185
+ -- action as complete. A started web server gets a short second window in which
186
+ -- Conductor can surface its Open control. New controls may expose their exact
187
+ -- AXURL; current builds expose only "Open :<port>". Non-server tasks return
188
+ -- with both lists empty.
189
+ set currentState to beforeState
190
+ set ports to {}
191
+ set previewURLs to {}
192
+ repeat with attempt from 1 to 44
193
+ set tg to my runStrip()
194
+ set btn to my runTaskButton(tg)
195
+ set currentState to my runTaskState(btn)
196
+ set taskName to my runTaskName(btn)
197
+ if currentState is wantedState then
198
+ if wantRunning and wantedTask is not "" and taskName is not wantedTask then error "Conductor started " & taskName & " instead of " & wantedTask
199
+ set runTargets to my openRunTargets(tg)
200
+ set ports to item 1 of runTargets
201
+ set previewURLs to item 2 of runTargets
202
+ if not wantRunning or (count of ports) > 0 or (count of previewURLs) > 0 or attempt > 20 then exit repeat
203
+ end if
204
+ delay 0.25
205
+ end repeat
206
+ if currentState is not wantedState then error "Conductor's Run task did not switch to " & wantedState
207
+ return currentState & tab & taskName & tab & changed & tab & my joinList(ports, ",") & tab & my joinList(previewURLs, ASCII character 30)
208
+ end setRunTask
@@ -0,0 +1,52 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ /**
4
+ * One AppleScript program, split only for editing. Order is intentional: runtime
5
+ * loading, compile checking and package copying all consume this same manifest.
6
+ * Parts retain their original whitespace; joining inserts no extra separators.
7
+ */
8
+ const PARTS = [
9
+ 'window.applescript',
10
+ 'workspace-targeting.applescript',
11
+ 'chat-targeting.applescript',
12
+ 'composer.applescript',
13
+ 'run-tasks.applescript',
14
+ 'lifecycle.applescript',
15
+ 'agent-options.applescript',
16
+ 'workspace-status.applescript',
17
+ 'hotspot.applescript'
18
+ ];
19
+ /**
20
+ * Assets are siblings of this module in both src/ and dist-node/src/. Validate
21
+ * the directory too, so adding a section without listing it cannot silently
22
+ * leave handlers out of a local run, repository check, or published package.
23
+ */
24
+ export function conductorAppleScriptSources() {
25
+ const files = fs
26
+ .readdirSync(import.meta.dirname, { recursive: true, withFileTypes: true })
27
+ .filter(entry => entry.isFile() && entry.name.endsWith('.applescript'))
28
+ .map(entry => path.relative(import.meta.dirname, path.join(entry.parentPath, entry.name)));
29
+ const expected = new Set(PARTS);
30
+ const missing = PARTS.filter(file => !files.includes(file));
31
+ const unlisted = files.filter(file => !expected.has(file));
32
+ if (expected.size !== PARTS.length || missing.length || unlisted.length) {
33
+ throw new Error(`AppleScript manifest mismatch: ${[
34
+ ...(expected.size !== PARTS.length ? ['duplicate parts'] : []),
35
+ ...(missing.length ? [`missing ${missing.join(', ')}`] : []),
36
+ ...(unlisted.length ? [`unlisted ${unlisted.join(', ')}`] : [])
37
+ ].join('; ')}`);
38
+ }
39
+ let firstLine = 1;
40
+ return PARTS.map(part => {
41
+ const file = path.join(import.meta.dirname, part);
42
+ const text = fs.readFileSync(file, 'utf8');
43
+ const source = { file, text, firstLine };
44
+ firstLine += text.split('\n').length - 1;
45
+ return source;
46
+ });
47
+ }
48
+ export function readConductorAppleScript() {
49
+ return conductorAppleScriptSources()
50
+ .map(source => source.text)
51
+ .join('');
52
+ }