@anhlp/alp-runtime 0.0.0-stage → 0.0.1-beta.4
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/LICENSE +202 -0
- package/NOTICE +7 -0
- package/README.md +6 -2
- package/dist/acp-transport.d.ts +130 -0
- package/dist/acp-transport.js +511 -0
- package/dist/antigravity-transport.d.ts +82 -0
- package/dist/antigravity-transport.js +619 -0
- package/dist/assignment-results.d.ts +18 -0
- package/dist/assignment-results.js +77 -0
- package/dist/attachments.d.ts +84 -0
- package/dist/attachments.js +213 -0
- package/dist/board-domain.d.ts +56 -0
- package/dist/board-domain.js +157 -0
- package/dist/board.d.ts +21 -0
- package/dist/board.js +69 -0
- package/dist/catalog.d.ts +31 -0
- package/dist/catalog.js +38 -0
- package/dist/claude-transport.d.ts +202 -0
- package/dist/claude-transport.js +817 -0
- package/dist/configure.d.ts +44 -0
- package/dist/configure.js +64 -0
- package/dist/contracts.d.ts +327 -0
- package/dist/contracts.js +16 -0
- package/dist/conversation.d.ts +45 -0
- package/dist/conversation.js +62 -0
- package/dist/events.d.ts +1 -0
- package/dist/events.js +0 -0
- package/dist/github.d.ts +10 -0
- package/dist/github.js +39 -0
- package/dist/handoff.d.ts +36 -0
- package/dist/handoff.js +73 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.js +87 -0
- package/dist/language.d.ts +41 -0
- package/dist/language.js +68 -0
- package/dist/live.d.ts +48 -0
- package/dist/live.js +45 -0
- package/dist/mailbox.d.ts +26 -0
- package/dist/mailbox.js +38 -0
- package/dist/mcp-tools.d.ts +115 -0
- package/dist/mcp-tools.js +71 -0
- package/dist/permission-path.d.ts +2 -0
- package/dist/permission-path.js +44 -0
- package/dist/process-info.d.ts +14 -0
- package/dist/process-info.js +32 -0
- package/dist/providers.d.ts +28 -0
- package/dist/providers.js +71 -0
- package/dist/recall.d.ts +42 -0
- package/dist/recall.js +60 -0
- package/dist/resolve.d.ts +121 -0
- package/dist/resolve.js +238 -0
- package/dist/runtime-context.d.ts +43 -0
- package/dist/runtime-context.js +61 -0
- package/dist/runtime.d.ts +11 -0
- package/dist/runtime.js +3976 -0
- package/dist/session-config.d.ts +2102 -0
- package/dist/session-config.js +205 -0
- package/dist/session-state.d.ts +166 -0
- package/dist/session-state.js +0 -0
- package/dist/tool-detail.d.ts +81 -0
- package/dist/tool-detail.js +332 -0
- package/dist/tool-results.d.ts +12 -0
- package/dist/tool-results.js +25 -0
- package/dist/tools.d.ts +719 -0
- package/dist/tools.js +396 -0
- package/dist/transport.d.ts +47 -0
- package/dist/transport.js +169 -0
- package/dist/usage.d.ts +25 -0
- package/dist/usage.js +60 -0
- package/dist/workspace.d.ts +101 -0
- package/dist/workspace.js +259 -0
- package/dist/worktree-domain.d.ts +89 -0
- package/dist/worktree-domain.js +199 -0
- package/package.json +262 -4
- package/src/acp-transport.ts +508 -0
- package/src/antigravity-transport.ts +667 -0
- package/src/assignment-results.ts +64 -0
- package/src/attachments.ts +254 -0
- package/src/board-domain.ts +185 -0
- package/src/board.ts +80 -0
- package/src/catalog.ts +32 -0
- package/src/claude-transport.ts +938 -0
- package/src/configure.ts +93 -0
- package/src/contracts.ts +318 -0
- package/src/conversation.ts +81 -0
- package/src/events.ts +1 -0
- package/src/github.ts +37 -0
- package/src/handoff.ts +86 -0
- package/src/index.ts +32 -0
- package/src/language.ts +102 -0
- package/src/live.ts +86 -0
- package/src/mailbox.ts +49 -0
- package/src/mcp-tools.ts +73 -0
- package/src/permission-path.ts +37 -0
- package/src/process-info.ts +40 -0
- package/src/providers.ts +84 -0
- package/src/recall.ts +85 -0
- package/src/resolve.ts +230 -0
- package/src/runtime-context.ts +49 -0
- package/src/runtime.ts +4691 -0
- package/src/session-config.ts +304 -0
- package/src/session-state.ts +164 -0
- package/src/tool-detail.ts +397 -0
- package/src/tool-results.ts +26 -0
- package/src/tools.ts +397 -0
- package/src/transport.ts +116 -0
- package/src/usage.ts +79 -0
- package/src/workspace.ts +318 -0
- package/src/worktree-domain.ts +220 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// packages/runtime/src/permission-path.ts
|
|
2
|
+
import { lstatSync, realpathSync } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
function canonical(file) {
|
|
6
|
+
const absolute = path.isAbsolute(file) ? file : `${process.cwd()}${path.sep}${file}`;
|
|
7
|
+
let current = path.parse(absolute).root;
|
|
8
|
+
for (const part of absolute.slice(current.length).split(process.platform === "win32" ? /[\\/]/ : /\//)) {
|
|
9
|
+
if (!part || part === ".") continue;
|
|
10
|
+
if (part === "..") {
|
|
11
|
+
current = path.dirname(current);
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
const candidate = path.join(current, part);
|
|
15
|
+
try {
|
|
16
|
+
current = realpathSync.native(candidate);
|
|
17
|
+
} catch (error) {
|
|
18
|
+
if (error?.code !== "ENOENT") throw error;
|
|
19
|
+
try {
|
|
20
|
+
if (lstatSync(candidate).isSymbolicLink()) throw new Error("Dangling symlink");
|
|
21
|
+
} catch (infoError) {
|
|
22
|
+
if (infoError?.code !== "ENOENT") throw infoError;
|
|
23
|
+
}
|
|
24
|
+
current = candidate;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return current;
|
|
28
|
+
}
|
|
29
|
+
var contains = (root, file) => {
|
|
30
|
+
const relative = path.relative(root, file);
|
|
31
|
+
return relative === "" || relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative);
|
|
32
|
+
};
|
|
33
|
+
function writablePath(file, workdir) {
|
|
34
|
+
if (typeof file !== "string" || !file.trim() || file.includes("\0")) return false;
|
|
35
|
+
try {
|
|
36
|
+
const target = canonical(path.isAbsolute(file) ? file : `${workdir}${path.sep}${file}`);
|
|
37
|
+
return contains(canonical(workdir), target) || contains(canonical(os.tmpdir()), target);
|
|
38
|
+
} catch {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
export {
|
|
43
|
+
writablePath
|
|
44
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telling a process from a later one that reuses its pid (ALPD §32): a pid alone
|
|
3
|
+
* can name an unrelated process once the first one is gone, so ALP also records
|
|
4
|
+
* when the process started, and compares that.
|
|
5
|
+
*/
|
|
6
|
+
/** When this process started, in milliseconds since the epoch. */
|
|
7
|
+
export declare const OWN_START: number;
|
|
8
|
+
/** When process `pid` started, from ps (PowerShell on Windows); undefined where they cannot tell. */
|
|
9
|
+
export declare function processStartedAt(pid: number): number | undefined;
|
|
10
|
+
/**
|
|
11
|
+
* Whether the process that started at `startedAt` as `pid` still runs. Without a
|
|
12
|
+
* start time, or where ps cannot tell, a live pid counts as that process.
|
|
13
|
+
*/
|
|
14
|
+
export declare function sameProcessAlive(pid: number, startedAt?: number): boolean;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// packages/runtime/src/process-info.ts
|
|
2
|
+
import { execFileSync } from "node:child_process";
|
|
3
|
+
var OWN_START = Math.round(Date.now() - process.uptime() * 1e3);
|
|
4
|
+
var START_TOLERANCE_MS = 2e3;
|
|
5
|
+
function processStartedAt(pid) {
|
|
6
|
+
try {
|
|
7
|
+
if (process.platform === "win32") {
|
|
8
|
+
const out = execFileSync("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", `[DateTimeOffset]::new((Get-Process -Id ${Math.trunc(pid)}).StartTime).ToUnixTimeMilliseconds()`], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: 5e3, windowsHide: true }).trim();
|
|
9
|
+
return /^\d+$/.test(out) ? Number(out) : void 0;
|
|
10
|
+
}
|
|
11
|
+
const started = execFileSync("ps", ["-o", "lstart=", "-p", String(pid)], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: 2e3, env: { ...process.env, LC_ALL: "C" } }).trim();
|
|
12
|
+
const at = Date.parse(started);
|
|
13
|
+
return Number.isNaN(at) ? void 0 : at;
|
|
14
|
+
} catch {
|
|
15
|
+
return void 0;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function sameProcessAlive(pid, startedAt) {
|
|
19
|
+
try {
|
|
20
|
+
process.kill(pid, 0);
|
|
21
|
+
} catch (error) {
|
|
22
|
+
if (error?.code !== "EPERM") return false;
|
|
23
|
+
}
|
|
24
|
+
if (startedAt === void 0) return true;
|
|
25
|
+
const actual = processStartedAt(pid);
|
|
26
|
+
return actual === void 0 || Math.abs(actual - startedAt) < START_TOLERANCE_MS;
|
|
27
|
+
}
|
|
28
|
+
export {
|
|
29
|
+
OWN_START,
|
|
30
|
+
processStartedAt,
|
|
31
|
+
sameProcessAlive
|
|
32
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type AcpProvider } from './acp-transport.js';
|
|
2
|
+
import { type AntigravityProvider } from './antigravity-transport.js';
|
|
3
|
+
import type { RuntimeKind } from './resolve.js';
|
|
4
|
+
import type { RuntimeOptions, RuntimeTransport } from './contracts.js';
|
|
5
|
+
/** The environment a native harness starts with: the host's, without variables that would tie it to the host's own agent session. */
|
|
6
|
+
export declare function nativeEnvironment(options: RuntimeOptions, extra?: Record<string, string>): NodeJS.ProcessEnv;
|
|
7
|
+
/** Provider factories adapt native harnesses to ALP's normalized transport contract. */
|
|
8
|
+
export type ProviderContext = {
|
|
9
|
+
options: RuntimeOptions;
|
|
10
|
+
runtime: RuntimeKind;
|
|
11
|
+
cwd: string;
|
|
12
|
+
environment: NodeJS.ProcessEnv;
|
|
13
|
+
acp?: AcpProvider;
|
|
14
|
+
antigravity?: AntigravityProvider;
|
|
15
|
+
};
|
|
16
|
+
export type ProviderFactory = (context: ProviderContext) => RuntimeTransport;
|
|
17
|
+
export type ProviderFactories = Partial<Record<RuntimeKind, ProviderFactory>>;
|
|
18
|
+
/** Explicit registry: each runtime instance can override adapters without global state. */
|
|
19
|
+
export declare class ProviderRegistry {
|
|
20
|
+
private readonly factories;
|
|
21
|
+
constructor(factories?: Record<string, ProviderFactory | undefined>);
|
|
22
|
+
register(id: string, factory: ProviderFactory): this;
|
|
23
|
+
create(id: string, context: ProviderContext): RuntimeTransport;
|
|
24
|
+
has(id: string): boolean;
|
|
25
|
+
list(): string[];
|
|
26
|
+
}
|
|
27
|
+
export declare function createProviderRegistry(overrides?: ProviderFactories): ProviderRegistry;
|
|
28
|
+
export declare function createTransport(options: RuntimeOptions, runtimeKind: RuntimeKind, cwd: string, environment: NodeJS.ProcessEnv, acp?: AcpProvider, antigravity?: AntigravityProvider): RuntimeTransport;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// packages/runtime/src/providers.ts
|
|
2
|
+
import { CodexTransport } from "./transport.js";
|
|
3
|
+
import { ClaudeTransport } from "./claude-transport.js";
|
|
4
|
+
import { AcpTransport } from "./acp-transport.js";
|
|
5
|
+
import { AntigravityTransport } from "./antigravity-transport.js";
|
|
6
|
+
function nativeEnvironment(options, extra = {}) {
|
|
7
|
+
const environment = { ...options.environment ?? process.env, ...extra };
|
|
8
|
+
for (const key of Object.keys(environment)) {
|
|
9
|
+
if (/^(PASEO_|CODEX_THREAD_ID$|CODEX_INTERNAL_|CODEX_PARENT_|CLAUDECODE$|CLAUDE_CODE_|ANTHROPIC_AGENT_)/.test(key)) delete environment[key];
|
|
10
|
+
}
|
|
11
|
+
return environment;
|
|
12
|
+
}
|
|
13
|
+
var ProviderRegistry = class {
|
|
14
|
+
factories = /* @__PURE__ */ new Map();
|
|
15
|
+
constructor(factories = {}) {
|
|
16
|
+
for (const [id, factory] of Object.entries(factories)) if (factory) this.register(id, factory);
|
|
17
|
+
}
|
|
18
|
+
register(id, factory) {
|
|
19
|
+
if (!id.trim()) throw new Error("A provider needs an id");
|
|
20
|
+
this.factories.set(id, factory);
|
|
21
|
+
return this;
|
|
22
|
+
}
|
|
23
|
+
create(id, context) {
|
|
24
|
+
const factory = this.factories.get(id);
|
|
25
|
+
if (!factory) throw new Error(`Unknown runtime provider: ${id}`);
|
|
26
|
+
return factory(context);
|
|
27
|
+
}
|
|
28
|
+
has(id) {
|
|
29
|
+
return this.factories.has(id);
|
|
30
|
+
}
|
|
31
|
+
list() {
|
|
32
|
+
return [...this.factories.keys()];
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
function createProviderRegistry(overrides = {}) {
|
|
36
|
+
return new ProviderRegistry({
|
|
37
|
+
codex: ({ options, cwd, environment }) => new CodexTransport(
|
|
38
|
+
options.codexCommand ?? process.env.ALP_CODEX_BIN ?? "codex",
|
|
39
|
+
cwd,
|
|
40
|
+
environment
|
|
41
|
+
),
|
|
42
|
+
claude: ({ options, cwd, environment }) => new ClaudeTransport(
|
|
43
|
+
options.claudeCommand ?? process.env.ALP_CLAUDE_BIN ?? "claude",
|
|
44
|
+
cwd,
|
|
45
|
+
environment
|
|
46
|
+
),
|
|
47
|
+
acp: ({ acp, cwd, environment }) => {
|
|
48
|
+
if (!acp) throw new Error("An ACP session needs its provider");
|
|
49
|
+
return new AcpTransport(acp, cwd, environment);
|
|
50
|
+
},
|
|
51
|
+
antigravity: ({ options, antigravity, cwd, environment }) => new AntigravityTransport(
|
|
52
|
+
options.antigravityCommand ?? process.env.ALP_ANTIGRAVITY_BIN ?? antigravity?.command ?? "agy",
|
|
53
|
+
cwd,
|
|
54
|
+
environment,
|
|
55
|
+
antigravity
|
|
56
|
+
),
|
|
57
|
+
...Object.fromEntries(Object.entries(overrides).filter(([, factory]) => factory !== void 0))
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
function createTransport(options, runtimeKind, cwd, environment, acp, antigravity) {
|
|
61
|
+
if (options.transport) return options.transport(cwd, environment, runtimeKind, acp, antigravity);
|
|
62
|
+
const registry = options.providerRegistry ?? createProviderRegistry(options.providers);
|
|
63
|
+
const providerId = runtimeKind === "acp" && acp && registry.has(`acp:${acp.id}`) ? `acp:${acp.id}` : runtimeKind;
|
|
64
|
+
return registry.create(providerId, { options, runtime: runtimeKind, cwd, environment, acp, antigravity });
|
|
65
|
+
}
|
|
66
|
+
export {
|
|
67
|
+
ProviderRegistry,
|
|
68
|
+
createProviderRegistry,
|
|
69
|
+
createTransport,
|
|
70
|
+
nativeEnvironment
|
|
71
|
+
};
|
package/dist/recall.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { RuntimeKind } from './resolve.js';
|
|
2
|
+
/**
|
|
3
|
+
* Finished assignments whose native threads alp_recall can question (ALPD §27).
|
|
4
|
+
* The threads stay on disk for RECALL_KEEP_MS; then ALP deletes them.
|
|
5
|
+
*/
|
|
6
|
+
export type RecallEntry = {
|
|
7
|
+
assignmentId: string;
|
|
8
|
+
rootId: string;
|
|
9
|
+
/** The requester and its requesters, nearest first: who may recall it besides a root of the project. */
|
|
10
|
+
requesters: string[];
|
|
11
|
+
agent: string;
|
|
12
|
+
project: string;
|
|
13
|
+
runtime: RuntimeKind;
|
|
14
|
+
threadId: string;
|
|
15
|
+
/** Where it worked: the project, or a worktree or copy that may be gone by now. */
|
|
16
|
+
cwd: string;
|
|
17
|
+
model: string;
|
|
18
|
+
thinking?: string;
|
|
19
|
+
taskId?: string;
|
|
20
|
+
status: string;
|
|
21
|
+
finishedAt: string;
|
|
22
|
+
/** Shared work remains in cwd; a new assignment can build on this brief and handoff. */
|
|
23
|
+
shared?: {
|
|
24
|
+
brief: string;
|
|
25
|
+
summary: string;
|
|
26
|
+
writeRoot?: string;
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
export declare const RECALL_KEEP_MS: number;
|
|
30
|
+
export declare const RECALL_MAX = 1000;
|
|
31
|
+
export declare const RECALL_QUESTION_CHARS = 4000;
|
|
32
|
+
export declare const RECALL_TIMEOUT_MS = 300000;
|
|
33
|
+
/** The record of recallable threads, kept in `file` when given, else in memory. */
|
|
34
|
+
export declare function createRecallBook(file?: string): {
|
|
35
|
+
all(): Promise<RecallEntry[]>;
|
|
36
|
+
add(entry: RecallEntry): Promise<void>;
|
|
37
|
+
/** Removes and returns the entries past their time, and the oldest beyond RECALL_MAX. */
|
|
38
|
+
expire(now?: number): Promise<RecallEntry[]>;
|
|
39
|
+
flush: () => Promise<void>;
|
|
40
|
+
};
|
|
41
|
+
/** What a recalled assignment is told before the question. */
|
|
42
|
+
export declare function recallPrompt(asker: string, question: string): string;
|
package/dist/recall.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// packages/runtime/src/recall.ts
|
|
2
|
+
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
3
|
+
import { randomBytes } from "node:crypto";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
var RECALL_KEEP_MS = 14 * 24 * 60 * 60 * 1e3;
|
|
6
|
+
var RECALL_MAX = 1e3;
|
|
7
|
+
var RECALL_QUESTION_CHARS = 4e3;
|
|
8
|
+
var RECALL_TIMEOUT_MS = 3e5;
|
|
9
|
+
var valid = (entry) => !!entry && typeof entry === "object" && ["assignmentId", "rootId", "agent", "project", "runtime", "threadId", "cwd", "model", "status", "finishedAt"].every((key) => typeof entry[key] === "string") && Array.isArray(entry.requesters);
|
|
10
|
+
function createRecallBook(file) {
|
|
11
|
+
let entries;
|
|
12
|
+
let writes = Promise.resolve();
|
|
13
|
+
const load = () => entries ??= file ? readFile(file, "utf8").then((text) => JSON.parse(text).filter(valid), () => []) : Promise.resolve([]);
|
|
14
|
+
const save = (list) => {
|
|
15
|
+
if (!file) return writes;
|
|
16
|
+
const body = JSON.stringify(list, null, 2) + "\n";
|
|
17
|
+
writes = writes.then(async () => {
|
|
18
|
+
await mkdir(path.dirname(file), { recursive: true, mode: 448 });
|
|
19
|
+
const temporary = `${file}.${process.pid}.${randomBytes(4).toString("hex")}.tmp`;
|
|
20
|
+
await writeFile(temporary, body, { mode: 384 });
|
|
21
|
+
await rename(temporary, file);
|
|
22
|
+
}).catch(() => {
|
|
23
|
+
});
|
|
24
|
+
return writes;
|
|
25
|
+
};
|
|
26
|
+
return {
|
|
27
|
+
async all() {
|
|
28
|
+
return [...await load()];
|
|
29
|
+
},
|
|
30
|
+
async add(entry) {
|
|
31
|
+
const list = await load();
|
|
32
|
+
list.push(entry);
|
|
33
|
+
await save(list);
|
|
34
|
+
},
|
|
35
|
+
/** Removes and returns the entries past their time, and the oldest beyond RECALL_MAX. */
|
|
36
|
+
async expire(now = Date.now()) {
|
|
37
|
+
const list = await load();
|
|
38
|
+
const kept = list.filter((entry) => now - Date.parse(entry.finishedAt) < RECALL_KEEP_MS).slice(-RECALL_MAX);
|
|
39
|
+
if (kept.length === list.length) return [];
|
|
40
|
+
const gone = list.filter((entry) => !kept.includes(entry));
|
|
41
|
+
list.splice(0, list.length, ...kept);
|
|
42
|
+
await save(list);
|
|
43
|
+
return gone;
|
|
44
|
+
},
|
|
45
|
+
flush: () => writes
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function recallPrompt(asker, question) {
|
|
49
|
+
return `ALP recall: ${asker} asks about the assignment you finished in this conversation. Answer from what you did, saw and decided then; read files only to check a detail. This is a read-only copy of your session: change nothing, and do not call ALP tools; they are unavailable. Answer briefly and concretely.
|
|
50
|
+
|
|
51
|
+
Question: ${question}`;
|
|
52
|
+
}
|
|
53
|
+
export {
|
|
54
|
+
RECALL_KEEP_MS,
|
|
55
|
+
RECALL_MAX,
|
|
56
|
+
RECALL_QUESTION_CHARS,
|
|
57
|
+
RECALL_TIMEOUT_MS,
|
|
58
|
+
createRecallBook,
|
|
59
|
+
recallPrompt
|
|
60
|
+
};
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import type { AcpProvider } from './acp-transport.js';
|
|
2
|
+
import type { AntigravityProvider } from './antigravity-transport.js';
|
|
3
|
+
import type { ResolvedAgent } from '@anhlp/alp-core/types';
|
|
4
|
+
import type { AlpRuntimeAdapter } from '@anhlp/alp-core/adapter';
|
|
5
|
+
/** Codex and Claude Code, or an ACP agent the user's library defines (ALPD §46). */
|
|
6
|
+
import type { RuntimeKind } from '@anhlp/alp-protocol/dto';
|
|
7
|
+
export type { RuntimeKind } from '@anhlp/alp-protocol/dto';
|
|
8
|
+
import type { SessionSpec } from '@anhlp/alp-protocol/session';
|
|
9
|
+
export type { HostMcpServer, SessionSpec } from '@anhlp/alp-protocol/session';
|
|
10
|
+
export type ResolvedSession = Awaited<ReturnType<typeof resolveSession>>;
|
|
11
|
+
/** The description in a SKILL.md's frontmatter, on one line; empty when it has none. */
|
|
12
|
+
export declare function skillDescription(file: string): Promise<string>;
|
|
13
|
+
/**
|
|
14
|
+
* Composes project and agent instructions with the agent's skills (ALPD §59): each by
|
|
15
|
+
* name, file and description, so an agent knows which work a skill is for without
|
|
16
|
+
* opening it, and opens it before that work.
|
|
17
|
+
*/
|
|
18
|
+
export declare class InstructionsAdapter implements AlpRuntimeAdapter<{
|
|
19
|
+
instructions: string;
|
|
20
|
+
mcp: ResolvedAgent['mcp'];
|
|
21
|
+
runtime: ResolvedAgent['runtime'];
|
|
22
|
+
}> {
|
|
23
|
+
id: string;
|
|
24
|
+
capabilities(): {
|
|
25
|
+
readonly instructions: "emulated";
|
|
26
|
+
readonly skills: "emulated";
|
|
27
|
+
readonly hooks: "emulated";
|
|
28
|
+
readonly mcp: "native";
|
|
29
|
+
};
|
|
30
|
+
compile(agent: ResolvedAgent): Promise<{
|
|
31
|
+
adapterId: string;
|
|
32
|
+
agentName: string;
|
|
33
|
+
projectRoot: string;
|
|
34
|
+
material: {
|
|
35
|
+
instructions: string;
|
|
36
|
+
mcp: import("@anhlp/alp-core/types").ResolvedMcpConfig;
|
|
37
|
+
runtime: {
|
|
38
|
+
provider?: string;
|
|
39
|
+
model?: string;
|
|
40
|
+
reasoning?: string;
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
}>;
|
|
44
|
+
}
|
|
45
|
+
/** Lessons main recorded after supervisor reviews; the newest are kept when the file grows. */
|
|
46
|
+
export declare const LESSONS_FILE = "lessons.md";
|
|
47
|
+
/** Agents that advise, review, or watch: they never write, and take no tasks. */
|
|
48
|
+
export declare const READ_ONLY_AGENTS: string[];
|
|
49
|
+
export declare function resolveSession(spec: SessionSpec, options?: {
|
|
50
|
+
templates?: Record<string, string>;
|
|
51
|
+
library?: string;
|
|
52
|
+
language?: string; /** False for a preview: it writes nothing (D31). */
|
|
53
|
+
initialize?: boolean;
|
|
54
|
+
}): Promise<{
|
|
55
|
+
autocompact: number | undefined;
|
|
56
|
+
language: string;
|
|
57
|
+
context?: number | undefined;
|
|
58
|
+
instructions: string;
|
|
59
|
+
mcp: Record<string, unknown>;
|
|
60
|
+
env: {
|
|
61
|
+
[x: string]: string;
|
|
62
|
+
};
|
|
63
|
+
persist: boolean;
|
|
64
|
+
keepThread: boolean;
|
|
65
|
+
copyOf?: string | undefined;
|
|
66
|
+
mode: string;
|
|
67
|
+
permissions: import("@anhlp/alp-core/permissions").PermissionProfile | null;
|
|
68
|
+
thinking: string;
|
|
69
|
+
threadId: string | undefined;
|
|
70
|
+
workdir: string;
|
|
71
|
+
copy: boolean;
|
|
72
|
+
antigravity?: AntigravityProvider | undefined;
|
|
73
|
+
acp?: AcpProvider | undefined;
|
|
74
|
+
agent: import("@anhlp/alp-core/types").ResolvedAgent;
|
|
75
|
+
workflow: {
|
|
76
|
+
mode: string;
|
|
77
|
+
maxPeers: number;
|
|
78
|
+
supervisor: boolean;
|
|
79
|
+
};
|
|
80
|
+
team: {
|
|
81
|
+
id: string;
|
|
82
|
+
source: "builtin" | "library" | "project";
|
|
83
|
+
label: string;
|
|
84
|
+
description?: string;
|
|
85
|
+
main: string;
|
|
86
|
+
members: Record<string, {
|
|
87
|
+
role?: string;
|
|
88
|
+
model?: string;
|
|
89
|
+
thinking?: string;
|
|
90
|
+
}>;
|
|
91
|
+
delegation: Record<string, string[]>;
|
|
92
|
+
oracleThinking?: Record<string, string>;
|
|
93
|
+
oracleModels?: string[];
|
|
94
|
+
maxPeers?: number;
|
|
95
|
+
supervisor: false | {
|
|
96
|
+
agent: string;
|
|
97
|
+
model?: string;
|
|
98
|
+
thinking?: string;
|
|
99
|
+
};
|
|
100
|
+
houseRules: string;
|
|
101
|
+
} | undefined;
|
|
102
|
+
oracleModels: string[];
|
|
103
|
+
modelProfiles: Record<string, import("@anhlp/alp-core/types").AgentModelConfig[]>;
|
|
104
|
+
houseRules: string;
|
|
105
|
+
hooks: {
|
|
106
|
+
name: string;
|
|
107
|
+
path: string;
|
|
108
|
+
project: boolean;
|
|
109
|
+
event: string;
|
|
110
|
+
command: string;
|
|
111
|
+
blocking?: boolean;
|
|
112
|
+
timeoutSec?: number;
|
|
113
|
+
match?: {
|
|
114
|
+
agent?: string;
|
|
115
|
+
label?: string;
|
|
116
|
+
};
|
|
117
|
+
description?: string;
|
|
118
|
+
}[];
|
|
119
|
+
runtimeKind: RuntimeKind;
|
|
120
|
+
model: any;
|
|
121
|
+
}>;
|
package/dist/resolve.js
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
// packages/runtime/src/resolve.ts
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { access, readFile, stat } from "node:fs/promises";
|
|
4
|
+
import { defaultTeam, resolveWorkflow } from "@anhlp/alp-core/workflow";
|
|
5
|
+
import { initProject } from "@anhlp/alp-core/init";
|
|
6
|
+
import { profileModel, agentProfiles, selectedProfile } from "@anhlp/alp-core/profiles";
|
|
7
|
+
import { jsonObject, resolveAgent } from "@anhlp/alp-core/resolver";
|
|
8
|
+
import { resolveTeam, teamOf } from "@anhlp/alp-core/teams";
|
|
9
|
+
import { loadHooks } from "@anhlp/alp-core/hooks";
|
|
10
|
+
import { acpModel, loadProvider } from "@anhlp/alp-core/providers";
|
|
11
|
+
import { ensureLibrary } from "@anhlp/alp-core/library";
|
|
12
|
+
import { compileAgent } from "@anhlp/alp-core/adapter";
|
|
13
|
+
import { capMode, profileFor } from "@anhlp/alp-core/permissions";
|
|
14
|
+
import { userLanguage } from "@anhlp/alp-core/user-settings";
|
|
15
|
+
import { claudeSandboxAvailable } from "./claude-transport.js";
|
|
16
|
+
import { DEFAULT_ANTIGRAVITY_MODEL, DEFAULT_CLAUDE_MODEL, DEFAULT_MODEL, ORACLE_MODELS, ORACLE_THINKING, models, modes, thinkingOptions, thinkingOptionsFor } from "./catalog.js";
|
|
17
|
+
async function exists(target) {
|
|
18
|
+
try {
|
|
19
|
+
await access(target);
|
|
20
|
+
return true;
|
|
21
|
+
} catch (error) {
|
|
22
|
+
if (error?.code === "ENOENT") return false;
|
|
23
|
+
throw error;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
var SKILL_DESCRIPTION_CHARS = 400;
|
|
27
|
+
async function skillDescription(file) {
|
|
28
|
+
const text = await readFile(file, "utf8").catch(() => "");
|
|
29
|
+
const front = /^---\r?\n([\s\S]*?)\r?\n---/.exec(text)?.[1] ?? "";
|
|
30
|
+
const match = /^description:[ \t]*(.*(?:\r?\n[ \t]+.*)*)/m.exec(front)?.[1] ?? "";
|
|
31
|
+
const flat = match.replace(/^[>|][-+]?/, "").replace(/\s+/g, " ").trim().replace(/^(['"])(.*)\1$/, "$2");
|
|
32
|
+
return flat.length > SKILL_DESCRIPTION_CHARS ? `${flat.slice(0, SKILL_DESCRIPTION_CHARS - 1)}\u2026` : flat;
|
|
33
|
+
}
|
|
34
|
+
var InstructionsAdapter = class {
|
|
35
|
+
id = "native";
|
|
36
|
+
// ALP runs an agent's hooks itself, at its own events (ALPD §45).
|
|
37
|
+
capabilities() {
|
|
38
|
+
return { instructions: "emulated", skills: "emulated", hooks: "emulated", mcp: "native" };
|
|
39
|
+
}
|
|
40
|
+
async compile(agent) {
|
|
41
|
+
const listed = await Promise.all(agent.skills.map(async (skill) => {
|
|
42
|
+
const description = await skillDescription(skill.path);
|
|
43
|
+
return `- ${skill.name} (${skill.path})${description ? `: ${description}` : ""}`;
|
|
44
|
+
}));
|
|
45
|
+
const skills = listed.length ? "Your skills: methods written for kinds of work. Before you start work that a skill's description matches, read its SKILL.md and follow it; say which skill you use.\n" + listed.join("\n") : "";
|
|
46
|
+
return { adapterId: this.id, agentName: agent.name, projectRoot: agent.projectRoot, material: {
|
|
47
|
+
instructions: [agent.instructions.project, agent.instructions.agent, skills].filter(Boolean).join("\n\n"),
|
|
48
|
+
mcp: agent.mcp,
|
|
49
|
+
runtime: agent.runtime
|
|
50
|
+
} };
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
var LESSONS_FILE = "lessons.md";
|
|
54
|
+
var LESSON_CHARS = 6e3;
|
|
55
|
+
async function lessons(file) {
|
|
56
|
+
const text = (await readFile(file, "utf8").catch(() => "")).trim();
|
|
57
|
+
return text.length > LESSON_CHARS ? `\u2026
|
|
58
|
+
${text.slice(-LESSON_CHARS)}` : text;
|
|
59
|
+
}
|
|
60
|
+
var READ_ONLY_AGENTS = ["oracle", "reviewer", "supervisor"];
|
|
61
|
+
async function resolveSession(spec, options = {}) {
|
|
62
|
+
if (!path.isAbsolute(spec.cwd) || !(await stat(spec.cwd)).isDirectory()) throw new Error("Session cwd must be an existing absolute directory");
|
|
63
|
+
if (spec.workdir !== void 0 && (!path.isAbsolute(spec.workdir) || !(await stat(spec.workdir)).isDirectory())) throw new Error("Session workdir must be an existing absolute directory");
|
|
64
|
+
const starter = options.templates ? { templates: options.templates } : {};
|
|
65
|
+
if (options.initialize !== false) {
|
|
66
|
+
const settingsFile = path.join(spec.cwd, ".alp", "settings.json");
|
|
67
|
+
const scaffoldMissing = !await exists(path.join(spec.cwd, "ALP.md")) || !await exists(settingsFile);
|
|
68
|
+
if (scaffoldMissing) await initProject(spec.cwd, { ...starter, library: options.library });
|
|
69
|
+
else if (!spec.restore) {
|
|
70
|
+
const settings = await jsonObject(settingsFile, "INVALID_SETTINGS");
|
|
71
|
+
await initProject(spec.cwd, { ...starter, library: options.library, onlyManagedProfiles: true, teamIds: [spec.workflow === void 0 ? defaultTeam(settings) : teamOf(spec.workflow)] });
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (options.library) await ensureLibrary(options.library, options.templates ? { templates: options.templates } : {});
|
|
75
|
+
const restored = spec.restore;
|
|
76
|
+
if (restored && spec.agent !== void 0 && spec.agent !== restored.agent) throw new Error("Cannot resume a thread as a different ALP agent");
|
|
77
|
+
const { team, ...workflow } = await resolveWorkflow(spec.cwd, spec.workflow, restored?.workflow, { library: options.library, templates: options.templates, scaffold: options.initialize === false });
|
|
78
|
+
const agent = await resolveAgent(spec.cwd, { agent: spec.agent ?? restored?.agent, library: options.library, templates: options.templates });
|
|
79
|
+
const oracleModels = team?.oracleModels ?? ORACLE_MODELS;
|
|
80
|
+
if (agent.name === "oracle" && !restored && !oracleModels.includes(spec.model ?? "")) throw new Error(`Oracle runs on ${oracleModels.join(" or ")}; choose one`);
|
|
81
|
+
const projectSettings = await jsonObject(path.join(spec.cwd, ".alp/settings.json"), "INVALID_SETTINGS");
|
|
82
|
+
const profile = (projectSettings.team ?? projectSettings.profile)?.[workflow.mode]?.[agent.name];
|
|
83
|
+
const modelProfiles = Object.fromEntries(Object.entries((projectSettings.team ?? projectSettings.profile)?.[workflow.mode] ?? {}).map(([name, config]) => [name, agentProfiles(config, name)]));
|
|
84
|
+
const requestedModel = restored?.model ? `${restored.runtime ?? agent.runtime.provider ?? "codex"}:${restored.model}` : spec.model;
|
|
85
|
+
const profileChoice = selectedProfile(modelProfiles[agent.name] ?? [], requestedModel);
|
|
86
|
+
const projectModel = profileModel(profileChoice);
|
|
87
|
+
const compiled = await compileAgent(new InstructionsAdapter(), agent);
|
|
88
|
+
const base = team ?? await resolveTeam(void 0, "pho", { templates: options.templates });
|
|
89
|
+
const main = base.main;
|
|
90
|
+
const member = Object.hasOwn(base.members, agent.name) && (team || agent.name === main) ? base.members[agent.name] : void 0;
|
|
91
|
+
const sameModel = (id) => id === void 0 || restored?.model === void 0 || `${restored.runtime}:${restored.model}` === id || restored.model === id;
|
|
92
|
+
const teamChoice = member && !agent.projectRuntime?.model && !agent.projectRuntime?.provider && spec.model === void 0 && sameModel(member.model) ? member : void 0;
|
|
93
|
+
let runtimeKind = restored?.runtime ?? profileChoice?.provider ?? (teamChoice?.model ? void 0 : agent.runtime.provider) ?? "codex";
|
|
94
|
+
let model = restored?.model ?? spec.model ?? projectModel ?? teamChoice?.model ?? agent.runtime.model ?? (runtimeKind === "claude" ? DEFAULT_CLAUDE_MODEL : DEFAULT_MODEL);
|
|
95
|
+
if (model.startsWith("codex:")) {
|
|
96
|
+
runtimeKind = "codex";
|
|
97
|
+
model = model.slice("codex:".length);
|
|
98
|
+
}
|
|
99
|
+
if (model.startsWith("claude:")) {
|
|
100
|
+
runtimeKind = "claude";
|
|
101
|
+
model = model.slice("claude:".length);
|
|
102
|
+
}
|
|
103
|
+
if (model.startsWith("codex/")) {
|
|
104
|
+
runtimeKind = "codex";
|
|
105
|
+
model = model.slice("codex/".length);
|
|
106
|
+
}
|
|
107
|
+
if (model.startsWith("claude/")) {
|
|
108
|
+
runtimeKind = "claude";
|
|
109
|
+
model = model.slice("claude/".length);
|
|
110
|
+
}
|
|
111
|
+
if (model.startsWith("antigravity:")) {
|
|
112
|
+
runtimeKind = "antigravity";
|
|
113
|
+
model = model.slice("antigravity:".length);
|
|
114
|
+
}
|
|
115
|
+
if (model.startsWith("antigravity/")) {
|
|
116
|
+
runtimeKind = "antigravity";
|
|
117
|
+
model = model.slice("antigravity/".length);
|
|
118
|
+
}
|
|
119
|
+
if (model.startsWith("acp:")) {
|
|
120
|
+
const raw = model.slice("acp:".length);
|
|
121
|
+
if (raw.startsWith("antigravity/") || raw === "antigravity") {
|
|
122
|
+
runtimeKind = "antigravity";
|
|
123
|
+
model = raw.startsWith("antigravity/") ? raw.slice("antigravity/".length) : "";
|
|
124
|
+
} else {
|
|
125
|
+
runtimeKind = "acp";
|
|
126
|
+
model = raw;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (runtimeKind === "antigravity") {
|
|
130
|
+
if (!model.trim() || model === DEFAULT_MODEL) model = DEFAULT_ANTIGRAVITY_MODEL;
|
|
131
|
+
}
|
|
132
|
+
if (!["codex", "claude", "acp", "antigravity"].includes(runtimeKind)) {
|
|
133
|
+
model = model === DEFAULT_MODEL && agent.runtime.model !== DEFAULT_MODEL ? runtimeKind : `${runtimeKind}/${model}`;
|
|
134
|
+
runtimeKind = "acp";
|
|
135
|
+
}
|
|
136
|
+
if (restored?.runtime && runtimeKind !== restored.runtime) throw new Error("Cannot resume a thread with a different runtime provider");
|
|
137
|
+
let acp;
|
|
138
|
+
if (runtimeKind === "acp") {
|
|
139
|
+
const { provider: id, model: native } = acpModel(model);
|
|
140
|
+
acp = await loadProvider(options.library, id);
|
|
141
|
+
if (!acp) throw new Error(`No ACP provider '${id}': add one to your library with alp provider add ${id}, or name codex or claude`);
|
|
142
|
+
if (acp.enabled === false) throw new Error(`ACP provider '${id}' is turned off; turn it on in the web app's Providers settings or in providers/${id}.json`);
|
|
143
|
+
const listed = [...acp.models ?? [], ...acp.additionalModels ?? []];
|
|
144
|
+
if (native && acp.models && !listed.some((candidate) => candidate.id === native)) throw new Error(`ACP provider '${id}' lists no model '${native}'`);
|
|
145
|
+
if (spec.copy) throw new Error("A review copy cannot run on an ACP agent: ALP cannot keep its commands inside the copy");
|
|
146
|
+
}
|
|
147
|
+
let antigravity;
|
|
148
|
+
if (runtimeKind === "antigravity") {
|
|
149
|
+
const provider = await loadProvider(options.library, "antigravity");
|
|
150
|
+
if (provider && provider.kind === "antigravity") {
|
|
151
|
+
antigravity = provider;
|
|
152
|
+
if (antigravity.enabled === false) throw new Error("Antigravity provider 'antigravity' is turned off; turn it on in the web app's Providers settings or in providers/antigravity.json");
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
const permissions = await profileFor(agent.projectRoot, options.library, agent.name);
|
|
156
|
+
const requested = spec.mode ?? agent.mode ?? (agent.name === main ? "full-access" : "read-only");
|
|
157
|
+
const mode = permissions ? capMode(requested, permissions.base) : requested;
|
|
158
|
+
if (runtimeKind === "antigravity") {
|
|
159
|
+
if (mode !== "full-access" && !(mode === "read-only" && antigravity?.readOnlyMode === "instructions")) throw new Error('Antigravity currently supports only full-access; read-only requires explicit readOnlyMode: "instructions" and is not sandboxed');
|
|
160
|
+
if (spec.copy || permissions?.workdir === "copy") throw new Error("Antigravity cannot enforce isolation for a review copy");
|
|
161
|
+
if (permissions && (permissions.deny.length || permissions.ask.length || permissions.allow.length || permissions.beyondMode === "ask")) {
|
|
162
|
+
throw new Error("Antigravity cannot enforce custom permission rules");
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
const availableThinking = ["acp", "antigravity"].includes(runtimeKind) ? [] : thinkingOptionsFor(runtimeKind, model);
|
|
166
|
+
const preferred = ["acp", "antigravity"].includes(runtimeKind) ? "none" : spec.thinking ?? profileChoice?.effort ?? agent.projectRuntime?.reasoning ?? (agent.name === "oracle" ? team?.oracleThinking?.[`${runtimeKind}:${model}`] ?? team?.oracleThinking?.[runtimeKind] : void 0) ?? teamChoice?.thinking ?? agent.runtime.reasoning ?? (agent.name === "oracle" ? ORACLE_THINKING : availableThinking.length ? "medium" : "none");
|
|
167
|
+
const thinking = spec.thinking === void 0 && availableThinking.length && !availableThinking.some((candidate) => candidate.id === preferred) ? models.find((candidate) => candidate.id === `${runtimeKind}:${model}`)?.defaultThinkingOptionId ?? availableThinking[0].id : preferred;
|
|
168
|
+
if (!model.trim()) throw new Error("Model must be nonempty");
|
|
169
|
+
if (!modes.some((m) => m.id === mode)) throw new Error(`Unsupported mode '${mode}'`);
|
|
170
|
+
if (spec.copy && runtimeKind === "claude" && !claudeSandboxAvailable()) throw new Error("A review copy on Claude needs its Bash sandbox (Seatbelt on macOS; bubblewrap and socat on Linux)");
|
|
171
|
+
if (!thinkingOptions.some((m) => m.id === thinking)) throw new Error(`Unsupported thinking option '${thinking}'`);
|
|
172
|
+
if (availableThinking.length && !availableThinking.some((candidate) => candidate.id === thinking)) throw new Error(`Thinking option '${thinking}' is unsupported by ${runtimeKind}:${model}`);
|
|
173
|
+
const mcp = {};
|
|
174
|
+
for (const [name, value] of Object.entries(compiled.material.mcp.mcpServers)) {
|
|
175
|
+
const { headers, ...rest } = value;
|
|
176
|
+
Object.defineProperty(mcp, name, { value: { ...rest, ...headers ? { http_headers: headers } : {} }, enumerable: true, writable: true });
|
|
177
|
+
}
|
|
178
|
+
for (const [name, value] of Object.entries(spec.mcpServers ?? {})) {
|
|
179
|
+
if (Object.hasOwn(mcp, name)) throw new Error(`MCP server name collision: '${name}'`);
|
|
180
|
+
if (value.type === "sse") throw new Error(`SSE MCP server '${name}' is unsupported by this runtime`);
|
|
181
|
+
const server = value.type === "stdio" ? { command: value.command, args: value.args ?? [], env: value.env ?? {} } : { url: value.url, http_headers: value.headers ?? {} };
|
|
182
|
+
Object.defineProperty(mcp, name, { value: server, enumerable: true, writable: true });
|
|
183
|
+
}
|
|
184
|
+
let learned = "";
|
|
185
|
+
const supervisorAgent = team?.supervisor ? team.supervisor.agent : void 0;
|
|
186
|
+
if ([main, supervisorAgent].includes(agent.name) && workflow.supervisor) {
|
|
187
|
+
const user = options.library ? await lessons(path.join(options.library, LESSONS_FILE)) : "";
|
|
188
|
+
const project = await lessons(path.join(agent.projectRoot, ".alp", LESSONS_FILE));
|
|
189
|
+
if (user || project) {
|
|
190
|
+
learned = "Lessons main recorded after earlier supervisor reviews. Follow them; they are the user's process, not a task." + (user ? `
|
|
191
|
+
|
|
192
|
+
For every project:
|
|
193
|
+
${user}` : "") + (project ? `
|
|
194
|
+
|
|
195
|
+
For this project:
|
|
196
|
+
${project}` : "");
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
const houseRules = base.houseRules;
|
|
200
|
+
const hooks = await loadHooks(agent);
|
|
201
|
+
return {
|
|
202
|
+
agent,
|
|
203
|
+
workflow,
|
|
204
|
+
team,
|
|
205
|
+
oracleModels,
|
|
206
|
+
modelProfiles,
|
|
207
|
+
houseRules,
|
|
208
|
+
hooks,
|
|
209
|
+
runtimeKind,
|
|
210
|
+
model,
|
|
211
|
+
...acp ? { acp } : {},
|
|
212
|
+
...antigravity ? { antigravity } : {},
|
|
213
|
+
mode,
|
|
214
|
+
permissions,
|
|
215
|
+
thinking,
|
|
216
|
+
threadId: restored?.threadId,
|
|
217
|
+
workdir: spec.workdir ?? agent.projectRoot,
|
|
218
|
+
copy: Boolean(spec.copy && spec.workdir),
|
|
219
|
+
...spec.copy && spec.copyOf ? { copyOf: spec.copyOf } : {},
|
|
220
|
+
instructions: [compiled.material.instructions, learned, spec.systemPrompt].filter(Boolean).join("\n\n"),
|
|
221
|
+
mcp,
|
|
222
|
+
env: { ...spec.env },
|
|
223
|
+
persist: spec.persist ?? false,
|
|
224
|
+
keepThread: spec.keepThread ?? false,
|
|
225
|
+
// ALPD §57: the window the agent works in before its runtime compacts; absent, the model's.
|
|
226
|
+
...agent.context ? { context: agent.context } : {},
|
|
227
|
+
autocompact: typeof profile?.autocompact === "number" ? profile.autocompact : typeof agent.autocompact === "number" ? agent.autocompact : void 0,
|
|
228
|
+
// ALPD §54: the language everything the user reads is written in.
|
|
229
|
+
language: options.language ?? await userLanguage(options.library)
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
export {
|
|
233
|
+
InstructionsAdapter,
|
|
234
|
+
LESSONS_FILE,
|
|
235
|
+
READ_ONLY_AGENTS,
|
|
236
|
+
resolveSession,
|
|
237
|
+
skillDescription
|
|
238
|
+
};
|