@clawos-dev/clawd 0.2.517 → 0.2.519

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/dist/cli.cjs CHANGED
@@ -5950,6 +5950,27 @@ var init_contact_handshake = __esm({
5950
5950
  }
5951
5951
  });
5952
5952
 
5953
+ // ../protocol/src/github-auth-schemas.ts
5954
+ var GithubAuthStatusSchema, GithubAuthAuthorizeArgsSchema, GithubAuthOauthStartArgsSchema;
5955
+ var init_github_auth_schemas = __esm({
5956
+ "../protocol/src/github-auth-schemas.ts"() {
5957
+ "use strict";
5958
+ init_zod();
5959
+ GithubAuthStatusSchema = external_exports.discriminatedUnion("status", [
5960
+ external_exports.object({ status: external_exports.literal("authorized"), login: external_exports.string().min(1), source: external_exports.enum(["git", "file"]) }).strict(),
5961
+ external_exports.object({ status: external_exports.literal("missing") }).strict(),
5962
+ external_exports.object({ status: external_exports.literal("invalid"), message: external_exports.string().min(1) }).strict(),
5963
+ external_exports.object({ status: external_exports.literal("pending"), authUrl: external_exports.string().url() }).strict()
5964
+ ]);
5965
+ GithubAuthAuthorizeArgsSchema = external_exports.object({
5966
+ token: external_exports.string().min(1)
5967
+ }).strict();
5968
+ GithubAuthOauthStartArgsSchema = external_exports.object({
5969
+ returnTo: external_exports.string().url().optional()
5970
+ }).strict();
5971
+ }
5972
+ });
5973
+
5953
5974
  // ../protocol/src/environment-schemas.ts
5954
5975
  function connectorBlockers(c2) {
5955
5976
  if (!c2.credentialSource) return [`\u8FDE\u63A5\u5668 ${c2.name} \u8FD8\u6CA1\u9009\u7528\u8C01\u7684\u8D26\u53F7`];
@@ -6077,11 +6098,12 @@ function cloudEngineOk(tool) {
6077
6098
  function repoSlug(url) {
6078
6099
  return url.trim().replace(/^git@[^:]+:/, "").replace(/^https?:\/\/[^/]+\//, "").replace(/\.git$/, "");
6079
6100
  }
6080
- var DeployFileSchema, SCAN_BASE_KINDS, ScanBaseKindSchema, DeployScanStatusSchema, DeployScanSchema, DeployRepoSchema, DeployGithubStatusSchema, DeploySecretPresenceSchema, DEPLOY_STAGE_STATUSES, DeployStageStatusSchema, DEPLOY_STAGE_KEYS, DeployStageKeySchema, DeployJobSchema, DeployRunSchema, DeployStateSchema, DeployReleaseResultSchema, DeployPersonaArgsSchema, DeployStartArgsSchema, DeployPatchArgsSchema, DeployPutSecretArgsSchema, DeployGithubAuthorizeArgsSchema, DeployGithubOauthStartArgsSchema, DeployRerunJobArgsSchema, DeployReportFindingArgsSchema, DeployFinishScanArgsSchema, PERSONA_REPO_ORG;
6101
+ var DeployFileSchema, SCAN_BASE_KINDS, ScanBaseKindSchema, DeployScanStatusSchema, DeployScanSchema, DeployRepoSchema, DeploySecretPresenceSchema, DEPLOY_STAGE_STATUSES, DeployStageStatusSchema, DEPLOY_STAGE_KEYS, DeployStageKeySchema, DeployJobSchema, DeployRunSchema, DeployStateSchema, DeployReleaseResultSchema, DeployPersonaArgsSchema, DeployStartArgsSchema, DeployPatchArgsSchema, DeployPutSecretArgsSchema, DeployRerunJobArgsSchema, DeployReportFindingArgsSchema, DeployFinishScanArgsSchema, PERSONA_REPO_ORG;
6081
6102
  var init_deploy_schemas = __esm({
6082
6103
  "../protocol/src/deploy-schemas.ts"() {
6083
6104
  "use strict";
6084
6105
  init_zod();
6106
+ init_github_auth_schemas();
6085
6107
  init_environment_schemas();
6086
6108
  DeployFileSchema = external_exports.object({
6087
6109
  version: external_exports.literal(1),
@@ -6148,12 +6170,6 @@ var init_deploy_schemas = __esm({
6148
6170
  /** 本地领先远端几个提交 */
6149
6171
  ahead: external_exports.number()
6150
6172
  }).strict();
6151
- DeployGithubStatusSchema = external_exports.discriminatedUnion("status", [
6152
- external_exports.object({ status: external_exports.literal("authorized"), login: external_exports.string().min(1), source: external_exports.enum(["git", "file"]) }).strict(),
6153
- external_exports.object({ status: external_exports.literal("missing") }).strict(),
6154
- external_exports.object({ status: external_exports.literal("invalid"), message: external_exports.string().min(1) }).strict(),
6155
- external_exports.object({ status: external_exports.literal("pending"), authUrl: external_exports.string().url() }).strict()
6156
- ]);
6157
6173
  DeploySecretPresenceSchema = external_exports.object({
6158
6174
  env: external_exports.string().min(1),
6159
6175
  setAt: external_exports.number().optional()
@@ -6183,8 +6199,8 @@ var init_deploy_schemas = __esm({
6183
6199
  }).strict();
6184
6200
  DeployStateSchema = external_exports.object({
6185
6201
  personaId: external_exports.string().min(1),
6186
- /** 本机 GitHub 身份。daemon 每次现算;老 daemon 不给(可选只为兼容),UI 缺席时按未知处理 */
6187
- github: DeployGithubStatusSchema.optional(),
6202
+ /** 本机 GitHub 身份(机器级,githubAuth:* 的同一份;上云页顶上那张卡用)。老 daemon 不给(可选只为兼容),UI 缺席时按未知处理 */
6203
+ github: GithubAuthStatusSchema.optional(),
6188
6204
  repo: DeployRepoSchema.optional(),
6189
6205
  file: DeployFileSchema.optional(),
6190
6206
  scan: DeployScanSchema.optional(),
@@ -6229,14 +6245,6 @@ var init_deploy_schemas = __esm({
6229
6245
  // 环境变量名 → 值。key 必须是该 secret 声明过的 env 之一,handler 校验。
6230
6246
  values: external_exports.record(external_exports.string(), external_exports.string())
6231
6247
  }).strict();
6232
- DeployGithubAuthorizeArgsSchema = external_exports.object({
6233
- personaId: external_exports.string().min(1),
6234
- token: external_exports.string().min(1)
6235
- }).strict();
6236
- DeployGithubOauthStartArgsSchema = external_exports.object({
6237
- personaId: external_exports.string().min(1),
6238
- returnTo: external_exports.string().url().optional()
6239
- }).strict();
6240
6248
  DeployRerunJobArgsSchema = external_exports.object({
6241
6249
  personaId: external_exports.string().min(1),
6242
6250
  /** GitHub 的 job databaseId。**是数字**——字符串会在拼命令时静默出错 */
@@ -8672,6 +8680,7 @@ var init_methods = __esm({
8672
8680
  init_attachment_schemas();
8673
8681
  init_contact_handshake();
8674
8682
  init_deploy_schemas();
8683
+ init_github_auth_schemas();
8675
8684
  init_contact_remote_access();
8676
8685
  init_cloud_connector_schemas();
8677
8686
  init_contact();
@@ -8909,13 +8918,18 @@ var init_methods = __esm({
8909
8918
  summary: "\u539F\u6837\u91CD\u8DD1\u6700\u65B0\u90A3\u6761\u6D41\u6C34\u7EBF\u91CC\u5931\u8D25\u7684\u67D0\u4E00\u6BB5\uFF08GitHub REST 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",
8910
8919
  args: DeployRerunJobArgsSchema
8911
8920
  },
8912
- "deploy:githubAuthorize": {
8913
- summary: "\u628A\u7528\u6237\u7C98\u7684 GitHub token \u5B58\u6210\u672C\u673A git \u7684\u8EAB\u4EFD\uFF1A\u5148 GET /user \u9A8C\u8BC1\uFF0C\u518D git credential approve \u4EA4\u7ED9 git \u7684 helper \u5B58\uFF08git \u6CA1\u914D helper \u65F6\u9000\u5230\u672C\u673A\u51ED\u636E\u8868\uFF09\u3002\u4E0A\u4E91\u7684\u5EFA\u4ED3 / \u63A8\u9001 / \u5199 Secrets / \u67E5\u6D41\u6C34\u7EBF\u90FD\u7528\u5B83\u3002\u5165\u53C2\u4E0D\u8FDB\u65E5\u5FD7\u3001\u54CD\u5E94\u4E0D\u56DE\u663E token\uFF1Bowner-only",
8914
- args: DeployGithubAuthorizeArgsSchema
8921
+ // ---- githubAuth:*(本机 GitHub 身份,机器级;spec 2026-09-15-github-auth-unify-design §4.3)----
8922
+ "githubAuth:get": {
8923
+ summary: "\u672C\u673A\u7684 GitHub \u8EAB\u4EFD\uFF1Aauthorized\uFF08@login\uFF0C\u6765\u81EA git \u51ED\u636E\u5B58\u50A8 / \u672C\u673A\u51ED\u636E\u8868\uFF09/ missing / invalid\uFF08GitHub \u4E0D\u8BA4\uFF09/ pending\uFF08\u7B49 GitHub \u8DF3\u56DE\u4E2D\u5FC3\uFF09\u3002\u8BBE\u7F6E\u9875\u300C\u8D26\u53F7\u300D\u3001\u4E0A\u4E91\u9875\u9876\u4E0A\u90A3\u5F20\u5361\u3001PR \u6536\u4EF6\u7BB1\u90FD\u770B\u5B83\uFF1Bpending \u671F\u9875\u9762\u6BCF 2 \u79D2\u8F6E\u8BE2\uFF0Cdaemon \u987A\u624B\u5411\u4E2D\u5FC3\u9886 token\u3002\u65E0\u53C2\u6570",
8924
+ args: NO_ARGS
8915
8925
  },
8916
- "deploy:githubOauthStart": {
8917
- summary: "\u4E0A\u4E91\u9875\u300C\u7528 GitHub \u6388\u6743\u300D\uFF1Adaemon \u5E26\u672C\u673A\u767B\u5F55\u51ED\u8BC1\u5411\u4E2D\u5FC3\u8981\u4E00\u6761 GitHub OAuth \u6388\u6743\u94FE\u63A5\u5E76\u56DE\u5728 DeployState.github\uFF08pending\uFF09\u91CC\uFF0C\u9875\u9762\u6253\u5F00\u5B83\uFF1B\u7528\u6237\u5728 GitHub \u70B9\u540C\u610F\u540E\u56DE\u8DF3\u4E2D\u5FC3\uFF0C\u4E2D\u5FC3\u6362\u597D token \u5B58\u4E00\u4EFD\uFF0Cdaemon \u5728\u4E4B\u540E\u7684 deploy:get \u91CC\u9886\u56DE\u6765\u5B58\u8FDB\u672C\u673A git\u3002\u4E2D\u5FC3\u5DF2\u6709\u672C\u4EBA\u7684 token \u65F6\u4E0D\u8DF3 GitHub\uFF0C\u76F4\u63A5\u9886\uFF1B\u8981\u767B\u5F55\u98DE\u4E66\uFF1Bowner-only",
8918
- args: DeployGithubOauthStartArgsSchema
8926
+ "githubAuth:oauthStart": {
8927
+ summary: "\u8BBE\u7F6E\u9875\u300C\u5173\u8054 GitHub\u300D\uFF1Adaemon \u5E26\u672C\u673A\u767B\u5F55\u51ED\u8BC1\u5411\u4E2D\u5FC3\u8981\u4E00\u6761 GitHub OAuth \u6388\u6743\u94FE\u63A5\u5E76\u56DE\u5728\u72B6\u6001\uFF08pending\uFF09\u91CC\uFF0C\u9875\u9762\u6253\u5F00\u5B83\uFF1B\u7528\u6237\u5728 GitHub \u70B9\u540C\u610F\u540E\u56DE\u8DF3\u4E2D\u5FC3\uFF0C\u4E2D\u5FC3\u6362\u597D token \u5B58\u4E00\u4EFD\uFF0Cdaemon \u5728\u4E4B\u540E\u7684 githubAuth:get \u91CC\u9886\u56DE\u6765\u5B58\u8FDB\u672C\u673A git\u3002\u4E2D\u5FC3\u5DF2\u6709\u672C\u4EBA\u7684 token \u65F6\u4E0D\u8DF3 GitHub\uFF0C\u76F4\u63A5\u9886\uFF1B\u8981\u767B\u5F55\u98DE\u4E66\uFF1Bowner-only",
8928
+ args: GithubAuthOauthStartArgsSchema
8929
+ },
8930
+ "githubAuth:authorize": {
8931
+ summary: "\u628A\u7528\u6237\u7C98\u7684 GitHub token \u5B58\u6210\u672C\u673A\u8EAB\u4EFD\uFF08\u70B9\u94FE\u63A5\u8D70\u4E0D\u901A\u65F6\u7684\u9000\u8DEF\uFF09\uFF1A\u5148 GET /user \u9A8C\u8BC1\uFF0C\u518D git credential approve \u4EA4\u7ED9 git \u7684 helper \u5B58\uFF08git \u6CA1\u914D helper \u65F6\u9000\u5230\u672C\u673A\u51ED\u636E\u8868 secrets/github.env\uFF09\u3002\u5165\u53C2\u4E0D\u8FDB\u65E5\u5FD7\u3001\u54CD\u5E94\u4E0D\u56DE\u663E token\uFF1Bowner-only",
8932
+ args: GithubAuthAuthorizeArgsSchema
8919
8933
  },
8920
8934
  // ---- cloud:connectors:*(云 persona 卡片上的连接器;design 2026-09-11-mcp-connector §3.8)----
8921
8935
  "cloud:connectors:list": {
@@ -9592,6 +9606,7 @@ var init_runtime = __esm({
9592
9606
  init_environment_schemas();
9593
9607
  init_cloud_connector_schemas();
9594
9608
  init_deploy_schemas();
9609
+ init_github_auth_schemas();
9595
9610
  init_persona_schemas();
9596
9611
  init_persona_mode();
9597
9612
  init_attachment_schemas();
@@ -54810,19 +54825,25 @@ var DEFAULT_TIMEOUT_MS2 = 15e3;
54810
54825
  var MAX_BUFFER = 4 * 1024 * 1024;
54811
54826
  function createGhRunner(defaults) {
54812
54827
  const baseTimeoutMs = defaults?.timeoutMs ?? DEFAULT_TIMEOUT_MS2;
54828
+ const spawn14 = defaults?.execFile ?? import_node_child_process7.execFile;
54813
54829
  return {
54814
54830
  run(args, opts) {
54831
+ const token = defaults?.token?.();
54815
54832
  return new Promise((resolve9, reject) => {
54816
- (0, import_node_child_process7.execFile)(
54833
+ spawn14(
54817
54834
  "gh",
54818
54835
  args,
54819
- { timeout: opts?.timeoutMs ?? baseTimeoutMs, maxBuffer: MAX_BUFFER },
54836
+ {
54837
+ timeout: opts?.timeoutMs ?? baseTimeoutMs,
54838
+ maxBuffer: MAX_BUFFER,
54839
+ env: { ...process.env, ...token ? { GH_TOKEN: token } : {} }
54840
+ },
54820
54841
  (err, stdout, stderr) => {
54821
- if (!err) return resolve9(stdout);
54842
+ if (!err) return resolve9(String(stdout));
54822
54843
  if (err.code === "ENOENT") {
54823
54844
  return reject(new GhError("gh-missing", "GitHub CLI (gh) \u672A\u5B89\u88C5"));
54824
54845
  }
54825
- const detail = (stderr ?? "").trim();
54846
+ const detail = String(stderr ?? "").trim();
54826
54847
  reject(new GhError("gh-error", detail !== "" ? detail : err.message, detail));
54827
54848
  }
54828
54849
  );
@@ -58949,8 +58970,8 @@ function createGithubApi(args) {
58949
58970
  }
58950
58971
 
58951
58972
  // src/deploy/github-auth.ts
58952
- var DEPLOY_TOKEN_FILE = "github-deploy.env";
58953
- var DEPLOY_TOKEN_VAR = "GITHUB_DEPLOY_TOKEN";
58973
+ var GITHUB_TOKEN_FILE = "github.env";
58974
+ var GITHUB_TOKEN_VAR = "GITHUB_TOKEN";
58954
58975
  var HOST = "github.com";
58955
58976
  var TIMEOUT_MS2 = 15e3;
58956
58977
  var record3 = (fields) => `${Object.entries(fields).map(([k3, v3]) => `${k3}=${v3}`).join("\n")}
@@ -59580,8 +59601,7 @@ var invalid = (msg) => new ClawdError(ERROR_CODES.VALIDATION_ERROR, `VALIDATION_
59580
59601
  function wrongEngine(tool) {
59581
59602
  return cloudEngineOk(tool) ? void 0 : `\u5F15\u64CE\u662F ${tool || "claude"}\uFF0C\u4E91\u4E0A\u53EA\u8DD1 Codex\u2014\u2014\u53BB persona \u8BBE\u7F6E\u91CC\u628A\u5F15\u64CE\u5207\u6210 Codex\uFF0C\u518D\u9009\u4E2A\u6A21\u578B`;
59582
59603
  }
59583
- var NO_GITHUB_AUTH = "\u8FD8\u6CA1\u6388\u6743 GitHub\uFF1A\u5728\u4E0A\u4E91\u9875\u9762\u9876\u4E0A\u90A3\u5F20\u300CGitHub \u6388\u6743\u300D\u5361\u91CC\u70B9\u300C\u7528 GitHub \u6388\u6743\u300D";
59584
- var OAUTH_PENDING_MS = 10 * 6e4;
59604
+ var NO_GITHUB_AUTH = "\u8FD8\u6CA1\u5173\u8054 GitHub\uFF1A\u5728\u8BBE\u7F6E \u2192 \u8D26\u53F7\uFF08\u6216\u4E0A\u4E91\u9875\u9876\u4E0A\u90A3\u5F20\u5361\uFF09\u91CC\u70B9\u300C\u5173\u8054 GitHub\u300D";
59585
59605
  function toClawdError(err) {
59586
59606
  if (err instanceof ClawdError) return err;
59587
59607
  if (err instanceof RepoError || err instanceof GithubApiError) return new ClawdError(ERROR_CODES.INTERNAL, err.message);
@@ -59597,62 +59617,11 @@ function buildDeployHandlers(deps) {
59597
59617
  const cacheMs = deps.secretsCacheMs ?? SECRETS_CACHE_MS;
59598
59618
  const secretsCache = /* @__PURE__ */ new Map();
59599
59619
  const runCache = /* @__PURE__ */ new Map();
59600
- const githubStatusCache = /* @__PURE__ */ new Map();
59601
- let pendingOauth = null;
59602
- let staleCenterToken;
59603
- async function adoptToken(token) {
59604
- await deps.github.store(token);
59605
- githubStatusCache.clear();
59606
- secretsCache.clear();
59607
- pendingOauth = null;
59608
- }
59609
- async function claimCenterToken() {
59610
- let got;
59611
- try {
59612
- got = await deps.github.oauth.token();
59613
- } catch {
59614
- return false;
59615
- }
59616
- if (!got || got.token === staleCenterToken) return false;
59617
- try {
59618
- await deps.github.api(got.token).whoami();
59619
- } catch (err) {
59620
- if (err instanceof GithubApiError && err.status === 401) staleCenterToken = got.token;
59621
- return false;
59622
- }
59623
- await adoptToken(got.token);
59624
- return true;
59625
- }
59626
- async function settlePendingOauth() {
59627
- if (!pendingOauth) return;
59628
- if (deps.now() > pendingOauth.expiresAt) {
59629
- pendingOauth = null;
59630
- return;
59631
- }
59632
- try {
59633
- await claimCenterToken();
59634
- } catch {
59635
- pendingOauth = null;
59636
- }
59637
- }
59620
+ deps.githubIdentity.onChange(() => secretsCache.clear());
59638
59621
  async function requireGithub() {
59639
- const auth = await deps.github.resolve();
59622
+ const auth = await deps.githubIdentity.refresh();
59640
59623
  if (!auth) throw new ClawdError(ERROR_CODES.INTERNAL, NO_GITHUB_AUTH);
59641
- return { auth, api: deps.github.api(auth.token) };
59642
- }
59643
- async function githubStatus(auth) {
59644
- if (!auth) return { status: "missing" };
59645
- const hit = githubStatusCache.get(auth.token);
59646
- if (hit && deps.now() - hit.at < cacheMs) return hit.status;
59647
- let status;
59648
- try {
59649
- const { login } = await deps.github.api(auth.token).whoami();
59650
- status = { status: "authorized", login, source: auth.source };
59651
- } catch (err) {
59652
- status = { status: "invalid", message: err instanceof Error ? err.message : String(err) };
59653
- }
59654
- githubStatusCache.set(auth.token, { at: deps.now(), status });
59655
- return status;
59624
+ return { auth, api: deps.githubApi(auth.token) };
59656
59625
  }
59657
59626
  function mustPersona(personaId2) {
59658
59627
  if (!deps.hasPersona(personaId2)) {
@@ -59670,11 +59639,9 @@ function buildDeployHandlers(deps) {
59670
59639
  async function stateOf(personaId2) {
59671
59640
  const dir = deps.personaDir(personaId2);
59672
59641
  const repo2 = await repoInfo(deps.exec, dir);
59673
- await settlePendingOauth();
59674
- const auth = await deps.github.resolve();
59675
- const current = await githubStatus(auth);
59676
- const github = pendingOauth && current.status !== "authorized" ? { status: "pending", authUrl: pendingOauth.authUrl } : current;
59677
- const api = auth ? deps.github.api(auth.token) : void 0;
59642
+ const github = await deps.githubIdentity.status();
59643
+ const auth = deps.githubIdentity.cached();
59644
+ const api = auth ? deps.githubApi(auth.token) : void 0;
59678
59645
  let file;
59679
59646
  let fileError;
59680
59647
  try {
@@ -59944,33 +59911,6 @@ function buildDeployHandlers(deps) {
59944
59911
  }
59945
59912
  return view(personaId2);
59946
59913
  };
59947
- const githubAuthorize = async (frame, _client, ctx) => {
59948
- ensureOwner(ctx);
59949
- const args = parseArgs2(DeployGithubAuthorizeArgsSchema, frame, "deploy:githubAuthorize");
59950
- mustPersona(args.personaId);
59951
- const token = args.token.trim();
59952
- try {
59953
- await deps.github.api(token).whoami();
59954
- } catch (err) {
59955
- throw invalid(`GitHub \u4E0D\u8BA4\u8FD9\u4E2A token\uFF1A${err instanceof Error ? err.message : String(err)}`);
59956
- }
59957
- await adoptToken(token);
59958
- return view(args.personaId);
59959
- };
59960
- const githubOauthStart = async (frame, _client, ctx) => {
59961
- ensureOwner(ctx);
59962
- const args = parseArgs2(DeployGithubOauthStartArgsSchema, frame, "deploy:githubOauthStart");
59963
- mustPersona(args.personaId);
59964
- const local = await deps.github.resolve();
59965
- if (!local && await claimCenterToken()) return view(args.personaId);
59966
- try {
59967
- staleCenterToken = (await deps.github.oauth.token())?.token;
59968
- } catch {
59969
- }
59970
- const { authUrl } = await deps.github.oauth.start(args.returnTo);
59971
- pendingOauth = { authUrl, expiresAt: deps.now() + OAUTH_PENDING_MS };
59972
- return view(args.personaId);
59973
- };
59974
59914
  return {
59975
59915
  "deploy:start": start,
59976
59916
  "deploy:get": get,
@@ -59979,9 +59919,156 @@ function buildDeployHandlers(deps) {
59979
59919
  "deploy:reportFinding": reportFinding,
59980
59920
  "deploy:finishScan": finishScan,
59981
59921
  "deploy:release": releaseHandler,
59982
- "deploy:rerunJob": rerunJobHandler,
59983
- "deploy:githubAuthorize": githubAuthorize,
59984
- "deploy:githubOauthStart": githubOauthStart
59922
+ "deploy:rerunJob": rerunJobHandler
59923
+ };
59924
+ }
59925
+
59926
+ // src/github/identity.ts
59927
+ var GithubTokenRejected = class extends Error {
59928
+ };
59929
+ var STATUS_CACHE_MS = 2e4;
59930
+ var OAUTH_PENDING_MS = 10 * 6e4;
59931
+ function secretsWithGithubIdentity(secrets, auth) {
59932
+ return auth ? { ...secrets, GITHUB_TOKEN: auth.token } : secrets;
59933
+ }
59934
+ function createGithubIdentity(deps) {
59935
+ const cacheMs = deps.statusCacheMs ?? STATUS_CACHE_MS;
59936
+ let auth;
59937
+ let seq = 0;
59938
+ const statusCache = /* @__PURE__ */ new Map();
59939
+ let pendingOauth = null;
59940
+ let staleCenterToken;
59941
+ const listeners = /* @__PURE__ */ new Set();
59942
+ async function refresh() {
59943
+ const mine = ++seq;
59944
+ const resolved = await deps.resolve();
59945
+ if (mine === seq) auth = resolved;
59946
+ return resolved;
59947
+ }
59948
+ async function whoami(a2) {
59949
+ const hit = statusCache.get(a2.token);
59950
+ if (hit && deps.now() - hit.at < cacheMs) return hit.status;
59951
+ let status2;
59952
+ try {
59953
+ const { login } = await deps.api(a2.token).whoami();
59954
+ status2 = { status: "authorized", login, source: a2.source };
59955
+ } catch (err) {
59956
+ status2 = { status: "invalid", message: err instanceof Error ? err.message : String(err) };
59957
+ }
59958
+ statusCache.set(a2.token, { at: deps.now(), status: status2 });
59959
+ return status2;
59960
+ }
59961
+ async function adopt(token) {
59962
+ auth = await deps.store(token);
59963
+ seq++;
59964
+ statusCache.clear();
59965
+ pendingOauth = null;
59966
+ for (const cb of listeners) cb();
59967
+ }
59968
+ async function claimCenterToken() {
59969
+ let got;
59970
+ try {
59971
+ got = await deps.oauth.token();
59972
+ } catch {
59973
+ return false;
59974
+ }
59975
+ if (!got || got.token === staleCenterToken) return false;
59976
+ try {
59977
+ await deps.api(got.token).whoami();
59978
+ } catch (err) {
59979
+ if (err instanceof GithubApiError && err.status === 401) staleCenterToken = got.token;
59980
+ return false;
59981
+ }
59982
+ await adopt(got.token);
59983
+ return true;
59984
+ }
59985
+ async function settlePendingOauth() {
59986
+ if (!pendingOauth) return;
59987
+ if (deps.now() > pendingOauth.expiresAt) {
59988
+ pendingOauth = null;
59989
+ return;
59990
+ }
59991
+ try {
59992
+ await claimCenterToken();
59993
+ } catch (err) {
59994
+ deps.logger?.warn("github.identity.claim-failed", { message: err instanceof Error ? err.message : String(err) });
59995
+ pendingOauth = null;
59996
+ }
59997
+ }
59998
+ async function status() {
59999
+ await settlePendingOauth();
60000
+ const a2 = await refresh();
60001
+ const current = a2 ? await whoami(a2) : { status: "missing" };
60002
+ return pendingOauth && current.status !== "authorized" ? { status: "pending", authUrl: pendingOauth.authUrl } : current;
60003
+ }
60004
+ return {
60005
+ cached: () => auth,
60006
+ refresh,
60007
+ status,
60008
+ adopt,
60009
+ async authorize(raw) {
60010
+ const token = raw.trim();
60011
+ try {
60012
+ await deps.api(token).whoami();
60013
+ } catch (err) {
60014
+ throw new GithubTokenRejected(`GitHub \u4E0D\u8BA4\u8FD9\u4E2A token\uFF1A${err instanceof Error ? err.message : String(err)}`);
60015
+ }
60016
+ await adopt(token);
60017
+ return status();
60018
+ },
60019
+ /**
60020
+ * 本机还没有身份时先问中心有没有本人现成的 token(换台机器授权过 / 上次跳回来了但 daemon 没轮到):
60021
+ * 有且 GitHub 认就直接领、不跳 GitHub。本机已有身份(能用的 = 用户要换一个;失效的 = 要重新授权,而中心那份就是这把死的)
60022
+ * 一律跳 GitHub 重来。进 pending 之后每次 status() 顺手问一次中心领没领到。
60023
+ */
60024
+ async oauthStart(returnTo) {
60025
+ const local = await refresh();
60026
+ if (!local && await claimCenterToken()) return status();
60027
+ try {
60028
+ staleCenterToken = (await deps.oauth.token())?.token;
60029
+ } catch {
60030
+ }
60031
+ const { authUrl } = await deps.oauth.start(returnTo);
60032
+ pendingOauth = { authUrl, expiresAt: deps.now() + OAUTH_PENDING_MS };
60033
+ return status();
60034
+ },
60035
+ onChange(cb) {
60036
+ listeners.add(cb);
60037
+ return () => {
60038
+ listeners.delete(cb);
60039
+ };
60040
+ }
60041
+ };
60042
+ }
60043
+
60044
+ // src/handlers/github-auth.ts
60045
+ init_protocol();
60046
+ function ensureOwner2(ctx) {
60047
+ if (!ctx || ctx.principal.kind !== "owner") {
60048
+ throw new ClawdError(ERROR_CODES.UNAUTHORIZED, "UNAUTHORIZED: githubAuth:* requires owner ctx");
60049
+ }
60050
+ }
60051
+ function buildGithubAuthHandlers(deps) {
60052
+ const get = async () => ({ response: await deps.identity.status() });
60053
+ const oauthStart = async (frame, _client, ctx) => {
60054
+ ensureOwner2(ctx);
60055
+ const args = parseArgs2(GithubAuthOauthStartArgsSchema, frame, "githubAuth:oauthStart");
60056
+ return { response: await deps.identity.oauthStart(args.returnTo) };
60057
+ };
60058
+ const authorize = async (frame, _client, ctx) => {
60059
+ ensureOwner2(ctx);
60060
+ const args = parseArgs2(GithubAuthAuthorizeArgsSchema, frame, "githubAuth:authorize");
60061
+ try {
60062
+ return { response: await deps.identity.authorize(args.token) };
60063
+ } catch (err) {
60064
+ if (err instanceof GithubTokenRejected) throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, `VALIDATION_ERROR: ${err.message}`);
60065
+ throw err;
60066
+ }
60067
+ };
60068
+ return {
60069
+ "githubAuth:get": get,
60070
+ "githubAuth:oauthStart": oauthStart,
60071
+ "githubAuth:authorize": authorize
59985
60072
  };
59986
60073
  }
59987
60074
 
@@ -62208,23 +62295,23 @@ async function centralConnectorFill(opts) {
62208
62295
  });
62209
62296
  if (body.ok !== true) throw new CentralClientError("BAD_RESPONSE", "connectors/fields: server did not ack ok");
62210
62297
  }
62211
- async function centralGithubDeployOauthStart(opts) {
62212
- const body = await centralRequest(opts, "/api/persona-cloud/github-deploy/oauth/start", {
62298
+ async function centralGithubAuthOauthStart(opts) {
62299
+ const body = await centralRequest(opts, "/api/persona-cloud/github/oauth/start", {
62213
62300
  method: "POST",
62214
62301
  bearer: opts.ttcJwt,
62215
62302
  body: { provider: opts.provider, ...opts.returnTo ? { returnTo: opts.returnTo } : {} }
62216
62303
  });
62217
- if (typeof body.authUrl !== "string" || !body.authUrl) throw new CentralClientError("BAD_RESPONSE", "github-deploy/oauth/start: missing authUrl");
62304
+ if (typeof body.authUrl !== "string" || !body.authUrl) throw new CentralClientError("BAD_RESPONSE", "github/oauth/start: missing authUrl");
62218
62305
  return { authUrl: body.authUrl };
62219
62306
  }
62220
- async function centralGithubDeployToken(opts) {
62221
- const body = await centralRequest(opts, "/api/persona-cloud/github-deploy/oauth/token", {
62307
+ async function centralGithubAuthToken(opts) {
62308
+ const body = await centralRequest(opts, "/api/persona-cloud/github/oauth/token", {
62222
62309
  method: "POST",
62223
62310
  bearer: opts.ttcJwt,
62224
62311
  body: { provider: opts.provider }
62225
62312
  });
62226
62313
  if (body.token === null || body.token === void 0) return void 0;
62227
- if (typeof body.token !== "string" || !body.token) throw new CentralClientError("BAD_RESPONSE", "github-deploy/oauth/token: malformed token");
62314
+ if (typeof body.token !== "string" || !body.token) throw new CentralClientError("BAD_RESPONSE", "github/oauth/token: malformed token");
62228
62315
  return { token: body.token, login: typeof body.login === "string" ? body.login : "" };
62229
62316
  }
62230
62317
 
@@ -65815,6 +65902,31 @@ async function importZip(buf, root) {
65815
65902
  }
65816
65903
  }
65817
65904
 
65905
+ // src/feishu-auth/login-callback.ts
65906
+ function parseLoginCallbackQuery(url) {
65907
+ return {
65908
+ token: url.searchParams.get("token") ?? "",
65909
+ state: url.searchParams.get("state") ?? "",
65910
+ expiresAt: url.searchParams.get("expires_at")
65911
+ };
65912
+ }
65913
+ function escapeHtml(s2) {
65914
+ return s2.replace(/[&<>"']/g, (c2) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c2]);
65915
+ }
65916
+ function renderLoginCallbackPage(result, successTagline) {
65917
+ const style = (color) => `<style>body{font-family:-apple-system,sans-serif;padding:48px;color:${color}}</style>`;
65918
+ if (result.ok) {
65919
+ return {
65920
+ status: 200,
65921
+ html: `<!doctype html><meta charset="utf-8"><title>clawd</title>${style("#222")}<h2>${escapeHtml(successTagline)}</h2><p>\u8EAB\u4EFD\uFF1A${escapeHtml(result.identity.displayName)}</p><script>setTimeout(()=>window.close(),1500)</script>`
65922
+ };
65923
+ }
65924
+ return {
65925
+ status: 400,
65926
+ html: `<!doctype html><meta charset="utf-8"><title>clawd</title>${style("#922")}<h2>\u767B\u5F55\u5931\u8D25</h2><p>${escapeHtml(result.reason)}</p>`
65927
+ };
65928
+ }
65929
+
65818
65930
  // src/transport/http-router.ts
65819
65931
  var RPC_BODY_MAX_BYTES = 1 * 1024 * 1024;
65820
65932
  var SHARE_UI_ASSETS_PREFIX = "/share-ui/assets/";
@@ -65954,14 +66066,10 @@ function createHttpRouter(deps) {
65954
66066
  sendJson(res, 501, { code: "NOT_IMPLEMENTED", message: "feishu login not wired" });
65955
66067
  return true;
65956
66068
  }
65957
- const result = await deps.feishuLogin.handleLoginCallback({
65958
- token: url.searchParams.get("token") ?? "",
65959
- state: url.searchParams.get("state") ?? "",
65960
- expiresAt: url.searchParams.get("expires_at")
65961
- });
65962
- const html = result.ok ? `<!doctype html><meta charset="utf-8"><title>clawd</title><style>body{font-family:-apple-system,sans-serif;padding:48px;color:#222}</style><h2>\u767B\u5F55\u6210\u529F\uFF0C\u53EF\u5173\u95ED\u6B64\u9875\u3002</h2><p>\u8EAB\u4EFD\uFF1A${escapeHtml(result.identity.displayName)}</p><script>setTimeout(()=>window.close(),1500)</script>` : `<!doctype html><meta charset="utf-8"><title>clawd</title><style>body{font-family:-apple-system,sans-serif;padding:48px;color:#922}</style><h2>\u767B\u5F55\u5931\u8D25</h2><p>${escapeHtml(result.reason)}</p>`;
65963
- res.writeHead(result.ok ? 200 : 400, { "Content-Type": "text/html; charset=utf-8" });
65964
- res.end(html);
66069
+ const result = await deps.feishuLogin.handleLoginCallback(parseLoginCallbackQuery(url));
66070
+ const page = renderLoginCallbackPage(result, "\u767B\u5F55\u6210\u529F\uFF0C\u53EF\u5173\u95ED\u6B64\u9875\u3002");
66071
+ res.writeHead(page.status, { "Content-Type": "text/html; charset=utf-8" });
66072
+ res.end(page.html);
65965
66073
  return true;
65966
66074
  }
65967
66075
  if (url.pathname === "/share/exchange" && req.method === "OPTIONS") {
@@ -66353,9 +66461,6 @@ function parseUrl(rawUrl) {
66353
66461
  return null;
66354
66462
  }
66355
66463
  }
66356
- function escapeHtml(s2) {
66357
- return s2.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
66358
- }
66359
66464
  function sendJson(res, status, body, extraHeaders) {
66360
66465
  res.writeHead(status, {
66361
66466
  "Content-Type": "application/json; charset=utf-8",
@@ -67767,7 +67872,7 @@ async function syncOrgMesh(deps) {
67767
67872
 
67768
67873
  // src/handlers/device.ts
67769
67874
  init_protocol();
67770
- function ensureOwner2(ctx) {
67875
+ function ensureOwner3(ctx) {
67771
67876
  if (!ctx || ctx.principal.kind !== "owner") {
67772
67877
  throw new ClawdError(ERROR_CODES.UNAUTHORIZED, "UNAUTHORIZED: device:* requires owner ctx");
67773
67878
  }
@@ -67862,14 +67967,14 @@ function createConnectToDevice(deps) {
67862
67967
  function buildDeviceHandlers(deps) {
67863
67968
  const connectToDevice = createConnectToDevice(deps);
67864
67969
  const list = async (_frame, _client, ctx) => {
67865
- ensureOwner2(ctx);
67970
+ ensureOwner3(ctx);
67866
67971
  const devices = await deps.listDevices().catch(rethrowLoginExpired);
67867
67972
  return {
67868
67973
  response: { type: "device:list:ok", devices }
67869
67974
  };
67870
67975
  };
67871
67976
  const connect = async (frame, _client, ctx) => {
67872
- ensureOwner2(ctx);
67977
+ ensureOwner3(ctx);
67873
67978
  const { type: _t2, requestId: _r2, ...rest } = frame;
67874
67979
  const args = DeviceConnectArgsSchema.parse(rest);
67875
67980
  const contact = await connectToDevice(args);
@@ -67884,7 +67989,7 @@ function buildDeviceHandlers(deps) {
67884
67989
  };
67885
67990
  };
67886
67991
  const connections = async (_frame, client, ctx) => {
67887
- ensureOwner2(ctx);
67992
+ ensureOwner3(ctx);
67888
67993
  const entries = deps.listOwnerConnections();
67889
67994
  return {
67890
67995
  response: {
@@ -69609,7 +69714,7 @@ function upsertPeerContact(args) {
69609
69714
  }
69610
69715
 
69611
69716
  // src/handlers/contact.ts
69612
- function ensureOwner3(ctx) {
69717
+ function ensureOwner4(ctx) {
69613
69718
  if (!ctx || ctx.principal.kind !== "owner") {
69614
69719
  throw new ClawdError(
69615
69720
  ERROR_CODES.UNAUTHORIZED,
@@ -69619,7 +69724,7 @@ function ensureOwner3(ctx) {
69619
69724
  }
69620
69725
  function buildContactHandlers(deps) {
69621
69726
  const list = async (_frame, _client, ctx) => {
69622
- ensureOwner3(ctx);
69727
+ ensureOwner4(ctx);
69623
69728
  return {
69624
69729
  response: {
69625
69730
  type: "contact:list:ok",
@@ -69628,7 +69733,7 @@ function buildContactHandlers(deps) {
69628
69733
  };
69629
69734
  };
69630
69735
  const pin = async (frame, _client, ctx) => {
69631
- ensureOwner3(ctx);
69736
+ ensureOwner4(ctx);
69632
69737
  const args = parseArgs2(ContactPinArgsSchema, frame, "contact:pin");
69633
69738
  const pinnedAt = args.pinned ? deps.now() : null;
69634
69739
  const hit = deps.store.setPin(args.deviceId, pinnedAt);
@@ -69652,7 +69757,7 @@ function buildContactHandlers(deps) {
69652
69757
  };
69653
69758
  };
69654
69759
  const setNote = async (frame, _client, ctx) => {
69655
- ensureOwner3(ctx);
69760
+ ensureOwner4(ctx);
69656
69761
  const args = parseArgs2(ContactSetNoteArgsSchema, frame, "contact:setNote");
69657
69762
  const hit = deps.store.setNote(args.deviceId, args.note);
69658
69763
  if (!hit) {
@@ -69675,13 +69780,13 @@ function buildContactHandlers(deps) {
69675
69780
  };
69676
69781
  };
69677
69782
  const orgPeers = async (_frame, _client, ctx) => {
69678
- ensureOwner3(ctx);
69783
+ ensureOwner4(ctx);
69679
69784
  return {
69680
69785
  response: { type: "org:peers:ok", peers: getCachedOrgPeers() }
69681
69786
  };
69682
69787
  };
69683
69788
  const remove = async (frame, _client, ctx) => {
69684
- ensureOwner3(ctx);
69789
+ ensureOwner4(ctx);
69685
69790
  const args = parseArgs2(ContactRemoveArgsSchema, frame, "contact:remove");
69686
69791
  deps.store.removeByDeviceId(args.deviceId);
69687
69792
  deps.broadcast({
@@ -69755,7 +69860,7 @@ function buildContactHandlers(deps) {
69755
69860
 
69756
69861
  // src/handlers/contact-remote-access.ts
69757
69862
  init_protocol();
69758
- function ensureOwner4(ctx) {
69863
+ function ensureOwner5(ctx) {
69759
69864
  if (!ctx || ctx.principal.kind !== "owner") {
69760
69865
  throw new ClawdError(
69761
69866
  ERROR_CODES.UNAUTHORIZED,
@@ -69765,7 +69870,7 @@ function ensureOwner4(ctx) {
69765
69870
  }
69766
69871
  function buildContactRemoteAccessHandlers(deps) {
69767
69872
  const setRemoteAccess = async (frame, _client, ctx) => {
69768
- ensureOwner4(ctx);
69873
+ ensureOwner5(ctx);
69769
69874
  const args = parseArgs2(ContactSetRemoteAccessArgsSchema, frame, "contact:setRemoteAccess");
69770
69875
  const hit = deps.store.setRemoteAccess(args.deviceId, {
69771
69876
  remoteAccessAllowed: args.remoteAccessAllowed
@@ -69925,7 +70030,7 @@ function buildFeishuAuthHandlers(deps) {
69925
70030
 
69926
70031
  // src/handlers/cloud-connectors.ts
69927
70032
  init_protocol();
69928
- function ensureOwner5(ctx) {
70033
+ function ensureOwner6(ctx) {
69929
70034
  if (!ctx || ctx.principal.kind !== "owner") {
69930
70035
  throw new ClawdError(ERROR_CODES.UNAUTHORIZED, "UNAUTHORIZED: cloud:connectors:* requires owner ctx");
69931
70036
  }
@@ -69945,7 +70050,7 @@ function buildCloudConnectorsHandlers(deps) {
69945
70050
  return { gatewayPersonaId: persona.cloud.personaId, connectors: all.filter((c2) => c2.credentialSource === "personal") };
69946
70051
  }
69947
70052
  const list = async (frame, _client, ctx) => {
69948
- ensureOwner5(ctx);
70053
+ ensureOwner6(ctx);
69949
70054
  const { personaId: personaId2 } = parseArgs2(CloudConnectorsListArgsSchema, frame, "cloud:connectors:list");
69950
70055
  const { gatewayPersonaId, connectors } = await personalConnectors(personaId2);
69951
70056
  if (connectors.length === 0) return { response: { type: "cloud:connectors:list:ok", connectors: [] } };
@@ -69956,7 +70061,7 @@ function buildCloudConnectorsHandlers(deps) {
69956
70061
  return { response: { type: "cloud:connectors:list:ok", connectors: parsed.data.connectors } };
69957
70062
  };
69958
70063
  const fill = async (frame, _client, ctx) => {
69959
- ensureOwner5(ctx);
70064
+ ensureOwner6(ctx);
69960
70065
  const args = parseArgs2(CloudConnectorFillArgsSchema, frame, "cloud:connectors:fill");
69961
70066
  const { connectors } = await personalConnectors(args.personaId);
69962
70067
  const connector = connectors.find((c2) => c2.name === args.name);
@@ -70027,8 +70132,6 @@ var METHOD_GRANT_MAP = {
70027
70132
  "deploy:finishScan": ADMIN_ANY,
70028
70133
  "deploy:release": ADMIN_ANY,
70029
70134
  "deploy:rerunJob": ADMIN_ANY,
70030
- "deploy:githubAuthorize": ADMIN_ANY,
70031
- "deploy:githubOauthStart": ADMIN_ANY,
70032
70135
  // cloud:connectors:*(云 persona 卡片上的连接器;design 2026-09-11-mcp-connector):
70033
70136
  // 拿本机登录凭证向中心代查 / 代填使用者本人的凭据,guest 一律够不着
70034
70137
  "cloud:connectors:list": ADMIN_ANY,
@@ -70117,7 +70220,11 @@ var METHOD_GRANT_MAP = {
70117
70220
  "project:create": ADMIN_ANY,
70118
70221
  "project:patch": ADMIN_ANY,
70119
70222
  "project:reveal": ADMIN_ANY,
70120
- // github:*(PR 收件箱,spec 2026-08-11):走 owner 本机 gh 凭据,owner-only 一刀切
70223
+ // githubAuth:*(本机 GitHub 身份,机器级;spec 2026-09-15-github-auth-unify):关联 / 粘 token 只认人手,guest 够不着
70224
+ "githubAuth:get": ADMIN_ANY,
70225
+ "githubAuth:oauthStart": ADMIN_ANY,
70226
+ "githubAuth:authorize": ADMIN_ANY,
70227
+ // github:*(PR 收件箱,spec 2026-08-11):凭据是 githubAuth 那份身份(GH_TOKEN),owner-only 一刀切
70121
70228
  "github:status": ADMIN_ANY,
70122
70229
  "github:prList": ADMIN_ANY,
70123
70230
  "github:prDetail": ADMIN_ANY,
@@ -70229,8 +70336,8 @@ var HUMAN_ONLY_METHODS = [
70229
70336
  "deploy:release",
70230
70337
  "deploy:putSecret",
70231
70338
  "deploy:rerunJob",
70232
- "deploy:githubAuthorize",
70233
- "deploy:githubOauthStart"
70339
+ "githubAuth:oauthStart",
70340
+ "githubAuth:authorize"
70234
70341
  ];
70235
70342
  var HUMAN_ONLY = new Set(HUMAN_ONLY_METHODS);
70236
70343
  function isHumanOnlyDenied(method, transport) {
@@ -70273,7 +70380,7 @@ function computeMethodAccess(args) {
70273
70380
  }
70274
70381
 
70275
70382
  // src/version.ts
70276
- var version = "0.2.517".length > 0 ? "0.2.517" : "dev";
70383
+ var version = "0.2.519".length > 0 ? "0.2.519" : "dev";
70277
70384
 
70278
70385
  // src/cli-probe/probe.ts
70279
70386
  var fs77 = __toESM(require("fs"), 1);
@@ -71830,9 +71937,8 @@ function parsePrDetailResponse(json, repoRef) {
71830
71937
  }
71831
71938
 
71832
71939
  // src/handlers/github.ts
71833
- var HOSTNAME = "github.com";
71834
71940
  function buildGithubHandlers(deps) {
71835
- const { gh } = deps;
71941
+ const { gh, identity } = deps;
71836
71942
  return {
71837
71943
  "github:status": async () => {
71838
71944
  try {
@@ -71843,13 +71949,9 @@ function buildGithubHandlers(deps) {
71843
71949
  }
71844
71950
  throw err;
71845
71951
  }
71846
- try {
71847
- await gh.run(["auth", "status", "--active", "--hostname", HOSTNAME]);
71848
- } catch {
71849
- return { response: { installed: true, authenticated: false } };
71850
- }
71851
- const login = (await gh.run(["api", "user", "--jq", ".login"])).trim();
71852
- const response = { installed: true, authenticated: true, login };
71952
+ const st2 = await identity.status();
71953
+ if (st2.status !== "authorized") return { response: { installed: true, authenticated: false } };
71954
+ const response = { installed: true, authenticated: true, login: st2.login };
71853
71955
  return { response };
71854
71956
  },
71855
71957
  "github:prList": async (frame) => {
@@ -71937,7 +72039,7 @@ function buildGithubHandlers(deps) {
71937
72039
 
71938
72040
  // src/handlers/visitor.ts
71939
72041
  init_protocol();
71940
- function ensureOwner6(ctx) {
72042
+ function ensureOwner7(ctx) {
71941
72043
  if (!ctx || ctx.principal.kind !== "owner") {
71942
72044
  throw new ClawdError(
71943
72045
  ERROR_CODES.UNAUTHORIZED,
@@ -71947,7 +72049,7 @@ function ensureOwner6(ctx) {
71947
72049
  }
71948
72050
  function buildVisitorHandlers(deps) {
71949
72051
  const list = async (_frame, _client, ctx) => {
71950
- ensureOwner6(ctx);
72052
+ ensureOwner7(ctx);
71951
72053
  return {
71952
72054
  response: {
71953
72055
  type: "visitor:list",
@@ -72265,7 +72367,7 @@ function buildMethodHandlers(deps) {
72265
72367
  ownerIdProvider: deps.shiftOwnerIdProvider,
72266
72368
  now: () => Date.now()
72267
72369
  }),
72268
- ...buildGithubHandlers({ gh: deps.ghRunner }),
72370
+ ...buildGithubHandlers({ gh: deps.ghRunner, identity: deps.githubIdentity }),
72269
72371
  ...buildVisitorHandlers({ visitorStore: deps.visitorStore })
72270
72372
  };
72271
72373
  handlers["meta:methods"] = buildMetaMethodsHandler({
@@ -73084,13 +73186,46 @@ async function startDaemon(config) {
73084
73186
  const secretsBundleRoot = findSecretsBundleRoot(logger);
73085
73187
  const localSecretsDir = import_node_path79.default.join(config.dataDir, LOCAL_SECRETS_DIRNAME);
73086
73188
  const readSecrets = () => readSecretTable({ bundleRoot: secretsBundleRoot, overrideDir: localSecretsDir, logger });
73189
+ const githubIdentity = createGithubIdentity({
73190
+ resolve: () => resolveGithubAuth({ exec: runCommand, fileToken: () => readSecrets()[GITHUB_TOKEN_VAR] }),
73191
+ store: (token) => storeGithubAuth({
73192
+ exec: runCommand,
73193
+ token,
73194
+ writeFileToken: (t2) => {
73195
+ upsertEnvFileVar(import_node_path79.default.join(localSecretsDir, GITHUB_TOKEN_FILE), GITHUB_TOKEN_VAR, t2);
73196
+ logger.warn("github.identity.token-in-file", { reason: "git \u6CA1\u914D credential helper\uFF0Ctoken \u843D\u672C\u673A\u51ED\u636E\u8868", file: GITHUB_TOKEN_FILE });
73197
+ }
73198
+ }),
73199
+ api: (token) => createGithubApi({ token }),
73200
+ oauth: {
73201
+ start: async (returnTo) => {
73202
+ const r2 = ownerIdentityStore.read();
73203
+ if (!r2) throw new ClawdError(ERROR_CODES.FEISHU_LOGIN_REQUIRED, "FEISHU_LOGIN_REQUIRED: \u8FD8\u6CA1\u767B\u5F55\u98DE\u4E66\uFF0C\u5173\u8054 GitHub \u8981\u7ECF\u4E2D\u5FC3\uFF1B\u5148\u767B\u5F55\uFF0C\u6216\u5728\u5361\u7247\u4E0A\u624B\u52A8\u7C98 token");
73204
+ try {
73205
+ return await centralGithubAuthOauthStart({ api: config.clawosApi ?? DEFAULT_CLAWOS_API, ttcJwt: r2.ttcToken, provider: r2.identity.provider, ...returnTo ? { returnTo } : {} });
73206
+ } catch (err) {
73207
+ throw new ClawdError(ERROR_CODES.INTERNAL, `\u5411\u4E2D\u5FC3\u8981 GitHub \u6388\u6743\u94FE\u63A5\u6CA1\u6210\uFF1A${err instanceof Error ? err.message : String(err)}`);
73208
+ }
73209
+ },
73210
+ token: async () => {
73211
+ const r2 = ownerIdentityStore.read();
73212
+ if (!r2) return void 0;
73213
+ return centralGithubAuthToken({ api: config.clawosApi ?? DEFAULT_CLAWOS_API, ttcJwt: r2.ttcToken, provider: r2.identity.provider });
73214
+ }
73215
+ },
73216
+ now: () => Date.now(),
73217
+ logger
73218
+ });
73219
+ void githubIdentity.refresh();
73087
73220
  let refreshAppBuilderMcpConfig;
73088
73221
  const resolvePersonaMcpConfig = (personaId2) => {
73089
73222
  refreshAppBuilderMcpConfig?.(personaId2);
73223
+ const secrets = secretsWithGithubIdentity(readSecrets(), githubIdentity.cached());
73224
+ void githubIdentity.refresh();
73090
73225
  return renderPersonaMcpSecrets({
73091
73226
  personaId: personaId2,
73092
73227
  personaDir: import_node_path79.default.join(config.dataDir, "personas", personaId2),
73093
- secrets: readSecrets(),
73228
+ secrets,
73094
73229
  outDir: import_node_path79.default.join(config.dataDir, "persona-mcp"),
73095
73230
  logger
73096
73231
  });
@@ -73928,8 +74063,9 @@ async function startDaemon(config) {
73928
74063
  // daemon 稳定身份),跟我们 ShiftStore boot 时用的 provider 一致。
73929
74064
  shiftStore,
73930
74065
  shiftOwnerIdProvider: () => ownerPrincipalId,
73931
- // PR 收件箱:github:* handler spawn 本机 gh CLI(凭据复用 gh 登录态)
73932
- ghRunner: createGhRunner(),
74066
+ // PR 收件箱:github:* handler spawn 本机 gh CLI,凭据是 clawd 的 GitHub 身份(GH_TOKEN),没有才是 gh 自己的登录态
74067
+ ghRunner: createGhRunner({ token: () => githubIdentity.cached()?.token }),
74068
+ githubIdentity,
73933
74069
  // persona web 分享:visitor:list owner RPC 读访客名单(同 exchange upsert 的实例)
73934
74070
  visitorStore
73935
74071
  })
@@ -74044,38 +74180,8 @@ async function startDaemon(config) {
74044
74180
  return p3 ? { ...p3.tool ? { tool: p3.tool } : {}, ...p3.model ? { model: p3.model } : {} } : void 0;
74045
74181
  },
74046
74182
  exec: runCommand,
74047
- // GitHub 的身份 = 本机 git 的身份(spec 2026-09-14-persona-deploy-github-api §3.1):先问 git 的凭据存储,
74048
- // 没有退到本机凭据表里上云专用的那条(GITHUB_DEPLOY_TOKEN,与 app-builder 的 GITHUB_TOKEN 刻意不同名);
74049
- // 授权时先交给 git 存,git 没配 helper 才写 <dataDir>/secrets/github-deploy.env。顺序 / 退路的规则在 github-auth.ts,有测试
74050
- github: {
74051
- resolve: () => resolveGithubAuth({ exec: runCommand, fileToken: () => readSecrets()[DEPLOY_TOKEN_VAR] }),
74052
- store: (token) => storeGithubAuth({
74053
- exec: runCommand,
74054
- token,
74055
- writeFileToken: (t2) => {
74056
- upsertEnvFileVar(import_node_path79.default.join(localSecretsDir, DEPLOY_TOKEN_FILE), DEPLOY_TOKEN_VAR, t2);
74057
- logger.warn("deploy.github.token-in-file", { reason: "git \u6CA1\u914D credential helper\uFF0Ctoken \u843D\u672C\u673A\u51ED\u636E\u8868", file: DEPLOY_TOKEN_FILE });
74058
- }
74059
- }),
74060
- api: (token) => createGithubApi({ token }),
74061
- // 点链接的 OAuth 经中心(spec §8):登录凭证只在 daemon 内部传给中心,与 cloudConnectors 同一条纪律
74062
- oauth: {
74063
- start: async (returnTo) => {
74064
- const r2 = ownerIdentityStore.read();
74065
- if (!r2) throw new ClawdError(ERROR_CODES.FEISHU_LOGIN_REQUIRED, "FEISHU_LOGIN_REQUIRED: \u8FD8\u6CA1\u767B\u5F55\u98DE\u4E66\uFF0C\u7528 GitHub \u6388\u6743\u8981\u7ECF\u4E2D\u5FC3\uFF1B\u5148\u767B\u5F55\uFF0C\u6216\u5728\u5361\u7247\u4E0A\u624B\u52A8\u7C98 token");
74066
- try {
74067
- return await centralGithubDeployOauthStart({ api: config.clawosApi ?? DEFAULT_CLAWOS_API, ttcJwt: r2.ttcToken, provider: r2.identity.provider, ...returnTo ? { returnTo } : {} });
74068
- } catch (err) {
74069
- throw new ClawdError(ERROR_CODES.INTERNAL, `\u5411\u4E2D\u5FC3\u8981 GitHub \u6388\u6743\u94FE\u63A5\u6CA1\u6210\uFF1A${err instanceof Error ? err.message : String(err)}`);
74070
- }
74071
- },
74072
- token: async () => {
74073
- const r2 = ownerIdentityStore.read();
74074
- if (!r2) return void 0;
74075
- return centralGithubDeployToken({ api: config.clawosApi ?? DEFAULT_CLAWOS_API, ttcJwt: r2.ttcToken, provider: r2.identity.provider });
74076
- }
74077
- }
74078
- },
74183
+ githubIdentity,
74184
+ githubApi: (token) => createGithubApi({ token }),
74079
74185
  org: PERSONA_REPO_ORG,
74080
74186
  now: () => Date.now(),
74081
74187
  startScan: (personaId2, scanBase) => {
@@ -74122,6 +74228,7 @@ async function startDaemon(config) {
74122
74228
  }
74123
74229
  });
74124
74230
  Object.assign(handlers, deployHandlers);
74231
+ Object.assign(handlers, buildGithubAuthHandlers({ identity: githubIdentity }));
74125
74232
  const shiftSpawnFn = createShiftSpawnFn({ sessionManager: manager });
74126
74233
  const shiftScheduler = createShiftScheduler({
74127
74234
  store: shiftStore,