aws-runtime-bridge 1.7.32 → 1.7.33

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/index.js CHANGED
@@ -35,6 +35,7 @@ import { ensureAwsClientAgentMcpReleased } from "./services/aws-client-agent-mcp
35
35
  import { ensureStartupConfig } from "./services/startup-config-wizard.js";
36
36
  import { handleCliCommand } from "./services/cli-commands.js";
37
37
  import { buildGracefulShutdownPlan, shouldCleanupPersistedSessionsOnStartup, } from "./services/runtime-lifecycle-policy.js";
38
+ import { deliverPendingBridgeLifecycleNotifications } from "./services/lifecycle-state.js";
38
39
  const cliCommandResult = await handleCliCommand();
39
40
  if (cliCommandResult.handled) {
40
41
  process.exit(cliCommandResult.exitCode);
@@ -81,6 +82,10 @@ performAutoRegister()
81
82
  // 不再默认把该 bridge 下的 Agent 标记为 offline/stopped,避免 server/bridge
82
83
  // 短暂重启导致正在运行的 Agent 被误停。
83
84
  return performBridgeRestartCleanup();
85
+ })
86
+ .then(() => deliverPendingBridgeLifecycleNotifications())
87
+ .then(() => {
88
+ logger.info("[runtime-bridge] Bridge 生命周期通知同步完成");
84
89
  })
85
90
  .catch((err) => {
86
91
  logger.warn("[runtime-bridge] 自动注册或清理出错:", err);
@@ -1 +1 @@
1
- {"version":3,"file":"instance.d.ts","sourceRoot":"","sources":["../../src/routes/instance.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAoBrD,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;AAmGvC,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"}
1
+ {"version":3,"file":"instance.d.ts","sourceRoot":"","sources":["../../src/routes/instance.ts"],"names":[],"mappings":"AA4BA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAoBrD,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;AAsJvC,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"}
@@ -11,6 +11,7 @@ import { discoverCcSwitchConfiguredItems, loadCcSwitchSdk } from "../services/cc
11
11
  import { syncLegacyStateFromSdk } from "../services/instance-init-service.js";
12
12
  import { detectToolStatuses, ensureToolsInstalled, SUPPORTED_INSTALLABLE_TOOLS, SUPPORTED_UNINSTALLABLE_TOOLS, uninstallTools, } from "../services/tool-installer.js";
13
13
  import { getConfiguredConnectionKeys, requestRuntimeAccessTokenRefreshForServer } from "../services/auto-register.js";
14
+ import { createBridgeLifecycleOperation, deliverPendingBridgeLifecycleNotifications, upsertBridgeLifecycleOperation, } from "../services/lifecycle-state.js";
14
15
  import { createLogger } from "../utils/logger.js";
15
16
  const log = createLogger("instance");
16
17
  const PANEL_TOOL_STATUS_KEYS = ["claude", "opencode", "codex"];
@@ -56,6 +57,50 @@ function runCommand(command, args) {
56
57
  });
57
58
  });
58
59
  }
60
+ function normalizeOptionalText(value) {
61
+ const normalized = String(value || "").trim();
62
+ return normalized || undefined;
63
+ }
64
+ function resolveRequestRuntimeBridgeBaseUrl(req) {
65
+ const host = req.get("host");
66
+ if (!host) {
67
+ return undefined;
68
+ }
69
+ return `${req.protocol}://${host}`;
70
+ }
71
+ async function runBridgeUpdateInBackground(input) {
72
+ const runningOperation = {
73
+ ...input.operation,
74
+ status: "running",
75
+ message: "Bridge 更新正在后台执行。",
76
+ };
77
+ await upsertBridgeLifecycleOperation(runningOperation);
78
+ try {
79
+ log.info("[update-bridge] 正在后台更新 aws-runtime-bridge 到最新版本");
80
+ const result = await runCommand(input.command, input.args);
81
+ const succeeded = result.exitCode === 0;
82
+ await upsertBridgeLifecycleOperation({
83
+ ...runningOperation,
84
+ status: succeeded ? "succeeded" : "failed",
85
+ message: succeeded
86
+ ? "Bridge 更新完成,请重启实例 Bridge 以加载新版本。"
87
+ : `Bridge 更新失败,npm 退出码: ${result.exitCode ?? "unknown"}`,
88
+ error: succeeded ? undefined : result.stderr || result.stdout,
89
+ });
90
+ await deliverPendingBridgeLifecycleNotifications();
91
+ }
92
+ catch (error) {
93
+ const err = error;
94
+ log.error("[update-bridge] background update failed:", err);
95
+ await upsertBridgeLifecycleOperation({
96
+ ...runningOperation,
97
+ status: "failed",
98
+ message: "Bridge 更新失败。",
99
+ error: err.message || "update bridge failed",
100
+ });
101
+ await deliverPendingBridgeLifecycleNotifications();
102
+ }
103
+ }
59
104
  async function writeRestartSessionFlag(preserveSessions) {
60
105
  const fs = await import("node:fs/promises");
61
106
  const path = await import("node:path");
@@ -568,24 +613,29 @@ instanceRouter.post("/prepare-restart", validateToken, async (req, res) => {
568
613
  * 通过当前 Bridge 进程执行全局 npm 包更新,等价于 CLI 的
569
614
  * `awsb update`,用于面板上的“更新实例 Bridge”。
570
615
  */
571
- instanceRouter.post("/update-bridge", validateToken, async (_req, res) => {
616
+ instanceRouter.post("/update-bridge", validateToken, async (req, res) => {
572
617
  const command = resolveNpmExecutable();
573
618
  const args = ["install", "-g", "aws-runtime-bridge@latest"];
619
+ const requestedByUserId = normalizeOptionalText(req.body?.requestedByUserId);
574
620
  try {
575
- log.info("[update-bridge] 正在更新 aws-runtime-bridge 到最新版本");
576
- const result = await runCommand(command, args);
577
- if (result.exitCode !== 0) {
578
- res.status(500).json({
579
- ok: false,
580
- error: `更新失败,npm 退出码: ${result.exitCode ?? "unknown"}`,
581
- result,
582
- });
621
+ if (!requestedByUserId) {
622
+ res.status(400).json({ error: "requestedByUserId is required" });
583
623
  return;
584
624
  }
625
+ const operation = createBridgeLifecycleOperation({
626
+ operationType: "update",
627
+ requestedByUserId,
628
+ schedulerBaseUrl: req.body?.schedulerBaseUrl,
629
+ runtimeBridgeBaseUrl: resolveRequestRuntimeBridgeBaseUrl(req),
630
+ status: "accepted",
631
+ message: "Bridge 更新请求已收到,将在后台执行。",
632
+ });
633
+ await upsertBridgeLifecycleOperation(operation);
634
+ void runBridgeUpdateInBackground({ command, args, operation });
585
635
  res.json({
586
636
  ok: true,
587
- message: "Bridge 更新完成,请重启实例 Bridge 以加载新版本。",
588
- result,
637
+ operationId: operation.operationId,
638
+ message: "Bridge 更新已在后台开始,完成后会通过消息中心通知结果。",
589
639
  });
590
640
  }
591
641
  catch (error) {
@@ -602,13 +652,28 @@ instanceRouter.post("/update-bridge", validateToken, async (_req, res) => {
602
652
  */
603
653
  instanceRouter.post("/restart-bridge", validateToken, async (req, res) => {
604
654
  const { preserveSessions = true } = req.body || {};
655
+ const requestedByUserId = normalizeOptionalText(req.body?.requestedByUserId);
605
656
  try {
657
+ if (!requestedByUserId) {
658
+ res.status(400).json({ error: "requestedByUserId is required" });
659
+ return;
660
+ }
606
661
  log.info(`[restart-bridge] 收到实例 Bridge 重启请求,preserveSessions=${preserveSessions}`);
662
+ const operation = createBridgeLifecycleOperation({
663
+ operationType: "restart",
664
+ requestedByUserId,
665
+ schedulerBaseUrl: req.body?.schedulerBaseUrl,
666
+ runtimeBridgeBaseUrl: resolveRequestRuntimeBridgeBaseUrl(req),
667
+ status: "accepted",
668
+ message: "Bridge 已收到重启请求;下次启动后会发送确认通知。",
669
+ });
670
+ await upsertBridgeLifecycleOperation(operation);
607
671
  await writeRestartSessionFlag(Boolean(preserveSessions));
608
672
  res.json({
609
673
  ok: true,
674
+ operationId: operation.operationId,
610
675
  preserveSessions: Boolean(preserveSessions),
611
- message: "Bridge 正在优雅退出;若实例由 systemd/PM2 等守护管理,将自动重启,否则需要手动重新启动。",
676
+ message: "Bridge 正在优雅退出;下次启动后会通过消息中心通知结果。若未由守护进程管理,请手动重新启动。",
612
677
  });
613
678
  setTimeout(() => {
614
679
  if (gracefulShutdownFn) {
@@ -7,6 +7,7 @@ const spawnMock = vi.hoisted(() => vi.fn());
7
7
  vi.mock('axios', () => ({
8
8
  default: {
9
9
  get: vi.fn(),
10
+ post: vi.fn(),
10
11
  },
11
12
  }));
12
13
  vi.mock('../services/auto-register.js', () => ({
@@ -15,6 +16,22 @@ vi.mock('../services/auto-register.js', () => ({
15
16
  }));
16
17
  vi.mock('../services/runtime-binding.js', () => ({
17
18
  getRuntimeAccessToken: vi.fn(() => 'stale-runtime-token-123456'),
19
+ loadRuntimeBinding: vi.fn(() => ({ status: 'paired', userId: 'user-1', schedulerBaseUrl: 'http://server.local:7380' })),
20
+ normalizeSchedulerBaseUrl: vi.fn((value) => (value ? String(value).replace(/\/+$/, '') : undefined)),
21
+ }));
22
+ vi.mock('../services/lifecycle-state.js', () => ({
23
+ createBridgeLifecycleOperation: vi.fn((input) => ({
24
+ operationId: 'operation-1',
25
+ requestedAt: '2026-01-01T00:00:00.000Z',
26
+ updatedAt: '2026-01-01T00:00:00.000Z',
27
+ ...input,
28
+ })),
29
+ deliverPendingBridgeLifecycleNotifications: vi.fn(),
30
+ upsertBridgeLifecycleOperation: vi.fn(),
31
+ }));
32
+ vi.mock('../config.js', async (importOriginal) => ({
33
+ ...(await importOriginal()),
34
+ getRuntimeHomeDir: vi.fn(() => process.cwd()),
18
35
  }));
19
36
  vi.mock('node:child_process', async (importOriginal) => ({
20
37
  ...(await importOriginal()),
@@ -321,34 +338,29 @@ describe('instance route validation', () => {
321
338
  hint: expect.stringContaining('AWS_RUNTIME_SCHEDULER_BASE_URL'),
322
339
  });
323
340
  });
324
- it('updates bridge package through npm global install route', async () => {
341
+ it('starts bridge package update in background and returns operation id', async () => {
325
342
  const { instanceRouter } = await import('./instance.js');
326
343
  mockSpawnResult(0, 'updated');
327
344
  const handler = instanceRouter.stack.find((layer) => layer.route?.path === '/update-bridge')?.route?.stack[1]?.handle;
328
345
  expect(handler).toBeTypeOf('function');
329
346
  const { json, status } = createMockResponse();
330
- await handler?.({}, { json, status }, vi.fn());
331
- expect(spawnMock).toHaveBeenCalledWith(process.platform === 'win32' ? 'npm.cmd' : 'npm', ['install', '-g', 'aws-runtime-bridge@latest'], { shell: false, windowsHide: true });
347
+ await handler?.({ body: { requestedByUserId: 'user-1' }, get: () => 'bridge.local:18081', protocol: 'http' }, { json, status }, vi.fn());
332
348
  expect(status).not.toHaveBeenCalled();
333
- expect(json).toHaveBeenCalledWith({
349
+ expect(json).toHaveBeenCalledWith(expect.objectContaining({
334
350
  ok: true,
335
- message: 'Bridge 更新完成,请重启实例 Bridge 以加载新版本。',
336
- result: expect.objectContaining({ exitCode: 0, stdout: 'updated' }),
337
- });
351
+ message: 'Bridge 更新已在后台开始,完成后会通过消息中心通知结果。',
352
+ }));
353
+ await new Promise((resolve) => setImmediate(resolve));
354
+ expect(spawnMock).toHaveBeenCalledWith(process.platform === 'win32' ? 'npm.cmd' : 'npm', ['install', '-g', 'aws-runtime-bridge@latest'], { shell: false, windowsHide: true });
338
355
  });
339
- it('returns update failure details when npm exits non-zero', async () => {
356
+ it('rejects background bridge update without requester user id', async () => {
340
357
  const { instanceRouter } = await import('./instance.js');
341
- mockSpawnResult(1, '', 'permission denied');
342
358
  const handler = instanceRouter.stack.find((layer) => layer.route?.path === '/update-bridge')?.route?.stack[1]?.handle;
343
359
  expect(handler).toBeTypeOf('function');
344
360
  const { json, status } = createMockResponse();
345
361
  await handler?.({}, { json, status }, vi.fn());
346
- expect(status).toHaveBeenCalledWith(500);
347
- expect(json).toHaveBeenCalledWith({
348
- ok: false,
349
- error: '更新失败,npm 退出码: 1',
350
- result: expect.objectContaining({ exitCode: 1, stderr: 'permission denied' }),
351
- });
362
+ expect(status).toHaveBeenCalledWith(400);
363
+ expect(json).toHaveBeenCalledWith({ error: 'requestedByUserId is required' });
352
364
  });
353
365
  it('responds before triggering graceful bridge restart', async () => {
354
366
  vi.useFakeTimers();
@@ -358,13 +370,13 @@ describe('instance route validation', () => {
358
370
  const handler = instanceRouter.stack.find((layer) => layer.route?.path === '/restart-bridge')?.route?.stack[1]?.handle;
359
371
  expect(handler).toBeTypeOf('function');
360
372
  const { json, status } = createMockResponse();
361
- await handler?.({ body: { preserveSessions: true } }, { json, status }, vi.fn());
373
+ await handler?.({ body: { preserveSessions: true, requestedByUserId: 'user-1' }, get: () => 'bridge.local:18081', protocol: 'http' }, { json, status }, vi.fn());
362
374
  expect(status).not.toHaveBeenCalled();
363
- expect(json).toHaveBeenCalledWith({
375
+ expect(json).toHaveBeenCalledWith(expect.objectContaining({
364
376
  ok: true,
365
377
  preserveSessions: true,
366
- message: 'Bridge 正在优雅退出;若实例由 systemd/PM2 等守护管理,将自动重启,否则需要手动重新启动。',
367
- });
378
+ message: 'Bridge 正在优雅退出;下次启动后会通过消息中心通知结果。若未由守护进程管理,请手动重新启动。',
379
+ }));
368
380
  expect(gracefulShutdown).not.toHaveBeenCalled();
369
381
  await vi.advanceTimersByTimeAsync(250);
370
382
  expect(gracefulShutdown).toHaveBeenCalledWith('PANEL_RESTART', true);
@@ -0,0 +1,30 @@
1
+ export type BridgeLifecycleOperationType = "update" | "restart";
2
+ export type BridgeLifecycleOperationStatus = "accepted" | "running" | "succeeded" | "failed" | "startup-confirmed";
3
+ export interface BridgeLifecycleOperation {
4
+ operationId: string;
5
+ operationType: BridgeLifecycleOperationType;
6
+ status: BridgeLifecycleOperationStatus;
7
+ requestedByUserId: string;
8
+ requestedAt: string;
9
+ updatedAt: string;
10
+ schedulerBaseUrl?: string;
11
+ runtimeBridgeBaseUrl?: string;
12
+ message?: string;
13
+ error?: string;
14
+ }
15
+ export declare function createBridgeLifecycleOperation(input: {
16
+ operationType: BridgeLifecycleOperationType;
17
+ requestedByUserId: string;
18
+ schedulerBaseUrl?: unknown;
19
+ runtimeBridgeBaseUrl?: unknown;
20
+ status?: BridgeLifecycleOperationStatus;
21
+ message?: string;
22
+ }): BridgeLifecycleOperation;
23
+ export declare function upsertBridgeLifecycleOperation(operation: BridgeLifecycleOperation): Promise<void>;
24
+ export declare function removeBridgeLifecycleOperation(operationId: string): Promise<void>;
25
+ /**
26
+ * 投递 Bridge 生命周期通知。
27
+ * 主流程:把已完成操作回调调度中心;已接收的操作从本地状态移除,失败则保留待下次启动重试。
28
+ */
29
+ export declare function deliverPendingBridgeLifecycleNotifications(): Promise<void>;
30
+ //# sourceMappingURL=lifecycle-state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lifecycle-state.d.ts","sourceRoot":"","sources":["../../src/services/lifecycle-state.ts"],"names":[],"mappings":"AAcA,MAAM,MAAM,4BAA4B,GAAG,QAAQ,GAAG,SAAS,CAAC;AAChE,MAAM,MAAM,8BAA8B,GACtC,UAAU,GACV,SAAS,GACT,WAAW,GACX,QAAQ,GACR,mBAAmB,CAAC;AAExB,MAAM,WAAW,wBAAwB;IACvC,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,4BAA4B,CAAC;IAC5C,MAAM,EAAE,8BAA8B,CAAC;IACvC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAuCD,wBAAgB,8BAA8B,CAAC,KAAK,EAAE;IACpD,aAAa,EAAE,4BAA4B,CAAC;IAC5C,iBAAiB,EAAE,MAAM,CAAC;IAC1B,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,MAAM,CAAC,EAAE,8BAA8B,CAAC;IACxC,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,GAAG,wBAAwB,CAgB3B;AAED,wBAAsB,8BAA8B,CAClD,SAAS,EAAE,wBAAwB,GAClC,OAAO,CAAC,IAAI,CAAC,CAOf;AAED,wBAAsB,8BAA8B,CAClD,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,IAAI,CAAC,CAKf;AAiCD;;;GAGG;AACH,wBAAsB,0CAA0C,IAAI,OAAO,CAAC,IAAI,CAAC,CA2BhF"}
@@ -0,0 +1,110 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { randomUUID } from "node:crypto";
4
+ import axios from "axios";
5
+ import { getRuntimeHomeDir, resolveSchedulerBaseUrlFrom } from "../config.js";
6
+ import { getRuntimeAccessToken, loadRuntimeBinding, normalizeSchedulerBaseUrl, } from "./runtime-binding.js";
7
+ import { createLogger } from "../utils/logger.js";
8
+ const log = createLogger("bridge-lifecycle");
9
+ function lifecycleStateFile() {
10
+ return path.join(getRuntimeHomeDir(), ".agentswork", "runtime-bridge", "lifecycle-state.json");
11
+ }
12
+ function normalizeText(value) {
13
+ const normalized = String(value || "").trim();
14
+ return normalized || undefined;
15
+ }
16
+ async function loadLifecycleState() {
17
+ try {
18
+ const raw = await fs.readFile(lifecycleStateFile(), "utf8");
19
+ const parsed = JSON.parse(raw);
20
+ return { operations: Array.isArray(parsed.operations) ? parsed.operations : [] };
21
+ }
22
+ catch {
23
+ return { operations: [] };
24
+ }
25
+ }
26
+ async function saveLifecycleState(state) {
27
+ const file = lifecycleStateFile();
28
+ await fs.mkdir(path.dirname(file), { recursive: true });
29
+ await fs.writeFile(file, `${JSON.stringify(state, null, 2)}\n`, {
30
+ encoding: "utf8",
31
+ mode: 0o600,
32
+ });
33
+ }
34
+ export function createBridgeLifecycleOperation(input) {
35
+ const now = new Date().toISOString();
36
+ const schedulerBaseUrl = normalizeSchedulerBaseUrl(input.schedulerBaseUrl) ||
37
+ resolveSchedulerBaseUrlFrom(undefined).url;
38
+ return {
39
+ operationId: randomUUID(),
40
+ operationType: input.operationType,
41
+ status: input.status || "accepted",
42
+ requestedByUserId: input.requestedByUserId,
43
+ requestedAt: now,
44
+ updatedAt: now,
45
+ schedulerBaseUrl,
46
+ runtimeBridgeBaseUrl: normalizeText(input.runtimeBridgeBaseUrl),
47
+ message: input.message,
48
+ };
49
+ }
50
+ export async function upsertBridgeLifecycleOperation(operation) {
51
+ const state = await loadLifecycleState();
52
+ const nextOperations = state.operations.filter((item) => item.operationId !== operation.operationId);
53
+ nextOperations.push({ ...operation, updatedAt: new Date().toISOString() });
54
+ await saveLifecycleState({ operations: nextOperations });
55
+ }
56
+ export async function removeBridgeLifecycleOperation(operationId) {
57
+ const state = await loadLifecycleState();
58
+ await saveLifecycleState({
59
+ operations: state.operations.filter((item) => item.operationId !== operationId),
60
+ });
61
+ }
62
+ function shouldDeliver(operation) {
63
+ return ["succeeded", "failed", "startup-confirmed"].includes(operation.status);
64
+ }
65
+ async function sendLifecycleNotification(operation) {
66
+ const schedulerBaseUrl = normalizeSchedulerBaseUrl(operation.schedulerBaseUrl) ||
67
+ normalizeSchedulerBaseUrl(loadRuntimeBinding().schedulerBaseUrl) ||
68
+ resolveSchedulerBaseUrlFrom(undefined).url;
69
+ const token = getRuntimeAccessToken(operation.requestedByUserId, schedulerBaseUrl);
70
+ if (!token) {
71
+ throw new Error("runtime access token is missing");
72
+ }
73
+ await axios.post(`${schedulerBaseUrl}/api/runtime/callback/bridge-lifecycle`, {
74
+ operationId: operation.operationId,
75
+ operationType: operation.operationType,
76
+ status: operation.status,
77
+ requestedByUserId: operation.requestedByUserId,
78
+ message: operation.message,
79
+ error: operation.error,
80
+ runtimeBridgeBaseUrl: operation.runtimeBridgeBaseUrl,
81
+ }, { headers: { "X-Runtime-Token": token } });
82
+ }
83
+ /**
84
+ * 投递 Bridge 生命周期通知。
85
+ * 主流程:把已完成操作回调调度中心;已接收的操作从本地状态移除,失败则保留待下次启动重试。
86
+ */
87
+ export async function deliverPendingBridgeLifecycleNotifications() {
88
+ const state = await loadLifecycleState();
89
+ for (const operation of state.operations) {
90
+ const operationToDeliver = operation.operationType === "restart" && operation.status === "accepted"
91
+ ? {
92
+ ...operation,
93
+ status: "startup-confirmed",
94
+ message: operation.message || "Bridge 已在收到重启请求后完成本次启动。",
95
+ }
96
+ : operation;
97
+ if (!shouldDeliver(operationToDeliver)) {
98
+ continue;
99
+ }
100
+ try {
101
+ await sendLifecycleNotification(operationToDeliver);
102
+ await removeBridgeLifecycleOperation(operation.operationId);
103
+ log.info(`[bridge-lifecycle] notification delivered: ${operation.operationId}`);
104
+ }
105
+ catch (error) {
106
+ const err = error;
107
+ log.warn(`[bridge-lifecycle] notification delivery failed: ${operation.operationId}, ${err.message}`);
108
+ }
109
+ }
110
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aws-runtime-bridge",
3
- "version": "1.7.32",
3
+ "version": "1.7.33",
4
4
  "description": "AgentsWorkStudio runtime bridge service for machine-level agent runtime integration",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",