@owlmeans/viable-sdk 0.1.18-rc.1

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 (166) hide show
  1. package/README.md +128 -0
  2. package/build/api/index.d.ts +2 -0
  3. package/build/api/index.d.ts.map +1 -0
  4. package/build/api/index.js +2 -0
  5. package/build/api/index.js.map +1 -0
  6. package/build/api/remote.d.ts +14 -0
  7. package/build/api/remote.d.ts.map +1 -0
  8. package/build/api/remote.js +123 -0
  9. package/build/api/remote.js.map +1 -0
  10. package/build/consts.d.ts +47 -0
  11. package/build/consts.d.ts.map +1 -0
  12. package/build/consts.js +47 -0
  13. package/build/consts.js.map +1 -0
  14. package/build/context/index.d.ts +22 -0
  15. package/build/context/index.d.ts.map +1 -0
  16. package/build/context/index.js +92 -0
  17. package/build/context/index.js.map +1 -0
  18. package/build/errors.d.ts +21 -0
  19. package/build/errors.d.ts.map +1 -0
  20. package/build/errors.js +36 -0
  21. package/build/errors.js.map +1 -0
  22. package/build/executor/boot-check.d.ts +73 -0
  23. package/build/executor/boot-check.d.ts.map +1 -0
  24. package/build/executor/boot-check.js +155 -0
  25. package/build/executor/boot-check.js.map +1 -0
  26. package/build/executor/env.d.ts +25 -0
  27. package/build/executor/env.d.ts.map +1 -0
  28. package/build/executor/env.js +77 -0
  29. package/build/executor/env.js.map +1 -0
  30. package/build/executor/errors.d.ts +69 -0
  31. package/build/executor/errors.d.ts.map +1 -0
  32. package/build/executor/errors.js +80 -0
  33. package/build/executor/errors.js.map +1 -0
  34. package/build/executor/files.d.ts +124 -0
  35. package/build/executor/files.d.ts.map +1 -0
  36. package/build/executor/files.js +529 -0
  37. package/build/executor/files.js.map +1 -0
  38. package/build/executor/git.d.ts +52 -0
  39. package/build/executor/git.d.ts.map +1 -0
  40. package/build/executor/git.js +353 -0
  41. package/build/executor/git.js.map +1 -0
  42. package/build/executor/health.d.ts +92 -0
  43. package/build/executor/health.d.ts.map +1 -0
  44. package/build/executor/health.js +98 -0
  45. package/build/executor/health.js.map +1 -0
  46. package/build/executor/index.d.ts +39 -0
  47. package/build/executor/index.d.ts.map +1 -0
  48. package/build/executor/index.js +173 -0
  49. package/build/executor/index.js.map +1 -0
  50. package/build/executor/integrity.d.ts +17 -0
  51. package/build/executor/integrity.d.ts.map +1 -0
  52. package/build/executor/integrity.js +59 -0
  53. package/build/executor/integrity.js.map +1 -0
  54. package/build/executor/layout.d.ts +61 -0
  55. package/build/executor/layout.d.ts.map +1 -0
  56. package/build/executor/layout.js +98 -0
  57. package/build/executor/layout.js.map +1 -0
  58. package/build/executor/shell.d.ts +89 -0
  59. package/build/executor/shell.d.ts.map +1 -0
  60. package/build/executor/shell.js +280 -0
  61. package/build/executor/shell.js.map +1 -0
  62. package/build/executor/spawn.d.ts +111 -0
  63. package/build/executor/spawn.d.ts.map +1 -0
  64. package/build/executor/spawn.js +314 -0
  65. package/build/executor/spawn.js.map +1 -0
  66. package/build/harness/index.d.ts +28 -0
  67. package/build/harness/index.d.ts.map +1 -0
  68. package/build/harness/index.js +83 -0
  69. package/build/harness/index.js.map +1 -0
  70. package/build/harness/templates.d.ts +30 -0
  71. package/build/harness/templates.d.ts.map +1 -0
  72. package/build/harness/templates.js +162 -0
  73. package/build/harness/templates.js.map +1 -0
  74. package/build/index.d.ts +13 -0
  75. package/build/index.d.ts.map +1 -0
  76. package/build/index.js +12 -0
  77. package/build/index.js.map +1 -0
  78. package/build/project/env.d.ts +108 -0
  79. package/build/project/env.d.ts.map +1 -0
  80. package/build/project/env.js +233 -0
  81. package/build/project/env.js.map +1 -0
  82. package/build/project/marker.d.ts +34 -0
  83. package/build/project/marker.d.ts.map +1 -0
  84. package/build/project/marker.js +54 -0
  85. package/build/project/marker.js.map +1 -0
  86. package/build/project/setup.d.ts +62 -0
  87. package/build/project/setup.d.ts.map +1 -0
  88. package/build/project/setup.js +190 -0
  89. package/build/project/setup.js.map +1 -0
  90. package/build/run/index.d.ts +75 -0
  91. package/build/run/index.d.ts.map +1 -0
  92. package/build/run/index.js +150 -0
  93. package/build/run/index.js.map +1 -0
  94. package/build/run/serve.d.ts +18 -0
  95. package/build/run/serve.d.ts.map +1 -0
  96. package/build/run/serve.js +148 -0
  97. package/build/run/serve.js.map +1 -0
  98. package/build/run/state.d.ts +75 -0
  99. package/build/run/state.d.ts.map +1 -0
  100. package/build/run/state.js +136 -0
  101. package/build/run/state.js.map +1 -0
  102. package/build/session/index.d.ts +17 -0
  103. package/build/session/index.d.ts.map +1 -0
  104. package/build/session/index.js +226 -0
  105. package/build/session/index.js.map +1 -0
  106. package/build/session/questions.d.ts +16 -0
  107. package/build/session/questions.d.ts.map +1 -0
  108. package/build/session/questions.js +17 -0
  109. package/build/session/questions.js.map +1 -0
  110. package/build/session/queue.d.ts +54 -0
  111. package/build/session/queue.d.ts.map +1 -0
  112. package/build/session/queue.js +102 -0
  113. package/build/session/queue.js.map +1 -0
  114. package/build/session/tasks.d.ts +15 -0
  115. package/build/session/tasks.d.ts.map +1 -0
  116. package/build/session/tasks.js +16 -0
  117. package/build/session/tasks.js.map +1 -0
  118. package/build/task/driver.d.ts +30 -0
  119. package/build/task/driver.d.ts.map +1 -0
  120. package/build/task/driver.js +120 -0
  121. package/build/task/driver.js.map +1 -0
  122. package/build/task/envelope.d.ts +35 -0
  123. package/build/task/envelope.d.ts.map +1 -0
  124. package/build/task/envelope.js +230 -0
  125. package/build/task/envelope.js.map +1 -0
  126. package/build/task/index.d.ts +4 -0
  127. package/build/task/index.d.ts.map +1 -0
  128. package/build/task/index.js +4 -0
  129. package/build/task/index.js.map +1 -0
  130. package/build/task/inquiry.d.ts +53 -0
  131. package/build/task/inquiry.d.ts.map +1 -0
  132. package/build/task/inquiry.js +232 -0
  133. package/build/task/inquiry.js.map +1 -0
  134. package/build/tools/catalogue.d.ts +17 -0
  135. package/build/tools/catalogue.d.ts.map +1 -0
  136. package/build/tools/catalogue.js +1169 -0
  137. package/build/tools/catalogue.js.map +1 -0
  138. package/build/tools/index.d.ts +7 -0
  139. package/build/tools/index.d.ts.map +1 -0
  140. package/build/tools/index.js +7 -0
  141. package/build/tools/index.js.map +1 -0
  142. package/build/tools/jobs.d.ts +12 -0
  143. package/build/tools/jobs.d.ts.map +1 -0
  144. package/build/tools/jobs.js +60 -0
  145. package/build/tools/jobs.js.map +1 -0
  146. package/build/tools/mcp.d.ts +43 -0
  147. package/build/tools/mcp.d.ts.map +1 -0
  148. package/build/tools/mcp.js +131 -0
  149. package/build/tools/mcp.js.map +1 -0
  150. package/build/tools/platform.d.ts +77 -0
  151. package/build/tools/platform.d.ts.map +1 -0
  152. package/build/tools/platform.js +316 -0
  153. package/build/tools/platform.js.map +1 -0
  154. package/build/tools/refusal.d.ts +66 -0
  155. package/build/tools/refusal.d.ts.map +1 -0
  156. package/build/tools/refusal.js +377 -0
  157. package/build/tools/refusal.js.map +1 -0
  158. package/build/tools/types.d.ts +83 -0
  159. package/build/tools/types.d.ts.map +1 -0
  160. package/build/tools/types.js +38 -0
  161. package/build/tools/types.js.map +1 -0
  162. package/build/types.d.ts +174 -0
  163. package/build/types.d.ts.map +1 -0
  164. package/build/types.js +2 -0
  165. package/build/types.js.map +1 -0
  166. package/package.json +105 -0
@@ -0,0 +1,75 @@
1
+ import { TARGET_API_PORT, TARGET_WEB_PORT, TARGET_WORKER_PORT } from '@owlmeans/viable-common';
2
+ /**
3
+ * What a local run is, written down.
4
+ *
5
+ * A separate module from the run itself, and the separation is load-bearing: `DbSync` has to
6
+ * bounce the api child, `DbSync` lives in the shell commands, and the shell commands are reached
7
+ * through the executor the run is built on. Everything that touches a child process therefore
8
+ * lives here, below both of them, instead of making the three import each other in a circle.
9
+ *
10
+ * The record is a file rather than memory because the processes outlive the call that started
11
+ * them and, on a restarted connector, outlive the process that started them too.
12
+ */
13
+ export interface LocalProcessRecord {
14
+ pid: number;
15
+ port: number;
16
+ /**
17
+ * The id this instance answers `/api/healtz` with.
18
+ *
19
+ * Port ownership is a restart's only proof: the child is a shell wrapping `bun`, so a signal
20
+ * the shell dies from and `bun` survives leaves an orphan holding the port while the exit
21
+ * handler says "gone". A replacement then loses the bind and exits — and since a failed
22
+ * `listen` announces nothing on its own, everything keeps reporting healthy while the older
23
+ * build is what is being served.
24
+ */
25
+ bootId?: string;
26
+ }
27
+ export interface LocalRunRecord {
28
+ dir: string;
29
+ startedAt: string;
30
+ api?: LocalProcessRecord;
31
+ worker?: LocalProcessRecord;
32
+ /** The process serving the built browser app — this connector itself, not a child. */
33
+ web?: LocalProcessRecord;
34
+ }
35
+ export declare const readRun: (dir: string) => Promise<LocalRunRecord | null>;
36
+ export declare const writeRun: (dir: string, record: LocalRunRecord) => Promise<void>;
37
+ export declare const clearRun: (dir: string) => Promise<void>;
38
+ /** Whether the api recorded here is still a live process. */
39
+ export declare const apiRunning: (record: LocalRunRecord | null) => boolean;
40
+ /**
41
+ * Start the target's HTTP server as a detached child.
42
+ *
43
+ * Detached so the whole group can be signalled later — the child is a shell wrapping `bun`, and a
44
+ * signal aimed at the shell alone leaves `bun` holding the port.
45
+ */
46
+ export declare const startApi: (dir: string, env?: Record<string, string>) => Promise<LocalProcessRecord>;
47
+ /**
48
+ * Start the target's queue worker, when this tree has one.
49
+ *
50
+ * The SAME `dist/index.js` as the api, out of the same package tree — the argv marker is the only
51
+ * thing separating the two processes, which is why nothing may reclaim a port without excluding
52
+ * the other's marker. Most generated applications declare no queues, and their absence is not a
53
+ * degraded state.
54
+ */
55
+ export declare const startWorker: (dir: string) => Promise<LocalProcessRecord | null>;
56
+ /**
57
+ * Stop a recorded process and wait for its port to actually free.
58
+ *
59
+ * The port and not the pid is what a replacement needs, so that is what is waited on.
60
+ */
61
+ export declare const stopProcess: (record: LocalProcessRecord | undefined) => Promise<void>;
62
+ /**
63
+ * Bounce the api child so it reconciles the target's tables against its code.
64
+ *
65
+ * What `DbSync` means for a target that owns its own structure: every `@owlmeans/postgres`
66
+ * resource creates and updates its table while the context initializes, so there is nothing to
67
+ * generate and no migration file to apply — the sync IS the restart. A tree with no run in front
68
+ * of it answers `null`: there is no process to bounce, and reporting that as a failure would send
69
+ * a caller after a repair that does not apply.
70
+ */
71
+ export declare const restartApi: (dir: string) => Promise<string | null>;
72
+ /** Whether anything is answering on the api's port right now. */
73
+ export declare const apiListening: (port?: number) => Promise<boolean>;
74
+ export { TARGET_API_PORT, TARGET_WEB_PORT, TARGET_WORKER_PORT };
75
+ //# sourceMappingURL=state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../src/run/state.ts"],"names":[],"mappings":"AAKA,OAAO,EACoD,eAAe,EAAE,eAAe,EAEzF,kBAAkB,EACnB,MAAM,yBAAyB,CAAA;AAOhC;;;;;;;;;;GAUG;AAEH,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;IACZ;;;;;;;;OAQG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAA;IACX,SAAS,EAAE,MAAM,CAAA;IACjB,GAAG,CAAC,EAAE,kBAAkB,CAAA;IACxB,MAAM,CAAC,EAAE,kBAAkB,CAAA;IAC3B,sFAAsF;IACtF,GAAG,CAAC,EAAE,kBAAkB,CAAA;CACzB;AAID,eAAO,MAAM,OAAO,QAAe,MAAM,KAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAWxE,CAAA;AAED,eAAO,MAAM,QAAQ,QAAe,MAAM,UAAU,cAAc,KAAG,OAAO,CAAC,IAAI,CAGhF,CAAA;AAED,eAAO,MAAM,QAAQ,QAAe,MAAM,KAAG,OAAO,CAAC,IAAI,CAExD,CAAA;AAED,6DAA6D;AAC7D,eAAO,MAAM,UAAU,WAAY,cAAc,GAAG,IAAI,KAAG,OACf,CAAA;AAE5C;;;;;GAKG;AACH,eAAO,MAAM,QAAQ,QACd,MAAM,QAAO,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KACvC,OAAO,CAAC,kBAAkB,CA0B5B,CAAA;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,WAAW,QAAe,MAAM,KAAG,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAyBhF,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,WAAW,WAAkB,kBAAkB,GAAG,SAAS,KAAG,OAAO,CAAC,IAAI,CAUtF,CAAA;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,UAAU,QAAe,MAAM,KAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAYnE,CAAA;AAED,iEAAiE;AACjE,eAAO,MAAM,YAAY,UACjB,MAAM,KACX,OAAO,CAAC,OAAO,CAA+B,CAAA;AAEjD,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,kBAAkB,EAAE,CAAA"}
@@ -0,0 +1,136 @@
1
+ import fs from 'fs-extra';
2
+ import { spawn } from 'node:child_process';
3
+ import { randomUUID } from 'node:crypto';
4
+ import p from 'node:path';
5
+ import { CONNECT_MARKER_DIR, CONNECT_RUN_FILE, TARGET_API_MARKER, TARGET_API_PORT, TARGET_WEB_PORT, TARGET_WORKER_MARKER, TARGET_WORKER_PORT } from '@owlmeans/viable-common';
6
+ import { backendEnv } from '../executor/env.js';
7
+ import { apiPath, hasWorker, workerPath } from '../executor/layout.js';
8
+ import { integrityRefusal } from '../executor/integrity.js';
9
+ import { isAlive, probeListening, reclaimPort, signalGroup, waitForPortFree } from '../executor/spawn.js';
10
+ const runFile = (dir) => p.join(dir, CONNECT_RUN_FILE);
11
+ export const readRun = async (dir) => {
12
+ const content = await fs.readFile(runFile(dir), 'utf-8').catch(() => null);
13
+ if (content == null)
14
+ return null;
15
+ try {
16
+ const parsed = JSON.parse(content);
17
+ return typeof parsed === 'object' && parsed !== null ? parsed : null;
18
+ }
19
+ catch {
20
+ return null;
21
+ }
22
+ };
23
+ export const writeRun = async (dir, record) => {
24
+ await fs.ensureDir(p.join(dir, CONNECT_MARKER_DIR));
25
+ await fs.writeFile(runFile(dir), `${JSON.stringify(record, null, 2)}\n`);
26
+ };
27
+ export const clearRun = async (dir) => {
28
+ await fs.rm(runFile(dir), { force: true }).catch(() => undefined);
29
+ };
30
+ /** Whether the api recorded here is still a live process. */
31
+ export const apiRunning = (record) => record != null && isAlive(record.api?.pid);
32
+ /**
33
+ * Start the target's HTTP server as a detached child.
34
+ *
35
+ * Detached so the whole group can be signalled later — the child is a shell wrapping `bun`, and a
36
+ * signal aimed at the shell alone leaves `bun` holding the port.
37
+ */
38
+ export const startApi = async (dir, env = {}) => {
39
+ const bootId = randomUUID();
40
+ const cwd = apiPath(dir);
41
+ // A run killed without a stop leaves this child alive — detaching it is what keeps it
42
+ // running after the tool that started it exits. What survives holds the port, and the
43
+ // replacement then loses the bind and exits without announcing it.
44
+ if (!await waitForPortFree(TARGET_API_PORT, 2_000)) {
45
+ await reclaimPort(TARGET_API_PORT, TARGET_API_MARKER);
46
+ }
47
+ const child = spawn('bun', ['dist/index.js', TARGET_API_MARKER], {
48
+ cwd,
49
+ detached: true,
50
+ shell: true,
51
+ stdio: 'ignore',
52
+ env: {
53
+ ...process.env,
54
+ ...await backendEnv(dir),
55
+ BACKEND_PORT: `${TARGET_API_PORT}`,
56
+ BACKEND_BOOT_ID: bootId,
57
+ ...env,
58
+ },
59
+ });
60
+ child.unref();
61
+ return { pid: child.pid ?? -1, port: TARGET_API_PORT, bootId };
62
+ };
63
+ /**
64
+ * Start the target's queue worker, when this tree has one.
65
+ *
66
+ * The SAME `dist/index.js` as the api, out of the same package tree — the argv marker is the only
67
+ * thing separating the two processes, which is why nothing may reclaim a port without excluding
68
+ * the other's marker. Most generated applications declare no queues, and their absence is not a
69
+ * degraded state.
70
+ */
71
+ export const startWorker = async (dir) => {
72
+ if (!hasWorker(dir))
73
+ return null;
74
+ const cwd = workerPath(dir);
75
+ if (cwd == null)
76
+ return null;
77
+ const bootId = randomUUID();
78
+ const child = spawn('bun', ['dist/index.js', TARGET_WORKER_MARKER], {
79
+ cwd,
80
+ detached: true,
81
+ shell: true,
82
+ stdio: 'ignore',
83
+ env: {
84
+ ...process.env,
85
+ ...await backendEnv(dir),
86
+ // The target reads one "port I bind" variable and the worker binds its health port with it;
87
+ // `WORKER_PORT` travels beside it so a worker that grew its own variable finds it.
88
+ BACKEND_PORT: `${TARGET_WORKER_PORT}`,
89
+ WORKER_PORT: `${TARGET_WORKER_PORT}`,
90
+ BACKEND_BOOT_ID: bootId,
91
+ },
92
+ });
93
+ child.unref();
94
+ return { pid: child.pid ?? -1, port: TARGET_WORKER_PORT, bootId };
95
+ };
96
+ /**
97
+ * Stop a recorded process and wait for its port to actually free.
98
+ *
99
+ * The port and not the pid is what a replacement needs, so that is what is waited on.
100
+ */
101
+ export const stopProcess = async (record) => {
102
+ if (record == null)
103
+ return;
104
+ if (isAlive(record.pid)) {
105
+ signalGroup(record.pid, 'SIGTERM');
106
+ if (!await waitForPortFree(record.port, 5_000)) {
107
+ signalGroup(record.pid, 'SIGKILL');
108
+ }
109
+ }
110
+ await waitForPortFree(record.port, 5_000);
111
+ };
112
+ /**
113
+ * Bounce the api child so it reconciles the target's tables against its code.
114
+ *
115
+ * What `DbSync` means for a target that owns its own structure: every `@owlmeans/postgres`
116
+ * resource creates and updates its table while the context initializes, so there is nothing to
117
+ * generate and no migration file to apply — the sync IS the restart. A tree with no run in front
118
+ * of it answers `null`: there is no process to bounce, and reporting that as a failure would send
119
+ * a caller after a repair that does not apply.
120
+ */
121
+ export const restartApi = async (dir) => {
122
+ const record = await readRun(dir);
123
+ if (record == null || record.api == null)
124
+ return null;
125
+ const refused = await integrityRefusal(dir, 'Refused to restart');
126
+ if (refused != null)
127
+ return refused;
128
+ await stopProcess(record.api);
129
+ const api = await startApi(dir);
130
+ await writeRun(dir, { ...record, api });
131
+ return null;
132
+ };
133
+ /** Whether anything is answering on the api's port right now. */
134
+ export const apiListening = async (port = TARGET_API_PORT) => await probeListening(port);
135
+ export { TARGET_API_PORT, TARGET_WEB_PORT, TARGET_WORKER_PORT };
136
+ //# sourceMappingURL=state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state.js","sourceRoot":"","sources":["../../src/run/state.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import { ConnectSessionStatus } from '@owlmeans/viable-common';
2
+ import type { ConnectorApi, LocalExecutor, OpenSessionArgs, SessionRuntime } from '../types.js';
3
+ export interface SessionOptions {
4
+ api: ConnectorApi;
5
+ open: OpenSessionArgs;
6
+ /** Absent for a cloud target: the platform's own pod executes its commands. */
7
+ executor?: LocalExecutor;
8
+ /** Which deployment this is, recorded in the project's marker so a later connector finds it. */
9
+ apiUrl?: string;
10
+ log?: (line: string) => void;
11
+ }
12
+ export declare const openSession: (opts: SessionOptions) => Promise<SessionRuntime>;
13
+ export { ConnectSessionStatus };
14
+ export * from './queue.js';
15
+ export * from './tasks.js';
16
+ export * from './questions.js';
17
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/session/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAC8B,oBAAoB,EACxD,MAAM,yBAAyB,CAAA;AAMhC,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,eAAe,EAAE,cAAc,EAAgB,MAAM,aAAa,CAAA;AAM7G,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,YAAY,CAAA;IACjB,IAAI,EAAE,eAAe,CAAA;IACrB,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,aAAa,CAAA;IACxB,gGAAgG;IAChG,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;CAC7B;AAgDD,eAAO,MAAM,WAAW,SAAgB,cAAc,KAAG,OAAO,CAAC,cAAc,CA6L9E,CAAA;AAED,OAAO,EAAE,oBAAoB,EAAE,CAAA;AAC/B,cAAc,YAAY,CAAA;AAC1B,cAAc,YAAY,CAAA;AAC1B,cAAc,gBAAgB,CAAA"}
@@ -0,0 +1,226 @@
1
+ import { ConnectOpErrorKind, ConnectOpKind, ConnectSessionStatus } from '@owlmeans/viable-common';
2
+ import { PULL_WAIT_MS } from '../consts.js';
3
+ import { writeEnv } from '../project/env.js';
4
+ import { readMarker, writeMarker } from '../project/marker.js';
5
+ import { QuestionQueue } from './questions.js';
6
+ import { TaskQueue } from './tasks.js';
7
+ /**
8
+ * One attached connector, running.
9
+ *
10
+ * The loop is deliberately simple and deliberately serial: pull operations, answer them one at a
11
+ * time, put model tasks aside for the parent agent to drain. Serial because the thing on the other
12
+ * end of a local operation is a filesystem the platform believes it is the only writer of — two
13
+ * concurrent template writes into the same tree is not a throughput problem, it is a corrupted
14
+ * tree.
15
+ *
16
+ * Results are remembered by operation id. A connector that reconnects is handed everything still
17
+ * outstanding, including whatever it had already answered when the connection dropped, and
18
+ * re-executing a build or a model task because an acknowledgement was lost is exactly the cost
19
+ * this avoids.
20
+ */
21
+ /**
22
+ * Record which project a directory belongs to.
23
+ *
24
+ * Attaching a connector to a project IS the moment the directory becomes that project's, so it is
25
+ * the moment to say so on disk. Without it `attach_project` cannot find a project by its directory
26
+ * and a connector started here tomorrow has to be told again which project this is — the marker is
27
+ * read at startup for exactly that. It holds no secret and is meant to be committed.
28
+ *
29
+ * Best-effort: a session that works is worth more than a note about it, and the platform is the
30
+ * authority on what this project is either way.
31
+ */
32
+ const recordMarker = async (opts, projectId) => {
33
+ const dir = opts.open.projectDir;
34
+ if (dir == null)
35
+ return;
36
+ try {
37
+ const existing = await readMarker(dir);
38
+ if (existing?.projectId === projectId)
39
+ return;
40
+ const status = await opts.api.project.status(projectId);
41
+ await writeMarker(dir, {
42
+ version: 1,
43
+ apiUrl: opts.apiUrl ?? '',
44
+ projectId,
45
+ slug: status.slot?.slug ?? status.project.alias,
46
+ createdAt: new Date().toISOString(),
47
+ });
48
+ }
49
+ catch (e) {
50
+ (opts.log ?? (() => undefined))(`could not record the project marker: ${e.message}`);
51
+ }
52
+ };
53
+ export const openSession = async (opts) => {
54
+ const log = opts.log ?? (() => undefined);
55
+ const session = await opts.api.openSession(opts.open);
56
+ if (session.projectId != null)
57
+ await recordMarker(opts, session.projectId);
58
+ const tasks = new TaskQueue();
59
+ const questions = new QuestionQueue();
60
+ const answered = new Map();
61
+ const stats = {
62
+ opsDone: 0,
63
+ opsFailed: 0,
64
+ tasksDelivered: 0,
65
+ tasksSubmitted: 0,
66
+ questionsDelivered: 0,
67
+ questionsAnswered: 0,
68
+ lastActivityAt: Date.now(),
69
+ transport: 'pull',
70
+ };
71
+ let closed = false;
72
+ const submit = async (result) => {
73
+ answered.set(result.opId, result);
74
+ stats.lastActivityAt = Date.now();
75
+ try {
76
+ await opts.api.submitOp(session.id, result);
77
+ }
78
+ catch (e) {
79
+ // A lost acknowledgement is not a lost answer: the operation stays in the platform's store
80
+ // and is redelivered, and the cached result answers it without doing the work again.
81
+ log(`submit failed for ${result.opId}: ${e.message}`);
82
+ }
83
+ };
84
+ const perform = async (op) => {
85
+ const cached = answered.get(op.id);
86
+ if (cached != null) {
87
+ await submit(cached);
88
+ return;
89
+ }
90
+ if (op.kind === ConnectOpKind.ModelTask) {
91
+ // Not executed here at all — it is the parent agent's to run, in its own clean subagent.
92
+ // What this loop owes it is delivery and nothing else.
93
+ if (tasks.push(op.payload, op.id)) {
94
+ stats.tasksDelivered += 1;
95
+ stats.lastActivityAt = Date.now();
96
+ }
97
+ return;
98
+ }
99
+ if (op.kind === ConnectOpKind.Inquiry) {
100
+ // Executed by nobody: it is a decision about the user's own project, and a connector that
101
+ // answered one itself would build a whole application on a guess nobody made. Queued for
102
+ // the parent agent to put to a person, exactly as a model task is queued for its subagent.
103
+ if (questions.push(op.payload, op.id)) {
104
+ stats.questionsDelivered += 1;
105
+ stats.lastActivityAt = Date.now();
106
+ }
107
+ return;
108
+ }
109
+ try {
110
+ const value = op.kind === ConnectOpKind.Configure
111
+ ? await writeEnv(requireExecutor().dir, op.payload)
112
+ : await requireExecutor().execute(op.payload);
113
+ stats.opsDone += 1;
114
+ await submit({ opId: op.id, sessionId: session.id, ok: true, value });
115
+ }
116
+ catch (e) {
117
+ stats.opsFailed += 1;
118
+ const error = e;
119
+ log(`operation ${op.id} failed: ${error.message}`);
120
+ await submit({
121
+ opId: op.id,
122
+ sessionId: session.id,
123
+ ok: false,
124
+ error: {
125
+ type: error.name,
126
+ message: error.message,
127
+ // A local failure is the connector's own — it ran and could not do the thing. Saying
128
+ // `Unavailable` here would tell the platform to give up on a run that is fine.
129
+ kind: ConnectOpErrorKind.Refused,
130
+ },
131
+ });
132
+ }
133
+ };
134
+ const requireExecutor = () => {
135
+ if (opts.executor == null) {
136
+ throw new Error('this session executes nothing locally — its target is a platform slot');
137
+ }
138
+ return opts.executor;
139
+ };
140
+ /**
141
+ * The pull loop.
142
+ *
143
+ * A long poll rather than a socket, as the first transport: it works through every proxy, needs
144
+ * no reconnection logic, and is the only option the URL-configured host has at all. The socket
145
+ * is an optimisation over it, not a replacement for it.
146
+ */
147
+ const loop = async () => {
148
+ while (!closed) {
149
+ try {
150
+ const ops = await opts.api.pullOps(session.id, Math.floor(PULL_WAIT_MS / 1000));
151
+ stats.lastActivityAt = Date.now();
152
+ for (const op of ops) {
153
+ if (closed)
154
+ break;
155
+ await perform(op);
156
+ }
157
+ }
158
+ catch (e) {
159
+ if (closed)
160
+ break;
161
+ log(`pull failed: ${e.message}`);
162
+ await new Promise(resolve => setTimeout(resolve, 2_000));
163
+ }
164
+ }
165
+ };
166
+ void loop();
167
+ return {
168
+ session,
169
+ stats,
170
+ nextTask: async (waitMs) => {
171
+ const task = await tasks.take(waitMs);
172
+ if (task != null)
173
+ stats.lastActivityAt = Date.now();
174
+ return task;
175
+ },
176
+ submitTask: async (result) => {
177
+ const opId = tasks.opIdOf(result.taskId);
178
+ if (opId == null) {
179
+ log(`no operation is waiting for task ${result.taskId}`);
180
+ return;
181
+ }
182
+ tasks.settle(result.taskId);
183
+ stats.tasksSubmitted += 1;
184
+ await submit({ opId, sessionId: session.id, ok: true, value: result });
185
+ },
186
+ taskById: taskId => tasks.outstandingById(taskId),
187
+ outstandingTasks: () => tasks.outstandingTasks(),
188
+ pendingTasks: () => tasks.size(),
189
+ nextQuestion: async (waitMs) => {
190
+ const question = await questions.take(waitMs);
191
+ if (question != null)
192
+ stats.lastActivityAt = Date.now();
193
+ return question;
194
+ },
195
+ answerQuestion: async (answer) => {
196
+ const opId = questions.opIdOf(answer.inquiryId);
197
+ if (opId == null) {
198
+ // The question outlived its operation — the run parked and stopped waiting on this
199
+ // session. The answer is not lost: it is sent by id through `connect.inquiry.answer`,
200
+ // which is the caller's fallback and the reason this reports rather than throws.
201
+ log(`no operation is waiting for question ${answer.inquiryId}`);
202
+ return;
203
+ }
204
+ questions.settle(answer.inquiryId);
205
+ stats.questionsAnswered += 1;
206
+ await submit({ opId, sessionId: session.id, ok: true, value: answer });
207
+ },
208
+ questionById: inquiryId => questions.outstandingById(inquiryId),
209
+ outstandingQuestions: () => questions.outstandingQuestions(),
210
+ pendingQuestions: () => questions.size(),
211
+ close: async () => {
212
+ closed = true;
213
+ try {
214
+ await opts.api.closeSession(session.id);
215
+ }
216
+ catch (e) {
217
+ log(`close failed: ${e.message}`);
218
+ }
219
+ },
220
+ };
221
+ };
222
+ export { ConnectSessionStatus };
223
+ export * from './queue.js';
224
+ export * from './tasks.js';
225
+ export * from './questions.js';
226
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/session/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import type { InquiryPayload } from '@owlmeans/viable-common';
2
+ import { OpQueue } from './queue.js';
3
+ /**
4
+ * Questions waiting for the person the parent agent is working for.
5
+ *
6
+ * The same queue as the model tasks, and deliberately a SEPARATE one: a question outranks a task
7
+ * because a person is slower than a subagent, and a parent draining one list would hand a question
8
+ * to a subagent — which is precisely the outcome the whole primitive exists to prevent. Nothing
9
+ * here executes anything: a question is delivered, and its answer comes back through the tool the
10
+ * parent calls after asking.
11
+ */
12
+ export declare class QuestionQueue extends OpQueue<InquiryPayload> {
13
+ /** Put to the parent and still unanswered, oldest first. */
14
+ outstandingQuestions(): InquiryPayload[];
15
+ }
16
+ //# sourceMappingURL=questions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"questions.d.ts","sourceRoot":"","sources":["../../src/session/questions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAA;AAE7D,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAA;AAEpC;;;;;;;;GAQG;AACH,qBAAa,aAAc,SAAQ,OAAO,CAAC,cAAc,CAAC;IACxD,4DAA4D;IAC5D,oBAAoB,IAAI,cAAc,EAAE,CAEvC;CACF"}
@@ -0,0 +1,17 @@
1
+ import { OpQueue } from './queue.js';
2
+ /**
3
+ * Questions waiting for the person the parent agent is working for.
4
+ *
5
+ * The same queue as the model tasks, and deliberately a SEPARATE one: a question outranks a task
6
+ * because a person is slower than a subagent, and a parent draining one list would hand a question
7
+ * to a subagent — which is precisely the outcome the whole primitive exists to prevent. Nothing
8
+ * here executes anything: a question is delivered, and its answer comes back through the tool the
9
+ * parent calls after asking.
10
+ */
11
+ export class QuestionQueue extends OpQueue {
12
+ /** Put to the parent and still unanswered, oldest first. */
13
+ outstandingQuestions() {
14
+ return this.outstanding();
15
+ }
16
+ }
17
+ //# sourceMappingURL=questions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"questions.js","sourceRoot":"","sources":["../../src/session/questions.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAA;AAEpC;;;;;;;;GAQG;AACH,MAAM,OAAO,aAAc,SAAQ,OAAuB;IACxD,4DAA4D;IAC5D,oBAAoB;QAClB,OAAO,IAAI,CAAC,WAAW,EAAE,CAAA;IAC3B,CAAC;CACF"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Operations waiting for the parent agent — the model calls it performs, and the questions it
3
+ * puts to a person.
4
+ *
5
+ * A queue rather than a callback because of who drains it: the parent agent asks for work when it
6
+ * is ready, through a tool call, and may take minutes to answer. Nothing here pushes — the whole
7
+ * point of the local-LLM mode is that the platform waits on somebody else's schedule, and a
8
+ * question waits on a slower schedule still.
9
+ *
10
+ * Generic over the payload rather than copied per kind: both of them are handed out once, both are
11
+ * redelivered by the platform until they are answered, and both route their answer back on an
12
+ * operation id. A second copy of these rules is a second place for the redelivery guard to be
13
+ * forgotten, and the symptom of forgetting it is real model calls paid for and thrown away.
14
+ *
15
+ * The operation id is kept beside each item and never shown to the parent. It is what an answer is
16
+ * routed back on, and a parent that could name one could answer an operation it was never given.
17
+ */
18
+ export declare class OpQueue<T extends {
19
+ id: string;
20
+ }> {
21
+ private readonly queue;
22
+ private readonly ops;
23
+ /**
24
+ * Every item this session has ever been handed.
25
+ *
26
+ * The platform redelivers an operation on every poll until it is answered — that is what makes a
27
+ * connector restart cost a round trip instead of a run. A model task takes a parent agent tens of
28
+ * seconds to answer, so without this the queue grew one copy per poll for the whole time it was
29
+ * being answered, and the parent then ran the same task over and over: real model calls, paid
30
+ * for, thrown away. Kept after settling too, so a redelivery that raced the platform's deletion
31
+ * is ignored rather than answered a second time.
32
+ */
33
+ private readonly seen;
34
+ /**
35
+ * Handed out and not yet answered. An answer is checked against the item that asked for it.
36
+ *
37
+ * Named `held` rather than `outstanding` so the public reading of it can carry that name: the
38
+ * two subclasses expose it as `outstandingTasks()` / `outstandingQuestions()`, and a field and
39
+ * a method cannot share one name.
40
+ */
41
+ private readonly held;
42
+ private readonly waiting;
43
+ /** @returns whether the item was accepted — `false` for a redelivery of one already handed over. */
44
+ push(item: T, opId: string): boolean;
45
+ /** The next item, or null once `waitMs` elapses. Never rejects: nothing to do is not a failure. */
46
+ take(waitMs: number): Promise<T | null>;
47
+ opIdOf(id: string): string | null;
48
+ outstandingById(id: string): T | null;
49
+ /** Handed out and still unanswered, oldest first. */
50
+ outstanding(): T[];
51
+ settle(id: string): void;
52
+ size(): number;
53
+ }
54
+ //# sourceMappingURL=queue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queue.d.ts","sourceRoot":"","sources":["../../src/session/queue.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,OAAO,CAAC,CAAC,SAAS;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE;IAC3C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAU;IAChC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAA4B;IAChD;;;;;;;;;OASG;IACH,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAoB;IACzC;;;;;;OAMG;IACH,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAuB;IAC5C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAwB;IAEhD,oGAAoG;IACpG,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAgBnC;IAED,mGAAmG;IAC7F,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAuB5C;IAED,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAEhC;IAED,eAAe,CAAC,EAAE,EAAE,MAAM,GAAG,CAAC,GAAG,IAAI,CAEpC;IAED,qDAAqD;IACrD,WAAW,IAAI,CAAC,EAAE,CAEjB;IAED,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAGvB;IAED,IAAI,IAAI,MAAM,CAEb;CACF"}