@@ -11,19 +11,18 @@ description: UniDesk AgentRun-backed Code Queue CLI — Skill(cli-spec)。legacy
|
||||
|
||||
```bash
|
||||
bun scripts/cli.ts agentrun get tasks --limit 20
|
||||
bun scripts/cli.ts agentrun describe "修复 HWLAB 的 cicd"
|
||||
bun scripts/cli.ts agentrun describe task/<taskId>
|
||||
bun scripts/cli.ts agentrun describe task/<taskId> --input -o json
|
||||
bun scripts/cli.ts agentrun describe aipodspec/<name>
|
||||
bun scripts/cli.ts agentrun events "修复 HWLAB 的 cicd" --after-seq 0 --expect 10 --timeout 120s --limit 20
|
||||
bun scripts/cli.ts agentrun events run/<runId> --after-seq 0 --expect 10 --timeout 120s --limit 20
|
||||
bun scripts/cli.ts agentrun events run/<runId> --detail-seq <seq> -o json
|
||||
bun scripts/cli.ts agentrun logs "修复 HWLAB 的 cicd" --tail 120
|
||||
bun scripts/cli.ts agentrun result "修复 HWLAB 的 cicd"
|
||||
bun scripts/cli.ts agentrun logs session/<sessionId> --tail 120
|
||||
bun scripts/cli.ts agentrun result run/<runId> --command <commandId>
|
||||
bun scripts/cli.ts agentrun get attempts --task <taskId>
|
||||
bun scripts/cli.ts agentrun retry task/<taskId> --idempotency-key <key> --reason <text> --dry-run
|
||||
bun scripts/cli.ts agentrun send <session> --prompt-stdin
|
||||
bun scripts/cli.ts agentrun create task \
|
||||
--aipod Artificer \
|
||||
--title "修复 HWLAB 的 cicd" \
|
||||
--target <Target> \
|
||||
--target-workspace <absolute-task-worktree> \
|
||||
--repo <owner/repo> \
|
||||
@@ -44,11 +43,6 @@ AipodSpec/Artificer、task manifest 和 queue 渐进披露见 [references/resour
|
||||
|
||||
## P0 边界
|
||||
|
||||
- Artificer `create task` 必须显式提供唯一中文短标题:
|
||||
- 标题为单行 2 到 80 字符,并至少包含一个中文汉字;
|
||||
- `HWLAB`、`CI/CD` 等专有名和空格可以保留;
|
||||
- `describe/events/logs/result` 优先使用标题的完全匹配查询;
|
||||
- 标题重复时 CLI 必须报歧义,不得猜测;内部 ID 仅用于深度下钻和机器身份。
|
||||
- AgentRun 与调用方之间的契约版本、源码/镜像版本和 `commitId` 对齐只能产生非阻塞 warning;可安全归一化的请求继续 admission/dispatch,不得因版本或契约漂移阻止用户任务、session、run、command 或 runner。
|
||||
- 新写入口不要用 `codex submit/steer/resume`;这些 legacy mutation 已冻结。
|
||||
- Artificer 的 runner primary workspace 与任务 Target 必须分开判定:
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
```bash
|
||||
bun scripts/cli.ts agentrun create task \
|
||||
--aipod Artificer \
|
||||
--title "修复 HWLAB 的 cicd" \
|
||||
--target <Target> \
|
||||
--target-workspace <absolute-task-worktree> \
|
||||
--repo <owner/repo> \
|
||||
@@ -27,10 +26,6 @@ bun scripts/cli.ts agentrun create task \
|
||||
--prompt-stdin
|
||||
```
|
||||
|
||||
- `--title` 为必填中文短标题,允许英文专有名和空格;
|
||||
- 后续 `describe/events/logs/result` 直接传入标题字符串;
|
||||
- 标题按规范化后的完全匹配解析,重复标题必须改用内部 ID 处理歧义。
|
||||
|
||||
- 默认直接执行 live 单命令;不要把 `--dry-run` 作为派单前置步骤;
|
||||
- live 必须先经 owning YAML 选中的 Target `trans` 重入并完成只读 preflight;
|
||||
- preflight 必须验证:
|
||||
|
||||
@@ -38,8 +38,6 @@ description: UniDesk 主代理调度子代理的必读技能。用户提到子
|
||||
- 审查证据只保留 issue/PR/commit、验证摘要和必要下钻链接,不复制无界日志或构造额外实证体系;
|
||||
- 只有直接保护真实业务资源、不可逆操作或明确损害风险时才允许最小 guard,且只覆盖实际风险窗口并在风险解除后立即释放;
|
||||
- Artificer 的 `create task` 外层入口:
|
||||
- 必须使用与子 issue / MDTODO 语义一致的唯一中文短标题;
|
||||
- 标题至少包含一个中文汉字,专有名和空格可以保留;
|
||||
- 必须按 owning YAML 选中 Target;
|
||||
- 必须通过 `trans <Target>:<unideskWorkspace>` 重入目标节点;
|
||||
- 预检、创建、自动派发和观察都在重入后完成;
|
||||
|
||||
@@ -254,7 +254,6 @@ UniDesk 指挥官新任务入口固定使用 `bun scripts/cli.ts agentrun get|de
|
||||
```bash
|
||||
bun scripts/cli.ts agentrun create task \
|
||||
--aipod Artificer \
|
||||
--title "修复 HWLAB 的 cicd" \
|
||||
--target <Target> \
|
||||
--target-workspace <absolute-task-worktree> \
|
||||
--repo <owner/repo> \
|
||||
@@ -263,14 +262,6 @@ bun scripts/cli.ts agentrun create task \
|
||||
--prompt-stdin
|
||||
```
|
||||
|
||||
Artificer 标题契约:
|
||||
|
||||
- `create task` 必须显式提供单行中文短标题;
|
||||
- 标题长度为 2 到 80 字符,至少包含一个中文汉字;
|
||||
- `HWLAB`、`CI/CD` 等专有名和空格可以保留;
|
||||
- `describe/events/logs/result` 使用标题完全匹配并解析最新执行资源;
|
||||
- 标题重复时返回歧义错误,内部 ID 继续作为不可变机器身份。
|
||||
|
||||
- 默认直接执行 live 单命令,不把 `--dry-run` 作为派单前置步骤;
|
||||
- live 先经 Target `trans` 重入并执行 source/worktree/Aipod/SecretRef 只读预检;
|
||||
- 预检通过后在同一命令内 submit 并自动 dispatch;
|
||||
|
||||
@@ -259,7 +259,6 @@ function options(output: AgentRunOutputMode): AgentRunResourceOptions {
|
||||
dryRun: false,
|
||||
file: null,
|
||||
aipod: null,
|
||||
title: null,
|
||||
idempotencyKey: null,
|
||||
promptStdin: false,
|
||||
node: null,
|
||||
|
||||
@@ -221,7 +221,6 @@ describe("AgentRun render-only REST client config", () => {
|
||||
describe("Artificer Target one-command dispatch", () => {
|
||||
test("requires trans re-entry before Target filesystem preflight and derives a stable idempotency key", () => {
|
||||
const request = targetTaskRequestFromArgs([
|
||||
"--title", "修复 SelfMedia 的 OCR 规划器",
|
||||
"--target", "NC01",
|
||||
"--target-workspace", "/root/.worktrees/selfmedia/r6-ocr-visual-planner",
|
||||
"--repo", "pikainc/selfmedia",
|
||||
@@ -270,7 +269,6 @@ describe("Artificer Target one-command dispatch", () => {
|
||||
|
||||
test("scopes post-task create replay to one complete logical operation", () => {
|
||||
const base = {
|
||||
title: "修复 UniDesk 的 Artificer 派单",
|
||||
target: "NC01",
|
||||
targetRoute: "NC01:/root/unidesk/.worktree/base",
|
||||
projectId: "pikasTech/unidesk",
|
||||
@@ -1644,7 +1642,6 @@ describe("AgentRun YAML tool credential binding", () => {
|
||||
test("preserves explicit credential subsets across Aipod admission and rejects unknown identities or conflicts", async () => {
|
||||
const renderBodies: Record<string, any>[] = [];
|
||||
const submittedBodies: Record<string, any>[] = [];
|
||||
const taskTitle = "验证 Artificer 工具凭据";
|
||||
let credentialMode: "all" | "subset" | "conflict" | "unknown" = "all";
|
||||
const renderedToolCredentials = (): Record<string, unknown>[] => {
|
||||
const credentials = [
|
||||
@@ -1682,9 +1679,6 @@ describe("AgentRun YAML tool credential binding", () => {
|
||||
return credentials;
|
||||
};
|
||||
const renderedTask = () => ({
|
||||
title: taskTitle,
|
||||
metadata: { title: taskTitle },
|
||||
sessionRef: { metadata: { title: taskTitle } },
|
||||
tenantId: "unidesk",
|
||||
projectId: "pikasTech/unidesk",
|
||||
queue: "commander",
|
||||
@@ -1732,7 +1726,7 @@ describe("AgentRun YAML tool credential binding", () => {
|
||||
process.env.AGENTRUN_CLIENT_CONFIG = tempConfigPath;
|
||||
await Bun.write(tempConfigPath, agentRunMinimalClientYaml.replace("http://agentrun.example.local:8080", server.url.href.replace(/\/$/u, "")));
|
||||
|
||||
const accepted = await runAgentRunCommand(null, ["create", "task", "--aipod", "Artificer", "--title", taskTitle, "--prompt", "fixture", "--model", "gpt-explicit", "--reasoning-effort", "high", "-o", "json"]);
|
||||
const accepted = await runAgentRunCommand(null, ["create", "task", "--aipod", "Artificer", "--prompt", "fixture", "--model", "gpt-explicit", "--reasoning-effort", "high", "-o", "json"]);
|
||||
expect(accepted.ok).toBe(true);
|
||||
expect(renderBodies).toHaveLength(1);
|
||||
expect(submittedBodies).toHaveLength(1);
|
||||
@@ -1743,20 +1737,20 @@ describe("AgentRun YAML tool credential binding", () => {
|
||||
expect(submittedBodies[0]).toEqual(renderedTask());
|
||||
|
||||
credentialMode = "subset";
|
||||
const subsetAccepted = await runAgentRunCommand(null, ["create", "task", "--aipod", "Artificer", "--title", taskTitle, "--prompt", "fixture", "-o", "json"]);
|
||||
const subsetAccepted = await runAgentRunCommand(null, ["create", "task", "--aipod", "Artificer", "--prompt", "fixture", "-o", "json"]);
|
||||
expect(subsetAccepted.ok).toBe(true);
|
||||
expect(renderBodies).toHaveLength(2);
|
||||
expect(submittedBodies).toHaveLength(2);
|
||||
expect(submittedBodies[1]).toEqual(renderedTask());
|
||||
|
||||
credentialMode = "conflict";
|
||||
const rejected = await runAgentRunCommand(null, ["create", "task", "--aipod", "Artificer", "--title", taskTitle, "--prompt", "fixture", "-o", "json"]);
|
||||
const rejected = await runAgentRunCommand(null, ["create", "task", "--aipod", "Artificer", "--prompt", "fixture", "-o", "json"]);
|
||||
expect(rejected.ok).toBe(false);
|
||||
expect(submittedBodies).toHaveLength(2);
|
||||
expect(renderedTextOf(rejected)).toContain("github/github-ssh keys conflict");
|
||||
|
||||
credentialMode = "unknown";
|
||||
const unknownRejected = await runAgentRunCommand(null, ["create", "task", "--aipod", "Artificer", "--title", taskTitle, "--prompt", "fixture", "-o", "json"]);
|
||||
const unknownRejected = await runAgentRunCommand(null, ["create", "task", "--aipod", "Artificer", "--prompt", "fixture", "-o", "json"]);
|
||||
expect(unknownRejected.ok).toBe(false);
|
||||
expect(submittedBodies).toHaveLength(2);
|
||||
expect(renderedTextOf(unknownRejected)).toContain("artifact-store/publish is not declared by YAML lane");
|
||||
|
||||
@@ -57,19 +57,19 @@ export function agentRunHelp(): unknown {
|
||||
"bun scripts/cli.ts agentrun describe run/<runId> --node NC01 --lane nc01-v02",
|
||||
"bun scripts/cli.ts agentrun get tasks -o wide",
|
||||
"bun scripts/cli.ts agentrun get tasks -o json",
|
||||
"bun scripts/cli.ts agentrun describe \"修复 HWLAB 的 cicd\"",
|
||||
"bun scripts/cli.ts agentrun describe task/<taskId>",
|
||||
"bun scripts/cli.ts agentrun describe task/<taskId> --input -o json",
|
||||
"bun scripts/cli.ts agentrun events \"修复 HWLAB 的 cicd\" --after-seq 0 --expect 10 --timeout 120s --limit 100",
|
||||
"bun scripts/cli.ts agentrun events run/<runId> --after-seq 0 --expect 10 --timeout 120s --limit 100",
|
||||
"bun scripts/cli.ts agentrun events run/<runId> --detail-seq <seq>",
|
||||
"bun scripts/cli.ts agentrun logs \"修复 HWLAB 的 cicd\" --tail 100",
|
||||
"bun scripts/cli.ts agentrun result \"修复 HWLAB 的 cicd\"",
|
||||
"bun scripts/cli.ts agentrun logs session/<sessionId> --tail 100",
|
||||
"bun scripts/cli.ts agentrun result run/<runId> --command <commandId>",
|
||||
"bun scripts/cli.ts agentrun ack task/<taskId> --reader-id cli",
|
||||
"bun scripts/cli.ts agentrun cancel task/<taskId> --reason <text> --dry-run",
|
||||
"bun scripts/cli.ts agentrun retry task/<taskId> --idempotency-key <key> --reason <text> --dry-run",
|
||||
"bun scripts/cli.ts agentrun get attempts --task <taskId> --limit 20",
|
||||
"bun scripts/cli.ts agentrun dispatch task/<taskId>",
|
||||
"bun scripts/cli.ts agentrun create task --aipod Artificer --title \"修复 HWLAB 的 cicd\" --prompt-stdin --idempotency-key <key>",
|
||||
"bun scripts/cli.ts agentrun create task --aipod Artificer --title \"修复 HWLAB 的 cicd\" --target <Target> --target-workspace <absolute-task-worktree> --repo <owner/repo> --ref <branch> --mdtodo-id <R6.3> --prompt-stdin [--dry-run]",
|
||||
"bun scripts/cli.ts agentrun create task --aipod Artificer --prompt-stdin --idempotency-key <key>",
|
||||
"bun scripts/cli.ts agentrun create task --aipod Artificer --target <Target> --target-workspace <absolute-task-worktree> --repo <owner/repo> --ref <branch> --mdtodo-id <R6.3> --prompt-stdin [--dry-run]",
|
||||
"bun scripts/cli.ts agentrun apply -f - --dry-run",
|
||||
"bun scripts/cli.ts agentrun send session/<sessionId> --aipod Artificer --prompt-stdin",
|
||||
"bun scripts/cli.ts agentrun explain task",
|
||||
@@ -318,7 +318,7 @@ export function agentRunHelpText(args: string[]): string {
|
||||
return "Usage: bun scripts/cli.ts agentrun dispatch task/<taskId>";
|
||||
}
|
||||
if (verb === "create") {
|
||||
return "Usage: bun scripts/cli.ts agentrun create task --aipod Artificer --title <中文短标题> --target <Target> --target-workspace <task-worktree> --repo <owner/repo> --ref <branch> --mdtodo-id <R6.3> --prompt-stdin [--idempotency-key <key>] [--dry-run]";
|
||||
return "Usage: bun scripts/cli.ts agentrun create task --aipod Artificer --target <Target> --target-workspace <task-worktree> --repo <owner/repo> --ref <branch> --mdtodo-id <R6.3> --prompt-stdin [--idempotency-key <key>] [--dry-run]";
|
||||
}
|
||||
if (verb === "apply") {
|
||||
return "Usage: bun scripts/cli.ts agentrun apply -f task.yaml|json|- [--dry-run]\nTask manifests use kind: Task and spec: <AgentRun task payload>.";
|
||||
@@ -413,7 +413,7 @@ export function agentRunHelpText(args: string[]): string {
|
||||
" bun scripts/cli.ts agentrun logs session/<sessionId> --tail 100",
|
||||
" bun scripts/cli.ts agentrun retry task/<taskId> --idempotency-key <key> --reason <text> --dry-run",
|
||||
" bun scripts/cli.ts agentrun get attempts --task <taskId> --limit 20",
|
||||
" bun scripts/cli.ts agentrun create task --aipod Artificer --title \"修复 HWLAB 的 cicd\" --target <Target> --target-workspace <task-worktree> --repo <owner/repo> --ref <branch> --mdtodo-id <R6.3> --prompt-stdin",
|
||||
" bun scripts/cli.ts agentrun create task --aipod Artificer --target <Target> --target-workspace <task-worktree> --repo <owner/repo> --ref <branch> --mdtodo-id <R6.3> --prompt-stdin",
|
||||
"",
|
||||
"Machine/debug output:",
|
||||
" -o json|yaml emits a stable UniDesk resource object without the global JSON envelope.",
|
||||
|
||||
@@ -528,7 +528,6 @@ export function renderTargetTaskMutationLines(targetTask: Record<string, unknown
|
||||
return [
|
||||
"",
|
||||
"TargetTaskPreflight:",
|
||||
` Title: ${displayValue(targetTask.title)}`,
|
||||
` MDTODO: ${displayValue(targetTask.mdtodoId)} project=${displayValue(targetTask.projectId)}`,
|
||||
` Target: ${displayValue(targetTask.targetRoute)}`,
|
||||
` Source: ${displayValue(targetTask.repository)} ref=${displayValue(targetTask.ref)}`,
|
||||
|
||||
@@ -43,7 +43,6 @@ import { AgentRunRestError, renderAgentRunRestError, resolveAgentRunRestTarget,
|
||||
import { renderSessionSendError } from "./session-send-render";
|
||||
import { runAgentRunResourceThroughTarget } from "./target-execution";
|
||||
import { runnerJobObservationScript, type RunnerJobObservationIdentity } from "./runner-observation";
|
||||
import { isChineseTaskTitle, validateChineseTaskTitle } from "./target-task";
|
||||
import { capture, captureJsonPayload, compactCapture, nonNegativeIntegerOrNull, record, stringOrNull } from "./utils";
|
||||
|
||||
export function agentRunGetKindHelp(kindRaw: string): string {
|
||||
@@ -124,9 +123,6 @@ function validateResourceActionBeforeTarget(
|
||||
if (verb === "get" && parseResourceKind(action) === "attempt" && options.taskId === null) {
|
||||
throw new AgentRunRestError("validation-failed", "get attempts requires --task <taskId>");
|
||||
}
|
||||
if (verb === "create" && parseResourceKind(action) === "task" && options.aipod === "Artificer") {
|
||||
validateChineseTaskTitle(options.title);
|
||||
}
|
||||
}
|
||||
|
||||
function resourceErrorOutputOptions(args: string[]): AgentRunResourceOptions {
|
||||
@@ -191,7 +187,6 @@ export function parseResourceOptions(args: string[]): AgentRunResourceOptions {
|
||||
dryRun: false,
|
||||
file: null,
|
||||
aipod: null,
|
||||
title: null,
|
||||
idempotencyKey: null,
|
||||
promptStdin: false,
|
||||
node: null,
|
||||
@@ -203,7 +198,7 @@ export function parseResourceOptions(args: string[]): AgentRunResourceOptions {
|
||||
mdtodoId: null,
|
||||
passthroughArgs: [],
|
||||
};
|
||||
const valueFlags = new Set(["-o", "--output", "--limit", "--cursor", "--queue", "--state", "--reader-id", "--task", "--task-id", "--run", "--run-id", "--command", "--command-id", "--session", "--session-id", "--after-seq", "--expect", "--timeout", "--detail-seq", "--tail", "--reason", "-f", "--file", "--filename", "--aipod", "--title", "--idempotency-key", "--node", "--lane", "--target", "--target-workspace", "--repo", "--ref", "--mdtodo-id"]);
|
||||
const valueFlags = new Set(["-o", "--output", "--limit", "--cursor", "--queue", "--state", "--reader-id", "--task", "--task-id", "--run", "--run-id", "--command", "--command-id", "--session", "--session-id", "--after-seq", "--expect", "--timeout", "--detail-seq", "--tail", "--reason", "-f", "--file", "--filename", "--aipod", "--idempotency-key", "--node", "--lane", "--target", "--target-workspace", "--repo", "--ref", "--mdtodo-id"]);
|
||||
const booleanFlags = new Set(["--full", "--raw", "--debug", "--input", "--unread", "--dry-run", "--full-text", "--prompt-stdin", "--stdin"]);
|
||||
for (let index = 0; index < args.length; index += 1) {
|
||||
const arg = args[index] ?? "";
|
||||
@@ -346,7 +341,6 @@ export function applyResourceOption(options: AgentRunResourceOptions, flag: stri
|
||||
else if (flag === "--reason") options.reason = requiredValue(value, flag);
|
||||
else if (flag === "-f" || flag === "--file" || flag === "--filename") options.file = requiredValue(value, flag);
|
||||
else if (flag === "--aipod") options.aipod = requiredValue(value, flag);
|
||||
else if (flag === "--title") options.title = requiredValue(value, flag);
|
||||
else if (flag === "--idempotency-key") options.idempotencyKey = requiredValue(value, flag);
|
||||
else if (flag === "--node") options.node = requiredValue(value, flag);
|
||||
else if (flag === "--lane") options.lane = requiredValue(value, flag);
|
||||
@@ -464,8 +458,7 @@ export async function resourceRetry(config: UniDeskConfig | null, command: strin
|
||||
}
|
||||
|
||||
export async function resourceDescribe(config: UniDeskConfig | null, command: string, action: string | undefined, args: string[], options: AgentRunResourceOptions): Promise<RenderedCliResult> {
|
||||
const titleContext = await resolveTaskTitleContext(config, action);
|
||||
const ref = titleContext === null ? parseResourceRef(action, args) : { kind: "task", name: titleContext.taskId } as const;
|
||||
const ref = parseResourceRef(action, args);
|
||||
if (options.taskInput && ref.kind !== "task") throw new AgentRunRestError("validation-failed", "describe --input supports task/<taskId> only");
|
||||
if (ref.kind === "task") {
|
||||
const result = await runAgentRunRestCommand(config, "queue", ["show", ref.name, ...(options.full ? ["--full"] : [])]);
|
||||
@@ -626,11 +619,8 @@ function runnerJobObservationIdentity(managerResource: Record<string, unknown>,
|
||||
}
|
||||
|
||||
export async function resourceEvents(config: UniDeskConfig | null, command: string, action: string | undefined, args: string[], options: AgentRunResourceOptions): Promise<RenderedCliResult> {
|
||||
const titleContext = await resolveTaskTitleContext(config, action);
|
||||
const ref = titleContext === null ? parseResourceRef(action, args, "run") : null;
|
||||
const runId = titleContext === null
|
||||
? ref?.kind === "run" ? ref.name : options.runId ?? requiredContext("events", "--run <runId>")
|
||||
: requiredTitleResource(titleContext, "runId", "events");
|
||||
const ref = parseResourceRef(action, args, "run");
|
||||
const runId = ref.kind === "run" ? ref.name : options.runId ?? requiredContext("events", "--run <runId>");
|
||||
if (options.eventDetailSeq !== null) {
|
||||
const detailSeq = options.eventDetailSeq;
|
||||
const result = await runAgentRunRestCommand(config, "runs", ["events", runId, "--after-seq", String(detailSeq - 1), "--limit", "1"]);
|
||||
@@ -709,11 +699,8 @@ export async function resourceEvents(config: UniDeskConfig | null, command: stri
|
||||
}
|
||||
|
||||
export async function resourceLogs(config: UniDeskConfig | null, command: string, action: string | undefined, args: string[], options: AgentRunResourceOptions): Promise<RenderedCliResult> {
|
||||
const titleContext = await resolveTaskTitleContext(config, action);
|
||||
const ref = titleContext === null
|
||||
? parseResourceRef(action, args, "session")
|
||||
: { kind: "session", name: requiredTitleResource(titleContext, "sessionId", "logs") } as const;
|
||||
if (ref.kind !== "session") throw new Error("logs currently requires session/<sessionId> or an exact Chinese task title");
|
||||
const ref = parseResourceRef(action, args, "session");
|
||||
if (ref.kind !== "session") throw new Error("logs currently requires session/<sessionId>");
|
||||
const effectiveLimit = options.tail ?? options.limit;
|
||||
const logArgs = ["output", ref.name, "--limit", String(effectiveLimit)];
|
||||
if (options.afterSeq !== null) logArgs.push("--after-seq", String(options.afterSeq));
|
||||
@@ -766,13 +753,6 @@ export async function resourceLogsTailResult(config: UniDeskConfig | null, sessi
|
||||
}
|
||||
|
||||
export async function resourceResult(config: UniDeskConfig | null, command: string, action: string | undefined, args: string[], options: AgentRunResourceOptions): Promise<RenderedCliResult> {
|
||||
const titleContext = await resolveTaskTitleContext(config, action);
|
||||
if (titleContext !== null) {
|
||||
const runId = requiredTitleResource(titleContext, "runId", "result");
|
||||
const commandId = requiredTitleResource(titleContext, "commandId", "result");
|
||||
const result = await runAgentRunRestCommand(config, "runs", ["result", runId, "--command-id", commandId]);
|
||||
return renderResultSummary(command, result, options, { kind: "run", name: runId });
|
||||
}
|
||||
const ref = parseResourceRef(action, args);
|
||||
if (ref.kind === "run") {
|
||||
const commandId = options.commandId ?? requiredContext("run result", "--command <commandId>");
|
||||
@@ -787,56 +767,6 @@ export async function resourceResult(config: UniDeskConfig | null, command: stri
|
||||
throw new Error("result supports run/<runId> or command/<commandId>");
|
||||
}
|
||||
|
||||
interface AgentRunTaskTitleContext {
|
||||
title: string;
|
||||
taskId: string;
|
||||
runId: string | null;
|
||||
commandId: string | null;
|
||||
sessionId: string | null;
|
||||
}
|
||||
|
||||
async function resolveTaskTitleContext(config: UniDeskConfig | null, action: string | undefined): Promise<AgentRunTaskTitleContext | null> {
|
||||
if (!isChineseTaskTitle(action)) return null;
|
||||
const title = validateChineseTaskTitle(action);
|
||||
const result = await runAgentRunRestCommand(config, "queue", ["list", "--limit", "500"]);
|
||||
const tasks = arrayRecords(record(innerData(result)).items ?? innerData(result));
|
||||
const matches = tasks.filter((task) => normalizeStoredTaskTitle(task) === title);
|
||||
if (matches.length === 0) {
|
||||
throw new AgentRunRestError("not-found", `未找到中文标题完全匹配“${title}”的 Artificer 任务。`);
|
||||
}
|
||||
if (matches.length > 1) {
|
||||
throw new AgentRunRestError("validation-failed", `中文标题“${title}”匹配到多个任务,不能猜测。`, {
|
||||
details: {
|
||||
title,
|
||||
candidates: matches.map((task) => ({ id: stringOrNull(task.id), state: stringOrNull(task.state ?? task.status) })),
|
||||
valuesPrinted: false,
|
||||
},
|
||||
});
|
||||
}
|
||||
const task = matches[0] ?? {};
|
||||
const attempt = record(task.latestAttempt);
|
||||
const taskId = stringOrNull(task.id);
|
||||
if (taskId === null) throw new AgentRunRestError("schema-mismatch", `中文标题“${title}”匹配的任务缺少 id。`);
|
||||
return {
|
||||
title,
|
||||
taskId,
|
||||
runId: stringOrNull(attempt.runId ?? task.runId),
|
||||
commandId: stringOrNull(attempt.commandId ?? task.commandId),
|
||||
sessionId: stringOrNull(attempt.sessionId ?? record(task.sessionRef).sessionId ?? task.sessionId),
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeStoredTaskTitle(task: Record<string, unknown>): string | null {
|
||||
const title = stringOrNull(task.title ?? record(task.metadata).title ?? record(record(task.sessionRef).metadata).title);
|
||||
return title === null ? null : title.trim().replace(/\s+/gu, " ");
|
||||
}
|
||||
|
||||
function requiredTitleResource(context: AgentRunTaskTitleContext, key: "runId" | "commandId" | "sessionId", operation: string): string {
|
||||
const value = context[key];
|
||||
if (value !== null) return value;
|
||||
throw new AgentRunRestError("not-found", `中文标题“${context.title}”对应任务尚无可用于 ${operation} 的 ${key}。`);
|
||||
}
|
||||
|
||||
export async function resourceAck(config: UniDeskConfig | null, command: string, action: string | undefined, args: string[], options: AgentRunResourceOptions): Promise<RenderedCliResult> {
|
||||
const ref = parseResourceRef(action, args, "task");
|
||||
const result = ref.kind === "task"
|
||||
|
||||
@@ -50,7 +50,6 @@ import {
|
||||
targetTaskDisclosure,
|
||||
targetTaskPrompt,
|
||||
targetTaskRequestFromArgs,
|
||||
validateChineseTaskTitle,
|
||||
type AgentRunTargetTaskPreflight,
|
||||
} from "./target-task";
|
||||
import { capture, captureJsonPayload, compactCapture, nonNegativeIntegerOrNull, progressEvent, record, shQuote, sleep, stringOrNull } from "./utils";
|
||||
@@ -600,10 +599,8 @@ export async function runAgentRunAipodSpecsRest(action: string | undefined, id:
|
||||
export async function submitQueueTaskRest(args: string[]): Promise<Record<string, unknown>> {
|
||||
const aipod = agentRunOption(args, "aipod") ?? agentRunOption(args, "aipod-spec");
|
||||
if (aipod) {
|
||||
const taskTitle = aipod === "Artificer" ? validateChineseTaskTitle(agentRunOption(args, "title")) : null;
|
||||
const targetTask = targetTaskPreflightFromArgs(args, aipod);
|
||||
const renderInput = await aipodRenderInputFromArgs(args, targetTask === null ? 2 : args.length);
|
||||
if (taskTitle !== null) renderInput.title = taskTitle;
|
||||
let targetTaskOperation: Record<string, unknown> | null = null;
|
||||
let canonicalIdempotencyKey: string | null = null;
|
||||
if (targetTask !== null) {
|
||||
@@ -628,12 +625,6 @@ export async function submitQueueTaskRest(args: string[]): Promise<Record<string
|
||||
const rendered = await agentRunRestRequest("agentrun aipod-specs render", "POST", `/api/v1/aipod-specs/${encodeURIComponent(aipod)}/render`, renderInput);
|
||||
const body = normalizeAipodRenderedQueueTask(record(record(innerData(rendered)).queueTask), args, aipod);
|
||||
if (Object.keys(body).length === 0) throw new AgentRunRestError("schema-mismatch", `aipod-spec ${aipod} render did not return queueTask`);
|
||||
if (taskTitle !== null) {
|
||||
body.title = taskTitle;
|
||||
body.metadata = { ...record(body.metadata), title: taskTitle };
|
||||
const sessionRef = record(body.sessionRef);
|
||||
body.sessionRef = { ...sessionRef, metadata: { ...record(sessionRef.metadata), title: taskTitle } };
|
||||
}
|
||||
assertLegalAipodWorkspaceRef(body, aipod);
|
||||
if (targetTask !== null && canonicalIdempotencyKey !== null) {
|
||||
body.idempotencyKey = canonicalIdempotencyKey;
|
||||
|
||||
@@ -70,7 +70,6 @@ function compactTargetTaskIdentity(
|
||||
function compactTargetTaskSummary(targetTask: Record<string, unknown>): Record<string, unknown> {
|
||||
const workspace = asRecord(targetTask.workspace);
|
||||
return {
|
||||
title: boundedText(targetTask.title, 80),
|
||||
mdtodoId: boundedText(targetTask.mdtodoId, 80),
|
||||
target: boundedText(targetTask.target, 120),
|
||||
targetWorkspace: boundedText(targetTask.targetWorkspace, 360),
|
||||
@@ -183,8 +182,6 @@ function targetTaskIdentityNext(
|
||||
const commandId = asString(identity.commandId);
|
||||
const runnerJobId = asString(identity.runnerJobId);
|
||||
const sessionId = asString(identity.sessionId);
|
||||
const title = asString(targetTask.title);
|
||||
const selector = title === null ? null : shellQuote(title);
|
||||
const target = asString(targetTask.target) ?? asString(aipodBinding.node);
|
||||
const lane = asString(aipodBinding.lane);
|
||||
const suffix = [
|
||||
@@ -194,30 +191,24 @@ function targetTaskIdentityNext(
|
||||
const scoped = (command: string): string => suffix.length === 0 ? command : `${command} ${suffix}`;
|
||||
return {
|
||||
...(taskId === null ? {} : {
|
||||
task: scoped(`bun scripts/cli.ts agentrun describe ${selector ?? `task/${taskId}`}`),
|
||||
task: scoped(`bun scripts/cli.ts agentrun describe task/${taskId}`),
|
||||
attempts: scoped(`bun scripts/cli.ts agentrun get attempts --task ${taskId}`),
|
||||
}),
|
||||
...(runId === null ? {} : {
|
||||
events: scoped(`bun scripts/cli.ts agentrun events ${selector ?? `run/${runId}`} --after-seq 0 --limit 20`),
|
||||
events: scoped(`bun scripts/cli.ts agentrun events run/${runId} --after-seq 0 --limit 20`),
|
||||
}),
|
||||
...(runId === null || commandId === null ? {} : {
|
||||
result: scoped(selector === null
|
||||
? `bun scripts/cli.ts agentrun result run/${runId} --command ${commandId}`
|
||||
: `bun scripts/cli.ts agentrun result ${selector}`),
|
||||
result: scoped(`bun scripts/cli.ts agentrun result run/${runId} --command ${commandId}`),
|
||||
}),
|
||||
...(runnerJobId === null || runId === null ? {} : {
|
||||
runner: scoped(`bun scripts/cli.ts agentrun describe runnerjob/${runnerJobId} --run ${runId}`),
|
||||
}),
|
||||
...(sessionId === null ? {} : {
|
||||
logs: scoped(`bun scripts/cli.ts agentrun logs ${selector ?? `session/${sessionId}`} --tail 120`),
|
||||
logs: scoped(`bun scripts/cli.ts agentrun logs session/${sessionId} --tail 120`),
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
function shellQuote(value: string): string {
|
||||
return `'${value.replace(/'/gu, `'"'"'`)}'`;
|
||||
}
|
||||
|
||||
function innerData(value: Record<string, unknown>): unknown {
|
||||
return value.data ?? value;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ import { realpathSync, statSync } from "node:fs";
|
||||
import { spawnSync } from "node:child_process";
|
||||
|
||||
export interface AgentRunTargetTaskRequest {
|
||||
readonly title: string;
|
||||
readonly target: string;
|
||||
readonly targetWorkspace: string;
|
||||
readonly repository: string;
|
||||
@@ -44,7 +43,6 @@ export interface AgentRunTargetTaskOperationEvidence {
|
||||
readonly tenantId: string;
|
||||
readonly projectId: string;
|
||||
readonly aipod: string;
|
||||
readonly title: string;
|
||||
readonly mdtodoId: string;
|
||||
readonly target: string;
|
||||
readonly repository: string;
|
||||
@@ -70,7 +68,6 @@ export class AgentRunTargetTaskPreflightError extends Error {
|
||||
|
||||
export function targetTaskRequestFromArgs(args: string[]): AgentRunTargetTaskRequest | null {
|
||||
const raw = {
|
||||
title: option(args, "title"),
|
||||
target: option(args, "target"),
|
||||
targetWorkspace: option(args, "target-workspace"),
|
||||
repository: option(args, "repo"),
|
||||
@@ -83,12 +80,11 @@ export function targetTaskRequestFromArgs(args: string[]): AgentRunTargetTaskReq
|
||||
if (missing.length > 0) {
|
||||
throw new AgentRunTargetTaskPreflightError(
|
||||
"target-context",
|
||||
`Artificer Target 派单缺少 ${missing.join(", ")};请同时提供 --title、--target、--target-workspace、--repo、--ref 和 --mdtodo-id。`,
|
||||
`Artificer Target 派单缺少 ${missing.join(", ")};请同时提供 --target、--target-workspace、--repo、--ref 和 --mdtodo-id。`,
|
||||
{ missing },
|
||||
);
|
||||
}
|
||||
return {
|
||||
title: validateChineseTaskTitle(raw.title),
|
||||
target: validateTarget(raw.target as string),
|
||||
targetWorkspace: validateTargetWorkspace(raw.targetWorkspace as string),
|
||||
repository: normalizeRepository(raw.repository as string),
|
||||
@@ -213,7 +209,6 @@ export function preflightTargetTask(request: AgentRunTargetTaskRequest, markerEn
|
||||
export function targetTaskPrompt(preflight: AgentRunTargetTaskPreflight, userPrompt: string | null): string {
|
||||
const context = [
|
||||
"任务目标上下文(由 UniDesk YAML-first CLI 在创建 task 前经 Target trans 只读核验):",
|
||||
`- 中文任务标题: ${preflight.title}`,
|
||||
`- MDTODO ID: ${preflight.mdtodoId}`,
|
||||
`- Target: ${preflight.target}`,
|
||||
`- targetWorkspace: ${preflight.targetWorkspace}`,
|
||||
@@ -234,7 +229,6 @@ export function targetTaskPrompt(preflight: AgentRunTargetTaskPreflight, userPro
|
||||
|
||||
export function targetTaskDisclosure(preflight: AgentRunTargetTaskPreflight): Record<string, unknown> {
|
||||
return {
|
||||
title: preflight.title,
|
||||
mdtodoId: preflight.mdtodoId,
|
||||
target: preflight.target,
|
||||
targetWorkspace: preflight.targetWorkspace,
|
||||
@@ -266,7 +260,6 @@ export function targetTaskOperationEvidence(
|
||||
tenantId: operation.tenantId,
|
||||
projectId: operation.projectId,
|
||||
aipod: operation.aipod,
|
||||
title: preflight.title,
|
||||
mdtodoId: preflight.mdtodoId,
|
||||
target: preflight.target,
|
||||
repository: preflight.repository,
|
||||
@@ -318,41 +311,6 @@ export function targetTaskIdempotencyKey(
|
||||
return resolveTargetTaskIdempotency(preflight, operation, null).idempotencyKey;
|
||||
}
|
||||
|
||||
export function validateChineseTaskTitle(raw: string | null): string {
|
||||
if (raw === null || raw.trim().length === 0) {
|
||||
throw new AgentRunTargetTaskPreflightError(
|
||||
"task-title",
|
||||
"Artificer 创建任务必须提供 --title <中文短标题>,例如 --title \"修复 HWLAB 的 cicd\"。",
|
||||
{ reason: "missing-chinese-task-title" },
|
||||
);
|
||||
}
|
||||
if (/\r|\n|[\u0000-\u001f\u007f]/u.test(raw)) {
|
||||
throw new AgentRunTargetTaskPreflightError("task-title", "Artificer 任务标题必须是单行短字符串。", {
|
||||
reason: "invalid-task-title-control-character",
|
||||
});
|
||||
}
|
||||
const title = raw.trim().replace(/\s+/gu, " ");
|
||||
const length = Array.from(title).length;
|
||||
if (length < 2 || length > 80) {
|
||||
throw new AgentRunTargetTaskPreflightError("task-title", "Artificer 任务标题长度必须为 2 到 80 个字符。", {
|
||||
reason: "invalid-task-title-length",
|
||||
length,
|
||||
});
|
||||
}
|
||||
if (!isChineseTaskTitle(title)) {
|
||||
throw new AgentRunTargetTaskPreflightError(
|
||||
"task-title",
|
||||
"Artificer 任务标题必须至少包含一个中文汉字;HWLAB、CI/CD 等专有名和空格可以保留。",
|
||||
{ reason: "missing-han-character" },
|
||||
);
|
||||
}
|
||||
return title;
|
||||
}
|
||||
|
||||
export function isChineseTaskTitle(value: string | undefined): value is string {
|
||||
return typeof value === "string" && /\p{Script=Han}/u.test(value);
|
||||
}
|
||||
|
||||
function git(request: AgentRunTargetTaskRequest, args: string[], stage: string): string {
|
||||
const result = spawnSync("git", args, {
|
||||
cwd: request.targetWorkspace,
|
||||
|
||||
@@ -85,7 +85,6 @@ export interface AgentRunResourceOptions {
|
||||
dryRun: boolean;
|
||||
file: string | null;
|
||||
aipod: string | null;
|
||||
title: string | null;
|
||||
idempotencyKey: string | null;
|
||||
promptStdin: boolean;
|
||||
node: string | null;
|
||||
|
||||
Reference in New Issue
Block a user