conductor-remote 1.106.3 → 1.108.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 +65 -44
- package/dist/assets/{PierrePatch-CEuLSHL_.js → PierrePatch-DF-STc4J.js} +1 -1
- package/dist/assets/index-DM7eCDXg.css +1 -0
- package/dist/assets/index-DWbWwEOp.js +59 -0
- package/dist/index.html +2 -2
- package/dist/licenses/vscode-seti.txt +48 -0
- package/dist/sw.js +1 -1
- package/dist-node/src/logbuf.js +4 -3
- package/dist-node/src/mcp-tools.js +48 -138
- package/dist-node/src/model-cache.js +22 -7
- package/dist-node/src/orchestration-db.js +2150 -0
- package/dist-node/src/orchestration-schema.generated.js +306 -0
- package/dist-node/src/orchestration-schema.js +417 -0
- package/dist-node/src/preview-urls.js +1 -1
- package/dist-node/src/reads.js +135 -6
- package/dist-node/src/relay-processes.js +230 -0
- package/dist-node/src/roles.js +64 -12
- package/dist-node/src/routes.js +14 -1
- package/dist-node/src/search-worker.js +3 -1
- package/dist-node/src/server.js +1050 -211
- package/dist-node/src/shared.js +79 -2
- package/dist-node/src/transcript.js +20 -10
- package/dist-node/src/ui-lease-watchdog.js +94 -0
- package/dist-node/src/workflow-coordinator.js +2119 -0
- package/dist-node/src/workflow-effect-runner.js +336 -0
- package/dist-node/src/workflow-http.js +141 -0
- package/dist-node/src/workflow-machine.js +112 -0
- package/dist-node/src/workflow.js +111 -22
- package/dist-node/src/writes.js +157 -12
- package/package.json +9 -2
- package/dist/assets/index-BfTQtpd5.css +0 -1
- package/dist/assets/index-BiWQQKon.js +0 -58
package/dist/index.html
CHANGED
|
@@ -25,8 +25,8 @@
|
|
|
25
25
|
<title>Conductor Remote</title>
|
|
26
26
|
<!-- Runs before the module bundle so it can catch a stale shell that fails to boot. -->
|
|
27
27
|
<script src="/self-heal.js"></script>
|
|
28
|
-
<script type="module" crossorigin src="/assets/index-
|
|
29
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
28
|
+
<script type="module" crossorigin src="/assets/index-DWbWwEOp.js"></script>
|
|
29
|
+
<link rel="stylesheet" crossorigin href="/assets/index-DM7eCDXg.css">
|
|
30
30
|
<link rel="manifest" href="/manifest.webmanifest"></head>
|
|
31
31
|
<body>
|
|
32
32
|
<div id="root"></div>
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
VS Code Seti file icon theme
|
|
2
|
+
Source: https://github.com/microsoft/vscode/tree/main/extensions/theme-seti
|
|
3
|
+
|
|
4
|
+
MIT License
|
|
5
|
+
|
|
6
|
+
Copyright (c) 2015 - present Microsoft Corporation
|
|
7
|
+
|
|
8
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
9
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
10
|
+
in the Software without restriction, including without limitation the rights
|
|
11
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
12
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
13
|
+
furnished to do so, subject to the following conditions:
|
|
14
|
+
|
|
15
|
+
The above copyright notice and this permission notice shall be included in all
|
|
16
|
+
copies or substantial portions of the Software.
|
|
17
|
+
|
|
18
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
19
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
20
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
21
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
22
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
23
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
24
|
+
SOFTWARE.
|
|
25
|
+
|
|
26
|
+
The theme incorporates Seti UI:
|
|
27
|
+
https://github.com/jesseweed/seti-ui
|
|
28
|
+
|
|
29
|
+
Copyright (c) 2014 Jesse Weed
|
|
30
|
+
|
|
31
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
32
|
+
a copy of this software and associated documentation files (the
|
|
33
|
+
"Software"), to deal in the Software without restriction, including
|
|
34
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
35
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
36
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
37
|
+
the following conditions:
|
|
38
|
+
|
|
39
|
+
The above copyright notice and this permission notice shall be
|
|
40
|
+
included in all copies or substantial portions of the Software.
|
|
41
|
+
|
|
42
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
43
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
44
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
45
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
46
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
47
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
48
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/dist/sw.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
if(!self.define){let e,s={};const i=(i,n)=>(i=new URL(i+".js",n).href,s[i]||new Promise(s=>{if("document"in self){const e=document.createElement("script");e.src=i,e.onload=s,document.head.appendChild(e)}else e=i,importScripts(i),s()}).then(()=>{let e=s[i];if(!e)throw new Error(`Module ${i} didn’t register its module`);return e}));self.define=(n,r)=>{const l=e||("document"in self?document.currentScript.src:"")||location.href;if(s[l])return;let o={};const
|
|
1
|
+
if(!self.define){let e,s={};const i=(i,n)=>(i=new URL(i+".js",n).href,s[i]||new Promise(s=>{if("document"in self){const e=document.createElement("script");e.src=i,e.onload=s,document.head.appendChild(e)}else e=i,importScripts(i),s()}).then(()=>{let e=s[i];if(!e)throw new Error(`Module ${i} didn’t register its module`);return e}));self.define=(n,r)=>{const l=e||("document"in self?document.currentScript.src:"")||location.href;if(s[l])return;let o={};const a=e=>i(e,l),t={module:{uri:l},exports:o,require:a};s[l]=Promise.all(n.map(e=>t[e]||a(e))).then(e=>(r(...e),o))}}define(["./workbox-dcde9eb3"],function(e){"use strict";importScripts("/push-sw.js"),self.addEventListener("message",e=>{e.data&&"SKIP_WAITING"===e.data.type&&self.skipWaiting()}),e.clientsClaim(),e.precacheAndRoute([{url:"self-heal.js",revision:"49bd63adb25a09341f8d2610e8bd3c76"},{url:"push-sw.js",revision:"e7ef44deca46c0539e6ff7bba5eb815e"},{url:"index.html",revision:"991cad8015a1f151f99868cf55ba55dc"},{url:"assets/workbox-window.prod.es5-BBnX5xw4.js",revision:null},{url:"assets/pierre-shiki-wasm-C8ETeZem.js",revision:null},{url:"assets/pierre-light-480U9XYS.js",revision:null},{url:"assets/pierre-dark-CyvmCCZW.js",revision:null},{url:"assets/index-DWbWwEOp.js",revision:null},{url:"assets/index-DM7eCDXg.css",revision:null},{url:"assets/PierrePatch-DF-STc4J.js",revision:null},{url:"apple-touch-icon.png",revision:"1127bb396b4648add53dce3f22c92aee"},{url:"icon-192.png",revision:"c5e01ac58768627e18ee7b8b6a9239ef"},{url:"icon-512.png",revision:"a40638c55e310312457a621c9a0002c8"},{url:"icon-maskable-512.png",revision:"a9b0d962686287452216492cd2247499"},{url:"icon.svg",revision:"c1aee186821798733dd477e69a0ef243"},{url:"manifest.webmanifest",revision:"cf88fbc5755108a7fe0616fa160a8a15"}],{}),e.cleanupOutdatedCaches(),e.registerRoute(new e.NavigationRoute(e.createHandlerBoundToURL("/index.html"),{denylist:[/^\/api\//]})),e.registerRoute(({url:e})=>e.pathname.startsWith("/assets/diff-syntax/"),new e.CacheFirst({cacheName:"diff-syntax",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:31536e3})]}),"GET")});
|
package/dist-node/src/logbuf.js
CHANGED
|
@@ -21,6 +21,7 @@ import fs from 'node:fs';
|
|
|
21
21
|
import os from 'node:os';
|
|
22
22
|
import path from 'node:path';
|
|
23
23
|
import util from 'node:util';
|
|
24
|
+
import { scrubWorkflowSecrets } from "./shared.js";
|
|
24
25
|
/** Kept small enough that a phone on a slow link can pull the whole thing; ~a day of a quiet relay. */
|
|
25
26
|
const CAPACITY = 600;
|
|
26
27
|
/** One AppleScript failure can carry a long stack; keep a single entry from crowding out the rest. */
|
|
@@ -60,7 +61,7 @@ export function installLogCapture() {
|
|
|
60
61
|
const wrap = (level, original) => {
|
|
61
62
|
return (...args) => {
|
|
62
63
|
const at = new Date();
|
|
63
|
-
const text = util.format(...args);
|
|
64
|
+
const text = scrubWorkflowSecrets(util.format(...args));
|
|
64
65
|
push(level, text, at.getTime());
|
|
65
66
|
original(prefixed(text, level, at));
|
|
66
67
|
};
|
|
@@ -142,8 +143,8 @@ export function tailLogFile(name, limit) {
|
|
|
142
143
|
*/
|
|
143
144
|
export function redactSecrets(text, token) {
|
|
144
145
|
const masked = token ? text.split(token).join('<token>') : text;
|
|
145
|
-
return masked
|
|
146
|
+
return scrubWorkflowSecrets(masked
|
|
146
147
|
.replace(/\bsk-[A-Za-z0-9_-]{8,}/g, '<openai-key>')
|
|
147
148
|
.replace(/\bwhsec_[A-Za-z0-9+/=_-]{8,}/g, '<webhook-secret>')
|
|
148
|
-
.replace(/(token=)[^\s&"'`]+/gi, '$1<token>');
|
|
149
|
+
.replace(/(token=)[^\s&"'`]+/gi, '$1<token>'));
|
|
149
150
|
}
|
|
@@ -14,11 +14,11 @@
|
|
|
14
14
|
*
|
|
15
15
|
* Every tool is an HTTP call to the relay, injected as `call`, and that is the
|
|
16
16
|
* load-bearing decision rather than a convenience. Conductor has one shared window,
|
|
17
|
-
* so the only thing that makes writes safe is `writes.ts` ▸ `uiTurn`,
|
|
18
|
-
*
|
|
19
|
-
* agents focusing different workspaces would land each other's
|
|
20
|
-
* failure every fail-closed assertion cannot catch. Routed through
|
|
21
|
-
* phone,
|
|
17
|
+
* so the only thing that makes writes safe is `writes.ts` ▸ `uiTurn`, backed by the
|
|
18
|
+
* coordinator's relay-shared mutex. A tool that drove AppleScript itself would sit
|
|
19
|
+
* outside it, and two agents focusing different workspaces would land each other's
|
|
20
|
+
* prompts — the exact failure every fail-closed assertion cannot catch. Routed through
|
|
21
|
+
* the relay, the phone, delivery queues and every agent share the same gate.
|
|
22
22
|
*
|
|
23
23
|
* The in-relay transport calls the relay's own API over loopback rather than reaching
|
|
24
24
|
* into `reads`/`writes` directly. That is a sub-millisecond hop against a 1000-line
|
|
@@ -27,11 +27,11 @@
|
|
|
27
27
|
*/
|
|
28
28
|
import { chatCursor, parseChatCursor } from "./chat-cursor.js";
|
|
29
29
|
import { routes } from "./routes.js";
|
|
30
|
-
import {
|
|
30
|
+
import { HIT_CLOSE, HIT_OPEN, isToolResult, timestampMs, workspaceTitle } from "./shared.js";
|
|
31
31
|
/** Versions we know how to speak. The client's choice wins when we know it. */
|
|
32
32
|
export const PROTOCOL_VERSIONS = ['2025-06-18', '2025-03-26', '2024-11-05'];
|
|
33
33
|
export const SERVER_INFO = { name: 'conductor-remote', version: '1' };
|
|
34
|
-
export const INSTRUCTIONS = 'Drives local Conductor agents through the conductor-remote relay. search_chats and read_chat reach archived workspaces, which is where most finished work lives. delegate_task
|
|
34
|
+
export const INSTRUCTIONS = 'Drives local Conductor agents through the conductor-remote relay. search_chats and read_chat reach archived workspaces, which is where most finished work lives. delegate_task operates only inside a Workflow already authorized from the phone: the current root envelope supplies its workflow id and phase capability, and the relay later drives the real Mac UI to launch the tracked child. MCP cannot start a Workflow, edit its frozen roles, or perform recovery actions. create_workspace opens its workspace link without direct UI control; requested agent settings and the first prompt are applied later through Conductor’s UI. plan_usage, dev_server status, dismiss_prompt, keep_awake and relay_logs touch no UI. Use dev_server instead of launching a long-lived development server from a shell: its start/stop actions use Conductor’s configured Run task, drive the real Mac UI and steal focus for a few seconds. send_prompt, split_chat, stop_turn, close_chat, set_agent_options, set_default_model, a live list_models call, set_workspace_status and archive_workspace also drive the real Mac UI and steal focus for a few seconds — confirm with the user before using them on a chat or workspace they did not name. archive_workspace also deletes the worktree and stops whatever is running there.';
|
|
35
35
|
/** Reads are quick; a UI write is measured in tens of seconds (writes.ts ▸ SEND_ATTEMPT_MS). */
|
|
36
36
|
export const READ_TIMEOUT_MS = 10_000;
|
|
37
37
|
export const WRITE_TIMEOUT_MS = 75_000;
|
|
@@ -137,6 +137,12 @@ function need(args, key) {
|
|
|
137
137
|
throw new Error(`${key} is required`);
|
|
138
138
|
return v;
|
|
139
139
|
}
|
|
140
|
+
function rejectUnknown(args, allowed) {
|
|
141
|
+
const accepted = new Set(allowed);
|
|
142
|
+
const unknown = Object.keys(args).filter(key => !accepted.has(key));
|
|
143
|
+
if (unknown.length)
|
|
144
|
+
throw new Error(`unknown ${unknown.length === 1 ? 'field' : 'fields'}: ${unknown.join(', ')}`);
|
|
145
|
+
}
|
|
140
146
|
/** Build the tool set against a given relay transport. */
|
|
141
147
|
export function createTools(call) {
|
|
142
148
|
return [
|
|
@@ -371,7 +377,7 @@ export function createTools(call) {
|
|
|
371
377
|
},
|
|
372
378
|
{
|
|
373
379
|
name: 'list_roles',
|
|
374
|
-
description: 'List the relay’s picker-backed cross-provider roles. A role marked invalid cannot be
|
|
380
|
+
description: 'List the relay’s picker-backed cross-provider roles. A role marked invalid cannot be used by a new Workflow until its exact model is selected. Active Workflows keep their frozen snapshot when this configuration changes.',
|
|
375
381
|
inputSchema: { type: 'object', properties: {} },
|
|
376
382
|
run: async () => {
|
|
377
383
|
const data = await call(routes.roles.path());
|
|
@@ -386,109 +392,41 @@ export function createTools(call) {
|
|
|
386
392
|
return lines.join('\n') || 'no delegated roles are configured';
|
|
387
393
|
}
|
|
388
394
|
},
|
|
389
|
-
{
|
|
390
|
-
name: 'set_role',
|
|
391
|
-
description: 'Set one cross-provider delegated role using an exact label from list_models. Effort and Fast are available only when Conductor exposes those controls for the selected provider; omit them for Cursor and OpenCode. This changes relay config only and does not drive Conductor. Plan mode is intentionally not a role option.',
|
|
392
|
-
inputSchema: {
|
|
393
|
-
type: 'object',
|
|
394
|
-
properties: {
|
|
395
|
-
role: { type: 'string', description: 'Lowercase role name, for example exploration.' },
|
|
396
|
-
model: { type: 'string', description: 'Exact picker label from list_models.' },
|
|
397
|
-
effort: {
|
|
398
|
-
type: 'string',
|
|
399
|
-
enum: ['none', 'low', 'medium', 'high', 'xhigh', 'max', 'ultracode'],
|
|
400
|
-
description: 'Optional Claude/Codex reasoning control. Omit for Cursor and OpenCode.'
|
|
401
|
-
},
|
|
402
|
-
fast: { type: 'boolean', description: 'Optional Claude/Codex Fast control.' },
|
|
403
|
-
preamble: { type: 'string', description: 'Optional instructions prepended to each delegated task.' }
|
|
404
|
-
},
|
|
405
|
-
required: ['role', 'model']
|
|
406
|
-
},
|
|
407
|
-
run: async (args) => {
|
|
408
|
-
const name = need(args, 'role');
|
|
409
|
-
const model = need(args, 'model');
|
|
410
|
-
const effort = str(args.effort);
|
|
411
|
-
const efforts = ['none', 'low', 'medium', 'high', 'xhigh', 'max', 'ultracode'];
|
|
412
|
-
if (effort && !efforts.includes(effort))
|
|
413
|
-
throw new Error('effort is invalid');
|
|
414
|
-
const agentType = modelAgentType(model);
|
|
415
|
-
if (effort && agentType && !agentTypeCanExposeEffort(agentType)) {
|
|
416
|
-
throw new Error('the selected provider has no Conductor reasoning control; omit effort');
|
|
417
|
-
}
|
|
418
|
-
if (typeof args.fast === 'boolean' && agentType && !agentTypeCanExposeFastMode(agentType)) {
|
|
419
|
-
throw new Error('the selected provider has no Conductor Fast control; omit fast');
|
|
420
|
-
}
|
|
421
|
-
const current = await call(routes.roles.path());
|
|
422
|
-
const prior = current.roles[name];
|
|
423
|
-
const preserveEffort = agentTypeCanExposeEffort(agentType);
|
|
424
|
-
const preserveFast = agentTypeCanExposeFastMode(agentType);
|
|
425
|
-
const role = {
|
|
426
|
-
model,
|
|
427
|
-
...(effort
|
|
428
|
-
? { effort: effort }
|
|
429
|
-
: preserveEffort && prior?.effort
|
|
430
|
-
? { effort: prior.effort }
|
|
431
|
-
: {}),
|
|
432
|
-
...(typeof args.fast === 'boolean'
|
|
433
|
-
? { fast: args.fast }
|
|
434
|
-
: preserveFast && prior?.fast !== undefined
|
|
435
|
-
? { fast: prior.fast }
|
|
436
|
-
: {}),
|
|
437
|
-
...(typeof args.preamble === 'string'
|
|
438
|
-
? { preamble: args.preamble }
|
|
439
|
-
: prior?.preamble !== undefined
|
|
440
|
-
? { preamble: prior.preamble }
|
|
441
|
-
: {})
|
|
442
|
-
};
|
|
443
|
-
const config = { version: 1, roles: { ...current.roles, [name]: role } };
|
|
444
|
-
const result = await call(routes.updateRoles.path(), {
|
|
445
|
-
method: routes.updateRoles.method,
|
|
446
|
-
body: config
|
|
447
|
-
});
|
|
448
|
-
if (!result.ok)
|
|
449
|
-
throw new Error(`${result.error.code}: ${result.error.message}`);
|
|
450
|
-
return `${name}: ${[role.model, role.effort, role.fast ? 'fast' : null].filter(Boolean).join(' · ')}`;
|
|
451
|
-
}
|
|
452
|
-
},
|
|
453
395
|
{
|
|
454
396
|
name: 'delegate_task',
|
|
455
|
-
description: 'Queue
|
|
397
|
+
description: 'Queue a tracked child in the Workflow this root already belongs to. Use only the workflow id and current phase capability delivered in the root’s private orchestration envelope. The relay owns the frozen child settings, transcript boundary, Baton routing, and later Mac UI work. Returns immediately with a delegation id; use list_delegations to observe it. This tool cannot start or recover a Workflow.',
|
|
456
398
|
inputSchema: {
|
|
457
399
|
type: 'object',
|
|
458
400
|
properties: {
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
role: { type: 'string', description: 'Exact role name from list_roles.' },
|
|
462
|
-
prompt: { type: 'string' },
|
|
463
|
-
through: { type: 'string', description: 'Optional cursor from read_chat; later rows are omitted.' },
|
|
464
|
-
include_thinking: { type: 'boolean', description: 'Include parent reasoning in the handoff (default true).' },
|
|
465
|
-
return_mode: {
|
|
401
|
+
workflow_id: { type: 'string', description: 'Workflow id from the private root envelope.' },
|
|
402
|
+
phase_capability: {
|
|
466
403
|
type: 'string',
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
}
|
|
404
|
+
description: 'Opaque current-phase capability from that same envelope; never copy it elsewhere.'
|
|
405
|
+
},
|
|
406
|
+
session_id: { type: 'string', description: 'The persisted Workflow root session.' },
|
|
407
|
+
role: { type: 'string', enum: ['exploration', 'implementation'] },
|
|
408
|
+
prompt: { type: 'string', description: 'The independent question or implementation brief.' }
|
|
470
409
|
},
|
|
471
|
-
required: ['session_id', 'role', 'prompt']
|
|
410
|
+
required: ['workflow_id', 'phase_capability', 'session_id', 'role', 'prompt'],
|
|
411
|
+
additionalProperties: false
|
|
472
412
|
},
|
|
473
413
|
run: async (args) => {
|
|
414
|
+
rejectUnknown(args, ['workflow_id', 'phase_capability', 'session_id', 'role', 'prompt']);
|
|
415
|
+
const workflowId = need(args, 'workflow_id');
|
|
474
416
|
const sessionId = need(args, 'session_id');
|
|
475
|
-
const
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
throughRowid,
|
|
489
|
-
includeThinking: args.include_thinking === undefined ? undefined : args.include_thinking === true,
|
|
490
|
-
returnMode
|
|
491
|
-
}
|
|
417
|
+
const role = need(args, 'role');
|
|
418
|
+
if (role !== 'exploration' && role !== 'implementation')
|
|
419
|
+
throw new Error('role is invalid');
|
|
420
|
+
const body = {
|
|
421
|
+
workflow_id: workflowId,
|
|
422
|
+
phase_capability: need(args, 'phase_capability'),
|
|
423
|
+
session_id: sessionId,
|
|
424
|
+
role,
|
|
425
|
+
prompt: need(args, 'prompt')
|
|
426
|
+
};
|
|
427
|
+
const result = await call(routes.workflowDelegation.path(workflowId), {
|
|
428
|
+
method: routes.workflowDelegation.method,
|
|
429
|
+
body
|
|
492
430
|
});
|
|
493
431
|
if (!result.ok)
|
|
494
432
|
throw new Error(`${result.error.code}: ${result.error.message}`);
|
|
@@ -517,24 +455,6 @@ export function createTools(call) {
|
|
|
517
455
|
.join('\n');
|
|
518
456
|
}
|
|
519
457
|
},
|
|
520
|
-
{
|
|
521
|
-
name: 'dismiss_delegation',
|
|
522
|
-
description: 'Dismiss one failed delegation job. Its parent/child chats and role identity remain.',
|
|
523
|
-
inputSchema: {
|
|
524
|
-
type: 'object',
|
|
525
|
-
properties: { delegation_id: { type: 'string' } },
|
|
526
|
-
required: ['delegation_id']
|
|
527
|
-
},
|
|
528
|
-
run: async (args) => {
|
|
529
|
-
const id = need(args, 'delegation_id');
|
|
530
|
-
const result = await call(routes.dismissDelegation.path(id), {
|
|
531
|
-
method: routes.dismissDelegation.method
|
|
532
|
-
});
|
|
533
|
-
if (!result.ok)
|
|
534
|
-
throw new Error(`${result.error.code}: ${result.error.message}`);
|
|
535
|
-
return `dismissed delegation ${result.delegationId}`;
|
|
536
|
-
}
|
|
537
|
-
},
|
|
538
458
|
{
|
|
539
459
|
name: 'workspace_diff',
|
|
540
460
|
description: 'The git diff of a live workspace against its target branch, untracked files included.',
|
|
@@ -584,7 +504,7 @@ export function createTools(call) {
|
|
|
584
504
|
},
|
|
585
505
|
{
|
|
586
506
|
name: 'create_workspace',
|
|
587
|
-
description: 'Start
|
|
507
|
+
description: 'Start an ordinary new Conductor workspace in a repo, optionally with a first prompt and agent settings. The workspace starts from a Conductor deep link, so creation itself needs no Accessibility. The relay applies requested model, effort, plan, and fast settings through Conductor’s UI after it creates the first chat and before it delivers the prompt. This utility cannot start or join a Workflow. Returns as soon as the workspace row exists (~2s), before the worktree is built.',
|
|
588
508
|
inputSchema: {
|
|
589
509
|
type: 'object',
|
|
590
510
|
properties: {
|
|
@@ -601,10 +521,6 @@ export function createTools(call) {
|
|
|
601
521
|
},
|
|
602
522
|
plan: { type: 'boolean', description: 'Start the first chat in Conductor’s Plan mode.' },
|
|
603
523
|
fast: { type: 'boolean', description: 'Set Fast mode before the first prompt.' },
|
|
604
|
-
workflow: {
|
|
605
|
-
type: 'boolean',
|
|
606
|
-
description: 'Start the first prompt as a delegated workflow rooted in the configured planning role. Requires a prompt and cannot be combined with model, effort, plan, or fast. Uses ordinary chat mode, never Conductor Plan mode.'
|
|
607
|
-
},
|
|
608
524
|
wait_for_send: {
|
|
609
525
|
type: 'boolean',
|
|
610
526
|
description: 'Block until the first prompt is actually delivered (tens of seconds, and longer behind other queued sends). Default false.'
|
|
@@ -614,39 +530,31 @@ export function createTools(call) {
|
|
|
614
530
|
description: "Send the first prompt without waiting for the worktree to finish building, which is how Conductor's own New workspace box behaves. Default true. Pass false only when the agent's first move needs what the repo's setup script installs."
|
|
615
531
|
}
|
|
616
532
|
},
|
|
617
|
-
required: ['repo']
|
|
533
|
+
required: ['repo'],
|
|
534
|
+
additionalProperties: false
|
|
618
535
|
},
|
|
619
536
|
run: async (args) => {
|
|
537
|
+
rejectUnknown(args, ['repo', 'prompt', 'model', 'effort', 'plan', 'fast', 'wait_for_send', 'send_immediately']);
|
|
620
538
|
const repo = need(args, 'repo');
|
|
621
539
|
const prompt = str(args.prompt);
|
|
622
540
|
const model = str(args.model);
|
|
623
541
|
const effort = str(args.effort);
|
|
624
542
|
const plan = typeof args.plan === 'boolean' ? args.plan : undefined;
|
|
625
543
|
const fast = typeof args.fast === 'boolean' ? args.fast : undefined;
|
|
626
|
-
const workflow = args.workflow === true;
|
|
627
544
|
const explicitAgent = model !== undefined || effort !== undefined || plan !== undefined || fast !== undefined;
|
|
628
|
-
|
|
629
|
-
throw new Error('workflow cannot be combined with model, effort, plan, or fast');
|
|
630
|
-
}
|
|
631
|
-
if (workflow && !prompt)
|
|
632
|
-
throw new Error('workflow needs a first prompt');
|
|
633
|
-
const configured = workflow || explicitAgent;
|
|
545
|
+
const configured = explicitAgent;
|
|
634
546
|
const send = args.wait_for_send === true;
|
|
635
547
|
// Default on, matching the relay: an omitted flag must never mean "wait".
|
|
636
548
|
const sendImmediately = args.send_immediately !== false;
|
|
637
549
|
const data = await call(routes.createWorkspace.path(), {
|
|
638
550
|
method: routes.createWorkspace.method,
|
|
639
|
-
body:
|
|
640
|
-
? { repo, prompt, workflow: true, send, sendImmediately }
|
|
641
|
-
: { repo, prompt, model, effort, plan, fast, send, sendImmediately },
|
|
551
|
+
body: { repo, prompt, model, effort, plan, fast, send, sendImmediately },
|
|
642
552
|
timeoutMs: send ? WRITE_TIMEOUT_MS : 30_000
|
|
643
553
|
});
|
|
644
554
|
const lines = [
|
|
645
555
|
`created ${data.workspace ? workspaceTitle(data.workspace) : data.workspaceId}`,
|
|
646
556
|
`workspace_id: ${data.workspaceId}`
|
|
647
557
|
];
|
|
648
|
-
if (data.workflow)
|
|
649
|
-
lines.push(`workflow root: ${data.workflow.role} · ${data.workflow.model}`);
|
|
650
558
|
if (data.warning)
|
|
651
559
|
lines.push(`! ${data.warning}`);
|
|
652
560
|
else if (data.pendingPrompt && !data.sent)
|
|
@@ -671,9 +579,11 @@ export function createTools(call) {
|
|
|
671
579
|
},
|
|
672
580
|
text: { type: 'string' }
|
|
673
581
|
},
|
|
674
|
-
required: ['session_id', 'text']
|
|
582
|
+
required: ['session_id', 'text'],
|
|
583
|
+
additionalProperties: false
|
|
675
584
|
},
|
|
676
585
|
run: async (args) => {
|
|
586
|
+
rejectUnknown(args, ['session_id', 'workspace_id', 'text']);
|
|
677
587
|
const sessionId = need(args, 'session_id');
|
|
678
588
|
const text = need(args, 'text');
|
|
679
589
|
const data = await call(routes.sendPrompt.path(sessionId), {
|
|
@@ -42,13 +42,15 @@ export class ModelCache {
|
|
|
42
42
|
return;
|
|
43
43
|
const key = group(agentType ?? '');
|
|
44
44
|
const selectedDefault = clean(defaultModel ? [defaultModel] : [])[0] ?? this.defaultModel();
|
|
45
|
+
const now = Date.now();
|
|
45
46
|
const entry = {
|
|
46
47
|
agentType: key,
|
|
47
48
|
// A live menu is authoritative. Replacing its group drops a model Conductor
|
|
48
49
|
// stopped offering instead of leaving a stale choice in a later workspace.
|
|
49
50
|
models: next,
|
|
50
51
|
defaultModel: selectedDefault,
|
|
51
|
-
|
|
52
|
+
snapshotAt: now,
|
|
53
|
+
updatedAt: now
|
|
52
54
|
};
|
|
53
55
|
this.entries = [...this.entries.filter(existing => existing.agentType !== key), entry]
|
|
54
56
|
.map(existing => ({ ...existing, defaultModel: selectedDefault ?? existing.defaultModel }))
|
|
@@ -65,6 +67,9 @@ export class ModelCache {
|
|
|
65
67
|
agentType: key,
|
|
66
68
|
models: clean([...(current?.models ?? []), model]),
|
|
67
69
|
defaultModel: current?.defaultModel ?? this.defaultModel(),
|
|
70
|
+
// Selecting one row proves that row, not the rest of the picker. Preserve
|
|
71
|
+
// an existing whole-menu observation but mark a brand-new group partial.
|
|
72
|
+
snapshotAt: current ? (current.snapshotAt === undefined ? current.updatedAt : current.snapshotAt) : null,
|
|
68
73
|
updatedAt: Date.now()
|
|
69
74
|
};
|
|
70
75
|
this.entries = [...this.entries.filter(existing => existing.agentType !== key), entry].sort((a, b) => a.agentType.localeCompare(b.agentType));
|
|
@@ -85,12 +90,22 @@ export class ModelCache {
|
|
|
85
90
|
return [];
|
|
86
91
|
return parsed
|
|
87
92
|
.filter(entry => typeof entry?.agentType === 'string' && Array.isArray(entry.models))
|
|
88
|
-
.map(entry =>
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
93
|
+
.map(entry => {
|
|
94
|
+
const snapshotAt = Object.hasOwn(entry, 'snapshotAt')
|
|
95
|
+
? entry.snapshotAt === null
|
|
96
|
+
? null
|
|
97
|
+
: Number.isFinite(entry.snapshotAt)
|
|
98
|
+
? Number(entry.snapshotAt)
|
|
99
|
+
: null
|
|
100
|
+
: undefined;
|
|
101
|
+
return {
|
|
102
|
+
agentType: group(entry.agentType),
|
|
103
|
+
models: clean(entry.models.filter((model) => typeof model === 'string')),
|
|
104
|
+
defaultModel: typeof entry.defaultModel === 'string' ? clean([entry.defaultModel])[0] || undefined : undefined,
|
|
105
|
+
...(snapshotAt === undefined ? {} : { snapshotAt }),
|
|
106
|
+
updatedAt: Number.isFinite(entry.updatedAt) ? entry.updatedAt : 0
|
|
107
|
+
};
|
|
108
|
+
})
|
|
94
109
|
.filter(entry => entry.models.length);
|
|
95
110
|
}
|
|
96
111
|
catch {
|