@clawos-dev/clawd 0.2.482 → 0.2.483

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.
@@ -21374,6 +21374,29 @@ var DeploySecretPresenceSchema = external_exports.object({
21374
21374
  env: external_exports.string().min(1),
21375
21375
  setAt: external_exports.number().optional()
21376
21376
  }).strict();
21377
+ var DEPLOY_STAGE_STATUSES = ["pending", "running", "done", "failed", "waiting", "skipped"];
21378
+ var DeployStageStatusSchema = external_exports.enum(DEPLOY_STAGE_STATUSES);
21379
+ var DEPLOY_STAGE_KEYS = ["build", "smoke", "deploy"];
21380
+ var DeployStageKeySchema = external_exports.enum(DEPLOY_STAGE_KEYS);
21381
+ var DeployJobSchema = external_exports.object({
21382
+ key: DeployStageKeySchema,
21383
+ status: DeployStageStatusSchema,
21384
+ /** 这个 job 在 GitHub 上的页面。还没跑起来的段没有 */
21385
+ url: external_exports.string().optional(),
21386
+ /** GitHub 的 job databaseId,`gh run rerun --job` 要用。还没跑起来的段没有 */
21387
+ jobId: external_exports.number().optional(),
21388
+ startedAt: external_exports.number().optional(),
21389
+ completedAt: external_exports.number().optional()
21390
+ }).strict();
21391
+ var DeployRunSchema = external_exports.object({
21392
+ runId: external_exports.number(),
21393
+ headSha: external_exports.string().min(1),
21394
+ /** run 整体只有三态。**别跟 job 的六态混用**:run 没有「跳过」也没有「等审批」 */
21395
+ status: external_exports.enum(["queued", "running", "completed"]),
21396
+ htmlUrl: external_exports.string().min(1),
21397
+ /** run 刚入队时可能一条都还没有 */
21398
+ jobs: external_exports.array(DeployJobSchema)
21399
+ }).strict();
21377
21400
  var DeployStateSchema = external_exports.object({
21378
21401
  personaId: external_exports.string().min(1),
21379
21402
  repo: DeployRepoSchema.optional(),
@@ -21381,6 +21404,11 @@ var DeployStateSchema = external_exports.object({
21381
21404
  scan: DeployScanSchema.optional(),
21382
21405
  /** 没仓库时缺席(查不了) */
21383
21406
  secrets: external_exports.array(DeploySecretPresenceSchema).optional(),
21407
+ /**
21408
+ * 最新那次流水线走到哪了。**缺席是常态**:没推过、gh 查不到、仓库还没有 run,
21409
+ * 都不该让整页打不开——同 `secrets` 的处理。
21410
+ */
21411
+ run: DeployRunSchema.optional(),
21384
21412
  /**
21385
21413
  * `.clawd/deploy.json` 读不出来(人手改坏了)时的人话。有它就没有 `file`。
21386
21414
  * 页面据此给一条能走的路(重新扫描会整份重写),而不是白屏。
@@ -21408,6 +21436,11 @@ var DeployPutSecretArgsSchema = external_exports.object({
21408
21436
  // 环境变量名 → 值。key 必须是该 secret 声明过的 env 之一,handler 校验。
21409
21437
  values: external_exports.record(external_exports.string(), external_exports.string())
21410
21438
  }).strict();
21439
+ var DeployRerunJobArgsSchema = external_exports.object({
21440
+ personaId: external_exports.string().min(1),
21441
+ /** GitHub 的 job databaseId。**是数字**——字符串会在拼命令时静默出错 */
21442
+ jobId: external_exports.number()
21443
+ }).strict();
21411
21444
  var DeployReportFindingArgsSchema = external_exports.object({
21412
21445
  personaId: external_exports.string().min(1),
21413
21446
  kind: external_exports.enum(["cli", "secret", "asset"]),
@@ -23923,6 +23956,10 @@ var METHOD_DOCS = {
23923
23956
  summary: "\u53D1\u5E03 = \u63D0\u4EA4\u5E76\u63A8\u9001\u5230\u4ED3\u5E93\uFF1A\u91CD\u5199 workflow \u6A21\u677F \u2192 git add -A \u2192 \u7A7A\u63D0\u4EA4\u4E5F\u5141\u8BB8 \u2192 push\u3002\u4ED3\u5E93\u91CC\u7684\u6D41\u6C34\u7EBF\u63A5\u7740\u6784\u955C\u50CF\u3001\u5728\u96C6\u7FA4\u4E0A\u771F\u8D77\u4E00\u6B21\u3001\u767B\u8BB0\u6210\u6B63\u5F0F\uFF0C\u8FDB\u5EA6\u5728 GitHub Actions \u770B\u3002\u524D\u7F6E\uFF1A\u5DF2\u7ED1\u5B9A\u4ED3\u5E93\u3001\u6709\u6E05\u5355\u3001\u6BCF\u4E2A\u53D8\u91CF\u540D\u90FD\u5728 Secrets \u91CC\uFF1Bowner-only",
23924
23957
  args: DeployPersonaArgsSchema
23925
23958
  },
23959
+ "deploy:rerunJob": {
23960
+ summary: "\u539F\u6837\u91CD\u8DD1\u6700\u65B0\u90A3\u6761\u6D41\u6C34\u7EBF\u91CC\u5931\u8D25\u7684\u67D0\u4E00\u6BB5\uFF08gh run rerun --job\uFF09\u3002**\u540C\u4E00\u4E2A commit\u3001\u540C\u4E00\u4EFD\u8F93\u5165**\uFF0C\u4E0D\u4EA7\u751F\u65B0\u63D0\u4EA4\u2014\u2014\u6539\u4E86 persona \u5185\u5BB9\u6216\u51ED\u636E\u8981\u8D70 deploy:release\u3002jobId \u5FC5\u987B\u5C5E\u4E8E\u6700\u65B0\u90A3\u6761 run\uFF1Bowner-only",
23961
+ args: DeployRerunJobArgsSchema
23962
+ },
23926
23963
  "shift:list": {
23927
23964
  summary: "\u5217\u51FA owner \u7684\u5168\u90E8 shift\uFF08\u6392\u73ED\uFF09\uFF0C\u7ED9 UI \u65E5\u5386\u6E32\u67D3\u7528\uFF1Bowner-only",
23928
23965
  args: NO_ARGS
package/dist/cli.cjs CHANGED
@@ -6025,7 +6025,7 @@ var init_environment_schemas = __esm({
6025
6025
  function repoSlug(url) {
6026
6026
  return url.trim().replace(/^git@[^:]+:/, "").replace(/^https?:\/\/[^/]+\//, "").replace(/\.git$/, "");
6027
6027
  }
6028
- var DeployFileSchema, DeployScanStatusSchema, DeployScanSchema, DeployRepoSchema, DeploySecretPresenceSchema, DeployStateSchema, DeployReleaseResultSchema, DeployPersonaArgsSchema, DeployPatchArgsSchema, DeployPutSecretArgsSchema, DeployReportFindingArgsSchema, DeployFinishScanArgsSchema, PERSONA_REPO_ORG;
6028
+ var DeployFileSchema, DeployScanStatusSchema, DeployScanSchema, DeployRepoSchema, DeploySecretPresenceSchema, DEPLOY_STAGE_STATUSES, DeployStageStatusSchema, DEPLOY_STAGE_KEYS, DeployStageKeySchema, DeployJobSchema, DeployRunSchema, DeployStateSchema, DeployReleaseResultSchema, DeployPersonaArgsSchema, DeployPatchArgsSchema, DeployPutSecretArgsSchema, DeployRerunJobArgsSchema, DeployReportFindingArgsSchema, DeployFinishScanArgsSchema, PERSONA_REPO_ORG;
6029
6029
  var init_deploy_schemas = __esm({
6030
6030
  "../protocol/src/deploy-schemas.ts"() {
6031
6031
  "use strict";
@@ -6061,6 +6061,29 @@ var init_deploy_schemas = __esm({
6061
6061
  env: external_exports.string().min(1),
6062
6062
  setAt: external_exports.number().optional()
6063
6063
  }).strict();
6064
+ DEPLOY_STAGE_STATUSES = ["pending", "running", "done", "failed", "waiting", "skipped"];
6065
+ DeployStageStatusSchema = external_exports.enum(DEPLOY_STAGE_STATUSES);
6066
+ DEPLOY_STAGE_KEYS = ["build", "smoke", "deploy"];
6067
+ DeployStageKeySchema = external_exports.enum(DEPLOY_STAGE_KEYS);
6068
+ DeployJobSchema = external_exports.object({
6069
+ key: DeployStageKeySchema,
6070
+ status: DeployStageStatusSchema,
6071
+ /** 这个 job 在 GitHub 上的页面。还没跑起来的段没有 */
6072
+ url: external_exports.string().optional(),
6073
+ /** GitHub 的 job databaseId,`gh run rerun --job` 要用。还没跑起来的段没有 */
6074
+ jobId: external_exports.number().optional(),
6075
+ startedAt: external_exports.number().optional(),
6076
+ completedAt: external_exports.number().optional()
6077
+ }).strict();
6078
+ DeployRunSchema = external_exports.object({
6079
+ runId: external_exports.number(),
6080
+ headSha: external_exports.string().min(1),
6081
+ /** run 整体只有三态。**别跟 job 的六态混用**:run 没有「跳过」也没有「等审批」 */
6082
+ status: external_exports.enum(["queued", "running", "completed"]),
6083
+ htmlUrl: external_exports.string().min(1),
6084
+ /** run 刚入队时可能一条都还没有 */
6085
+ jobs: external_exports.array(DeployJobSchema)
6086
+ }).strict();
6064
6087
  DeployStateSchema = external_exports.object({
6065
6088
  personaId: external_exports.string().min(1),
6066
6089
  repo: DeployRepoSchema.optional(),
@@ -6068,6 +6091,11 @@ var init_deploy_schemas = __esm({
6068
6091
  scan: DeployScanSchema.optional(),
6069
6092
  /** 没仓库时缺席(查不了) */
6070
6093
  secrets: external_exports.array(DeploySecretPresenceSchema).optional(),
6094
+ /**
6095
+ * 最新那次流水线走到哪了。**缺席是常态**:没推过、gh 查不到、仓库还没有 run,
6096
+ * 都不该让整页打不开——同 `secrets` 的处理。
6097
+ */
6098
+ run: DeployRunSchema.optional(),
6071
6099
  /**
6072
6100
  * `.clawd/deploy.json` 读不出来(人手改坏了)时的人话。有它就没有 `file`。
6073
6101
  * 页面据此给一条能走的路(重新扫描会整份重写),而不是白屏。
@@ -6095,6 +6123,11 @@ var init_deploy_schemas = __esm({
6095
6123
  // 环境变量名 → 值。key 必须是该 secret 声明过的 env 之一,handler 校验。
6096
6124
  values: external_exports.record(external_exports.string(), external_exports.string())
6097
6125
  }).strict();
6126
+ DeployRerunJobArgsSchema = external_exports.object({
6127
+ personaId: external_exports.string().min(1),
6128
+ /** GitHub 的 job databaseId。**是数字**——字符串会在拼命令时静默出错 */
6129
+ jobId: external_exports.number()
6130
+ }).strict();
6098
6131
  DeployReportFindingArgsSchema = external_exports.object({
6099
6132
  personaId: external_exports.string().min(1),
6100
6133
  kind: external_exports.enum(["cli", "secret", "asset"]),
@@ -8875,6 +8908,10 @@ var init_methods = __esm({
8875
8908
  summary: "\u53D1\u5E03 = \u63D0\u4EA4\u5E76\u63A8\u9001\u5230\u4ED3\u5E93\uFF1A\u91CD\u5199 workflow \u6A21\u677F \u2192 git add -A \u2192 \u7A7A\u63D0\u4EA4\u4E5F\u5141\u8BB8 \u2192 push\u3002\u4ED3\u5E93\u91CC\u7684\u6D41\u6C34\u7EBF\u63A5\u7740\u6784\u955C\u50CF\u3001\u5728\u96C6\u7FA4\u4E0A\u771F\u8D77\u4E00\u6B21\u3001\u767B\u8BB0\u6210\u6B63\u5F0F\uFF0C\u8FDB\u5EA6\u5728 GitHub Actions \u770B\u3002\u524D\u7F6E\uFF1A\u5DF2\u7ED1\u5B9A\u4ED3\u5E93\u3001\u6709\u6E05\u5355\u3001\u6BCF\u4E2A\u53D8\u91CF\u540D\u90FD\u5728 Secrets \u91CC\uFF1Bowner-only",
8876
8909
  args: DeployPersonaArgsSchema
8877
8910
  },
8911
+ "deploy:rerunJob": {
8912
+ summary: "\u539F\u6837\u91CD\u8DD1\u6700\u65B0\u90A3\u6761\u6D41\u6C34\u7EBF\u91CC\u5931\u8D25\u7684\u67D0\u4E00\u6BB5\uFF08gh run rerun --job\uFF09\u3002**\u540C\u4E00\u4E2A commit\u3001\u540C\u4E00\u4EFD\u8F93\u5165**\uFF0C\u4E0D\u4EA7\u751F\u65B0\u63D0\u4EA4\u2014\u2014\u6539\u4E86 persona \u5185\u5BB9\u6216\u51ED\u636E\u8981\u8D70 deploy:release\u3002jobId \u5FC5\u987B\u5C5E\u4E8E\u6700\u65B0\u90A3\u6761 run\uFF1Bowner-only",
8913
+ args: DeployRerunJobArgsSchema
8914
+ },
8878
8915
  "shift:list": {
8879
8916
  summary: "\u5217\u51FA owner \u7684\u5168\u90E8 shift\uFF08\u6392\u73ED\uFF09\uFF0C\u7ED9 UI \u65E5\u5386\u6E32\u67D3\u7528\uFF1Bowner-only",
8880
8917
  args: NO_ARGS
@@ -54780,7 +54817,9 @@ var L = [
54780
54817
  "",
54781
54818
  "jobs:",
54782
54819
  " build:",
54783
- " name: build \u2014 \u6784 persona \u5C42\u955C\u50CF",
54820
+ // name 就是段 key:daemon jobs API 回的 name 认段(那接口不返回 job 的 key)。
54821
+ // 中文说明归 clawd 的上云页面,这里保持机器好认。
54822
+ " name: build",
54784
54823
  " runs-on: ubuntu-latest",
54785
54824
  " timeout-minutes: 30",
54786
54825
  " outputs:",
@@ -54886,7 +54925,7 @@ var L = [
54886
54925
  ' run: docker push "$REGISTRY/$IMAGE_NAME:${{ steps.plan.outputs.tag }}"',
54887
54926
  "",
54888
54927
  " smoke:",
54889
- " name: smoke \u2014 \u96C6\u7FA4\u4E0A\u771F\u8D77\u4E00\u6B21",
54928
+ " name: smoke",
54890
54929
  " needs: build",
54891
54930
  " runs-on: ubuntu-latest",
54892
54931
  " timeout-minutes: 20",
@@ -54917,7 +54956,7 @@ var L = [
54917
54956
  " --run-number '${{ github.run_number }}'",
54918
54957
  "",
54919
54958
  " deploy:",
54920
- " name: deploy \u2014 \u767B\u8BB0\u6210\u6B63\u5F0F",
54959
+ " name: deploy",
54921
54960
  " needs: [build, smoke]",
54922
54961
  " runs-on: ubuntu-latest",
54923
54962
  " # \u4ED3\u5E93\u914D\u4E86 production \u73AF\u5883\u7684\u300C\u987B\u5BA1\u6838\u4EBA\u6279\u51C6\u300D\uFF0Cjob \u5C31\u505C\u5728\u8FD9\u91CC\u7B49\u4EBA\u70B9\uFF1B\u6CA1\u914D\u5C31\u76F4\u63A5\u8FC7\u3002",
@@ -55110,8 +55149,8 @@ async function listSecrets(exec, dir) {
55110
55149
  throw new RepoError(`gh secret list \u56DE\u7684\u4E0D\u662F JSON\uFF1A${tail(r.stdout, 200)}`);
55111
55150
  }
55112
55151
  return rows.filter((x) => typeof x.name === "string" && x.name.length > 0).map((x) => {
55113
- const at = x.updatedAt ? Date.parse(x.updatedAt) : Number.NaN;
55114
- return { env: x.name, ...Number.isFinite(at) ? { setAt: at } : {} };
55152
+ const at2 = x.updatedAt ? Date.parse(x.updatedAt) : Number.NaN;
55153
+ return { env: x.name, ...Number.isFinite(at2) ? { setAt: at2 } : {} };
55115
55154
  });
55116
55155
  }
55117
55156
  async function release(exec, args) {
@@ -55127,6 +55166,83 @@ async function release(exec, args) {
55127
55166
  return { sha, actionsUrl: `https://github.com/${repoSlug(info.url)}/actions` };
55128
55167
  }
55129
55168
 
55169
+ // src/deploy/run-status.ts
55170
+ init_protocol();
55171
+ var TIMEOUT_MS = 8e3;
55172
+ var pipelineJobKey = (name) => DEPLOY_STAGE_KEYS.find((k2) => name === k2 || name.startsWith(`${k2} `));
55173
+ function stageStatusOf(status, conclusion) {
55174
+ if (status === "queued" || status === "requested" || status === "pending") return "pending";
55175
+ if (status === "waiting") return "waiting";
55176
+ if (status === "in_progress") return "running";
55177
+ if (conclusion === "success") return "done";
55178
+ if (conclusion === "skipped") return "skipped";
55179
+ return "failed";
55180
+ }
55181
+ function runStatusOf(status) {
55182
+ if (status === "completed") return "completed";
55183
+ if (status === "queued" || status === "waiting" || status === "requested" || status === "pending") return "queued";
55184
+ return "running";
55185
+ }
55186
+ async function ghJson(exec, path97) {
55187
+ const r = await exec("gh", ["api", path97], { timeoutMs: TIMEOUT_MS });
55188
+ if (r.spawnError || r.code !== 0) return void 0;
55189
+ try {
55190
+ return JSON.parse(r.stdout);
55191
+ } catch {
55192
+ return void 0;
55193
+ }
55194
+ }
55195
+ function at(iso) {
55196
+ if (typeof iso !== "string") return void 0;
55197
+ const ms = Date.parse(iso);
55198
+ return Number.isNaN(ms) ? void 0 : ms;
55199
+ }
55200
+ function toJob(raw) {
55201
+ const name = typeof raw.name === "string" ? raw.name : "";
55202
+ const key = pipelineJobKey(name);
55203
+ if (key === void 0) return void 0;
55204
+ const startedAt = at(raw.started_at);
55205
+ const completedAt = at(raw.completed_at);
55206
+ return {
55207
+ key,
55208
+ status: stageStatusOf(String(raw.status ?? ""), typeof raw.conclusion === "string" ? raw.conclusion : null),
55209
+ ...typeof raw.html_url === "string" ? { url: raw.html_url } : {},
55210
+ ...typeof raw.id === "number" ? { jobId: raw.id } : {},
55211
+ ...startedAt !== void 0 ? { startedAt } : {},
55212
+ ...completedAt !== void 0 ? { completedAt } : {}
55213
+ };
55214
+ }
55215
+ async function latestRun(exec, slug) {
55216
+ const listed = await ghJson(exec, `/repos/${slug}/actions/runs?per_page=1`);
55217
+ const raw = listed?.workflow_runs?.[0];
55218
+ if (!raw || typeof raw.id !== "number" || typeof raw.head_sha !== "string" || typeof raw.html_url !== "string") {
55219
+ return void 0;
55220
+ }
55221
+ const runId = raw.id;
55222
+ const listedJobs = await ghJson(exec, `/repos/${slug}/actions/runs/${runId}/jobs`);
55223
+ const rawJobs = listedJobs?.jobs ?? [];
55224
+ const byKey = /* @__PURE__ */ new Map();
55225
+ for (const rawJob of rawJobs) {
55226
+ const job = toJob(rawJob);
55227
+ if (job) byKey.set(job.key, job);
55228
+ }
55229
+ return {
55230
+ runId,
55231
+ headSha: raw.head_sha,
55232
+ status: runStatusOf(String(raw.status ?? "")),
55233
+ htmlUrl: raw.html_url,
55234
+ // 按流水线的顺序排,不跟着 GitHub 返回的顺序走
55235
+ jobs: DEPLOY_STAGE_KEYS.map((k2) => byKey.get(k2)).filter((j) => j !== void 0)
55236
+ };
55237
+ }
55238
+ async function rerunJob(exec, slug, runId, jobId) {
55239
+ const r = await exec("gh", ["run", "rerun", String(runId), "--job", String(jobId), "--repo", slug], {
55240
+ timeoutMs: TIMEOUT_MS
55241
+ });
55242
+ if (r.spawnError) throw new Error(`\u672C\u673A\u8DD1\u4E0D\u4E86 gh\uFF1A${r.spawnError}`);
55243
+ if (r.code !== 0) throw new Error((r.stderr || r.stdout).trim().slice(-400) || `gh \u9000\u51FA\u7801 ${r.code}`);
55244
+ }
55245
+
55130
55246
  // src/deploy/scan-task.ts
55131
55247
  var import_node_path32 = __toESM(require("path"), 1);
55132
55248
  var BUTLER_PERSONA_ID = "persona-clawd-butler";
@@ -55177,6 +55293,7 @@ function reconcileInterruptedScans(deps) {
55177
55293
 
55178
55294
  // src/handlers/deploy.ts
55179
55295
  var SECRETS_CACHE_MS = 2e4;
55296
+ var RUN_CACHE_MS = 4e3;
55180
55297
  function ensureOwner(ctx) {
55181
55298
  if (!ctx || ctx.principal.kind !== "owner") {
55182
55299
  throw new ClawdError(ERROR_CODES.UNAUTHORIZED, "UNAUTHORIZED: deploy:* requires owner ctx");
@@ -55197,6 +55314,7 @@ function mustBeScanning(scan, method) {
55197
55314
  function buildDeployHandlers(deps) {
55198
55315
  const cacheMs = deps.secretsCacheMs ?? SECRETS_CACHE_MS;
55199
55316
  const secretsCache = /* @__PURE__ */ new Map();
55317
+ const runCache = /* @__PURE__ */ new Map();
55200
55318
  function mustPersona(personaId2) {
55201
55319
  if (!deps.hasPersona(personaId2)) {
55202
55320
  throw new ClawdError(ERROR_CODES.PERSONA_NOT_FOUND, `PERSONA_NOT_FOUND: \u6CA1\u6709\u8FD9\u4E2A persona\uFF08${personaId2}\uFF09`);
@@ -55227,12 +55345,20 @@ function buildDeployHandlers(deps) {
55227
55345
  scan = { ...scan, notes: stale ? [...kept, stale] : kept };
55228
55346
  }
55229
55347
  let secrets;
55348
+ let run;
55230
55349
  if (repo2) {
55231
55350
  try {
55232
55351
  secrets = await cachedSecrets(personaId2, dir);
55233
55352
  } catch {
55234
55353
  secrets = void 0;
55235
55354
  }
55355
+ const hit = runCache.get(personaId2);
55356
+ if (hit && deps.now() - hit.at < RUN_CACHE_MS) {
55357
+ run = hit.run;
55358
+ } else {
55359
+ run = await latestRun(deps.exec, repoSlug(repo2.url));
55360
+ runCache.set(personaId2, { at: deps.now(), run });
55361
+ }
55236
55362
  }
55237
55363
  return {
55238
55364
  personaId: personaId2,
@@ -55240,6 +55366,7 @@ function buildDeployHandlers(deps) {
55240
55366
  ...file ? { file } : {},
55241
55367
  ...scan ? { scan } : {},
55242
55368
  ...secrets ? { secrets } : {},
55369
+ ...run ? { run } : {},
55243
55370
  ...fileError ? { fileError } : {}
55244
55371
  };
55245
55372
  }
@@ -55390,11 +55517,32 @@ function buildDeployHandlers(deps) {
55390
55517
  if (blockers.length > 0) throw invalid(`\u8FD8\u5DEE\uFF1A${blockers.join("\uFF1B")}`);
55391
55518
  try {
55392
55519
  const result = await release(deps.exec, { dir, now: deps.now });
55520
+ runCache.delete(personaId2);
55393
55521
  return { response: { type: "deploy:release", ...result } };
55394
55522
  } catch (err) {
55395
55523
  throw toClawdError(err);
55396
55524
  }
55397
55525
  };
55526
+ const rerunJobHandler = async (frame, _client, ctx) => {
55527
+ ensureOwner(ctx);
55528
+ const { personaId: personaId2, jobId } = parseArgs2(DeployRerunJobArgsSchema, frame, "deploy:rerunJob");
55529
+ const dir = mustPersona(personaId2);
55530
+ const repo2 = await repoInfo(deps.exec, dir);
55531
+ if (!repo2) throw invalid("\u8FD8\u6CA1\u6709\u4ED3\u5E93\uFF0C\u5148\u70B9\u4E00\u6B21\u300C\u53D1\u5E03\u5230\u4E91\u4E0A\u300D\u5EFA\u4ED3");
55532
+ const slug = repoSlug(repo2.url);
55533
+ const run = await latestRun(deps.exec, slug);
55534
+ if (!run) throw invalid("\u67E5\u4E0D\u5230\u8FD9\u4E2A\u4ED3\u5E93\u4E0A\u7684\u6D41\u6C34\u7EBF\uFF08gh \u6CA1\u54CD\u5E94\uFF0C\u6216\u8005\u8FD8\u6CA1\u8DD1\u8FC7\uFF09");
55535
+ if (!run.jobs.some((j) => j.jobId === jobId)) {
55536
+ throw invalid("\u8FD9\u4E00\u6BB5\u4E0D\u5728\u6700\u65B0\u90A3\u6761\u6D41\u6C34\u7EBF\u91CC\u4E86\uFF0C\u5237\u65B0\u4E00\u4E0B\u518D\u91CD\u8BD5");
55537
+ }
55538
+ try {
55539
+ await rerunJob(deps.exec, slug, run.runId, jobId);
55540
+ runCache.delete(personaId2);
55541
+ } catch (err) {
55542
+ throw new ClawdError(ERROR_CODES.INTERNAL, err instanceof Error ? err.message : String(err));
55543
+ }
55544
+ return view(personaId2);
55545
+ };
55398
55546
  return {
55399
55547
  "deploy:start": start,
55400
55548
  "deploy:get": get,
@@ -55402,7 +55550,8 @@ function buildDeployHandlers(deps) {
55402
55550
  "deploy:putSecret": putSecret,
55403
55551
  "deploy:reportFinding": reportFinding,
55404
55552
  "deploy:finishScan": finishScan,
55405
- "deploy:release": releaseHandler
55553
+ "deploy:release": releaseHandler,
55554
+ "deploy:rerunJob": rerunJobHandler
55406
55555
  };
55407
55556
  }
55408
55557
 
@@ -62418,9 +62567,9 @@ function lineToSearchableText(raw) {
62418
62567
  function escapeRegExp(s) {
62419
62568
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
62420
62569
  }
62421
- function makeSnippet(text, at, needleLength) {
62422
- const from = Math.max(0, at - SNIPPET_PAD);
62423
- const to = Math.min(text.length, at + needleLength + SNIPPET_PAD);
62570
+ function makeSnippet(text, at2, needleLength) {
62571
+ const from = Math.max(0, at2 - SNIPPET_PAD);
62572
+ const to = Math.min(text.length, at2 + needleLength + SNIPPET_PAD);
62424
62573
  const body = text.slice(from, to).replace(/\s+/g, " ").trim();
62425
62574
  return `${from > 0 ? "\u2026 " : ""}${body}${to < text.length ? " \u2026" : ""}`;
62426
62575
  }
@@ -62448,9 +62597,9 @@ function searchTranscriptFile(file, query) {
62448
62597
  }
62449
62598
  const text = lineToSearchableText(parsed);
62450
62599
  if (text === null) continue;
62451
- const at = text.toLowerCase().indexOf(query.toLowerCase());
62452
- if (at < 0) continue;
62453
- return makeSnippet(text, at, query.length);
62600
+ const at2 = text.toLowerCase().indexOf(query.toLowerCase());
62601
+ if (at2 < 0) continue;
62602
+ return makeSnippet(text, at2, query.length);
62454
62603
  }
62455
62604
  return null;
62456
62605
  }
@@ -64153,6 +64302,7 @@ var METHOD_GRANT_MAP = {
64153
64302
  "deploy:reportFinding": ADMIN_ANY,
64154
64303
  "deploy:finishScan": ADMIN_ANY,
64155
64304
  "deploy:release": ADMIN_ANY,
64305
+ "deploy:rerunJob": ADMIN_ANY,
64156
64306
  // ---- session:* / chat:* 业务方法(v2 Phase 8 两层模型)----
64157
64307
  // dispatcher 不验资源,handler 内按 ctx + frame.args 反查 ownerPersonaId 调 assertGrant。
64158
64308
  // owner 自动通过(ctx 自带 '*':'admin' grant 一切 match);guest 在被授权 persona 内可调。
@@ -64401,7 +64551,7 @@ function computeMethodAccess(args) {
64401
64551
  }
64402
64552
 
64403
64553
  // src/version.ts
64404
- var version = "0.2.482".length > 0 ? "0.2.482" : "dev";
64554
+ var version = "0.2.483".length > 0 ? "0.2.483" : "dev";
64405
64555
 
64406
64556
  // src/cli-probe/probe.ts
64407
64557
  var fs70 = __toESM(require("fs"), 1);
@@ -21367,6 +21367,29 @@ var DeploySecretPresenceSchema = external_exports.object({
21367
21367
  env: external_exports.string().min(1),
21368
21368
  setAt: external_exports.number().optional()
21369
21369
  }).strict();
21370
+ var DEPLOY_STAGE_STATUSES = ["pending", "running", "done", "failed", "waiting", "skipped"];
21371
+ var DeployStageStatusSchema = external_exports.enum(DEPLOY_STAGE_STATUSES);
21372
+ var DEPLOY_STAGE_KEYS = ["build", "smoke", "deploy"];
21373
+ var DeployStageKeySchema = external_exports.enum(DEPLOY_STAGE_KEYS);
21374
+ var DeployJobSchema = external_exports.object({
21375
+ key: DeployStageKeySchema,
21376
+ status: DeployStageStatusSchema,
21377
+ /** 这个 job 在 GitHub 上的页面。还没跑起来的段没有 */
21378
+ url: external_exports.string().optional(),
21379
+ /** GitHub 的 job databaseId,`gh run rerun --job` 要用。还没跑起来的段没有 */
21380
+ jobId: external_exports.number().optional(),
21381
+ startedAt: external_exports.number().optional(),
21382
+ completedAt: external_exports.number().optional()
21383
+ }).strict();
21384
+ var DeployRunSchema = external_exports.object({
21385
+ runId: external_exports.number(),
21386
+ headSha: external_exports.string().min(1),
21387
+ /** run 整体只有三态。**别跟 job 的六态混用**:run 没有「跳过」也没有「等审批」 */
21388
+ status: external_exports.enum(["queued", "running", "completed"]),
21389
+ htmlUrl: external_exports.string().min(1),
21390
+ /** run 刚入队时可能一条都还没有 */
21391
+ jobs: external_exports.array(DeployJobSchema)
21392
+ }).strict();
21370
21393
  var DeployStateSchema = external_exports.object({
21371
21394
  personaId: external_exports.string().min(1),
21372
21395
  repo: DeployRepoSchema.optional(),
@@ -21374,6 +21397,11 @@ var DeployStateSchema = external_exports.object({
21374
21397
  scan: DeployScanSchema.optional(),
21375
21398
  /** 没仓库时缺席(查不了) */
21376
21399
  secrets: external_exports.array(DeploySecretPresenceSchema).optional(),
21400
+ /**
21401
+ * 最新那次流水线走到哪了。**缺席是常态**:没推过、gh 查不到、仓库还没有 run,
21402
+ * 都不该让整页打不开——同 `secrets` 的处理。
21403
+ */
21404
+ run: DeployRunSchema.optional(),
21377
21405
  /**
21378
21406
  * `.clawd/deploy.json` 读不出来(人手改坏了)时的人话。有它就没有 `file`。
21379
21407
  * 页面据此给一条能走的路(重新扫描会整份重写),而不是白屏。
@@ -21401,6 +21429,11 @@ var DeployPutSecretArgsSchema = external_exports.object({
21401
21429
  // 环境变量名 → 值。key 必须是该 secret 声明过的 env 之一,handler 校验。
21402
21430
  values: external_exports.record(external_exports.string(), external_exports.string())
21403
21431
  }).strict();
21432
+ var DeployRerunJobArgsSchema = external_exports.object({
21433
+ personaId: external_exports.string().min(1),
21434
+ /** GitHub 的 job databaseId。**是数字**——字符串会在拼命令时静默出错 */
21435
+ jobId: external_exports.number()
21436
+ }).strict();
21404
21437
  var DeployReportFindingArgsSchema = external_exports.object({
21405
21438
  personaId: external_exports.string().min(1),
21406
21439
  kind: external_exports.enum(["cli", "secret", "asset"]),
@@ -23915,6 +23948,10 @@ var METHOD_DOCS = {
23915
23948
  summary: "\u53D1\u5E03 = \u63D0\u4EA4\u5E76\u63A8\u9001\u5230\u4ED3\u5E93\uFF1A\u91CD\u5199 workflow \u6A21\u677F \u2192 git add -A \u2192 \u7A7A\u63D0\u4EA4\u4E5F\u5141\u8BB8 \u2192 push\u3002\u4ED3\u5E93\u91CC\u7684\u6D41\u6C34\u7EBF\u63A5\u7740\u6784\u955C\u50CF\u3001\u5728\u96C6\u7FA4\u4E0A\u771F\u8D77\u4E00\u6B21\u3001\u767B\u8BB0\u6210\u6B63\u5F0F\uFF0C\u8FDB\u5EA6\u5728 GitHub Actions \u770B\u3002\u524D\u7F6E\uFF1A\u5DF2\u7ED1\u5B9A\u4ED3\u5E93\u3001\u6709\u6E05\u5355\u3001\u6BCF\u4E2A\u53D8\u91CF\u540D\u90FD\u5728 Secrets \u91CC\uFF1Bowner-only",
23916
23949
  args: DeployPersonaArgsSchema
23917
23950
  },
23951
+ "deploy:rerunJob": {
23952
+ summary: "\u539F\u6837\u91CD\u8DD1\u6700\u65B0\u90A3\u6761\u6D41\u6C34\u7EBF\u91CC\u5931\u8D25\u7684\u67D0\u4E00\u6BB5\uFF08gh run rerun --job\uFF09\u3002**\u540C\u4E00\u4E2A commit\u3001\u540C\u4E00\u4EFD\u8F93\u5165**\uFF0C\u4E0D\u4EA7\u751F\u65B0\u63D0\u4EA4\u2014\u2014\u6539\u4E86 persona \u5185\u5BB9\u6216\u51ED\u636E\u8981\u8D70 deploy:release\u3002jobId \u5FC5\u987B\u5C5E\u4E8E\u6700\u65B0\u90A3\u6761 run\uFF1Bowner-only",
23953
+ args: DeployRerunJobArgsSchema
23954
+ },
23918
23955
  "shift:list": {
23919
23956
  summary: "\u5217\u51FA owner \u7684\u5168\u90E8 shift\uFF08\u6392\u73ED\uFF09\uFF0C\u7ED9 UI \u65E5\u5386\u6E32\u67D3\u7528\uFF1Bowner-only",
23920
23957
  args: NO_ARGS