@alfe.ai/claude-code-host 0.1.12 → 0.1.14
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/host-config.cjs +108 -42
- package/dist/host-config.js +108 -42
- package/dist/index.d.cts +128 -11
- package/dist/index.d.ts +128 -11
- package/package.json +2 -2
package/dist/host-config.cjs
CHANGED
|
@@ -3,9 +3,9 @@ let _alfe_ai_chat = require("@alfe.ai/chat");
|
|
|
3
3
|
let node_fs_promises = require("node:fs/promises");
|
|
4
4
|
let node_path = require("node:path");
|
|
5
5
|
let node_os = require("node:os");
|
|
6
|
+
let node_crypto = require("node:crypto");
|
|
6
7
|
let node_child_process = require("node:child_process");
|
|
7
8
|
let node_readline = require("node:readline");
|
|
8
|
-
let node_crypto = require("node:crypto");
|
|
9
9
|
let node_fs = require("node:fs");
|
|
10
10
|
let _alfe_ai_config = require("@alfe.ai/config");
|
|
11
11
|
//#region src/child-env.ts
|
|
@@ -925,6 +925,7 @@ const MAX_IDENTIFIER_CHARS = 512;
|
|
|
925
925
|
const MAX_MEMORY_CONTEXT_CHARS = 64 * 1024;
|
|
926
926
|
const MAX_CLAUDE_SESSION_IDS = 1e3;
|
|
927
927
|
const MAX_IDEMPOTENCY_KEYS = 1e4;
|
|
928
|
+
const MAX_IDEMPOTENCY_RESULT_BYTES = 8 * 1024 * 1024;
|
|
928
929
|
const SHUTDOWN_GRACE_MS = 3e3;
|
|
929
930
|
function asRecord(value) {
|
|
930
931
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
@@ -1122,12 +1123,10 @@ var ClaudeCodeHost = class {
|
|
|
1122
1123
|
/** Alfe sessionId → claude session id map (mirrors ~/.alfe/claude-code/sessions.json). */
|
|
1123
1124
|
claudeSessionIds = /* @__PURE__ */ new Map();
|
|
1124
1125
|
claudeSessionsPath;
|
|
1125
|
-
/**
|
|
1126
|
+
/** Session → one running turn's wire identity and owned child. */
|
|
1126
1127
|
runningTurns = /* @__PURE__ */ new Map();
|
|
1127
|
-
/**
|
|
1128
|
-
|
|
1129
|
-
/** RPC sessionKey → resolved SessionDriver so chat.abort can SIGINT the child. */
|
|
1130
|
-
activeDriverBySession = /* @__PURE__ */ new Map();
|
|
1128
|
+
/** Process-local admission/result replay. In-flight records are never evicted. */
|
|
1129
|
+
agentOperations = /* @__PURE__ */ new Map();
|
|
1131
1130
|
claudeSessionSave = Promise.resolve();
|
|
1132
1131
|
stopping = false;
|
|
1133
1132
|
stopPromise = null;
|
|
@@ -1166,7 +1165,10 @@ var ClaudeCodeHost = class {
|
|
|
1166
1165
|
if (this.stopPromise) return this.stopPromise;
|
|
1167
1166
|
this.stopping = true;
|
|
1168
1167
|
this.queues.stop();
|
|
1169
|
-
for (const driver of this.
|
|
1168
|
+
for (const { driver, cancellation } of this.runningTurns.values()) {
|
|
1169
|
+
cancellation.abort();
|
|
1170
|
+
driver.abort();
|
|
1171
|
+
}
|
|
1170
1172
|
this.chat.stop();
|
|
1171
1173
|
this.stopPromise = (async () => {
|
|
1172
1174
|
let timer;
|
|
@@ -1204,7 +1206,7 @@ var ClaudeCodeHost = class {
|
|
|
1204
1206
|
await this.handleSessionsGet(req);
|
|
1205
1207
|
return;
|
|
1206
1208
|
case "runtime.activity":
|
|
1207
|
-
this.chat.sendResponse(req.id, true, { active: this.
|
|
1209
|
+
this.chat.sendResponse(req.id, true, { active: this.runningTurns.size > 0 });
|
|
1208
1210
|
return;
|
|
1209
1211
|
default:
|
|
1210
1212
|
this.chat.sendResponse(req.id, false, { message: `Unknown method: ${req.method}` });
|
|
@@ -1221,29 +1223,63 @@ var ClaudeCodeHost = class {
|
|
|
1221
1223
|
const sessionKey = params.sessionKey;
|
|
1222
1224
|
const idem = params.idempotencyKey;
|
|
1223
1225
|
if (idem) this.pruneIdempotency();
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
+
const fingerprint = (0, node_crypto.createHash)("sha256").update(JSON.stringify(params)).digest("hex");
|
|
1227
|
+
const existing = idem ? this.agentOperations.get(idem) : void 0;
|
|
1228
|
+
if (existing) {
|
|
1229
|
+
if (existing.fingerprint !== fingerprint) throw new Error("Idempotency key already belongs to a different agent request");
|
|
1230
|
+
if (existing.outcome) this.deliverAgentOutcome(req.id, existing.outcome);
|
|
1231
|
+
else {
|
|
1232
|
+
const outcome = await existing.completion;
|
|
1233
|
+
this.chat.sendResponse(req.id, outcome.ok, outcome.payload);
|
|
1234
|
+
}
|
|
1226
1235
|
return;
|
|
1227
1236
|
}
|
|
1228
|
-
|
|
1229
|
-
|
|
1237
|
+
let resolveCompletion;
|
|
1238
|
+
const operation = {
|
|
1239
|
+
fingerprint,
|
|
1240
|
+
completion: new Promise((resolve) => {
|
|
1241
|
+
resolveCompletion = resolve;
|
|
1242
|
+
}),
|
|
1243
|
+
resultBytes: 0
|
|
1244
|
+
};
|
|
1245
|
+
if (idem) this.agentOperations.set(idem, operation);
|
|
1246
|
+
const complete = (outcome) => {
|
|
1247
|
+
if (operation.outcome) return;
|
|
1248
|
+
operation.outcome = outcome;
|
|
1249
|
+
operation.settledAt = Date.now();
|
|
1250
|
+
operation.resultBytes = Buffer.byteLength(JSON.stringify(outcome), "utf-8");
|
|
1251
|
+
this.deliverAgentOutcome(req.id, outcome);
|
|
1252
|
+
resolveCompletion(outcome);
|
|
1253
|
+
if (idem) this.pruneIdempotency();
|
|
1254
|
+
};
|
|
1230
1255
|
const coalesceKey = params.coalesce === "replace" ? sessionKey : void 0;
|
|
1231
1256
|
try {
|
|
1232
|
-
await this.queues.enqueue(sessionKey, () => this.runAgentTurn(req, params), coalesceKey ? {
|
|
1257
|
+
await this.queues.enqueue(sessionKey, () => this.runAgentTurn(req, params, complete), coalesceKey ? {
|
|
1233
1258
|
coalesceKey,
|
|
1234
1259
|
onSuperseded: () => {
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1260
|
+
complete({
|
|
1261
|
+
ok: true,
|
|
1262
|
+
payload: {
|
|
1263
|
+
superseded: true,
|
|
1264
|
+
sessionKey
|
|
1265
|
+
}
|
|
1238
1266
|
});
|
|
1239
1267
|
}
|
|
1240
1268
|
} : void 0);
|
|
1241
1269
|
} catch (error) {
|
|
1242
|
-
|
|
1243
|
-
|
|
1270
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1271
|
+
this.log.error(`[claude-host] agent operation failed: ${message}`);
|
|
1272
|
+
complete({
|
|
1273
|
+
ok: false,
|
|
1274
|
+
payload: { message }
|
|
1275
|
+
});
|
|
1244
1276
|
}
|
|
1245
1277
|
}
|
|
1246
|
-
|
|
1278
|
+
deliverAgentOutcome(requestId, outcome) {
|
|
1279
|
+
if (outcome.notification) this.chat.notify("agent-message", outcome.notification);
|
|
1280
|
+
this.chat.sendResponse(requestId, outcome.ok, outcome.payload);
|
|
1281
|
+
}
|
|
1282
|
+
async runAgentTurn(req, params, complete) {
|
|
1247
1283
|
const sessionKey = params.sessionKey;
|
|
1248
1284
|
const bareSessionId = params.conversationId ?? stripAgentPrefix(sessionKey);
|
|
1249
1285
|
const runId = params.idempotencyKey ?? req.id;
|
|
@@ -1260,11 +1296,15 @@ var ClaudeCodeHost = class {
|
|
|
1260
1296
|
const appendSystemPrompt = this.buildSystemPrompt(memoryContext, isVoice);
|
|
1261
1297
|
const claudeSessionId = this.claudeSessionIds.get(bareSessionId) ?? null;
|
|
1262
1298
|
const driver = this.makeDriver();
|
|
1263
|
-
|
|
1264
|
-
this.
|
|
1299
|
+
const cancellation = new AbortController();
|
|
1300
|
+
this.runningTurns.set(sessionKey, {
|
|
1301
|
+
runId,
|
|
1302
|
+
driver,
|
|
1303
|
+
cancellation
|
|
1304
|
+
});
|
|
1265
1305
|
let result;
|
|
1266
1306
|
try {
|
|
1267
|
-
result = await this.runWithResumeFallback(driver, bareSessionId,
|
|
1307
|
+
result = await this.runWithResumeFallback(driver, bareSessionId, cancellation.signal, {
|
|
1268
1308
|
prompt: params.message,
|
|
1269
1309
|
claudeSessionId,
|
|
1270
1310
|
cwd: this.cfg.workspace,
|
|
@@ -1277,8 +1317,6 @@ var ClaudeCodeHost = class {
|
|
|
1277
1317
|
}, isVoice);
|
|
1278
1318
|
} finally {
|
|
1279
1319
|
this.runningTurns.delete(sessionKey);
|
|
1280
|
-
this.activeDriverBySession.delete(sessionKey);
|
|
1281
|
-
if (params.idempotencyKey) this.seenIdempotencyKeys.set(params.idempotencyKey, Date.now());
|
|
1282
1320
|
}
|
|
1283
1321
|
if (result.claudeSessionId && result.claudeSessionId !== claudeSessionId) {
|
|
1284
1322
|
this.rememberClaudeSessionId(bareSessionId, result.claudeSessionId);
|
|
@@ -1286,19 +1324,32 @@ var ClaudeCodeHost = class {
|
|
|
1286
1324
|
}
|
|
1287
1325
|
const activityRecords = collectTurnActivityRecords(result.emissions);
|
|
1288
1326
|
if (activityRecords.length) await appendActivity(bareSessionId, activityRecords);
|
|
1289
|
-
if (result.aborted)
|
|
1327
|
+
if (result.aborted) {
|
|
1328
|
+
complete({
|
|
1329
|
+
ok: true,
|
|
1330
|
+
payload: { sessionKey }
|
|
1331
|
+
});
|
|
1332
|
+
return;
|
|
1333
|
+
}
|
|
1290
1334
|
if (result.isError) {
|
|
1291
|
-
|
|
1335
|
+
complete({
|
|
1336
|
+
ok: false,
|
|
1337
|
+
payload: { message: result.errorMessage ?? "Claude Code turn failed" }
|
|
1338
|
+
});
|
|
1292
1339
|
return;
|
|
1293
1340
|
}
|
|
1294
1341
|
if (result.finalText) await addMessage(bareSessionId, "assistant", result.finalText);
|
|
1295
|
-
|
|
1342
|
+
const notification = result.finalText ? {
|
|
1296
1343
|
conversationId: bareSessionId,
|
|
1297
1344
|
text: result.finalText,
|
|
1298
1345
|
sessionKey,
|
|
1299
1346
|
messageId: params.chatMessageId ?? req.id
|
|
1347
|
+
} : void 0;
|
|
1348
|
+
complete({
|
|
1349
|
+
ok: true,
|
|
1350
|
+
payload: { sessionKey },
|
|
1351
|
+
...notification ? { notification } : {}
|
|
1300
1352
|
});
|
|
1301
|
-
this.chat.sendResponse(req.id, true, { sessionKey });
|
|
1302
1353
|
try {
|
|
1303
1354
|
const messages = [{
|
|
1304
1355
|
role: "user",
|
|
@@ -1321,18 +1372,30 @@ var ClaudeCodeHost = class {
|
|
|
1321
1372
|
* Run a turn; on a `--resume` failure (stale/missing claude session id) start
|
|
1322
1373
|
* fresh and prepend the last-N stored messages so the model keeps context.
|
|
1323
1374
|
*/
|
|
1324
|
-
async runWithResumeFallback(driver, bareSessionId,
|
|
1375
|
+
async runWithResumeFallback(driver, bareSessionId, cancellation, baseParams, isVoice) {
|
|
1376
|
+
const cancelled = () => cancellation.aborted || this.stopping;
|
|
1377
|
+
const aborted = (result) => ({
|
|
1378
|
+
claudeSessionId: result?.claudeSessionId ?? null,
|
|
1379
|
+
emissions: result?.emissions ?? [],
|
|
1380
|
+
isError: false,
|
|
1381
|
+
finalText: "",
|
|
1382
|
+
aborted: true
|
|
1383
|
+
});
|
|
1384
|
+
if (cancelled()) return aborted();
|
|
1325
1385
|
const first = await driver.runTurn(baseParams, isVoice ? "voice" : "chat");
|
|
1326
|
-
if (
|
|
1386
|
+
if (cancelled()) return aborted(first);
|
|
1387
|
+
if (!(baseParams.claudeSessionId !== null && first.isError && first.claudeSessionId === null && first.emissions.length === 0) || first.aborted) return first;
|
|
1327
1388
|
this.log.warn(`[claude-host] --resume failed for ${bareSessionId}; starting a fresh claude session`);
|
|
1328
1389
|
this.claudeSessionIds.delete(bareSessionId);
|
|
1329
1390
|
const history = await this.buildHistoryPreamble(bareSessionId);
|
|
1391
|
+
if (cancelled()) return aborted(first);
|
|
1330
1392
|
const freshPrompt = history ? `${history}\n\n---\n\n${baseParams.prompt}` : baseParams.prompt;
|
|
1331
|
-
|
|
1393
|
+
const fresh = await driver.runTurn({
|
|
1332
1394
|
...baseParams,
|
|
1333
1395
|
claudeSessionId: null,
|
|
1334
1396
|
prompt: freshPrompt
|
|
1335
1397
|
}, isVoice ? "voice" : "chat");
|
|
1398
|
+
return cancelled() ? aborted(fresh) : fresh;
|
|
1336
1399
|
}
|
|
1337
1400
|
forwardEmission(runId, sessionKey, emission) {
|
|
1338
1401
|
if (emission.type === "chat-delta") {
|
|
@@ -1368,13 +1431,13 @@ var ClaudeCodeHost = class {
|
|
|
1368
1431
|
}
|
|
1369
1432
|
}
|
|
1370
1433
|
handleChatAbort(req) {
|
|
1371
|
-
const { sessionKey } = req.params;
|
|
1434
|
+
const { sessionKey, runId } = req.params;
|
|
1372
1435
|
let aborted = false;
|
|
1373
|
-
const
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1436
|
+
const active = sessionKey ? this.runningTurns.get(sessionKey) : void 0;
|
|
1437
|
+
const matchesRun = runId === void 0 || typeof runId === "string" && runId.length > 0 && runId.length <= 256 && active?.runId === runId;
|
|
1438
|
+
if (sessionKey && active && matchesRun) {
|
|
1439
|
+
active.cancellation.abort();
|
|
1440
|
+
active.driver.abort();
|
|
1378
1441
|
aborted = true;
|
|
1379
1442
|
}
|
|
1380
1443
|
this.chat.sendResponse(req.id, true, { aborted });
|
|
@@ -1477,11 +1540,14 @@ var ClaudeCodeHost = class {
|
|
|
1477
1540
|
}
|
|
1478
1541
|
pruneIdempotency() {
|
|
1479
1542
|
const cutoff = Date.now() - 10 * 6e4;
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
this.
|
|
1543
|
+
let resultBytes = 0;
|
|
1544
|
+
for (const [key, operation] of this.agentOperations) if (operation.settledAt !== void 0 && operation.settledAt < cutoff) this.agentOperations.delete(key);
|
|
1545
|
+
else resultBytes += operation.resultBytes;
|
|
1546
|
+
for (const [key, operation] of this.agentOperations) {
|
|
1547
|
+
if (this.agentOperations.size < MAX_IDEMPOTENCY_KEYS && resultBytes <= MAX_IDEMPOTENCY_RESULT_BYTES) break;
|
|
1548
|
+
if (operation.settledAt === void 0) continue;
|
|
1549
|
+
this.agentOperations.delete(key);
|
|
1550
|
+
resultBytes -= operation.resultBytes;
|
|
1485
1551
|
}
|
|
1486
1552
|
}
|
|
1487
1553
|
};
|
package/dist/host-config.js
CHANGED
|
@@ -3,9 +3,9 @@ import { ChatServiceClient, resolveAlfeChat } from "@alfe.ai/chat";
|
|
|
3
3
|
import { mkdir, readFile, readdir, rename, stat, unlink, writeFile } from "node:fs/promises";
|
|
4
4
|
import { basename, dirname, join } from "node:path";
|
|
5
5
|
import { homedir } from "node:os";
|
|
6
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
6
7
|
import { spawn } from "node:child_process";
|
|
7
8
|
import { createInterface } from "node:readline";
|
|
8
|
-
import { randomUUID } from "node:crypto";
|
|
9
9
|
import { existsSync } from "node:fs";
|
|
10
10
|
import { resolveConfig } from "@alfe.ai/config";
|
|
11
11
|
//#region src/child-env.ts
|
|
@@ -925,6 +925,7 @@ const MAX_IDENTIFIER_CHARS = 512;
|
|
|
925
925
|
const MAX_MEMORY_CONTEXT_CHARS = 64 * 1024;
|
|
926
926
|
const MAX_CLAUDE_SESSION_IDS = 1e3;
|
|
927
927
|
const MAX_IDEMPOTENCY_KEYS = 1e4;
|
|
928
|
+
const MAX_IDEMPOTENCY_RESULT_BYTES = 8 * 1024 * 1024;
|
|
928
929
|
const SHUTDOWN_GRACE_MS = 3e3;
|
|
929
930
|
function asRecord(value) {
|
|
930
931
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
@@ -1122,12 +1123,10 @@ var ClaudeCodeHost = class {
|
|
|
1122
1123
|
/** Alfe sessionId → claude session id map (mirrors ~/.alfe/claude-code/sessions.json). */
|
|
1123
1124
|
claudeSessionIds = /* @__PURE__ */ new Map();
|
|
1124
1125
|
claudeSessionsPath;
|
|
1125
|
-
/**
|
|
1126
|
+
/** Session → one running turn's wire identity and owned child. */
|
|
1126
1127
|
runningTurns = /* @__PURE__ */ new Map();
|
|
1127
|
-
/**
|
|
1128
|
-
|
|
1129
|
-
/** RPC sessionKey → resolved SessionDriver so chat.abort can SIGINT the child. */
|
|
1130
|
-
activeDriverBySession = /* @__PURE__ */ new Map();
|
|
1128
|
+
/** Process-local admission/result replay. In-flight records are never evicted. */
|
|
1129
|
+
agentOperations = /* @__PURE__ */ new Map();
|
|
1131
1130
|
claudeSessionSave = Promise.resolve();
|
|
1132
1131
|
stopping = false;
|
|
1133
1132
|
stopPromise = null;
|
|
@@ -1166,7 +1165,10 @@ var ClaudeCodeHost = class {
|
|
|
1166
1165
|
if (this.stopPromise) return this.stopPromise;
|
|
1167
1166
|
this.stopping = true;
|
|
1168
1167
|
this.queues.stop();
|
|
1169
|
-
for (const driver of this.
|
|
1168
|
+
for (const { driver, cancellation } of this.runningTurns.values()) {
|
|
1169
|
+
cancellation.abort();
|
|
1170
|
+
driver.abort();
|
|
1171
|
+
}
|
|
1170
1172
|
this.chat.stop();
|
|
1171
1173
|
this.stopPromise = (async () => {
|
|
1172
1174
|
let timer;
|
|
@@ -1204,7 +1206,7 @@ var ClaudeCodeHost = class {
|
|
|
1204
1206
|
await this.handleSessionsGet(req);
|
|
1205
1207
|
return;
|
|
1206
1208
|
case "runtime.activity":
|
|
1207
|
-
this.chat.sendResponse(req.id, true, { active: this.
|
|
1209
|
+
this.chat.sendResponse(req.id, true, { active: this.runningTurns.size > 0 });
|
|
1208
1210
|
return;
|
|
1209
1211
|
default:
|
|
1210
1212
|
this.chat.sendResponse(req.id, false, { message: `Unknown method: ${req.method}` });
|
|
@@ -1221,29 +1223,63 @@ var ClaudeCodeHost = class {
|
|
|
1221
1223
|
const sessionKey = params.sessionKey;
|
|
1222
1224
|
const idem = params.idempotencyKey;
|
|
1223
1225
|
if (idem) this.pruneIdempotency();
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
+
const fingerprint = createHash("sha256").update(JSON.stringify(params)).digest("hex");
|
|
1227
|
+
const existing = idem ? this.agentOperations.get(idem) : void 0;
|
|
1228
|
+
if (existing) {
|
|
1229
|
+
if (existing.fingerprint !== fingerprint) throw new Error("Idempotency key already belongs to a different agent request");
|
|
1230
|
+
if (existing.outcome) this.deliverAgentOutcome(req.id, existing.outcome);
|
|
1231
|
+
else {
|
|
1232
|
+
const outcome = await existing.completion;
|
|
1233
|
+
this.chat.sendResponse(req.id, outcome.ok, outcome.payload);
|
|
1234
|
+
}
|
|
1226
1235
|
return;
|
|
1227
1236
|
}
|
|
1228
|
-
|
|
1229
|
-
|
|
1237
|
+
let resolveCompletion;
|
|
1238
|
+
const operation = {
|
|
1239
|
+
fingerprint,
|
|
1240
|
+
completion: new Promise((resolve) => {
|
|
1241
|
+
resolveCompletion = resolve;
|
|
1242
|
+
}),
|
|
1243
|
+
resultBytes: 0
|
|
1244
|
+
};
|
|
1245
|
+
if (idem) this.agentOperations.set(idem, operation);
|
|
1246
|
+
const complete = (outcome) => {
|
|
1247
|
+
if (operation.outcome) return;
|
|
1248
|
+
operation.outcome = outcome;
|
|
1249
|
+
operation.settledAt = Date.now();
|
|
1250
|
+
operation.resultBytes = Buffer.byteLength(JSON.stringify(outcome), "utf-8");
|
|
1251
|
+
this.deliverAgentOutcome(req.id, outcome);
|
|
1252
|
+
resolveCompletion(outcome);
|
|
1253
|
+
if (idem) this.pruneIdempotency();
|
|
1254
|
+
};
|
|
1230
1255
|
const coalesceKey = params.coalesce === "replace" ? sessionKey : void 0;
|
|
1231
1256
|
try {
|
|
1232
|
-
await this.queues.enqueue(sessionKey, () => this.runAgentTurn(req, params), coalesceKey ? {
|
|
1257
|
+
await this.queues.enqueue(sessionKey, () => this.runAgentTurn(req, params, complete), coalesceKey ? {
|
|
1233
1258
|
coalesceKey,
|
|
1234
1259
|
onSuperseded: () => {
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1260
|
+
complete({
|
|
1261
|
+
ok: true,
|
|
1262
|
+
payload: {
|
|
1263
|
+
superseded: true,
|
|
1264
|
+
sessionKey
|
|
1265
|
+
}
|
|
1238
1266
|
});
|
|
1239
1267
|
}
|
|
1240
1268
|
} : void 0);
|
|
1241
1269
|
} catch (error) {
|
|
1242
|
-
|
|
1243
|
-
|
|
1270
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1271
|
+
this.log.error(`[claude-host] agent operation failed: ${message}`);
|
|
1272
|
+
complete({
|
|
1273
|
+
ok: false,
|
|
1274
|
+
payload: { message }
|
|
1275
|
+
});
|
|
1244
1276
|
}
|
|
1245
1277
|
}
|
|
1246
|
-
|
|
1278
|
+
deliverAgentOutcome(requestId, outcome) {
|
|
1279
|
+
if (outcome.notification) this.chat.notify("agent-message", outcome.notification);
|
|
1280
|
+
this.chat.sendResponse(requestId, outcome.ok, outcome.payload);
|
|
1281
|
+
}
|
|
1282
|
+
async runAgentTurn(req, params, complete) {
|
|
1247
1283
|
const sessionKey = params.sessionKey;
|
|
1248
1284
|
const bareSessionId = params.conversationId ?? stripAgentPrefix(sessionKey);
|
|
1249
1285
|
const runId = params.idempotencyKey ?? req.id;
|
|
@@ -1260,11 +1296,15 @@ var ClaudeCodeHost = class {
|
|
|
1260
1296
|
const appendSystemPrompt = this.buildSystemPrompt(memoryContext, isVoice);
|
|
1261
1297
|
const claudeSessionId = this.claudeSessionIds.get(bareSessionId) ?? null;
|
|
1262
1298
|
const driver = this.makeDriver();
|
|
1263
|
-
|
|
1264
|
-
this.
|
|
1299
|
+
const cancellation = new AbortController();
|
|
1300
|
+
this.runningTurns.set(sessionKey, {
|
|
1301
|
+
runId,
|
|
1302
|
+
driver,
|
|
1303
|
+
cancellation
|
|
1304
|
+
});
|
|
1265
1305
|
let result;
|
|
1266
1306
|
try {
|
|
1267
|
-
result = await this.runWithResumeFallback(driver, bareSessionId,
|
|
1307
|
+
result = await this.runWithResumeFallback(driver, bareSessionId, cancellation.signal, {
|
|
1268
1308
|
prompt: params.message,
|
|
1269
1309
|
claudeSessionId,
|
|
1270
1310
|
cwd: this.cfg.workspace,
|
|
@@ -1277,8 +1317,6 @@ var ClaudeCodeHost = class {
|
|
|
1277
1317
|
}, isVoice);
|
|
1278
1318
|
} finally {
|
|
1279
1319
|
this.runningTurns.delete(sessionKey);
|
|
1280
|
-
this.activeDriverBySession.delete(sessionKey);
|
|
1281
|
-
if (params.idempotencyKey) this.seenIdempotencyKeys.set(params.idempotencyKey, Date.now());
|
|
1282
1320
|
}
|
|
1283
1321
|
if (result.claudeSessionId && result.claudeSessionId !== claudeSessionId) {
|
|
1284
1322
|
this.rememberClaudeSessionId(bareSessionId, result.claudeSessionId);
|
|
@@ -1286,19 +1324,32 @@ var ClaudeCodeHost = class {
|
|
|
1286
1324
|
}
|
|
1287
1325
|
const activityRecords = collectTurnActivityRecords(result.emissions);
|
|
1288
1326
|
if (activityRecords.length) await appendActivity(bareSessionId, activityRecords);
|
|
1289
|
-
if (result.aborted)
|
|
1327
|
+
if (result.aborted) {
|
|
1328
|
+
complete({
|
|
1329
|
+
ok: true,
|
|
1330
|
+
payload: { sessionKey }
|
|
1331
|
+
});
|
|
1332
|
+
return;
|
|
1333
|
+
}
|
|
1290
1334
|
if (result.isError) {
|
|
1291
|
-
|
|
1335
|
+
complete({
|
|
1336
|
+
ok: false,
|
|
1337
|
+
payload: { message: result.errorMessage ?? "Claude Code turn failed" }
|
|
1338
|
+
});
|
|
1292
1339
|
return;
|
|
1293
1340
|
}
|
|
1294
1341
|
if (result.finalText) await addMessage(bareSessionId, "assistant", result.finalText);
|
|
1295
|
-
|
|
1342
|
+
const notification = result.finalText ? {
|
|
1296
1343
|
conversationId: bareSessionId,
|
|
1297
1344
|
text: result.finalText,
|
|
1298
1345
|
sessionKey,
|
|
1299
1346
|
messageId: params.chatMessageId ?? req.id
|
|
1347
|
+
} : void 0;
|
|
1348
|
+
complete({
|
|
1349
|
+
ok: true,
|
|
1350
|
+
payload: { sessionKey },
|
|
1351
|
+
...notification ? { notification } : {}
|
|
1300
1352
|
});
|
|
1301
|
-
this.chat.sendResponse(req.id, true, { sessionKey });
|
|
1302
1353
|
try {
|
|
1303
1354
|
const messages = [{
|
|
1304
1355
|
role: "user",
|
|
@@ -1321,18 +1372,30 @@ var ClaudeCodeHost = class {
|
|
|
1321
1372
|
* Run a turn; on a `--resume` failure (stale/missing claude session id) start
|
|
1322
1373
|
* fresh and prepend the last-N stored messages so the model keeps context.
|
|
1323
1374
|
*/
|
|
1324
|
-
async runWithResumeFallback(driver, bareSessionId,
|
|
1375
|
+
async runWithResumeFallback(driver, bareSessionId, cancellation, baseParams, isVoice) {
|
|
1376
|
+
const cancelled = () => cancellation.aborted || this.stopping;
|
|
1377
|
+
const aborted = (result) => ({
|
|
1378
|
+
claudeSessionId: result?.claudeSessionId ?? null,
|
|
1379
|
+
emissions: result?.emissions ?? [],
|
|
1380
|
+
isError: false,
|
|
1381
|
+
finalText: "",
|
|
1382
|
+
aborted: true
|
|
1383
|
+
});
|
|
1384
|
+
if (cancelled()) return aborted();
|
|
1325
1385
|
const first = await driver.runTurn(baseParams, isVoice ? "voice" : "chat");
|
|
1326
|
-
if (
|
|
1386
|
+
if (cancelled()) return aborted(first);
|
|
1387
|
+
if (!(baseParams.claudeSessionId !== null && first.isError && first.claudeSessionId === null && first.emissions.length === 0) || first.aborted) return first;
|
|
1327
1388
|
this.log.warn(`[claude-host] --resume failed for ${bareSessionId}; starting a fresh claude session`);
|
|
1328
1389
|
this.claudeSessionIds.delete(bareSessionId);
|
|
1329
1390
|
const history = await this.buildHistoryPreamble(bareSessionId);
|
|
1391
|
+
if (cancelled()) return aborted(first);
|
|
1330
1392
|
const freshPrompt = history ? `${history}\n\n---\n\n${baseParams.prompt}` : baseParams.prompt;
|
|
1331
|
-
|
|
1393
|
+
const fresh = await driver.runTurn({
|
|
1332
1394
|
...baseParams,
|
|
1333
1395
|
claudeSessionId: null,
|
|
1334
1396
|
prompt: freshPrompt
|
|
1335
1397
|
}, isVoice ? "voice" : "chat");
|
|
1398
|
+
return cancelled() ? aborted(fresh) : fresh;
|
|
1336
1399
|
}
|
|
1337
1400
|
forwardEmission(runId, sessionKey, emission) {
|
|
1338
1401
|
if (emission.type === "chat-delta") {
|
|
@@ -1368,13 +1431,13 @@ var ClaudeCodeHost = class {
|
|
|
1368
1431
|
}
|
|
1369
1432
|
}
|
|
1370
1433
|
handleChatAbort(req) {
|
|
1371
|
-
const { sessionKey } = req.params;
|
|
1434
|
+
const { sessionKey, runId } = req.params;
|
|
1372
1435
|
let aborted = false;
|
|
1373
|
-
const
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1436
|
+
const active = sessionKey ? this.runningTurns.get(sessionKey) : void 0;
|
|
1437
|
+
const matchesRun = runId === void 0 || typeof runId === "string" && runId.length > 0 && runId.length <= 256 && active?.runId === runId;
|
|
1438
|
+
if (sessionKey && active && matchesRun) {
|
|
1439
|
+
active.cancellation.abort();
|
|
1440
|
+
active.driver.abort();
|
|
1378
1441
|
aborted = true;
|
|
1379
1442
|
}
|
|
1380
1443
|
this.chat.sendResponse(req.id, true, { aborted });
|
|
@@ -1477,11 +1540,14 @@ var ClaudeCodeHost = class {
|
|
|
1477
1540
|
}
|
|
1478
1541
|
pruneIdempotency() {
|
|
1479
1542
|
const cutoff = Date.now() - 10 * 6e4;
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
this.
|
|
1543
|
+
let resultBytes = 0;
|
|
1544
|
+
for (const [key, operation] of this.agentOperations) if (operation.settledAt !== void 0 && operation.settledAt < cutoff) this.agentOperations.delete(key);
|
|
1545
|
+
else resultBytes += operation.resultBytes;
|
|
1546
|
+
for (const [key, operation] of this.agentOperations) {
|
|
1547
|
+
if (this.agentOperations.size < MAX_IDEMPOTENCY_KEYS && resultBytes <= MAX_IDEMPOTENCY_RESULT_BYTES) break;
|
|
1548
|
+
if (operation.settledAt === void 0) continue;
|
|
1549
|
+
this.agentOperations.delete(key);
|
|
1550
|
+
resultBytes -= operation.resultBytes;
|
|
1485
1551
|
}
|
|
1486
1552
|
}
|
|
1487
1553
|
};
|
package/dist/index.d.cts
CHANGED
|
@@ -48,12 +48,18 @@ declare class AgentApiTransport {
|
|
|
48
48
|
* @param extra.retry Whether to retry once on transient failures. Safe reads
|
|
49
49
|
* (GET/HEAD/OPTIONS) default to true; mutations default to false. Set true
|
|
50
50
|
* only when the endpoint's server-side contract is explicitly idempotent.
|
|
51
|
+
* @param extra.retryStatuses Narrow the HTTP statuses that trigger the retry
|
|
52
|
+
* (default 500/502/503/504). Network errors and timeouts are always retried
|
|
53
|
+
* when `retry` is on. Pass a subset when a status in the default set is a
|
|
54
|
+
* deterministic answer for this endpoint (e.g. a 502 that means "upstream
|
|
55
|
+
* denied", which a retry cannot change).
|
|
51
56
|
* @param extra.signal Optional caller cancellation combined with the client's
|
|
52
57
|
* own timeout budget. Aborting either signal cancels the request.
|
|
53
58
|
*/
|
|
54
59
|
request<T>(path: string, options?: RequestInit, extra?: {
|
|
55
60
|
timeoutMs?: number;
|
|
56
61
|
retry?: boolean;
|
|
62
|
+
retryStatuses?: ReadonlySet<number>;
|
|
57
63
|
signal?: AbortSignal;
|
|
58
64
|
}): Promise<T>;
|
|
59
65
|
}
|
|
@@ -82,7 +88,37 @@ interface ConnectProviderAccounts {
|
|
|
82
88
|
provider: string;
|
|
83
89
|
accounts: ConnectProviderAccount[];
|
|
84
90
|
}
|
|
91
|
+
/**
|
|
92
|
+
* One AWS CLI profile an agent may use (`GET /agent/connect/aws/accounts`).
|
|
93
|
+
* `roleArn` is null for a connection's Direct identity. Carries no secret
|
|
94
|
+
* material; sessions are minted per call via `getAwsSession`.
|
|
95
|
+
*/
|
|
96
|
+
interface AwsProfile {
|
|
97
|
+
profile: string;
|
|
98
|
+
roleArn: string | null;
|
|
99
|
+
accountId: string;
|
|
100
|
+
region: string;
|
|
101
|
+
label: string | null;
|
|
102
|
+
connectionId: string;
|
|
103
|
+
accountIdentifier: string;
|
|
104
|
+
displayName: string | null;
|
|
105
|
+
connectedAt: string;
|
|
106
|
+
}
|
|
107
|
+
/** AWS CLI `credential_process` output (Version 1), as minted by Connect. */
|
|
108
|
+
interface AwsSessionCredentials {
|
|
109
|
+
Version: 1;
|
|
110
|
+
AccessKeyId: string;
|
|
111
|
+
SecretAccessKey: string;
|
|
112
|
+
SessionToken: string;
|
|
113
|
+
/** ISO 8601 expiry. */
|
|
114
|
+
Expiration: string;
|
|
115
|
+
}
|
|
85
116
|
declare class ConnectCredentialsApi extends ApiBase {
|
|
117
|
+
/** Platform application identity for private, local Telegram login. Never expose this to tools. */
|
|
118
|
+
telegramGetApplication(): Promise<{
|
|
119
|
+
apiId: number;
|
|
120
|
+
apiHash: string;
|
|
121
|
+
}>;
|
|
86
122
|
/**
|
|
87
123
|
* Discover all active Connections visible to this agent for one provider.
|
|
88
124
|
* Preserve the provider-specific credential projection without guessing a
|
|
@@ -476,7 +512,9 @@ declare class ConnectCredentialsApi extends ApiBase {
|
|
|
476
512
|
* endpoint walks the agent's full visible scope chain to find a matching
|
|
477
513
|
* connection (works for inherited team/project Microsoft connections).
|
|
478
514
|
*/
|
|
479
|
-
refreshMicrosoftAccountToken(accountIdentifier: string
|
|
515
|
+
refreshMicrosoftAccountToken(accountIdentifier: string, options?: {
|
|
516
|
+
connectionId?: string;
|
|
517
|
+
}): Promise<{
|
|
480
518
|
accessToken: string;
|
|
481
519
|
accessTokenExpiresAt: string;
|
|
482
520
|
expiresAt: string;
|
|
@@ -691,12 +729,33 @@ declare class ConnectCredentialsApi extends ApiBase {
|
|
|
691
729
|
* the fresh `accessJwt`) — callers typically ignore it and re-fetch via
|
|
692
730
|
* `getSocialAccounts` for a consistent shape.
|
|
693
731
|
*/
|
|
694
|
-
refreshSocialAccount(provider: string, accountIdentifier: string
|
|
732
|
+
refreshSocialAccount(provider: string, accountIdentifier: string, options?: {
|
|
733
|
+
connectionId?: string;
|
|
734
|
+
}): Promise<{
|
|
695
735
|
accountIdentifier: string;
|
|
696
736
|
accessToken: string;
|
|
697
737
|
accessTokenExpiresAt: string;
|
|
698
738
|
expiresAt: string;
|
|
699
739
|
}>;
|
|
740
|
+
/**
|
|
741
|
+
* List the AWS CLI profiles visible to this agent across every in-scope AWS
|
|
742
|
+
* connection. Each entry names the `connectionId` + `profile` pair that
|
|
743
|
+
* {@link getAwsSession} mints a session for. Fields are selected explicitly;
|
|
744
|
+
* the wire row is never spread.
|
|
745
|
+
*/
|
|
746
|
+
getAwsProfiles(): Promise<AwsProfile[]>;
|
|
747
|
+
/**
|
|
748
|
+
* Mint short-lived AWS session credentials for one profile of one AWS
|
|
749
|
+
* connection, in the AWS CLI `credential_process` (Version 1) shape. The
|
|
750
|
+
* server re-checks scope + active status on every call (403 otherwise; 404
|
|
751
|
+
* for an unknown profile; 403 `aws_access_denied` when AWS denies the
|
|
752
|
+
* assume, 502 on older servers). Minting has no persistent side effect, so
|
|
753
|
+
* one retry is safe, but only on genuinely transient failures (500/503/504
|
|
754
|
+
* and network errors): a denial is deterministic and a retry just doubles
|
|
755
|
+
* the latency of every failing `aws` call. The response is secret material:
|
|
756
|
+
* never log it.
|
|
757
|
+
*/
|
|
758
|
+
getAwsSession(connectionId: string, profile: string): Promise<AwsSessionCredentials>;
|
|
700
759
|
}
|
|
701
760
|
//# sourceMappingURL=connect-credentials.d.ts.map
|
|
702
761
|
//#endregion
|
|
@@ -1392,17 +1451,34 @@ declare class IdentityApi extends ApiBase {
|
|
|
1392
1451
|
platformId: string;
|
|
1393
1452
|
kind?: "user" | "agent" | "service" | "bot" | "workspace";
|
|
1394
1453
|
displayName?: string;
|
|
1454
|
+
/**
|
|
1455
|
+
* Provider `email` only: the sender address, matched read-only against an
|
|
1456
|
+
* existing identity's verified email contact. Ignored for other providers.
|
|
1457
|
+
*/
|
|
1458
|
+
email?: string;
|
|
1459
|
+
/**
|
|
1460
|
+
* Provider `email` only: `true` when this agent verified the sender
|
|
1461
|
+
* (SPF/DKIM/DMARC). Unverified email senders are never matched to an
|
|
1462
|
+
* identity. Ignored for other providers.
|
|
1463
|
+
*/
|
|
1464
|
+
emailVerified?: boolean;
|
|
1395
1465
|
}): Promise<{
|
|
1396
1466
|
identityId: string | null;
|
|
1397
1467
|
status: string;
|
|
1398
1468
|
created?: boolean;
|
|
1399
1469
|
reason?: string;
|
|
1400
1470
|
/**
|
|
1401
|
-
* Flattened auriclabs permission strings
|
|
1402
|
-
*
|
|
1403
|
-
*
|
|
1471
|
+
* Flattened auriclabs permission strings that apply at the calling
|
|
1472
|
+
* agent's own scope (`agent:<agentId>`, plus unscoped entries). A sender
|
|
1473
|
+
* with no identity still receives the tenant's `principalKind="all"`
|
|
1474
|
+
* grants. Empty when nothing applies, or when `permissionsUnavailable`.
|
|
1404
1475
|
*/
|
|
1405
1476
|
permissions: string[];
|
|
1477
|
+
/**
|
|
1478
|
+
* `true` when the org service could not resolve permissions. The empty
|
|
1479
|
+
* `permissions` is then an outage, not a denial: retry rather than deny.
|
|
1480
|
+
*/
|
|
1481
|
+
permissionsUnavailable?: boolean;
|
|
1406
1482
|
}>;
|
|
1407
1483
|
searchIdentities(args?: {
|
|
1408
1484
|
q?: string;
|
|
@@ -1632,6 +1708,48 @@ declare class WebhooksApi extends ApiBase {
|
|
|
1632
1708
|
}
|
|
1633
1709
|
//# sourceMappingURL=webhooks.d.ts.map
|
|
1634
1710
|
//#endregion
|
|
1711
|
+
//#region src/domains/wallet.d.ts
|
|
1712
|
+
interface AgentWalletAccount {
|
|
1713
|
+
connectionId: string;
|
|
1714
|
+
chainType: "ethereum" | "solana";
|
|
1715
|
+
address: string;
|
|
1716
|
+
}
|
|
1717
|
+
type AgentWalletTransferStatus = "pending_approval" | "processing" | "succeeded" | "rejected" | "failed";
|
|
1718
|
+
interface AgentWalletTransfer {
|
|
1719
|
+
requestId: string;
|
|
1720
|
+
agentId: string;
|
|
1721
|
+
connectionId: string;
|
|
1722
|
+
chainType: "ethereum" | "solana";
|
|
1723
|
+
network: string;
|
|
1724
|
+
fromAddress: string;
|
|
1725
|
+
toAddress: string;
|
|
1726
|
+
amount: string;
|
|
1727
|
+
reason?: string;
|
|
1728
|
+
status: AgentWalletTransferStatus;
|
|
1729
|
+
providerStatus?: string;
|
|
1730
|
+
providerActionId?: string;
|
|
1731
|
+
failureReason?: string;
|
|
1732
|
+
billingStatus: "unmetered" | "metered" | "failed";
|
|
1733
|
+
createdAt: string;
|
|
1734
|
+
updatedAt: string;
|
|
1735
|
+
decidedAt?: string;
|
|
1736
|
+
}
|
|
1737
|
+
declare class WalletApi extends ApiBase {
|
|
1738
|
+
listWalletAccounts(): Promise<AgentWalletAccount[]>;
|
|
1739
|
+
/** Create a proposal only; a human approves it in the Alfe dashboard. */
|
|
1740
|
+
requestWalletTransfer(input: {
|
|
1741
|
+
connectionId: string;
|
|
1742
|
+
chainType: "ethereum" | "solana";
|
|
1743
|
+
network: "ethereum" | "base" | "arbitrum" | "optimism" | "solana";
|
|
1744
|
+
fromAddress: string;
|
|
1745
|
+
toAddress: string;
|
|
1746
|
+
amount: string;
|
|
1747
|
+
reason?: string;
|
|
1748
|
+
}): Promise<AgentWalletTransfer>;
|
|
1749
|
+
getWalletTransfer(requestId: string): Promise<AgentWalletTransfer>;
|
|
1750
|
+
}
|
|
1751
|
+
//# sourceMappingURL=wallet.d.ts.map
|
|
1752
|
+
//#endregion
|
|
1635
1753
|
//#region src/domains/chat.d.ts
|
|
1636
1754
|
declare class ChatApi extends ApiBase {
|
|
1637
1755
|
ensureDirectConversation(identityId: string): Promise<{
|
|
@@ -2003,7 +2121,7 @@ declare class TeamsApi extends ApiBase {
|
|
|
2003
2121
|
|
|
2004
2122
|
//#endregion
|
|
2005
2123
|
//#region src/index.d.ts
|
|
2006
|
-
interface AgentApiClient extends SyncApi, IntegrationsApi, WorkspaceApi, ConnectCredentialsApi, TeamsApi, ChatApi, SecretsApi, IdentityApi, MemoryApi, SearchApi, KnowledgeApi, DatabaseApi, MobileApi, RemoteApi, SelfApi, VoiceApi, ImagesApi, WebhooksApi {}
|
|
2124
|
+
interface AgentApiClient extends SyncApi, IntegrationsApi, WorkspaceApi, ConnectCredentialsApi, TeamsApi, ChatApi, SecretsApi, IdentityApi, MemoryApi, SearchApi, KnowledgeApi, DatabaseApi, MobileApi, RemoteApi, SelfApi, VoiceApi, ImagesApi, WebhooksApi, WalletApi {}
|
|
2007
2125
|
declare class AgentApiClient extends ApiBase {
|
|
2008
2126
|
constructor(config: AgentApiClientConfig);
|
|
2009
2127
|
}
|
|
@@ -2396,12 +2514,10 @@ declare class ClaudeCodeHost {
|
|
|
2396
2514
|
/** Alfe sessionId → claude session id map (mirrors ~/.alfe/claude-code/sessions.json). */
|
|
2397
2515
|
private readonly claudeSessionIds;
|
|
2398
2516
|
private claudeSessionsPath;
|
|
2399
|
-
/**
|
|
2517
|
+
/** Session → one running turn's wire identity and owned child. */
|
|
2400
2518
|
private readonly runningTurns;
|
|
2401
|
-
/**
|
|
2402
|
-
private readonly
|
|
2403
|
-
/** RPC sessionKey → resolved SessionDriver so chat.abort can SIGINT the child. */
|
|
2404
|
-
private activeDriverBySession;
|
|
2519
|
+
/** Process-local admission/result replay. In-flight records are never evicted. */
|
|
2520
|
+
private readonly agentOperations;
|
|
2405
2521
|
private claudeSessionSave;
|
|
2406
2522
|
private stopping;
|
|
2407
2523
|
private stopPromise;
|
|
@@ -2421,6 +2537,7 @@ declare class ClaudeCodeHost {
|
|
|
2421
2537
|
*/
|
|
2422
2538
|
handleRequest(req: RpcRequest): Promise<void>;
|
|
2423
2539
|
private handleAgent;
|
|
2540
|
+
private deliverAgentOutcome;
|
|
2424
2541
|
private runAgentTurn;
|
|
2425
2542
|
/**
|
|
2426
2543
|
* Run a turn; on a `--resume` failure (stale/missing claude session id) start
|
package/dist/index.d.ts
CHANGED
|
@@ -48,12 +48,18 @@ declare class AgentApiTransport {
|
|
|
48
48
|
* @param extra.retry Whether to retry once on transient failures. Safe reads
|
|
49
49
|
* (GET/HEAD/OPTIONS) default to true; mutations default to false. Set true
|
|
50
50
|
* only when the endpoint's server-side contract is explicitly idempotent.
|
|
51
|
+
* @param extra.retryStatuses Narrow the HTTP statuses that trigger the retry
|
|
52
|
+
* (default 500/502/503/504). Network errors and timeouts are always retried
|
|
53
|
+
* when `retry` is on. Pass a subset when a status in the default set is a
|
|
54
|
+
* deterministic answer for this endpoint (e.g. a 502 that means "upstream
|
|
55
|
+
* denied", which a retry cannot change).
|
|
51
56
|
* @param extra.signal Optional caller cancellation combined with the client's
|
|
52
57
|
* own timeout budget. Aborting either signal cancels the request.
|
|
53
58
|
*/
|
|
54
59
|
request<T>(path: string, options?: RequestInit, extra?: {
|
|
55
60
|
timeoutMs?: number;
|
|
56
61
|
retry?: boolean;
|
|
62
|
+
retryStatuses?: ReadonlySet<number>;
|
|
57
63
|
signal?: AbortSignal;
|
|
58
64
|
}): Promise<T>;
|
|
59
65
|
}
|
|
@@ -82,7 +88,37 @@ interface ConnectProviderAccounts {
|
|
|
82
88
|
provider: string;
|
|
83
89
|
accounts: ConnectProviderAccount[];
|
|
84
90
|
}
|
|
91
|
+
/**
|
|
92
|
+
* One AWS CLI profile an agent may use (`GET /agent/connect/aws/accounts`).
|
|
93
|
+
* `roleArn` is null for a connection's Direct identity. Carries no secret
|
|
94
|
+
* material; sessions are minted per call via `getAwsSession`.
|
|
95
|
+
*/
|
|
96
|
+
interface AwsProfile {
|
|
97
|
+
profile: string;
|
|
98
|
+
roleArn: string | null;
|
|
99
|
+
accountId: string;
|
|
100
|
+
region: string;
|
|
101
|
+
label: string | null;
|
|
102
|
+
connectionId: string;
|
|
103
|
+
accountIdentifier: string;
|
|
104
|
+
displayName: string | null;
|
|
105
|
+
connectedAt: string;
|
|
106
|
+
}
|
|
107
|
+
/** AWS CLI `credential_process` output (Version 1), as minted by Connect. */
|
|
108
|
+
interface AwsSessionCredentials {
|
|
109
|
+
Version: 1;
|
|
110
|
+
AccessKeyId: string;
|
|
111
|
+
SecretAccessKey: string;
|
|
112
|
+
SessionToken: string;
|
|
113
|
+
/** ISO 8601 expiry. */
|
|
114
|
+
Expiration: string;
|
|
115
|
+
}
|
|
85
116
|
declare class ConnectCredentialsApi extends ApiBase {
|
|
117
|
+
/** Platform application identity for private, local Telegram login. Never expose this to tools. */
|
|
118
|
+
telegramGetApplication(): Promise<{
|
|
119
|
+
apiId: number;
|
|
120
|
+
apiHash: string;
|
|
121
|
+
}>;
|
|
86
122
|
/**
|
|
87
123
|
* Discover all active Connections visible to this agent for one provider.
|
|
88
124
|
* Preserve the provider-specific credential projection without guessing a
|
|
@@ -476,7 +512,9 @@ declare class ConnectCredentialsApi extends ApiBase {
|
|
|
476
512
|
* endpoint walks the agent's full visible scope chain to find a matching
|
|
477
513
|
* connection (works for inherited team/project Microsoft connections).
|
|
478
514
|
*/
|
|
479
|
-
refreshMicrosoftAccountToken(accountIdentifier: string
|
|
515
|
+
refreshMicrosoftAccountToken(accountIdentifier: string, options?: {
|
|
516
|
+
connectionId?: string;
|
|
517
|
+
}): Promise<{
|
|
480
518
|
accessToken: string;
|
|
481
519
|
accessTokenExpiresAt: string;
|
|
482
520
|
expiresAt: string;
|
|
@@ -691,12 +729,33 @@ declare class ConnectCredentialsApi extends ApiBase {
|
|
|
691
729
|
* the fresh `accessJwt`) — callers typically ignore it and re-fetch via
|
|
692
730
|
* `getSocialAccounts` for a consistent shape.
|
|
693
731
|
*/
|
|
694
|
-
refreshSocialAccount(provider: string, accountIdentifier: string
|
|
732
|
+
refreshSocialAccount(provider: string, accountIdentifier: string, options?: {
|
|
733
|
+
connectionId?: string;
|
|
734
|
+
}): Promise<{
|
|
695
735
|
accountIdentifier: string;
|
|
696
736
|
accessToken: string;
|
|
697
737
|
accessTokenExpiresAt: string;
|
|
698
738
|
expiresAt: string;
|
|
699
739
|
}>;
|
|
740
|
+
/**
|
|
741
|
+
* List the AWS CLI profiles visible to this agent across every in-scope AWS
|
|
742
|
+
* connection. Each entry names the `connectionId` + `profile` pair that
|
|
743
|
+
* {@link getAwsSession} mints a session for. Fields are selected explicitly;
|
|
744
|
+
* the wire row is never spread.
|
|
745
|
+
*/
|
|
746
|
+
getAwsProfiles(): Promise<AwsProfile[]>;
|
|
747
|
+
/**
|
|
748
|
+
* Mint short-lived AWS session credentials for one profile of one AWS
|
|
749
|
+
* connection, in the AWS CLI `credential_process` (Version 1) shape. The
|
|
750
|
+
* server re-checks scope + active status on every call (403 otherwise; 404
|
|
751
|
+
* for an unknown profile; 403 `aws_access_denied` when AWS denies the
|
|
752
|
+
* assume, 502 on older servers). Minting has no persistent side effect, so
|
|
753
|
+
* one retry is safe, but only on genuinely transient failures (500/503/504
|
|
754
|
+
* and network errors): a denial is deterministic and a retry just doubles
|
|
755
|
+
* the latency of every failing `aws` call. The response is secret material:
|
|
756
|
+
* never log it.
|
|
757
|
+
*/
|
|
758
|
+
getAwsSession(connectionId: string, profile: string): Promise<AwsSessionCredentials>;
|
|
700
759
|
}
|
|
701
760
|
//# sourceMappingURL=connect-credentials.d.ts.map
|
|
702
761
|
//#endregion
|
|
@@ -1392,17 +1451,34 @@ declare class IdentityApi extends ApiBase {
|
|
|
1392
1451
|
platformId: string;
|
|
1393
1452
|
kind?: "user" | "agent" | "service" | "bot" | "workspace";
|
|
1394
1453
|
displayName?: string;
|
|
1454
|
+
/**
|
|
1455
|
+
* Provider `email` only: the sender address, matched read-only against an
|
|
1456
|
+
* existing identity's verified email contact. Ignored for other providers.
|
|
1457
|
+
*/
|
|
1458
|
+
email?: string;
|
|
1459
|
+
/**
|
|
1460
|
+
* Provider `email` only: `true` when this agent verified the sender
|
|
1461
|
+
* (SPF/DKIM/DMARC). Unverified email senders are never matched to an
|
|
1462
|
+
* identity. Ignored for other providers.
|
|
1463
|
+
*/
|
|
1464
|
+
emailVerified?: boolean;
|
|
1395
1465
|
}): Promise<{
|
|
1396
1466
|
identityId: string | null;
|
|
1397
1467
|
status: string;
|
|
1398
1468
|
created?: boolean;
|
|
1399
1469
|
reason?: string;
|
|
1400
1470
|
/**
|
|
1401
|
-
* Flattened auriclabs permission strings
|
|
1402
|
-
*
|
|
1403
|
-
*
|
|
1471
|
+
* Flattened auriclabs permission strings that apply at the calling
|
|
1472
|
+
* agent's own scope (`agent:<agentId>`, plus unscoped entries). A sender
|
|
1473
|
+
* with no identity still receives the tenant's `principalKind="all"`
|
|
1474
|
+
* grants. Empty when nothing applies, or when `permissionsUnavailable`.
|
|
1404
1475
|
*/
|
|
1405
1476
|
permissions: string[];
|
|
1477
|
+
/**
|
|
1478
|
+
* `true` when the org service could not resolve permissions. The empty
|
|
1479
|
+
* `permissions` is then an outage, not a denial: retry rather than deny.
|
|
1480
|
+
*/
|
|
1481
|
+
permissionsUnavailable?: boolean;
|
|
1406
1482
|
}>;
|
|
1407
1483
|
searchIdentities(args?: {
|
|
1408
1484
|
q?: string;
|
|
@@ -1632,6 +1708,48 @@ declare class WebhooksApi extends ApiBase {
|
|
|
1632
1708
|
}
|
|
1633
1709
|
//# sourceMappingURL=webhooks.d.ts.map
|
|
1634
1710
|
//#endregion
|
|
1711
|
+
//#region src/domains/wallet.d.ts
|
|
1712
|
+
interface AgentWalletAccount {
|
|
1713
|
+
connectionId: string;
|
|
1714
|
+
chainType: "ethereum" | "solana";
|
|
1715
|
+
address: string;
|
|
1716
|
+
}
|
|
1717
|
+
type AgentWalletTransferStatus = "pending_approval" | "processing" | "succeeded" | "rejected" | "failed";
|
|
1718
|
+
interface AgentWalletTransfer {
|
|
1719
|
+
requestId: string;
|
|
1720
|
+
agentId: string;
|
|
1721
|
+
connectionId: string;
|
|
1722
|
+
chainType: "ethereum" | "solana";
|
|
1723
|
+
network: string;
|
|
1724
|
+
fromAddress: string;
|
|
1725
|
+
toAddress: string;
|
|
1726
|
+
amount: string;
|
|
1727
|
+
reason?: string;
|
|
1728
|
+
status: AgentWalletTransferStatus;
|
|
1729
|
+
providerStatus?: string;
|
|
1730
|
+
providerActionId?: string;
|
|
1731
|
+
failureReason?: string;
|
|
1732
|
+
billingStatus: "unmetered" | "metered" | "failed";
|
|
1733
|
+
createdAt: string;
|
|
1734
|
+
updatedAt: string;
|
|
1735
|
+
decidedAt?: string;
|
|
1736
|
+
}
|
|
1737
|
+
declare class WalletApi extends ApiBase {
|
|
1738
|
+
listWalletAccounts(): Promise<AgentWalletAccount[]>;
|
|
1739
|
+
/** Create a proposal only; a human approves it in the Alfe dashboard. */
|
|
1740
|
+
requestWalletTransfer(input: {
|
|
1741
|
+
connectionId: string;
|
|
1742
|
+
chainType: "ethereum" | "solana";
|
|
1743
|
+
network: "ethereum" | "base" | "arbitrum" | "optimism" | "solana";
|
|
1744
|
+
fromAddress: string;
|
|
1745
|
+
toAddress: string;
|
|
1746
|
+
amount: string;
|
|
1747
|
+
reason?: string;
|
|
1748
|
+
}): Promise<AgentWalletTransfer>;
|
|
1749
|
+
getWalletTransfer(requestId: string): Promise<AgentWalletTransfer>;
|
|
1750
|
+
}
|
|
1751
|
+
//# sourceMappingURL=wallet.d.ts.map
|
|
1752
|
+
//#endregion
|
|
1635
1753
|
//#region src/domains/chat.d.ts
|
|
1636
1754
|
declare class ChatApi extends ApiBase {
|
|
1637
1755
|
ensureDirectConversation(identityId: string): Promise<{
|
|
@@ -2003,7 +2121,7 @@ declare class TeamsApi extends ApiBase {
|
|
|
2003
2121
|
|
|
2004
2122
|
//#endregion
|
|
2005
2123
|
//#region src/index.d.ts
|
|
2006
|
-
interface AgentApiClient extends SyncApi, IntegrationsApi, WorkspaceApi, ConnectCredentialsApi, TeamsApi, ChatApi, SecretsApi, IdentityApi, MemoryApi, SearchApi, KnowledgeApi, DatabaseApi, MobileApi, RemoteApi, SelfApi, VoiceApi, ImagesApi, WebhooksApi {}
|
|
2124
|
+
interface AgentApiClient extends SyncApi, IntegrationsApi, WorkspaceApi, ConnectCredentialsApi, TeamsApi, ChatApi, SecretsApi, IdentityApi, MemoryApi, SearchApi, KnowledgeApi, DatabaseApi, MobileApi, RemoteApi, SelfApi, VoiceApi, ImagesApi, WebhooksApi, WalletApi {}
|
|
2007
2125
|
declare class AgentApiClient extends ApiBase {
|
|
2008
2126
|
constructor(config: AgentApiClientConfig);
|
|
2009
2127
|
}
|
|
@@ -2396,12 +2514,10 @@ declare class ClaudeCodeHost {
|
|
|
2396
2514
|
/** Alfe sessionId → claude session id map (mirrors ~/.alfe/claude-code/sessions.json). */
|
|
2397
2515
|
private readonly claudeSessionIds;
|
|
2398
2516
|
private claudeSessionsPath;
|
|
2399
|
-
/**
|
|
2517
|
+
/** Session → one running turn's wire identity and owned child. */
|
|
2400
2518
|
private readonly runningTurns;
|
|
2401
|
-
/**
|
|
2402
|
-
private readonly
|
|
2403
|
-
/** RPC sessionKey → resolved SessionDriver so chat.abort can SIGINT the child. */
|
|
2404
|
-
private activeDriverBySession;
|
|
2519
|
+
/** Process-local admission/result replay. In-flight records are never evicted. */
|
|
2520
|
+
private readonly agentOperations;
|
|
2405
2521
|
private claudeSessionSave;
|
|
2406
2522
|
private stopping;
|
|
2407
2523
|
private stopPromise;
|
|
@@ -2421,6 +2537,7 @@ declare class ClaudeCodeHost {
|
|
|
2421
2537
|
*/
|
|
2422
2538
|
handleRequest(req: RpcRequest): Promise<void>;
|
|
2423
2539
|
private handleAgent;
|
|
2540
|
+
private deliverAgentOutcome;
|
|
2424
2541
|
private runAgentTurn;
|
|
2425
2542
|
/**
|
|
2426
2543
|
* Run a turn; on a `--resume` failure (stale/missing claude session id) start
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alfe.ai/claude-code-host",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.14",
|
|
4
4
|
"description": "Chat bridge that makes a self-hosted Claude Code agent reachable from Alfe chat on the user's Claude subscription",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"dist"
|
|
20
20
|
],
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@alfe.ai/agent-api-client": "^0.
|
|
22
|
+
"@alfe.ai/agent-api-client": "^0.20.0",
|
|
23
23
|
"@alfe.ai/chat": "^0.0.19",
|
|
24
24
|
"@alfe.ai/config": "^0.4.1"
|
|
25
25
|
},
|