@fnndsc/brasa 0.23.2 → 0.25.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.
- package/dist/buildinfo.json +1 -1
- package/dist/builtins/fs/pull.args.d.ts +1 -1
- package/dist/builtins/fs/pull.args.js +5 -3
- package/dist/builtins/fs/pull.args.js.map +1 -1
- package/dist/builtins/fs/pull.js +2 -2
- package/dist/builtins/fs/touch.js +1 -1
- package/dist/builtins/fs/touch.js.map +1 -1
- package/dist/builtins/help.js +134 -10
- package/dist/builtins/help.js.map +1 -1
- package/dist/builtins/index.d.ts +6 -0
- package/dist/builtins/index.js +6 -0
- package/dist/builtins/index.js.map +1 -1
- package/dist/builtins/net/query.csv.js +14 -0
- package/dist/builtins/net/query.csv.js.map +1 -1
- package/dist/builtins/net/query.js +3 -0
- package/dist/builtins/net/query.js.map +1 -1
- package/dist/builtins/pluginExecute.js +28 -3
- package/dist/builtins/pluginExecute.js.map +1 -1
- package/dist/builtins/proc.js +32 -2
- package/dist/builtins/proc.js.map +1 -1
- package/dist/builtins/res/dicom.d.ts +35 -1
- package/dist/builtins/res/dicom.js +15 -2
- package/dist/builtins/res/dicom.js.map +1 -1
- package/dist/builtins/res/expect.d.ts +40 -0
- package/dist/builtins/res/expect.js +447 -0
- package/dist/builtins/res/expect.js.map +1 -0
- package/dist/builtins/res/gather.d.ts +10 -0
- package/dist/builtins/res/gather.js +224 -0
- package/dist/builtins/res/gather.js.map +1 -0
- package/dist/builtins/res/gather.store.d.ts +131 -0
- package/dist/builtins/res/gather.store.js +225 -0
- package/dist/builtins/res/gather.store.js.map +1 -0
- package/dist/builtins/res/pipeline.args.d.ts +5 -2
- package/dist/builtins/res/pipeline.args.js +8 -3
- package/dist/builtins/res/pipeline.args.js.map +1 -1
- package/dist/builtins/res/pipeline.js +23 -0
- package/dist/builtins/res/pipeline.js.map +1 -1
- package/dist/builtins/res/play.d.ts +70 -0
- package/dist/builtins/res/play.js +334 -0
- package/dist/builtins/res/play.js.map +1 -0
- package/dist/builtins/res/plugin.js +21 -6
- package/dist/builtins/res/plugin.js.map +1 -1
- package/dist/builtins/res/record.d.ts +49 -0
- package/dist/builtins/res/record.js +150 -0
- package/dist/builtins/res/record.js.map +1 -0
- package/dist/builtins/res/runWait.d.ts +49 -0
- package/dist/builtins/res/runWait.js +193 -0
- package/dist/builtins/res/runWait.js.map +1 -0
- package/dist/builtins/sys/motd.d.ts +41 -0
- package/dist/builtins/sys/motd.js +139 -0
- package/dist/builtins/sys/motd.js.map +1 -0
- package/dist/builtins/sys/weather.d.ts +132 -0
- package/dist/builtins/sys/weather.js +297 -0
- package/dist/builtins/sys/weather.js.map +1 -0
- package/dist/command-keys.js +4 -0
- package/dist/command-keys.js.map +1 -1
- package/dist/core/connect.d.ts +31 -1
- package/dist/core/connect.js +32 -1
- package/dist/core/connect.js.map +1 -1
- package/dist/core/dispatch.d.ts +0 -14
- package/dist/core/dispatch.js +85 -27
- package/dist/core/dispatch.js.map +1 -1
- package/dist/core/engine.d.ts +10 -0
- package/dist/core/engine.js +9 -0
- package/dist/core/engine.js.map +1 -1
- package/dist/core/expansion.d.ts +70 -0
- package/dist/core/expansion.js +289 -0
- package/dist/core/expansion.js.map +1 -0
- package/dist/core/promptContext.js +5 -1
- package/dist/core/promptContext.js.map +1 -1
- package/dist/lib/duration.d.ts +17 -0
- package/dist/lib/duration.js +25 -0
- package/dist/lib/duration.js.map +1 -0
- package/dist/lib/parser.d.ts +50 -0
- package/dist/lib/parser.js +119 -0
- package/dist/lib/parser.js.map +1 -1
- package/dist/logo/brain.d.ts +6 -27
- package/dist/logo/brain.js +5 -250
- package/dist/logo/brain.js.map +1 -1
- package/dist/session/answer.d.ts +141 -0
- package/dist/session/answer.js +163 -0
- package/dist/session/answer.js.map +1 -0
- package/dist/session/answerAdapters.d.ts +4 -0
- package/dist/session/answerAdapters.js +106 -0
- package/dist/session/answerAdapters.js.map +1 -0
- package/dist/session/recent.d.ts +68 -0
- package/dist/session/recent.js +99 -0
- package/dist/session/recent.js.map +1 -0
- package/dist/session/recorder.d.ts +61 -0
- package/dist/session/recorder.js +95 -0
- package/dist/session/recorder.js.map +1 -0
- package/package.json +5 -5
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { CommandEnvelope } from '@fnndsc/cumin';
|
|
2
|
+
/** The model kind under which a finished recording travels. */
|
|
3
|
+
export declare const RECORD_MODEL_KIND: string;
|
|
4
|
+
/** What a recording turned into. */
|
|
5
|
+
export interface RecordedManifest {
|
|
6
|
+
/** Where it was written. */
|
|
7
|
+
path: string;
|
|
8
|
+
/** How many lines it holds. */
|
|
9
|
+
lines: number;
|
|
10
|
+
/** The parameters it was given, and the literal each replaced. */
|
|
11
|
+
params: Array<{
|
|
12
|
+
name: string;
|
|
13
|
+
was: string;
|
|
14
|
+
}>;
|
|
15
|
+
/** How long the recording ran. */
|
|
16
|
+
elapsedMs: number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Offers parameters for the identifiers a recording carries.
|
|
20
|
+
*
|
|
21
|
+
* @param lines - The captured lines.
|
|
22
|
+
* @returns The lines with identifiers replaced, and what was replaced.
|
|
23
|
+
*/
|
|
24
|
+
export declare function identifiers_offer(lines: string[]): {
|
|
25
|
+
lines: string[];
|
|
26
|
+
params: Array<{
|
|
27
|
+
name: string;
|
|
28
|
+
was: string;
|
|
29
|
+
}>;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Renders the manifest a recording became.
|
|
33
|
+
*
|
|
34
|
+
* @param name - What to call it.
|
|
35
|
+
* @param lines - The captured lines, already parameterized.
|
|
36
|
+
* @param params - The parameters offered.
|
|
37
|
+
* @returns The file's text.
|
|
38
|
+
*/
|
|
39
|
+
export declare function manifest_render(name: string, lines: string[], params: Array<{
|
|
40
|
+
name: string;
|
|
41
|
+
was: string;
|
|
42
|
+
}>): string;
|
|
43
|
+
/**
|
|
44
|
+
* Builtin handler for the `record` command.
|
|
45
|
+
*
|
|
46
|
+
* @param args - Parsed command arguments.
|
|
47
|
+
* @returns An envelope reporting what the recorder is doing, or what it wrote.
|
|
48
|
+
*/
|
|
49
|
+
export declare function builtin_record(args: string[]): Promise<CommandEnvelope>;
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Builtin record command: writing a session down as a manifest.
|
|
3
|
+
*
|
|
4
|
+
* Subcommands: start, stop, status.
|
|
5
|
+
*
|
|
6
|
+
* Every verb lowers to a line the operator could have typed, so a session's
|
|
7
|
+
* line history is very nearly a manifest already — a press on a surface and
|
|
8
|
+
* a line at a console arrive at the same place, and the recorder sits there.
|
|
9
|
+
* That is why this costs almost nothing: the lowering was already the law.
|
|
10
|
+
*
|
|
11
|
+
* @module
|
|
12
|
+
*/
|
|
13
|
+
import chalk from 'chalk';
|
|
14
|
+
import { envelope_ok, envelope_error } from '@fnndsc/cumin';
|
|
15
|
+
import { files_touch as chefs_touch_cmd } from '@fnndsc/chili/commands/fs/touch.js';
|
|
16
|
+
import { files_mkdir as chefs_mkdir_cmd } from '@fnndsc/chili/commands/fs/mkdir.js';
|
|
17
|
+
import path from 'path';
|
|
18
|
+
import { commandArgs_process, path_resolve } from '../utils.js';
|
|
19
|
+
import { recorder_start, recorder_state, recorder_stop, } from '../../session/recorder.js';
|
|
20
|
+
/** The model kind under which a finished recording travels. */
|
|
21
|
+
export const RECORD_MODEL_KIND = 'manifest.recorded';
|
|
22
|
+
/**
|
|
23
|
+
* Literals worth offering as parameters.
|
|
24
|
+
*
|
|
25
|
+
* A recording is personal until its identifiers come out of it: the MRN and
|
|
26
|
+
* the accession are exactly what the next person has to change, and they are
|
|
27
|
+
* the two a PACS line wears on its face. Nothing else is guessed at — a
|
|
28
|
+
* parameter the author did not want is worse than one they have to add.
|
|
29
|
+
*/
|
|
30
|
+
const IDENTIFIER_PATTERNS = [
|
|
31
|
+
{ criterion: 'PatientID', param: 'MRN' },
|
|
32
|
+
{ criterion: 'AccessionNumber', param: 'ACC' },
|
|
33
|
+
{ criterion: 'PatientName', param: 'PATIENT' },
|
|
34
|
+
];
|
|
35
|
+
/**
|
|
36
|
+
* Offers parameters for the identifiers a recording carries.
|
|
37
|
+
*
|
|
38
|
+
* @param lines - The captured lines.
|
|
39
|
+
* @returns The lines with identifiers replaced, and what was replaced.
|
|
40
|
+
*/
|
|
41
|
+
export function identifiers_offer(lines) {
|
|
42
|
+
const params = [];
|
|
43
|
+
const taken = new Map();
|
|
44
|
+
const rewritten = lines.map((line) => {
|
|
45
|
+
let text = line;
|
|
46
|
+
for (const { criterion, param } of IDENTIFIER_PATTERNS) {
|
|
47
|
+
// The value stops at an underscore or a slash: a query's projection
|
|
48
|
+
// folder is `PatientID:1279049_qid:3125_owner`, and a value that ran on
|
|
49
|
+
// to the end of the word would parameterize the query id too, so the
|
|
50
|
+
// same MRN in a path and in a query would read as two patients.
|
|
51
|
+
const pattern = new RegExp(`${criterion}:([^\\s'"/_]+)`, 'g');
|
|
52
|
+
text = text.replace(pattern, (match, value) => {
|
|
53
|
+
const held = taken.get(match);
|
|
54
|
+
if (held !== undefined)
|
|
55
|
+
return `${criterion}:\${${held}}`;
|
|
56
|
+
// A second distinct value for the same criterion gets its own name,
|
|
57
|
+
// or a workflow over two patients would collapse into one.
|
|
58
|
+
const name = taken.size === 0 || ![...taken.values()].includes(param)
|
|
59
|
+
? param
|
|
60
|
+
: `${param}_${taken.size + 1}`;
|
|
61
|
+
taken.set(match, name);
|
|
62
|
+
params.push({ name, was: value });
|
|
63
|
+
return `${criterion}:\${${name}}`;
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
return text;
|
|
67
|
+
});
|
|
68
|
+
return { lines: rewritten, params };
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Renders the manifest a recording became.
|
|
72
|
+
*
|
|
73
|
+
* @param name - What to call it.
|
|
74
|
+
* @param lines - The captured lines, already parameterized.
|
|
75
|
+
* @param params - The parameters offered.
|
|
76
|
+
* @returns The file's text.
|
|
77
|
+
*/
|
|
78
|
+
export function manifest_render(name, lines, params) {
|
|
79
|
+
const header = [
|
|
80
|
+
`@name ${name}`,
|
|
81
|
+
`@description Recorded from a session on ${new Date().toISOString().slice(0, 10)}.`,
|
|
82
|
+
];
|
|
83
|
+
for (const param of params)
|
|
84
|
+
header.push(`@param ${param.name} = ${param.was}`);
|
|
85
|
+
return `${[...header, '', ...lines].join('\n')}\n`;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Builtin handler for the `record` command.
|
|
89
|
+
*
|
|
90
|
+
* @param args - Parsed command arguments.
|
|
91
|
+
* @returns An envelope reporting what the recorder is doing, or what it wrote.
|
|
92
|
+
*/
|
|
93
|
+
export async function builtin_record(args) {
|
|
94
|
+
const parsed = commandArgs_process(args);
|
|
95
|
+
const subcommand = parsed._[0];
|
|
96
|
+
if (subcommand === 'start') {
|
|
97
|
+
const target = parsed._[1];
|
|
98
|
+
if (target === undefined) {
|
|
99
|
+
process.exitCode = 1;
|
|
100
|
+
return envelope_error('', undefined, `${chalk.red('Usage: record start <manifest>')}\n`);
|
|
101
|
+
}
|
|
102
|
+
const state = recorder_state();
|
|
103
|
+
if (state.target !== null) {
|
|
104
|
+
process.exitCode = 1;
|
|
105
|
+
return envelope_error('', undefined, `${chalk.red(`record: already recording to ${state.target} — stop it first.`)}\n`);
|
|
106
|
+
}
|
|
107
|
+
const resolved = await path_resolve(target);
|
|
108
|
+
recorder_start(resolved);
|
|
109
|
+
return envelope_ok(`${chalk.green(`record: recording to ${resolved}. Do the work; \`record stop\` writes it.`)}\n`);
|
|
110
|
+
}
|
|
111
|
+
if (subcommand === 'stop') {
|
|
112
|
+
const held = recorder_stop();
|
|
113
|
+
if (held === null) {
|
|
114
|
+
process.exitCode = 1;
|
|
115
|
+
return envelope_error('', undefined, `${chalk.red('record: nothing is recording.')}\n`);
|
|
116
|
+
}
|
|
117
|
+
if (held.lines.length === 0) {
|
|
118
|
+
return envelope_ok(`${chalk.yellow(`record: nothing to write — the session ran no lines while recording.`)}\n`);
|
|
119
|
+
}
|
|
120
|
+
const { lines, params } = identifiers_offer(held.lines);
|
|
121
|
+
const name = path.posix.basename(held.target).replace(/\.[^.]+$/, '');
|
|
122
|
+
await chefs_mkdir_cmd(path.posix.dirname(held.target));
|
|
123
|
+
const written = await chefs_touch_cmd(held.target, {
|
|
124
|
+
withContents: manifest_render(name, lines, params),
|
|
125
|
+
});
|
|
126
|
+
if (!written) {
|
|
127
|
+
process.exitCode = 1;
|
|
128
|
+
return envelope_error('', undefined, `${chalk.red(`record: could not write ${held.target}.`)}\n`);
|
|
129
|
+
}
|
|
130
|
+
const recorded = {
|
|
131
|
+
path: held.target,
|
|
132
|
+
lines: lines.length,
|
|
133
|
+
params,
|
|
134
|
+
elapsedMs: held.elapsedMs,
|
|
135
|
+
};
|
|
136
|
+
const offered = params.length === 0
|
|
137
|
+
? ''
|
|
138
|
+
: `${chalk.gray(` offered ${params.length} param${params.length === 1 ? '' : 's'}: `)}${params.map((param) => `${param.was} → \${${param.name}}`).join(', ')}\n`;
|
|
139
|
+
return envelope_ok(`${chalk.green(`record: wrote ${held.target} · ${lines.length} line${lines.length === 1 ? '' : 's'}`)}\n${offered}`, { kind: RECORD_MODEL_KIND, data: recorded });
|
|
140
|
+
}
|
|
141
|
+
if (subcommand === undefined || subcommand === 'status') {
|
|
142
|
+
const state = recorder_state();
|
|
143
|
+
if (state.target === null)
|
|
144
|
+
return envelope_ok(`${chalk.gray('Nothing is recording.')}\n`);
|
|
145
|
+
return envelope_ok(`${chalk.green(`Recording to ${state.target}`)} ${chalk.gray(`· ${state.lines} line${state.lines === 1 ? '' : 's'} · ${Math.round(state.elapsedMs / 1000)}s`)}\n`);
|
|
146
|
+
}
|
|
147
|
+
process.exitCode = 1;
|
|
148
|
+
return envelope_error('', undefined, `${chalk.red(`Unknown subcommand: ${subcommand}. Usage: record <start|stop|status>`)}\n`);
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=record.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"record.js","sourceRoot":"","sources":["../../../src/builtins/res/record.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/** Statuses a run does not leave. */
|
|
2
|
+
export declare const RUN_TERMINAL_STATUSES: ReadonlySet<string>;
|
|
3
|
+
/** How a wait ended. */
|
|
4
|
+
export type RunSettlementOutcome = 'settled' | 'detached' | 'unreachable';
|
|
5
|
+
/** What a wait found out. */
|
|
6
|
+
export interface RunSettlement {
|
|
7
|
+
/** The runs waited on. */
|
|
8
|
+
instanceIDs: number[];
|
|
9
|
+
/** Each run's status: terminal when settled, last seen otherwise. */
|
|
10
|
+
statuses: Map<number, string>;
|
|
11
|
+
/** How the wait ended. */
|
|
12
|
+
outcome: RunSettlementOutcome;
|
|
13
|
+
/** How many runs ended in a status that is not success. */
|
|
14
|
+
failed: number;
|
|
15
|
+
/** How long the wait lasted. */
|
|
16
|
+
elapsedMs: number;
|
|
17
|
+
}
|
|
18
|
+
/** How often a wait asks, for a caller that knows better than the default. */
|
|
19
|
+
export interface RunWaitCadence {
|
|
20
|
+
/** Milliseconds between asks. Both windows collapse to this when given. */
|
|
21
|
+
pollMs?: number;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Waits for every given run to reach a status it does not leave.
|
|
25
|
+
*
|
|
26
|
+
* @param instanceIDs - The plugin instances to wait on.
|
|
27
|
+
* @param label - What to call the work in progress and readouts.
|
|
28
|
+
* @param cadence - How often to ask, when the default rhythm is wrong.
|
|
29
|
+
* @returns How the wait ended, and the statuses it ended on.
|
|
30
|
+
*/
|
|
31
|
+
export declare function runs_awaitSettled(instanceIDs: number[], label: string, cadence?: RunWaitCadence): Promise<RunSettlement>;
|
|
32
|
+
/**
|
|
33
|
+
* Renders how long a wait lasted, the way an operator says it.
|
|
34
|
+
*
|
|
35
|
+
* @param ms - Elapsed milliseconds.
|
|
36
|
+
* @returns A short duration, such as `2m14s`.
|
|
37
|
+
*/
|
|
38
|
+
export declare function elapsed_render(ms: number): string;
|
|
39
|
+
/**
|
|
40
|
+
* Renders what a wait found, in the operator's terms.
|
|
41
|
+
*
|
|
42
|
+
* A detached wait says how to get back to the work, because the run is still
|
|
43
|
+
* going and the handle is the only way to reach it.
|
|
44
|
+
*
|
|
45
|
+
* @param settlement - What the wait found.
|
|
46
|
+
* @param label - What the work is called.
|
|
47
|
+
* @returns The line to print.
|
|
48
|
+
*/
|
|
49
|
+
export declare function settlement_render(settlement: RunSettlement, label: string): string;
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Waiting for scheduled work to settle.
|
|
3
|
+
*
|
|
4
|
+
* A scheduled run used to return the moment CUBE accepted it, which reads as
|
|
5
|
+
* completion and is not: the next line in a script runs against output that
|
|
6
|
+
* does not exist yet, and the only way to sequence work was a sleep, which is
|
|
7
|
+
* a lie about time. A verb that starts work therefore waits for it, and
|
|
8
|
+
* `--detach` is how an operator asks for the handle instead.
|
|
9
|
+
*
|
|
10
|
+
* Waiting holds the LINE, never the session. Progress goes out on the
|
|
11
|
+
* progress channel while the wait runs, so a surface shows the work moving
|
|
12
|
+
* and other panes keep refreshing; a cancellation (Esc at a surface, Ctrl-C
|
|
13
|
+
* at a console) DETACHES rather than kills — the run continues and its
|
|
14
|
+
* handle is printed, which is what `--detach` would have given, asked for
|
|
15
|
+
* later.
|
|
16
|
+
*
|
|
17
|
+
* A pipeline schedules several nodes at once, so a wait is over a SET: it
|
|
18
|
+
* ends when every node it was given has reached a status it does not leave.
|
|
19
|
+
*
|
|
20
|
+
* @module
|
|
21
|
+
*/
|
|
22
|
+
import chalk from 'chalk';
|
|
23
|
+
import { jobs_statusBatch } from '@fnndsc/salsa';
|
|
24
|
+
import { sink_get } from '../../core/sink.js';
|
|
25
|
+
import { commandCancellation_enable, commandCancellation_signalGet } from '../../core/cancellation.js';
|
|
26
|
+
/** Statuses a run does not leave. */
|
|
27
|
+
export const RUN_TERMINAL_STATUSES = new Set([
|
|
28
|
+
'finishedSuccessfully',
|
|
29
|
+
'finishedWithError',
|
|
30
|
+
'cancelled',
|
|
31
|
+
]);
|
|
32
|
+
/** First seconds are polled briskly; a long run is not worth the traffic. */
|
|
33
|
+
const POLL_FAST_MS = 2000;
|
|
34
|
+
/** What the interval settles to once the work is clearly not instant. */
|
|
35
|
+
const POLL_SLOW_MS = 5000;
|
|
36
|
+
/** When the interval stretches. */
|
|
37
|
+
const POLL_BRISK_WINDOW_MS = 30000;
|
|
38
|
+
/** Consecutive unanswered status asks before the wait gives up on knowing. */
|
|
39
|
+
const STATUS_MISSES_MAX = 5;
|
|
40
|
+
/**
|
|
41
|
+
* Sleeps, unless the command is cancelled first.
|
|
42
|
+
*
|
|
43
|
+
* @param ms - How long to wait.
|
|
44
|
+
* @param signal - The foreground command's abort signal, when there is one.
|
|
45
|
+
* @returns True when the sleep completed, false when cancellation cut it short.
|
|
46
|
+
*/
|
|
47
|
+
async function pause_take(ms, signal) {
|
|
48
|
+
if (signal?.aborted === true)
|
|
49
|
+
return false;
|
|
50
|
+
return await new Promise((resolve) => {
|
|
51
|
+
const onAbort = () => {
|
|
52
|
+
clearTimeout(timer);
|
|
53
|
+
resolve(false);
|
|
54
|
+
};
|
|
55
|
+
const timer = setTimeout(() => {
|
|
56
|
+
signal?.removeEventListener('abort', onAbort);
|
|
57
|
+
resolve(true);
|
|
58
|
+
}, ms);
|
|
59
|
+
signal?.addEventListener('abort', onAbort, { once: true });
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Says how the wait is going, on the channel progress travels on.
|
|
64
|
+
*
|
|
65
|
+
* @param label - What to call the work on a surface.
|
|
66
|
+
* @param settled - How many of the runs have finished.
|
|
67
|
+
* @param total - How many runs the wait covers.
|
|
68
|
+
* @param phase - Whether the work is still going, done, or failed.
|
|
69
|
+
*/
|
|
70
|
+
function runProgress_emit(label, settled, total, phase) {
|
|
71
|
+
sink_get().progress_write({
|
|
72
|
+
operation: 'workflow',
|
|
73
|
+
kind: 'workflow',
|
|
74
|
+
phase,
|
|
75
|
+
label,
|
|
76
|
+
current: settled,
|
|
77
|
+
total,
|
|
78
|
+
percent: total > 0 ? Math.min(100, (settled / total) * 100) : undefined,
|
|
79
|
+
unit: 'jobs',
|
|
80
|
+
status: phase === 'working' ? 'running' : (phase === 'complete' ? 'done' : 'error'),
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Counts the runs that have reached a terminal status, and those that failed.
|
|
85
|
+
*
|
|
86
|
+
* @param statuses - Status by instance id.
|
|
87
|
+
* @returns How many are settled, and how many of those did not succeed.
|
|
88
|
+
*/
|
|
89
|
+
function settlement_count(statuses) {
|
|
90
|
+
let settled = 0;
|
|
91
|
+
let failed = 0;
|
|
92
|
+
for (const status of statuses.values()) {
|
|
93
|
+
if (!RUN_TERMINAL_STATUSES.has(status))
|
|
94
|
+
continue;
|
|
95
|
+
settled += 1;
|
|
96
|
+
if (status !== 'finishedSuccessfully')
|
|
97
|
+
failed += 1;
|
|
98
|
+
}
|
|
99
|
+
return { settled, failed };
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Waits for every given run to reach a status it does not leave.
|
|
103
|
+
*
|
|
104
|
+
* @param instanceIDs - The plugin instances to wait on.
|
|
105
|
+
* @param label - What to call the work in progress and readouts.
|
|
106
|
+
* @param cadence - How often to ask, when the default rhythm is wrong.
|
|
107
|
+
* @returns How the wait ended, and the statuses it ended on.
|
|
108
|
+
*/
|
|
109
|
+
export async function runs_awaitSettled(instanceIDs, label, cadence = {}) {
|
|
110
|
+
const started = Date.now();
|
|
111
|
+
const statuses = new Map(instanceIDs.map((id) => [id, 'scheduled']));
|
|
112
|
+
if (instanceIDs.length === 0) {
|
|
113
|
+
return { instanceIDs, statuses, outcome: 'settled', failed: 0, elapsedMs: 0 };
|
|
114
|
+
}
|
|
115
|
+
// A wait is exactly the kind of work a cancellation should reach.
|
|
116
|
+
commandCancellation_enable();
|
|
117
|
+
const signal = commandCancellation_signalGet();
|
|
118
|
+
let misses = 0;
|
|
119
|
+
for (;;) {
|
|
120
|
+
const read = await jobs_statusBatch(instanceIDs);
|
|
121
|
+
if (read.size === 0) {
|
|
122
|
+
// One unanswered ask is a network hiccup, not a verdict on the work.
|
|
123
|
+
misses += 1;
|
|
124
|
+
if (misses >= STATUS_MISSES_MAX) {
|
|
125
|
+
return { instanceIDs, statuses, outcome: 'unreachable', failed: 0, elapsedMs: Date.now() - started };
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
misses = 0;
|
|
130
|
+
for (const [id, status] of read)
|
|
131
|
+
statuses.set(id, status);
|
|
132
|
+
const { settled, failed } = settlement_count(statuses);
|
|
133
|
+
if (settled === instanceIDs.length) {
|
|
134
|
+
runProgress_emit(label, settled, instanceIDs.length, failed > 0 ? 'failed' : 'complete');
|
|
135
|
+
return { instanceIDs, statuses, outcome: 'settled', failed, elapsedMs: Date.now() - started };
|
|
136
|
+
}
|
|
137
|
+
runProgress_emit(label, settled, instanceIDs.length, 'working');
|
|
138
|
+
}
|
|
139
|
+
const elapsed = Date.now() - started;
|
|
140
|
+
const interval = cadence.pollMs
|
|
141
|
+
?? (elapsed < POLL_BRISK_WINDOW_MS ? POLL_FAST_MS : POLL_SLOW_MS);
|
|
142
|
+
const slept = await pause_take(interval, signal);
|
|
143
|
+
if (!slept) {
|
|
144
|
+
const { failed } = settlement_count(statuses);
|
|
145
|
+
return { instanceIDs, statuses, outcome: 'detached', failed, elapsedMs: Date.now() - started };
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Renders how long a wait lasted, the way an operator says it.
|
|
151
|
+
*
|
|
152
|
+
* @param ms - Elapsed milliseconds.
|
|
153
|
+
* @returns A short duration, such as `2m14s`.
|
|
154
|
+
*/
|
|
155
|
+
export function elapsed_render(ms) {
|
|
156
|
+
const seconds = Math.round(ms / 1000);
|
|
157
|
+
if (seconds < 60)
|
|
158
|
+
return `${seconds}s`;
|
|
159
|
+
const minutes = Math.floor(seconds / 60);
|
|
160
|
+
return `${minutes}m${`${seconds % 60}`.padStart(2, '0')}s`;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Renders what a wait found, in the operator's terms.
|
|
164
|
+
*
|
|
165
|
+
* A detached wait says how to get back to the work, because the run is still
|
|
166
|
+
* going and the handle is the only way to reach it.
|
|
167
|
+
*
|
|
168
|
+
* @param settlement - What the wait found.
|
|
169
|
+
* @param label - What the work is called.
|
|
170
|
+
* @returns The line to print.
|
|
171
|
+
*/
|
|
172
|
+
export function settlement_render(settlement, label) {
|
|
173
|
+
const took = elapsed_render(settlement.elapsedMs);
|
|
174
|
+
const handles = settlement.instanceIDs.join(', ');
|
|
175
|
+
if (settlement.outcome === 'detached') {
|
|
176
|
+
return chalk.yellow(`${label}: detached after ${took} — the run continues. Instance ${handles}; follow it with \`plugininstance list\` or the feed's graph.`);
|
|
177
|
+
}
|
|
178
|
+
if (settlement.outcome === 'unreachable') {
|
|
179
|
+
return chalk.yellow(`${label}: lost touch with CUBE after ${took} — the run may still be going. Instance ${handles}.`);
|
|
180
|
+
}
|
|
181
|
+
if (settlement.failed > 0) {
|
|
182
|
+
const failedIds = [...settlement.statuses.entries()]
|
|
183
|
+
.filter(([, status]) => status !== 'finishedSuccessfully')
|
|
184
|
+
.map(([id, status]) => `${id} ${status}`)
|
|
185
|
+
.join(', ');
|
|
186
|
+
return chalk.red(`${label}: finished with errors after ${took} — ${failedIds}`);
|
|
187
|
+
}
|
|
188
|
+
const nodes = settlement.instanceIDs.length === 1
|
|
189
|
+
? `instance ${handles}`
|
|
190
|
+
: `${settlement.instanceIDs.length} nodes`;
|
|
191
|
+
return chalk.green(`${label}: finished successfully in ${took} (${nodes})`);
|
|
192
|
+
}
|
|
193
|
+
//# sourceMappingURL=runWait.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runWait.js","sourceRoot":"","sources":["../../../src/builtins/res/runWait.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Builtin motd.
|
|
3
|
+
*
|
|
4
|
+
* The message of the day: the greeting a session gives the operator who
|
|
5
|
+
* has just arrived. A kernel command, so a TTY and a browser say the same
|
|
6
|
+
* thing — who you are, how many feeds you hold, how many jobs have run
|
|
7
|
+
* and how many of them failed, what is running now — from the session's
|
|
8
|
+
* own index, which costs no call. While the index is still warming the
|
|
9
|
+
* greeting says what it holds so far and says so, rather than keeping the
|
|
10
|
+
* operator waiting for a number.
|
|
11
|
+
*
|
|
12
|
+
* A surface may say where the operator has arrived (`motd ARGUS`); the
|
|
13
|
+
* kernel does not know its surfaces by name and should not.
|
|
14
|
+
*
|
|
15
|
+
* @module
|
|
16
|
+
*/
|
|
17
|
+
import { CommandEnvelope } from '@fnndsc/cumin';
|
|
18
|
+
import { type SessionMotd } from '@fnndsc/menu';
|
|
19
|
+
/**
|
|
20
|
+
* Gathers the greeting's facts from the session's own index.
|
|
21
|
+
*
|
|
22
|
+
* @param user - Who arrived.
|
|
23
|
+
* @param surface - Where, as the surface said it; empty for none.
|
|
24
|
+
* @returns The facts.
|
|
25
|
+
*/
|
|
26
|
+
export declare function motd_gather(user: string, surface: string): SessionMotd;
|
|
27
|
+
/**
|
|
28
|
+
* Renders the greeting.
|
|
29
|
+
*
|
|
30
|
+
* @param motd - The facts.
|
|
31
|
+
* @returns The text, with a trailing newline.
|
|
32
|
+
*/
|
|
33
|
+
export declare function motd_render(motd: SessionMotd): string;
|
|
34
|
+
/**
|
|
35
|
+
* Greets the operator.
|
|
36
|
+
*
|
|
37
|
+
* @param args - Optionally where the operator arrived, e.g. `ARGUS` or
|
|
38
|
+
* `chell`; the word goes into the greeting as given. No flags.
|
|
39
|
+
* @returns An envelope carrying the greeting and the `session.motd` model.
|
|
40
|
+
*/
|
|
41
|
+
export declare function builtin_motd(args: string[]): Promise<CommandEnvelope>;
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Builtin motd.
|
|
3
|
+
*
|
|
4
|
+
* The message of the day: the greeting a session gives the operator who
|
|
5
|
+
* has just arrived. A kernel command, so a TTY and a browser say the same
|
|
6
|
+
* thing — who you are, how many feeds you hold, how many jobs have run
|
|
7
|
+
* and how many of them failed, what is running now — from the session's
|
|
8
|
+
* own index, which costs no call. While the index is still warming the
|
|
9
|
+
* greeting says what it holds so far and says so, rather than keeping the
|
|
10
|
+
* operator waiting for a number.
|
|
11
|
+
*
|
|
12
|
+
* A surface may say where the operator has arrived (`motd ARGUS`); the
|
|
13
|
+
* kernel does not know its surfaces by name and should not.
|
|
14
|
+
*
|
|
15
|
+
* @module
|
|
16
|
+
*/
|
|
17
|
+
import { envelope_ok, procCache_get } from '@fnndsc/cumin';
|
|
18
|
+
import { context_getSingle } from '@fnndsc/salsa';
|
|
19
|
+
import { SESSION_MOTD_MODEL_KIND } from '@fnndsc/menu';
|
|
20
|
+
import { jobsState_derive } from '../../core/jobsState.js';
|
|
21
|
+
import { fortune_random } from './fortune.js';
|
|
22
|
+
/** The most lines a greeting's fortune may run to. */
|
|
23
|
+
const FORTUNE_LINES_MAX = 4;
|
|
24
|
+
/**
|
|
25
|
+
* Says a count the way a person reads one.
|
|
26
|
+
*
|
|
27
|
+
* @param n - The count.
|
|
28
|
+
* @returns The count with thousands separated.
|
|
29
|
+
*/
|
|
30
|
+
function count_say(n) {
|
|
31
|
+
return n.toLocaleString('en-US');
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Says a failure rate as a percentage with one decimal, or as a whole
|
|
35
|
+
* number when it is one.
|
|
36
|
+
*
|
|
37
|
+
* @param rate - 0..1.
|
|
38
|
+
* @returns The percentage, e.g. `1.3 %`.
|
|
39
|
+
*/
|
|
40
|
+
function rate_say(rate) {
|
|
41
|
+
const percent = rate * 100;
|
|
42
|
+
const text = Number.isInteger(percent) ? String(percent) : percent.toFixed(1);
|
|
43
|
+
return `${text} %`;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Gathers the greeting's facts from the session's own index.
|
|
47
|
+
*
|
|
48
|
+
* @param user - Who arrived.
|
|
49
|
+
* @param surface - Where, as the surface said it; empty for none.
|
|
50
|
+
* @returns The facts.
|
|
51
|
+
*/
|
|
52
|
+
export function motd_gather(user, surface) {
|
|
53
|
+
const cache = procCache_get();
|
|
54
|
+
const feeds = cache.feeds_find('');
|
|
55
|
+
const scope = cache.feedScopeCounts_get(user);
|
|
56
|
+
const state = jobsState_derive(feeds);
|
|
57
|
+
let finished = 0;
|
|
58
|
+
let errored = 0;
|
|
59
|
+
let cancelled = 0;
|
|
60
|
+
let total = 0;
|
|
61
|
+
for (const feed of feeds) {
|
|
62
|
+
finished += feed.finishedJobs;
|
|
63
|
+
errored += feed.erroredJobs;
|
|
64
|
+
cancelled += feed.cancelledJobs;
|
|
65
|
+
total += feed.finishedJobs + feed.erroredJobs + feed.cancelledJobs + feed.startedJobs + feed.scheduledJobs + feed.createdJobs;
|
|
66
|
+
}
|
|
67
|
+
const settled = finished + errored;
|
|
68
|
+
const warmup = cache.warmupProgress_get();
|
|
69
|
+
const lifecycle = cache.lifecycle_get().state;
|
|
70
|
+
const index = {
|
|
71
|
+
state: lifecycle === 'current' ? 'current' : warmup.active || warmup.loaded > 0 ? 'warming' : 'cold',
|
|
72
|
+
loaded: warmup.loaded,
|
|
73
|
+
total: warmup.total,
|
|
74
|
+
};
|
|
75
|
+
return {
|
|
76
|
+
user,
|
|
77
|
+
surface,
|
|
78
|
+
feeds: { total: scope.total, own: scope.user, shared: scope.shared, public: scope.public },
|
|
79
|
+
jobs: { total, finished, errored, cancelled, running: state.running, scheduled: state.scheduled },
|
|
80
|
+
failureRate: settled > 0 ? errored / settled : null,
|
|
81
|
+
index,
|
|
82
|
+
fortune: fortune_random(FORTUNE_LINES_MAX),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Renders the greeting.
|
|
87
|
+
*
|
|
88
|
+
* @param motd - The facts.
|
|
89
|
+
* @returns The text, with a trailing newline.
|
|
90
|
+
*/
|
|
91
|
+
export function motd_render(motd) {
|
|
92
|
+
const where = motd.surface.length > 0 ? ` to ${motd.surface}` : '';
|
|
93
|
+
const lines = [`Welcome${where}, ${motd.user}.`];
|
|
94
|
+
const facts = [];
|
|
95
|
+
if (motd.index.state === 'cold' && motd.feeds.total === 0) {
|
|
96
|
+
facts.push('your index is still cold: the feeds and jobs arrive as it warms');
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
facts.push(`${count_say(motd.feeds.total)} feed${motd.feeds.total === 1 ? '' : 's'}`);
|
|
100
|
+
const jobsWord = motd.index.state === 'current'
|
|
101
|
+
? `${count_say(motd.jobs.total)} jobs run`
|
|
102
|
+
: motd.index.total > 0
|
|
103
|
+
? `${count_say(motd.index.loaded)} of ${count_say(motd.index.total)} jobs indexed so far`
|
|
104
|
+
: `${count_say(motd.jobs.total)} jobs indexed so far`;
|
|
105
|
+
facts.push(jobsWord);
|
|
106
|
+
if (motd.failureRate !== null)
|
|
107
|
+
facts.push(`${rate_say(motd.failureRate)} failed`);
|
|
108
|
+
if (motd.jobs.running > 0 || motd.jobs.scheduled > 0) {
|
|
109
|
+
facts.push(`${count_say(motd.jobs.running)} running, ${count_say(motd.jobs.scheduled)} scheduled`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
lines.push(facts.join(' · '));
|
|
113
|
+
if (motd.fortune.length > 0) {
|
|
114
|
+
lines.push('');
|
|
115
|
+
lines.push(motd.fortune);
|
|
116
|
+
}
|
|
117
|
+
return `${lines.join('\n')}\n`;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Greets the operator.
|
|
121
|
+
*
|
|
122
|
+
* @param args - Optionally where the operator arrived, e.g. `ARGUS` or
|
|
123
|
+
* `chell`; the word goes into the greeting as given. No flags.
|
|
124
|
+
* @returns An envelope carrying the greeting and the `session.motd` model.
|
|
125
|
+
*/
|
|
126
|
+
export async function builtin_motd(args) {
|
|
127
|
+
const flag = args.find((arg) => arg.startsWith('-'));
|
|
128
|
+
if (flag !== undefined) {
|
|
129
|
+
return envelope_ok(`motd: unknown option '${flag}' (usage: motd [surface])\n`);
|
|
130
|
+
}
|
|
131
|
+
const context = await context_getSingle();
|
|
132
|
+
const user = context.user ?? '';
|
|
133
|
+
if (user.length === 0) {
|
|
134
|
+
return envelope_ok('Welcome. You are not connected: connect --user <user> --password <pwd> <url>\n');
|
|
135
|
+
}
|
|
136
|
+
const motd = motd_gather(user, args.join(' '));
|
|
137
|
+
return envelope_ok(motd_render(motd), { kind: SESSION_MOTD_MODEL_KIND, data: motd });
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=motd.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"motd.js","sourceRoot":"","sources":["../../../src/builtins/sys/motd.ts"],"names":[],"mappings":"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"}
|