impel-cli 0.14.1 → 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/README.md +4 -0
- package/package.json +1 -1
- package/src/apps.js +116 -2
package/README.md
CHANGED
|
@@ -657,6 +657,10 @@ without contacting the gateway.
|
|
|
657
657
|
Start a new task after syncing. Claude Code detects updates to an existing
|
|
658
658
|
`agents/` directory within a few seconds, but if the sync created that directory
|
|
659
659
|
for the first time while Claude was already running, restart Claude once.
|
|
660
|
+
In Claude Desktop, selecting an agent on the first turn binds that Code session
|
|
661
|
+
to the agent. Follow-up `@` menus continue to offer that same bound agent and
|
|
662
|
+
insert a normal mention chip. Other agents remain available from a new task,
|
|
663
|
+
because the running Claude session cannot safely switch agents mid-session.
|
|
660
664
|
|
|
661
665
|
Invocation uses the clients' native agent behavior:
|
|
662
666
|
|
package/package.json
CHANGED
package/src/apps.js
CHANGED
|
@@ -83,6 +83,12 @@ const CLAUDE_PLAN_USAGE_GUARD = "if(!Ze().hasOrgPolicyBackend())return;if((r=fr(
|
|
|
83
83
|
const IMPEL_CLAUDE_PLAN_USAGE_GUARD = `${"if(!wze())return;".padEnd("if(!Ze().hasOrgPolicyBackend())return;".length, " ")}if((r=fr())`;
|
|
84
84
|
const CLAUDE_PLAN_USAGE_REQUEST = 'P.net.fetch(`${ht()}/api/organizations/${o}/usage`,{signal:AbortSignal.timeout(mcr)})';
|
|
85
85
|
const IMPEL_CLAUDE_PLAN_USAGE_REQUEST = 'P.net.fetch("https://gateway.useimpel.com/ui/a/u",{headers:{key:dR(Oe()).apiKey}})'.padEnd(CLAUDE_PLAN_USAGE_REQUEST.length, " ");
|
|
86
|
+
const CLAUDE_DRAFT_AGENT_MENTIONS = 'agentMentionsEnabled:"draft"===Be&&s';
|
|
87
|
+
const IMPEL_CLAUDE_BOUND_AGENT_MENTIONS = 'agentMentionsEnabled:"draft"===Be?s:Le?.agent??!1';
|
|
88
|
+
const CLAUDE_AGENT_MENTION_FILTER = 'return e.filter(e=>"user"===e.source&&(!s||e.name.toLowerCase().includes(s))).slice(0,10)';
|
|
89
|
+
const IMPEL_CLAUDE_BOUND_AGENT_MENTION_FILTER = 'return e.filter(e=>"user"===e.source&&(!s||e.name.toLowerCase().includes(s))&&("string"!=typeof y||e.name===y)).slice(0,10)';
|
|
90
|
+
const CLAUDE_AGENT_MENTION_SELECT = 'onSelect:t=>(e(String(t)),Promise.resolve(null))';
|
|
91
|
+
const IMPEL_CLAUDE_BOUND_AGENT_MENTION_SELECT = 'onSelect:t=>"string"==typeof y?Promise.resolve({chipText:String(t)}):(e(String(t)),Promise.resolve(null))';
|
|
86
92
|
const LEGACY_CLAUDE_SAFE_STORAGE_NAME = "Claude";
|
|
87
93
|
const CLAUDE_SAFE_STORAGE_METADATA = "safe-storage.json";
|
|
88
94
|
|
|
@@ -154,7 +160,7 @@ export const CURRENT_CONFIG_VERSION = 10;
|
|
|
154
160
|
// — which is what made every `impel update` re-trigger macOS permission
|
|
155
161
|
// prompts. Bump this ONLY when a code change alters the bytes of a built
|
|
156
162
|
// bundle; leave it alone for changes that don't touch bundle contents.
|
|
157
|
-
export const BUNDLE_BUILD_FINGERPRINT = "bundle-2026-07-
|
|
163
|
+
export const BUNDLE_BUILD_FINGERPRINT = "bundle-2026-07-17.1";
|
|
158
164
|
|
|
159
165
|
/** Parse the tenant's install manifest, or null when absent/corrupt. */
|
|
160
166
|
export function readTenantManifest(homeDir = os.homedir(), tenantId = null) {
|
|
@@ -205,6 +211,12 @@ export function bundleIsCurrent(status, { signingMode = desiredSigningMode() } =
|
|
|
205
211
|
&& compatibility.browserData === status.browserData
|
|
206
212
|
))
|
|
207
213
|
&& managedBundleMatchesCompatibility(status, compatibility)
|
|
214
|
+
&& (status.target !== "claude" || (
|
|
215
|
+
compatibility.patches?.followupBoundAgentEnable === 1
|
|
216
|
+
&& compatibility.patches?.followupBoundAgentFilter === 1
|
|
217
|
+
&& compatibility.patches?.followupBoundAgentChip === 1
|
|
218
|
+
&& managedClaudeRendererMatchesCompatibility(status, compatibility)
|
|
219
|
+
))
|
|
208
220
|
&& (status.target !== "chatgpt" || (
|
|
209
221
|
compatibility.vendorSignaturesPreserved === true
|
|
210
222
|
&& compatibility.resourceRoot === CHATGPT_RESOURCE_ROOT
|
|
@@ -214,6 +226,19 @@ export function bundleIsCurrent(status, { signingMode = desiredSigningMode() } =
|
|
|
214
226
|
);
|
|
215
227
|
}
|
|
216
228
|
|
|
229
|
+
function managedClaudeRendererMatchesCompatibility(status, compatibility) {
|
|
230
|
+
try {
|
|
231
|
+
const renderer = compatibility.rendererAssets?.agentMentions;
|
|
232
|
+
if (!renderer?.path || !renderer.sha256 || path.isAbsolute(renderer.path)) return false;
|
|
233
|
+
const root = path.resolve(status.launcher, "Contents", "Resources", "ion-dist");
|
|
234
|
+
const target = path.resolve(root, renderer.path);
|
|
235
|
+
if (!target.startsWith(`${root}${path.sep}`)) return false;
|
|
236
|
+
return sha256(fs.readFileSync(target)) === renderer.sha256;
|
|
237
|
+
} catch {
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
217
242
|
function managedBundleMatchesCompatibility(status, compatibility) {
|
|
218
243
|
try {
|
|
219
244
|
const managedVendorBundle = status.target === "claude"
|
|
@@ -1153,6 +1178,7 @@ function writeVendoredClaudeBundle(paths, vendorPath, gatewayUrl, safeStorageNam
|
|
|
1153
1178
|
);
|
|
1154
1179
|
}
|
|
1155
1180
|
|
|
1181
|
+
const agentMentionPatches = patchClaudeFollowupAgentMentions(staging);
|
|
1156
1182
|
const oldAsarHash = asarHeaderHash(asarPath);
|
|
1157
1183
|
let planUsageGuardPatchCount = 0;
|
|
1158
1184
|
let planUsageRequestPatchCount = 0;
|
|
@@ -1193,7 +1219,7 @@ function writeVendoredClaudeBundle(paths, vendorPath, gatewayUrl, safeStorageNam
|
|
|
1193
1219
|
|
|
1194
1220
|
const macos = path.join(staging, "Contents", "MacOS");
|
|
1195
1221
|
writeAtomic(path.join(staging, "Contents", "Resources", "impel-compatibility.json"), JSON.stringify({
|
|
1196
|
-
schemaVersion:
|
|
1222
|
+
schemaVersion: 4,
|
|
1197
1223
|
cliVersion: CLI_VERSION,
|
|
1198
1224
|
bundleFingerprint: BUNDLE_BUILD_FINGERPRINT,
|
|
1199
1225
|
signingMode: desiredSigningMode(),
|
|
@@ -1209,6 +1235,15 @@ function writeVendoredClaudeBundle(paths, vendorPath, gatewayUrl, safeStorageNam
|
|
|
1209
1235
|
aggregatePlanUsageGuard: planUsageGuardPatchCount,
|
|
1210
1236
|
aggregatePlanUsageRequest: planUsageRequestPatchCount,
|
|
1211
1237
|
safeStorageName: safeStorageNamePatchCount,
|
|
1238
|
+
followupBoundAgentEnable: agentMentionPatches.enable,
|
|
1239
|
+
followupBoundAgentFilter: agentMentionPatches.filter,
|
|
1240
|
+
followupBoundAgentChip: agentMentionPatches.chip,
|
|
1241
|
+
},
|
|
1242
|
+
rendererAssets: {
|
|
1243
|
+
agentMentions: {
|
|
1244
|
+
path: agentMentionPatches.path,
|
|
1245
|
+
sha256: agentMentionPatches.sha256,
|
|
1246
|
+
},
|
|
1212
1247
|
},
|
|
1213
1248
|
asarHeaderSha256: newAsarHash,
|
|
1214
1249
|
}, null, 2) + "\n", 0o644);
|
|
@@ -1330,6 +1365,85 @@ function linkVendoredChatGPTResources(bundle, vendorBundleName) {
|
|
|
1330
1365
|
}
|
|
1331
1366
|
}
|
|
1332
1367
|
|
|
1368
|
+
function patchClaudeFollowupAgentMentions(bundle) {
|
|
1369
|
+
const version = bundleVersion(bundle);
|
|
1370
|
+
if (version !== PINNED_VENDOR_APPS.claude.version) {
|
|
1371
|
+
throw new Error(
|
|
1372
|
+
`Claude follow-up agent mentions support is pinned to ${PINNED_VENDOR_APPS.claude.version}; found ${version || "unknown"}`,
|
|
1373
|
+
);
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
const ionDist = path.join(bundle, "Contents", "Resources", "ion-dist");
|
|
1377
|
+
const contracts = [
|
|
1378
|
+
CLAUDE_DRAFT_AGENT_MENTIONS,
|
|
1379
|
+
CLAUDE_AGENT_MENTION_FILTER,
|
|
1380
|
+
CLAUDE_AGENT_MENTION_SELECT,
|
|
1381
|
+
];
|
|
1382
|
+
const candidates = listJavaScriptFiles(ionDist).filter((candidate) => {
|
|
1383
|
+
const source = fs.readFileSync(candidate, "utf8");
|
|
1384
|
+
return contracts.every((contract) => source.includes(contract));
|
|
1385
|
+
});
|
|
1386
|
+
if (candidates.length !== 1) {
|
|
1387
|
+
throw new Error(
|
|
1388
|
+
`Claude follow-up agent-mention renderer expected 1 matching asset, found ${candidates.length}; update impel-cli before installing this desktop app version`,
|
|
1389
|
+
);
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
const renderer = candidates[0];
|
|
1393
|
+
let source = fs.readFileSync(renderer, "utf8");
|
|
1394
|
+
const enable = exactTextMatchCount(source, CLAUDE_DRAFT_AGENT_MENTIONS);
|
|
1395
|
+
const filter = exactTextMatchCount(source, CLAUDE_AGENT_MENTION_FILTER);
|
|
1396
|
+
const chip = exactTextMatchCount(source, CLAUDE_AGENT_MENTION_SELECT);
|
|
1397
|
+
for (const [label, count] of [["enable", enable], ["filter", filter], ["chip", chip]]) {
|
|
1398
|
+
if (count !== 1) {
|
|
1399
|
+
throw new Error(`Claude follow-up agent-mention ${label} patch expected 1 match, found ${count}`);
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
source = source
|
|
1404
|
+
.replace(CLAUDE_DRAFT_AGENT_MENTIONS, IMPEL_CLAUDE_BOUND_AGENT_MENTIONS)
|
|
1405
|
+
.replace(CLAUDE_AGENT_MENTION_FILTER, IMPEL_CLAUDE_BOUND_AGENT_MENTION_FILTER)
|
|
1406
|
+
.replace(CLAUDE_AGENT_MENTION_SELECT, IMPEL_CLAUDE_BOUND_AGENT_MENTION_SELECT);
|
|
1407
|
+
writeAtomic(renderer, source, fs.statSync(renderer).mode & 0o777);
|
|
1408
|
+
return {
|
|
1409
|
+
enable,
|
|
1410
|
+
filter,
|
|
1411
|
+
chip,
|
|
1412
|
+
path: path.relative(ionDist, renderer).split(path.sep).join("/"),
|
|
1413
|
+
sha256: sha256(Buffer.from(source, "utf8")),
|
|
1414
|
+
};
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
function listJavaScriptFiles(root) {
|
|
1418
|
+
const files = [];
|
|
1419
|
+
const visit = (directory) => {
|
|
1420
|
+
let entries;
|
|
1421
|
+
try {
|
|
1422
|
+
entries = fs.readdirSync(directory, { withFileTypes: true });
|
|
1423
|
+
} catch (error) {
|
|
1424
|
+
if (error?.code === "ENOENT") return;
|
|
1425
|
+
throw error;
|
|
1426
|
+
}
|
|
1427
|
+
for (const entry of entries) {
|
|
1428
|
+
const target = path.join(directory, entry.name);
|
|
1429
|
+
if (entry.isDirectory()) visit(target);
|
|
1430
|
+
else if (entry.isFile() && entry.name.endsWith(".js")) files.push(target);
|
|
1431
|
+
}
|
|
1432
|
+
};
|
|
1433
|
+
visit(root);
|
|
1434
|
+
return files.sort();
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
function exactTextMatchCount(source, expected) {
|
|
1438
|
+
let count = 0;
|
|
1439
|
+
for (
|
|
1440
|
+
let offset = source.indexOf(expected);
|
|
1441
|
+
offset !== -1;
|
|
1442
|
+
offset = source.indexOf(expected, offset + expected.length)
|
|
1443
|
+
) count += 1;
|
|
1444
|
+
return count;
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1333
1447
|
function patchFixedWidthAsarString(asarPath, original, replacement, label) {
|
|
1334
1448
|
if (Buffer.byteLength(original, "latin1") !== Buffer.byteLength(replacement, "latin1")) {
|
|
1335
1449
|
throw new Error(`${label} replacement must preserve the vendor ASAR byte length`);
|