aws-runtime-bridge 1.7.7 → 1.7.9

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/config.d.ts CHANGED
@@ -7,7 +7,7 @@
7
7
  export declare const port: number;
8
8
  /** 默认调度器基础 URL */
9
9
  export declare const DEFAULT_SCHEDULER_BASE_URL = "http://localhost:8080";
10
- export type SchedulerBaseUrlSource = "env" | "single-auto-register-target" | "default" | "ambiguous-auto-register-targets";
10
+ export type SchedulerBaseUrlSource = "request" | "env" | "single-auto-register-target" | "default" | "ambiguous-auto-register-targets";
11
11
  export interface SchedulerBaseUrlResolution {
12
12
  url: string;
13
13
  source: SchedulerBaseUrlSource;
@@ -18,6 +18,11 @@ export interface SchedulerBaseUrlResolution {
18
18
  * 主流程:显式环境变量优先;未配置时,单个自动注册目标可作为宿主机部署的零额外配置回退;多目标不静默取第一个。
19
19
  */
20
20
  export declare function resolveSchedulerBaseUrl(): SchedulerBaseUrlResolution;
21
+ /**
22
+ * 解析调度中心基础 URL。
23
+ * 主流程:服务端请求携带的 scheduler 地址优先;再读显式环境变量;最后回退到单个自动注册目标或默认值。
24
+ */
25
+ export declare function resolveSchedulerBaseUrlFrom(requestSchedulerBaseUrl: unknown): SchedulerBaseUrlResolution;
21
26
  /** 调度器基础 URL(兼容旧导入;请求处理应优先调用 resolveSchedulerBaseUrl 获取最新值) */
22
27
  export declare const schedulerBaseUrl: string;
23
28
  /** Node 环境 */
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH,WAAW;AACX,eAAO,MAAM,IAAI,EAAE,MAElB,CAAC;AAEF,kBAAkB;AAClB,eAAO,MAAM,0BAA0B,0BAA0B,CAAC;AAElE,MAAM,MAAM,sBAAsB,GAC9B,KAAK,GACL,6BAA6B,GAC7B,SAAS,GACT,iCAAiC,CAAC;AAEtC,MAAM,WAAW,0BAA0B;IACzC,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,sBAAsB,CAAC;IAC/B,mBAAmB,EAAE,MAAM,EAAE,CAAC;CAC/B;AA+ED;;;GAGG;AACH,wBAAgB,uBAAuB,IAAI,0BAA0B,CAiCpE;AAED,+DAA+D;AAC/D,eAAO,MAAM,gBAAgB,EAAE,MAEH,CAAC;AAE7B,cAAc;AACd,eAAO,MAAM,OAAO,EAAE,MAA8C,CAAC;AAErE,oCAAoC;AACpC,eAAO,MAAM,oBAAoB,EAAE,OACiB,CAAC;AAErD,8BAA8B;AAC9B,eAAO,MAAM,kBAAkB,EAAE,MAAM,EAMC,CAAC;AAEzC,gEAAgE;AAChE,wBAAgB,uBAAuB,IAAI,IAAI,CAAG;AAElD,eAAe;AACf,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C;AAED,8CAA8C;AAC9C,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAK/D;AAED,2BAA2B;AAC3B,wBAAgB,sBAAsB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAKlE;AAED,0BAA0B;AAC1B,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAK9D;AAED,yBAAyB;AACzB,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAKjE;AAED,sBAAsB;AACtB,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAK9D;AAED,yBAAyB;AACzB,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAK7D;AAED,4BAA4B;AAC5B,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAKhE;AAED;;GAEG;AAEH,yBAAyB;AACzB,eAAO,MAAM,oBAAoB,QAEhC,CAAC;AAEF,0CAA0C;AAC1C,eAAO,MAAM,sBAAsB,EACrB,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAC;AAE1C,eAAe;AACf,eAAO,MAAM,mBAAmB,SACiB,CAAC;AAElD,iCAAiC;AACjC,eAAO,MAAM,2BAA2B,QAEvC,CAAC;AAEF,0CAA0C;AAC1C,eAAO,MAAM,oBAAoB,QAEhC,CAAC;AAEF,qCAAqC;AACrC,eAAO,MAAM,uBAAuB,QAEnC,CAAC;AAEF;;GAEG;AAEH,eAAe;AACf,eAAO,MAAM,qBAAqB,SAA2C,CAAC;AAE9E,YAAY;AACZ,eAAO,MAAM,uBAAuB,QAAkC,CAAC;AAEvE,iBAAiB;AACjB,eAAO,MAAM,sBAAsB,QAAiC,CAAC;AAErE,oBAAoB;AACpB,eAAO,MAAM,2BAA2B,QAAsC,CAAC;AAE/E,WAAW;AACX,eAAO,MAAM,0BAA0B,QAAqC,CAAC;AAE7E,WAAW;AACX,eAAO,MAAM,uBAAuB,QAAkC,CAAC;AAEvE,aAAa;AACb,eAAO,MAAM,4BAA4B,QACH,CAAC;AAEvC,kCAAkC;AAClC,eAAO,MAAM,wBAAwB,QAC4B,CAAC;AAElE,aAAa;AACb,eAAO,MAAM,yBAAyB,QAErC,CAAC;AAEF,iBAAiB;AACjB,eAAO,MAAM,4BAA4B,QAExC,CAAC"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH,WAAW;AACX,eAAO,MAAM,IAAI,EAAE,MAElB,CAAC;AAEF,kBAAkB;AAClB,eAAO,MAAM,0BAA0B,0BAA0B,CAAC;AAElE,MAAM,MAAM,sBAAsB,GAC9B,SAAS,GACT,KAAK,GACL,6BAA6B,GAC7B,SAAS,GACT,iCAAiC,CAAC;AAEtC,MAAM,WAAW,0BAA0B;IACzC,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,sBAAsB,CAAC;IAC/B,mBAAmB,EAAE,MAAM,EAAE,CAAC;CAC/B;AA+ED;;;GAGG;AACH,wBAAgB,uBAAuB,IAAI,0BAA0B,CAEpE;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,CACzC,uBAAuB,EAAE,OAAO,GAC/B,0BAA0B,CA4C5B;AAED,+DAA+D;AAC/D,eAAO,MAAM,gBAAgB,EAAE,MAEH,CAAC;AAE7B,cAAc;AACd,eAAO,MAAM,OAAO,EAAE,MAA8C,CAAC;AAErE,oCAAoC;AACpC,eAAO,MAAM,oBAAoB,EAAE,OACiB,CAAC;AAErD,8BAA8B;AAC9B,eAAO,MAAM,kBAAkB,EAAE,MAAM,EAMC,CAAC;AAEzC,gEAAgE;AAChE,wBAAgB,uBAAuB,IAAI,IAAI,CAAG;AAElD,eAAe;AACf,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C;AAED,8CAA8C;AAC9C,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAK/D;AAED,2BAA2B;AAC3B,wBAAgB,sBAAsB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAKlE;AAED,0BAA0B;AAC1B,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAK9D;AAED,yBAAyB;AACzB,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAKjE;AAED,sBAAsB;AACtB,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAK9D;AAED,yBAAyB;AACzB,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAK7D;AAED,4BAA4B;AAC5B,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAKhE;AAED;;GAEG;AAEH,yBAAyB;AACzB,eAAO,MAAM,oBAAoB,QAEhC,CAAC;AAEF,0CAA0C;AAC1C,eAAO,MAAM,sBAAsB,EACrB,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAC;AAE1C,eAAe;AACf,eAAO,MAAM,mBAAmB,SACiB,CAAC;AAElD,iCAAiC;AACjC,eAAO,MAAM,2BAA2B,QAEvC,CAAC;AAEF,0CAA0C;AAC1C,eAAO,MAAM,oBAAoB,QAEhC,CAAC;AAEF,qCAAqC;AACrC,eAAO,MAAM,uBAAuB,QAEnC,CAAC;AAEF;;GAEG;AAEH,eAAe;AACf,eAAO,MAAM,qBAAqB,SAA2C,CAAC;AAE9E,YAAY;AACZ,eAAO,MAAM,uBAAuB,QAAkC,CAAC;AAEvE,iBAAiB;AACjB,eAAO,MAAM,sBAAsB,QAAiC,CAAC;AAErE,oBAAoB;AACpB,eAAO,MAAM,2BAA2B,QAAsC,CAAC;AAE/E,WAAW;AACX,eAAO,MAAM,0BAA0B,QAAqC,CAAC;AAE7E,WAAW;AACX,eAAO,MAAM,uBAAuB,QAAkC,CAAC;AAEvE,aAAa;AACb,eAAO,MAAM,4BAA4B,QACH,CAAC;AAEvC,kCAAkC;AAClC,eAAO,MAAM,wBAAwB,QAC4B,CAAC;AAElE,aAAa;AACb,eAAO,MAAM,yBAAyB,QAErC,CAAC;AAEF,iBAAiB;AACjB,eAAO,MAAM,4BAA4B,QAExC,CAAC"}
package/dist/config.js CHANGED
@@ -76,6 +76,21 @@ function readAutoRegisterTargetUrls() {
76
76
  * 主流程:显式环境变量优先;未配置时,单个自动注册目标可作为宿主机部署的零额外配置回退;多目标不静默取第一个。
77
77
  */
78
78
  export function resolveSchedulerBaseUrl() {
79
+ return resolveSchedulerBaseUrlFrom(undefined);
80
+ }
81
+ /**
82
+ * 解析调度中心基础 URL。
83
+ * 主流程:服务端请求携带的 scheduler 地址优先;再读显式环境变量;最后回退到单个自动注册目标或默认值。
84
+ */
85
+ export function resolveSchedulerBaseUrlFrom(requestSchedulerBaseUrl) {
86
+ const normalizedRequestSchedulerBaseUrl = normalizeSchedulerHttpBaseUrl(requestSchedulerBaseUrl);
87
+ if (normalizedRequestSchedulerBaseUrl) {
88
+ return {
89
+ url: normalizedRequestSchedulerBaseUrl,
90
+ source: "request",
91
+ ambiguousTargetUrls: [],
92
+ };
93
+ }
79
94
  const envSchedulerBaseUrl = normalizeSchedulerHttpBaseUrl(process.env.AWS_RUNTIME_SCHEDULER_BASE_URL);
80
95
  if (envSchedulerBaseUrl) {
81
96
  return {
@@ -42,6 +42,18 @@ afterEach(() => {
42
42
  }
43
43
  });
44
44
  describe("scheduler base URL resolution", () => {
45
+ it("keeps scheduler URL supplied by the server request as the highest priority", async () => {
46
+ const runtimeHome = createRuntimeHome();
47
+ useRuntimeHome(runtimeHome);
48
+ process.env.AWS_RUNTIME_SCHEDULER_BASE_URL = "http://env.local:7380";
49
+ writeBridgeConfig(runtimeHome, [{ serverUrl: "http://target.local:7380" }]);
50
+ const { resolveSchedulerBaseUrlFrom } = await import("./config.js");
51
+ expect(resolveSchedulerBaseUrlFrom("http://server.local:7380/api")).toEqual({
52
+ url: "http://server.local:7380",
53
+ source: "request",
54
+ ambiguousTargetUrls: [],
55
+ });
56
+ });
45
57
  it("keeps explicit AWS_RUNTIME_SCHEDULER_BASE_URL as the highest priority", async () => {
46
58
  const runtimeHome = createRuntimeHome();
47
59
  useRuntimeHome(runtimeHome);
@@ -5,4 +5,11 @@
5
5
  */
6
6
  export declare const gitRouter: import("express-serve-static-core").Router;
7
7
  export declare function assertGitWorkspacePathAccessible(workspacePath: string): Promise<void>;
8
+ type GitDiffFileStatus = 'modified' | 'added' | 'deleted' | 'renamed';
9
+ /**
10
+ * 判断 Git diff 汇总行是否应该展示在差异树中。
11
+ * 普通 modified 且文本增删均为 0 通常是 filemode/元数据噪声,避免显示为“无差异内容”。
12
+ */
13
+ export declare function shouldIncludeGitDiffSummaryFile(status: GitDiffFileStatus, additions: number, deletions: number, isBinaryDiff: boolean): boolean;
14
+ export {};
8
15
  //# sourceMappingURL=git.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"git.d.ts","sourceRoot":"","sources":["../../src/routes/git.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAQH,eAAO,MAAM,SAAS,4CAAW,CAAC;AAWlC,wBAAsB,gCAAgC,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAe3F"}
1
+ {"version":3,"file":"git.d.ts","sourceRoot":"","sources":["../../src/routes/git.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAQH,eAAO,MAAM,SAAS,4CAAW,CAAC;AAWlC,wBAAsB,gCAAgC,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAe3F;AASD,KAAK,iBAAiB,GAAG,UAAU,GAAG,OAAO,GAAG,SAAS,GAAG,SAAS,CAAC;AAEtE;;;GAGG;AACH,wBAAgB,+BAA+B,CAC7C,MAAM,EAAE,iBAAiB,EACzB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,OAAO,GACpB,OAAO,CAMT"}
@@ -5,8 +5,8 @@
5
5
  */
6
6
  import { Router } from 'express';
7
7
  import { spawn } from 'node:child_process';
8
- import path from 'node:path';
9
8
  import { promises as fs } from 'node:fs';
9
+ import path from 'node:path';
10
10
  import { validateToken } from '../middleware/auth.js';
11
11
  export const gitRouter = Router();
12
12
  export async function assertGitWorkspacePathAccessible(workspacePath) {
@@ -25,6 +25,16 @@ export async function assertGitWorkspacePathAccessible(workspacePath) {
25
25
  throw new Error(`workspace path is not a directory: ${workspacePath}`);
26
26
  }
27
27
  }
28
+ /**
29
+ * 判断 Git diff 汇总行是否应该展示在差异树中。
30
+ * 普通 modified 且文本增删均为 0 通常是 filemode/元数据噪声,避免显示为“无差异内容”。
31
+ */
32
+ export function shouldIncludeGitDiffSummaryFile(status, additions, deletions, isBinaryDiff) {
33
+ if (status === 'added' || status === 'deleted' || status === 'renamed') {
34
+ return true;
35
+ }
36
+ return isBinaryDiff || additions > 0 || deletions > 0;
37
+ }
28
38
  /**
29
39
  * 执行 git 命令并返回输出
30
40
  */
@@ -418,10 +428,14 @@ gitRouter.post('/git/diff', validateToken, async (req, res) => {
418
428
  const parts = line.split('\t');
419
429
  if (parts.length < 3)
420
430
  continue;
431
+ const isBinaryDiff = parts[0] === '-' || parts[1] === '-';
421
432
  const additions = parts[0] === '-' ? 0 : parseInt(parts[0], 10) || 0;
422
433
  const deletions = parts[1] === '-' ? 0 : parseInt(parts[1], 10) || 0;
423
434
  const filePath = parseRenameDisplayPath(parts.slice(2).join('\t').trim());
424
435
  const status = statusMap.get(filePath) || 'modified';
436
+ if (!filePath || !shouldIncludeGitDiffSummaryFile(status, additions, deletions, isBinaryDiff)) {
437
+ continue;
438
+ }
425
439
  files.push({ path: filePath, status, additions, deletions });
426
440
  }
427
441
  res.json({
@@ -4,8 +4,8 @@
4
4
  import { mkdtemp, rm, writeFile } from 'node:fs/promises';
5
5
  import { tmpdir } from 'node:os';
6
6
  import path from 'node:path';
7
- import { describe, it, expect } from 'vitest';
8
- import { assertGitWorkspacePathAccessible } from './git.js';
7
+ import { describe, expect, it } from 'vitest';
8
+ import { assertGitWorkspacePathAccessible, shouldIncludeGitDiffSummaryFile } from './git.js';
9
9
  describe('git stash operations', () => {
10
10
  function parseStashList(output) {
11
11
  const stashes = [];
@@ -98,6 +98,21 @@ describe('git workspace path validation', () => {
98
98
  }
99
99
  });
100
100
  });
101
+ describe('git diff summary visibility', () => {
102
+ it('filters ordinary modified files without textual changes', () => {
103
+ expect(shouldIncludeGitDiffSummaryFile('modified', 0, 0, false)).toBe(false);
104
+ });
105
+ it('keeps modified files with text or binary changes', () => {
106
+ expect(shouldIncludeGitDiffSummaryFile('modified', 1, 0, false)).toBe(true);
107
+ expect(shouldIncludeGitDiffSummaryFile('modified', 0, 1, false)).toBe(true);
108
+ expect(shouldIncludeGitDiffSummaryFile('modified', 0, 0, true)).toBe(true);
109
+ });
110
+ it('keeps structural status changes even when line counts are zero', () => {
111
+ expect(shouldIncludeGitDiffSummaryFile('added', 0, 0, false)).toBe(true);
112
+ expect(shouldIncludeGitDiffSummaryFile('deleted', 0, 0, false)).toBe(true);
113
+ expect(shouldIncludeGitDiffSummaryFile('renamed', 0, 0, false)).toBe(true);
114
+ });
115
+ });
101
116
  describe('error handling', () => {
102
117
  it('detects "no local changes" error', () => {
103
118
  const isNoChangesError = (stderr) => stderr.includes('No local changes to save');
@@ -111,7 +126,7 @@ describe('error handling', () => {
111
126
  });
112
127
  it('detects invalid stash reference', () => {
113
128
  const isInvalidRef = (stderr) => stderr.includes('is not a valid reference');
114
- expect(isInvalidRef("stash@{99} is not a valid reference")).toBe(true);
129
+ expect(isInvalidRef('stash@{99} is not a valid reference')).toBe(true);
115
130
  expect(isInvalidRef('other error')).toBe(false);
116
131
  });
117
132
  });
@@ -1 +1 @@
1
- {"version":3,"file":"instance.d.ts","sourceRoot":"","sources":["../../src/routes/instance.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAYrD,wBAAgB,qBAAqB,CACnC,EAAE,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,gBAAgB,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,GAC/D,IAAI,CAEN;AAED,eAAO,MAAM,cAAc,4CAAW,CAAC;AAyBvC,wBAAgB,4BAA4B,CAAC,gBAAgB,EAAE,MAAM,GAAG;IACtE,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE;QACJ,EAAE,EAAE,OAAO,CAAC;QACZ,aAAa,CAAC,EAAE,SAAS,CAAC;QAC1B,oBAAoB,EAAE,OAAO,CAAC;QAC9B,qBAAqB,EAAE,OAAO,CAAC;QAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;CACH,CAqCA;AAED,MAAM,WAAW,4BAA4B;IAC3C,EAAE,EAAE,KAAK,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,gBAAgB,CAAC;IAC/B,aAAa,EAAE,SAAS,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,UAAU,iCAAiC;IACzC,EAAE,EAAE,KAAK,CAAC;IACV,KAAK,EAAE,8BAA8B,CAAC;IACtC,YAAY,EAAE,gBAAgB,CAAC;IAC/B,aAAa,EAAE,SAAS,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,sBAAsB,EAAE,MAAM,EAAE,CAAC;IACjC,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;GAGG;AACH,wBAAgB,iCAAiC,CAC/C,KAAK,EAAE,KAAK,EACZ,0BAA0B,EAAE,MAAM,GACjC,4BAA4B,CAiB9B;AAED;;;GAGG;AACH,wBAAgB,sCAAsC,CACpD,UAAU,EAAE,MAAM,EAAE,GACnB,iCAAiC,CAUnC;AAED;;;GAGG;AACH,wBAAgB,+BAA+B,CAAC,YAAY,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAQhF;AAED;;GAEG;AACH,wBAAgB,4BAA4B,CAC1C,cAAc,EAAE,MAAM,EAAE,EACxB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,GAC5C,MAAM,CAaR;AAED,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,GAC5C,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,iBAAiB,EAAE,WAAW,GAAG,SAAS,GAAG,YAAY,GAAG,OAAO,CAAC,CAAC,CAY3F"}
1
+ {"version":3,"file":"instance.d.ts","sourceRoot":"","sources":["../../src/routes/instance.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAYrD,wBAAgB,qBAAqB,CACnC,EAAE,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,gBAAgB,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,GAC/D,IAAI,CAEN;AAED,eAAO,MAAM,cAAc,4CAAW,CAAC;AAyBvC,wBAAgB,4BAA4B,CAAC,gBAAgB,EAAE,MAAM,GAAG;IACtE,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE;QACJ,EAAE,EAAE,OAAO,CAAC;QACZ,aAAa,CAAC,EAAE,SAAS,CAAC;QAC1B,oBAAoB,EAAE,OAAO,CAAC;QAC9B,qBAAqB,EAAE,OAAO,CAAC;QAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;CACH,CAqCA;AAED,MAAM,WAAW,4BAA4B;IAC3C,EAAE,EAAE,KAAK,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,gBAAgB,CAAC;IAC/B,aAAa,EAAE,SAAS,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,UAAU,iCAAiC;IACzC,EAAE,EAAE,KAAK,CAAC;IACV,KAAK,EAAE,8BAA8B,CAAC;IACtC,YAAY,EAAE,gBAAgB,CAAC;IAC/B,aAAa,EAAE,SAAS,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,sBAAsB,EAAE,MAAM,EAAE,CAAC;IACjC,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;GAGG;AACH,wBAAgB,iCAAiC,CAC/C,KAAK,EAAE,KAAK,EACZ,0BAA0B,EAAE,MAAM,GACjC,4BAA4B,CAiB9B;AAoDD;;;GAGG;AACH,wBAAgB,sCAAsC,CACpD,UAAU,EAAE,MAAM,EAAE,GACnB,iCAAiC,CAUnC;AAED;;;GAGG;AACH,wBAAgB,+BAA+B,CAAC,YAAY,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAQhF;AAED;;GAEG;AACH,wBAAgB,4BAA4B,CAC1C,cAAc,EAAE,MAAM,EAAE,EACxB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,GAC5C,MAAM,CAaR;AAED,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,GAC5C,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,iBAAiB,EAAE,WAAW,GAAG,SAAS,GAAG,YAAY,GAAG,OAAO,CAAC,CAAC,CAY3F"}
@@ -2,14 +2,14 @@ import { Router } from "express";
2
2
  import axios from "axios";
3
3
  import { createHash, timingSafeEqual } from "node:crypto";
4
4
  import { validateToken } from "../middleware/auth.js";
5
- import { resolveSchedulerBaseUrl } from "../config.js";
5
+ import { resolveSchedulerBaseUrlFrom } from "../config.js";
6
6
  import { getRuntimeAccessToken } from "../services/runtime-binding.js";
7
7
  import { loadInstanceState, saveInstanceState, } from "../services/instance-state.js";
8
8
  import { initInstance } from "../services/instance-init-service.js";
9
9
  import { discoverCcSwitchConfiguredItems, loadCcSwitchSdk } from "../services/cc-switch-sdk.js";
10
10
  import { syncLegacyStateFromSdk } from "../services/instance-init-service.js";
11
11
  import { detectToolStatuses, ensureToolsInstalled, SUPPORTED_INSTALLABLE_TOOLS, SUPPORTED_UNINSTALLABLE_TOOLS, uninstallTools, } from "../services/tool-installer.js";
12
- import { getConfiguredConnectionKeys } from "../services/auto-register.js";
12
+ import { getConfiguredConnectionKeys, requestRuntimeAccessTokenRefreshForServer } from "../services/auto-register.js";
13
13
  import { createLogger } from "../utils/logger.js";
14
14
  const log = createLogger("instance");
15
15
  const PANEL_TOOL_STATUS_KEYS = ["claude", "opencode", "codex"];
@@ -92,6 +92,40 @@ export function buildSchedulerPingFailureResponse(error, configuredSchedulerBase
92
92
  : "aws-runtime-bridge 已连通,但它回连调度中心失败。请确认 AWS_RUNTIME_SCHEDULER_BASE_URL 指向的调度中心地址可从 bridge 机器访问,并且运行时访问令牌仍有效。",
93
93
  };
94
94
  }
95
+ function getAxiosStatusCode(error) {
96
+ if (typeof error !== "object" || error === null) {
97
+ return undefined;
98
+ }
99
+ const response = Reflect.get(error, "response");
100
+ if (typeof response !== "object" || response === null) {
101
+ return undefined;
102
+ }
103
+ const status = Reflect.get(response, "status");
104
+ return typeof status === "number" ? status : undefined;
105
+ }
106
+ async function pingSchedulerWithToken(schedulerBaseUrl, runtimeAccessToken) {
107
+ try {
108
+ const schedulerResponse = await axios.get(`${schedulerBaseUrl}/api/runtime/ping`, { headers: { "X-Runtime-Token": runtimeAccessToken } });
109
+ return {
110
+ schedulerReachable: schedulerResponse.status >= 200 && schedulerResponse.status < 300,
111
+ tokenRefreshed: false,
112
+ };
113
+ }
114
+ catch (error) {
115
+ if (getAxiosStatusCode(error) !== 401) {
116
+ throw error;
117
+ }
118
+ const refreshResult = await requestRuntimeAccessTokenRefreshForServer(schedulerBaseUrl);
119
+ if (!refreshResult.success || !refreshResult.runtimeAccessToken) {
120
+ throw error;
121
+ }
122
+ const retryResponse = await axios.get(`${schedulerBaseUrl}/api/runtime/ping`, { headers: { "X-Runtime-Token": refreshResult.runtimeAccessToken } });
123
+ return {
124
+ schedulerReachable: retryResponse.status >= 200 && retryResponse.status < 300,
125
+ tokenRefreshed: true,
126
+ };
127
+ }
128
+ }
95
129
  /**
96
130
  * Build the response for ambiguous scheduler targets.
97
131
  * Main flow: never silently pick the first target; require an explicit scheduler URL or future scheduler identity routing.
@@ -154,8 +188,8 @@ instanceRouter.get("/connection-check", (req, res) => {
154
188
  const result = buildConnectionCheckResponse(connectionKeyMd5);
155
189
  res.status(result.status).json(result.body);
156
190
  });
157
- instanceRouter.get("/ping", validateToken, async (_req, res) => {
158
- const schedulerBaseUrlResolution = resolveSchedulerBaseUrl();
191
+ instanceRouter.get("/ping", validateToken, async (req, res) => {
192
+ const schedulerBaseUrlResolution = resolveSchedulerBaseUrlFrom(req.header("X-Scheduler-Base-Url"));
159
193
  if (schedulerBaseUrlResolution.source === "ambiguous-auto-register-targets") {
160
194
  res
161
195
  .status(400)
@@ -169,11 +203,12 @@ instanceRouter.get("/ping", validateToken, async (_req, res) => {
169
203
  res.status(401).json({ ok: false, error: "runtime_access_token_required" });
170
204
  return;
171
205
  }
172
- const schedulerResponse = await axios.get(`${schedulerBaseUrl}/api/runtime/ping`, { headers: { "X-Runtime-Token": runtimeAccessToken } });
206
+ const schedulerPing = await pingSchedulerWithToken(schedulerBaseUrl, runtimeAccessToken);
173
207
  res.json({
174
208
  ok: true,
175
209
  runtimeBridge: "healthy",
176
- schedulerReachable: schedulerResponse.status >= 200 && schedulerResponse.status < 300,
210
+ schedulerReachable: schedulerPing.schedulerReachable,
211
+ tokenRefreshed: schedulerPing.tokenRefreshed,
177
212
  schedulerBaseUrl,
178
213
  });
179
214
  }
@@ -2,8 +2,17 @@
2
2
  * Instance 路由单元测试
3
3
  */
4
4
  import { describe, it, expect, vi, afterEach } from 'vitest';
5
+ vi.mock('axios', () => ({
6
+ default: {
7
+ get: vi.fn(),
8
+ },
9
+ }));
5
10
  vi.mock('../services/auto-register.js', () => ({
6
11
  getConfiguredConnectionKeys: vi.fn(() => []),
12
+ requestRuntimeAccessTokenRefreshForServer: vi.fn(),
13
+ }));
14
+ vi.mock('../services/runtime-binding.js', () => ({
15
+ getRuntimeAccessToken: vi.fn(() => 'stale-runtime-token-123456'),
7
16
  }));
8
17
  afterEach(() => {
9
18
  vi.clearAllMocks();
@@ -203,6 +212,67 @@ describe('instance route validation', () => {
203
212
  expect(response.runtimeBridge).toBe('healthy');
204
213
  expect(response.hint).toContain('运行时访问令牌仍有效');
205
214
  });
215
+ it('refreshes runtime token and retries scheduler ping after 401', async () => {
216
+ process.env.AWS_RUNTIME_SCHEDULER_BASE_URL = 'http://localhost:8080';
217
+ const { default: axios } = await import('axios');
218
+ const { requestRuntimeAccessTokenRefreshForServer } = await import('../services/auto-register.js');
219
+ const { instanceRouter } = await import('./instance.js');
220
+ vi.mocked(requestRuntimeAccessTokenRefreshForServer).mockResolvedValue({
221
+ success: true,
222
+ runtimeAccessToken: 'fresh-runtime-token-123456',
223
+ updated: true,
224
+ });
225
+ vi.mocked(axios.get)
226
+ .mockRejectedValueOnce({ response: { status: 401 }, message: 'Request failed with status code 401' })
227
+ .mockResolvedValueOnce({ status: 200 });
228
+ const handler = instanceRouter.stack.find((layer) => layer.route?.path === '/ping')?.route?.stack[1]?.handle;
229
+ expect(handler).toBeTypeOf('function');
230
+ const json = vi.fn();
231
+ const status = vi.fn(() => ({ json }));
232
+ await handler?.({ header: vi.fn(() => '') }, { json, status }, vi.fn());
233
+ expect(requestRuntimeAccessTokenRefreshForServer).toHaveBeenCalledWith('http://localhost:8080');
234
+ expect(axios.get).toHaveBeenNthCalledWith(1, 'http://localhost:8080/api/runtime/ping', {
235
+ headers: { 'X-Runtime-Token': 'stale-runtime-token-123456' },
236
+ });
237
+ expect(axios.get).toHaveBeenNthCalledWith(2, 'http://localhost:8080/api/runtime/ping', {
238
+ headers: { 'X-Runtime-Token': 'fresh-runtime-token-123456' },
239
+ });
240
+ expect(json).toHaveBeenCalledWith({
241
+ ok: true,
242
+ runtimeBridge: 'healthy',
243
+ schedulerReachable: true,
244
+ tokenRefreshed: true,
245
+ schedulerBaseUrl: 'http://localhost:8080',
246
+ });
247
+ expect(status).not.toHaveBeenCalled();
248
+ });
249
+ it('uses scheduler base URL supplied by server ping request before local bridge config', async () => {
250
+ process.env.AWS_RUNTIME_SCHEDULER_BASE_URL = 'http://localhost:8080';
251
+ const { default: axios } = await import('axios');
252
+ const { getRuntimeAccessToken } = await import('../services/runtime-binding.js');
253
+ const { instanceRouter } = await import('./instance.js');
254
+ vi.mocked(getRuntimeAccessToken).mockReturnValue('stale-runtime-token-123456');
255
+ vi.mocked(axios.get).mockReset();
256
+ vi.mocked(axios.get).mockResolvedValueOnce({ status: 200 });
257
+ const handler = instanceRouter.stack.find((layer) => layer.route?.path === '/ping')?.route?.stack[1]?.handle;
258
+ expect(handler).toBeTypeOf('function');
259
+ const json = vi.fn();
260
+ const status = vi.fn(() => ({ json }));
261
+ const header = vi.fn((name) => (name === 'X-Scheduler-Base-Url' ? 'http://server.local:7380/api' : ''));
262
+ await handler?.({ header }, { json, status }, vi.fn());
263
+ expect(getRuntimeAccessToken).toHaveBeenCalledWith(undefined, 'http://server.local:7380');
264
+ expect(axios.get).toHaveBeenCalledWith('http://server.local:7380/api/runtime/ping', {
265
+ headers: { 'X-Runtime-Token': 'stale-runtime-token-123456' },
266
+ });
267
+ expect(json).toHaveBeenCalledWith({
268
+ ok: true,
269
+ runtimeBridge: 'healthy',
270
+ schedulerReachable: true,
271
+ tokenRefreshed: false,
272
+ schedulerBaseUrl: 'http://server.local:7380',
273
+ });
274
+ expect(status).not.toHaveBeenCalled();
275
+ });
206
276
  it('explains ambiguous auto-register scheduler targets without picking the first one', async () => {
207
277
  const { buildAmbiguousSchedulerBaseUrlResponse } = await import('./instance.js');
208
278
  const response = buildAmbiguousSchedulerBaseUrlResponse([
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aws-runtime-bridge",
3
- "version": "1.7.7",
3
+ "version": "1.7.9",
4
4
  "description": "AgentsWorkStudio runtime bridge service for machine-level agent runtime integration",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",