diff --git a/.agents/skills/unidesk-agentrun/SKILL.md b/.agents/skills/unidesk-agentrun/SKILL.md index 14125970..d7ae33eb 100644 --- a/.agents/skills/unidesk-agentrun/SKILL.md +++ b/.agents/skills/unidesk-agentrun/SKILL.md @@ -76,7 +76,8 @@ AipodSpec/Artificer、task manifest 和 queue 渐进披露见 [references/resour - 新写入口不要用 `codex submit/steer/resume`;这些 legacy mutation 已冻结。 - Artificer 的 runner primary workspace 与任务 Target 必须分开判定: - runner primary workspace 只装配默认 UniDesk resource bundle、`tools/trans`、skill 和轻量准备; - - 待修改源码只来自派单上下文中的 `targetWorkspace`,且该路径必须是任务专属 worktree; + - 待修改源码只来自派单上下文中的显式 `targetWorkspace`; + - admission 只校验绝对路径、存在性和目录类型,不读取 Git 状态,也不因 dirty、ahead、behind 或已有 diff 产生 warning/blocker; - 现场探测、Git、编辑、验证和运行面观察必须使用 `trans :`; - 不得把 runner 本地结果、默认 bundle 的 Git 状态或容器内同名路径当作 Target 验收; - `unidesk-trans` required skill、runner tools bundle、`unidesk-ssh` SecretRef 和 endpoint presence 缺一项即归为装配失败; diff --git a/.agents/skills/unidesk-agentrun/references/resources.md b/.agents/skills/unidesk-agentrun/references/resources.md index de4edf67..e9cf240a 100644 --- a/.agents/skills/unidesk-agentrun/references/resources.md +++ b/.agents/skills/unidesk-agentrun/references/resources.md @@ -32,21 +32,22 @@ bun scripts/cli.ts agentrun create task \ - 标题按规范化后的完全匹配解析,重复标题必须改用内部 ID 处理歧义。 - 默认直接执行 live 单命令;不要把 `--dry-run` 作为派单前置步骤; -- live 必须先经 owning YAML 选中的 Target `trans` 重入并完成只读 preflight; +- live 必须先经 owning YAML 选中的 Target `trans` 重入并完成最小路径 preflight; - preflight 必须验证: - - task worktree 是 Git 根目录且 clean; - - origin 与 `--repo` 一致; - - current branch 与 `--ref` 一致; - - HEAD 与 origin 远端 ref commit 一致; + - `targetWorkspace` 已显式提供为绝对路径; + - Target 上的该路径存在且为目录; +- preflight 禁止读取 task worktree Git 状态: + - dirty、ahead、behind、detached 和已有 diff 不产生 warning 或 blocker; + - `repo` 和 `ref` 只保留为显式任务身份; - preflight 输出必须披露: - - Target route、repo/ref、projectId、HEAD/remote 关系; + - Target route、repo/ref、projectId 和 `gitStateInspected=false`; - 合法的 `workspaceRef.kind/path`; - 从 AipodSpec 继承的默认 resource bundle; - session identity; - provider/tool SecretRef 的对象、key 与 presence; - preflight 失败固定 `mutation=false`,不得创建 task; - preflight 通过后在同一命令内 submit 并自动 dispatch,不要求用户再运行第二条命令; -- 未显式传 `--idempotency-key` 时,由 Target/repo/ref/MDTODO/verified commit 派生稳定键; +- 未显式传 `--idempotency-key` 时,由 Target/repo/ref/targetWorkspace/MDTODO 和完整请求 payload 派生稳定键; - 幂等重试遇到已有 running 或 terminal task 时,返回既有 attempt/run/session,不重复 dispatch; - 最终输出必须包含 task、attempt、run、command、runner 和 session identity。 - Target live 的默认 human、`-o json` 和 `-o yaml` 使用同一份有界投影: @@ -60,7 +61,7 @@ Artificer 内部执行边界: - primary workspace 只承载默认 UniDesk bundle、`tools/trans` 和 required skills; - 任务源码操作全部使用 `trans :`; -- `targetWorkspace` 必须是任务专属 worktree; +- `targetWorkspace` 是用户显式指定的任务工作目录; - runner 本地结果不得替代 Target 原入口证据; - `unidesk-trans`、runner tools bundle、`unidesk-ssh` SecretRef 和 endpoint presence 必须同时存在; - Secret 值不得进入 prompt、task、日志或报告。 diff --git a/docs/MDTODO/details/sub2api-upstream-reliability/R5.2_Task_Report.md b/docs/MDTODO/details/sub2api-upstream-reliability/R5.2_Task_Report.md new file mode 100644 index 00000000..637fb05c --- /dev/null +++ b/docs/MDTODO/details/sub2api-upstream-reliability/R5.2_Task_Report.md @@ -0,0 +1,25 @@ +# R5.2 任务报告 + +## 结果 + +- 已解决 [UniDesk #2310](https://github.com/pikasTech/unidesk/issues/2310)。 +- 实现提交:`69c37875`。 +- 独立 PR:[UniDesk #2313](https://github.com/pikasTech/unidesk/pull/2313)。 +- 未修改生产运行面,未自行合并。 + +## 改动 + +- AgentRun Target preflight 仅保留 Target 重入标记、显式 `targetWorkspace`、绝对路径和目录存在性校验。 +- 删除 Git 根、origin、dirty、branch、HEAD 和远端 ref 的全部读取及 admission 结论。 +- dirty、ahead、behind、detached、branch/ref/origin 不匹配和已有 diff 不产生 warning 或 blocker。 +- `repo`、`ref` 和 `targetWorkspace` 仅作为显式任务身份参与幂等键。 +- CLI human、JSON 和 YAML 输出统一披露 `gitStateInspected=false`,不再输出 clean、commit 或 ref relation。 +- apply 与 dispatch 路径未发现第二处 task worktree Git 状态读取或门禁。 + +## 验证 + +- `bun test scripts/src/agentrun.test.ts`:26 项通过,0 失败。 +- 回归夹具实际构造 dirty、ahead=1、behind=1、branch/ref/origin 不匹配状态,preflight 仍成功。 +- 绝对路径和目录存在性拒绝用例通过。 +- `bun scripts/cli.ts check --syntax-only`:11 项通过,0 失败。 +- `git diff --check`:通过。 diff --git a/docs/MDTODO/sub2api-upstream-reliability.md b/docs/MDTODO/sub2api-upstream-reliability.md index d015b9ff..13694149 100644 --- a/docs/MDTODO/sub2api-upstream-reliability.md +++ b/docs/MDTODO/sub2api-upstream-reliability.md @@ -288,6 +288,10 @@ 泛化改进 PaC consumer 首次 CI/CD bootstrap 与排错:source-artifact 在 PR 前验证流水线源码入口,debug-step 一次返回失败 task/step/exit code/有界日志,closeout --wait 持续观察并精确关联 source、GitOps、Argo 与 runtime digest,history 提示单 consumer 快路径;由 [GitHub issue #2307](https://github.com/pikasTech/unidesk/issues/2307) 跟踪,完成任务后将详细报告写入[任务报告](./details/sub2api-upstream-reliability/R5.1_Task_Report.md)。 +### R5.2 [completed] + +解决 [UniDesk #2310](https://github.com/pikasTech/unidesk/issues/2310):彻底废弃 AgentRun 对 task worktree 的 Git 状态校验,使 dirty、ahead、behind 和已有 diff 均不阻断 create/apply/dispatch;只保留显式 targetWorkspace、绝对路径和目录存在性校验,不读取 Git 状态形成 warning 或 blocker,不修改生产运行面,提交独立 PR 且不自行合并,完成任务后将详细报告写入[任务报告](./details/sub2api-upstream-reliability/R5.2_Task_Report.md)。 + ## R6 [completed] 处理 `529 Overloaded / API is at capacity` 客户可见错误:以 Sub2API 原生 Ops 证据锁定未触发 failover 的账号和规则缺口,在通用非 auth API-key 临时不可调度模板补 529 一分钟冷却并精准批量应用,排除 lyon9801,完成 runtime 回读和修复后短窗口复核,完成任务后将详细报告写入[任务报告](./details/sub2api-upstream-reliability/R6_Task_Report.md)。 diff --git a/docs/reference/agentrun.md b/docs/reference/agentrun.md index 1c220000..29578fca 100644 --- a/docs/reference/agentrun.md +++ b/docs/reference/agentrun.md @@ -273,21 +273,20 @@ Artificer 标题契约: - 默认直接执行 live 单命令,不把 `--dry-run` 作为派单前置步骤; - 未显式传 `--lane` 时,空选择或只传 YAML 默认 node 都使用 `config/agentrun.yaml#controlPlane.default`; -- live 先经 Target `trans` 重入并执行 source/worktree/Aipod/SecretRef 只读预检; +- live 先经 Target `trans` 重入,校验显式工作区路径与 Aipod/SecretRef; - 预检通过后在同一命令内 submit 并自动 dispatch; - 幂等重试不得重复创建 attempt 或重复 dispatch; - UniDesk 不实现 AgentRun queue 协议,也不把 task double-write 回旧 Code Queue。 Artificer Target preflight 必须在 task 创建前完成: -- `targetWorkspace` 是任务专属 Git worktree 根目录; -- worktree 必须 clean; -- origin 与 `--repo` 一致; -- current branch 与 `--ref` 一致; -- HEAD 与 origin 远端 ref commit 一致; +- `targetWorkspace` 必须显式提供为 Target 上的绝对路径,且路径存在并为目录; +- `repo` 和 `ref` 只作为用户显式提供的任务身份,不用于读取或判断工作区 Git 状态; +- dirty、ahead、behind、detached 和已有 diff 均不产生 warning 或 blocker; +- create、apply 和 dispatch 不得读取 task worktree Git 状态形成 admission 结论; - AipodSpec 渲染出的 `workspaceRef` 只允许 `kind/path`; - 默认 UniDesk resource bundle 保持继承,不把私有目标 repo 改成 runner primary bundle; -- 输出包含 projectId、session identity、SecretRef presence 和 HEAD/remote 关系; +- 输出包含 projectId、显式 Target 上下文、session identity 和 SecretRef presence; - 失败固定 `mutation=false`,且不创建 task; - 成功返回 task、attempt、run、command、runner 和 session identity。 diff --git a/scripts/src/agentrun.test.ts b/scripts/src/agentrun.test.ts index 1ef5a4b8..621c2004 100644 --- a/scripts/src/agentrun.test.ts +++ b/scripts/src/agentrun.test.ts @@ -1,4 +1,7 @@ -import { readFileSync, rmSync } from "node:fs"; +import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { spawnSync } from "node:child_process"; import { describe, expect, test } from "bun:test"; import { parseAgentRunClientConfigYaml, @@ -37,6 +40,12 @@ function renderedContentTypeOf(value: unknown): string { return contentType; } +function gitFixture(cwd: string, args: string[]): string { + const result = spawnSync("git", args, { cwd, encoding: "utf8" }); + if (result.status !== 0) throw new Error(String(result.stderr || result.stdout)); + return String(result.stdout).trim(); +} + const agentRunClientYaml = [ "version: 1", "kind: AgentRunConfig", @@ -219,13 +228,44 @@ 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", () => { + test("keeps only explicit absolute path and directory existence admission", () => { + expect(() => targetTaskRequestFromArgs([ + "--title", "检查 AgentRun 工作区路径", + "--target", "NC01", + "--target-workspace", "relative/workspace", + "--repo", "pikasTech/unidesk", + "--ref", "fix/path-check", + "--mdtodo-id", "R5.2", + ])).toThrow("--target-workspace 必须是 Target 上的绝对路径"); + + const request = targetTaskRequestFromArgs([ + "--title", "检查 AgentRun 工作区路径", + "--target", "NC01", + "--target-workspace", "/definitely/missing/agentrun-workspace", + "--repo", "pikasTech/unidesk", + "--ref", "fix/path-check", + "--mdtodo-id", "R5.2", + ]); + if (request === null) throw new Error("expected Target request"); + const markerEnv = "UNIDESK_AGENTRUN_TARGET_EXECUTED_PATH_TEST"; + const previous = process.env[markerEnv]; + process.env[markerEnv] = request.target; + try { + expect(() => preflightTargetTask(request, markerEnv)).toThrow("Target 工作区不存在或不是目录"); + } finally { + if (previous === undefined) delete process.env[markerEnv]; + else process.env[markerEnv] = previous; + } + }); + + test("requires trans re-entry but admits dirty, ahead, behind, and mismatched Git state", () => { + const workspace = mkdtempSync(join(tmpdir(), "unidesk-agentrun-target-")); const request = targetTaskRequestFromArgs([ "--title", "修复 SelfMedia 的 OCR 规划器", "--target", "NC01", - "--target-workspace", "/root/.worktrees/selfmedia/r6-ocr-visual-planner", + "--target-workspace", workspace, "--repo", "pikainc/selfmedia", - "--ref", "feat/r6-ocr-visual-planner", + "--ref", "declared/ref", "--mdtodo-id", "R6.3", ]); expect(request).not.toBeNull(); @@ -235,25 +275,53 @@ describe("Artificer Target one-command dispatch", () => { delete process.env[markerEnv]; try { expect(() => preflightTargetTask(request, markerEnv)).toThrow("只能在外层 trans 重入 NC01 后执行"); + + gitFixture(workspace, ["init", "-b", "actual/ref"]); + gitFixture(workspace, ["config", "user.email", "test@example.com"]); + gitFixture(workspace, ["config", "user.name", "AgentRun Test"]); + writeFileSync(join(workspace, "tracked.txt"), "base\n", "utf8"); + gitFixture(workspace, ["add", "tracked.txt"]); + gitFixture(workspace, ["commit", "-m", "base"]); + const baseCommit = gitFixture(workspace, ["rev-parse", "HEAD"]); + const baseTree = gitFixture(workspace, ["rev-parse", "HEAD^{tree}"]); + writeFileSync(join(workspace, "tracked.txt"), "local\n", "utf8"); + gitFixture(workspace, ["commit", "-am", "local ahead"]); + const remoteCommit = gitFixture(workspace, ["commit-tree", baseTree, "-p", baseCommit, "-m", "remote behind"]); + gitFixture(workspace, ["update-ref", "refs/remotes/origin/declared/ref", remoteCommit]); + gitFixture(workspace, ["remote", "add", "origin", "https://example.invalid/wrong/repository.git"]); + writeFileSync(join(workspace, "tracked.txt"), "dirty diff\n", "utf8"); + expect(gitFixture(workspace, ["rev-list", "--left-right", "--count", "HEAD...refs/remotes/origin/declared/ref"])) + .toMatch(/^1\s+1$/u); + expect(gitFixture(workspace, ["status", "--porcelain"])).not.toBe(""); + + process.env[markerEnv] = request.target; + const preflight = preflightTargetTask(request, markerEnv); + expect(preflight).toMatchObject({ + targetWorkspace: workspace, + workspaceRoot: workspace, + repository: "pikainc/selfmedia", + ref: "declared/ref", + sourceAuthority: { + kind: "explicit-target-context", + gitStateInspected: false, + valuesPrinted: false, + }, + }); + expect(preflight).not.toHaveProperty("verifiedCommit"); + expect(preflight).not.toHaveProperty("clean"); } finally { if (previous === undefined) delete process.env[markerEnv]; else process.env[markerEnv] = previous; + rmSync(workspace, { recursive: true, force: true }); } const preflight = { ...request, targetRoute: `${request.target}:${request.targetWorkspace}`, projectId: request.repository, workspaceRoot: request.targetWorkspace, - currentBranch: request.ref, - headCommit: "a".repeat(40), - clean: true, - originRepository: request.repository, - verifiedCommit: "a".repeat(40), - matchedRemoteRef: `refs/heads/${request.ref}`, - refRelation: "branch-and-head-match-remote-ref", sourceAuthority: { - kind: "target-workspace-origin", - credentialScope: "target-owned-git-credential", + kind: "explicit-target-context", + gitStateInspected: false, valuesPrinted: false, }, } satisfies AgentRunTargetTaskPreflight; @@ -275,16 +343,9 @@ describe("Artificer Target one-command dispatch", () => { targetRoute: "NC01:/root/unidesk/.worktree/base", projectId: "pikasTech/unidesk", workspaceRoot: "/root/unidesk/.worktree/base", - currentBranch: "fix/base", - headCommit: "a".repeat(40), - clean: true, - originRepository: "pikasTech/unidesk", - verifiedCommit: "a".repeat(40), - matchedRemoteRef: "refs/heads/fix/base", - refRelation: "branch-and-head-match-remote-ref", sourceAuthority: { - kind: "target-workspace-origin", - credentialScope: "target-owned-git-credential", + kind: "explicit-target-context", + gitStateInspected: false, valuesPrinted: false, }, } as const; @@ -300,8 +361,6 @@ describe("Artificer Target one-command dispatch", () => { ...fixture, targetRoute: `NC01:${fixture.targetWorkspace}`, workspaceRoot: fixture.targetWorkspace, - currentBranch: fixture.ref, - matchedRemoteRef: `refs/heads/${fixture.ref}`, }, { aipod: "Artificer", tenantId: "default", projectId: fixture.repository, prompt: fixture.prompt }, )); @@ -312,8 +371,6 @@ describe("Artificer Target one-command dispatch", () => { ...fixtures[0], targetRoute: `NC01:${fixtures[0].targetWorkspace}`, workspaceRoot: fixtures[0].targetWorkspace, - currentBranch: fixtures[0].ref, - matchedRemoteRef: `refs/heads/${fixtures[0].ref}`, } satisfies AgentRunTargetTaskPreflight; const operation = { aipod: "Artificer", tenantId: "default", projectId: replay.repository, prompt: fixtures[0].prompt }; const evidence = targetTaskOperationEvidence(replay, operation); @@ -497,12 +554,9 @@ describe("Artificer Target one-command dispatch", () => { projectId: "pikasTech/unidesk", repository: "pikasTech/unidesk", ref: "fix/artificer-compact-output", - verifiedCommit: "a".repeat(40), - refRelation: "branch-and-head-match-remote-ref", workspace: { - branch: "fix/artificer-compact-output", - headCommit: "a".repeat(40), - clean: true, + root: "/root/.worktrees/unidesk/artificer-compact-output", + gitStateInspected: false, }, prompt: hugePrompt, }, @@ -564,6 +618,9 @@ describe("Artificer Target one-command dispatch", () => { } expect(text).toContain("R6.4"); expect(text).toContain("dispatched"); + expect(text).toContain("gitStateInspected"); + expect(text).not.toContain("verifiedCommit"); + expect(text).not.toContain("refRelation"); } const jsonText = renderedTextOf(renderMutationSummary( @@ -592,6 +649,11 @@ describe("Artificer Target one-command dispatch", () => { }); expect((json.autoDispatch as Record).decision).toBe("dispatched"); expect((json.targetTask as Record).mdtodoId).toBe("R6.4"); + expect((json.targetTask as Record).workspace).toEqual({ + root: "/root/.worktrees/unidesk/artificer-compact-output", + gitStateInspected: false, + valuesPrinted: false, + }); expect(jsonCredentials).toMatchObject({ totalCount: 2, allPresent: true }); expect((jsonCredentials.provider as Record).presentCount).toBe(1); expect((jsonCredentials.tool as Record).presentCount).toBe(1); diff --git a/scripts/src/agentrun/render.ts b/scripts/src/agentrun/render.ts index 475d8126..2dc80469 100644 --- a/scripts/src/agentrun/render.ts +++ b/scripts/src/agentrun/render.ts @@ -532,8 +532,7 @@ export function renderTargetTaskMutationLines(targetTask: Record): Record 0) { - throw new AgentRunTargetTaskPreflightError( - "workspace-status", - "Target 固定工作区存在未提交修改;请先保存并按语义提交并行改动,再重新派单。", - { targetWorkspace: request.targetWorkspace, dirtyEntryCount }, - ); - } - - const currentBranch = git(request, ["branch", "--show-current"], "workspace-branch").trim() || "(detached)"; - const headCommit = git(request, ["rev-parse", "HEAD"], "workspace-head").trim().toLowerCase(); - const remote = git(request, ["ls-remote", "--exit-code", "origin", request.ref], "remote-ref"); - const refs = remote.split(/\r?\n/u) - .map((line) => line.trim().split(/\s+/u)) - .filter((parts) => /^[0-9a-f]{40}$/u.test(parts[0] ?? "") && (parts[1] ?? "").length > 0) - .map((parts) => ({ commit: parts[0] as string, ref: parts[1] as string })); - const selected = refs.find((item) => item.ref === `refs/heads/${request.ref}`) ?? refs[0]; - if (selected === undefined) { - throw new AgentRunTargetTaskPreflightError( - "remote-ref", - `Target 工作区 origin 中找不到 --ref ${request.ref}。`, - { repository: request.repository, ref: request.ref }, - ); - } - if (currentBranch !== request.ref || headCommit !== selected.commit.toLowerCase()) { - throw new AgentRunTargetTaskPreflightError( - "workspace-ref-relation", - "Target 任务 worktree 必须检出 --ref,且 HEAD 必须等于 origin 远端提交;请先完成分支切换、提交和推送。", - { - targetWorkspace: request.targetWorkspace, - currentBranch, - expectedBranch: request.ref, - headCommit, - remoteCommit: selected.commit, - matchedRemoteRef: selected.ref, - }, - ); - } - return { ...request, targetRoute: `${request.target}:${request.targetWorkspace}`, projectId: request.repository, workspaceRoot, - currentBranch, - headCommit, - clean: true, - originRepository, - verifiedCommit: selected.commit, - matchedRemoteRef: selected.ref, - refRelation: "branch-and-head-match-remote-ref", sourceAuthority: { - kind: "target-workspace-origin", - credentialScope: "target-owned-git-credential", + kind: "explicit-target-context", + gitStateInspected: false, valuesPrinted: false, }, }; @@ -212,7 +123,7 @@ export function preflightTargetTask(request: AgentRunTargetTaskRequest, markerEn export function targetTaskPrompt(preflight: AgentRunTargetTaskPreflight, userPrompt: string | null): string { const context = [ - "任务目标上下文(由 UniDesk YAML-first CLI 在创建 task 前经 Target trans 只读核验):", + "任务目标上下文(由用户显式提供,并由 UniDesk CLI 在 Target trans 重入后校验目录存在):", `- 中文任务标题: ${preflight.title}`, `- MDTODO ID: ${preflight.mdtodoId}`, `- Target: ${preflight.target}`, @@ -220,9 +131,7 @@ export function targetTaskPrompt(preflight: AgentRunTargetTaskPreflight, userPro `- targetRoute: ${preflight.targetRoute}`, `- repository: ${preflight.repository}`, `- ref: ${preflight.ref}`, - `- verifiedCommit: ${preflight.verifiedCommit}`, - `- refRelation: ${preflight.refRelation}`, - "- sourceAuthority: target-workspace-origin(凭据值不进入 task、prompt 或日志)", + "- sourceAuthority: explicit-target-context(不读取 Git 状态,不把 dirty/ahead/behind/diff 作为门禁或警告)", "", "执行边界:", `- 目标源码的探测、Git、编辑、验证和运行面观察全部使用 \`trans ${preflight.target}:${preflight.targetWorkspace} \` 或对应 k3s route。`, @@ -242,14 +151,10 @@ export function targetTaskDisclosure(preflight: AgentRunTargetTaskPreflight): Re repository: preflight.repository, ref: preflight.ref, projectId: preflight.projectId, - verifiedCommit: preflight.verifiedCommit, - matchedRemoteRef: preflight.matchedRemoteRef, - refRelation: preflight.refRelation, workspace: { root: preflight.workspaceRoot, - branch: preflight.currentBranch, - headCommit: preflight.headCommit, - clean: preflight.clean, + gitStateInspected: false, + valuesPrinted: false, }, sourceAuthority: preflight.sourceAuthority, mutation: false, @@ -272,7 +177,6 @@ export function targetTaskOperationEvidence( repository: preflight.repository, ref: preflight.ref, targetWorkspace: preflight.targetWorkspace, - verifiedCommit: preflight.verifiedCommit, }; const fingerprint = sha256Hex(JSON.stringify({ version: "v1", scope, payloadHash })); return { @@ -353,33 +257,6 @@ 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, - encoding: "utf8", - timeout: 15_000, - env: { ...process.env, GIT_TERMINAL_PROMPT: "0" }, - maxBuffer: 1024 * 1024, - }); - if (result.error !== undefined || result.status !== 0) { - const stderr = String(result.stderr ?? "").trim().split(/\r?\n/u)[0] ?? ""; - throw new AgentRunTargetTaskPreflightError( - stage, - `Target Git 只读预检失败(${stage});请先在 ${request.target}:${request.targetWorkspace} 修复 origin/ref/凭据可达性。`, - { - target: request.target, - targetWorkspace: request.targetWorkspace, - repository: request.repository, - ref: request.ref, - exitCode: result.status, - timedOut: result.error?.name === "ETIMEDOUT", - stderrSummary: redactGitError(stderr), - }, - ); - } - return String(result.stdout ?? ""); -} - function option(args: string[], name: string): string | null { const flag = `--${name}`; for (let index = 0; index < args.length; index += 1) { @@ -437,19 +314,6 @@ function validateMdtodoId(value: string): string { return value; } -function repositoryFromRemote(value: string): string | null { - const normalized = value.replace(/\.git$/u, ""); - const match = normalized.match(/^(?:git@github\.com:|https:\/\/github\.com\/)([A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+)$/u); - return match?.[1] ?? null; -} - -function redactGitError(value: string): string { - return value - .replace(/https?:\/\/[^@\s]+@/gu, "https://[redacted]@") - .replace(/(token|password|authorization)[=:][^\s]+/giu, "$1=[redacted]") - .slice(0, 240); -} - function sha256(value: string): string { return `sha256:${sha256Hex(value)}`; } diff --git a/scripts/src/pikaoa-delivery-renderer.test.ts b/scripts/src/pikaoa-delivery-renderer.test.ts index 374cfd27..d28421f3 100644 --- a/scripts/src/pikaoa-delivery-renderer.test.ts +++ b/scripts/src/pikaoa-delivery-renderer.test.ts @@ -158,6 +158,8 @@ describe("PikaOA development PaC renderer", () => { expect(manifest.match(/key: DATABASE_URL/gu)).toHaveLength(3); expect(manifest.match(/name: OA_ADMIN_TOKEN/gu)).toHaveLength(1); expect(manifest.match(/key: OA_ADMIN_TOKEN/gu)).toHaveLength(1); + expect(manifest).toContain("name: PIKAOA_MAX_UPLOAD_BYTES"); + expect(manifest).toContain('value: "104857600"'); expect(script).toContain("grep -Eq '@sha256:[0-9a-f]{64}$'"); expect(manifest).toContain("image: __PIKAOA_API_IMAGE__"); expect(manifest).toContain("pikaoa.unidesk.io/source-commit: __PIKAOA_SOURCE_COMMIT__"); diff --git a/scripts/src/pikaoa-runtime-manifest.ts b/scripts/src/pikaoa-runtime-manifest.ts index e9c243ad..90265c50 100644 --- a/scripts/src/pikaoa-runtime-manifest.ts +++ b/scripts/src/pikaoa-runtime-manifest.ts @@ -114,6 +114,7 @@ export function renderPikaoaRuntimeManifest( ], target.probes.workerLivenessPath, target.probes.workerReadinessPath), deployment(target, context, "web", context.webImage, webSelector, target.runtime.webPort, {}, {}, [ { name: target.runtime.webApiUpstreamEnv, value: `http://${target.runtime.apiServiceName}.${context.namespace}.svc.cluster.local:${target.runtime.apiPort}` }, + { name: "PIKAOA_MAX_UPLOAD_BYTES", value: String(target.runtime.attachment.maxBytes) }, ], target.probes.webLivenessPath, target.probes.webReadinessPath), ]; }