conductor-remote 1.109.0 → 1.111.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 +3 -3
- package/dist/assets/{PierrePatch-djOpkB_6.js → PierrePatch-QISkVnaE.js} +1 -1
- package/dist/assets/index-Dg6_e4df.js +59 -0
- package/dist/assets/index-iryu1HEQ.css +1 -0
- package/dist/index.html +2 -2
- package/dist/sw.js +1 -1
- package/dist-node/src/server.js +46 -3
- package/dist-node/src/voice/brief.js +157 -10
- package/dist-node/src/voice/preview.js +49 -6
- package/dist-node/src/voice/prompt.js +3 -1
- package/dist-node/src/voice/tools.js +164 -7
- package/docs/voice-setup.md +22 -10
- package/package.json +1 -1
- package/dist/assets/index-Bbv8DA0_.js +0 -59
- package/dist/assets/index-DEuZ2MOq.css +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { oneLine } from "../speech.js";
|
|
1
|
+
import { clipExact, oneLine } from "../speech.js";
|
|
2
2
|
/**
|
|
3
3
|
* One definition table feeds both transports: SIP exposes it through the scoped
|
|
4
4
|
* MCP server, while the PWA's private sideband exposes the same entries as
|
|
@@ -9,6 +9,9 @@ export const VOICE_TOOL_NAMES = [
|
|
|
9
9
|
'voice_roll_call',
|
|
10
10
|
'voice_workspace_overview',
|
|
11
11
|
'voice_next_decision',
|
|
12
|
+
'voice_list_repos',
|
|
13
|
+
'voice_create_workspace_preview',
|
|
14
|
+
'voice_create_workspace',
|
|
12
15
|
'voice_send_preview',
|
|
13
16
|
'voice_send'
|
|
14
17
|
];
|
|
@@ -20,11 +23,37 @@ export const VOICE_TOOL_DEFINITIONS = [
|
|
|
20
23
|
},
|
|
21
24
|
{
|
|
22
25
|
name: 'voice_workspace_overview',
|
|
23
|
-
description: 'Get a fresh overview of current workspaces with
|
|
26
|
+
description: 'Get a fresh, dated overview of current workspaces with filters and the relay as-of time. Merged and Done workspaces are excluded unless explicitly included. Call this every time the user asks for an overview or workspace status, even if one was already given. Pass the same filters with the returned cursor to continue.',
|
|
24
27
|
inputSchema: {
|
|
25
28
|
type: 'object',
|
|
26
29
|
properties: {
|
|
27
|
-
cursor: { type: 'number', description: 'The cursor returned by the previous overview page; default 0.' }
|
|
30
|
+
cursor: { type: 'number', description: 'The cursor returned by the previous overview page; default 0.' },
|
|
31
|
+
repo: { type: 'string', description: 'Only this exact repository name.' },
|
|
32
|
+
agent_status: {
|
|
33
|
+
type: 'string',
|
|
34
|
+
enum: ['working', 'idle', 'error', 'needs-you'],
|
|
35
|
+
description: 'Only workspaces whose matching chat has this live agent status.'
|
|
36
|
+
},
|
|
37
|
+
workspace_status: {
|
|
38
|
+
type: 'string',
|
|
39
|
+
enum: ['backlog', 'in-progress', 'in-review', 'done', 'canceled'],
|
|
40
|
+
description: 'Only workspaces in this Conductor sidebar status. Requesting done includes Done workspaces.'
|
|
41
|
+
},
|
|
42
|
+
pr_status: {
|
|
43
|
+
type: 'string',
|
|
44
|
+
enum: ['merged', 'draft', 'conflicts', 'checks_failed', 'checks_pending', 'mergeable', 'none'],
|
|
45
|
+
description: 'Only workspaces with this pull-request status. Requesting merged includes merged workspaces.'
|
|
46
|
+
},
|
|
47
|
+
updated_since: {
|
|
48
|
+
type: 'string',
|
|
49
|
+
description: 'Only chat activity since today, yesterday, this-week, this-month, a relative duration like 24h or 7d, or an ISO date/time.'
|
|
50
|
+
},
|
|
51
|
+
updated_before: {
|
|
52
|
+
type: 'string',
|
|
53
|
+
description: 'Only chat activity before this exclusive named boundary, relative duration, ISO date/time, or date. For yesterday alone, use updated_since yesterday and updated_before today.'
|
|
54
|
+
},
|
|
55
|
+
include_done: { type: 'boolean', description: 'Include Done workspaces; default false.' },
|
|
56
|
+
include_merged: { type: 'boolean', description: 'Include merged workspaces; default false.' }
|
|
28
57
|
}
|
|
29
58
|
}
|
|
30
59
|
},
|
|
@@ -42,6 +71,36 @@ export const VOICE_TOOL_DEFINITIONS = [
|
|
|
42
71
|
}
|
|
43
72
|
}
|
|
44
73
|
},
|
|
74
|
+
{
|
|
75
|
+
name: 'voice_list_repos',
|
|
76
|
+
description: 'List the repositories where Conductor can create a workspace. Use before a creation preview.',
|
|
77
|
+
inputSchema: { type: 'object', properties: {} }
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
name: 'voice_create_workspace_preview',
|
|
81
|
+
description: 'Create a two-minute preview for a new workspace in an exact repository, with an optional first prompt. Speak its exact target and prompt and ask for yes before using voice_create_workspace.',
|
|
82
|
+
inputSchema: {
|
|
83
|
+
type: 'object',
|
|
84
|
+
properties: {
|
|
85
|
+
repo: { type: 'string', description: 'Exact repository name from voice_list_repos.' },
|
|
86
|
+
prompt: { type: 'string', description: 'Optional first prompt. Omit to create an empty workspace.' }
|
|
87
|
+
},
|
|
88
|
+
required: ['repo']
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
name: 'voice_create_workspace',
|
|
93
|
+
description: 'Create an exact workspace preview after the user said yes. Requires its token, repository, and unchanged prompt; never accepts raw unpreviewed creation.',
|
|
94
|
+
inputSchema: {
|
|
95
|
+
type: 'object',
|
|
96
|
+
properties: {
|
|
97
|
+
token: { type: 'string' },
|
|
98
|
+
repo: { type: 'string' },
|
|
99
|
+
prompt: { type: 'string', description: 'The unchanged preview prompt; omit only when the preview was empty.' }
|
|
100
|
+
},
|
|
101
|
+
required: ['token', 'repo']
|
|
102
|
+
}
|
|
103
|
+
},
|
|
45
104
|
{
|
|
46
105
|
name: 'voice_send_preview',
|
|
47
106
|
description: 'Create a two-minute exact-text send preview. Speak its exact target and text and ask for yes before using voice_send.',
|
|
@@ -84,23 +143,33 @@ function need(args, key) {
|
|
|
84
143
|
throw new Error(`${key} is required`);
|
|
85
144
|
return value.trim();
|
|
86
145
|
}
|
|
146
|
+
function optional(args, key) {
|
|
147
|
+
const value = args[key];
|
|
148
|
+
return typeof value === 'string' && value.trim() ? value.trim() : undefined;
|
|
149
|
+
}
|
|
87
150
|
function answer(value) {
|
|
88
151
|
return JSON.stringify(value);
|
|
89
152
|
}
|
|
90
153
|
function refusal(reason) {
|
|
91
154
|
switch (reason) {
|
|
92
155
|
case 'expired':
|
|
93
|
-
return 'That preview expired. Read the exact
|
|
156
|
+
return 'That preview expired. Read the exact action back again before approving it.';
|
|
94
157
|
case 'foreign-call':
|
|
95
158
|
return 'That preview belongs to another call and cannot be used here.';
|
|
96
159
|
case 'foreign-session':
|
|
97
160
|
return 'That preview belongs to a different chat. Preview this target again.';
|
|
161
|
+
case 'foreign-repo':
|
|
162
|
+
return 'That preview belongs to a different repository. Preview this workspace again.';
|
|
98
163
|
case 'text-mismatch':
|
|
99
164
|
return 'The send text does not exactly match the preview. Preview the changed text first.';
|
|
165
|
+
case 'prompt-mismatch':
|
|
166
|
+
return 'The first prompt does not exactly match the preview. Preview the changed workspace first.';
|
|
167
|
+
case 'wrong-action':
|
|
168
|
+
return 'That preview is for a different action. Make a new preview first.';
|
|
100
169
|
case 'already-used':
|
|
101
|
-
return 'That preview was already used. The relay will not
|
|
170
|
+
return 'That preview was already used. The relay will not run it twice.';
|
|
102
171
|
case 'unknown':
|
|
103
|
-
return 'That preview token is unknown. Make a new preview before
|
|
172
|
+
return 'That preview token is unknown. Make a new preview before continuing.';
|
|
104
173
|
}
|
|
105
174
|
}
|
|
106
175
|
function later(task) {
|
|
@@ -123,7 +192,30 @@ export function createVoiceTools(context) {
|
|
|
123
192
|
...definition('voice_workspace_overview'),
|
|
124
193
|
run: async (args) => {
|
|
125
194
|
const cursor = typeof args.cursor === 'number' && Number.isFinite(args.cursor) ? args.cursor : 0;
|
|
126
|
-
|
|
195
|
+
const filters = {};
|
|
196
|
+
const repo = optional(args, 'repo');
|
|
197
|
+
const agentStatus = optional(args, 'agent_status');
|
|
198
|
+
const workspaceStatus = optional(args, 'workspace_status');
|
|
199
|
+
const prStatus = optional(args, 'pr_status');
|
|
200
|
+
const updatedSince = optional(args, 'updated_since');
|
|
201
|
+
const updatedBefore = optional(args, 'updated_before');
|
|
202
|
+
if (repo)
|
|
203
|
+
filters.repo = repo;
|
|
204
|
+
if (agentStatus)
|
|
205
|
+
filters.agentStatus = agentStatus;
|
|
206
|
+
if (workspaceStatus)
|
|
207
|
+
filters.workspaceStatus = workspaceStatus;
|
|
208
|
+
if (prStatus)
|
|
209
|
+
filters.prStatus = prStatus;
|
|
210
|
+
if (updatedSince)
|
|
211
|
+
filters.updatedSince = updatedSince;
|
|
212
|
+
if (updatedBefore)
|
|
213
|
+
filters.updatedBefore = updatedBefore;
|
|
214
|
+
if (typeof args.include_done === 'boolean')
|
|
215
|
+
filters.includeDone = args.include_done;
|
|
216
|
+
if (typeof args.include_merged === 'boolean')
|
|
217
|
+
filters.includeMerged = args.include_merged;
|
|
218
|
+
return answer(await context.board.workspaceOverview(cursor, filters));
|
|
127
219
|
}
|
|
128
220
|
},
|
|
129
221
|
{
|
|
@@ -136,6 +228,71 @@ export function createVoiceTools(context) {
|
|
|
136
228
|
return answer(next ?? { spoken: 'There are no more decisions in this call.', cursor, done: true });
|
|
137
229
|
}
|
|
138
230
|
},
|
|
231
|
+
{
|
|
232
|
+
...definition('voice_list_repos'),
|
|
233
|
+
run: async () => {
|
|
234
|
+
const repos = context.listRepos();
|
|
235
|
+
return answer({
|
|
236
|
+
spoken: repos.length
|
|
237
|
+
? clipExact(`Available repositories: ${repos.map(repo => repo.name).join(', ')}.`, 600)
|
|
238
|
+
: 'Conductor has no repository available for a new workspace.',
|
|
239
|
+
repos
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
...definition('voice_create_workspace_preview'),
|
|
245
|
+
run: async (args) => {
|
|
246
|
+
const requestedRepo = need(args, 'repo');
|
|
247
|
+
const repo = context.listRepos().find(candidate => candidate.name.toLowerCase() === requestedRepo.toLowerCase());
|
|
248
|
+
if (!repo)
|
|
249
|
+
return answer({
|
|
250
|
+
status: 'refused',
|
|
251
|
+
spoken: `I could not find the ${oneLine(requestedRepo, 80)} repository. Ask me to list repositories first.`
|
|
252
|
+
});
|
|
253
|
+
const prompt = optional(args, 'prompt') ?? '';
|
|
254
|
+
const preview = context.previews.createWorkspace({ callId: context.callId, repo: repo.name, prompt });
|
|
255
|
+
const detail = prompt ? ` with this first prompt: “${oneLine(prompt, 220)}”` : ' with no first prompt.';
|
|
256
|
+
return answer({
|
|
257
|
+
status: 'preview',
|
|
258
|
+
token: preview.token,
|
|
259
|
+
repo: repo.name,
|
|
260
|
+
prompt,
|
|
261
|
+
spoken: `Create a new workspace in ${oneLine(repo.name, 80)}${detail} Say yes to create it.`
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
...definition('voice_create_workspace'),
|
|
267
|
+
run: async (args) => {
|
|
268
|
+
const token = need(args, 'token');
|
|
269
|
+
const repo = need(args, 'repo');
|
|
270
|
+
const prompt = optional(args, 'prompt') ?? '';
|
|
271
|
+
const claimed = context.previews.claimWorkspace(token, { callId: context.callId, repo, prompt });
|
|
272
|
+
if (!claimed.ok)
|
|
273
|
+
return answer({ status: 'refused', spoken: refusal(claimed.reason) });
|
|
274
|
+
later(async () => {
|
|
275
|
+
try {
|
|
276
|
+
const result = await context.createWorkspace(claimed.preview);
|
|
277
|
+
if (!result.ok) {
|
|
278
|
+
await context.announce(`The new ${oneLine(repo, 80)} workspace was not created. ${oneLine(result.error ?? 'Try again later.', 220)}`);
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
const created = prompt
|
|
282
|
+
? `Created a new ${oneLine(repo, 80)} workspace and queued its first prompt.`
|
|
283
|
+
: `Created a new empty ${oneLine(repo, 80)} workspace.`;
|
|
284
|
+
await context.announce(result.warning ? `${created} ${oneLine(result.warning, 220)}` : created);
|
|
285
|
+
}
|
|
286
|
+
catch (error) {
|
|
287
|
+
await context.announce(`The new ${oneLine(repo, 80)} workspace was not created. ${oneLine(error instanceof Error ? error.message : String(error), 220)}`);
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
return answer({
|
|
291
|
+
status: 'queued',
|
|
292
|
+
spoken: `Creating a new workspace in ${oneLine(repo, 80)}. I will say when it is ready.`
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
},
|
|
139
296
|
{
|
|
140
297
|
...definition('voice_send_preview'),
|
|
141
298
|
run: async (args) => {
|
package/docs/voice-setup.md
CHANGED
|
@@ -5,19 +5,28 @@
|
|
|
5
5
|
The primary foreground voice mode is one fleet-wide control room. In the
|
|
6
6
|
workspace-list header, tap the phone immediately left of **+**, then start the
|
|
7
7
|
call. The orchestrator surveys every workspace, presents one bounded decision
|
|
8
|
-
at a time,
|
|
9
|
-
back and you confirm. It is not owned by
|
|
10
|
-
sheet or move between workspaces and the
|
|
8
|
+
at a time, creates new workspaces, and queues exact prompts. Both writes happen
|
|
9
|
+
only after it reads the target and text back and you confirm. It is not owned by
|
|
10
|
+
the chat currently on screen: hide the sheet or move between workspaces and the
|
|
11
|
+
same call continues.
|
|
11
12
|
|
|
12
13
|
Live captions keep both sides readable, and the text box in the call sheet is a
|
|
13
|
-
fallback when speaking is inconvenient. The
|
|
14
|
-
call, fresh workspace overview, next decision,
|
|
15
|
-
|
|
14
|
+
fallback when speaking is inconvenient. The eight available actions are roll
|
|
15
|
+
call, fresh workspace overview, next decision, repository list, workspace-create
|
|
16
|
+
preview and confirmed creation, plus send preview and confirmed send. The two
|
|
17
|
+
writes use the same persisted, one-use preview/confirmation gate as the dial-in
|
|
18
|
+
orchestrator.
|
|
19
|
+
|
|
20
|
+
An ordinary overview excludes workspaces marked **Done** or whose pull request
|
|
21
|
+
is merged. Ask to include either when completed work is relevant. Overview
|
|
22
|
+
requests can also filter by repository, live agent status, workspace status, PR
|
|
23
|
+
status, and an `updated_since`/`updated_before` time window; each spoken row says
|
|
24
|
+
how recently its selected chat changed.
|
|
16
25
|
|
|
17
26
|
This path needs the managed relay, its usual private phone URL, and an OpenAI API
|
|
18
27
|
key. It does **not** need a phone number, SIP, a webhook, Funnel, or any other
|
|
19
28
|
public endpoint. The permanent key stays on the Mac: the relay creates the
|
|
20
|
-
WebRTC call, then executes those
|
|
29
|
+
WebRTC call, then executes those eight tools over its private sideband connection.
|
|
21
30
|
|
|
22
31
|
Store the key without leaving it in shell history:
|
|
23
32
|
|
|
@@ -46,9 +55,12 @@ screen locked, so use the optional dial-in transport for a pocketed commute.
|
|
|
46
55
|
|
|
47
56
|
## Optional dial-in orchestrator
|
|
48
57
|
|
|
49
|
-
The optional voice listener turns a phone call into a small Conductor control room: hear a bounded fleet tally, walk one decision at a time, and dispatch an exact prompt after a spoken read-back and explicit confirmation. It does not expose the PWA or the relay API publicly.
|
|
58
|
+
The optional voice listener turns a phone call into a small Conductor control room: hear a bounded fleet tally, walk one decision at a time, create a workspace, and dispatch an exact prompt after a spoken read-back and explicit confirmation. It does not expose the PWA or the relay API publicly.
|
|
50
59
|
|
|
51
|
-
|
|
60
|
+
The scoped endpoint has eight tools: roll call, a fresh paged workspace overview,
|
|
61
|
+
next decision, repository list, create preview, confirmed create, send preview,
|
|
62
|
+
and confirmed send. Forward-to-owner answers, artifact pushes, and voice grooming
|
|
63
|
+
remain later milestones.
|
|
52
64
|
|
|
53
65
|
## What you need
|
|
54
66
|
|
|
@@ -142,7 +154,7 @@ conductor-remote config set voice.webhook-secret "$OPENAI_WEBHOOK_SECRET"
|
|
|
142
154
|
unset OPENAI_WEBHOOK_SECRET
|
|
143
155
|
```
|
|
144
156
|
|
|
145
|
-
The relay accepts a valid incoming call through `POST /v1/realtime/calls/{call_id}/accept`, attaches an authenticated sideband WebSocket, and gives the session only the
|
|
157
|
+
The relay accepts a valid incoming call through `POST /v1/realtime/calls/{call_id}/accept`, attaches an authenticated sideband WebSocket, and gives the session only the eight scoped remote MCP tools. Remote MCP follow-up responses are driven by the broker only after both the response and every tool call in it have finished, as required by OpenAI's [Realtime MCP guide](https://developers.openai.com/api/docs/guides/realtime-mcp).
|
|
146
158
|
|
|
147
159
|
## 5. Configure the Twilio number
|
|
148
160
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "conductor-remote",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.111.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"packageManager": "yarn@4.15.0",
|
|
6
6
|
"description": "Phone control panel for local Conductor agents. Reads ride SQLite + git; prompts ride Conductor's own dispatch path.",
|