@amalgm/chat 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +98 -0
- package/PURPOSE.md +143 -104
- package/README.md +82 -54
- package/dist/acp/index.d.ts +7 -0
- package/dist/acp/index.d.ts.map +1 -0
- package/dist/acp/index.js +7 -0
- package/dist/acp/index.js.map +1 -0
- package/dist/acp/projection.d.ts +30 -0
- package/dist/acp/projection.d.ts.map +1 -0
- package/dist/acp/projection.js +147 -0
- package/dist/acp/projection.js.map +1 -0
- package/dist/api/conversations.d.ts +66 -0
- package/dist/api/conversations.d.ts.map +1 -0
- package/dist/api/conversations.js +202 -0
- package/dist/api/conversations.js.map +1 -0
- package/dist/api/index.d.ts +3 -3
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +1 -2
- package/dist/api/index.js.map +1 -1
- package/dist/conversations/index.d.ts +3 -0
- package/dist/conversations/index.d.ts.map +1 -0
- package/dist/conversations/index.js +3 -0
- package/dist/conversations/index.js.map +1 -0
- package/dist/conversations/transcript.d.ts +13 -0
- package/dist/conversations/transcript.d.ts.map +1 -0
- package/dist/conversations/transcript.js +171 -0
- package/dist/conversations/transcript.js.map +1 -0
- package/dist/conversations/types.d.ts +147 -0
- package/dist/conversations/types.d.ts.map +1 -0
- package/dist/conversations/types.js +9 -0
- package/dist/conversations/types.js.map +1 -0
- package/dist/events/constructors.d.ts +9 -15
- package/dist/events/constructors.d.ts.map +1 -1
- package/dist/events/constructors.js +6 -12
- package/dist/events/constructors.js.map +1 -1
- package/dist/events/index.d.ts +2 -3
- package/dist/events/index.d.ts.map +1 -1
- package/dist/events/index.js +2 -3
- package/dist/events/index.js.map +1 -1
- package/dist/events/schema.d.ts +2 -5
- package/dist/events/schema.d.ts.map +1 -1
- package/dist/events/schema.js +2 -5
- package/dist/events/schema.js.map +1 -1
- package/dist/execution/contract.d.ts +51 -0
- package/dist/execution/contract.d.ts.map +1 -0
- package/dist/execution/contract.js +127 -0
- package/dist/execution/contract.js.map +1 -0
- package/dist/execution/index.d.ts +6 -0
- package/dist/execution/index.d.ts.map +1 -0
- package/dist/execution/index.js +4 -0
- package/dist/execution/index.js.map +1 -0
- package/dist/execution/revision.d.ts +5 -0
- package/dist/execution/revision.d.ts.map +1 -0
- package/dist/execution/revision.js +17 -0
- package/dist/execution/revision.js.map +1 -0
- package/dist/execution/store.d.ts +26 -0
- package/dist/execution/store.d.ts.map +1 -0
- package/dist/execution/store.js +38 -0
- package/dist/execution/store.js.map +1 -0
- package/dist/index.d.ts +8 -23
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -23
- package/dist/index.js.map +1 -1
- package/dist/input/index.d.ts +3 -0
- package/dist/input/index.d.ts.map +1 -0
- package/dist/input/index.js +2 -0
- package/dist/input/index.js.map +1 -0
- package/dist/input/prompt.d.ts +17 -0
- package/dist/input/prompt.d.ts.map +1 -0
- package/dist/input/prompt.js +104 -0
- package/dist/input/prompt.js.map +1 -0
- package/dist/normalizers/claude.d.ts +6 -9
- package/dist/normalizers/claude.d.ts.map +1 -1
- package/dist/normalizers/claude.js +5 -8
- package/dist/normalizers/claude.js.map +1 -1
- package/dist/normalizers/codex.d.ts +5 -8
- package/dist/normalizers/codex.d.ts.map +1 -1
- package/dist/normalizers/codex.js +4 -7
- package/dist/normalizers/codex.js.map +1 -1
- package/dist/normalizers/cursor.d.ts +1 -2
- package/dist/normalizers/cursor.d.ts.map +1 -1
- package/dist/normalizers/cursor.js +2 -3
- package/dist/normalizers/cursor.js.map +1 -1
- package/dist/normalizers/index.d.ts +3 -5
- package/dist/normalizers/index.d.ts.map +1 -1
- package/dist/normalizers/index.js +3 -5
- package/dist/normalizers/index.js.map +1 -1
- package/dist/normalizers/opencode.d.ts +2 -4
- package/dist/normalizers/opencode.d.ts.map +1 -1
- package/dist/normalizers/opencode.js +2 -4
- package/dist/normalizers/opencode.js.map +1 -1
- package/dist/normalizers/pi.d.ts +3 -4
- package/dist/normalizers/pi.d.ts.map +1 -1
- package/dist/normalizers/pi.js +3 -4
- package/dist/normalizers/pi.js.map +1 -1
- package/dist/sessions/chat.d.ts +176 -0
- package/dist/sessions/chat.d.ts.map +1 -0
- package/dist/sessions/chat.js +701 -0
- package/dist/sessions/chat.js.map +1 -0
- package/dist/sessions/index.d.ts +3 -0
- package/dist/sessions/index.d.ts.map +1 -0
- package/dist/sessions/index.js +2 -0
- package/dist/sessions/index.js.map +1 -0
- package/dist/transcript/index.d.ts +1 -1
- package/dist/transcript/index.js +1 -1
- package/dist/transcript/message-reducer.d.ts +1 -1
- package/dist/transcript/message-reducer.js +1 -1
- package/dist/transcript/part-accumulator.d.ts +1 -1
- package/dist/transcript/part-accumulator.js +1 -1
- package/dist/transcript/tool-display.d.ts +1 -1
- package/dist/transcript/tool-display.js +1 -1
- package/dist/transcript/tool-shape.d.ts +1 -1
- package/dist/transcript/tool-shape.js +1 -1
- package/dist/usage/finalize.d.ts +7 -9
- package/dist/usage/finalize.d.ts.map +1 -1
- package/dist/usage/finalize.js +11 -13
- package/dist/usage/finalize.js.map +1 -1
- package/dist/usage/index.d.ts +2 -3
- package/dist/usage/index.d.ts.map +1 -1
- package/dist/usage/index.js +2 -3
- package/dist/usage/index.js.map +1 -1
- package/dist/usage/model-catalog.d.ts +2 -3
- package/dist/usage/model-catalog.d.ts.map +1 -1
- package/dist/usage/model-catalog.js +10 -3
- package/dist/usage/model-catalog.js.map +1 -1
- package/dist/usage/normalize.d.ts +5 -7
- package/dist/usage/normalize.d.ts.map +1 -1
- package/dist/usage/normalize.js +3 -5
- package/dist/usage/normalize.js.map +1 -1
- package/dist/usage/ports.d.ts +14 -24
- package/dist/usage/ports.d.ts.map +1 -1
- package/dist/usage/ports.js +9 -17
- package/dist/usage/ports.js.map +1 -1
- package/dist/usage/steps.d.ts +70 -0
- package/dist/usage/steps.d.ts.map +1 -0
- package/dist/usage/steps.js +152 -0
- package/dist/usage/steps.js.map +1 -0
- package/dist/usage/tokens.d.ts +4 -7
- package/dist/usage/tokens.d.ts.map +1 -1
- package/dist/usage/tokens.js +4 -7
- package/dist/usage/tokens.js.map +1 -1
- package/docs/contracts/acp-and-step-usage.md +51 -0
- package/docs/contracts/conversation-persistence.md +47 -0
- package/docs/contracts/input-and-execution.md +177 -0
- package/docs/contracts/platform-authorization.md +17 -0
- package/host/adapters/acp-capabilities.js +30 -0
- package/host/adapters/acp-client.js +156 -0
- package/host/adapters/acp.js +284 -0
- package/host/adapters/claude.js +235 -0
- package/host/adapters/codex.js +733 -0
- package/host/adapters/cursor.js +347 -0
- package/host/adapters/input-capabilities.js +89 -0
- package/host/adapters/opencode.js +486 -0
- package/host/adapters/pi.js +519 -0
- package/host/adapters/prompt.js +212 -0
- package/host/auth.js +257 -0
- package/host/credentials/store.js +209 -0
- package/host/http.d.ts +32 -0
- package/host/http.js +238 -0
- package/host/index.d.ts +84 -0
- package/host/index.js +59 -0
- package/host/lib/runtime-paths.js +44 -0
- package/host/model-resolution.js +313 -0
- package/host/native-contract.js +185 -0
- package/host/native-runtime.js +194 -0
- package/host/permissions.js +27 -0
- package/host/platform-egress.js +114 -0
- package/host/recorder.js +61 -0
- package/host/sqlite/index.d.ts +69 -0
- package/host/sqlite/index.js +704 -0
- package/host/tooling/mcp-bundle.js +214 -0
- package/host/tooling/mcp-relay.js +83 -0
- package/host/tooling/native-binaries.js +667 -0
- package/host/tooling/native-config.js +460 -0
- package/host/tooling/package-import.js +111 -0
- package/host/tooling/runtime-home.js +26 -0
- package/host/tooling/system-instructions.js +69 -0
- package/host/tooling/system-prompt.js +35 -0
- package/package.json +45 -21
- package/skills/chat/SKILL.md +97 -0
- package/skills/chat/agents/openai.yaml +4 -0
- package/skills/chat/references/contracts.md +131 -0
- package/dist/api/chat-payloads.d.ts +0 -10
- package/dist/api/chat-payloads.d.ts.map +0 -1
- package/dist/api/chat-payloads.js +0 -57
- package/dist/api/chat-payloads.js.map +0 -1
- package/dist/contract/chat-input.d.ts +0 -144
- package/dist/contract/chat-input.d.ts.map +0 -1
- package/dist/contract/chat-input.js +0 -261
- package/dist/contract/chat-input.js.map +0 -1
- package/dist/contract/create.d.ts +0 -191
- package/dist/contract/create.d.ts.map +0 -1
- package/dist/contract/create.js +0 -249
- package/dist/contract/create.js.map +0 -1
- package/dist/contract/freeze.d.ts +0 -85
- package/dist/contract/freeze.d.ts.map +0 -1
- package/dist/contract/freeze.js +0 -151
- package/dist/contract/freeze.js.map +0 -1
- package/dist/contract/harnesses.d.ts +0 -21
- package/dist/contract/harnesses.d.ts.map +0 -1
- package/dist/contract/harnesses.js +0 -39
- package/dist/contract/harnesses.js.map +0 -1
- package/dist/contract/index.d.ts +0 -17
- package/dist/contract/index.d.ts.map +0 -1
- package/dist/contract/index.js +0 -17
- package/dist/contract/index.js.map +0 -1
- package/dist/contract/input.d.ts +0 -45
- package/dist/contract/input.d.ts.map +0 -1
- package/dist/contract/input.js +0 -78
- package/dist/contract/input.js.map +0 -1
- package/dist/contract/models.d.ts +0 -57
- package/dist/contract/models.d.ts.map +0 -1
- package/dist/contract/models.js +0 -380
- package/dist/contract/models.js.map +0 -1
- package/dist/contract/payload-merge.d.ts +0 -76
- package/dist/contract/payload-merge.d.ts.map +0 -1
- package/dist/contract/payload-merge.js +0 -215
- package/dist/contract/payload-merge.js.map +0 -1
- package/dist/events/tool-display.d.ts +0 -22
- package/dist/events/tool-display.d.ts.map +0 -1
- package/dist/events/tool-display.js +0 -60
- package/dist/events/tool-display.js.map +0 -1
- package/dist/events/tool-shape.d.ts +0 -48
- package/dist/events/tool-shape.d.ts.map +0 -1
- package/dist/events/tool-shape.js +0 -225
- package/dist/events/tool-shape.js.map +0 -1
- package/dist/turns/index.d.ts +0 -11
- package/dist/turns/index.d.ts.map +0 -1
- package/dist/turns/index.js +0 -11
- package/dist/turns/index.js.map +0 -1
- package/dist/turns/queries.d.ts +0 -36
- package/dist/turns/queries.d.ts.map +0 -1
- package/dist/turns/queries.js +0 -37
- package/dist/turns/queries.js.map +0 -1
- package/dist/turns/replay.d.ts +0 -105
- package/dist/turns/replay.d.ts.map +0 -1
- package/dist/turns/replay.js +0 -163
- package/dist/turns/replay.js.map +0 -1
- package/dist/turns/status.d.ts +0 -49
- package/dist/turns/status.d.ts.map +0 -1
- package/dist/turns/status.js +0 -56
- package/dist/turns/status.js.map +0 -1
- package/dist/turns/turn-store.d.ts +0 -109
- package/dist/turns/turn-store.d.ts.map +0 -1
- package/dist/turns/turn-store.js +0 -169
- package/dist/turns/turn-store.js.map +0 -1
- package/dist/warm/client-cache.d.ts +0 -47
- package/dist/warm/client-cache.d.ts.map +0 -1
- package/dist/warm/client-cache.js +0 -48
- package/dist/warm/client-cache.js.map +0 -1
- package/dist/warm/envelope-cache.d.ts +0 -79
- package/dist/warm/envelope-cache.d.ts.map +0 -1
- package/dist/warm/envelope-cache.js +0 -85
- package/dist/warm/envelope-cache.js.map +0 -1
- package/dist/warm/index.d.ts +0 -18
- package/dist/warm/index.d.ts.map +0 -1
- package/dist/warm/index.js +0 -12
- package/dist/warm/index.js.map +0 -1
- package/dist/warm/payload.d.ts +0 -137
- package/dist/warm/payload.d.ts.map +0 -1
- package/dist/warm/payload.js +0 -112
- package/dist/warm/payload.js.map +0 -1
- package/dist/warm/revision.d.ts +0 -70
- package/dist/warm/revision.d.ts.map +0 -1
- package/dist/warm/revision.js +0 -63
- package/dist/warm/revision.js.map +0 -1
- package/dist/warm/store-policy.d.ts +0 -57
- package/dist/warm/store-policy.d.ts.map +0 -1
- package/dist/warm/store-policy.js +0 -89
- package/dist/warm/store-policy.js.map +0 -1
- package/dist/warm/store.d.ts +0 -42
- package/dist/warm/store.d.ts.map +0 -1
- package/dist/warm/store.js +0 -69
- package/dist/warm/store.js.map +0 -1
- package/dist/wire/frames.d.ts +0 -208
- package/dist/wire/frames.d.ts.map +0 -1
- package/dist/wire/frames.js +0 -245
- package/dist/wire/frames.js.map +0 -1
- package/dist/wire/index.d.ts +0 -7
- package/dist/wire/index.d.ts.map +0 -1
- package/dist/wire/index.js +0 -7
- package/dist/wire/index.js.map +0 -1
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native harness config and provider-auth synchronization.
|
|
3
|
+
*
|
|
4
|
+
* The provider-auth import rules are preserved exactly: `provider_auth`
|
|
5
|
+
* syncs auth material only (auth.json links, .credentials.json, oauth JSON
|
|
6
|
+
* subsets, keychain alias) — never native prefs — and non-provider auth
|
|
7
|
+
* methods never import native config at all.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import fs from 'node:fs';
|
|
11
|
+
import os from 'node:os';
|
|
12
|
+
import path from 'node:path';
|
|
13
|
+
|
|
14
|
+
const EXCLUDED_DIR_NAMES = new Set([
|
|
15
|
+
'.cache',
|
|
16
|
+
'.npm',
|
|
17
|
+
'.tmp',
|
|
18
|
+
'cache',
|
|
19
|
+
'debug',
|
|
20
|
+
'generated_images',
|
|
21
|
+
'log',
|
|
22
|
+
'logs',
|
|
23
|
+
'memories',
|
|
24
|
+
'node_modules',
|
|
25
|
+
'plugins',
|
|
26
|
+
'projects',
|
|
27
|
+
'sessions',
|
|
28
|
+
'shell_snapshots',
|
|
29
|
+
'skills',
|
|
30
|
+
'tmp',
|
|
31
|
+
'worktrees',
|
|
32
|
+
]);
|
|
33
|
+
|
|
34
|
+
const EXCLUDED_FILE_PATTERNS = [
|
|
35
|
+
/^logs?_\d+\.sqlite(?:-.+)?$/i,
|
|
36
|
+
/^state_\d+\.sqlite(?:-.+)?$/i,
|
|
37
|
+
/^.*\.db-(?:shm|wal)$/i,
|
|
38
|
+
/^.*\.sqlite-(?:shm|wal)$/i,
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
function exists(p) {
|
|
42
|
+
try {
|
|
43
|
+
fs.accessSync(p);
|
|
44
|
+
return true;
|
|
45
|
+
} catch {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function shouldCopyConfigPath(root, source) {
|
|
51
|
+
const relative = path.relative(root, source);
|
|
52
|
+
if (!relative || relative === '.') return true;
|
|
53
|
+
const parts = relative.split(path.sep);
|
|
54
|
+
if (parts.some((part) => EXCLUDED_DIR_NAMES.has(part))) return false;
|
|
55
|
+
try {
|
|
56
|
+
if (fs.lstatSync(source).isSymbolicLink()) return false;
|
|
57
|
+
} catch {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
const basename = path.basename(source);
|
|
61
|
+
return !EXCLUDED_FILE_PATTERNS.some((pattern) => pattern.test(basename));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function sameFile(a, b) {
|
|
65
|
+
try {
|
|
66
|
+
const left = fs.statSync(a);
|
|
67
|
+
const right = fs.statSync(b);
|
|
68
|
+
return left.dev === right.dev && left.ino === right.ino;
|
|
69
|
+
} catch {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function copyConfigTree(sourceDir, targetDir) {
|
|
75
|
+
if (!sourceDir || !targetDir || !exists(sourceDir)) return false;
|
|
76
|
+
if (path.resolve(sourceDir) === path.resolve(targetDir)) return true;
|
|
77
|
+
return copyDirBounded(sourceDir, targetDir, { maxFiles: 500, maxBytes: 25 * 1024 * 1024 }).copied;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function copyFileIfPresent(source, target) {
|
|
81
|
+
if (!exists(source)) return false;
|
|
82
|
+
if (!ensureDirectory(path.dirname(target)) || !canWriteFileTarget(target)) return false;
|
|
83
|
+
unlinkSymlinkIfPresent(target);
|
|
84
|
+
fs.copyFileSync(source, target);
|
|
85
|
+
try {
|
|
86
|
+
fs.chmodSync(target, fs.statSync(source).mode & 0o777);
|
|
87
|
+
} catch {}
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function unlinkSymlinkIfPresent(target) {
|
|
92
|
+
try {
|
|
93
|
+
if (fs.lstatSync(target).isSymbolicLink()) fs.unlinkSync(target);
|
|
94
|
+
} catch {}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function linkFileIfPresent(source, target) {
|
|
98
|
+
if (!exists(source)) return false;
|
|
99
|
+
if (!ensureDirectory(path.dirname(target))) return false;
|
|
100
|
+
if (sameFile(source, target)) return true;
|
|
101
|
+
try {
|
|
102
|
+
const stat = fs.lstatSync(target);
|
|
103
|
+
if (stat.isDirectory()) return false;
|
|
104
|
+
fs.unlinkSync(target);
|
|
105
|
+
} catch {}
|
|
106
|
+
try {
|
|
107
|
+
fs.symlinkSync(source, target, 'file');
|
|
108
|
+
return true;
|
|
109
|
+
} catch {}
|
|
110
|
+
try {
|
|
111
|
+
fs.linkSync(source, target);
|
|
112
|
+
return true;
|
|
113
|
+
} catch {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function ensureHomeAlias(runtimeHome, dotDirName) {
|
|
119
|
+
if (!runtimeHome || !dotDirName) return;
|
|
120
|
+
const alias = path.join(runtimeHome, dotDirName);
|
|
121
|
+
try {
|
|
122
|
+
const stat = fs.lstatSync(alias);
|
|
123
|
+
if (stat.isSymbolicLink()) {
|
|
124
|
+
const target = fs.readlinkSync(alias);
|
|
125
|
+
if (target === '.' || path.resolve(runtimeHome, target) === path.resolve(runtimeHome)) return;
|
|
126
|
+
}
|
|
127
|
+
return;
|
|
128
|
+
} catch {}
|
|
129
|
+
try {
|
|
130
|
+
fs.symlinkSync('.', alias, 'dir');
|
|
131
|
+
} catch {
|
|
132
|
+
fs.mkdirSync(alias, { recursive: true });
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function ensureNativeKeychainAlias(runtimeHome) {
|
|
137
|
+
if (process.platform !== 'darwin' || !runtimeHome) return false;
|
|
138
|
+
const source = path.join(nativeHome(), 'Library', 'Keychains');
|
|
139
|
+
if (!exists(source)) return false;
|
|
140
|
+
const target = path.join(runtimeHome, 'Library', 'Keychains');
|
|
141
|
+
try {
|
|
142
|
+
const stat = fs.lstatSync(target);
|
|
143
|
+
if (stat.isSymbolicLink()) {
|
|
144
|
+
const linkTarget = fs.readlinkSync(target);
|
|
145
|
+
if (path.resolve(path.dirname(target), linkTarget) === path.resolve(source)) return true;
|
|
146
|
+
}
|
|
147
|
+
return false;
|
|
148
|
+
} catch {}
|
|
149
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
150
|
+
try {
|
|
151
|
+
fs.symlinkSync(source, target, 'dir');
|
|
152
|
+
return true;
|
|
153
|
+
} catch {
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function nativeHome() {
|
|
159
|
+
return process.env.AMALGM_NATIVE_HOME || os.homedir();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function ensureDirectory(dir) {
|
|
163
|
+
try {
|
|
164
|
+
const stat = fs.lstatSync(dir);
|
|
165
|
+
return stat.isDirectory();
|
|
166
|
+
} catch {
|
|
167
|
+
try {
|
|
168
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
169
|
+
return true;
|
|
170
|
+
} catch {
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function canWriteFileTarget(target) {
|
|
177
|
+
try {
|
|
178
|
+
const stat = fs.lstatSync(target);
|
|
179
|
+
return stat.isFile();
|
|
180
|
+
} catch {
|
|
181
|
+
return true;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function copyDirBounded(sourceDir, targetDir, options = {}) {
|
|
186
|
+
if (!sourceDir || !targetDir || !exists(sourceDir)) return { copied: false, files: 0, bytes: 0, truncated: false };
|
|
187
|
+
const maxFiles = Number(options.maxFiles || 200);
|
|
188
|
+
const maxBytes = Number(options.maxBytes || 10 * 1024 * 1024);
|
|
189
|
+
const skipRelatives = new Set(Array.isArray(options.skipRelatives) ? options.skipRelatives : []);
|
|
190
|
+
const state = { copied: false, files: 0, bytes: 0, truncated: false };
|
|
191
|
+
const root = path.resolve(sourceDir);
|
|
192
|
+
|
|
193
|
+
function walk(source, target) {
|
|
194
|
+
const relative = path.relative(root, source);
|
|
195
|
+
if (relative && skipRelatives.has(relative)) return;
|
|
196
|
+
if (state.truncated || !shouldCopyConfigPath(root, source)) return;
|
|
197
|
+
let stat;
|
|
198
|
+
try {
|
|
199
|
+
stat = fs.lstatSync(source);
|
|
200
|
+
} catch {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
if (stat.isSymbolicLink()) return;
|
|
204
|
+
if (stat.isDirectory()) {
|
|
205
|
+
if (!ensureDirectory(target)) return;
|
|
206
|
+
for (const entry of fs.readdirSync(source)) {
|
|
207
|
+
walk(path.join(source, entry), path.join(target, entry));
|
|
208
|
+
if (state.truncated) break;
|
|
209
|
+
}
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
if (!stat.isFile()) return;
|
|
213
|
+
if (state.files + 1 > maxFiles || state.bytes + stat.size > maxBytes) {
|
|
214
|
+
state.truncated = true;
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
if (!ensureDirectory(path.dirname(target)) || !canWriteFileTarget(target)) return;
|
|
218
|
+
fs.copyFileSync(source, target);
|
|
219
|
+
state.files += 1;
|
|
220
|
+
state.bytes += stat.size;
|
|
221
|
+
state.copied = true;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
walk(sourceDir, targetDir);
|
|
225
|
+
return state;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export function syncCodexNativeConfig(runtimeHome) {
|
|
229
|
+
if (!runtimeHome) return null;
|
|
230
|
+
const sourceDir = path.join(nativeHome(), '.codex');
|
|
231
|
+
const sourceAuth = path.join(sourceDir, 'auth.json');
|
|
232
|
+
fs.mkdirSync(runtimeHome, { recursive: true });
|
|
233
|
+
if (!exists(sourceDir)) return null;
|
|
234
|
+
|
|
235
|
+
const nativeConfig = copyDirBounded(sourceDir, runtimeHome, { skipRelatives: ['auth.json'] });
|
|
236
|
+
const copiedFiles = [
|
|
237
|
+
copyFileIfPresent(path.join(sourceDir, 'config.toml'), path.join(runtimeHome, 'config.toml')),
|
|
238
|
+
copyFileIfPresent(path.join(sourceDir, 'hooks.json'), path.join(runtimeHome, 'hooks.json')),
|
|
239
|
+
copyFileIfPresent(path.join(sourceDir, 'supermemory.json'), path.join(runtimeHome, 'supermemory.json')),
|
|
240
|
+
].filter(Boolean).length;
|
|
241
|
+
const supermemory = copyDirBounded(path.join(sourceDir, 'supermemory'), path.join(runtimeHome, 'supermemory'));
|
|
242
|
+
copyDirBounded(path.join(nativeHome(), '.codex-supermemory'), path.join(runtimeHome, '.codex-supermemory'));
|
|
243
|
+
ensureHomeAlias(runtimeHome, '.codex');
|
|
244
|
+
const linkedAuth = linkFileIfPresent(sourceAuth, path.join(runtimeHome, 'auth.json'));
|
|
245
|
+
linkFileIfPresent(sourceAuth, path.join(runtimeHome, '.codex', 'auth.json'));
|
|
246
|
+
|
|
247
|
+
return {
|
|
248
|
+
sourceDir,
|
|
249
|
+
runtimeHome,
|
|
250
|
+
sourceConfigPath: path.join(sourceDir, 'config.toml'),
|
|
251
|
+
copied: nativeConfig.copied || copiedFiles + (supermemory.copied ? 1 : 0) > 0 || linkedAuth,
|
|
252
|
+
truncated: nativeConfig.truncated || supermemory.truncated,
|
|
253
|
+
linkedAuth,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export function syncClaudeNativeConfig(runtimeHome) {
|
|
258
|
+
if (!runtimeHome) return null;
|
|
259
|
+
const home = nativeHome();
|
|
260
|
+
if (path.resolve(runtimeHome) === path.resolve(home)) return null;
|
|
261
|
+
const sourceDir = path.join(home, '.claude');
|
|
262
|
+
fs.mkdirSync(runtimeHome, { recursive: true });
|
|
263
|
+
const copied = copyConfigTree(sourceDir, runtimeHome);
|
|
264
|
+
ensureHomeAlias(runtimeHome, '.claude');
|
|
265
|
+
ensureNativeKeychainAlias(runtimeHome);
|
|
266
|
+
copyFileIfPresent(path.join(home, '.claude.json'), path.join(runtimeHome, '.claude.json'));
|
|
267
|
+
copyConfigTree(path.join(home, '.config', 'claude'), path.join(runtimeHome, '.config', 'claude'));
|
|
268
|
+
return copied ? { sourceDir, runtimeHome } : null;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export function syncOpenCodeNativeConfig(runtimeHome) {
|
|
272
|
+
if (!runtimeHome) return null;
|
|
273
|
+
const home = nativeHome();
|
|
274
|
+
fs.mkdirSync(runtimeHome, { recursive: true });
|
|
275
|
+
const config = copyConfigTree(
|
|
276
|
+
path.join(home, '.config', 'opencode'),
|
|
277
|
+
path.join(runtimeHome, '.config', 'opencode'),
|
|
278
|
+
);
|
|
279
|
+
const data = copyConfigTree(
|
|
280
|
+
path.join(home, '.local', 'share', 'opencode'),
|
|
281
|
+
path.join(runtimeHome, '.local', 'share', 'opencode'),
|
|
282
|
+
);
|
|
283
|
+
const dot = copyConfigTree(path.join(home, '.opencode'), path.join(runtimeHome, '.opencode'));
|
|
284
|
+
if (!config && !data && !dot) return null;
|
|
285
|
+
return {
|
|
286
|
+
sourceDir: path.join(home, '.config', 'opencode'),
|
|
287
|
+
runtimeHome,
|
|
288
|
+
copied: config || data || dot,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function copyJsonSubsetIfPresent(source, target, keys) {
|
|
293
|
+
const data = readJsonFile(source);
|
|
294
|
+
if (!data || typeof data !== 'object' || Array.isArray(data)) return false;
|
|
295
|
+
const out = {};
|
|
296
|
+
for (const key of keys) {
|
|
297
|
+
if (data[key] !== undefined) out[key] = data[key];
|
|
298
|
+
}
|
|
299
|
+
if (Object.keys(out).length === 0) return false;
|
|
300
|
+
if (!ensureDirectory(path.dirname(target)) || !canWriteFileTarget(target)) return false;
|
|
301
|
+
fs.writeFileSync(target, JSON.stringify(out, null, 2), { mode: 0o600 });
|
|
302
|
+
try { fs.chmodSync(target, 0o600); } catch {}
|
|
303
|
+
return true;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
export function syncCodexProviderAuth(runtimeHome) {
|
|
307
|
+
if (!runtimeHome) return null;
|
|
308
|
+
const sourceDir = path.join(nativeHome(), '.codex');
|
|
309
|
+
const sourceAuth = path.join(sourceDir, 'auth.json');
|
|
310
|
+
fs.mkdirSync(runtimeHome, { recursive: true });
|
|
311
|
+
ensureHomeAlias(runtimeHome, '.codex');
|
|
312
|
+
const linked = linkFileIfPresent(sourceAuth, path.join(runtimeHome, 'auth.json'));
|
|
313
|
+
if (linked) linkFileIfPresent(sourceAuth, path.join(runtimeHome, '.codex', 'auth.json'));
|
|
314
|
+
return linked ? { sourceDir, runtimeHome, copied: false, linkedAuth: true, authOnly: true } : null;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
export function syncClaudeProviderAuth(runtimeHome) {
|
|
318
|
+
if (!runtimeHome) return null;
|
|
319
|
+
const home = nativeHome();
|
|
320
|
+
const sourceDir = path.join(home, '.claude');
|
|
321
|
+
fs.mkdirSync(runtimeHome, { recursive: true });
|
|
322
|
+
ensureHomeAlias(runtimeHome, '.claude');
|
|
323
|
+
const copiedCredentials = copyFileIfPresent(
|
|
324
|
+
path.join(sourceDir, '.credentials.json'),
|
|
325
|
+
path.join(runtimeHome, '.credentials.json'),
|
|
326
|
+
);
|
|
327
|
+
const copiedOauth = copyJsonSubsetIfPresent(
|
|
328
|
+
path.join(home, '.claude.json'),
|
|
329
|
+
path.join(runtimeHome, '.claude.json'),
|
|
330
|
+
['claudeAiOauth', 'oauthAccount'],
|
|
331
|
+
) || copyJsonSubsetIfPresent(
|
|
332
|
+
path.join(sourceDir, '.claude.json'),
|
|
333
|
+
path.join(runtimeHome, '.claude.json'),
|
|
334
|
+
['claudeAiOauth', 'oauthAccount'],
|
|
335
|
+
);
|
|
336
|
+
const keychain = ensureNativeKeychainAlias(runtimeHome);
|
|
337
|
+
return copiedCredentials || copiedOauth || keychain
|
|
338
|
+
? { sourceDir, runtimeHome, copied: copiedCredentials || copiedOauth, keychain, authOnly: true }
|
|
339
|
+
: null;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Cursor stores its login token in the macOS Keychain (service "cursor-access-token"),
|
|
343
|
+
// not in a file, so warm auth in a managed home rides on the Keychains alias.
|
|
344
|
+
// cli-config.json is copied once so the CLI starts from the user's preferences.
|
|
345
|
+
export function syncCursorProviderAuth(runtimeHome) {
|
|
346
|
+
if (!runtimeHome) return null;
|
|
347
|
+
const home = nativeHome();
|
|
348
|
+
const sourceDir = path.join(home, '.cursor');
|
|
349
|
+
fs.mkdirSync(runtimeHome, { recursive: true });
|
|
350
|
+
const keychain = ensureNativeKeychainAlias(runtimeHome);
|
|
351
|
+
const copiedAuth = copyFileIfPresent(
|
|
352
|
+
path.join(sourceDir, 'auth.json'),
|
|
353
|
+
path.join(runtimeHome, '.cursor', 'auth.json'),
|
|
354
|
+
);
|
|
355
|
+
const targetConfig = path.join(runtimeHome, '.cursor', 'cli-config.json');
|
|
356
|
+
const copiedConfig = !exists(targetConfig) && copyFileIfPresent(path.join(sourceDir, 'cli-config.json'), targetConfig);
|
|
357
|
+
return keychain || copiedAuth || copiedConfig
|
|
358
|
+
? { sourceDir, runtimeHome, copied: copiedAuth || copiedConfig, keychain, authOnly: true }
|
|
359
|
+
: null;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Pi keeps provider keys and custom-provider config under ~/.pi; auth.json is
|
|
363
|
+
// the auth material, models.json/settings.json carry provider definitions the
|
|
364
|
+
// managed home needs to resolve the same models.
|
|
365
|
+
export function syncPiProviderAuth(runtimeHome) {
|
|
366
|
+
if (!runtimeHome) return null;
|
|
367
|
+
const home = nativeHome();
|
|
368
|
+
const sourceDir = path.join(home, '.pi');
|
|
369
|
+
fs.mkdirSync(runtimeHome, { recursive: true });
|
|
370
|
+
const copied = ['agent/auth.json', 'agent/models.json', 'agent/settings.json']
|
|
371
|
+
.map((file) => copyFileIfPresent(path.join(sourceDir, file), path.join(runtimeHome, '.pi', file)))
|
|
372
|
+
.some(Boolean);
|
|
373
|
+
return copied ? { sourceDir, runtimeHome, copied: true, authOnly: true } : null;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
export function syncOpenCodeProviderAuth(runtimeHome) {
|
|
377
|
+
if (!runtimeHome) return null;
|
|
378
|
+
const home = nativeHome();
|
|
379
|
+
const source = path.join(home, '.local', 'share', 'opencode', 'auth.json');
|
|
380
|
+
const target = path.join(runtimeHome, '.local', 'share', 'opencode', 'auth.json');
|
|
381
|
+
fs.mkdirSync(runtimeHome, { recursive: true });
|
|
382
|
+
const copied = copyFileIfPresent(source, target);
|
|
383
|
+
return copied ? { sourceDir: path.dirname(source), runtimeHome, copied: true, authOnly: true } : null;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function readJsonFile(file) {
|
|
387
|
+
try {
|
|
388
|
+
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
389
|
+
} catch {
|
|
390
|
+
return null;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function cloneJson(value) {
|
|
395
|
+
if (value == null) return value;
|
|
396
|
+
try {
|
|
397
|
+
return JSON.parse(JSON.stringify(value));
|
|
398
|
+
} catch {
|
|
399
|
+
return null;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function mergeHookMaps(target, hooks) {
|
|
404
|
+
if (!hooks || typeof hooks !== 'object' || Array.isArray(hooks)) return;
|
|
405
|
+
for (const [event, matchers] of Object.entries(hooks)) {
|
|
406
|
+
if (!Array.isArray(matchers)) continue;
|
|
407
|
+
const cloned = cloneJson(matchers);
|
|
408
|
+
if (!Array.isArray(cloned)) continue;
|
|
409
|
+
target[event] = [...(Array.isArray(target[event]) ? target[event] : []), ...cloned];
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
export function claudeNativeHookSettings(options = {}) {
|
|
414
|
+
const sources = [
|
|
415
|
+
path.join(nativeHome(), '.claude', 'settings.json'),
|
|
416
|
+
options.cwd ? path.join(options.cwd, '.claude', 'settings.json') : null,
|
|
417
|
+
options.cwd ? path.join(options.cwd, '.claude', 'settings.local.json') : null,
|
|
418
|
+
].filter(Boolean);
|
|
419
|
+
const hooks = {};
|
|
420
|
+
for (const source of sources) {
|
|
421
|
+
const data = readJsonFile(source);
|
|
422
|
+
mergeHookMaps(hooks, data?.hooks);
|
|
423
|
+
}
|
|
424
|
+
return Object.keys(hooks).length > 0 ? { hooks } : null;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
export function syncNativeHarnessConfig(contract) {
|
|
428
|
+
const runtimeHome = contract?.auth?.runtimeHome;
|
|
429
|
+
if (!runtimeHome) return null;
|
|
430
|
+
if (contract.authMethod !== 'provider_auth') return null;
|
|
431
|
+
if (contract.harness === 'codex') return syncCodexNativeConfig(runtimeHome);
|
|
432
|
+
if (contract.harness === 'claude_code') return syncClaudeNativeConfig(runtimeHome);
|
|
433
|
+
if (contract.harness === 'opencode') return syncOpenCodeNativeConfig(runtimeHome);
|
|
434
|
+
return null;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
export function syncNativeProviderAuth(contract) {
|
|
438
|
+
const runtimeHome = contract?.auth?.runtimeHome;
|
|
439
|
+
if (!runtimeHome) return null;
|
|
440
|
+
if (contract.authMethod !== 'provider_auth') return null;
|
|
441
|
+
if (contract.harness === 'codex') return syncCodexProviderAuth(runtimeHome);
|
|
442
|
+
if (contract.harness === 'claude_code') return syncClaudeProviderAuth(runtimeHome);
|
|
443
|
+
if (contract.harness === 'opencode') return syncOpenCodeProviderAuth(runtimeHome);
|
|
444
|
+
if (contract.harness === 'cursor') return syncCursorProviderAuth(runtimeHome);
|
|
445
|
+
if (contract.harness === 'pi') return syncPiProviderAuth(runtimeHome);
|
|
446
|
+
return null;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
export const __private = {
|
|
450
|
+
claudeNativeHookSettings,
|
|
451
|
+
copyConfigTree,
|
|
452
|
+
copyDirBounded,
|
|
453
|
+
copyFileIfPresent,
|
|
454
|
+
ensureDirectory,
|
|
455
|
+
ensureNativeKeychainAlias,
|
|
456
|
+
ensureHomeAlias,
|
|
457
|
+
linkFileIfPresent,
|
|
458
|
+
shouldCopyConfigPath,
|
|
459
|
+
unlinkSymlinkIfPresent,
|
|
460
|
+
};
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Optional-package importer. Bare `import()` is followed by explicit
|
|
3
|
+
* node_modules fallbacks for NODE_PATH, ancestors, and Electron resources.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import fs from 'node:fs';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
9
|
+
|
|
10
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
|
|
12
|
+
function packageParts(specifier) {
|
|
13
|
+
const parts = String(specifier || '').split('/').filter(Boolean);
|
|
14
|
+
if (parts.length === 0) throw new Error('Package specifier is required');
|
|
15
|
+
if (parts[0].startsWith('@')) {
|
|
16
|
+
if (parts.length < 2) throw new Error(`Invalid scoped package specifier: ${specifier}`);
|
|
17
|
+
return {
|
|
18
|
+
packageName: `${parts[0]}/${parts[1]}`,
|
|
19
|
+
subpath: parts.slice(2).join('/'),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
packageName: parts[0],
|
|
24
|
+
subpath: parts.slice(1).join('/'),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function nodePathEntries() {
|
|
29
|
+
return String(process.env.NODE_PATH || '')
|
|
30
|
+
.split(path.delimiter)
|
|
31
|
+
.map((entry) => entry.trim())
|
|
32
|
+
.filter(Boolean);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function ancestorNodeModules() {
|
|
36
|
+
const entries = [];
|
|
37
|
+
let current = __dirname;
|
|
38
|
+
for (;;) {
|
|
39
|
+
entries.push(path.join(current, 'node_modules'));
|
|
40
|
+
const next = path.dirname(current);
|
|
41
|
+
if (next === current) break;
|
|
42
|
+
current = next;
|
|
43
|
+
}
|
|
44
|
+
return entries;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function candidateNodeModules() {
|
|
48
|
+
const resourcesPath = process.resourcesPath || '';
|
|
49
|
+
const entries = [
|
|
50
|
+
...nodePathEntries(),
|
|
51
|
+
...ancestorNodeModules(),
|
|
52
|
+
resourcesPath ? path.join(resourcesPath, 'app.asar', 'node_modules') : '',
|
|
53
|
+
resourcesPath ? path.join(resourcesPath, 'app.asar.unpacked', 'node_modules') : '',
|
|
54
|
+
resourcesPath ? path.join(resourcesPath, 'cli', 'node_modules') : '',
|
|
55
|
+
resourcesPath ? path.join(resourcesPath, 'runtime', 'node_modules') : '',
|
|
56
|
+
].filter(Boolean);
|
|
57
|
+
return Array.from(new Set(entries));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function readPackageJson(packageRoot) {
|
|
61
|
+
try {
|
|
62
|
+
return JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf8'));
|
|
63
|
+
} catch {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function exportedPath(exportsField, subpath) {
|
|
69
|
+
if (!exportsField) return '';
|
|
70
|
+
const key = subpath ? `./${subpath}` : '.';
|
|
71
|
+
const value = typeof exportsField === 'object' && !Array.isArray(exportsField)
|
|
72
|
+
? exportsField[key]
|
|
73
|
+
: (key === '.' ? exportsField : null);
|
|
74
|
+
if (!value) return '';
|
|
75
|
+
if (typeof value === 'string') return value;
|
|
76
|
+
if (typeof value !== 'object' || Array.isArray(value)) return '';
|
|
77
|
+
for (const condition of ['import', 'module', 'default', 'node', 'require']) {
|
|
78
|
+
if (typeof value[condition] === 'string') return value[condition];
|
|
79
|
+
}
|
|
80
|
+
return '';
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function resolvePackageEntry(specifier) {
|
|
84
|
+
const { packageName, subpath } = packageParts(specifier);
|
|
85
|
+
for (const nodeModules of candidateNodeModules()) {
|
|
86
|
+
const packageRoot = path.join(nodeModules, packageName);
|
|
87
|
+
const pkg = readPackageJson(packageRoot);
|
|
88
|
+
if (!pkg) continue;
|
|
89
|
+
|
|
90
|
+
const target = (
|
|
91
|
+
exportedPath(pkg.exports, subpath)
|
|
92
|
+
|| (subpath ? subpath : '')
|
|
93
|
+
|| pkg.module
|
|
94
|
+
|| pkg.main
|
|
95
|
+
|| 'index.js'
|
|
96
|
+
);
|
|
97
|
+
const resolved = path.join(packageRoot, target);
|
|
98
|
+
if (fs.existsSync(resolved)) return resolved;
|
|
99
|
+
}
|
|
100
|
+
return '';
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export async function importPackage(specifier) {
|
|
104
|
+
try {
|
|
105
|
+
return await import(specifier);
|
|
106
|
+
} catch (originalError) {
|
|
107
|
+
const resolved = resolvePackageEntry(specifier);
|
|
108
|
+
if (!resolved) throw originalError;
|
|
109
|
+
return import(pathToFileURL(resolved).href);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** Prepare the one managed CLI home owned by an agent installation. */
|
|
2
|
+
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import { runtimeEnv } from '../auth.js';
|
|
5
|
+
import { syncNativeProviderAuth } from './native-config.js';
|
|
6
|
+
|
|
7
|
+
export function managedRuntimeHome(contract) {
|
|
8
|
+
const runtimeHome = contract?.auth?.runtimeHome;
|
|
9
|
+
if (typeof runtimeHome !== 'string' || !runtimeHome.trim()) {
|
|
10
|
+
throw new Error(`${contract?.harness || 'Agent'} is missing a managed CLI home`);
|
|
11
|
+
}
|
|
12
|
+
return runtimeHome;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function prepareHarnessRuntime(contract, baseEnv = process.env) {
|
|
16
|
+
const runtimeHome = managedRuntimeHome(contract);
|
|
17
|
+
fs.mkdirSync(runtimeHome, { recursive: true });
|
|
18
|
+
const syncInfo = contract?.authMethod === 'provider_auth'
|
|
19
|
+
? syncNativeProviderAuth(contract)
|
|
20
|
+
: null;
|
|
21
|
+
return {
|
|
22
|
+
runtimeHome,
|
|
23
|
+
syncInfo,
|
|
24
|
+
env: runtimeEnv(contract, baseEnv),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* System-level instructions — the one machine-wide context document.
|
|
3
|
+
*
|
|
4
|
+
* `<AMALGM_DIR>/context/system-instructions.md` is user-authored and applies
|
|
5
|
+
* to every session on this computer. Project context is handled separately.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import fs from 'node:fs';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import { amalgmDir } from '../lib/runtime-paths.js';
|
|
11
|
+
|
|
12
|
+
const CONTEXT_FILE_MAX_CHARS = 12_000;
|
|
13
|
+
const SYSTEM_INSTRUCTIONS_FILENAME = 'system-instructions.md';
|
|
14
|
+
|
|
15
|
+
function systemContextPaths(dir) {
|
|
16
|
+
const root = path.join(path.resolve(dir), 'context');
|
|
17
|
+
return {
|
|
18
|
+
root,
|
|
19
|
+
instructions: path.join(root, SYSTEM_INSTRUCTIONS_FILENAME),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* A title-only document has no operative instructions.
|
|
25
|
+
*/
|
|
26
|
+
function isEmptyContextDoc(content) {
|
|
27
|
+
const body = String(content || '')
|
|
28
|
+
.replace(/^\uFEFF/, '')
|
|
29
|
+
.replace(/^#[^\n]*\n?/, '')
|
|
30
|
+
.trim();
|
|
31
|
+
return !body;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function cappedDocContent(content, maxChars = CONTEXT_FILE_MAX_CHARS) {
|
|
35
|
+
const text = String(content || '').trim();
|
|
36
|
+
if (!text) return '';
|
|
37
|
+
if (text.length <= maxChars) return text;
|
|
38
|
+
return `${text.slice(0, maxChars).trimEnd()}\n[truncated]`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function systemInstructionsPath() {
|
|
42
|
+
return systemContextPaths(amalgmDir()).instructions;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function escapeAttribute(value) {
|
|
46
|
+
return String(value || '')
|
|
47
|
+
.replace(/&/g, '&')
|
|
48
|
+
.replace(/"/g, '"')
|
|
49
|
+
.replace(/</g, '<')
|
|
50
|
+
.replace(/>/g, '>');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function systemInstructionsBlock() {
|
|
54
|
+
const filePath = systemInstructionsPath();
|
|
55
|
+
let content = '';
|
|
56
|
+
try {
|
|
57
|
+
content = fs.readFileSync(filePath, 'utf8');
|
|
58
|
+
} catch {
|
|
59
|
+
return '';
|
|
60
|
+
}
|
|
61
|
+
if (isEmptyContextDoc(content)) return '';
|
|
62
|
+
return [
|
|
63
|
+
`<system_instructions path="${escapeAttribute(filePath)}">`,
|
|
64
|
+
'These are user-authored instructions that apply to every session on this computer. Current user requests and higher-priority system/developer instructions win on conflict.',
|
|
65
|
+
'',
|
|
66
|
+
cappedDocContent(content),
|
|
67
|
+
'</system_instructions>',
|
|
68
|
+
].join('\n');
|
|
69
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { systemInstructionsBlock } from './system-instructions.js';
|
|
2
|
+
|
|
3
|
+
function trimBlock(value) {
|
|
4
|
+
return String(value || '').trim();
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function safeBlock(label, render) {
|
|
8
|
+
if (typeof render !== 'function') return '';
|
|
9
|
+
try {
|
|
10
|
+
return trimBlock(render());
|
|
11
|
+
} catch (error) {
|
|
12
|
+
const message = error instanceof Error ? error.message.split('\n')[0] : String(error);
|
|
13
|
+
console.warn(`[chat] ${label} failed:`, message);
|
|
14
|
+
return '';
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Compose machine, project, and exact agent-revision instructions once. */
|
|
19
|
+
export function composeSystemPrompt(contract, options = {}) {
|
|
20
|
+
const parts = [];
|
|
21
|
+
const systemInstructions = contract?.providerSessionId
|
|
22
|
+
? ''
|
|
23
|
+
: safeBlock('system instructions', systemInstructionsBlock);
|
|
24
|
+
const projectContext = safeBlock(
|
|
25
|
+
'project context',
|
|
26
|
+
typeof options.projectContextPromptBlock === 'function'
|
|
27
|
+
? () => options.projectContextPromptBlock(contract)
|
|
28
|
+
: null,
|
|
29
|
+
);
|
|
30
|
+
const agentInstructions = trimBlock(contract?.compiledAgentInstructions);
|
|
31
|
+
if (systemInstructions) parts.push(systemInstructions);
|
|
32
|
+
if (projectContext) parts.push(projectContext);
|
|
33
|
+
if (agentInstructions) parts.push(agentInstructions);
|
|
34
|
+
return parts.join('\n\n');
|
|
35
|
+
}
|