@agent-vm/agent-vm 0.0.31 → 0.0.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.
Files changed (190) hide show
  1. package/dist/backup/backup-create-operation.d.ts +3 -1
  2. package/dist/backup/backup-create-operation.d.ts.map +1 -1
  3. package/dist/backup/backup-create-operation.js +49 -14
  4. package/dist/backup/backup-create-operation.js.map +1 -1
  5. package/dist/backup/backup-manager.d.ts +8 -6
  6. package/dist/backup/backup-manager.d.ts.map +1 -1
  7. package/dist/backup/backup-manager.js +7 -5
  8. package/dist/backup/backup-manager.js.map +1 -1
  9. package/dist/backup/backup-restore-operation.d.ts +1 -1
  10. package/dist/backup/backup-restore-operation.d.ts.map +1 -1
  11. package/dist/backup/backup-restore-operation.js +13 -9
  12. package/dist/backup/backup-restore-operation.js.map +1 -1
  13. package/dist/build/docker-image-builder.d.ts.map +1 -1
  14. package/dist/build/docker-image-builder.js +12 -6
  15. package/dist/build/docker-image-builder.js.map +1 -1
  16. package/dist/cli/backup-commands.d.ts.map +1 -1
  17. package/dist/cli/backup-commands.js +4 -2
  18. package/dist/cli/backup-commands.js.map +1 -1
  19. package/dist/cli/commands/controller-definition.d.ts +96 -78
  20. package/dist/cli/commands/controller-definition.d.ts.map +1 -1
  21. package/dist/cli/commands/controller-definition.js +20 -0
  22. package/dist/cli/commands/controller-definition.js.map +1 -1
  23. package/dist/cli/commands/create-app.d.ts +96 -78
  24. package/dist/cli/commands/create-app.d.ts.map +1 -1
  25. package/dist/cli/commands/init-definition.d.ts.map +1 -1
  26. package/dist/cli/commands/init-definition.js +3 -1
  27. package/dist/cli/commands/init-definition.js.map +1 -1
  28. package/dist/cli/commands/paths-definition.d.ts.map +1 -1
  29. package/dist/cli/commands/paths-definition.js +9 -4
  30. package/dist/cli/commands/paths-definition.js.map +1 -1
  31. package/dist/cli/controller-operation-commands.d.ts.map +1 -1
  32. package/dist/cli/controller-operation-commands.js +70 -11
  33. package/dist/cli/controller-operation-commands.js.map +1 -1
  34. package/dist/cli/init-command.d.ts.map +1 -1
  35. package/dist/cli/init-command.js +57 -35
  36. package/dist/cli/init-command.js.map +1 -1
  37. package/dist/cli/lease-commands.d.ts.map +1 -1
  38. package/dist/cli/lease-commands.js +8 -0
  39. package/dist/cli/lease-commands.js.map +1 -1
  40. package/dist/cli/run-task.js +1 -1
  41. package/dist/cli/vm-host-system-templates.d.ts +1 -0
  42. package/dist/cli/vm-host-system-templates.d.ts.map +1 -1
  43. package/dist/cli/vm-host-system-templates.js +2 -2
  44. package/dist/cli/vm-host-system-templates.js.map +1 -1
  45. package/dist/config/system-config.d.ts +57 -13
  46. package/dist/config/system-config.d.ts.map +1 -1
  47. package/dist/config/system-config.js +181 -32
  48. package/dist/config/system-config.js.map +1 -1
  49. package/dist/controller/active-task-registry.d.ts +18 -3
  50. package/dist/controller/active-task-registry.d.ts.map +1 -1
  51. package/dist/controller/active-task-registry.js +33 -2
  52. package/dist/controller/active-task-registry.js.map +1 -1
  53. package/dist/controller/controller-runtime-operations.d.ts +15 -31
  54. package/dist/controller/controller-runtime-operations.d.ts.map +1 -1
  55. package/dist/controller/controller-runtime-operations.js +17 -92
  56. package/dist/controller/controller-runtime-operations.js.map +1 -1
  57. package/dist/controller/controller-runtime-types.d.ts +16 -11
  58. package/dist/controller/controller-runtime-types.d.ts.map +1 -1
  59. package/dist/controller/controller-runtime.d.ts.map +1 -1
  60. package/dist/controller/controller-runtime.js +126 -231
  61. package/dist/controller/controller-runtime.js.map +1 -1
  62. package/dist/controller/git-pull-default-operations.d.ts +101 -9
  63. package/dist/controller/git-pull-default-operations.d.ts.map +1 -1
  64. package/dist/controller/git-pull-default-operations.js +405 -68
  65. package/dist/controller/git-pull-default-operations.js.map +1 -1
  66. package/dist/controller/git-push-operations.d.ts +2 -0
  67. package/dist/controller/git-push-operations.d.ts.map +1 -1
  68. package/dist/controller/git-push-operations.js +267 -76
  69. package/dist/controller/git-push-operations.js.map +1 -1
  70. package/dist/controller/git-retry-support.d.ts +21 -0
  71. package/dist/controller/git-retry-support.d.ts.map +1 -0
  72. package/dist/controller/git-retry-support.js +60 -0
  73. package/dist/controller/git-retry-support.js.map +1 -0
  74. package/dist/controller/http/controller-client.d.ts +2 -0
  75. package/dist/controller/http/controller-client.d.ts.map +1 -1
  76. package/dist/controller/http/controller-client.js +13 -0
  77. package/dist/controller/http/controller-client.js.map +1 -1
  78. package/dist/controller/http/controller-http-route-support.d.ts +9 -4
  79. package/dist/controller/http/controller-http-route-support.d.ts.map +1 -1
  80. package/dist/controller/http/controller-http-route-support.js +17 -1
  81. package/dist/controller/http/controller-http-route-support.js.map +1 -1
  82. package/dist/controller/http/controller-http-routes.d.ts +10 -3
  83. package/dist/controller/http/controller-http-routes.d.ts.map +1 -1
  84. package/dist/controller/http/controller-http-routes.js +88 -15
  85. package/dist/controller/http/controller-http-routes.js.map +1 -1
  86. package/dist/controller/http/controller-lease-response-types.d.ts +17 -0
  87. package/dist/controller/http/controller-lease-response-types.d.ts.map +1 -0
  88. package/dist/controller/http/controller-lease-response-types.js +16 -0
  89. package/dist/controller/http/controller-lease-response-types.js.map +1 -0
  90. package/dist/controller/http/controller-request-schemas.d.ts +84 -13
  91. package/dist/controller/http/controller-request-schemas.d.ts.map +1 -1
  92. package/dist/controller/http/controller-request-schemas.js +110 -9
  93. package/dist/controller/http/controller-request-schemas.js.map +1 -1
  94. package/dist/controller/http/controller-zone-operation-routes.d.ts.map +1 -1
  95. package/dist/controller/http/controller-zone-operation-routes.js +140 -14
  96. package/dist/controller/http/controller-zone-operation-routes.js.map +1 -1
  97. package/dist/controller/leases/agent-sandbox-seeding.d.ts +55 -0
  98. package/dist/controller/leases/agent-sandbox-seeding.d.ts.map +1 -0
  99. package/dist/controller/leases/agent-sandbox-seeding.js +178 -0
  100. package/dist/controller/leases/agent-sandbox-seeding.js.map +1 -0
  101. package/dist/controller/leases/idle-reaper.d.ts +7 -2
  102. package/dist/controller/leases/idle-reaper.d.ts.map +1 -1
  103. package/dist/controller/leases/idle-reaper.js +19 -7
  104. package/dist/controller/leases/idle-reaper.js.map +1 -1
  105. package/dist/controller/leases/lease-idle-policy.d.ts +12 -0
  106. package/dist/controller/leases/lease-idle-policy.d.ts.map +1 -0
  107. package/dist/controller/leases/lease-idle-policy.js +28 -0
  108. package/dist/controller/leases/lease-idle-policy.js.map +1 -0
  109. package/dist/controller/leases/lease-manager.d.ts +21 -12
  110. package/dist/controller/leases/lease-manager.d.ts.map +1 -1
  111. package/dist/controller/leases/lease-manager.js +155 -57
  112. package/dist/controller/leases/lease-manager.js.map +1 -1
  113. package/dist/controller/leases/lease-scope.d.ts +12 -0
  114. package/dist/controller/leases/lease-scope.d.ts.map +1 -0
  115. package/dist/controller/leases/lease-scope.js +19 -0
  116. package/dist/controller/leases/lease-scope.js.map +1 -0
  117. package/dist/controller/leases/lease-workspace-paths.d.ts +14 -0
  118. package/dist/controller/leases/lease-workspace-paths.d.ts.map +1 -0
  119. package/dist/controller/leases/lease-workspace-paths.js +104 -0
  120. package/dist/controller/leases/lease-workspace-paths.js.map +1 -0
  121. package/dist/controller/runtime-instructions-builder.d.ts +1 -2
  122. package/dist/controller/runtime-instructions-builder.d.ts.map +1 -1
  123. package/dist/controller/runtime-instructions-builder.js +8 -9
  124. package/dist/controller/runtime-instructions-builder.js.map +1 -1
  125. package/dist/controller/task-config-builder.d.ts +2 -1
  126. package/dist/controller/task-config-builder.d.ts.map +1 -1
  127. package/dist/controller/task-config-builder.js +2 -1
  128. package/dist/controller/task-config-builder.js.map +1 -1
  129. package/dist/controller/worker-task-runner.d.ts +10 -4
  130. package/dist/controller/worker-task-runner.d.ts.map +1 -1
  131. package/dist/controller/worker-task-runner.js +147 -44
  132. package/dist/controller/worker-task-runner.js.map +1 -1
  133. package/dist/controller/zone-runtimes/openclaw-zone-runtime.d.ts +30 -0
  134. package/dist/controller/zone-runtimes/openclaw-zone-runtime.d.ts.map +1 -0
  135. package/dist/controller/zone-runtimes/openclaw-zone-runtime.js +110 -0
  136. package/dist/controller/zone-runtimes/openclaw-zone-runtime.js.map +1 -0
  137. package/dist/controller/zone-runtimes/worker-zone-runtime.d.ts +33 -0
  138. package/dist/controller/zone-runtimes/worker-zone-runtime.d.ts.map +1 -0
  139. package/dist/controller/zone-runtimes/worker-zone-runtime.js +224 -0
  140. package/dist/controller/zone-runtimes/worker-zone-runtime.js.map +1 -0
  141. package/dist/controller/zone-runtimes/zone-runtime-errors.d.ts +51 -0
  142. package/dist/controller/zone-runtimes/zone-runtime-errors.d.ts.map +1 -0
  143. package/dist/controller/zone-runtimes/zone-runtime-errors.js +94 -0
  144. package/dist/controller/zone-runtimes/zone-runtime-errors.js.map +1 -0
  145. package/dist/controller/zone-runtimes/zone-runtime-registry.d.ts +26 -0
  146. package/dist/controller/zone-runtimes/zone-runtime-registry.d.ts.map +1 -0
  147. package/dist/controller/zone-runtimes/zone-runtime-registry.js +84 -0
  148. package/dist/controller/zone-runtimes/zone-runtime-registry.js.map +1 -0
  149. package/dist/controller/zone-runtimes/zone-runtime-types.d.ts +89 -0
  150. package/dist/controller/zone-runtimes/zone-runtime-types.d.ts.map +1 -0
  151. package/dist/controller/zone-runtimes/zone-runtime-types.js +2 -0
  152. package/dist/controller/zone-runtimes/zone-runtime-types.js.map +1 -0
  153. package/dist/gateway/gateway-zone-orchestrator.d.ts.map +1 -1
  154. package/dist/gateway/gateway-zone-orchestrator.js +4 -2
  155. package/dist/gateway/gateway-zone-orchestrator.js.map +1 -1
  156. package/dist/gateway/gateway-zone-support.d.ts.map +1 -1
  157. package/dist/gateway/gateway-zone-support.js +22 -11
  158. package/dist/gateway/gateway-zone-support.js.map +1 -1
  159. package/dist/index.d.ts +1 -0
  160. package/dist/index.d.ts.map +1 -1
  161. package/dist/index.js +1 -0
  162. package/dist/index.js.map +1 -1
  163. package/dist/operations/config-validation.d.ts +1 -0
  164. package/dist/operations/config-validation.d.ts.map +1 -1
  165. package/dist/operations/config-validation.js +55 -5
  166. package/dist/operations/config-validation.js.map +1 -1
  167. package/dist/operations/controller-status.d.ts +13 -8
  168. package/dist/operations/controller-status.d.ts.map +1 -1
  169. package/dist/operations/controller-status.js +13 -10
  170. package/dist/operations/controller-status.js.map +1 -1
  171. package/dist/operations/destroy-zone.d.ts.map +1 -1
  172. package/dist/operations/destroy-zone.js +8 -1
  173. package/dist/operations/destroy-zone.js.map +1 -1
  174. package/dist/operations/doctor.d.ts +4 -0
  175. package/dist/operations/doctor.d.ts.map +1 -1
  176. package/dist/operations/doctor.js +187 -6
  177. package/dist/operations/doctor.js.map +1 -1
  178. package/dist/operations/runtime-config-paths.d.ts +4 -0
  179. package/dist/operations/runtime-config-paths.d.ts.map +1 -0
  180. package/dist/operations/runtime-config-paths.js +6 -0
  181. package/dist/operations/runtime-config-paths.js.map +1 -0
  182. package/dist/perf/gondolin-vfs-benchmark-support.d.ts +28 -0
  183. package/dist/perf/gondolin-vfs-benchmark-support.d.ts.map +1 -0
  184. package/dist/perf/gondolin-vfs-benchmark-support.js +50 -0
  185. package/dist/perf/gondolin-vfs-benchmark-support.js.map +1 -0
  186. package/dist/tool-vm/tool-vm-lifecycle.d.ts +7 -9
  187. package/dist/tool-vm/tool-vm-lifecycle.d.ts.map +1 -1
  188. package/dist/tool-vm/tool-vm-lifecycle.js +32 -27
  189. package/dist/tool-vm/tool-vm-lifecycle.js.map +1 -1
  190. package/package.json +7 -7
@@ -0,0 +1,178 @@
1
+ import { lstat, mkdir, open, realpath } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { parseAgentScopeKey } from './lease-scope.js';
4
+ function isNodeErrorCode(error, code) {
5
+ return error instanceof Error && 'code' in error && error.code === code;
6
+ }
7
+ function isPathWithin(candidatePath, rootPath) {
8
+ const relativePath = path.relative(rootPath, candidatePath);
9
+ return relativePath === '' || (!relativePath.startsWith('..') && !path.isAbsolute(relativePath));
10
+ }
11
+ function toSecretRef(seed) {
12
+ return seed.source.source === 'environment'
13
+ ? {
14
+ source: 'environment',
15
+ ref: seed.source.envVar,
16
+ }
17
+ : {
18
+ source: '1password',
19
+ ref: seed.source.ref,
20
+ };
21
+ }
22
+ async function writeSeedFileIfAbsent(options) {
23
+ let fileHandle;
24
+ try {
25
+ const targetParentPath = await ensureSeedParentDirectoryInsideWorkspace({
26
+ targetPath: options.targetPath,
27
+ workspaceDir: options.workspaceDir,
28
+ });
29
+ const safeTargetPath = path.join(targetParentPath, path.basename(options.targetPath));
30
+ fileHandle = await open(safeTargetPath, 'wx', options.mode);
31
+ await fileHandle.chmod(options.mode);
32
+ await fileHandle.writeFile(options.content, 'utf8');
33
+ await fileHandle.close();
34
+ fileHandle = undefined;
35
+ return 'written';
36
+ }
37
+ catch (error) {
38
+ if (fileHandle) {
39
+ await fileHandle.close();
40
+ }
41
+ if (isNodeErrorCode(error, 'EEXIST')) {
42
+ return 'already-existed';
43
+ }
44
+ throw error;
45
+ }
46
+ }
47
+ async function ensureSeedParentDirectoryInsideWorkspace(options) {
48
+ const targetParentPath = path.dirname(options.targetPath);
49
+ const relativeParentPath = path.relative(options.workspaceDir, targetParentPath);
50
+ if (relativeParentPath !== '' &&
51
+ (relativeParentPath.startsWith('..') || path.isAbsolute(relativeParentPath))) {
52
+ throw new Error(`Agent sandbox seed target parent '${targetParentPath}' resolves outside workspace '${options.workspaceDir}'.`);
53
+ }
54
+ let currentPath = options.workspaceDir;
55
+ /* oxlint-disable no-await-in-loop -- each path segment must be validated before the next segment can be trusted */
56
+ for (const segment of relativeParentPath.split(path.sep).filter((part) => part.length > 0)) {
57
+ currentPath = path.join(currentPath, segment);
58
+ let entry = await lstat(currentPath).catch((error) => {
59
+ if (isNodeErrorCode(error, 'ENOENT')) {
60
+ return null;
61
+ }
62
+ throw error;
63
+ });
64
+ if (!entry) {
65
+ await mkdir(currentPath, { mode: 0o700 });
66
+ entry = await lstat(currentPath);
67
+ }
68
+ if (entry.isSymbolicLink()) {
69
+ throw new Error(`Agent sandbox seed parent '${currentPath}' must not be a symlink.`);
70
+ }
71
+ if (!entry.isDirectory()) {
72
+ throw new Error(`Agent sandbox seed parent '${currentPath}' is not a directory.`);
73
+ }
74
+ const resolvedPath = await realpath(currentPath);
75
+ if (!isPathWithin(resolvedPath, options.workspaceDir)) {
76
+ throw new Error(`Agent sandbox seed parent '${currentPath}' resolves outside workspace '${options.workspaceDir}'.`);
77
+ }
78
+ currentPath = resolvedPath;
79
+ }
80
+ /* oxlint-enable no-await-in-loop */
81
+ return currentPath;
82
+ }
83
+ export async function seedAgentSandboxWorkspace(options) {
84
+ if (options.zone.gateway.type !== 'openclaw') {
85
+ return { kind: 'not-openclaw-zone', zoneId: options.zone.id };
86
+ }
87
+ const parsedScope = parseAgentScopeKey(options.scopeKey);
88
+ if (parsedScope.kind === 'non-agent-scope') {
89
+ return { kind: 'non-agent-scope', scopeKey: options.scopeKey, zoneId: options.zone.id };
90
+ }
91
+ if (parsedScope.kind === 'malformed-agent-scope') {
92
+ return {
93
+ kind: 'malformed-agent-scope',
94
+ reason: parsedScope.reason,
95
+ scopeKey: options.scopeKey,
96
+ zoneId: options.zone.id,
97
+ };
98
+ }
99
+ const agentId = parsedScope.agentId;
100
+ const seeds = options.zone.agentSandboxSeeds?.[agentId] ?? [];
101
+ if (seeds.length === 0) {
102
+ return {
103
+ agentId,
104
+ kind: 'no-seeds-configured',
105
+ scopeKey: options.scopeKey,
106
+ zoneId: options.zone.id,
107
+ };
108
+ }
109
+ const sandboxRootPath = path.join(options.zone.gateway.stateDir, 'sandboxes');
110
+ let sandboxRoot;
111
+ try {
112
+ sandboxRoot = await realpath(sandboxRootPath);
113
+ }
114
+ catch (error) {
115
+ if (isNodeErrorCode(error, 'ENOENT')) {
116
+ return {
117
+ agentId,
118
+ kind: 'sandbox-root-missing',
119
+ sandboxRoot: sandboxRootPath,
120
+ scopeKey: options.scopeKey,
121
+ zoneId: options.zone.id,
122
+ };
123
+ }
124
+ throw error;
125
+ }
126
+ let workspaceDir;
127
+ try {
128
+ workspaceDir = await realpath(options.workspaceDir);
129
+ }
130
+ catch (error) {
131
+ if (isNodeErrorCode(error, 'ENOENT')) {
132
+ return {
133
+ agentId,
134
+ kind: 'workspace-missing',
135
+ scopeKey: options.scopeKey,
136
+ workspaceDir: options.workspaceDir,
137
+ zoneId: options.zone.id,
138
+ };
139
+ }
140
+ throw error;
141
+ }
142
+ if (!isPathWithin(workspaceDir, sandboxRoot)) {
143
+ return {
144
+ agentId,
145
+ kind: 'workspace-outside-sandbox',
146
+ sandboxRoot,
147
+ scopeKey: options.scopeKey,
148
+ workspaceDir,
149
+ zoneId: options.zone.id,
150
+ };
151
+ }
152
+ const seedResults = await Promise.all(seeds.map(async (seed) => {
153
+ const targetPath = path.resolve(workspaceDir, seed.target);
154
+ if (!isPathWithin(targetPath, workspaceDir)) {
155
+ throw new Error(`Agent sandbox seed target '${seed.target}' resolves outside workspace '${workspaceDir}'.`);
156
+ }
157
+ await ensureSeedParentDirectoryInsideWorkspace({
158
+ targetPath,
159
+ workspaceDir,
160
+ });
161
+ const content = await options.secretResolver.resolve(toSecretRef(seed));
162
+ return await writeSeedFileIfAbsent({
163
+ content,
164
+ mode: seed.mode,
165
+ targetPath,
166
+ workspaceDir,
167
+ });
168
+ }));
169
+ return {
170
+ agentId,
171
+ alreadyExisted: seedResults.filter((result) => result === 'already-existed').length,
172
+ kind: 'seeded',
173
+ scopeKey: options.scopeKey,
174
+ written: seedResults.filter((result) => result === 'written').length,
175
+ zoneId: options.zone.id,
176
+ };
177
+ }
178
+ //# sourceMappingURL=agent-sandbox-seeding.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-sandbox-seeding.js","sourceRoot":"","sources":["../../../src/controller/leases/agent-sandbox-seeding.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAChE,OAAO,IAAI,MAAM,WAAW,CAAC;AAK7B,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AA0DtD,SAAS,eAAe,CAAC,KAAc,EAAE,IAAY;IACpD,OAAO,KAAK,YAAY,KAAK,IAAI,MAAM,IAAI,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC;AACzE,CAAC;AAED,SAAS,YAAY,CAAC,aAAqB,EAAE,QAAgB;IAC5D,MAAM,YAAY,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC,CAAC;IAC5D,OAAO,YAAY,KAAK,EAAE,IAAI,CAAC,CAAC,YAAY,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC,CAAC;AAClG,CAAC;AAED,SAAS,WAAW,CAAC,IAAsB;IAC1C,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,KAAK,aAAa;QAC1C,CAAC,CAAC;YACA,MAAM,EAAE,aAAa;YACrB,GAAG,EAAE,IAAI,CAAC,MAAM,CAAC,MAAM;SACvB;QACF,CAAC,CAAC;YACA,MAAM,EAAE,WAAW;YACnB,GAAG,EAAE,IAAI,CAAC,MAAM,CAAC,GAAG;SACpB,CAAC;AACL,CAAC;AAED,KAAK,UAAU,qBAAqB,CAAC,OAKpC;IACA,IAAI,UAAwD,CAAC;IAC7D,IAAI,CAAC;QACJ,MAAM,gBAAgB,GAAG,MAAM,wCAAwC,CAAC;YACvE,UAAU,EAAE,OAAO,CAAC,UAAU;YAC9B,YAAY,EAAE,OAAO,CAAC,YAAY;SAClC,CAAC,CAAC;QACH,MAAM,cAAc,GAAG,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC;QACtF,UAAU,GAAG,MAAM,IAAI,CAAC,cAAc,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;QAC5D,MAAM,UAAU,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACrC,MAAM,UAAU,CAAC,SAAS,CAAC,OAAO,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QACpD,MAAM,UAAU,CAAC,KAAK,EAAE,CAAC;QACzB,UAAU,GAAG,SAAS,CAAC;QACvB,OAAO,SAAS,CAAC;IAClB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,IAAI,UAAU,EAAE,CAAC;YAChB,MAAM,UAAU,CAAC,KAAK,EAAE,CAAC;QAC1B,CAAC;QACD,IAAI,eAAe,CAAC,KAAK,EAAE,QAAQ,CAAC,EAAE,CAAC;YACtC,OAAO,iBAAiB,CAAC;QAC1B,CAAC;QACD,MAAM,KAAK,CAAC;IACb,CAAC;AACF,CAAC;AAED,KAAK,UAAU,wCAAwC,CAAC,OAGvD;IACA,MAAM,gBAAgB,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IAC1D,MAAM,kBAAkB,GAAG,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,YAAY,EAAE,gBAAgB,CAAC,CAAC;IACjF,IACC,kBAAkB,KAAK,EAAE;QACzB,CAAC,kBAAkB,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,kBAAkB,CAAC,CAAC,EAC3E,CAAC;QACF,MAAM,IAAI,KAAK,CACd,qCAAqC,gBAAgB,iCAAiC,OAAO,CAAC,YAAY,IAAI,CAC9G,CAAC;IACH,CAAC;IAED,IAAI,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC;IACvC,mHAAmH;IACnH,KAAK,MAAM,OAAO,IAAI,kBAAkB,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,EAAE,CAAC;QAC5F,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;QAC9C,IAAI,KAAK,GAAG,MAAM,KAAK,CAAC,WAAW,CAAC,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;YAC7D,IAAI,eAAe,CAAC,KAAK,EAAE,QAAQ,CAAC,EAAE,CAAC;gBACtC,OAAO,IAAI,CAAC;YACb,CAAC;YACD,MAAM,KAAK,CAAC;QACb,CAAC,CAAC,CAAC;QACH,IAAI,CAAC,KAAK,EAAE,CAAC;YACZ,MAAM,KAAK,CAAC,WAAW,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;YAC1C,KAAK,GAAG,MAAM,KAAK,CAAC,WAAW,CAAC,CAAC;QAClC,CAAC;QACD,IAAI,KAAK,CAAC,cAAc,EAAE,EAAE,CAAC;YAC5B,MAAM,IAAI,KAAK,CAAC,8BAA8B,WAAW,0BAA0B,CAAC,CAAC;QACtF,CAAC;QACD,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;YAC1B,MAAM,IAAI,KAAK,CAAC,8BAA8B,WAAW,uBAAuB,CAAC,CAAC;QACnF,CAAC;QACD,MAAM,YAAY,GAAG,MAAM,QAAQ,CAAC,WAAW,CAAC,CAAC;QACjD,IAAI,CAAC,YAAY,CAAC,YAAY,EAAE,OAAO,CAAC,YAAY,CAAC,EAAE,CAAC;YACvD,MAAM,IAAI,KAAK,CACd,8BAA8B,WAAW,iCAAiC,OAAO,CAAC,YAAY,IAAI,CAClG,CAAC;QACH,CAAC;QACD,WAAW,GAAG,YAAY,CAAC;IAC5B,CAAC;IACD,oCAAoC;IAEpC,OAAO,WAAW,CAAC;AACpB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAAC,OAK/C;IACA,IAAI,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;QAC9C,OAAO,EAAE,IAAI,EAAE,mBAAmB,EAAE,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;IAC/D,CAAC;IACD,MAAM,WAAW,GAAG,kBAAkB,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACzD,IAAI,WAAW,CAAC,IAAI,KAAK,iBAAiB,EAAE,CAAC;QAC5C,OAAO,EAAE,IAAI,EAAE,iBAAiB,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;IACzF,CAAC;IACD,IAAI,WAAW,CAAC,IAAI,KAAK,uBAAuB,EAAE,CAAC;QAClD,OAAO;YACN,IAAI,EAAE,uBAAuB;YAC7B,MAAM,EAAE,WAAW,CAAC,MAAM;YAC1B,QAAQ,EAAE,OAAO,CAAC,QAAQ;YAC1B,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,EAAE;SACvB,CAAC;IACH,CAAC;IACD,MAAM,OAAO,GAAG,WAAW,CAAC,OAAO,CAAC;IACpC,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,iBAAiB,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;IAC9D,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxB,OAAO;YACN,OAAO;YACP,IAAI,EAAE,qBAAqB;YAC3B,QAAQ,EAAE,OAAO,CAAC,QAAQ;YAC1B,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,EAAE;SACvB,CAAC;IACH,CAAC;IACD,MAAM,eAAe,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;IAC9E,IAAI,WAAmB,CAAC;IACxB,IAAI,CAAC;QACJ,WAAW,GAAG,MAAM,QAAQ,CAAC,eAAe,CAAC,CAAC;IAC/C,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,IAAI,eAAe,CAAC,KAAK,EAAE,QAAQ,CAAC,EAAE,CAAC;YACtC,OAAO;gBACN,OAAO;gBACP,IAAI,EAAE,sBAAsB;gBAC5B,WAAW,EAAE,eAAe;gBAC5B,QAAQ,EAAE,OAAO,CAAC,QAAQ;gBAC1B,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,EAAE;aACvB,CAAC;QACH,CAAC;QACD,MAAM,KAAK,CAAC;IACb,CAAC;IACD,IAAI,YAAoB,CAAC;IACzB,IAAI,CAAC;QACJ,YAAY,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;IACrD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,IAAI,eAAe,CAAC,KAAK,EAAE,QAAQ,CAAC,EAAE,CAAC;YACtC,OAAO;gBACN,OAAO;gBACP,IAAI,EAAE,mBAAmB;gBACzB,QAAQ,EAAE,OAAO,CAAC,QAAQ;gBAC1B,YAAY,EAAE,OAAO,CAAC,YAAY;gBAClC,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,EAAE;aACvB,CAAC;QACH,CAAC;QACD,MAAM,KAAK,CAAC;IACb,CAAC;IACD,IAAI,CAAC,YAAY,CAAC,YAAY,EAAE,WAAW,CAAC,EAAE,CAAC;QAC9C,OAAO;YACN,OAAO;YACP,IAAI,EAAE,2BAA2B;YACjC,WAAW;YACX,QAAQ,EAAE,OAAO,CAAC,QAAQ;YAC1B,YAAY;YACZ,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,EAAE;SACvB,CAAC;IACH,CAAC;IAED,MAAM,WAAW,GAAG,MAAM,OAAO,CAAC,GAAG,CACpC,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;QACxB,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;QAC3D,IAAI,CAAC,YAAY,CAAC,UAAU,EAAE,YAAY,CAAC,EAAE,CAAC;YAC7C,MAAM,IAAI,KAAK,CACd,8BAA8B,IAAI,CAAC,MAAM,iCAAiC,YAAY,IAAI,CAC1F,CAAC;QACH,CAAC;QACD,MAAM,wCAAwC,CAAC;YAC9C,UAAU;YACV,YAAY;SACZ,CAAC,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,cAAc,CAAC,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC;QACxE,OAAO,MAAM,qBAAqB,CAAC;YAClC,OAAO;YACP,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,UAAU;YACV,YAAY;SACZ,CAAC,CAAC;IACJ,CAAC,CAAC,CACF,CAAC;IACF,OAAO;QACN,OAAO;QACP,cAAc,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,KAAK,iBAAiB,CAAC,CAAC,MAAM;QACnF,IAAI,EAAE,QAAQ;QACd,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,OAAO,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,MAAM;QACpE,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,EAAE;KACvB,CAAC;AACH,CAAC"}
@@ -2,10 +2,15 @@ export declare function createIdleReaper(options: {
2
2
  readonly getLeases: () => {
3
3
  readonly id: string;
4
4
  readonly lastUsedAt: number;
5
+ readonly scopeKey: string;
5
6
  }[];
6
7
  readonly now: () => number;
7
- readonly releaseLease: (leaseId: string) => Promise<void>;
8
- readonly ttlMs: number;
8
+ readonly releaseLease: (leaseId: string, options?: {
9
+ readonly ifLastUsedAtBeforeOrAt?: number;
10
+ }) => Promise<void>;
11
+ readonly ttlForLease: (lease: {
12
+ readonly scopeKey: string;
13
+ }) => number;
9
14
  }): {
10
15
  reapExpiredLeases(): Promise<void>;
11
16
  };
@@ -1 +1 @@
1
- {"version":3,"file":"idle-reaper.d.ts","sourceRoot":"","sources":["../../../src/controller/leases/idle-reaper.ts"],"names":[],"mappings":"AAAA,wBAAgB,gBAAgB,CAAC,OAAO,EAAE;IACzC,QAAQ,CAAC,SAAS,EAAE,MAAM;QACzB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;QACpB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;KAC5B,EAAE,CAAC;IACJ,QAAQ,CAAC,GAAG,EAAE,MAAM,MAAM,CAAC;IAC3B,QAAQ,CAAC,YAAY,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1D,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACvB,GAAG;IACH,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACnC,CAaA"}
1
+ {"version":3,"file":"idle-reaper.d.ts","sourceRoot":"","sources":["../../../src/controller/leases/idle-reaper.ts"],"names":[],"mappings":"AAAA,wBAAgB,gBAAgB,CAAC,OAAO,EAAE;IACzC,QAAQ,CAAC,SAAS,EAAE,MAAM;QACzB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;QACpB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;QAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;KAC1B,EAAE,CAAC;IACJ,QAAQ,CAAC,GAAG,EAAE,MAAM,MAAM,CAAC;IAC3B,QAAQ,CAAC,YAAY,EAAE,CACtB,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,sBAAsB,CAAC,EAAE,MAAM,CAAA;KAAE,KAClD,OAAO,CAAC,IAAI,CAAC,CAAC;IACnB,QAAQ,CAAC,WAAW,EAAE,CAAC,KAAK,EAAE;QAAE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,KAAK,MAAM,CAAC;CACvE,GAAG;IACH,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACnC,CA2BA"}
@@ -1,13 +1,25 @@
1
1
  export function createIdleReaper(options) {
2
2
  return {
3
3
  async reapExpiredLeases() {
4
- const expiredLeaseIds = options
5
- .getLeases()
6
- .filter((lease) => options.now() - lease.lastUsedAt > options.ttlMs)
7
- .map((lease) => lease.id);
8
- for (const leaseId of expiredLeaseIds) {
9
- // oxlint-disable-next-line eslint/no-await-in-loop -- release must stay sequential to avoid TCP pool races
10
- await options.releaseLease(leaseId);
4
+ const now = options.now();
5
+ const expiredLeases = options.getLeases().flatMap((lease) => {
6
+ const expirationCutoff = now - options.ttlForLease(lease);
7
+ return lease.lastUsedAt < expirationCutoff ? [{ expirationCutoff, leaseId: lease.id }] : [];
8
+ });
9
+ const releaseErrors = [];
10
+ for (const expiredLease of expiredLeases) {
11
+ try {
12
+ // oxlint-disable-next-line eslint/no-await-in-loop -- release must stay sequential to avoid TCP pool races
13
+ await options.releaseLease(expiredLease.leaseId, {
14
+ ifLastUsedAtBeforeOrAt: expiredLease.expirationCutoff,
15
+ });
16
+ }
17
+ catch (error) {
18
+ releaseErrors.push(error instanceof Error ? error : new Error(String(error)));
19
+ }
20
+ }
21
+ if (releaseErrors.length > 0) {
22
+ throw new AggregateError(releaseErrors, `Failed to release ${String(releaseErrors.length)} expired lease(s).`);
11
23
  }
12
24
  },
13
25
  };
@@ -1 +1 @@
1
- {"version":3,"file":"idle-reaper.js","sourceRoot":"","sources":["../../../src/controller/leases/idle-reaper.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,gBAAgB,CAAC,OAQhC;IAGA,OAAO;QACN,KAAK,CAAC,iBAAiB;YACtB,MAAM,eAAe,GAAG,OAAO;iBAC7B,SAAS,EAAE;iBACX,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,UAAU,GAAG,OAAO,CAAC,KAAK,CAAC;iBACnE,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YAC3B,KAAK,MAAM,OAAO,IAAI,eAAe,EAAE,CAAC;gBACvC,2GAA2G;gBAC3G,MAAM,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;YACrC,CAAC;QACF,CAAC;KACD,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"idle-reaper.js","sourceRoot":"","sources":["../../../src/controller/leases/idle-reaper.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,gBAAgB,CAAC,OAYhC;IAGA,OAAO;QACN,KAAK,CAAC,iBAAiB;YACtB,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;YAC1B,MAAM,aAAa,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE;gBAC3D,MAAM,gBAAgB,GAAG,GAAG,GAAG,OAAO,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;gBAC1D,OAAO,KAAK,CAAC,UAAU,GAAG,gBAAgB,CAAC,CAAC,CAAC,CAAC,EAAE,gBAAgB,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YAC7F,CAAC,CAAC,CAAC;YACH,MAAM,aAAa,GAAY,EAAE,CAAC;YAClC,KAAK,MAAM,YAAY,IAAI,aAAa,EAAE,CAAC;gBAC1C,IAAI,CAAC;oBACJ,2GAA2G;oBAC3G,MAAM,OAAO,CAAC,YAAY,CAAC,YAAY,CAAC,OAAO,EAAE;wBAChD,sBAAsB,EAAE,YAAY,CAAC,gBAAgB;qBACrD,CAAC,CAAC;gBACJ,CAAC;gBAAC,OAAO,KAAK,EAAE,CAAC;oBAChB,aAAa,CAAC,IAAI,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;gBAC/E,CAAC;YACF,CAAC;YACD,IAAI,aAAa,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC9B,MAAM,IAAI,cAAc,CACvB,aAAa,EACb,qBAAqB,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,oBAAoB,CACrE,CAAC;YACH,CAAC;QACF,CAAC;KACD,CAAC;AACH,CAAC"}
@@ -0,0 +1,12 @@
1
+ export declare const leaseScopeKinds: readonly ["agent", "discord", "project", "session", "shared", "workspace"];
2
+ export type LeaseScopeKind = (typeof leaseScopeKinds)[number];
3
+ export interface LeaseIdleTtlPolicy {
4
+ readonly defaultMs: number;
5
+ readonly byScopeKind: Partial<Readonly<Record<LeaseScopeKind, number>>>;
6
+ readonly byScopePrefix: Readonly<Record<string, number>>;
7
+ }
8
+ export declare function ttlForLeaseScope(options: {
9
+ readonly policy: LeaseIdleTtlPolicy;
10
+ readonly scopeKey: string;
11
+ }): number;
12
+ //# sourceMappingURL=lease-idle-policy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lease-idle-policy.d.ts","sourceRoot":"","sources":["../../../src/controller/leases/lease-idle-policy.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,eAAe,4EAOlB,CAAC;AAEX,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC;AAE9D,MAAM,WAAW,kBAAkB;IAClC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;IACxE,QAAQ,CAAC,aAAa,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CACzD;AAWD,wBAAgB,gBAAgB,CAAC,OAAO,EAAE;IACzC,QAAQ,CAAC,MAAM,EAAE,kBAAkB,CAAC;IACpC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC1B,GAAG,MAAM,CAWT"}
@@ -0,0 +1,28 @@
1
+ export const leaseScopeKinds = [
2
+ 'agent',
3
+ 'discord',
4
+ 'project',
5
+ 'session',
6
+ 'shared',
7
+ 'workspace',
8
+ ];
9
+ function scopePrefixes(scopeKey) {
10
+ const segments = scopeKey.split(':').filter((segment) => segment.length > 0);
11
+ return segments.map((_segment, index) => segments.slice(0, index + 1).join(':')).toReversed();
12
+ }
13
+ function isLeaseScopeKind(scopeKind) {
14
+ return leaseScopeKinds.some((candidate) => candidate === scopeKind);
15
+ }
16
+ export function ttlForLeaseScope(options) {
17
+ for (const prefix of scopePrefixes(options.scopeKey)) {
18
+ const ttl = options.policy.byScopePrefix[prefix];
19
+ if (ttl !== undefined) {
20
+ return ttl;
21
+ }
22
+ }
23
+ const scopeKind = options.scopeKey.split(':')[0] ?? '';
24
+ return isLeaseScopeKind(scopeKind)
25
+ ? (options.policy.byScopeKind[scopeKind] ?? options.policy.defaultMs)
26
+ : options.policy.defaultMs;
27
+ }
28
+ //# sourceMappingURL=lease-idle-policy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lease-idle-policy.js","sourceRoot":"","sources":["../../../src/controller/leases/lease-idle-policy.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,eAAe,GAAG;IAC9B,OAAO;IACP,SAAS;IACT,SAAS;IACT,SAAS;IACT,QAAQ;IACR,WAAW;CACF,CAAC;AAUX,SAAS,aAAa,CAAC,QAAgB;IACtC,MAAM,QAAQ,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAC7E,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,KAAK,EAAE,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC;AAC/F,CAAC;AAED,SAAS,gBAAgB,CAAC,SAAiB;IAC1C,OAAO,eAAe,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC;AACrE,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,OAGhC;IACA,KAAK,MAAM,MAAM,IAAI,aAAa,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;QACtD,MAAM,GAAG,GAAG,OAAO,CAAC,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;QACjD,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;YACvB,OAAO,GAAG,CAAC;QACZ,CAAC;IACF,CAAC;IACD,MAAM,SAAS,GAAG,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACvD,OAAO,gBAAgB,CAAC,SAAS,CAAC;QACjC,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC,SAAS,CAAC,IAAI,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC;QACrE,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC;AAC7B,CAAC"}
@@ -1,13 +1,12 @@
1
1
  import type { ManagedVm } from '@agent-vm/gondolin-adapter';
2
2
  import type { TcpPool } from './tcp-pool.js';
3
- export interface ToolProfile {
3
+ export interface ToolVmProfile {
4
4
  readonly cpus: number;
5
5
  readonly imageProfile: string;
6
6
  readonly memory: string;
7
- readonly workspaceRoot: string;
8
7
  }
9
8
  export interface Lease {
10
- readonly cleanWorkspace?: () => Promise<void>;
9
+ readonly agentWorkspaceDir: string;
11
10
  readonly createdAt: number;
12
11
  readonly id: string;
13
12
  readonly lastUsedAt: number;
@@ -22,30 +21,40 @@ export interface Lease {
22
21
  };
23
22
  readonly tcpSlot: number;
24
23
  readonly vm: ManagedVm;
24
+ readonly workspaceDir: string;
25
25
  readonly zoneId: string;
26
26
  }
27
+ export interface LeaseRenewal {
28
+ readonly kind: 'renewed';
29
+ readonly lastUsedAt: number;
30
+ readonly lease: Lease;
31
+ }
32
+ export interface LeaseSnapshot {
33
+ readonly kind: 'snapshot';
34
+ readonly lease: Lease;
35
+ }
27
36
  export interface LeaseManager {
28
37
  createLease(options: {
29
38
  readonly agentWorkspaceDir: string;
30
- readonly profile: ToolProfile;
39
+ readonly profile: ToolVmProfile;
31
40
  readonly profileId: string;
32
41
  readonly scopeKey: string;
33
42
  readonly workspaceDir: string;
34
43
  readonly zoneId: string;
35
44
  }): Promise<Lease>;
36
- getLease(leaseId: string): Lease | undefined;
45
+ keepLeaseAlive(leaseId: string): LeaseRenewal | undefined;
37
46
  listLeases(): Lease[];
38
- releaseLease(leaseId: string): Promise<void>;
47
+ peekLease(leaseId: string): LeaseSnapshot | undefined;
48
+ releaseLease(leaseId: string, options?: {
49
+ readonly ifLastUsedAtBeforeOrAt?: number;
50
+ }): Promise<void>;
51
+ }
52
+ export declare class LeaseScopeConflictError extends Error {
39
53
  }
40
54
  export declare function createLeaseManager(options: {
41
- readonly cleanWorkspace?: (leaseOptions: {
42
- readonly profile: ToolProfile;
43
- readonly tcpSlot: number;
44
- readonly zoneId: string;
45
- }) => Promise<void>;
46
55
  readonly createManagedVm: (leaseOptions: {
47
56
  readonly agentWorkspaceDir: string;
48
- readonly profile: ToolProfile;
57
+ readonly profile: ToolVmProfile;
49
58
  readonly profileId: string;
50
59
  readonly scopeKey: string;
51
60
  readonly tcpSlot: number;
@@ -1 +1 @@
1
- {"version":3,"file":"lease-manager.d.ts","sourceRoot":"","sources":["../../../src/controller/leases/lease-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAE5D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAE7C,MAAM,WAAW,WAAW;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,KAAK;IACrB,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE;QACnB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QACtB,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;QAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QACtB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;KACvB,CAAC;IACF,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,EAAE,EAAE,SAAS,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,YAAY;IAC5B,WAAW,CAAC,OAAO,EAAE;QACpB,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;QACnC,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC;QAC9B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;QAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;QAC1B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;QAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;KACxB,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;IACnB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,KAAK,GAAG,SAAS,CAAC;IAC7C,UAAU,IAAI,KAAK,EAAE,CAAC;IACtB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7C;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE;IAC3C,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC,YAAY,EAAE;QACxC,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC;QAC9B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;KACxB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpB,QAAQ,CAAC,eAAe,EAAE,CAAC,YAAY,EAAE;QACxC,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;QACnC,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC;QAC9B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;QAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;QAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QACzB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;QAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;KACxB,KAAK,OAAO,CAAC,SAAS,CAAC,CAAC;IACzB,QAAQ,CAAC,GAAG,EAAE,MAAM,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC1B,GAAG,YAAY,CAiFf"}
1
+ {"version":3,"file":"lease-manager.d.ts","sourceRoot":"","sources":["../../../src/controller/leases/lease-manager.ts"],"names":[],"mappings":"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"}
@@ -1,80 +1,178 @@
1
+ export class LeaseScopeConflictError extends Error {
2
+ }
3
+ function assertReusableScopeLease(existingLease, requestedLease) {
4
+ if (existingLease.profileId !== requestedLease.profileId) {
5
+ throw new LeaseScopeConflictError(`Tool VM lease scope conflict for zone '${requestedLease.zoneId}' scopeKey '${requestedLease.scopeKey}': existing profileId '${existingLease.profileId}' does not match requested profileId '${requestedLease.profileId}'.`);
6
+ }
7
+ if (existingLease.workspaceDir !== requestedLease.workspaceDir) {
8
+ throw new LeaseScopeConflictError(`Tool VM lease scope conflict for zone '${requestedLease.zoneId}' scopeKey '${requestedLease.scopeKey}': existing workspaceDir '${existingLease.workspaceDir}' does not match requested workspaceDir '${requestedLease.workspaceDir}'.`);
9
+ }
10
+ if (existingLease.agentWorkspaceDir !== requestedLease.agentWorkspaceDir) {
11
+ throw new LeaseScopeConflictError(`Tool VM lease scope conflict for zone '${requestedLease.zoneId}' scopeKey '${requestedLease.scopeKey}': existing agentWorkspaceDir '${existingLease.agentWorkspaceDir}' does not match requested agentWorkspaceDir '${requestedLease.agentWorkspaceDir}'.`);
12
+ }
13
+ }
14
+ async function isLeaseVmLive(lease) {
15
+ try {
16
+ const result = await lease.vm.exec('true');
17
+ return result.exitCode === 0;
18
+ }
19
+ catch {
20
+ return false;
21
+ }
22
+ }
23
+ function scopeIndexKey(scopeRequest) {
24
+ return `${scopeRequest.zoneId}\0${scopeRequest.scopeKey}`;
25
+ }
1
26
  export function createLeaseManager(options) {
2
27
  const leases = new Map();
28
+ const leaseIdsByScope = new Map();
29
+ const scopeLocks = new Map();
30
+ function storeLease(lease) {
31
+ leases.set(lease.id, lease);
32
+ leaseIdsByScope.set(scopeIndexKey(lease), lease.id);
33
+ }
34
+ function deleteLease(lease) {
35
+ leases.delete(lease.id);
36
+ const indexKey = scopeIndexKey(lease);
37
+ // Only clear the scope index if it still points at this exact lease.
38
+ if (leaseIdsByScope.get(indexKey) === lease.id) {
39
+ leaseIdsByScope.delete(indexKey);
40
+ }
41
+ }
42
+ function findLeaseForScope(scopeRequest) {
43
+ const leaseId = leaseIdsByScope.get(scopeIndexKey(scopeRequest));
44
+ return leaseId ? leases.get(leaseId) : undefined;
45
+ }
46
+ function touchLease(lease) {
47
+ const touchedLease = {
48
+ ...lease,
49
+ lastUsedAt: options.now(),
50
+ };
51
+ storeLease(touchedLease);
52
+ return touchedLease;
53
+ }
54
+ async function withScopeLock(scopeRequest, fn) {
55
+ const lockKey = `${scopeRequest.zoneId}\0${scopeRequest.scopeKey}`;
56
+ const previousLock = scopeLocks.get(lockKey) ?? Promise.resolve();
57
+ let releaseCurrentLock;
58
+ const currentLock = new Promise((resolve) => {
59
+ releaseCurrentLock = resolve;
60
+ });
61
+ scopeLocks.set(lockKey, currentLock);
62
+ await previousLock.catch(() => { });
63
+ try {
64
+ return await fn();
65
+ }
66
+ finally {
67
+ releaseCurrentLock?.();
68
+ if (scopeLocks.get(lockKey) === currentLock) {
69
+ scopeLocks.delete(lockKey);
70
+ }
71
+ }
72
+ }
73
+ async function evictLease(lease) {
74
+ deleteLease(lease);
75
+ options.tcpPool.release(lease.tcpSlot);
76
+ await lease.vm.close().catch(() => { });
77
+ }
3
78
  return {
4
79
  async createLease(leaseOptions) {
5
- const tcpSlot = options.tcpPool.allocate();
6
- try {
7
- const vm = await options.createManagedVm({
8
- ...leaseOptions,
9
- tcpSlot,
80
+ return await withScopeLock(leaseOptions, async () => {
81
+ const existingLease = findLeaseForScope({
82
+ scopeKey: leaseOptions.scopeKey,
83
+ zoneId: leaseOptions.zoneId,
10
84
  });
85
+ if (existingLease) {
86
+ assertReusableScopeLease(existingLease, leaseOptions);
87
+ if (await isLeaseVmLive(existingLease)) {
88
+ return touchLease(existingLease);
89
+ }
90
+ await evictLease(existingLease);
91
+ }
92
+ const tcpSlot = options.tcpPool.allocate();
11
93
  try {
12
- const sshAccess = await vm.enableSsh({
13
- listenPort: options.tcpPool.portForSlot(tcpSlot),
14
- });
15
- const createdAt = options.now();
16
- const lease = {
17
- ...(options.cleanWorkspace
18
- ? {
19
- cleanWorkspace: async () => await options.cleanWorkspace?.({
20
- profile: leaseOptions.profile,
21
- tcpSlot,
22
- zoneId: leaseOptions.zoneId,
23
- }),
24
- }
25
- : {}),
26
- createdAt,
27
- id: `${leaseOptions.zoneId}-${leaseOptions.scopeKey}-${createdAt}`,
28
- lastUsedAt: createdAt,
29
- profileId: leaseOptions.profileId,
30
- scopeKey: leaseOptions.scopeKey,
31
- sshAccess,
94
+ const vm = await options.createManagedVm({
95
+ ...leaseOptions,
32
96
  tcpSlot,
33
- vm,
34
- zoneId: leaseOptions.zoneId,
35
- };
36
- leases.set(lease.id, lease);
37
- return lease;
97
+ });
98
+ try {
99
+ const sshAccess = await vm.enableSsh({
100
+ listenPort: options.tcpPool.portForSlot(tcpSlot),
101
+ });
102
+ const createdAt = options.now();
103
+ const lease = {
104
+ agentWorkspaceDir: leaseOptions.agentWorkspaceDir,
105
+ createdAt,
106
+ id: `${leaseOptions.zoneId}-${leaseOptions.scopeKey}-${createdAt}`,
107
+ lastUsedAt: createdAt,
108
+ profileId: leaseOptions.profileId,
109
+ scopeKey: leaseOptions.scopeKey,
110
+ sshAccess,
111
+ tcpSlot,
112
+ vm,
113
+ workspaceDir: leaseOptions.workspaceDir,
114
+ zoneId: leaseOptions.zoneId,
115
+ };
116
+ storeLease(lease);
117
+ return lease;
118
+ }
119
+ catch (error) {
120
+ await vm.close().catch(() => { });
121
+ throw error;
122
+ }
38
123
  }
39
124
  catch (error) {
40
- await vm.close().catch(() => { });
125
+ options.tcpPool.release(tcpSlot);
41
126
  throw error;
42
127
  }
43
- }
44
- catch (error) {
45
- options.tcpPool.release(tcpSlot);
46
- throw error;
47
- }
128
+ });
48
129
  },
49
- getLease(leaseId) {
50
- return leases.get(leaseId);
130
+ keepLeaseAlive(leaseId) {
131
+ const lease = leases.get(leaseId);
132
+ if (!lease) {
133
+ return undefined;
134
+ }
135
+ const renewedLease = touchLease(lease);
136
+ return {
137
+ kind: 'renewed',
138
+ lastUsedAt: renewedLease.lastUsedAt,
139
+ lease: renewedLease,
140
+ };
51
141
  },
52
142
  listLeases() {
53
143
  return [...leases.values()];
54
144
  },
55
- async releaseLease(leaseId) {
145
+ peekLease(leaseId) {
146
+ const lease = leases.get(leaseId);
147
+ return lease ? { kind: 'snapshot', lease } : undefined;
148
+ },
149
+ async releaseLease(leaseId, releaseOptions) {
56
150
  const lease = leases.get(leaseId);
57
151
  if (!lease) {
58
152
  return;
59
153
  }
60
- let releaseError;
61
- try {
62
- await lease.vm.close();
63
- }
64
- catch (error) {
65
- releaseError = error instanceof Error ? error : new Error(String(error));
66
- }
67
- try {
68
- await lease.cleanWorkspace?.();
69
- }
70
- catch (error) {
71
- releaseError ??= error instanceof Error ? error : new Error(String(error));
72
- }
73
- leases.delete(leaseId);
74
- options.tcpPool.release(lease.tcpSlot);
75
- if (releaseError) {
76
- throw releaseError;
77
- }
154
+ await withScopeLock(lease, async () => {
155
+ const currentLease = leases.get(leaseId);
156
+ if (!currentLease) {
157
+ return;
158
+ }
159
+ if (releaseOptions?.ifLastUsedAtBeforeOrAt !== undefined &&
160
+ currentLease.lastUsedAt > releaseOptions.ifLastUsedAtBeforeOrAt) {
161
+ return;
162
+ }
163
+ let releaseError;
164
+ try {
165
+ await currentLease.vm.close();
166
+ }
167
+ catch (error) {
168
+ releaseError = error instanceof Error ? error : new Error(String(error));
169
+ }
170
+ deleteLease(currentLease);
171
+ options.tcpPool.release(currentLease.tcpSlot);
172
+ if (releaseError) {
173
+ throw releaseError;
174
+ }
175
+ });
78
176
  },
79
177
  };
80
178
  }