@withtruing/companion 0.14.0 → 0.14.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +79 -33
- package/dist/http-client.js +16 -4
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -4,7 +4,7 @@ import { existsSync } from 'node:fs';
|
|
|
4
4
|
import { dirname, join, resolve } from 'node:path';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
6
|
import { addConnection, cleanupMigratedManagedRepositories, configPath, enableWorkspacePolling, hasTemporaryManagedRepository, loadConfig, managedReposDir, migrateManagedRepositoriesToTemporaryStorage, normalizeConsoleUrl, recordWorkspaceId, removePairing, } from './config.js';
|
|
7
|
-
import { CompanionClient, CompanionHttpError, CLAUDE_CLI_FAST_MODEL, CODEX_FAST_MODEL, PairingHttpError, } from './http-client.js';
|
|
7
|
+
import { CompanionClient, CompanionHttpError, CLAUDE_CLI_FAST_MODEL, wholeReadBackendName as sharedWholeReadBackendName, CODEX_FAST_MODEL, PairingHttpError, } from './http-client.js';
|
|
8
8
|
import { assertIsRepo, currentCommit, GIT_UNAVAILABLE_MESSAGE, REMOTE_TROUBLESHOOTING_HINT, repositoryIdentity, RepositoryIdentityError, repositoryTopLevel, } from './local-scan.js';
|
|
9
9
|
import { runAnalyseShipmentTask, runDesignPromptTask, runGenerateSpecBatchTask, runGenericTask, runProposeNewCapabilitiesTask, runProposeSkeletonTask, runStructuredModelCall, runVerifyModuleTask } from './api-key-caller.js';
|
|
10
10
|
import { runAnalyseShipmentTaskViaCli, runDesignPromptTaskViaCli, runGenerateSpecBatchTaskViaCli, runGenericTaskViaCli, runProposeNewCapabilitiesTaskViaCli, runProposeSkeletonTaskViaCli, runStructuredModelCallViaCli, runVerifyModuleTaskViaCli, runWholeReadModelCallViaCli } from './claude-cli-caller.js';
|
|
@@ -21,7 +21,7 @@ import { AttemptSourceCleanupError, createCompanionAttemptSourceManager, startAt
|
|
|
21
21
|
import { createWholeReadAssignmentPool, } from './whole-read-pool.js';
|
|
22
22
|
import { createWholeReadSharedSourceManager, } from './whole-read-shared-source.js';
|
|
23
23
|
import { checkConnectionRepositoryReadiness, createRepositoryProvider, proveManagedRepositoryAccess, RepositoryProviderError, } from './repository-provider.js';
|
|
24
|
-
import { WholeReadGitError } from './whole-read-git.js';
|
|
24
|
+
import { WholeReadGitError, resolveLocalBuildCommit } from './whole-read-git.js';
|
|
25
25
|
import { acquireGithubCompanionSource, acquireAssignedSource, assignmentRouteReadinessCode, acquireLocalSource, dispatchWholeReadClaim, } from './whole-read-runner.js';
|
|
26
26
|
import { WHOLE_READ_PHASE_POLICY } from './whole-read/phase-policy.js';
|
|
27
27
|
import { acquireInstanceLock, installService, isNpxCachePath, releaseInstanceLock, runDaemon, servicePaths, serviceStatus, startService, stopService, uninstallService, } from './service.js';
|
|
@@ -1180,11 +1180,75 @@ function wholeReadTransport(client, onUsageLimit) {
|
|
|
1180
1180
|
};
|
|
1181
1181
|
}
|
|
1182
1182
|
function wholeReadBackendName(runtime) {
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1183
|
+
return sharedWholeReadBackendName(runtime.backend);
|
|
1184
|
+
}
|
|
1185
|
+
export function assignmentSourceProblem(reason) {
|
|
1186
|
+
if (!(reason instanceof WholeReadGitError))
|
|
1187
|
+
return 'git_failed';
|
|
1188
|
+
if (reason.code === 'snapshot_unavailable')
|
|
1189
|
+
return 'snapshot_unavailable';
|
|
1190
|
+
if (reason.code === 'branch_unavailable')
|
|
1191
|
+
return 'branch_missing';
|
|
1192
|
+
if (reason.code === 'repository_unavailable')
|
|
1193
|
+
return 'folder_missing';
|
|
1194
|
+
return 'git_failed';
|
|
1195
|
+
}
|
|
1196
|
+
function safeReason(reason) {
|
|
1197
|
+
const text = reason instanceof Error ? reason.message : String(reason);
|
|
1198
|
+
return text.replace(/\b([a-z][a-z0-9+.-]*:\/\/)[^\s/@]+@/gi, '$1').replace(/\s+/g, ' ').slice(0, 200);
|
|
1199
|
+
}
|
|
1200
|
+
/** Runs the offered assignment's source check; says why on failure instead of swallowing it. */
|
|
1201
|
+
export async function verifyAssignmentSource(input) {
|
|
1202
|
+
const { assignment, local } = input;
|
|
1203
|
+
try {
|
|
1204
|
+
const acquired = await input.sharedSources.acquire(wholeReadSharedSourceKey(input.pairing, assignment), async (lease, signal) => {
|
|
1205
|
+
const source = await acquireAssignedSource({
|
|
1206
|
+
route: assignment.route,
|
|
1207
|
+
acquireLocal: () => local?.branch
|
|
1208
|
+
? acquireLocalSource({ repoPath: local.repoPath, branch: local.branch,
|
|
1209
|
+
snapshotSha: assignment.snapshotSha, signal })
|
|
1210
|
+
: Promise.reject(new WholeReadGitError(local ? 'branch_unavailable' : 'repository_unavailable')),
|
|
1211
|
+
acquireGithubCompanion: () => {
|
|
1212
|
+
const repositoryUrl = `https://github.com/${githubLabel(parseGitHubRepo(assignment.canonicalRepository))}.git`;
|
|
1213
|
+
return acquireGithubCompanionSource({ lease, repositoryUrl,
|
|
1214
|
+
snapshotSha: assignment.snapshotSha, signal });
|
|
1215
|
+
},
|
|
1216
|
+
});
|
|
1217
|
+
if (!source)
|
|
1218
|
+
throw new WholeReadGitError('snapshot_unavailable');
|
|
1219
|
+
return source;
|
|
1220
|
+
});
|
|
1221
|
+
await acquired.release();
|
|
1222
|
+
input.reportedFailures?.delete(`${assignment.id}:${assignment.assignmentRevision}`);
|
|
1223
|
+
return { verified: true };
|
|
1224
|
+
}
|
|
1225
|
+
catch (reason) {
|
|
1226
|
+
const sourceProblem = assignmentSourceProblem(reason);
|
|
1227
|
+
const failureKey = `${assignment.id}:${assignment.assignmentRevision}`;
|
|
1228
|
+
if (input.reportedFailures?.get(failureKey) === sourceProblem)
|
|
1229
|
+
return { verified: false, sourceProblem };
|
|
1230
|
+
input.reportedFailures?.set(failureKey, sourceProblem);
|
|
1231
|
+
const name = assignment.canonicalRepository.split('/').filter(Boolean).pop() ?? 'the repository';
|
|
1232
|
+
const prefix = `[${new Date().toISOString()}] Couldn't prepare ${name} for the build`;
|
|
1233
|
+
if (sourceProblem === 'snapshot_unavailable' && assignment.route === 'local' && local?.branch) {
|
|
1234
|
+
let folderCommit = 'unknown';
|
|
1235
|
+
try {
|
|
1236
|
+
folderCommit = (await resolveLocalBuildCommit(local.repoPath, local.branch, new AbortController().signal)).slice(0, 7);
|
|
1237
|
+
}
|
|
1238
|
+
catch { /* the reason below still names the code */ }
|
|
1239
|
+
input.log(`${prefix}: the folder's ${local.branch} branch is at ${folderCommit}, but Truing asked for ${assignment.snapshotSha.slice(0, 7)} (snapshot_unavailable). Bring the folder level with GitHub and start the map again.`);
|
|
1240
|
+
}
|
|
1241
|
+
else if (reason instanceof WholeReadGitError) {
|
|
1242
|
+
input.log(`${prefix}: ${sourceProblem === 'branch_missing' ? "the folder's branch isn't set"
|
|
1243
|
+
: sourceProblem === 'folder_missing' ? "the folder isn't where Truing expects it"
|
|
1244
|
+
: sourceProblem === 'snapshot_unavailable' ? 'the code Truing asked for could not be fetched'
|
|
1245
|
+
: 'git could not read the code'} (${reason.code}).`);
|
|
1246
|
+
}
|
|
1247
|
+
else {
|
|
1248
|
+
input.log(`${prefix}: ${safeReason(reason)} (${sourceProblem}).`);
|
|
1249
|
+
}
|
|
1250
|
+
return { verified: false, sourceProblem };
|
|
1251
|
+
}
|
|
1188
1252
|
}
|
|
1189
1253
|
async function runWholeReadAssignment(input) {
|
|
1190
1254
|
const connection = input.connections.find((candidate) => candidate.connectionId === input.assignment.connectionId);
|
|
@@ -1207,35 +1271,14 @@ async function runWholeReadAssignment(input) {
|
|
|
1207
1271
|
return { status: 'not_claimed' };
|
|
1208
1272
|
}
|
|
1209
1273
|
if (input.assignment.state === 'offered') {
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
const source = await acquireAssignedSource({
|
|
1214
|
-
route: input.assignment.route,
|
|
1215
|
-
acquireLocal: () => local?.branch
|
|
1216
|
-
? acquireLocalSource({ repoPath: local.repoPath, branch: local.branch,
|
|
1217
|
-
snapshotSha: input.assignment.snapshotSha, signal })
|
|
1218
|
-
: Promise.reject(new WholeReadGitError('snapshot_unavailable')),
|
|
1219
|
-
acquireGithubCompanion: () => {
|
|
1220
|
-
const repositoryUrl = `https://github.com/${githubLabel(parseGitHubRepo(input.assignment.canonicalRepository))}.git`;
|
|
1221
|
-
return acquireGithubCompanionSource({ lease, repositoryUrl,
|
|
1222
|
-
snapshotSha: input.assignment.snapshotSha, signal });
|
|
1223
|
-
},
|
|
1224
|
-
});
|
|
1225
|
-
if (!source)
|
|
1226
|
-
throw new WholeReadGitError('snapshot_unavailable');
|
|
1227
|
-
return source;
|
|
1228
|
-
});
|
|
1229
|
-
await acquired.release();
|
|
1230
|
-
verified = true;
|
|
1231
|
-
}
|
|
1232
|
-
catch {
|
|
1233
|
-
verified = false;
|
|
1234
|
-
}
|
|
1274
|
+
const check = await verifyAssignmentSource({ pairing: input.pairing, assignment: input.assignment,
|
|
1275
|
+
local, sharedSources: input.sharedSources, log: input.log, reportedFailures: input.reportedFailures });
|
|
1276
|
+
const verified = check.verified;
|
|
1235
1277
|
const acceptance = await input.client.acceptBuildAssignment(input.assignment, {
|
|
1236
1278
|
lsRemoteVerified: verified, exactCommitFetched: verified,
|
|
1237
1279
|
backend: input.assignment.frozenBackend, backendReady,
|
|
1238
1280
|
routeSupported: routeReadiness === 'ready',
|
|
1281
|
+
...(check.verified ? {} : { sourceProblem: check.sourceProblem }),
|
|
1239
1282
|
});
|
|
1240
1283
|
if (acceptance.status !== 'ready')
|
|
1241
1284
|
return { status: 'not_claimed' };
|
|
@@ -1368,7 +1411,8 @@ async function runPairing(pairing, options) {
|
|
|
1368
1411
|
return [[connection.connectionId, CODEX_FAST_MODEL]];
|
|
1369
1412
|
return [];
|
|
1370
1413
|
}));
|
|
1371
|
-
const
|
|
1414
|
+
const backendsByConnection = Object.fromEntries(pairing.connections.map((connection) => [connection.connectionId, sharedWholeReadBackendName(connection.backend)]));
|
|
1415
|
+
const client = new CompanionClient(pairing.url, pairing.token, fastModelsByConnection, backendsByConnection);
|
|
1372
1416
|
const tokenRejected = () => {
|
|
1373
1417
|
error(`The Console at ${pairing.url} no longer accepts this computer's token — stopped working for it. Pair again from that Console to reconnect.`);
|
|
1374
1418
|
return 'token_rejected';
|
|
@@ -1503,6 +1547,7 @@ async function runPairing(pairing, options) {
|
|
|
1503
1547
|
const sharedSources = createWholeReadSharedSourceManager({
|
|
1504
1548
|
onCleanupPending: () => { error('[whole-read-temp] cleanup_pending'); },
|
|
1505
1549
|
});
|
|
1550
|
+
const reportedFailures = new Map();
|
|
1506
1551
|
const wholeReadPool = createWholeReadAssignmentPool({
|
|
1507
1552
|
key: (assignment) => assignment.id,
|
|
1508
1553
|
mayClaim,
|
|
@@ -1511,6 +1556,7 @@ async function runPairing(pairing, options) {
|
|
|
1511
1556
|
usageClaimLimit--; },
|
|
1512
1557
|
run: (assignment) => runWholeReadAssignment({ pairing, client, assignment,
|
|
1513
1558
|
connections: activeConnections, runtimes, workspaceRuntime, sharedSources,
|
|
1559
|
+
log: (message) => error(message), reportedFailures,
|
|
1514
1560
|
onUsageLimit: () => recordCompletedClaim('usage limit reached'),
|
|
1515
1561
|
shouldStop: options.shouldStop }),
|
|
1516
1562
|
onError: (reason) => {
|
package/dist/http-client.js
CHANGED
|
@@ -30,10 +30,20 @@ export const COMPANION_CAPABILITIES = [
|
|
|
30
30
|
export const CLAUDE_CLI_FAST_MODEL = 'claude-haiku-5-5';
|
|
31
31
|
export const CODEX_FAST_MODEL = 'gpt-5.6-luna';
|
|
32
32
|
export const WHOLE_READ_FAST_MODEL_CAPABILITY = 'whole-read-fast-model@';
|
|
33
|
-
const
|
|
33
|
+
export const WHOLE_READ_BACKEND_CAPABILITY = 'whole-read-backend@';
|
|
34
|
+
/** The one mapping from a connection's backend to the name the server freezes and the readiness report uses. */
|
|
35
|
+
export function wholeReadBackendName(backend) {
|
|
36
|
+
if (backend === 'api-key')
|
|
37
|
+
return 'anthropic_api';
|
|
38
|
+
if (backend === 'cli')
|
|
39
|
+
return 'claude_cli';
|
|
40
|
+
return 'codex';
|
|
41
|
+
}
|
|
42
|
+
export const capabilityHeaders = (fastModel, backend) => ({
|
|
34
43
|
'X-Companion-Capabilities': [...COMPANION_CAPABILITIES,
|
|
35
44
|
...(fastModel && /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(fastModel)
|
|
36
|
-
? [`${WHOLE_READ_FAST_MODEL_CAPABILITY}${fastModel}`] : [])
|
|
45
|
+
? [`${WHOLE_READ_FAST_MODEL_CAPABILITY}${fastModel}`] : []),
|
|
46
|
+
...(backend && /^[a-z_]{1,32}$/.test(backend) ? [`${WHOLE_READ_BACKEND_CAPABILITY}${backend}`] : [])].join(','),
|
|
37
47
|
});
|
|
38
48
|
export class CompanionHttpError extends Error {
|
|
39
49
|
status;
|
|
@@ -61,13 +71,15 @@ export class CompanionClient {
|
|
|
61
71
|
url;
|
|
62
72
|
token;
|
|
63
73
|
fastModelsByConnection;
|
|
64
|
-
|
|
74
|
+
backendsByConnection;
|
|
75
|
+
constructor(url, token, fastModelsByConnection = {}, backendsByConnection = {}) {
|
|
65
76
|
this.url = url;
|
|
66
77
|
this.token = token;
|
|
67
78
|
this.fastModelsByConnection = fastModelsByConnection;
|
|
79
|
+
this.backendsByConnection = backendsByConnection;
|
|
68
80
|
}
|
|
69
81
|
capabilityHeaders(connectionId) {
|
|
70
|
-
return capabilityHeaders(connectionId ? this.fastModelsByConnection[connectionId] : undefined);
|
|
82
|
+
return capabilityHeaders(connectionId ? this.fastModelsByConnection[connectionId] : undefined, connectionId ? this.backendsByConnection[connectionId] : undefined);
|
|
71
83
|
}
|
|
72
84
|
request(path, timeoutMs, init) {
|
|
73
85
|
return fetch(`${this.url}${path}`, {
|