agentlas 0.4.0 → 0.5.5
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/README.md +112 -23
- package/bin/agentlas.cjs +55 -8
- package/engine/agentlas-api-agent.cjs +1 -1
- package/engine/agentlas-banner.cjs +66 -51
- package/engine/agentlas-capabilities.cjs +3 -0
- package/engine/agentlas-cloud-runtime.cjs +65 -11
- package/engine/agentlas-composer.cjs +109 -44
- package/engine/agentlas-doctor.cjs +65 -14
- package/engine/agentlas-i18n.cjs +132 -12
- package/engine/agentlas-input.cjs +123 -19
- package/engine/agentlas-native-host.cjs +381 -83
- package/engine/agentlas-parity.cjs +373 -53
- package/engine/agentlas-permissions.cjs +90 -0
- package/engine/agentlas-repl.cjs +149 -47
- package/engine/agentlas-tasks.cjs +111 -0
- package/engine/agentlas-tools.cjs +174 -12
- package/engine/agentlas-ui.cjs +349 -24
- package/engine/agentlas.cjs +3074 -379
- package/engine/architecture.data.json +5 -1
- package/engine/semver.cjs +64 -0
- package/package.json +1 -1
- package/test/bootstrap-race.cjs +47 -0
- package/test/capture-runtime-guard.cjs +122 -0
- package/test/cloud-asset-restore.cjs +423 -0
- package/test/cloud-cas-client.cjs +333 -0
- package/test/cloud-owner-restore.cjs +183 -0
- package/test/cloud-runtime-paths.cjs +40 -0
- package/test/cloud-save-publish.cjs +453 -0
- package/test/credential-env-regression.cjs +52 -0
- package/test/login-loopback-security.cjs +115 -0
- package/test/mcp-config-isolation.cjs +36 -0
- package/test/permission-mapping.cjs +180 -0
- package/test/run-api-regression.cjs +322 -0
- package/test/runtime-env-protection.cjs +45 -0
- package/test/semver-precedence.cjs +39 -0
- package/test/smoke.sh +33 -0
- package/test/sqlite-driver-probe.cjs +22 -0
- package/test/terminal-ui-regression.cjs +454 -0
- package/test/timeout-regression.cjs +218 -0
- package/test/tool-workspace-boundary.cjs +165 -0
- package/test/update-safety.cjs +376 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "1.5.
|
|
2
|
+
"version": "1.5.35",
|
|
3
3
|
"emitterBlock": "## Memory (Agentlas curated memory)\n\nIf — and only if — this turn produced something durable (a decision, a stable fact,\na user preference, a risk, a reusable procedure), end your reply with a Memory Events\nblock. Emit nothing when nothing durable was learned.\n\nRules:\n- Never include secrets, credentials, API keys, raw logs, or full transcripts.\n- Real credential values may live only in local project .env/.env.local,\n ignored signing/ or credentials/ files, or a local keychain/vault. Memory\n Events may mention env names and local relative paths only.\n- For deploy, release, store, billing, auth, API, or cloud work, first read the\n project's .agentlas/local-credentials.map.json and the top\n \"Local Credential Index\" section of .agentlas/project-soul-memory.md\n before saying a credential is missing.\n- One event per durable item. Keep \"content\" to one or two sentences.\n- \"memory_kind\": fact | decision | preference | risk | procedure | hypothesis | evidence | deprecation | conflict\n- \"suggested_scope\": user_identity | team_memory | project (this folder) | agent_repo | session (temporary) | discard\n- \"agent_team\" is accepted only as a legacy alias for team_memory.\n- Add \"request_context\" when it improves future recall: user_intent, trigger_terms,\n cwd_at_request, target_project, target_path, cross_context, outcome.\n- Never put the raw user prompt or transcript in request_context.\n- Suggest a scope; the Memory Curator decides the final destination.\n\nFormat (omit entirely if empty):\n\n## Memory Events\n```json\n[\n {\n \"memory_kind\": \"decision\",\n \"content\": \"...\",\n \"suggested_scope\": \"project\",\n \"confidence\": \"high\",\n \"evidence_refs\": [],\n \"request_context\": {\n \"user_intent\": \"...\",\n \"trigger_terms\": [\"...\"],\n \"cwd_at_request\": null,\n \"target_project\": null,\n \"target_path\": null,\n \"cross_context\": false,\n \"outcome\": \"...\"\n }\n }\n]\n```",
|
|
4
4
|
"eventsHeading": "## Memory Events",
|
|
5
5
|
"memoryDir": ".agentlas",
|
|
@@ -14,6 +14,10 @@
|
|
|
14
14
|
"skillRegistryFile": "skill-registry.json",
|
|
15
15
|
"skillTrialsFile": "skill-trials.jsonl",
|
|
16
16
|
"curatorDecisionsFile": "curator-decisions.jsonl",
|
|
17
|
+
"careerGraphConfigFile": "career-graph.json",
|
|
18
|
+
"careerGraphSourceManifestFile": "career-graph-sources.json",
|
|
19
|
+
"careerGraphInboxDir": "career-graph-inbox",
|
|
20
|
+
"careerGraphDbFile": "career-graph.sqlite",
|
|
17
21
|
"superOntologyContractFile": "super-ontology-contract.json",
|
|
18
22
|
"superOntologyOpenWorldCoverageFile": "super-ontology-open-world-coverage.json",
|
|
19
23
|
"superOntologyConsensusCoordinationFile": "super-ontology-consensus-coordination.json",
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const SEMVER_RE = /^[vV]?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?$/;
|
|
4
|
+
|
|
5
|
+
function parseSemVer(value) {
|
|
6
|
+
if (typeof value !== "string") return null;
|
|
7
|
+
const match = value.trim().match(SEMVER_RE);
|
|
8
|
+
if (!match) return null;
|
|
9
|
+
const prerelease = match[4] ? match[4].split(".") : [];
|
|
10
|
+
if (prerelease.some((identifier) => /^\d+$/.test(identifier) && identifier.length > 1 && identifier.startsWith("0"))) {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
return {
|
|
14
|
+
major: match[1],
|
|
15
|
+
minor: match[2],
|
|
16
|
+
patch: match[3],
|
|
17
|
+
prerelease,
|
|
18
|
+
build: match[5] ? match[5].split(".") : [],
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function normalizeSemVer(value) {
|
|
23
|
+
const parsed = parseSemVer(value);
|
|
24
|
+
if (!parsed) return null;
|
|
25
|
+
return `${parsed.major}.${parsed.minor}.${parsed.patch}` +
|
|
26
|
+
`${parsed.prerelease.length ? `-${parsed.prerelease.join(".")}` : ""}` +
|
|
27
|
+
`${parsed.build.length ? `+${parsed.build.join(".")}` : ""}`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function compareNumericIdentifier(left, right) {
|
|
31
|
+
if (left.length !== right.length) return left.length < right.length ? -1 : 1;
|
|
32
|
+
if (left === right) return 0;
|
|
33
|
+
return left < right ? -1 : 1;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** SemVer 2.0.0 precedence. Build metadata is intentionally ignored. */
|
|
37
|
+
function compareSemVer(left, right) {
|
|
38
|
+
const a = parseSemVer(left);
|
|
39
|
+
const b = parseSemVer(right);
|
|
40
|
+
if (!a || !b) return null;
|
|
41
|
+
for (const key of ["major", "minor", "patch"]) {
|
|
42
|
+
const compared = compareNumericIdentifier(a[key], b[key]);
|
|
43
|
+
if (compared !== 0) return compared;
|
|
44
|
+
}
|
|
45
|
+
if (a.prerelease.length === 0 && b.prerelease.length === 0) return 0;
|
|
46
|
+
if (a.prerelease.length === 0) return 1;
|
|
47
|
+
if (b.prerelease.length === 0) return -1;
|
|
48
|
+
const length = Math.max(a.prerelease.length, b.prerelease.length);
|
|
49
|
+
for (let index = 0; index < length; index += 1) {
|
|
50
|
+
const leftIdentifier = a.prerelease[index];
|
|
51
|
+
const rightIdentifier = b.prerelease[index];
|
|
52
|
+
if (leftIdentifier === undefined) return -1;
|
|
53
|
+
if (rightIdentifier === undefined) return 1;
|
|
54
|
+
if (leftIdentifier === rightIdentifier) continue;
|
|
55
|
+
const leftNumeric = /^\d+$/.test(leftIdentifier);
|
|
56
|
+
const rightNumeric = /^\d+$/.test(rightIdentifier);
|
|
57
|
+
if (leftNumeric && rightNumeric) return compareNumericIdentifier(leftIdentifier, rightIdentifier);
|
|
58
|
+
if (leftNumeric !== rightNumeric) return leftNumeric ? -1 : 1;
|
|
59
|
+
return leftIdentifier < rightIdentifier ? -1 : 1;
|
|
60
|
+
}
|
|
61
|
+
return 0;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
module.exports = { parseSemVer, normalizeSemVer, compareSemVer };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentlas",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.5",
|
|
4
4
|
"description": "Agentlas agent terminal — chat with your installed AI agents and teams from the terminal, Claude Code style. Standalone: no desktop app required.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"agentlas": "bin/agentlas.cjs"
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
const assert = require("node:assert/strict");
|
|
5
|
+
const fs = require("node:fs");
|
|
6
|
+
const os = require("node:os");
|
|
7
|
+
const path = require("node:path");
|
|
8
|
+
const { spawn } = require("node:child_process");
|
|
9
|
+
|
|
10
|
+
const root = path.resolve(__dirname, "..");
|
|
11
|
+
const launcher = path.join(root, "bin", "agentlas.cjs");
|
|
12
|
+
const temp = fs.mkdtempSync(path.join(os.tmpdir(), "agentlas-bootstrap-race-"));
|
|
13
|
+
const probe = `const x=require(${JSON.stringify(launcher)}); process.stdout.write(JSON.stringify(x.bootstrapDbIfMissing()))`;
|
|
14
|
+
|
|
15
|
+
function runOne() {
|
|
16
|
+
return new Promise((resolve, reject) => {
|
|
17
|
+
const child = spawn(process.execPath, ["-e", probe], {
|
|
18
|
+
cwd: root,
|
|
19
|
+
env: { ...process.env, AGENTLAS_USER_DATA_DIR: temp },
|
|
20
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
21
|
+
});
|
|
22
|
+
let stdout = "";
|
|
23
|
+
let stderr = "";
|
|
24
|
+
child.stdout.on("data", (chunk) => { stdout += chunk; });
|
|
25
|
+
child.stderr.on("data", (chunk) => { stderr += chunk; });
|
|
26
|
+
child.on("error", reject);
|
|
27
|
+
child.on("close", (code) => {
|
|
28
|
+
if (code !== 0) return reject(new Error(`bootstrap child ${code}: ${stderr}`));
|
|
29
|
+
resolve(JSON.parse(stdout));
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
Promise.all(Array.from({ length: 8 }, () => runOne()))
|
|
35
|
+
.then((results) => {
|
|
36
|
+
assert.equal(results.filter((result) => result.created).length, 1, "exactly one process owns first creation");
|
|
37
|
+
const db = path.join(temp, "agentlas.sqlite");
|
|
38
|
+
assert.equal(fs.existsSync(db), true);
|
|
39
|
+
assert.ok(fs.statSync(db).size > 0);
|
|
40
|
+
assert.deepEqual(fs.readdirSync(temp).filter((name) => name.includes(".bootstrap-")), []);
|
|
41
|
+
console.log(JSON.stringify({ ok: true, workers: results.length, created: 1 }, null, 2));
|
|
42
|
+
})
|
|
43
|
+
.catch((error) => {
|
|
44
|
+
console.error(error);
|
|
45
|
+
process.exitCode = 1;
|
|
46
|
+
})
|
|
47
|
+
.finally(() => fs.rmSync(temp, { recursive: true, force: true }));
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
const assert = require("node:assert/strict");
|
|
5
|
+
const { EventEmitter } = require("node:events");
|
|
6
|
+
const { PassThrough } = require("node:stream");
|
|
7
|
+
const { captureRuntime, captureOutputLimit, isProtectedChildEnvKeyCli } = require("../engine/agentlas.cjs");
|
|
8
|
+
|
|
9
|
+
class FakeChild extends EventEmitter {
|
|
10
|
+
constructor(options = {}) {
|
|
11
|
+
super();
|
|
12
|
+
this.stdout = new PassThrough();
|
|
13
|
+
this.stderr = new PassThrough();
|
|
14
|
+
this.signals = [];
|
|
15
|
+
this.ignoreTerm = !!options.ignoreTerm;
|
|
16
|
+
this.ignoreKill = !!options.ignoreKill;
|
|
17
|
+
this.closed = false;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
kill(signal) {
|
|
21
|
+
this.signals.push(signal);
|
|
22
|
+
if (signal === "SIGTERM" && this.ignoreTerm) return true;
|
|
23
|
+
if (signal === "SIGKILL" && this.ignoreKill) return true;
|
|
24
|
+
this.finish(null, signal);
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
finish(code = 0, signal = null) {
|
|
29
|
+
if (this.closed) return;
|
|
30
|
+
this.closed = true;
|
|
31
|
+
this.stdout.end();
|
|
32
|
+
this.stderr.end();
|
|
33
|
+
queueMicrotask(() => this.emit("close", code, signal));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function capture(child, options = {}) {
|
|
38
|
+
return captureRuntime("gemini", "system", "prompt", {
|
|
39
|
+
cwd: process.cwd(),
|
|
40
|
+
permission: "read",
|
|
41
|
+
env: {},
|
|
42
|
+
timeoutConfig: options.timeoutConfig || { idleMs: 40, totalMs: 300, killGraceMs: 15 },
|
|
43
|
+
outputLimitBytes: options.outputLimitBytes || 1_024,
|
|
44
|
+
signal: options.signal,
|
|
45
|
+
spawn: () => child,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function main() {
|
|
50
|
+
assert.equal(captureOutputLimit({ AGENTLAS_CAPTURE_MAX_OUTPUT_BYTES: "NaN" }), 4 * 1024 * 1024);
|
|
51
|
+
assert.equal(captureOutputLimit({ AGENTLAS_CAPTURE_MAX_OUTPUT_BYTES: "-1" }), 64 * 1024);
|
|
52
|
+
assert.equal(captureOutputLimit({ AGENTLAS_CAPTURE_MAX_OUTPUT_BYTES: "999999999999" }), 32 * 1024 * 1024);
|
|
53
|
+
for (const key of [
|
|
54
|
+
"AGENTLAS_NATIVE_IDLE_TIMEOUT_MS",
|
|
55
|
+
"AGENTLAS_NATIVE_TOTAL_TIMEOUT_MS",
|
|
56
|
+
"AGENTLAS_NATIVE_KILL_GRACE_MS",
|
|
57
|
+
"AGENTLAS_CAPTURE_MAX_OUTPUT_BYTES",
|
|
58
|
+
]) {
|
|
59
|
+
assert.equal(isProtectedChildEnvKeyCli(key), true, `${key} must not be overridden by a project .env`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const streaming = new FakeChild();
|
|
63
|
+
const streamingRun = capture(streaming, { timeoutConfig: { idleMs: 35, totalMs: 250, killGraceMs: 15 } });
|
|
64
|
+
["one", "two", "three", "four"].forEach((value, index) => {
|
|
65
|
+
setTimeout(() => streaming.stdout.write(value), 15 + index * 20);
|
|
66
|
+
});
|
|
67
|
+
setTimeout(() => streaming.finish(0), 110);
|
|
68
|
+
assert.equal(await streamingRun, "onetwothreefour", "regular output must keep the capture idle timer alive");
|
|
69
|
+
assert.deepEqual(streaming.signals, []);
|
|
70
|
+
|
|
71
|
+
const silent = new FakeChild({ ignoreTerm: true });
|
|
72
|
+
await assert.rejects(
|
|
73
|
+
capture(silent, { timeoutConfig: { idleMs: 30, totalMs: 250, killGraceMs: 15 } }),
|
|
74
|
+
(error) => error && error.code === "AGENTLAS_CAPTURE_IDLE_TIMEOUT",
|
|
75
|
+
);
|
|
76
|
+
assert.deepEqual(silent.signals, ["SIGTERM", "SIGKILL"]);
|
|
77
|
+
|
|
78
|
+
const active = new FakeChild({ ignoreTerm: true });
|
|
79
|
+
const activeRun = capture(active, { timeoutConfig: { idleMs: 35, totalMs: 85, killGraceMs: 15 } });
|
|
80
|
+
const activity = setInterval(() => active.stderr.write("progress\n"), 12);
|
|
81
|
+
await assert.rejects(activeRun, (error) => error && error.code === "AGENTLAS_CAPTURE_TOTAL_TIMEOUT");
|
|
82
|
+
clearInterval(activity);
|
|
83
|
+
assert.deepEqual(active.signals, ["SIGTERM", "SIGKILL"], "total timeout caps an otherwise active capture");
|
|
84
|
+
|
|
85
|
+
const noisy = new FakeChild();
|
|
86
|
+
const noisyRun = capture(noisy, {
|
|
87
|
+
outputLimitBytes: 128,
|
|
88
|
+
timeoutConfig: { idleMs: 200, totalMs: 300, killGraceMs: 15 },
|
|
89
|
+
});
|
|
90
|
+
noisy.stdout.write(Buffer.alloc(512, 0x61));
|
|
91
|
+
await assert.rejects(noisyRun, (error) => error && error.code === "AGENTLAS_CAPTURE_OUTPUT_LIMIT");
|
|
92
|
+
assert.deepEqual(noisy.signals, ["SIGTERM"], "output overflow should stop immediately without retaining unbounded data");
|
|
93
|
+
|
|
94
|
+
const cancellable = new FakeChild();
|
|
95
|
+
const controller = new AbortController();
|
|
96
|
+
const cancelRun = capture(cancellable, {
|
|
97
|
+
signal: controller.signal,
|
|
98
|
+
timeoutConfig: { idleMs: 200, totalMs: 300, killGraceMs: 15 },
|
|
99
|
+
});
|
|
100
|
+
setTimeout(() => controller.abort(new Error("operator cancelled")), 15);
|
|
101
|
+
await assert.rejects(cancelRun, /operator cancelled/);
|
|
102
|
+
assert.deepEqual(cancellable.signals, ["SIGTERM"]);
|
|
103
|
+
|
|
104
|
+
const stubborn = new FakeChild({ ignoreTerm: true, ignoreKill: true });
|
|
105
|
+
const startedAt = Date.now();
|
|
106
|
+
await assert.rejects(
|
|
107
|
+
capture(stubborn, { timeoutConfig: { idleMs: 20, totalMs: 300, killGraceMs: 10 } }),
|
|
108
|
+
(error) => error && error.code === "AGENTLAS_CAPTURE_IDLE_TIMEOUT",
|
|
109
|
+
);
|
|
110
|
+
assert.ok(Date.now() - startedAt < 1_000, "capture slot must be released even if the child never emits close");
|
|
111
|
+
assert.deepEqual(stubborn.signals, ["SIGTERM", "SIGKILL"]);
|
|
112
|
+
assert.equal(stubborn.stdout.listenerCount("data"), 0, "force resolution must detach stdout capture");
|
|
113
|
+
assert.equal(stubborn.stderr.listenerCount("data"), 0, "force resolution must detach stderr capture");
|
|
114
|
+
assert.equal(stubborn.listenerCount("close"), 0, "force resolution must detach process listeners");
|
|
115
|
+
|
|
116
|
+
console.log("capture-runtime-guard: PASS");
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
main().catch((error) => {
|
|
120
|
+
console.error(error);
|
|
121
|
+
process.exitCode = 1;
|
|
122
|
+
});
|