amalgm 0.1.114 → 0.1.115
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/package.json
CHANGED
|
@@ -120,6 +120,25 @@ function runtimeHome({ amalgmDir, harness, agentConfigId, runtimeHomeLayout }) {
|
|
|
120
120
|
);
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
+
function legacyRuntimeHomeSources({ amalgmDir, harness, runtimeHome }) {
|
|
124
|
+
const harnessHome = path.join(amalgmDir, 'cli-homes', safePathSegment(harness));
|
|
125
|
+
const canonical = path.resolve(runtimeHome || '');
|
|
126
|
+
const sources = [path.join(harnessHome, 'home')];
|
|
127
|
+
try {
|
|
128
|
+
for (const entry of fs.readdirSync(harnessHome, { withFileTypes: true })) {
|
|
129
|
+
if (!entry.isDirectory()) continue;
|
|
130
|
+
if (entry.name === 'agents' || entry.name === 'home') continue;
|
|
131
|
+
if (!/^(amalgm|provider|byok)-/.test(entry.name)) continue;
|
|
132
|
+
sources.push(path.join(harnessHome, entry.name));
|
|
133
|
+
}
|
|
134
|
+
} catch {
|
|
135
|
+
// The harness home is created lazily.
|
|
136
|
+
}
|
|
137
|
+
return [...new Set(sources
|
|
138
|
+
.map((source) => path.resolve(source))
|
|
139
|
+
.filter((source) => source && source !== canonical && !canonical.startsWith(`${source}${path.sep}`)))];
|
|
140
|
+
}
|
|
141
|
+
|
|
123
142
|
function providerAuthFingerprint(harness) {
|
|
124
143
|
return fingerprint(providerAuthIdentity(harness));
|
|
125
144
|
}
|
|
@@ -153,6 +172,7 @@ function authEnvelope({ harness, authMethod, sessionId, localBaseUrl, proxyToken
|
|
|
153
172
|
} else if (method === 'provider_auth') {
|
|
154
173
|
tokenFingerprint = providerAuthFingerprint(harness);
|
|
155
174
|
}
|
|
175
|
+
const runtimeHomePath = runtimeHome({ amalgmDir, harness, agentConfigId, runtimeHomeLayout });
|
|
156
176
|
return {
|
|
157
177
|
method,
|
|
158
178
|
baseUrl,
|
|
@@ -162,7 +182,8 @@ function authEnvelope({ harness, authMethod, sessionId, localBaseUrl, proxyToken
|
|
|
162
182
|
forwardBaseUrl,
|
|
163
183
|
tokenRef,
|
|
164
184
|
tokenFingerprint,
|
|
165
|
-
runtimeHome:
|
|
185
|
+
runtimeHome: runtimeHomePath,
|
|
186
|
+
runtimeHomeSources: legacyRuntimeHomeSources({ amalgmDir, harness, runtimeHome: runtimeHomePath }),
|
|
166
187
|
};
|
|
167
188
|
}
|
|
168
189
|
|
|
@@ -222,5 +243,6 @@ module.exports = {
|
|
|
222
243
|
authEnvelope,
|
|
223
244
|
coerceAuth,
|
|
224
245
|
fingerprint,
|
|
246
|
+
legacyRuntimeHomeSources,
|
|
225
247
|
runtimeEnv,
|
|
226
248
|
};
|
|
@@ -5,7 +5,8 @@ const fs = require('node:fs');
|
|
|
5
5
|
const os = require('node:os');
|
|
6
6
|
const path = require('node:path');
|
|
7
7
|
const test = require('node:test');
|
|
8
|
-
const { authEnvelope, runtimeEnv } = require('../auth');
|
|
8
|
+
const { authEnvelope, legacyRuntimeHomeSources, runtimeEnv } = require('../auth');
|
|
9
|
+
const { matchRuntimeHome } = require('../tooling/runtime-home');
|
|
9
10
|
|
|
10
11
|
test('codex uses one stable per-agent CLI home across auth methods', () => {
|
|
11
12
|
const amalgmDir = fs.mkdtempSync(path.join(os.tmpdir(), 'amalgm-auth-test-'));
|
|
@@ -139,3 +140,69 @@ test('provided CLI home path cannot move an agent off its canonical home', () =>
|
|
|
139
140
|
|
|
140
141
|
assert.equal(envelope.runtimeHome, '/tmp/amalgm-test/cli-homes/codex/agents/codex/home');
|
|
141
142
|
});
|
|
143
|
+
|
|
144
|
+
test('per-agent auth envelope exposes legacy homes as match sources', () => {
|
|
145
|
+
const amalgmDir = fs.mkdtempSync(path.join(os.tmpdir(), 'amalgm-home-sources-'));
|
|
146
|
+
try {
|
|
147
|
+
const harnessHome = path.join(amalgmDir, 'cli-homes', 'codex');
|
|
148
|
+
fs.mkdirSync(path.join(harnessHome, 'home'), { recursive: true });
|
|
149
|
+
fs.mkdirSync(path.join(harnessHome, 'provider-abc123'), { recursive: true });
|
|
150
|
+
fs.mkdirSync(path.join(harnessHome, 'amalgm-def456'), { recursive: true });
|
|
151
|
+
fs.mkdirSync(path.join(harnessHome, 'agents', 'codex', 'home'), { recursive: true });
|
|
152
|
+
|
|
153
|
+
const envelope = authEnvelope({
|
|
154
|
+
harness: 'codex',
|
|
155
|
+
authMethod: 'provider_auth',
|
|
156
|
+
sessionId: 'session-test',
|
|
157
|
+
amalgmDir,
|
|
158
|
+
agentConfigId: 'codex',
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
assert.equal(envelope.runtimeHome, path.join(harnessHome, 'agents', 'codex', 'home'));
|
|
162
|
+
assert.deepEqual(envelope.runtimeHomeSources.sort(), [
|
|
163
|
+
path.join(harnessHome, 'amalgm-def456'),
|
|
164
|
+
path.join(harnessHome, 'home'),
|
|
165
|
+
path.join(harnessHome, 'provider-abc123'),
|
|
166
|
+
].sort());
|
|
167
|
+
assert.deepEqual(legacyRuntimeHomeSources({
|
|
168
|
+
amalgmDir,
|
|
169
|
+
harness: 'codex',
|
|
170
|
+
runtimeHome: envelope.runtimeHome,
|
|
171
|
+
}).sort(), envelope.runtimeHomeSources.sort());
|
|
172
|
+
} finally {
|
|
173
|
+
fs.rmSync(amalgmDir, { recursive: true, force: true });
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test('runtime home matcher preserves agent-id home and imports missing legacy sessions', () => {
|
|
178
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'amalgm-home-match-'));
|
|
179
|
+
try {
|
|
180
|
+
const target = path.join(root, 'cli-homes', 'codex', 'agents', 'codex', 'home');
|
|
181
|
+
const legacy = path.join(root, 'cli-homes', 'codex', 'home');
|
|
182
|
+
const provider = path.join(root, 'cli-homes', 'codex', 'provider-abc123');
|
|
183
|
+
const legacySession = path.join(legacy, 'sessions', '2026', '06', '15', 'rollout-old.jsonl');
|
|
184
|
+
const targetSession = path.join(target, 'sessions', '2026', '06', '15', 'rollout-new.jsonl');
|
|
185
|
+
const providerSession = path.join(provider, 'sessions', '2026', '06', '16', 'rollout-provider.jsonl');
|
|
186
|
+
const overlapping = path.join(legacy, 'config.toml');
|
|
187
|
+
const targetOverlapping = path.join(target, 'config.toml');
|
|
188
|
+
fs.mkdirSync(path.dirname(legacySession), { recursive: true });
|
|
189
|
+
fs.mkdirSync(path.dirname(targetSession), { recursive: true });
|
|
190
|
+
fs.mkdirSync(path.dirname(providerSession), { recursive: true });
|
|
191
|
+
fs.writeFileSync(legacySession, 'old session\n');
|
|
192
|
+
fs.writeFileSync(targetSession, 'new session\n');
|
|
193
|
+
fs.writeFileSync(providerSession, 'provider session\n');
|
|
194
|
+
fs.writeFileSync(overlapping, 'legacy config\n');
|
|
195
|
+
fs.writeFileSync(targetOverlapping, 'target config\n');
|
|
196
|
+
|
|
197
|
+
const summary = matchRuntimeHome(target, [legacy, provider]);
|
|
198
|
+
|
|
199
|
+
assert.equal(fs.readFileSync(path.join(target, 'sessions', '2026', '06', '15', 'rollout-old.jsonl'), 'utf8'), 'old session\n');
|
|
200
|
+
assert.equal(fs.readFileSync(targetSession, 'utf8'), 'new session\n');
|
|
201
|
+
assert.equal(fs.readFileSync(path.join(target, 'sessions', '2026', '06', '16', 'rollout-provider.jsonl'), 'utf8'), 'provider session\n');
|
|
202
|
+
assert.equal(fs.readFileSync(targetOverlapping, 'utf8'), 'target config\n');
|
|
203
|
+
assert.ok(summary.length >= 1);
|
|
204
|
+
assert.equal(matchRuntimeHome(target, [legacy, provider]).length, 0);
|
|
205
|
+
} finally {
|
|
206
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
207
|
+
}
|
|
208
|
+
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
4
5
|
const { runtimeEnv } = require('../auth');
|
|
5
6
|
const { syncNativeProviderAuth } = require('./native-config');
|
|
6
7
|
|
|
@@ -12,20 +13,93 @@ function managedRuntimeHome(contract) {
|
|
|
12
13
|
return runtimeHome;
|
|
13
14
|
}
|
|
14
15
|
|
|
16
|
+
function isSubpath(parent, child) {
|
|
17
|
+
const relative = path.relative(parent, child);
|
|
18
|
+
return relative && !relative.startsWith('..') && !path.isAbsolute(relative);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function mergeMissingEntries(source, target, stats) {
|
|
22
|
+
const sourceStat = fs.lstatSync(source);
|
|
23
|
+
if (fs.existsSync(target)) {
|
|
24
|
+
if (!sourceStat.isDirectory() || !fs.lstatSync(target).isDirectory()) {
|
|
25
|
+
stats.skipped += 1;
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
for (const entry of fs.readdirSync(source)) {
|
|
29
|
+
mergeMissingEntries(path.join(source, entry), path.join(target, entry), stats);
|
|
30
|
+
}
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (sourceStat.isSymbolicLink()) {
|
|
35
|
+
fs.symlinkSync(fs.readlinkSync(source), target);
|
|
36
|
+
stats.copied += 1;
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (sourceStat.isDirectory()) {
|
|
41
|
+
fs.mkdirSync(target, { recursive: true, mode: sourceStat.mode });
|
|
42
|
+
stats.dirs += 1;
|
|
43
|
+
for (const entry of fs.readdirSync(source)) {
|
|
44
|
+
mergeMissingEntries(path.join(source, entry), path.join(target, entry), stats);
|
|
45
|
+
}
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
50
|
+
fs.copyFileSync(source, target);
|
|
51
|
+
try {
|
|
52
|
+
fs.chmodSync(target, sourceStat.mode);
|
|
53
|
+
} catch {
|
|
54
|
+
// Best effort; content preservation matters more than exact mode metadata.
|
|
55
|
+
}
|
|
56
|
+
stats.copied += 1;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function matchRuntimeHome(runtimeHome, sources = []) {
|
|
60
|
+
const target = path.resolve(runtimeHome);
|
|
61
|
+
fs.mkdirSync(target, { recursive: true });
|
|
62
|
+
const marker = path.join(target, '.amalgm-home-match.json');
|
|
63
|
+
if (fs.existsSync(marker) && process.env.AMALGM_FORCE_HOME_MATCH !== '1') return [];
|
|
64
|
+
const summaries = [];
|
|
65
|
+
const checked = [];
|
|
66
|
+
for (const rawSource of Array.isArray(sources) ? sources : []) {
|
|
67
|
+
if (typeof rawSource !== 'string' || !rawSource.trim()) continue;
|
|
68
|
+
const source = path.resolve(rawSource);
|
|
69
|
+
if (source === target || isSubpath(target, source) || isSubpath(source, target)) continue;
|
|
70
|
+
if (!fs.existsSync(source)) continue;
|
|
71
|
+
checked.push(source);
|
|
72
|
+
const stats = { source, copied: 0, dirs: 0, skipped: 0 };
|
|
73
|
+
mergeMissingEntries(source, target, stats);
|
|
74
|
+
if (stats.copied || stats.dirs) summaries.push(stats);
|
|
75
|
+
}
|
|
76
|
+
if (checked.length) {
|
|
77
|
+
fs.writeFileSync(marker, `${JSON.stringify({
|
|
78
|
+
matched_at: new Date().toISOString(),
|
|
79
|
+
checked,
|
|
80
|
+
sources: summaries,
|
|
81
|
+
}, null, 2)}\n`);
|
|
82
|
+
}
|
|
83
|
+
return summaries;
|
|
84
|
+
}
|
|
85
|
+
|
|
15
86
|
function prepareHarnessRuntime(contract, baseEnv = process.env) {
|
|
16
87
|
const runtimeHome = managedRuntimeHome(contract);
|
|
17
88
|
fs.mkdirSync(runtimeHome, { recursive: true });
|
|
89
|
+
const homeMatch = matchRuntimeHome(runtimeHome, contract?.auth?.runtimeHomeSources);
|
|
18
90
|
const syncInfo = contract?.authMethod === 'provider_auth'
|
|
19
91
|
? syncNativeProviderAuth(contract)
|
|
20
92
|
: null;
|
|
21
93
|
return {
|
|
22
94
|
runtimeHome,
|
|
95
|
+
homeMatch,
|
|
23
96
|
syncInfo,
|
|
24
97
|
env: runtimeEnv(contract, baseEnv),
|
|
25
98
|
};
|
|
26
99
|
}
|
|
27
100
|
|
|
28
101
|
module.exports = {
|
|
102
|
+
matchRuntimeHome,
|
|
29
103
|
managedRuntimeHome,
|
|
30
104
|
prepareHarnessRuntime,
|
|
31
105
|
};
|