agentlas 0.7.0 → 0.9.2
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/CHANGELOG.md +199 -0
- package/README.md +161 -18
- package/bin/agentlas.cjs +8 -8
- package/engine/agentlas-core-harness.cjs +212 -0
- package/engine/agentlas-desktop-loadout.cjs +527 -0
- package/engine/agentlas-doctor.cjs +1 -1
- package/engine/agentlas-experience-exchange.cjs +835 -85
- package/engine/agentlas-experience-intake.cjs +444 -0
- package/engine/agentlas-experience-mcp.cjs +580 -18
- package/engine/agentlas-i18n.cjs +10 -10
- package/engine/agentlas-input.cjs +5 -4
- package/engine/agentlas-mcp-env.cjs +219 -0
- package/engine/agentlas-mcp-wrapper.cjs +51 -0
- package/engine/agentlas-memory-governance.cjs +1029 -0
- package/engine/agentlas-native-host.cjs +129 -39
- package/engine/agentlas-parity.cjs +339 -154
- package/engine/agentlas-repl.cjs +306 -31
- package/engine/agentlas-workforce.cjs +2991 -0
- package/engine/agentlas-workload-routing.cjs +523 -0
- package/engine/agentlas.cjs +1619 -234
- package/engine/bootstrap-schema.sql +1 -1
- package/engine/experience-taxonomy-v1.json +49 -0
- package/package.json +8 -4
- package/scripts/gen-bootstrap-schema.sh +0 -23
- package/test/bootstrap-race.cjs +0 -47
- package/test/capture-runtime-guard.cjs +0 -122
- package/test/cloud-asset-restore.cjs +0 -423
- package/test/cloud-cas-client.cjs +0 -333
- package/test/cloud-owner-restore.cjs +0 -183
- package/test/cloud-runtime-paths.cjs +0 -40
- package/test/cloud-save-publish.cjs +0 -487
- package/test/credential-env-regression.cjs +0 -52
- package/test/engine-hardening-regression.cjs +0 -74
- package/test/experience-exchange-contract.cjs +0 -569
- package/test/experience-mcp-contract.cjs +0 -391
- package/test/fixtures/portable-experience-bundle-v1-golden.json +0 -124
- package/test/login-loopback-security.cjs +0 -115
- package/test/mcp-config-isolation.cjs +0 -36
- package/test/permission-mapping.cjs +0 -180
- package/test/route-regression.cjs +0 -357
- package/test/run-api-regression.cjs +0 -322
- package/test/runtime-env-protection.cjs +0 -89
- package/test/semver-precedence.cjs +0 -39
- package/test/smoke.sh +0 -93
- package/test/sqlite-driver-probe.cjs +0 -22
- package/test/terminal-ui-regression.cjs +0 -477
- package/test/timeout-regression.cjs +0 -218
- package/test/tool-workspace-boundary.cjs +0 -165
- package/test/update-safety.cjs +0 -376
|
@@ -31,7 +31,7 @@ CREATE TABLE chats (
|
|
|
31
31
|
id TEXT PRIMARY KEY,
|
|
32
32
|
project_id TEXT,
|
|
33
33
|
agent_id TEXT NOT NULL,
|
|
34
|
-
title TEXT NOT NULL DEFAULT '
|
|
34
|
+
title TEXT NOT NULL DEFAULT 'New chat',
|
|
35
35
|
created_at TEXT NOT NULL,
|
|
36
36
|
updated_at TEXT NOT NULL, firm_id TEXT REFERENCES firms(id) ON DELETE SET NULL, archived_at TEXT, working_folder TEXT, kind TEXT NOT NULL DEFAULT 'user', parent_chat_id TEXT, used_at TEXT, agent_group_id TEXT REFERENCES agent_groups(id) ON DELETE SET NULL, continuous_mode INTEGER NOT NULL DEFAULT 0, swarm_mode INTEGER NOT NULL DEFAULT 0,
|
|
37
37
|
FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE SET NULL,
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema": "agentlas.experience-taxonomy.v1",
|
|
3
|
+
"kind": "agentlas-experience-taxonomy",
|
|
4
|
+
"taskSignaturePrefix": "agentlas.task.v1/",
|
|
5
|
+
"taskSlugs": [
|
|
6
|
+
"research",
|
|
7
|
+
"writing",
|
|
8
|
+
"coding",
|
|
9
|
+
"debugging",
|
|
10
|
+
"design",
|
|
11
|
+
"image-generation",
|
|
12
|
+
"video-production",
|
|
13
|
+
"presentation",
|
|
14
|
+
"document",
|
|
15
|
+
"data-analysis",
|
|
16
|
+
"browser-automation",
|
|
17
|
+
"social-publishing",
|
|
18
|
+
"marketing",
|
|
19
|
+
"sales",
|
|
20
|
+
"customer-support",
|
|
21
|
+
"ecommerce",
|
|
22
|
+
"legal-review",
|
|
23
|
+
"finance",
|
|
24
|
+
"project-planning",
|
|
25
|
+
"agent-building",
|
|
26
|
+
"workflow-automation",
|
|
27
|
+
"file-operations",
|
|
28
|
+
"translation"
|
|
29
|
+
],
|
|
30
|
+
"environment": {
|
|
31
|
+
"osPrefix": "agentlas.env.v1/os/",
|
|
32
|
+
"archPrefix": "agentlas.env.v1/arch/",
|
|
33
|
+
"runtimePrefix": "agentlas.env.v1/runtime/",
|
|
34
|
+
"osValues": ["macos", "windows", "linux", "ios", "android", "unknown"],
|
|
35
|
+
"archValues": ["arm64", "x64", "unknown"],
|
|
36
|
+
"runtimePattern": "^[a-z0-9][a-z0-9._-]{1,63}$",
|
|
37
|
+
"matching": "all-canonical-constraints-must-match",
|
|
38
|
+
"unknownConstraint": "item-ineligible-base-unaffected"
|
|
39
|
+
},
|
|
40
|
+
"normalization": {
|
|
41
|
+
"unicode": "NFKC",
|
|
42
|
+
"trim": true,
|
|
43
|
+
"case": "lower",
|
|
44
|
+
"portableSource": "canonical-id-only",
|
|
45
|
+
"runtimeProfile": "canonical-id-or-exact-bare-slug",
|
|
46
|
+
"fuzzySimilarity": false,
|
|
47
|
+
"generalAutoMatch": false
|
|
48
|
+
}
|
|
49
|
+
}
|
package/package.json
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentlas",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.2",
|
|
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"
|
|
7
7
|
},
|
|
8
8
|
"scripts": {
|
|
9
|
-
"smoke": "sh test/smoke.sh"
|
|
9
|
+
"smoke": "sh test/smoke.sh",
|
|
10
|
+
"test:stormbreaker-core": "node test/stormbreaker-core-contract.cjs && node test/workload-routing-contract.cjs",
|
|
11
|
+
"test:workforce": "node test/workforce-runtime-contract.cjs",
|
|
12
|
+
"test:project-bootstrap": "node test/project-bootstrap-contract.cjs",
|
|
13
|
+
"test:project-bootstrap:release": "node test/project-bootstrap-contract.cjs --strict",
|
|
14
|
+
"test:release-contracts": "npm run test:stormbreaker-core && npm run test:workforce && npm run test:project-bootstrap:release"
|
|
10
15
|
},
|
|
11
16
|
"engines": {
|
|
12
17
|
"node": ">=20"
|
|
@@ -18,10 +23,9 @@
|
|
|
18
23
|
"files": [
|
|
19
24
|
"bin/",
|
|
20
25
|
"engine/",
|
|
21
|
-
"scripts/",
|
|
22
|
-
"test/",
|
|
23
26
|
"install.sh",
|
|
24
27
|
"install.ps1",
|
|
28
|
+
"CHANGELOG.md",
|
|
25
29
|
"README.md"
|
|
26
30
|
],
|
|
27
31
|
"keywords": [
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
#!/bin/sh
|
|
2
|
-
# 실제 앱 DB에서 부트스트랩 스키마를 재생성한다 (앱 없는 첫 실행용).
|
|
3
|
-
#
|
|
4
|
-
# sh scripts/gen-bootstrap-schema.sh [db-path]
|
|
5
|
-
#
|
|
6
|
-
# 기본 소스: ~/Library/Application Support/Agentlas/agentlas.sqlite
|
|
7
|
-
set -eu
|
|
8
|
-
|
|
9
|
-
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
10
|
-
PKG_ROOT="$(dirname "$SCRIPT_DIR")"
|
|
11
|
-
DB="${1:-$HOME/Library/Application Support/Agentlas/agentlas.sqlite}"
|
|
12
|
-
OUT="$PKG_ROOT/engine/bootstrap-schema.sql"
|
|
13
|
-
|
|
14
|
-
[ -f "$DB" ] || { echo "DB를 찾을 수 없습니다: $DB" >&2; exit 1; }
|
|
15
|
-
|
|
16
|
-
USER_VERSION="$(sqlite3 "$DB" 'PRAGMA user_version;')"
|
|
17
|
-
{
|
|
18
|
-
echo "-- Agentlas 첫 실행 부트스트랩 스키마 (생성: $(date -u +%Y-%m-%dT%H:%M:%SZ))"
|
|
19
|
-
echo "-- 소스 DB user_version=$USER_VERSION — 앱이 나중에 설치되면 여기서부터 마이그레이션한다."
|
|
20
|
-
echo "PRAGMA user_version=$USER_VERSION;"
|
|
21
|
-
sqlite3 "$DB" ".schema" | grep -v "sqlite_sequence"
|
|
22
|
-
} > "$OUT"
|
|
23
|
-
echo "written: $OUT (user_version=$USER_VERSION, $(grep -c 'CREATE' "$OUT") CREATE statements)"
|
package/test/bootstrap-race.cjs
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
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 }));
|
|
@@ -1,122 +0,0 @@
|
|
|
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
|
-
});
|