@fnndsc/brasa 0.49.1 → 0.50.0

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 (175) hide show
  1. package/dist/buildinfo.json +1 -1
  2. package/dist/builtins/fs/cat.js +6 -5
  3. package/dist/builtins/fs/cat.js.map +1 -1
  4. package/dist/builtins/fs/configWrite.js +6 -5
  5. package/dist/builtins/fs/configWrite.js.map +1 -1
  6. package/dist/builtins/fs/cp.js +3 -2
  7. package/dist/builtins/fs/cp.js.map +1 -1
  8. package/dist/builtins/fs/download.js +5 -4
  9. package/dist/builtins/fs/download.js.map +1 -1
  10. package/dist/builtins/fs/du.js +4 -6
  11. package/dist/builtins/fs/du.js.map +1 -1
  12. package/dist/builtins/fs/edit.js +10 -9
  13. package/dist/builtins/fs/edit.js.map +1 -1
  14. package/dist/builtins/fs/find.js +3 -2
  15. package/dist/builtins/fs/find.js.map +1 -1
  16. package/dist/builtins/fs/ls.js +2 -1
  17. package/dist/builtins/fs/ls.js.map +1 -1
  18. package/dist/builtins/fs/mv.js +3 -2
  19. package/dist/builtins/fs/mv.js.map +1 -1
  20. package/dist/builtins/fs/pull.js +13 -12
  21. package/dist/builtins/fs/pull.js.map +1 -1
  22. package/dist/builtins/fs/rmdir.js +2 -1
  23. package/dist/builtins/fs/rmdir.js.map +1 -1
  24. package/dist/builtins/fs/tree.js +30 -39
  25. package/dist/builtins/fs/tree.js.map +1 -1
  26. package/dist/builtins/fs/upload.js +3 -2
  27. package/dist/builtins/fs/upload.js.map +1 -1
  28. package/dist/builtins/fs/url.js +2 -1
  29. package/dist/builtins/fs/url.js.map +1 -1
  30. package/dist/builtins/games/stdin.d.ts +8 -0
  31. package/dist/builtins/games/stdin.js +26 -14
  32. package/dist/builtins/games/stdin.js.map +1 -1
  33. package/dist/builtins/help.js +19 -3
  34. package/dist/builtins/help.js.map +1 -1
  35. package/dist/builtins/net/connect.js +2 -1
  36. package/dist/builtins/net/connect.js.map +1 -1
  37. package/dist/builtins/net/cubepath.js +5 -4
  38. package/dist/builtins/net/cubepath.js.map +1 -1
  39. package/dist/builtins/net/pacs.js +6 -5
  40. package/dist/builtins/net/pacs.js.map +1 -1
  41. package/dist/builtins/net/query.js +14 -13
  42. package/dist/builtins/net/query.js.map +1 -1
  43. package/dist/builtins/net/status.js +5 -4
  44. package/dist/builtins/net/status.js.map +1 -1
  45. package/dist/builtins/parametersofplugin.js +2 -1
  46. package/dist/builtins/parametersofplugin.js.map +1 -1
  47. package/dist/builtins/pluginExecute.js +2 -1
  48. package/dist/builtins/pluginExecute.js.map +1 -1
  49. package/dist/builtins/proc.js +31 -30
  50. package/dist/builtins/proc.js.map +1 -1
  51. package/dist/builtins/res/compute.js +4 -3
  52. package/dist/builtins/res/compute.js.map +1 -1
  53. package/dist/builtins/res/dicom.js +4 -3
  54. package/dist/builtins/res/dicom.js.map +1 -1
  55. package/dist/builtins/res/expect.js +8 -7
  56. package/dist/builtins/res/expect.js.map +1 -1
  57. package/dist/builtins/res/feed.diagram.js +4 -3
  58. package/dist/builtins/res/feed.diagram.js.map +1 -1
  59. package/dist/builtins/res/feed.js +13 -12
  60. package/dist/builtins/res/feed.js.map +1 -1
  61. package/dist/builtins/res/feed.tree.js +3 -2
  62. package/dist/builtins/res/feed.tree.js.map +1 -1
  63. package/dist/builtins/res/files.js +3 -2
  64. package/dist/builtins/res/files.js.map +1 -1
  65. package/dist/builtins/res/gather.js +2 -1
  66. package/dist/builtins/res/gather.js.map +1 -1
  67. package/dist/builtins/res/gather.process.js +2 -1
  68. package/dist/builtins/res/gather.process.js.map +1 -1
  69. package/dist/builtins/res/group.js +7 -6
  70. package/dist/builtins/res/group.js.map +1 -1
  71. package/dist/builtins/res/image.js +2 -1
  72. package/dist/builtins/res/image.js.map +1 -1
  73. package/dist/builtins/res/pipeline.diagram.js +2 -1
  74. package/dist/builtins/res/pipeline.diagram.js.map +1 -1
  75. package/dist/builtins/res/pipeline.js +20 -19
  76. package/dist/builtins/res/pipeline.js.map +1 -1
  77. package/dist/builtins/res/play.js +25 -31
  78. package/dist/builtins/res/play.js.map +1 -1
  79. package/dist/builtins/res/plugin.js +7 -6
  80. package/dist/builtins/res/plugin.js.map +1 -1
  81. package/dist/builtins/res/plugininstance.js +6 -5
  82. package/dist/builtins/res/plugininstance.js.map +1 -1
  83. package/dist/builtins/res/pluginmeta.js +5 -4
  84. package/dist/builtins/res/pluginmeta.js.map +1 -1
  85. package/dist/builtins/res/record.js +6 -5
  86. package/dist/builtins/res/record.js.map +1 -1
  87. package/dist/builtins/res/tag.js +5 -4
  88. package/dist/builtins/res/tag.js.map +1 -1
  89. package/dist/builtins/res/user.js +2 -1
  90. package/dist/builtins/res/user.js.map +1 -1
  91. package/dist/builtins/res/workflow.js +5 -4
  92. package/dist/builtins/res/workflow.js.map +1 -1
  93. package/dist/builtins/store.js +5 -4
  94. package/dist/builtins/store.js.map +1 -1
  95. package/dist/builtins/sys/apropos.js +2 -1
  96. package/dist/builtins/sys/apropos.js.map +1 -1
  97. package/dist/builtins/sys/echo.js +4 -3
  98. package/dist/builtins/sys/echo.js.map +1 -1
  99. package/dist/builtins/sys/grep.js +3 -2
  100. package/dist/builtins/sys/grep.js.map +1 -1
  101. package/dist/builtins/sys/joblets.d.ts +28 -0
  102. package/dist/builtins/sys/joblets.js +95 -0
  103. package/dist/builtins/sys/joblets.js.map +1 -0
  104. package/dist/builtins/sys/tasks.d.ts +1 -1
  105. package/dist/builtins/sys/tasks.js +7 -1
  106. package/dist/builtins/sys/tasks.js.map +1 -1
  107. package/dist/builtins/sys/textFilters.js +5 -4
  108. package/dist/builtins/sys/textFilters.js.map +1 -1
  109. package/dist/builtins/sys/variables.js +6 -5
  110. package/dist/builtins/sys/variables.js.map +1 -1
  111. package/dist/builtins/sys/whoami.js +4 -3
  112. package/dist/builtins/sys/whoami.js.map +1 -1
  113. package/dist/chris/chiliDelegate.js +2 -1
  114. package/dist/chris/chiliDelegate.js.map +1 -1
  115. package/dist/chris/chrisCommands.d.ts +0 -1
  116. package/dist/chris/chrisCommands.js +13 -0
  117. package/dist/chris/chrisCommands.js.map +1 -1
  118. package/dist/core/assignment.js +2 -2
  119. package/dist/core/assignment.js.map +1 -1
  120. package/dist/core/cancellation.d.ts +10 -7
  121. package/dist/core/cancellation.js +39 -24
  122. package/dist/core/cancellation.js.map +1 -1
  123. package/dist/core/commandRegistry.d.ts +19 -0
  124. package/dist/core/commandRegistry.js +12 -0
  125. package/dist/core/commandRegistry.js.map +1 -1
  126. package/dist/core/coreCommands.d.ts +0 -1
  127. package/dist/core/coreCommands.js +22 -0
  128. package/dist/core/coreCommands.js.map +1 -1
  129. package/dist/core/dispatch.js +12 -18
  130. package/dist/core/dispatch.js.map +1 -1
  131. package/dist/core/elevation.js +7 -6
  132. package/dist/core/elevation.js.map +1 -1
  133. package/dist/core/engine.d.ts +43 -40
  134. package/dist/core/engine.js +43 -19
  135. package/dist/core/engine.js.map +1 -1
  136. package/dist/core/handlerRun.d.ts +16 -6
  137. package/dist/core/handlerRun.js +47 -2
  138. package/dist/core/handlerRun.js.map +1 -1
  139. package/dist/core/holds.d.ts +25 -0
  140. package/dist/core/holds.js +74 -0
  141. package/dist/core/holds.js.map +1 -0
  142. package/dist/core/joblets.d.ts +94 -0
  143. package/dist/core/joblets.js +363 -0
  144. package/dist/core/joblets.js.map +1 -0
  145. package/dist/core/lineOrigin.d.ts +31 -0
  146. package/dist/core/lineOrigin.js +38 -0
  147. package/dist/core/lineOrigin.js.map +1 -0
  148. package/dist/core/listing.d.ts +8 -0
  149. package/dist/core/listing.js +26 -14
  150. package/dist/core/listing.js.map +1 -1
  151. package/dist/core/runner.d.ts +15 -0
  152. package/dist/core/runner.js +25 -7
  153. package/dist/core/runner.js.map +1 -1
  154. package/dist/core/surface.d.ts +17 -2
  155. package/dist/core/surface.js +26 -3
  156. package/dist/core/surface.js.map +1 -1
  157. package/dist/core/variables.d.ts +7 -0
  158. package/dist/core/variables.js +9 -0
  159. package/dist/core/variables.js.map +1 -1
  160. package/dist/notes.json +1 -1
  161. package/dist/session/answer.d.ts +8 -11
  162. package/dist/session/answer.js +36 -25
  163. package/dist/session/answer.js.map +1 -1
  164. package/dist/session/cwdScope.d.ts +25 -0
  165. package/dist/session/cwdScope.js +36 -0
  166. package/dist/session/cwdScope.js.map +1 -0
  167. package/dist/session/index.js +14 -4
  168. package/dist/session/index.js.map +1 -1
  169. package/dist/session/recorder.d.ts +7 -4
  170. package/dist/session/recorder.js +16 -21
  171. package/dist/session/recorder.js.map +1 -1
  172. package/package.json +4 -4
  173. package/dist/core/turn.d.ts +0 -61
  174. package/dist/core/turn.js +0 -83
  175. package/dist/core/turn.js.map +0 -1
@@ -0,0 +1,363 @@
1
+ /**
2
+ * @file Joblets: lines run in the background, `line &` (#1273).
3
+ *
4
+ * A long line (a PACS pull, a feed's creation, a teardown) need not hold the
5
+ * console it was typed in. Ended with `&`, it runs as a joblet: the runner
6
+ * keeps it as `/proc/joblets/<N>`, its output goes to its log rather than to
7
+ * a console someone is typing in, and its surface hears one line when it
8
+ * starts (`[1] pull … &`) and one when it ends (`[1]+ Done pull …`).
9
+ *
10
+ * A joblet is a subshell, as POSIX's is: it stands where its line was typed
11
+ * and starts with that line's variables, and its own `cd`s and assignments
12
+ * stay inside it. What lives in CUBE (a feed, the cohort) is shared, and a
13
+ * joblet holds what its line changes while it works, as a foreground line
14
+ * does, so it waits for, and is waited for by, the lines it conflicts with.
15
+ *
16
+ * A joblet that needs an answer pauses and says so (`[1]+ Waiting for an
17
+ * answer: … · fg %1 to answer`), as a POSIX background job that reads the
18
+ * terminal is stopped: a question asked out of the blue in a console would
19
+ * be answered by accident. `fg %1`, from any surface, asks it there.
20
+ *
21
+ * Joblets belong to the session: they outlive the surface that started them,
22
+ * and a daemon restart ends them, their logs kept ("ended by restart").
23
+ *
24
+ * @module
25
+ */
26
+ import { AsyncLocalStorage } from 'node:async_hooks';
27
+ import { EventEmitter } from 'node:events';
28
+ import { envelope_error, envelope_ok, taskState_isOver } from '@fnndsc/menu';
29
+ import { ambient_publish } from './ambient.js';
30
+ import { commandCancellation_abort } from './cancellation.js';
31
+ import { lineHolds_get } from './holds.js';
32
+ import { lineOrigin_get } from './lineOrigin.js';
33
+ import { JobRefusal, runner_get } from './runner.js';
34
+ import { ansi_strip, sinkScope_run } from './sink.js';
35
+ import { surfaceInstalled_get, surfaceScope_run } from './surface.js';
36
+ import { variableScope_fork } from './variables.js';
37
+ import { quoteOpen_find } from '../lib/quoting.js';
38
+ import { cwdScope_run } from '../session/cwdScope.js';
39
+ import { session } from '../session/index.js';
40
+ /** The runner source joblets are kept in: `/proc/joblets/<N>`. */
41
+ export const JOBLETS_SOURCE_ID = 'joblets';
42
+ /**
43
+ * Runs work outside every line's scope, as the kernel stood when it loaded:
44
+ * a joblet is its own line, not part of the one that started it.
45
+ */
46
+ const rootContext_run = AsyncLocalStorage.snapshot();
47
+ const running = new Map();
48
+ const asking = new EventEmitter();
49
+ asking.setMaxListeners(0);
50
+ /**
51
+ * The line a joblet ends with, as its surface and `joblets` print it.
52
+ *
53
+ * @param record - The joblet's record.
54
+ * @returns The notice.
55
+ */
56
+ function jobletNotice_render(record) {
57
+ if (record.state === 'done')
58
+ return `[${record.id}]+ Done ${record.label}`;
59
+ if (record.state === 'cancelled')
60
+ return `[${record.id}]+ Stopped ${record.label}`;
61
+ const exit = record.data.exit;
62
+ return `[${record.id}]+ Failed (exit ${typeof exit === 'number' ? exit : 1}): ${record.outcome ?? record.label}`;
63
+ }
64
+ /** The joblets' source, which the core gives every session's runner. */
65
+ export const jobletsSource = {
66
+ id: JOBLETS_SOURCE_ID,
67
+ label: 'joblets',
68
+ // A joblet runs the session's own lines, which need no tier in front.
69
+ ungated: true,
70
+ interrupted: 'ended by restart',
71
+ notice: jobletNotice_render,
72
+ };
73
+ /**
74
+ * The line a `&` sends to the background, when the line ends with one.
75
+ *
76
+ * @param line - The line as typed.
77
+ * @returns The line without its `&`, or null when it is not one (`&&`,
78
+ * `\&`, a `&` inside quotes, a bare `&`).
79
+ */
80
+ export function jobletLine_parse(line) {
81
+ const trimmed = line.trimEnd();
82
+ if (!trimmed.endsWith('&') || trimmed.endsWith('&&'))
83
+ return null;
84
+ const body = trimmed.slice(0, -1);
85
+ if (body.endsWith('\\') || quoteOpen_find(body) !== null)
86
+ return null;
87
+ const command = body.trim();
88
+ return command === '' ? null : command;
89
+ }
90
+ /**
91
+ * Publishes a line to the surface a joblet came from.
92
+ *
93
+ * @param origin - The surface.
94
+ * @param text - The line.
95
+ */
96
+ function notice_publish(origin, text, model) {
97
+ ambient_publish({ kind: 'envelope', envelope: envelope_ok(`${text}\n`, model), origin });
98
+ }
99
+ /** A sink that writes a joblet's output to its log, a line at a time. */
100
+ class JobletLogSink {
101
+ write;
102
+ progress;
103
+ relay;
104
+ pending = '';
105
+ /**
106
+ * @param write - Writes one line to the log.
107
+ * @param progress - Says how far the joblet has come, on its task.
108
+ * @param relay - Hands a progress event to the joblet's surface.
109
+ */
110
+ constructor(write, progress, relay) {
111
+ this.write = write;
112
+ this.progress = progress;
113
+ this.relay = relay;
114
+ }
115
+ /** @inheritdoc */
116
+ data_write(chunk) {
117
+ this.text_take(typeof chunk === 'string' ? chunk : chunk.toString('utf-8'));
118
+ }
119
+ /** @inheritdoc */
120
+ err_write(chunk) {
121
+ this.text_take(typeof chunk === 'string' ? chunk : chunk.toString('utf-8'));
122
+ }
123
+ /** @inheritdoc */
124
+ status_write(_text) {
125
+ // A status line is for whoever watches now; a log keeps what happened.
126
+ }
127
+ /** @inheritdoc */
128
+ progress_write(event) {
129
+ // The surface it came from follows it as it would a line in front.
130
+ this.relay(event);
131
+ // A count goes on the joblet's task, where a tile or a watch reads it.
132
+ if (event.current === undefined)
133
+ return;
134
+ this.progress({ current: event.current, total: event.total ?? null, ...(event.unit !== undefined ? { unit: event.unit } : {}) });
135
+ }
136
+ /** @inheritdoc */
137
+ event_write(_kind, _data) {
138
+ // An event is live by contract.
139
+ }
140
+ /** Writes what is left of a line without its newline. */
141
+ flush() {
142
+ if (this.pending !== '')
143
+ this.write(this.pending);
144
+ this.pending = '';
145
+ }
146
+ /**
147
+ * Splits text into the log's lines, keeping a part-line for the next chunk.
148
+ *
149
+ * @param text - The text.
150
+ */
151
+ text_take(text) {
152
+ const lines = ansi_strip(this.pending + text).split('\n');
153
+ this.pending = lines.pop() ?? '';
154
+ for (const line of lines)
155
+ this.write(line);
156
+ }
157
+ }
158
+ /**
159
+ * The number a new joblet takes: one past the highest still running, as a
160
+ * shell numbers its jobs.
161
+ *
162
+ * @param runner - The session's runner.
163
+ * @returns The number.
164
+ */
165
+ function jobletNumber_next(runner) {
166
+ const numbers = runner.jobs_list(JOBLETS_SOURCE_ID)
167
+ .filter((record) => !taskState_isOver(record.state))
168
+ .map((record) => Number(record.id))
169
+ .filter((number) => Number.isInteger(number));
170
+ return numbers.length === 0 ? 1 : Math.max(...numbers) + 1;
171
+ }
172
+ /**
173
+ * Starts a line as a joblet.
174
+ *
175
+ * @param line - The line, without its `&`.
176
+ * @returns The start notice, `[1] line &`, or why it could not start.
177
+ */
178
+ export async function joblet_start(line) {
179
+ const runner = runner_get();
180
+ if (runner === null)
181
+ return envelope_error('', undefined, `${line}: this session runs no joblets\n`);
182
+ const number = jobletNumber_next(runner);
183
+ const joblet = { number, line, origin: lineOrigin_get(), question: null };
184
+ // Where the line was typed: the joblet stands there whatever moves later.
185
+ const cwd = await session.getCWD();
186
+ try {
187
+ const started = runner.job_start(JOBLETS_SOURCE_ID, String(number), line, (context) => {
188
+ running.set(number, joblet);
189
+ return rootContext_run(() => joblet_run(joblet, context, cwd))
190
+ .finally(() => { running.delete(number); });
191
+ }, { origin: joblet.origin });
192
+ void started.finished;
193
+ }
194
+ catch (error) {
195
+ if (error instanceof JobRefusal)
196
+ return envelope_error('', undefined, `${error.message}\n`);
197
+ throw error;
198
+ }
199
+ return envelope_ok(`[${number}] ${line} &\n`, { kind: 'joblet.started', data: { number, line, path: `/proc/${JOBLETS_SOURCE_ID}/${number}` } });
200
+ }
201
+ /**
202
+ * A joblet's work: its line, run as a line of its own, writing to its log.
203
+ *
204
+ * @param joblet - The joblet.
205
+ * @param context - What the runner hands it.
206
+ * @param cwd - Where it stands.
207
+ */
208
+ async function joblet_run(joblet, context, cwd) {
209
+ const { line_execute } = await import('./engine.js');
210
+ const host = surfaceInstalled_get();
211
+ let release = null;
212
+ const hold_take = async () => {
213
+ release = host.hold !== undefined ? await host.hold(lineHolds_get(joblet.line)) : null;
214
+ };
215
+ const hold_give = () => {
216
+ const giving = release;
217
+ release = null;
218
+ giving?.();
219
+ };
220
+ const key = `joblet-${joblet.number}`;
221
+ context.signal.addEventListener('abort', () => {
222
+ commandCancellation_abort(key);
223
+ joblet.question?.refuse(new Error('the joblet was stopped'));
224
+ }, { once: true });
225
+ const standIn = jobletSurface_make(joblet, context, hold_take, hold_give);
226
+ const sink = new JobletLogSink((text) => context.log_write(text), (progress) => context.progress_set(progress), (event) => ambient_publish({ kind: 'progress', origin: joblet.origin, id: key, progress: event }));
227
+ let exit = 0;
228
+ await hold_take();
229
+ let envelopes;
230
+ try {
231
+ envelopes = await sinkScope_run(sink, () => surfaceScope_run(standIn, () => variableScope_fork(() => cwdScope_run(() => line_execute(joblet.line, { origin: `joblet:${joblet.number}`, id: key, ended: (code) => { exit = code; } }), cwd))));
232
+ }
233
+ finally {
234
+ hold_give();
235
+ }
236
+ // What the line answered with goes to the log after what it printed, and
237
+ // a model it answered with (a cohort its pull brought home) to its surface,
238
+ // whose views take it as they would from a line in front.
239
+ for (const envelope of envelopes) {
240
+ if (envelope.model !== undefined)
241
+ ambient_publish({ kind: 'envelope', envelope: { status: envelope.status, rendered: '', model: envelope.model }, origin: joblet.origin });
242
+ if (envelope.rendered !== '')
243
+ sink.data_write(envelope.rendered.endsWith('\n') ? envelope.rendered : `${envelope.rendered}\n`);
244
+ if (envelope.renderedErr)
245
+ sink.err_write(envelope.renderedErr.endsWith('\n') ? envelope.renderedErr : `${envelope.renderedErr}\n`);
246
+ }
247
+ sink.flush();
248
+ const failed = exit !== 0 || envelopes.some((envelope) => envelope.status === 'error');
249
+ if (!failed)
250
+ return;
251
+ context.data_set('exit', exit === 0 ? 1 : exit);
252
+ const lastErr = envelopes.map((envelope) => ansi_strip(envelope.renderedErr ?? '')).join('').trim().split('\n').pop() ?? '';
253
+ throw new Error(lastErr !== '' ? lastErr : `exit ${exit === 0 ? 1 : exit}`);
254
+ }
255
+ /**
256
+ * The surface a joblet runs under: the host's, except that a question
257
+ * pauses the joblet until someone `fg`s it, giving up what it holds while
258
+ * it waits.
259
+ *
260
+ * @param joblet - The joblet.
261
+ * @param context - Its runner context.
262
+ * @param hold_take - Takes its hold again.
263
+ * @param hold_give - Gives its hold up.
264
+ * @returns The stand-in.
265
+ */
266
+ function jobletSurface_make(joblet, context, hold_take, hold_give) {
267
+ const question_pause = async (request) => {
268
+ if (context.signal.aborted)
269
+ throw new Error('the joblet was stopped');
270
+ hold_give();
271
+ notice_publish(joblet.origin, `[${joblet.number}]+ Waiting for an answer: ${request.message.trim()} · fg %${joblet.number} to answer`, { kind: 'joblet.waiting', data: { number: joblet.number, line: joblet.line, question: request.message } });
272
+ context.log_write(`waiting for an answer: ${request.message.trim()}`);
273
+ try {
274
+ return await new Promise((resolve, reject) => {
275
+ joblet.question = {
276
+ request,
277
+ answer: (text) => { joblet.question = null; resolve(text); },
278
+ refuse: (reason) => { joblet.question = null; reject(reason); },
279
+ };
280
+ asking.emit(`asked:${joblet.number}`);
281
+ });
282
+ }
283
+ finally {
284
+ await hold_take();
285
+ }
286
+ };
287
+ return new Proxy(surfaceInstalled_get(), {
288
+ get: (_target, property) => {
289
+ if (property === 'prompt')
290
+ return question_pause;
291
+ const host = surfaceInstalled_get();
292
+ const value = Reflect.get(host, property);
293
+ return typeof value === 'function' ? value.bind(host) : value;
294
+ },
295
+ });
296
+ }
297
+ /**
298
+ * The question a joblet waits on now, if any.
299
+ *
300
+ * @param number - The joblet.
301
+ * @returns Its question, or null.
302
+ */
303
+ export function jobletQuestion_get(number) {
304
+ return running.get(number)?.question ?? null;
305
+ }
306
+ /**
307
+ * Waits until a joblet asks a question, or the wait is called off.
308
+ *
309
+ * @param number - The joblet.
310
+ * @param until - Calls the wait off when it settles.
311
+ * @returns The question, or null when called off first.
312
+ */
313
+ export async function jobletQuestion_await(number, until) {
314
+ const now = jobletQuestion_get(number);
315
+ if (now !== null)
316
+ return now;
317
+ let heard = null;
318
+ const asked = new Promise((resolve) => {
319
+ heard = () => resolve('asked');
320
+ asking.once(`asked:${number}`, heard);
321
+ });
322
+ const outcome = await Promise.race([asked, until.then(() => 'off', () => 'off')]);
323
+ if (heard !== null)
324
+ asking.off(`asked:${number}`, heard);
325
+ return outcome === 'asked' ? jobletQuestion_get(number) : null;
326
+ }
327
+ /**
328
+ * The joblets running now: what a restart would end.
329
+ *
330
+ * @returns Them, by number.
331
+ */
332
+ export function joblets_running() {
333
+ return [...running.values()]
334
+ .sort((a, b) => a.number - b.number)
335
+ .map((joblet) => ({ number: joblet.number, line: joblet.line, waiting: joblet.question !== null }));
336
+ }
337
+ /**
338
+ * Whether a joblet waits on an answer.
339
+ *
340
+ * @param number - The joblet.
341
+ * @returns True while it waits.
342
+ */
343
+ export function joblet_isWaiting(number) {
344
+ return jobletQuestion_get(number) !== null;
345
+ }
346
+ /**
347
+ * The joblet a word names: `%3`, `%%`/`%+` (the most recent), or `3`.
348
+ *
349
+ * @param word - The word.
350
+ * @returns The joblet's number, or null when the word names none.
351
+ */
352
+ export function jobletWord_parse(word) {
353
+ const runner = runner_get();
354
+ if (word === '%%' || word === '%+' || word === '%') {
355
+ const numbers = (runner?.jobs_list(JOBLETS_SOURCE_ID) ?? []).map((record) => Number(record.id)).filter(Number.isInteger);
356
+ const live = numbers.filter((number) => running.has(number));
357
+ const pool = live.length > 0 ? live : numbers;
358
+ return pool.length === 0 ? null : Math.max(...pool);
359
+ }
360
+ const match = word.match(/^%?(\d+)$/);
361
+ return match === null ? null : Number(match[1]);
362
+ }
363
+ //# sourceMappingURL=joblets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"joblets.js","sourceRoot":"","sources":["../../src/core/joblets.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * @file Which surface a line came from.
3
+ *
4
+ * One session serves several surfaces at once: a console, a browser, a
5
+ * second terminal. What a line leaves behind for the operator who typed it
6
+ * (the listing `@3` counts) belongs to that operator's surface, not to the
7
+ * session: a listing one surface reads must not renumber the rows another
8
+ * is about to act on (#1273).
9
+ *
10
+ * The host names the origin when it hands the kernel a line; a line run
11
+ * inside it (a manifest's, a capture's) inherits it. A line with no host
12
+ * behind it (a test, a headless script) has the default origin.
13
+ *
14
+ * @module
15
+ */
16
+ /** The origin of a line no host named. */
17
+ export declare const ORIGIN_DEFAULT: string;
18
+ /**
19
+ * Runs an operation as lines from one origin.
20
+ *
21
+ * @param origin - The surface the line came from.
22
+ * @param operation - The work.
23
+ * @returns What the operation returns.
24
+ */
25
+ export declare function lineOrigin_run<T>(origin: string, operation: () => Promise<T>): Promise<T>;
26
+ /**
27
+ * The origin of the line running now.
28
+ *
29
+ * @returns The surface it came from, or {@link ORIGIN_DEFAULT}.
30
+ */
31
+ export declare function lineOrigin_get(): string;
@@ -0,0 +1,38 @@
1
+ /**
2
+ * @file Which surface a line came from.
3
+ *
4
+ * One session serves several surfaces at once: a console, a browser, a
5
+ * second terminal. What a line leaves behind for the operator who typed it
6
+ * (the listing `@3` counts) belongs to that operator's surface, not to the
7
+ * session: a listing one surface reads must not renumber the rows another
8
+ * is about to act on (#1273).
9
+ *
10
+ * The host names the origin when it hands the kernel a line; a line run
11
+ * inside it (a manifest's, a capture's) inherits it. A line with no host
12
+ * behind it (a test, a headless script) has the default origin.
13
+ *
14
+ * @module
15
+ */
16
+ import { AsyncLocalStorage } from 'node:async_hooks';
17
+ /** The origin of a line no host named. */
18
+ export const ORIGIN_DEFAULT = '';
19
+ const originScope = new AsyncLocalStorage();
20
+ /**
21
+ * Runs an operation as lines from one origin.
22
+ *
23
+ * @param origin - The surface the line came from.
24
+ * @param operation - The work.
25
+ * @returns What the operation returns.
26
+ */
27
+ export async function lineOrigin_run(origin, operation) {
28
+ return await originScope.run(origin, operation);
29
+ }
30
+ /**
31
+ * The origin of the line running now.
32
+ *
33
+ * @returns The surface it came from, or {@link ORIGIN_DEFAULT}.
34
+ */
35
+ export function lineOrigin_get() {
36
+ return originScope.getStore() ?? ORIGIN_DEFAULT;
37
+ }
38
+ //# sourceMappingURL=lineOrigin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lineOrigin.js","sourceRoot":"","sources":["../../src/core/lineOrigin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAErD,0CAA0C;AAC1C,MAAM,CAAC,MAAM,cAAc,GAAW,EAAE,CAAC;AAEzC,MAAM,WAAW,GAA8B,IAAI,iBAAiB,EAAU,CAAC;AAE/E;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAI,MAAc,EAAE,SAA2B;IACjF,OAAO,MAAM,WAAW,CAAC,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;AAClD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc;IAC5B,OAAO,WAAW,CAAC,QAAQ,EAAE,IAAI,cAAc,CAAC;AAClD,CAAC"}
@@ -16,6 +16,14 @@ export interface ListingModel {
16
16
  kind: string;
17
17
  data: unknown;
18
18
  }
19
+ /**
20
+ * Runs a pipe segment with the listing piped into it, in a slot of its own.
21
+ *
22
+ * @param model - The previous segment's listing, or null.
23
+ * @param operation - The segment.
24
+ * @returns What the segment returned.
25
+ */
26
+ export declare function listingInScope_run<T>(model: ListingModel | null, operation: () => Promise<T>): Promise<T>;
19
27
  /**
20
28
  * Hands a listing to the next builtin in a pipe (null clears it).
21
29
  *
@@ -12,24 +12,35 @@
12
12
  */
13
13
  import { answer_get, answerRows_derive } from '../session/answer.js';
14
14
  import { answerKinds_get } from './answerKinds.js';
15
- import { turnPart_register } from './turn.js';
16
- /** The listing the previous builtin in a pipe produced, for the next to take. */
17
- let piped = null;
18
- // A pipe's listing not yet taken belongs to its command, not to one that
19
- // runs while that command waits on a question.
20
- turnPart_register({
21
- name: 'listingIn',
22
- save: () => piped,
23
- clear: () => { piped = null; },
24
- restore: (saved) => { piped = saved ?? null; },
25
- });
15
+ import { AsyncLocalStorage } from 'node:async_hooks';
16
+ /** The slot outside any pipe segment's scope (a test, a direct call). */
17
+ const unscoped = { value: null };
18
+ /**
19
+ * The slot of the pipe segment running on this async path: two lines that
20
+ * run at once each read their own pipe's input, never the other's (#1273).
21
+ */
22
+ const pipedScope = new AsyncLocalStorage();
23
+ /** The slot for the work running now. */
24
+ function piped_slot() {
25
+ return pipedScope.getStore() ?? unscoped;
26
+ }
27
+ /**
28
+ * Runs a pipe segment with the listing piped into it, in a slot of its own.
29
+ *
30
+ * @param model - The previous segment's listing, or null.
31
+ * @param operation - The segment.
32
+ * @returns What the segment returned.
33
+ */
34
+ export async function listingInScope_run(model, operation) {
35
+ return await pipedScope.run({ value: model }, operation);
36
+ }
26
37
  /**
27
38
  * Hands a listing to the next builtin in a pipe (null clears it).
28
39
  *
29
40
  * @param model - The previous segment's model, when it is a listing.
30
41
  */
31
42
  export function listingIn_set(model) {
32
- piped = model;
43
+ piped_slot().value = model;
33
44
  }
34
45
  /**
35
46
  * Takes the piped listing, once.
@@ -37,8 +48,9 @@ export function listingIn_set(model) {
37
48
  * @returns It, or null when nothing was piped in.
38
49
  */
39
50
  export function listingIn_take() {
40
- const taken = piped;
41
- piped = null;
51
+ const slot = piped_slot();
52
+ const taken = slot.value;
53
+ slot.value = null;
42
54
  return taken;
43
55
  }
44
56
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"listing.js","sourceRoot":"","sources":["../../src/core/listing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAkB,MAAM,sBAAsB,CAAC;AACrF,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAQ9C,iFAAiF;AACjF,IAAI,KAAK,GAAwB,IAAI,CAAC;AAEtC,yEAAyE;AACzE,+CAA+C;AAC/C,iBAAiB,CAAC;IAChB,IAAI,EAAE,WAAW;IACjB,IAAI,EAAE,GAAY,EAAE,CAAC,KAAK;IAC1B,KAAK,EAAE,GAAS,EAAE,GAAG,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC;IACpC,OAAO,EAAE,CAAC,KAAc,EAAQ,EAAE,GAAG,KAAK,GAAI,KAA6B,IAAI,IAAI,CAAC,CAAC,CAAC;CACvF,CAAC,CAAC;AAEH;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,KAA0B;IACtD,KAAK,GAAG,KAAK,CAAC;AAChB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc;IAC5B,MAAM,KAAK,GAAwB,KAAK,CAAC;IACzC,KAAK,GAAG,IAAI,CAAC;IACb,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAA8B;IAChE,MAAM,MAAM,GAAgB,IAAI,GAAG,CAAC,eAAe,EAAE,CAAC,MAAM,CAAC,CAAC,IAAI,EAAW,EAAE,CAAC,IAAI,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAU,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IACvI,MAAM,IAAI,GAAgB,IAAI,GAAG,EAAE,CAAC;IACpC,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,IAAI,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,SAAS;QACnC,KAAK,MAAM,KAAK,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC;YAC/B,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC;gBAAE,SAAS;YAC9B,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;YAChB,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACvB,CAAC;IACH,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,uBAAuB,CAAC,KAAmB;IACzD,MAAM,IAAI,GAAuB,iBAAiB,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;IAC3E,OAAO,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,mBAAmB,CAAC,IAAI,CAAC,CAAC;AAC1D,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,uBAAuB;IACrC,MAAM,IAAI,GAAG,UAAU,EAAE,CAAC;IAC1B,OAAO,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC/D,CAAC"}
1
+ {"version":3,"file":"listing.js","sourceRoot":"","sources":["../../src/core/listing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAkB,MAAM,sBAAsB,CAAC;AACrF,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAarD,yEAAyE;AACzE,MAAM,QAAQ,GAAiB,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;AAE/C;;;GAGG;AACH,MAAM,UAAU,GAAoC,IAAI,iBAAiB,EAAgB,CAAC;AAE1F,yCAAyC;AACzC,SAAS,UAAU;IACjB,OAAO,UAAU,CAAC,QAAQ,EAAE,IAAI,QAAQ,CAAC;AAC3C,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAI,KAA0B,EAAE,SAA2B;IACjG,OAAO,MAAM,UAAU,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,SAAS,CAAC,CAAC;AAC3D,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,KAA0B;IACtD,UAAU,EAAE,CAAC,KAAK,GAAG,KAAK,CAAC;AAC7B,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc;IAC5B,MAAM,IAAI,GAAiB,UAAU,EAAE,CAAC;IACxC,MAAM,KAAK,GAAwB,IAAI,CAAC,KAAK,CAAC;IAC9C,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;IAClB,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAA8B;IAChE,MAAM,MAAM,GAAgB,IAAI,GAAG,CAAC,eAAe,EAAE,CAAC,MAAM,CAAC,CAAC,IAAI,EAAW,EAAE,CAAC,IAAI,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAU,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IACvI,MAAM,IAAI,GAAgB,IAAI,GAAG,EAAE,CAAC;IACpC,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,IAAI,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,SAAS;QACnC,KAAK,MAAM,KAAK,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC;YAC/B,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC;gBAAE,SAAS;YAC9B,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;YAChB,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACvB,CAAC;IACH,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,uBAAuB,CAAC,KAAmB;IACzD,MAAM,IAAI,GAAuB,iBAAiB,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;IAC3E,OAAO,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,mBAAmB,CAAC,IAAI,CAAC,CAAC;AAC1D,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,uBAAuB;IACrC,MAAM,IAAI,GAAG,UAAU,EAAE,CAAC;IAC1B,OAAO,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC/D,CAAC"}
@@ -51,11 +51,19 @@ export type JobRun = (context: JobContext) => Promise<void>;
51
51
  * @property resume - Picks a job that was running when the session ended up
52
52
  * again, from its record: the work to go on with, or null when it cannot
53
53
  * go on (it is then marked failed, saying so).
54
+ * @property ungated - Starting one needs no tier: its work is the session's
55
+ * own lines, which the operator may run anyway (a joblet).
56
+ * @property interrupted - What a job that cannot go on after a restart says.
57
+ * @property notice - The line every surface (or the job's own, `data.origin`)
58
+ * hears when a job ends; the job's path and state when absent.
54
59
  */
55
60
  export interface JobSource {
56
61
  readonly id: string;
57
62
  readonly label: string;
58
63
  readonly resume?: (record: JobRecord) => JobRun | null;
64
+ readonly ungated?: boolean;
65
+ readonly interrupted?: string;
66
+ readonly notice?: (record: JobRecord) => string;
59
67
  }
60
68
  /** Why a job did not start. */
61
69
  export declare class JobRefusal extends Error {
@@ -88,6 +96,13 @@ export declare class JobRunner {
88
96
  * @param allowed - Whether starting a job is allowed now (the `tasks` tier).
89
97
  */
90
98
  constructor(root: string, sources: ReadonlyArray<JobSource>, allowed?: () => boolean);
99
+ /**
100
+ * A job source by id.
101
+ *
102
+ * @param id - Its id.
103
+ * @returns It, or undefined.
104
+ */
105
+ private source_find;
91
106
  /**
92
107
  * A job's key in the map.
93
108
  *
@@ -83,6 +83,15 @@ export class JobRunner {
83
83
  for (const source of sources)
84
84
  this.records_load(source.id);
85
85
  }
86
+ /**
87
+ * A job source by id.
88
+ *
89
+ * @param id - Its id.
90
+ * @returns It, or undefined.
91
+ */
92
+ source_find(id) {
93
+ return this.sources.find((candidate) => candidate.id === id);
94
+ }
86
95
  /**
87
96
  * A job's key in the map.
88
97
  *
@@ -190,10 +199,16 @@ export class JobRunner {
190
199
  delete entry.controller;
191
200
  this.record_save(entry);
192
201
  this.events.emit(`end:${this.key_format(entry.record.source, entry.record.id)}`, entry.record);
193
- // Every surface hears a job end, as it hears a chime: a line and its model.
202
+ // Every surface hears a job end, as it hears a chime: a line and its
203
+ // model. A job that names the surface it was started on tells it alone.
204
+ const source = this.source_find(entry.record.source);
205
+ const notice = source?.notice?.(entry.record)
206
+ ?? `/proc/${entry.record.source}/${entry.record.id}: ${entry.record.state}${entry.record.state === 'done' ? '' : ` (${entry.record.outcome ?? ''})`}`;
207
+ const origin = entry.record.data.origin;
194
208
  ambient_publish({
195
209
  kind: 'envelope',
196
- envelope: envelope_ok(`/proc/${entry.record.source}/${entry.record.id}: ${entry.record.state}${entry.record.state === 'done' ? '' : ` (${entry.record.outcome ?? ''})`}\n`, { kind: JOB_MODEL_KIND, data: { ...entry.record } }),
210
+ envelope: envelope_ok(`${notice}\n`, { kind: JOB_MODEL_KIND, data: { ...entry.record } }),
211
+ ...(typeof origin === 'string' ? { origin } : {}),
197
212
  });
198
213
  return entry.record;
199
214
  }
@@ -209,10 +224,11 @@ export class JobRunner {
209
224
  * @throws JobRefusal - Without the `tasks` tier, for an unknown source, or while that id still runs.
210
225
  */
211
226
  job_start(source, id, label, run, data = {}) {
212
- if (!this.allowed())
213
- throw new JobRefusal(`${id}: Operation not permitted: starting a task needs the tasks tier (calypso --host-control tasks)`);
214
- if (!this.sources.some((candidate) => candidate.id === source))
227
+ const kind = this.source_find(source);
228
+ if (kind === undefined)
215
229
  throw new JobRefusal(`${id}: no task source ${source}`);
230
+ if (kind.ungated !== true && !this.allowed())
231
+ throw new JobRefusal(`${id}: Operation not permitted: starting a task needs the tasks tier (calypso --host-control tasks)`);
216
232
  if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(id))
217
233
  throw new JobRefusal(`${id}: a task's name is letters, digits, '.', '_' and '-'`);
218
234
  const existing = this.entries.get(this.key_format(source, id));
@@ -240,10 +256,12 @@ export class JobRunner {
240
256
  continue;
241
257
  const source = this.sources.find((candidate) => candidate.id === entry.record.source);
242
258
  const run = source?.resume?.(entry.record) ?? null;
243
- if (run === null || !this.allowed()) {
259
+ if (run === null || (source?.ungated !== true && !this.allowed())) {
244
260
  entry.record.state = 'failed';
245
261
  entry.record.ended = new Date().toISOString();
246
- entry.record.outcome = run === null ? 'interrupted: the session ended while it ran, and it cannot be picked up again' : 'interrupted: the session restarted without the tasks tier';
262
+ entry.record.outcome = run === null
263
+ ? source?.interrupted ?? 'interrupted: the session ended while it ran, and it cannot be picked up again'
264
+ : 'interrupted: the session restarted without the tasks tier';
247
265
  this.log_append(entry, `failed: ${entry.record.outcome}`);
248
266
  this.record_save(entry);
249
267
  continue;