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.
- package/README.md +10 -8
- package/dist/assets/{PierrePatch-D3_YBIAr.js → PierrePatch-D4YbKD8Q.js} +1 -1
- package/dist/assets/index-BRzsSW8g.css +1 -0
- package/dist/assets/index-BaS6qXUW.js +65 -0
- package/dist/index.html +2 -2
- package/dist/push-sw.js +2 -2
- package/dist/sw.js +1 -1
- package/dist-node/scripts/nosleep-setup.js +2 -2
- package/dist-node/scripts/nosleep.js +3 -3
- package/dist-node/scripts/service/commands.js +32 -0
- package/dist-node/scripts/service/configuration.js +205 -0
- package/dist-node/scripts/service/environment.js +59 -0
- package/dist-node/scripts/service/flags.js +45 -0
- package/dist-node/scripts/service/installation.js +91 -0
- package/dist-node/scripts/service/launch-agent.js +134 -0
- package/dist-node/scripts/service/network.js +244 -0
- package/dist-node/scripts/service/presentation.js +101 -0
- package/dist-node/scripts/service/voice.js +113 -0
- package/dist-node/scripts/service.js +5 -980
- package/dist-node/src/{agent-config.js → agents/agent-config.js} +1 -1
- package/dist-node/src/{model-cache.js → agents/model-cache.js} +1 -1
- package/dist-node/src/{roles.js → agents/roles.js} +3 -3
- package/dist-node/src/db.js +1 -1
- package/dist-node/src/{parked.js → delivery/parked.js} +1 -1
- package/dist-node/src/{dev-server.js → dev-server/controller.js} +8 -291
- package/dist-node/src/dev-server/ports.js +63 -0
- package/dist-node/src/dev-server/processes.js +73 -0
- package/dist-node/src/dev-server/proxy.js +158 -0
- package/dist-node/src/{run-activity.js → dev-server/run-activity.js} +2 -2
- package/dist-node/src/dev-server/types.js +1 -0
- package/dist-node/src/{file-preview.js → files/file-preview.js} +1 -1
- package/dist-node/src/{change-stats.js → git/change-stats.js} +1 -1
- package/dist-node/src/{git.js → git/diff.js} +1 -1
- package/dist-node/src/{autoupdate.js → host/autoupdate.js} +1 -1
- package/dist-node/src/{funnel-watchdog.js → host/funnel-watchdog.js} +4 -4
- package/dist-node/src/{logbuf.js → host/logbuf.js} +1 -1
- package/dist-node/src/{nosleep-helper.js → host/nosleep-helper.js} +1 -1
- package/dist-node/src/{tailscale.js → host/tailscale.js} +1 -1
- package/dist-node/src/http/router-types.js +2 -0
- package/dist-node/src/http/router.js +78 -0
- package/dist-node/src/http/routes/create-workspace.js +90 -0
- package/dist-node/src/http/routes/files.js +53 -0
- package/dist-node/src/http/routes/prompts.js +363 -0
- package/dist-node/src/http/routes/sessions.js +279 -0
- package/dist-node/src/http/routes/state.js +186 -0
- package/dist-node/src/http/routes/system.js +220 -0
- package/dist-node/src/http/routes/voice.js +117 -0
- package/dist-node/src/http/routes/workflows.js +248 -0
- package/dist-node/src/http/routes/workspaces.js +267 -0
- package/dist-node/src/http/services/base.js +252 -0
- package/dist-node/src/http/services/delegations.js +301 -0
- package/dist-node/src/http/services/delivery.js +433 -0
- package/dist-node/src/http/services/files.js +199 -0
- package/dist-node/src/http/services/mcp.js +94 -0
- package/dist-node/src/http/services/responses.js +120 -0
- package/dist-node/src/http/services/voice.js +147 -0
- package/dist-node/src/http/services/workflow-probes.js +215 -0
- package/dist-node/src/http/services/workflow-state.js +195 -0
- package/dist-node/src/http/services/workflow.js +366 -0
- package/dist-node/src/http/services.js +35 -0
- package/dist-node/src/mcp/arguments.js +14 -0
- package/dist-node/src/mcp/dispatcher.js +56 -0
- package/dist-node/src/mcp/formatters.js +110 -0
- package/dist-node/src/mcp/protocol.js +7 -0
- package/dist-node/src/mcp/registry.js +66 -0
- package/dist-node/src/mcp/tools/agent-options.js +122 -0
- package/dist-node/src/mcp/tools/chat-actions.js +194 -0
- package/dist-node/src/mcp/tools/chats.js +194 -0
- package/dist-node/src/mcp/tools/delegation.js +135 -0
- package/dist-node/src/mcp/tools/system.js +135 -0
- package/dist-node/src/mcp/tools/voice.js +136 -0
- package/dist-node/src/mcp/tools/workspaces.js +259 -0
- package/dist-node/src/mcp/types.js +1 -0
- package/dist-node/src/mcp.js +6 -4
- package/dist-node/src/{notify.js → notifications/notify.js} +5 -5
- package/dist-node/src/{webpush.js → notifications/webpush.js} +1 -1
- package/dist-node/src/orchestration/delegation/codec.js +201 -0
- package/dist-node/src/{delegation-intake.js → orchestration/delegation/intake.js} +2 -2
- package/dist-node/src/{delegation-prompt.js → orchestration/delegation/prompt.js} +1 -1
- package/dist-node/src/orchestration/delegation/queue.js +255 -0
- package/dist-node/src/orchestration/delegation/store.js +111 -0
- package/dist-node/src/orchestration/delegation/types.js +2 -0
- package/dist-node/src/orchestration/persistence/capabilities.js +109 -0
- package/dist-node/src/orchestration/persistence/codecs.js +148 -0
- package/dist-node/src/orchestration/persistence/connection.js +205 -0
- package/dist-node/src/orchestration/persistence/db.js +101 -0
- package/dist-node/src/orchestration/persistence/effect-recovery.js +227 -0
- package/dist-node/src/orchestration/persistence/effect-transitions.js +157 -0
- package/dist-node/src/orchestration/persistence/effects.js +109 -0
- package/dist-node/src/orchestration/persistence/errors.js +39 -0
- package/dist-node/src/orchestration/persistence/events.js +29 -0
- package/dist-node/src/orchestration/persistence/idempotency.js +82 -0
- package/dist-node/src/orchestration/persistence/job-attempts.js +114 -0
- package/dist-node/src/orchestration/persistence/jobs.js +159 -0
- package/dist-node/src/orchestration/persistence/projections.js +190 -0
- package/dist-node/src/orchestration/persistence/quarantine.js +46 -0
- package/dist-node/src/orchestration/persistence/records.js +115 -0
- package/dist-node/src/orchestration/persistence/relays.js +78 -0
- package/dist-node/src/orchestration/persistence/runs.js +156 -0
- package/dist-node/src/{orchestration-schema.js → orchestration/persistence/schema.js} +1 -1
- package/dist-node/src/orchestration/persistence/types.js +1 -0
- package/dist-node/src/orchestration/persistence/ui-lease.js +197 -0
- package/dist-node/src/orchestration/persistence/values.js +114 -0
- package/dist-node/src/orchestration/workflow/commands.js +309 -0
- package/dist-node/src/orchestration/workflow/coordinator.js +28 -0
- package/dist-node/src/orchestration/workflow/effect-recovery.js +125 -0
- package/dist-node/src/orchestration/workflow/effects.js +307 -0
- package/dist-node/src/orchestration/workflow/errors.js +13 -0
- package/dist-node/src/orchestration/workflow/helpers.js +154 -0
- package/dist-node/src/orchestration/workflow/job-dispatch.js +312 -0
- package/dist-node/src/orchestration/workflow/job-results.js +280 -0
- package/dist-node/src/{workflow.js → orchestration/workflow/prompts.js} +2 -2
- package/dist-node/src/orchestration/workflow/root.js +293 -0
- package/dist-node/src/orchestration/workflow/start.js +99 -0
- package/dist-node/src/orchestration/workflow/state.js +105 -0
- package/dist-node/src/orchestration/workflow/types.js +1 -0
- package/dist-node/src/orchestration/workflow/wake.js +164 -0
- package/dist-node/src/prefs.js +1 -1
- package/dist-node/src/{background-tasks.js → reads/background-tasks.js} +1 -1
- package/dist-node/src/reads/messages.js +362 -0
- package/dist-node/src/reads/repository.js +87 -0
- package/dist-node/src/reads/sessions.js +165 -0
- package/dist-node/src/reads/types.js +1 -0
- package/dist-node/src/reads/workspaces.js +230 -0
- package/dist-node/src/reads/worktrees.js +45 -0
- package/dist-node/src/routes.js +5 -5
- package/dist-node/src/{search.js → search/coordinator.js} +4 -4
- package/dist-node/src/{search-worker.js → search/worker.js} +3 -3
- package/dist-node/src/server.js +14 -3930
- package/dist-node/src/settings.js +1 -1
- package/dist-node/src/shared.js +10 -10
- package/dist-node/src/{transcript.js → transcript/parser.js} +4 -4
- package/dist-node/src/{tool-usage-worker.js → usage/tool-usage-worker.js} +1 -1
- package/dist-node/src/{tool-usage.js → usage/tool-usage.js} +1 -1
- package/dist-node/src/voice/brief.js +22 -50
- package/dist-node/src/voice/context.js +1 -1
- package/dist-node/src/voice/dates.js +38 -0
- package/dist-node/src/voice/history.js +14 -5
- package/dist-node/src/voice/prompt.js +10 -9
- package/dist-node/src/voice/recall.js +115 -0
- package/dist-node/src/{speech.js → voice/speech.js} +1 -1
- package/dist-node/src/voice/tools.js +92 -3
- package/dist-node/src/voice/webrtc.js +1 -1
- package/dist-node/src/wire.js +2 -2
- package/dist-node/src/writes/actuator.js +124 -0
- package/dist-node/src/writes/agent-options.js +151 -0
- package/dist-node/src/writes/applescript/agent-options.applescript +334 -0
- package/dist-node/src/writes/applescript/chat-targeting.applescript +149 -0
- package/dist-node/src/writes/applescript/composer.applescript +195 -0
- package/dist-node/src/writes/applescript/hotspot.applescript +155 -0
- package/dist-node/src/writes/applescript/lifecycle.applescript +266 -0
- package/dist-node/src/writes/applescript/run-tasks.applescript +208 -0
- package/dist-node/src/writes/applescript/source.js +52 -0
- package/dist-node/src/writes/applescript/window.applescript +436 -0
- package/dist-node/src/writes/applescript/workspace-status.applescript +177 -0
- package/dist-node/src/writes/applescript/workspace-targeting.applescript +359 -0
- package/dist-node/src/writes/chats.js +160 -0
- package/dist-node/src/writes/guards.js +77 -0
- package/dist-node/src/writes/runner.js +67 -0
- package/dist-node/src/{sidecar.js → writes/sidecar.js} +1 -1
- package/dist-node/src/writes/system.js +81 -0
- package/dist-node/src/writes/targeting.js +121 -0
- package/dist-node/src/writes/types.js +1 -0
- package/dist-node/src/writes/ui-lock.js +229 -0
- package/dist-node/src/writes/workspaces.js +233 -0
- package/docs/voice-setup.md +34 -12
- package/package.json +2 -2
- package/dist/assets/index-ByTwVInL.css +0 -1
- package/dist/assets/index-LAm9X7wv.js +0 -65
- package/dist-node/src/conductor.applescript +0 -2279
- package/dist-node/src/delegations.js +0 -557
- package/dist-node/src/mcp-tools.js +0 -1319
- package/dist-node/src/orchestration-db.js +0 -2186
- package/dist-node/src/reads.js +0 -787
- package/dist-node/src/workflow-coordinator.js +0 -2119
- package/dist-node/src/writes.js +0 -1223
- /package/dist-node/src/{conductor-settings.js → agents/conductor-settings.js} +0 -0
- /package/dist-node/src/{firstprompt.js → delivery/firstprompt.js} +0 -0
- /package/dist-node/src/{sendonce.js → delivery/sendonce.js} +0 -0
- /package/dist-node/src/{preview-urls.js → dev-server/preview-urls.js} +0 -0
- /package/dist-node/src/{run-configs.js → dev-server/run-configs.js} +0 -0
- /package/dist-node/src/{attachments.js → files/attachments.js} +0 -0
- /package/dist-node/src/{icons.js → files/icons.js} +0 -0
- /package/dist-node/src/{staged-attachments.js → files/staged-attachments.js} +0 -0
- /package/dist-node/src/{fork-workspace.js → git/fork-workspace.js} +0 -0
- /package/dist-node/src/{merge.js → git/merge.js} +0 -0
- /package/dist-node/src/{pr.js → git/pr.js} +0 -0
- /package/dist-node/src/{nosleep.js → host/nosleep.js} +0 -0
- /package/dist-node/src/{relay-processes.js → host/relay-processes.js} +0 -0
- /package/dist-node/src/{ui-lease-watchdog.js → host/ui-lease-watchdog.js} +0 -0
- /package/dist-node/src/{wifi.js → host/wifi.js} +0 -0
- /package/dist-node/src/{orchestration-schema.generated.js → orchestration/persistence/schema.generated.js} +0 -0
- /package/dist-node/src/{workflow-effect-runner.js → orchestration/workflow/effect-runner.js} +0 -0
- /package/dist-node/src/{workflow-http.js → orchestration/workflow/http.js} +0 -0
- /package/dist-node/src/{workflow-machine.js → orchestration/workflow/machine.js} +0 -0
- /package/dist-node/src/{session-poller.js → reads/session-poller.js} +0 -0
- /package/dist-node/src/{context-breakdown.js → transcript/context-breakdown.js} +0 -0
- /package/dist-node/src/{chat-cursor.js → transcript/cursor.js} +0 -0
- /package/dist-node/src/{plan-usage.js → usage/plan-usage.js} +0 -0
- /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
|
+
}
|