@rse/ase 2.0.1 → 2.0.3
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/dst/ase-agent-definition-core.js +111 -87
- package/dst/ase-agent-fleet.js +1 -1
- package/dst/ase-agent-runtime-claim.js +5 -3
- package/dst/ase-agent-runtime-engine.js +1 -1
- package/dst/ase-agent-runtime-phase.js +1 -1
- package/dst/ase-agent-runtime-request.js +1 -1
- package/dst/ase-agent-runtime.js +3 -5
- package/dst/ase-artifact.js +3 -3
- package/dst/ase-config-core.js +2 -16
- package/dst/ase-config-schema.js +1 -3
- package/dst/ase-config.js +4 -1
- package/dst/ase-hook.js +14 -19
- package/dst/ase-lib-daemon-cli.js +117 -4
- package/dst/ase-lib-server-cli.js +10 -10
- package/dst/ase-mcp.js +4 -4
- package/dst/ase-service-getopt.js +2 -2
- package/dst/ase-service-otel.js +4 -4
- package/dst/ase-service-skills.js +5 -15
- package/dst/ase-service.js +87 -210
- package/dst/ase-setup-common.js +1 -2
- package/dst/ase-setup-settings.js +6 -9
- package/dst/ase-setup.js +2 -3
- package/dst/ase-statusline.js +170 -170
- package/dst/ase-task-board-core.js +1 -1
- package/dst/ase-task-board-flow.js +7 -6
- package/dst/ase-task-board-graph.js +5 -4
- package/dst/ase-task-board-tui-control.js +51 -45
- package/dst/ase-task-board-tui-model.js +195 -181
- package/dst/ase-task-board-tui-popup.js +2 -3
- package/dst/ase-task-board-tui-view.js +88 -79
- package/dst/ase-task-board-web-client.css +1 -1
- package/dst/ase-task-board-web-client.js +2 -2
- package/dst/ase-task-board-web-server.js +4 -27
- package/dst/ase-task-board.js +1 -1
- package/dst/ase-task-format.js +1 -1
- package/dst/ase-task-store-client.js +17 -5
- package/dst/ase-task-store-core.js +5 -7
- package/dst/ase-task-store-issues.js +91 -0
- package/dst/ase-task-store-plugin-ase.js +7 -3
- package/dst/ase-task-store-plugin-gitea.js +11 -97
- package/dst/ase-task-store-plugin-github.js +5 -77
- package/dst/ase-task-store-plugin-gitlab.js +4 -83
- package/dst/ase-task.js +51 -23
- package/dst/ase-util-compat.js +1 -1
- package/dst/ase-util-diagram.js +16 -42
- package/dst/ase.js +5 -5
- package/dst/man/ase-agent-adhoc.1 +1 -1
- package/dst/man/ase-agent-condenser.1 +1 -1
- package/dst/man/ase-agent-editor.1 +1 -1
- package/dst/man/ase-agent-estimator.1 +1 -1
- package/dst/man/ase-agent-fleet.1 +1 -1
- package/dst/man/ase-agent-implementer.1 +1 -1
- package/dst/man/ase-agent-integrator.1 +1 -1
- package/dst/man/ase-agent-qualifier.1 +1 -1
- package/dst/man/ase-agent-rebooter.1 +1 -1
- package/dst/man/ase-agent-relay.1 +1 -1
- package/dst/man/ase-agent-researcher.1 +1 -1
- package/dst/man/ase-agent.1 +1 -1
- package/dst/man/ase-artifact-list.1 +1 -1
- package/dst/man/ase-artifact-name.1 +1 -1
- package/dst/man/ase-artifact.1 +1 -1
- package/dst/man/ase-config-delete.1 +1 -1
- package/dst/man/ase-config-edit.1 +1 -1
- package/dst/man/ase-config-get.1 +1 -1
- package/dst/man/ase-config-init.1 +1 -1
- package/dst/man/ase-config-list.1 +1 -1
- package/dst/man/ase-config-set.1 +1 -1
- package/dst/man/ase-config.1 +1 -1
- package/dst/man/ase-hook-permission-request.1 +1 -1
- package/dst/man/ase-hook-pre-tool-use.1 +1 -1
- package/dst/man/ase-hook-session-end.1 +1 -1
- package/dst/man/ase-hook-session-persona.1 +1 -1
- package/dst/man/ase-hook-session-start.1 +1 -1
- package/dst/man/ase-hook-stop.1 +1 -1
- package/dst/man/ase-hook-user-prompt-submit.1 +1 -1
- package/dst/man/ase-hook.1 +1 -1
- package/dst/man/ase-man.1 +1 -1
- package/dst/man/ase-mcp.1 +1 -1
- package/dst/man/ase-service-log.1 +1 -1
- package/dst/man/ase-service-send.1 +1 -1
- package/dst/man/ase-service-start.1 +1 -1
- package/dst/man/ase-service-status.1 +1 -1
- package/dst/man/ase-service-stop.1 +1 -1
- package/dst/man/ase-service.1 +1 -1
- package/dst/man/ase-setup-disable.1 +1 -1
- package/dst/man/ase-setup-enable.1 +1 -1
- package/dst/man/ase-setup-install.1 +1 -1
- package/dst/man/ase-setup-mcp.1 +1 -1
- package/dst/man/ase-setup-status.1 +1 -1
- package/dst/man/ase-setup-statusline.1 +1 -1
- package/dst/man/ase-setup-telemetry.1 +1 -1
- package/dst/man/ase-setup-uninstall.1 +1 -1
- package/dst/man/ase-setup-update.1 +1 -1
- package/dst/man/ase-setup.1 +1 -1
- package/dst/man/ase-spec-export.1 +1 -1
- package/dst/man/ase-spec-lint.1 +1 -1
- package/dst/man/ase-spec-preview.1 +1 -1
- package/dst/man/ase-spec.1 +1 -1
- package/dst/man/ase-statusline.1 +1 -1
- package/dst/man/ase-task-assign.1 +1 -1
- package/dst/man/ase-task-board.1 +3 -3
- package/dst/man/ase-task-delete.1 +1 -1
- package/dst/man/ase-task-edit.1 +1 -1
- package/dst/man/ase-task-idscheme.1 +1 -1
- package/dst/man/ase-task-lifecycle.1 +1 -1
- package/dst/man/ase-task-list.1 +1 -1
- package/dst/man/ase-task-load.1 +1 -1
- package/dst/man/ase-task-newid.1 +1 -1
- package/dst/man/ase-task-purge.1 +1 -1
- package/dst/man/ase-task-rename.1 +1 -1
- package/dst/man/ase-task-request.1 +1 -1
- package/dst/man/ase-task-save.1 +1 -1
- package/dst/man/ase-task-status.1 +1 -1
- package/dst/man/ase-task-store.1 +1 -1
- package/dst/man/ase-task-view.1 +1 -1
- package/dst/man/ase-task.1 +1 -1
- package/dst/man/ase-util-compat.1 +1 -1
- package/dst/man/ase-util-diagram.1 +1 -1
- package/dst/man/ase-util-meta.1 +1 -1
- package/dst/man/ase-util-metric.1 +1 -1
- package/dst/man/ase-util-mint.1 +1 -1
- package/dst/man/ase-util-worktree.1 +1 -1
- package/dst/man/ase-util.1 +1 -1
- package/dst/man/ase.1 +1 -1
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/.github/plugin/plugin.json +1 -1
- package/plugin/meta/ase-common-resolve.md +2 -2
- package/plugin/meta/ase-skill.md +7 -0
- package/plugin/mods/ase-mods.ts +16 -12
- package/plugin/skills/ase-arch-analyze/SKILL.md +3 -1
- package/plugin/skills/ase-code-analyze/SKILL.md +26 -10
- package/plugin/skills/ase-code-analyze/help.md +11 -6
- package/plugin/skills/ase-code-edit/SKILL.md +5 -4
- package/plugin/skills/ase-code-edit/help.md +1 -1
- package/plugin/skills/ase-code-lint/SKILL.md +52 -27
- package/plugin/skills/ase-code-lint/help.md +10 -5
- package/plugin/skills/ase-help-intent/data.md +60 -30
- package/plugin/skills/ase-help-skill/help.md +3 -4
- package/plugin/skills/ase-meta-quorum/SKILL.md +9 -0
- package/plugin/skills/ase-meta-quorum/help.md +5 -3
- package/plugin/skills/ase-meta-workflow/sample.md +5 -3
- package/plugin/skills/ase-repo-changelog/SKILL.md +8 -9
- package/plugin/skills/ase-repo-changelog/help.md +2 -2
- package/plugin/skills/ase-repo-review/SKILL.md +5 -3
- package/plugin/skills/ase-repo-review/help.md +3 -1
- package/plugin/skills/ase-spec-edit/SKILL.md +2 -1
- package/plugin/skills/ase-spec-edit/help.md +2 -1
- package/plugin/skills/ase-sync-reconcile/help.md +4 -4
- package/plugin/skills/ase-task-dissect/SKILL.md +10 -6
- package/plugin/skills/ase-task-grill/help.md +1 -1
- package/plugin/skills/ase-task-implement/SKILL.md +58 -1
- package/plugin/skills/ase-task-implement/help.md +16 -0
- package/plugin/skills/ase-task-list/help.md +2 -2
|
@@ -5,9 +5,122 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import fs from "node:fs";
|
|
8
|
+
import net from "node:net";
|
|
8
9
|
import { fileURLToPath } from "node:url";
|
|
9
10
|
import { spawn } from "node:child_process";
|
|
10
|
-
import {
|
|
11
|
+
import { Tail } from "tail";
|
|
12
|
+
/* the loopback host to probe the ports on, the range and the number of attempts
|
|
13
|
+
of the random port allocation, and the bounds of the append-only log files:
|
|
14
|
+
maximum tolerated size and number of tail lines surviving a trim */
|
|
15
|
+
const HOST = "127.0.0.1";
|
|
16
|
+
const PORT_MIN = 42000;
|
|
17
|
+
const PORT_MAX = 44000;
|
|
18
|
+
const PORT_TRIES = 20;
|
|
19
|
+
const LOG_MAX_SIZE = 1024 * 1024;
|
|
20
|
+
const LOG_KEEP_LINES = 2000;
|
|
21
|
+
/* reusable functionality: port allocation and log file handling of the background processes */
|
|
22
|
+
export class Daemon {
|
|
23
|
+
/* try binding a single candidate port to verify availability */
|
|
24
|
+
static tryBind(port) {
|
|
25
|
+
return new Promise((resolve) => {
|
|
26
|
+
const s = net.createServer();
|
|
27
|
+
s.once("error", () => {
|
|
28
|
+
resolve(false);
|
|
29
|
+
});
|
|
30
|
+
s.once("listening", () => {
|
|
31
|
+
s.close(() => resolve(true));
|
|
32
|
+
});
|
|
33
|
+
s.listen(port, HOST);
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
/* allocate the preferred port, if given and available,
|
|
37
|
+
else a fresh random port in PORT_MIN..PORT_MAX */
|
|
38
|
+
static async allocatePort(pref = null) {
|
|
39
|
+
if (pref !== null && await Daemon.tryBind(pref))
|
|
40
|
+
return pref;
|
|
41
|
+
for (let i = 0; i < PORT_TRIES; i++) {
|
|
42
|
+
const p = PORT_MIN + Math.floor(Math.random() * (PORT_MAX - PORT_MIN + 1));
|
|
43
|
+
if (await Daemon.tryBind(p))
|
|
44
|
+
return p;
|
|
45
|
+
}
|
|
46
|
+
throw new Error(`failed to allocate a port in ${PORT_MIN}..${PORT_MAX} after ${PORT_TRIES} attempts`);
|
|
47
|
+
}
|
|
48
|
+
/* trim the log file down to its tail if it grew beyond the size limit */
|
|
49
|
+
static trimLog(logFile) {
|
|
50
|
+
try {
|
|
51
|
+
if (!fs.existsSync(logFile) || fs.statSync(logFile).size <= LOG_MAX_SIZE)
|
|
52
|
+
return;
|
|
53
|
+
const tail = Daemon.readLogTail(logFile, LOG_KEEP_LINES);
|
|
54
|
+
fs.writeFileSync(logFile, tail.length > 0 ? `${tail}\n` : "");
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
/* intentionally ignore all trimming errors, as a
|
|
58
|
+
non-trimmable log file must never block the service */
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/* read the last N non-empty lines of a log file for diagnostics */
|
|
62
|
+
static readLogTail(logFile, lines) {
|
|
63
|
+
let fd = null;
|
|
64
|
+
try {
|
|
65
|
+
fd = fs.openSync(logFile, "r");
|
|
66
|
+
const size = fs.fstatSync(fd).size;
|
|
67
|
+
const CHUNK = 8192;
|
|
68
|
+
const chunks = [];
|
|
69
|
+
let pos = size;
|
|
70
|
+
let count = 0;
|
|
71
|
+
while (pos > 0 && count <= lines) {
|
|
72
|
+
const n = Math.min(CHUNK, pos);
|
|
73
|
+
const buf = Buffer.alloc(n);
|
|
74
|
+
pos -= n;
|
|
75
|
+
fs.readSync(fd, buf, 0, n, pos);
|
|
76
|
+
chunks.unshift(buf);
|
|
77
|
+
for (let i = 0; i < n; i++)
|
|
78
|
+
if (buf[i] === 10)
|
|
79
|
+
count++;
|
|
80
|
+
}
|
|
81
|
+
/* decode once after re-assembly to avoid splitting
|
|
82
|
+
multi-byte UTF-8 sequences at chunk boundaries */
|
|
83
|
+
const all = Buffer.concat(chunks).toString("utf8")
|
|
84
|
+
.split("\n").filter((l) => l.length > 0);
|
|
85
|
+
return all.slice(-lines).join("\n");
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return "";
|
|
89
|
+
}
|
|
90
|
+
finally {
|
|
91
|
+
if (fd !== null)
|
|
92
|
+
fs.closeSync(fd);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/* show a log file on stdout, either completely or its last <lines> lines only, and
|
|
96
|
+
optionally follow it for newly appended lines (default: the last 10 lines first),
|
|
97
|
+
where each line is passed through <transform> (e.g. to colorize parts of it) */
|
|
98
|
+
static async showLog(logFile, opts, transform = (line) => line) {
|
|
99
|
+
const lines = opts.lines !== undefined ? Number(opts.lines) : null;
|
|
100
|
+
if (lines !== null && (!Number.isInteger(lines) || lines < 1))
|
|
101
|
+
throw new Error(`invalid number of lines: ${opts.lines}`);
|
|
102
|
+
if (!fs.existsSync(logFile))
|
|
103
|
+
return 0;
|
|
104
|
+
if (opts.follow) {
|
|
105
|
+
/* follow until interrupted (on log trimming,
|
|
106
|
+
"tail" continues at the new end of the file) */
|
|
107
|
+
const tail = new Tail(logFile, { nLines: lines ?? 10 });
|
|
108
|
+
tail.on("line", (line) => {
|
|
109
|
+
process.stdout.write(`${transform(line)}\n`);
|
|
110
|
+
});
|
|
111
|
+
return await new Promise((_resolve, reject) => {
|
|
112
|
+
tail.on("error", (err) => {
|
|
113
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
const text = lines !== null ?
|
|
118
|
+
Daemon.readLogTail(logFile, lines) :
|
|
119
|
+
fs.readFileSync(logFile, "utf8").replace(/\n$/, "");
|
|
120
|
+
process.stdout.write(text.length > 0 ? `${text.split("\n").map(transform).join("\n")}\n` : "");
|
|
121
|
+
return 0;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
11
124
|
/* the CLI command group managing a long-running process (a server, an agent, or a fleet
|
|
12
125
|
of them) in the background or in the foreground: it registers the command group, spawns
|
|
13
126
|
the process detached into its log file, awaits its startup, checks whether it is alive,
|
|
@@ -47,14 +160,14 @@ export class DaemonCommand {
|
|
|
47
160
|
"(not yet run in the background, as one run in the foreground logs to the terminal)");
|
|
48
161
|
return;
|
|
49
162
|
}
|
|
50
|
-
process.exitCode = await
|
|
163
|
+
process.exitCode = await Daemon.showLog(file, opts, transform);
|
|
51
164
|
});
|
|
52
165
|
}
|
|
53
166
|
/* spawn the CLI with the arguments <args> detached, with the additional
|
|
54
167
|
environment <env>, logging into the (trimmed) log file <logFile> */
|
|
55
168
|
spawnDetached(args, env, logFile) {
|
|
56
169
|
fs.mkdirSync(path.dirname(logFile), { recursive: true });
|
|
57
|
-
|
|
170
|
+
Daemon.trimLog(logFile);
|
|
58
171
|
const fd = fs.openSync(logFile, "a");
|
|
59
172
|
const entry = fileURLToPath(new URL("./ase.js", import.meta.url));
|
|
60
173
|
const child = spawn(process.execPath, [entry, ...args], {
|
|
@@ -95,7 +208,7 @@ export class DaemonCommand {
|
|
|
95
208
|
if (failure === null)
|
|
96
209
|
return;
|
|
97
210
|
const reason = `${this.label}: ${failure}`;
|
|
98
|
-
const tail =
|
|
211
|
+
const tail = Daemon.readLogTail(logFile, 20);
|
|
99
212
|
throw new Error(tail.length > 0 ? `${reason}\n---- ${logFile} (tail) ----\n${tail}` : reason);
|
|
100
213
|
}
|
|
101
214
|
/* check whether a process is still alive */
|
|
@@ -13,8 +13,8 @@ import { isSeq, isScalar } from "yaml";
|
|
|
13
13
|
import { Config } from "./ase-config-core.js";
|
|
14
14
|
import { parseScope } from "./ase-config-scope.js";
|
|
15
15
|
import { isLogLevel } from "./ase-lib-log.js";
|
|
16
|
-
import {
|
|
17
|
-
import { DaemonCommand } from "./ase-lib-daemon-cli.js";
|
|
16
|
+
import { writeStdout } from "./ase-lib-stdio.js";
|
|
17
|
+
import { Daemon, DaemonCommand } from "./ase-lib-daemon-cli.js";
|
|
18
18
|
import { urlHost, isLoopback, serverHeader } from "./ase-lib-server-bind.js";
|
|
19
19
|
/* the defaults */
|
|
20
20
|
export const DEFAULT_ADDRESS = "127.0.0.1";
|
|
@@ -44,7 +44,7 @@ export const configuredTls = (cfg, prefix) => {
|
|
|
44
44
|
if (isLocal(new URL(url.value).hostname.replace(/^\[|\]$/g, "")))
|
|
45
45
|
return { cert: null, key: null };
|
|
46
46
|
}
|
|
47
|
-
catch
|
|
47
|
+
catch {
|
|
48
48
|
/* an invalid URL is reported by its users */
|
|
49
49
|
}
|
|
50
50
|
}
|
|
@@ -270,7 +270,7 @@ export class ServerCommand extends DaemonCommand {
|
|
|
270
270
|
child.kill("SIGTERM");
|
|
271
271
|
child.unref();
|
|
272
272
|
}
|
|
273
|
-
const tail =
|
|
273
|
+
const tail = Daemon.readLogTail(logFile, 20);
|
|
274
274
|
const reason = exited ?
|
|
275
275
|
`${this.label}: server exited during startup (code ${exitCode})` :
|
|
276
276
|
probed === "foreign" ?
|
|
@@ -351,7 +351,7 @@ export class ServerCommand extends DaemonCommand {
|
|
|
351
351
|
/* re-allocate a configured port held by a foreign service */
|
|
352
352
|
if (foreign)
|
|
353
353
|
this.log.write("warning", `${this.label}: configured port ${port} in use by foreign service, re-allocating`);
|
|
354
|
-
port = await
|
|
354
|
+
port = await Daemon.allocatePort();
|
|
355
355
|
}
|
|
356
356
|
let token = opts.token ?? process.env[this.env("TOKEN")] ?? ctx.token;
|
|
357
357
|
if (token === undefined || token === null || token === "")
|
|
@@ -381,26 +381,26 @@ export class ServerCommand extends DaemonCommand {
|
|
|
381
381
|
async doStatus() {
|
|
382
382
|
const ctx = this.loadContext();
|
|
383
383
|
if (ctx.port === null) {
|
|
384
|
-
|
|
384
|
+
await writeStdout(`${this.label}: not running (no port configured)\n`);
|
|
385
385
|
return 1;
|
|
386
386
|
}
|
|
387
387
|
const address = ctx.address ?? DEFAULT_ADDRESS;
|
|
388
388
|
const scheme = ctx.tlsCert !== null ? "https" : "http";
|
|
389
389
|
const state = await this.probe(address, ctx.port, ctx.token, ctx.tlsCert !== null);
|
|
390
390
|
if (state === "running") {
|
|
391
|
-
|
|
391
|
+
await writeStdout(`${this.label}: running on ${scheme}://${urlHost(address)}:${ctx.port}` +
|
|
392
392
|
(ctx.pid !== null ? ` (pid ${ctx.pid})` : "") + this.running(ctx) + "\n");
|
|
393
393
|
return 0;
|
|
394
394
|
}
|
|
395
395
|
if (state === "unauthorized") {
|
|
396
|
-
|
|
396
|
+
await writeStdout(`${this.label}: running on ${scheme}://${urlHost(address)}:${ctx.port}, but the configured token is rejected\n`);
|
|
397
397
|
return 1;
|
|
398
398
|
}
|
|
399
399
|
if (state === "foreign") {
|
|
400
|
-
|
|
400
|
+
await writeStdout(`${this.label}: not running (port ${ctx.port} in use by foreign service)\n`);
|
|
401
401
|
return 1;
|
|
402
402
|
}
|
|
403
|
-
|
|
403
|
+
await writeStdout(`${this.label}: not running (port ${ctx.port} not responding)\n`);
|
|
404
404
|
return 1;
|
|
405
405
|
}
|
|
406
406
|
/* stop flow: terminate the server process and wait until it is gone */
|
package/dst/ase-mcp.js
CHANGED
|
@@ -72,7 +72,7 @@ export default class MCPCommand {
|
|
|
72
72
|
const value = state.getExplicit("basedir");
|
|
73
73
|
return typeof value === "string" && value !== "" ? value : null;
|
|
74
74
|
}
|
|
75
|
-
catch
|
|
75
|
+
catch {
|
|
76
76
|
/* best-effort: an unreadable session config just defers the lookup */
|
|
77
77
|
return null;
|
|
78
78
|
}
|
|
@@ -139,7 +139,7 @@ export default class MCPCommand {
|
|
|
139
139
|
next.onerror = (err) => {
|
|
140
140
|
this.log.write("error", `mcp: http: ${err.message}`);
|
|
141
141
|
};
|
|
142
|
-
/* service closed the connection
|
|
142
|
+
/* service closed the connection -- try to recover */
|
|
143
143
|
next.onclose = () => {
|
|
144
144
|
if (client !== next || closedByUs.has(next) || bridgeDone || reconnecting)
|
|
145
145
|
return;
|
|
@@ -183,7 +183,7 @@ export default class MCPCommand {
|
|
|
183
183
|
if (reconnecting)
|
|
184
184
|
return;
|
|
185
185
|
reconnecting = true;
|
|
186
|
-
this.log.write("warning", `mcp: ${reason}
|
|
186
|
+
this.log.write("warning", `mcp: ${reason} -- reconnecting`);
|
|
187
187
|
reconnect(0).catch(() => { });
|
|
188
188
|
};
|
|
189
189
|
/* bounded buffer for messages arriving while no HTTP client is connected */
|
|
@@ -315,7 +315,7 @@ export default class MCPCommand {
|
|
|
315
315
|
await connectClient();
|
|
316
316
|
}
|
|
317
317
|
catch (err) {
|
|
318
|
-
/* service vanished between probe and connect
|
|
318
|
+
/* service vanished between probe and connect -- recover instead of crashing */
|
|
319
319
|
triggerReconnect(`initial connect failed: ${this.asError(err).message}`);
|
|
320
320
|
}
|
|
321
321
|
}
|
|
@@ -23,7 +23,7 @@ const tokenizeRanges = (input) => {
|
|
|
23
23
|
const quote = ch;
|
|
24
24
|
i++;
|
|
25
25
|
while (i < input.length && input[i] !== quote) {
|
|
26
|
-
if (input[i] === "\\" && i + 1 < input.length)
|
|
26
|
+
if (quote === "\"" && input[i] === "\\" && i + 1 < input.length)
|
|
27
27
|
i++;
|
|
28
28
|
i++;
|
|
29
29
|
}
|
|
@@ -50,7 +50,7 @@ const unquote = (s) => {
|
|
|
50
50
|
const quote = ch;
|
|
51
51
|
j++;
|
|
52
52
|
while (j < s.length && s[j] !== quote) {
|
|
53
|
-
if (s[j] === "\\" && j + 1 < s.length) {
|
|
53
|
+
if (quote === "\"" && s[j] === "\\" && j + 1 < s.length) {
|
|
54
54
|
out += s[j + 1];
|
|
55
55
|
j += 2;
|
|
56
56
|
}
|
package/dst/ase-service-otel.js
CHANGED
|
@@ -580,7 +580,7 @@ export class UsageTracker {
|
|
|
580
580
|
const self = userId();
|
|
581
581
|
if (assignee.trim() === "")
|
|
582
582
|
throw new Error("usage: empty assignee (use \"none\" to unassign)");
|
|
583
|
-
const to = assignee === "me" ? self : assignee.trim();
|
|
583
|
+
const to = assignee.trim() === "me" ? self : assignee.trim();
|
|
584
584
|
/* (keep the session out of the adoptions meanwhile, and let an assignment to oneself
|
|
585
585
|
settle the adoption of its active task, as the explicit assignment takes precedence) */
|
|
586
586
|
this.explicit.add(session);
|
|
@@ -591,6 +591,7 @@ export class UsageTracker {
|
|
|
591
591
|
this.explicit.delete(session);
|
|
592
592
|
}
|
|
593
593
|
}
|
|
594
|
+
/* perform the assignment of "assign" (with the session kept out of the adoptions) */
|
|
594
595
|
async assignSession(id, to, self, session) {
|
|
595
596
|
const warning = await Task.checkAssignee(this.log, to);
|
|
596
597
|
const from = await Task.assign(this.log, id, to === "none" ? null : to);
|
|
@@ -952,6 +953,7 @@ export class UsageTracker {
|
|
|
952
953
|
this.unwatch = null;
|
|
953
954
|
}
|
|
954
955
|
}
|
|
956
|
+
/* close resp. implicitly account the open windows of the watched tasks whose assignee changed elsewhere */
|
|
955
957
|
async check() {
|
|
956
958
|
for (const task of Array.from(this.watched)) {
|
|
957
959
|
const w = this.openWindow(task);
|
|
@@ -1212,9 +1214,7 @@ export class UsageTracker {
|
|
|
1212
1214
|
(dropping the records of unregistered sessions), with the session registry
|
|
1213
1215
|
re-read from the telemetry state file only once the file changed */
|
|
1214
1216
|
route(records) {
|
|
1215
|
-
const bySession =
|
|
1216
|
-
for (const r of records)
|
|
1217
|
-
bySession.set(r.session, [...(bySession.get(r.session) ?? []), r]);
|
|
1217
|
+
const bySession = Map.groupBy(records, (r) => r.session);
|
|
1218
1218
|
let registry;
|
|
1219
1219
|
try {
|
|
1220
1220
|
const file = UsageTracker.stateFile();
|
|
@@ -187,7 +187,8 @@ export class Skills {
|
|
|
187
187
|
if (m === null)
|
|
188
188
|
return "N.A.";
|
|
189
189
|
const base = parseFloat(m[1].replace(/,/g, ""));
|
|
190
|
-
const
|
|
190
|
+
const unit = m[2].toUpperCase();
|
|
191
|
+
const mult = unit === "K" ? 1_000 : unit === "M" ? 1_000_000 : unit === "B" ? 1_000_000_000 : 1;
|
|
191
192
|
const n = Math.round(base * mult);
|
|
192
193
|
return Number.isFinite(n) ? n : "N.A.";
|
|
193
194
|
}
|
|
@@ -246,20 +247,9 @@ export class Skills {
|
|
|
246
247
|
const now = Date.now();
|
|
247
248
|
const msPerDay = 1000 * 60 * 60 * 24;
|
|
248
249
|
const halfLife = 365 / 2;
|
|
249
|
-
/*
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
remaining metrics, instead of collapsing the product to zero.
|
|
253
|
-
For `lifespan` (an unbounded duration) the multiplicative-neutral
|
|
254
|
-
`1` is also the low floor, so a missing value cannot unfairly
|
|
255
|
-
inflate the rank. For `recentness`, however, the value is an
|
|
256
|
-
exp-decay factor bounded in `(0,1]` where `1` is the *ceiling*
|
|
257
|
-
(a just-now update), not a floor -- defaulting a missing update
|
|
258
|
-
date to `1` would hand packages with absent metadata the highest
|
|
259
|
-
possible recentness and let them outrank genuinely fresh ones.
|
|
260
|
-
We therefore default a missing `recentness` to the decay value at
|
|
261
|
-
one half-life (`0.5`), a conservative midpoint that keeps the
|
|
262
|
-
entry rankable without rewarding the missing date. */
|
|
250
|
+
/* an unavailable date-derived factor gets a neutral default: a missing lifespan
|
|
251
|
+
its floor "1", a missing recentness (whose ceiling is "1") the decay value at
|
|
252
|
+
one half-life ("0.5"), so a missing date is never rewarded */
|
|
263
253
|
const lifespan = (!Number.isNaN(cMs) && !Number.isNaN(uMs)) ? Math.max(1, uMs - cMs) : 1;
|
|
264
254
|
const recentness = !Number.isNaN(uMs) ? 0.5 ** (Math.max(0, (now - uMs) / msPerDay) / halfLife) : 0.5;
|
|
265
255
|
let rank = d * s * lifespan * recentness;
|