@love-moon/conductor-cli 0.7.7 → 0.8.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/CHANGELOG.md +19 -0
- package/bin/conductor-task.js +124 -0
- package/bin/conductor.js +3 -2
- package/package.json +5 -5
- package/src/daemon.js +199 -0
- package/src/entity-helpers.js +27 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# @love-moon/conductor-cli
|
|
2
2
|
|
|
3
|
+
## 0.8.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 959dd1d: Add project-configured worker and reviewer task groups, task-group discovery in
|
|
8
|
+
the SDK and `conductor task group`, and a lightweight daemon protocol for
|
|
9
|
+
refreshing the project agent registry.
|
|
10
|
+
- fe76139: Add `conductor task create` for creating app tasks with title, prompt, backend,
|
|
11
|
+
project resolution, and optional parent task-card grouping. App tasks now
|
|
12
|
+
require an online compatible daemon, and grouping results are exposed to
|
|
13
|
+
callers so partial success is visible without retrying task creation.
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- Updated dependencies [959dd1d]
|
|
18
|
+
- Updated dependencies [fe76139]
|
|
19
|
+
- @love-moon/conductor-sdk@0.8.0
|
|
20
|
+
- @love-moon/ai-sdk@0.8.0
|
|
21
|
+
|
|
3
22
|
## 0.7.7
|
|
4
23
|
|
|
5
24
|
### Patch Changes
|
package/bin/conductor-task.js
CHANGED
|
@@ -5,7 +5,10 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Subcommands:
|
|
7
7
|
* list [--project ...] [--issue <id>] [--status ...]
|
|
8
|
+
* create --title <title> [--prompt <prompt>] [--backend <backend>]
|
|
9
|
+
* [--parent-task-id <id>] [--project ...]
|
|
8
10
|
* show <id>
|
|
11
|
+
* group [<id>]
|
|
9
12
|
* send <id> [<message>] [--stdin] [--from-file FILE] [--metadata-json '{...}']
|
|
10
13
|
* insert <id> [<message>] [--stdin] [--from-file FILE] [--target-reply-to <msg-id>]
|
|
11
14
|
* messages <id> [--limit N] [--before <msg-id>]
|
|
@@ -63,6 +66,7 @@ function taskAsObject(task) {
|
|
|
63
66
|
sessionId: task.sessionId,
|
|
64
67
|
createdAt: task.createdAt,
|
|
65
68
|
updatedAt: task.updatedAt,
|
|
69
|
+
grouping: task.grouping ?? undefined,
|
|
66
70
|
};
|
|
67
71
|
}
|
|
68
72
|
|
|
@@ -243,6 +247,54 @@ async function handleList(argv, deps) {
|
|
|
243
247
|
return EXIT.OK;
|
|
244
248
|
}
|
|
245
249
|
|
|
250
|
+
async function handleCreate(argv, deps) {
|
|
251
|
+
const apis = await buildApis(deps);
|
|
252
|
+
const project = await resolveProject(apis, {
|
|
253
|
+
env: deps.env,
|
|
254
|
+
cwd: deps.cwd,
|
|
255
|
+
project: argv.project,
|
|
256
|
+
});
|
|
257
|
+
const title = String(argv.title ?? "").trim();
|
|
258
|
+
if (!title) {
|
|
259
|
+
const err = new Error("--title must not be empty");
|
|
260
|
+
err.code = "ARGS";
|
|
261
|
+
throw err;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const body = {
|
|
265
|
+
projectId: project.id,
|
|
266
|
+
title,
|
|
267
|
+
taskType: "ai_task",
|
|
268
|
+
...(argv.prompt !== undefined ? { initialContent: String(argv.prompt) } : {}),
|
|
269
|
+
...(argv.backend ? { backendType: String(argv.backend) } : {}),
|
|
270
|
+
...(argv.parentTaskId ? { parentTaskId: String(argv.parentTaskId) } : {}),
|
|
271
|
+
metadata: buildAuditMetadata(deps.env),
|
|
272
|
+
};
|
|
273
|
+
if (argv.dryRun) {
|
|
274
|
+
emitDryRun(
|
|
275
|
+
deps.stdout,
|
|
276
|
+
argv.json,
|
|
277
|
+
makeDryRunPayload("POST", `${buildBaseUrl(apis.config)}/api/tasks`, body),
|
|
278
|
+
);
|
|
279
|
+
return EXIT.OK;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const created = await apis.tasks.createTask(body);
|
|
283
|
+
const obj = taskAsObject(created);
|
|
284
|
+
if (argv.json) {
|
|
285
|
+
printJson(deps.stdout, obj);
|
|
286
|
+
return EXIT.OK;
|
|
287
|
+
}
|
|
288
|
+
printPretty(deps.stdout, `Created app task ${obj.id}: ${obj.title}`);
|
|
289
|
+
if (obj.grouping?.grouped === false) {
|
|
290
|
+
printPretty(
|
|
291
|
+
deps.stderr,
|
|
292
|
+
`Warning: ${obj.grouping.warning || `task was not grouped with ${obj.grouping.parentTaskId}`}. The task itself was created successfully.`,
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
return EXIT.OK;
|
|
296
|
+
}
|
|
297
|
+
|
|
246
298
|
async function handleShow(argv, deps) {
|
|
247
299
|
const apis = await buildApis(deps);
|
|
248
300
|
const task = await apis.tasks.getTask(argv.id);
|
|
@@ -267,6 +319,45 @@ async function handleShow(argv, deps) {
|
|
|
267
319
|
return EXIT.OK;
|
|
268
320
|
}
|
|
269
321
|
|
|
322
|
+
async function handleGroup(argv, deps) {
|
|
323
|
+
const apis = await buildApis(deps);
|
|
324
|
+
const taskId =
|
|
325
|
+
(argv.id && String(argv.id).trim()) ||
|
|
326
|
+
(deps.env.CONDUCTOR_TASK_ID && String(deps.env.CONDUCTOR_TASK_ID).trim());
|
|
327
|
+
if (!taskId) {
|
|
328
|
+
const err = new Error(
|
|
329
|
+
"No task id: pass <id> or run inside a task (CONDUCTOR_TASK_ID)",
|
|
330
|
+
);
|
|
331
|
+
err.statusCode = 400;
|
|
332
|
+
throw err;
|
|
333
|
+
}
|
|
334
|
+
const group = await apis.tasks.getTaskGroup(taskId);
|
|
335
|
+
if (argv.json) {
|
|
336
|
+
printJson(deps.stdout, group);
|
|
337
|
+
return EXIT.OK;
|
|
338
|
+
}
|
|
339
|
+
if (!group.groupId) {
|
|
340
|
+
printPretty(deps.stdout, "(task is not in a group)");
|
|
341
|
+
return EXIT.OK;
|
|
342
|
+
}
|
|
343
|
+
printPretty(deps.stdout, `group ${group.groupId}`);
|
|
344
|
+
printPretty(
|
|
345
|
+
deps.stdout,
|
|
346
|
+
`${pad("ROLE", 10)} ${pad("TASK ID", 24)} ${pad("AGENT", 20)} STATUS`,
|
|
347
|
+
);
|
|
348
|
+
for (const member of group.members) {
|
|
349
|
+
const selfMark = member.isSelf ? " (you)" : "";
|
|
350
|
+
printPretty(
|
|
351
|
+
deps.stdout,
|
|
352
|
+
`${pad(member.role || "?", 10)} ${pad(member.taskId, 24)} ${pad(
|
|
353
|
+
member.agent || "-",
|
|
354
|
+
20,
|
|
355
|
+
)} ${member.status || ""}${selfMark}`,
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
return EXIT.OK;
|
|
359
|
+
}
|
|
360
|
+
|
|
270
361
|
async function handleSend(argv, deps) {
|
|
271
362
|
const apis = await buildApis(deps);
|
|
272
363
|
const content = readMessageInput({
|
|
@@ -485,6 +576,31 @@ export async function main(argvInput = hideBin(process.argv), deps = {}) {
|
|
|
485
576
|
exitCode = await handleList(argv, { ...handlerDeps, configFile: argv.configFile });
|
|
486
577
|
},
|
|
487
578
|
)
|
|
579
|
+
.command(
|
|
580
|
+
"create",
|
|
581
|
+
"Create a new app task through the frontend task pipeline",
|
|
582
|
+
(cmd) => cmd
|
|
583
|
+
.option("title", {
|
|
584
|
+
type: "string",
|
|
585
|
+
demandOption: true,
|
|
586
|
+
describe: "Task title",
|
|
587
|
+
})
|
|
588
|
+
.option("prompt", {
|
|
589
|
+
type: "string",
|
|
590
|
+
describe: "Initial user prompt",
|
|
591
|
+
})
|
|
592
|
+
.option("backend", {
|
|
593
|
+
type: "string",
|
|
594
|
+
describe: "AI backend type, for example codex or claude",
|
|
595
|
+
})
|
|
596
|
+
.option("parent-task-id", {
|
|
597
|
+
type: "string",
|
|
598
|
+
describe: "Display the new task in the same task-card group as this task",
|
|
599
|
+
}),
|
|
600
|
+
async (argv) => {
|
|
601
|
+
exitCode = await handleCreate(argv, { ...handlerDeps, configFile: argv.configFile });
|
|
602
|
+
},
|
|
603
|
+
)
|
|
488
604
|
.command(
|
|
489
605
|
"show <id>",
|
|
490
606
|
"Show one task's detail",
|
|
@@ -493,6 +609,14 @@ export async function main(argvInput = hideBin(process.argv), deps = {}) {
|
|
|
493
609
|
exitCode = await handleShow(argv, { ...handlerDeps, configFile: argv.configFile });
|
|
494
610
|
},
|
|
495
611
|
)
|
|
612
|
+
.command(
|
|
613
|
+
"group [id]",
|
|
614
|
+
"Show the multi-agent group a task belongs to (defaults to $CONDUCTOR_TASK_ID)",
|
|
615
|
+
(cmd) => cmd.positional("id", { type: "string" }),
|
|
616
|
+
async (argv) => {
|
|
617
|
+
exitCode = await handleGroup(argv, { ...handlerDeps, configFile: argv.configFile });
|
|
618
|
+
},
|
|
619
|
+
)
|
|
496
620
|
.command(
|
|
497
621
|
"send <id> [message]",
|
|
498
622
|
"Send a user message into a running task",
|
package/bin/conductor.js
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* serve-ai - Start an OpenAI-compatible local AI server
|
|
15
15
|
* project - Manage Conductor projects (list/show/create/...)
|
|
16
16
|
* issue - Manage issues (list/show/create/update/start/done)
|
|
17
|
-
* task - Manage tasks (list/show/send/messages/schedule)
|
|
17
|
+
* task - Manage tasks (create/list/show/send/messages/schedule)
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
20
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
@@ -134,7 +134,7 @@ Subcommands:
|
|
|
134
134
|
serve-ai Start an OpenAI-compatible local AI server
|
|
135
135
|
project Manage Conductor projects (list/show/create/...)
|
|
136
136
|
issue Manage issues (list/show/create/update/start/done)
|
|
137
|
-
task Manage tasks (list/show/send/messages/schedule)
|
|
137
|
+
task Manage tasks (create/list/show/send/messages/schedule)
|
|
138
138
|
|
|
139
139
|
Options:
|
|
140
140
|
-h, --help Show this help message
|
|
@@ -156,6 +156,7 @@ Examples:
|
|
|
156
156
|
conductor update
|
|
157
157
|
conductor project list
|
|
158
158
|
conductor issue create --title "Refactor module" --priority P2
|
|
159
|
+
conductor task create --title "Refactor module" --prompt "Extract the parser" --backend codex
|
|
159
160
|
conductor task send <task-id> "please add a unit test"
|
|
160
161
|
conductor task schedule create <task-id> "follow up" --delay 10m
|
|
161
162
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@love-moon/conductor-cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"gitCommitId": "
|
|
3
|
+
"version": "0.8.0",
|
|
4
|
+
"gitCommitId": "04d4f62",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/lovemoon-ai/conductor.git"
|
|
@@ -24,8 +24,8 @@
|
|
|
24
24
|
"test": "node --test test/*.test.js"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@love-moon/ai-sdk": "0.
|
|
28
|
-
"@love-moon/conductor-sdk": "0.
|
|
27
|
+
"@love-moon/ai-sdk": "0.8.0",
|
|
28
|
+
"@love-moon/conductor-sdk": "0.8.0",
|
|
29
29
|
"@github/copilot-sdk": "^0.3.0",
|
|
30
30
|
"chrome-launcher": "^1.2.1",
|
|
31
31
|
"chrome-remote-interface": "^0.33.0",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
},
|
|
39
39
|
"optionalDependencies": {
|
|
40
40
|
"@roamhq/wrtc": "^0.10.0",
|
|
41
|
-
"@love-moon/chat-web": "0.
|
|
41
|
+
"@love-moon/chat-web": "0.8.0"
|
|
42
42
|
},
|
|
43
43
|
"pnpm": {
|
|
44
44
|
"onlyBuiltDependencies": [
|
package/src/daemon.js
CHANGED
|
@@ -1843,6 +1843,14 @@ export function startDaemon(config = {}, deps = {}) {
|
|
|
1843
1843
|
"# (svg/png/jpg/gif/webp/ico/avif) relative to this .conductor/ directory.",
|
|
1844
1844
|
"# Remove this line to use the default folder icon.",
|
|
1845
1845
|
"",
|
|
1846
|
+
"# Optional agent registry used by the task-composer worker/reviewer picker.",
|
|
1847
|
+
"# Agent docs are workspace-relative paths; backend is optional.",
|
|
1848
|
+
"# agents:",
|
|
1849
|
+
"# feature-dev:",
|
|
1850
|
+
"# doc: claw/agents/feature-dev.md",
|
|
1851
|
+
"# description: Implements features end to end.",
|
|
1852
|
+
"# backend: codex",
|
|
1853
|
+
"",
|
|
1846
1854
|
"worktree:",
|
|
1847
1855
|
" sync_branch: false",
|
|
1848
1856
|
" sync_submodules: true",
|
|
@@ -1856,6 +1864,13 @@ export function startDaemon(config = {}, deps = {}) {
|
|
|
1856
1864
|
].join("\n");
|
|
1857
1865
|
|
|
1858
1866
|
const MAX_PROJECT_ICON_IMAGE_BYTES = 128 * 1024;
|
|
1867
|
+
const MAX_PROJECT_AGENT_ENTRIES = 64;
|
|
1868
|
+
const MAX_PROJECT_AGENT_NAME_LENGTH = 64;
|
|
1869
|
+
const MAX_PROJECT_AGENT_DOC_LENGTH = 512;
|
|
1870
|
+
const MAX_PROJECT_AGENT_DESCRIPTION_LENGTH = 512;
|
|
1871
|
+
const MAX_PROJECT_AGENT_BACKEND_LENGTH = 64;
|
|
1872
|
+
const PROJECT_AGENT_NAME_RE = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
1873
|
+
const PROJECT_AGENT_BACKEND_RE = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
1859
1874
|
const PROJECT_ICON_MIME_BY_EXTENSION = {
|
|
1860
1875
|
".svg": "image/svg+xml",
|
|
1861
1876
|
".png": "image/png",
|
|
@@ -1954,6 +1969,118 @@ export function startDaemon(config = {}, deps = {}) {
|
|
|
1954
1969
|
return null;
|
|
1955
1970
|
}
|
|
1956
1971
|
|
|
1972
|
+
function normalizeProjectAgentSetting(name, value) {
|
|
1973
|
+
const normalizedName = normalizeOptionalString(name);
|
|
1974
|
+
if (
|
|
1975
|
+
!normalizedName ||
|
|
1976
|
+
normalizedName.length > MAX_PROJECT_AGENT_NAME_LENGTH ||
|
|
1977
|
+
!PROJECT_AGENT_NAME_RE.test(normalizedName)
|
|
1978
|
+
) {
|
|
1979
|
+
return null;
|
|
1980
|
+
}
|
|
1981
|
+
|
|
1982
|
+
let docValue = null;
|
|
1983
|
+
let descriptionValue = null;
|
|
1984
|
+
let backendValue = null;
|
|
1985
|
+
if (typeof value === "string") {
|
|
1986
|
+
docValue = value;
|
|
1987
|
+
} else if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
1988
|
+
docValue = value.doc ?? value.path;
|
|
1989
|
+
descriptionValue = value.description;
|
|
1990
|
+
backendValue = value.backend;
|
|
1991
|
+
} else {
|
|
1992
|
+
return null;
|
|
1993
|
+
}
|
|
1994
|
+
|
|
1995
|
+
const doc = normalizeOptionalString(docValue);
|
|
1996
|
+
if (
|
|
1997
|
+
!doc ||
|
|
1998
|
+
doc.length > MAX_PROJECT_AGENT_DOC_LENGTH ||
|
|
1999
|
+
path.isAbsolute(doc) ||
|
|
2000
|
+
/^[A-Za-z]:[\\/]/.test(doc) ||
|
|
2001
|
+
/^[/\\]{2}/.test(doc)
|
|
2002
|
+
) {
|
|
2003
|
+
return null;
|
|
2004
|
+
}
|
|
2005
|
+
const normalizedDoc = path.posix.normalize(doc.split("\\").join("/"));
|
|
2006
|
+
if (normalizedDoc === ".." || normalizedDoc.startsWith("../")) {
|
|
2007
|
+
return null;
|
|
2008
|
+
}
|
|
2009
|
+
|
|
2010
|
+
const description = normalizeOptionalString(descriptionValue);
|
|
2011
|
+
const backend = normalizeOptionalString(backendValue)?.toLowerCase() || null;
|
|
2012
|
+
return {
|
|
2013
|
+
name: normalizedName,
|
|
2014
|
+
doc,
|
|
2015
|
+
description:
|
|
2016
|
+
description && description.length <= MAX_PROJECT_AGENT_DESCRIPTION_LENGTH
|
|
2017
|
+
? description
|
|
2018
|
+
: null,
|
|
2019
|
+
backend:
|
|
2020
|
+
backend &&
|
|
2021
|
+
backend.length <= MAX_PROJECT_AGENT_BACKEND_LENGTH &&
|
|
2022
|
+
PROJECT_AGENT_BACKEND_RE.test(backend)
|
|
2023
|
+
? backend
|
|
2024
|
+
: null,
|
|
2025
|
+
};
|
|
2026
|
+
}
|
|
2027
|
+
|
|
2028
|
+
function normalizeProjectAgentsSetting(agentsNode) {
|
|
2029
|
+
if (!agentsNode || typeof agentsNode !== "object") {
|
|
2030
|
+
return [];
|
|
2031
|
+
}
|
|
2032
|
+
const result = [];
|
|
2033
|
+
const seen = new Set();
|
|
2034
|
+
const push = (entry) => {
|
|
2035
|
+
if (
|
|
2036
|
+
entry &&
|
|
2037
|
+
result.length < MAX_PROJECT_AGENT_ENTRIES &&
|
|
2038
|
+
!seen.has(entry.name)
|
|
2039
|
+
) {
|
|
2040
|
+
seen.add(entry.name);
|
|
2041
|
+
result.push(entry);
|
|
2042
|
+
}
|
|
2043
|
+
};
|
|
2044
|
+
|
|
2045
|
+
if (Array.isArray(agentsNode)) {
|
|
2046
|
+
for (const item of agentsNode) {
|
|
2047
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) {
|
|
2048
|
+
continue;
|
|
2049
|
+
}
|
|
2050
|
+
const keys = Object.keys(item);
|
|
2051
|
+
if (keys.length === 1) {
|
|
2052
|
+
push(normalizeProjectAgentSetting(keys[0], item[keys[0]]));
|
|
2053
|
+
} else if (Object.prototype.hasOwnProperty.call(item, "name")) {
|
|
2054
|
+
push(normalizeProjectAgentSetting(item.name, item));
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
2057
|
+
return result;
|
|
2058
|
+
}
|
|
2059
|
+
|
|
2060
|
+
for (const [name, value] of Object.entries(agentsNode)) {
|
|
2061
|
+
push(normalizeProjectAgentSetting(name, value));
|
|
2062
|
+
}
|
|
2063
|
+
return result;
|
|
2064
|
+
}
|
|
2065
|
+
|
|
2066
|
+
function readProjectAgentsSetting(projectWorkspacePath) {
|
|
2067
|
+
for (const settingsPath of getProjectSettingsCandidates(projectWorkspacePath)) {
|
|
2068
|
+
if (!existsSyncFn(settingsPath)) {
|
|
2069
|
+
continue;
|
|
2070
|
+
}
|
|
2071
|
+
try {
|
|
2072
|
+
const parsed = yaml.load(readFileSyncFn(settingsPath, "utf8"));
|
|
2073
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
2074
|
+
return [];
|
|
2075
|
+
}
|
|
2076
|
+
return normalizeProjectAgentsSetting(parsed.agents);
|
|
2077
|
+
} catch {
|
|
2078
|
+
return [];
|
|
2079
|
+
}
|
|
2080
|
+
}
|
|
2081
|
+
return [];
|
|
2082
|
+
}
|
|
2083
|
+
|
|
1957
2084
|
function readProjectWorktreeSettings(projectWorkspacePath) {
|
|
1958
2085
|
for (const settingsPath of getProjectSettingsCandidates(projectWorkspacePath)) {
|
|
1959
2086
|
if (!existsSyncFn(settingsPath)) {
|
|
@@ -2804,6 +2931,7 @@ export function startDaemon(config = {}, deps = {}) {
|
|
|
2804
2931
|
};
|
|
2805
2932
|
const advertisedCapabilities = [
|
|
2806
2933
|
"project_path_validation",
|
|
2934
|
+
"project_agents_registry",
|
|
2807
2935
|
"restart_daemon",
|
|
2808
2936
|
"refresh_session_inplace",
|
|
2809
2937
|
CUSTOM_COMMANDS_CAPABILITY,
|
|
@@ -5027,6 +5155,11 @@ export function startDaemon(config = {}, deps = {}) {
|
|
|
5027
5155
|
}
|
|
5028
5156
|
if (event.type === "validate_project_path") {
|
|
5029
5157
|
void handleValidateProjectPath(event.payload);
|
|
5158
|
+
return;
|
|
5159
|
+
}
|
|
5160
|
+
if (event.type === "get_project_agents") {
|
|
5161
|
+
void handleGetProjectAgents(event.payload);
|
|
5162
|
+
return;
|
|
5030
5163
|
}
|
|
5031
5164
|
if (event.type === "ai_manager_request") {
|
|
5032
5165
|
handleAiManagerRequest(client, aiManagerHandlers, event.payload).catch((error) => {
|
|
@@ -5132,6 +5265,7 @@ export function startDaemon(config = {}, deps = {}) {
|
|
|
5132
5265
|
lastCommitAt: null,
|
|
5133
5266
|
fileCount: null,
|
|
5134
5267
|
icon: null,
|
|
5268
|
+
agents: [],
|
|
5135
5269
|
error: null,
|
|
5136
5270
|
errorCode: null,
|
|
5137
5271
|
validatedAt,
|
|
@@ -5186,6 +5320,7 @@ export function startDaemon(config = {}, deps = {}) {
|
|
|
5186
5320
|
? snapshot.fileCount
|
|
5187
5321
|
: null,
|
|
5188
5322
|
icon: readProjectIconSetting(effectiveWorkspace),
|
|
5323
|
+
agents: readProjectAgentsSetting(effectiveWorkspace),
|
|
5189
5324
|
};
|
|
5190
5325
|
}
|
|
5191
5326
|
} catch (error) {
|
|
@@ -5210,6 +5345,7 @@ export function startDaemon(config = {}, deps = {}) {
|
|
|
5210
5345
|
git_remote_url: result.gitRemoteUrl,
|
|
5211
5346
|
file_count: result.fileCount,
|
|
5212
5347
|
icon: result.icon,
|
|
5348
|
+
agents: result.agents,
|
|
5213
5349
|
error: result.error,
|
|
5214
5350
|
error_code: result.errorCode,
|
|
5215
5351
|
validated_at: result.validatedAt,
|
|
@@ -5220,6 +5356,69 @@ export function startDaemon(config = {}, deps = {}) {
|
|
|
5220
5356
|
}
|
|
5221
5357
|
}
|
|
5222
5358
|
|
|
5359
|
+
async function handleGetProjectAgents(payload) {
|
|
5360
|
+
const requestId = payload?.request_id ? String(payload.request_id).trim() : "";
|
|
5361
|
+
const rawWorkspacePath = payload?.workspace_path ? String(payload.workspace_path).trim() : "";
|
|
5362
|
+
const resolvedAt = new Date().toISOString();
|
|
5363
|
+
|
|
5364
|
+
if (!requestId || !rawWorkspacePath) {
|
|
5365
|
+
logError(`Invalid get_project_agents payload: ${JSON.stringify(payload)}`);
|
|
5366
|
+
return;
|
|
5367
|
+
}
|
|
5368
|
+
|
|
5369
|
+
let result = {
|
|
5370
|
+
workspacePath: null,
|
|
5371
|
+
agents: [],
|
|
5372
|
+
error: null,
|
|
5373
|
+
errorCode: null,
|
|
5374
|
+
};
|
|
5375
|
+
try {
|
|
5376
|
+
const resolvedPath = path.resolve(rawWorkspacePath);
|
|
5377
|
+
if (!existsSyncFn(resolvedPath)) {
|
|
5378
|
+
result = {
|
|
5379
|
+
...result,
|
|
5380
|
+
error: `Workspace path does not exist on daemon ${AGENT_NAME}: ${rawWorkspacePath}`,
|
|
5381
|
+
errorCode: "workspace_not_found",
|
|
5382
|
+
};
|
|
5383
|
+
} else if (!statSyncFn(resolvedPath).isDirectory()) {
|
|
5384
|
+
result = {
|
|
5385
|
+
...result,
|
|
5386
|
+
error: `Workspace path is not a directory on daemon ${AGENT_NAME}: ${rawWorkspacePath}`,
|
|
5387
|
+
errorCode: "workspace_not_directory",
|
|
5388
|
+
};
|
|
5389
|
+
} else {
|
|
5390
|
+
result = {
|
|
5391
|
+
...result,
|
|
5392
|
+
workspacePath: resolvedPath,
|
|
5393
|
+
agents: readProjectAgentsSetting(resolvedPath),
|
|
5394
|
+
};
|
|
5395
|
+
}
|
|
5396
|
+
} catch (error) {
|
|
5397
|
+
result = {
|
|
5398
|
+
...result,
|
|
5399
|
+
error: `Failed to read project agents on daemon ${AGENT_NAME}: ${error?.message || error}`,
|
|
5400
|
+
errorCode: "project_agents_read_failed",
|
|
5401
|
+
};
|
|
5402
|
+
}
|
|
5403
|
+
|
|
5404
|
+
try {
|
|
5405
|
+
await client.sendJson({
|
|
5406
|
+
type: "project_agents_resolved",
|
|
5407
|
+
payload: {
|
|
5408
|
+
request_id: requestId,
|
|
5409
|
+
daemon_host: AGENT_NAME,
|
|
5410
|
+
workspace_path: result.workspacePath,
|
|
5411
|
+
agents: result.agents,
|
|
5412
|
+
error: result.error,
|
|
5413
|
+
error_code: result.errorCode,
|
|
5414
|
+
resolved_at: resolvedAt,
|
|
5415
|
+
},
|
|
5416
|
+
});
|
|
5417
|
+
} catch (error) {
|
|
5418
|
+
logError(`Failed to report project_agents_resolved for ${rawWorkspacePath}: ${error?.message || error}`);
|
|
5419
|
+
}
|
|
5420
|
+
}
|
|
5421
|
+
|
|
5223
5422
|
function stopActiveTaskProcess(
|
|
5224
5423
|
taskId,
|
|
5225
5424
|
{
|
package/src/entity-helpers.js
CHANGED
|
@@ -192,8 +192,14 @@ export async function resolveProject(apis, options = {}) {
|
|
|
192
192
|
if (!apis.projects || typeof apis.projects.resolveProject !== "function") {
|
|
193
193
|
throw new Error("ProjectsApi.resolveProject is not available in conductor-sdk");
|
|
194
194
|
}
|
|
195
|
+
const explicitProject = String(options.project ?? "").trim();
|
|
196
|
+
if (explicitProject) {
|
|
197
|
+
if (typeof apis.projects.getProject !== "function") {
|
|
198
|
+
throw new Error("ProjectsApi.getProject is not available in conductor-sdk");
|
|
199
|
+
}
|
|
200
|
+
return apis.projects.getProject(explicitProject);
|
|
201
|
+
}
|
|
195
202
|
return apis.projects.resolveProject({
|
|
196
|
-
project: options.project,
|
|
197
203
|
env: options.env || process.env,
|
|
198
204
|
cwd: options.cwd || process.cwd(),
|
|
199
205
|
});
|
|
@@ -332,11 +338,30 @@ export function emitDryRun(stream, json, payload) {
|
|
|
332
338
|
}
|
|
333
339
|
}
|
|
334
340
|
|
|
341
|
+
/**
|
|
342
|
+
* Pull a human-readable reason out of a backend error payload. Backend routes
|
|
343
|
+
* respond with `{ error }` (occasionally `{ message }`), which the SDK attaches
|
|
344
|
+
* to BackendApiError.details. Surfacing it turns an opaque "Backend responded
|
|
345
|
+
* with 409" into the actual cause (e.g. "Project daemon X is offline").
|
|
346
|
+
*/
|
|
347
|
+
function backendErrorDetail(error) {
|
|
348
|
+
const details = error && typeof error === "object" ? error.details : null;
|
|
349
|
+
if (!details) return null;
|
|
350
|
+
if (typeof details === "string") return details.trim() || null;
|
|
351
|
+
if (typeof details === "object") {
|
|
352
|
+
const reason = details.error ?? details.message;
|
|
353
|
+
if (typeof reason === "string" && reason.trim()) return reason.trim();
|
|
354
|
+
}
|
|
355
|
+
return null;
|
|
356
|
+
}
|
|
357
|
+
|
|
335
358
|
/**
|
|
336
359
|
* Translate an unknown error into a printable + exit-coded form.
|
|
337
360
|
*/
|
|
338
361
|
export function reportError(consoleErr, error) {
|
|
339
362
|
const message = error instanceof Error ? error.message : String(error);
|
|
340
|
-
|
|
363
|
+
const detail = backendErrorDetail(error);
|
|
364
|
+
const line = detail && detail !== message ? `${message}: ${detail}` : message;
|
|
365
|
+
consoleErr.error(`Error: ${line}`);
|
|
341
366
|
return exitCodeForError(error);
|
|
342
367
|
}
|