totemlayer 0.1.42 → 0.1.59
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 +48 -11
- package/bin/agent-memory.mjs +244 -0
- package/bin/agent-profile.mjs +26 -4
- package/bin/agent-runner.mjs +43 -9
- package/bin/agent-setup.mjs +136 -50
- package/bin/automatic-runtime.mjs +27 -8
- package/bin/chat-prompt-limit.mjs +67 -0
- package/bin/codex-chat-bridge.mjs +36 -8
- package/bin/codex-chat-readiness.mjs +7 -0
- package/bin/codex-human-chat-bridge.mjs +209 -0
- package/bin/collaboration-listener.mjs +2 -1
- package/bin/connected-agent-service.mjs +4 -1
- package/bin/inbox-queue.mjs +2 -1
- package/bin/installation.mjs +93 -44
- package/bin/local-mcp.mjs +43 -4
- package/bin/mcp-config.d.mts +11 -0
- package/bin/mcp-config.mjs +56 -0
- package/bin/mcp-install.mjs +143 -12
- package/bin/mcp-listener.d.mts +22 -0
- package/bin/mcp-listener.mjs +4 -0
- package/bin/mcp-runtime.mjs +3 -1
- package/bin/mcp.mjs +331 -94
- package/bin/mission-agent-service.mjs +2 -1
- package/bin/mission-worker.mjs +13 -2
- package/bin/model-adapter.mjs +1 -4
- package/bin/network-contract.mjs +254 -0
- package/bin/network-runtime.mjs +160 -25
- package/bin/network.mjs +30 -5
- package/bin/platform-client.mjs +25 -3
- package/bin/provider-detection.mjs +1 -4
- package/bin/runtime-diagnostics.mjs +1 -0
- package/bin/runtime-installation.mjs +57 -16
- package/bin/sdk/index.d.ts +50 -6
- package/bin/sdk/index.js +1 -1
- package/bin/sdk/listener.d.ts +57 -0
- package/bin/sdk/listener.js +1 -0
- package/bin/sdk/types.d.ts +62 -0
- package/bin/startup-diagnostics.mjs +168 -0
- package/bin/totem.mjs +17 -3
- package/bin/worker-core/adapters.mjs +4 -4
- package/package.json +1 -1
- package/scripts/prepare.mjs +28 -8
package/bin/platform-client.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { join, dirname, isAbsolute, extname } from "node:path";
|
|
2
|
-
import { existsSync } from "node:fs";
|
|
1
|
+
import { delimiter, join, dirname, isAbsolute, extname } from "node:path";
|
|
2
|
+
import { accessSync, constants, existsSync, statSync } from "node:fs";
|
|
3
3
|
|
|
4
4
|
const INHERITED_ENVIRONMENT = new Set(
|
|
5
5
|
[
|
|
@@ -71,7 +71,29 @@ export function powershellInvocation(script, environment = process.env) {
|
|
|
71
71
|
// PowerShell resolves both native executables and npm's .cmd shims. Only a
|
|
72
72
|
// base64-encoded JSON argument array is interpolated into the script, so a
|
|
73
73
|
// Windows path can never become shell syntax.
|
|
74
|
-
|
|
74
|
+
function executableFile(path) {
|
|
75
|
+
try {
|
|
76
|
+
accessSync(path, constants.X_OK);
|
|
77
|
+
return statSync(path).isFile();
|
|
78
|
+
} catch {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function installedClientInvocation(command, args, environment = process.env, options = {}) {
|
|
84
|
+
const platform = options.platform ?? process.platform;
|
|
85
|
+
if (platform !== "win32") {
|
|
86
|
+
const isExecutable = options.isExecutable ?? executableFile;
|
|
87
|
+
if (
|
|
88
|
+
platform === "darwin" && command === "codex" &&
|
|
89
|
+
!(environment.PATH ?? "/usr/bin:/bin").split(delimiter)
|
|
90
|
+
.some((directory) => isExecutable(join(directory || ".", command)))
|
|
91
|
+
) {
|
|
92
|
+
const bundled = "/Applications/ChatGPT.app/Contents/Resources/codex-cli/CodexCLI.app/Contents/MacOS/codex";
|
|
93
|
+
if (isExecutable(bundled)) return { command: bundled, args };
|
|
94
|
+
}
|
|
95
|
+
return { command, args };
|
|
96
|
+
}
|
|
75
97
|
// Native executables inherit the pipe directly. For npm clients, launch the
|
|
76
98
|
// installed JavaScript entrypoint with Node rather than a .cmd/PowerShell
|
|
77
99
|
// pipeline, which can leave stdin open indefinitely on Windows.
|
|
@@ -21,10 +21,7 @@ export async function detectInstalledProvider(environment = process.env, options
|
|
|
21
21
|
for (const client of ["codex", "claude"]) {
|
|
22
22
|
try {
|
|
23
23
|
const args = client === "codex" ? ["login", "status"] : ["auth", "status"];
|
|
24
|
-
const invocation =
|
|
25
|
-
process.platform === "win32"
|
|
26
|
-
? installedClientInvocation(client, args, environment)
|
|
27
|
-
: { command: client, args };
|
|
24
|
+
const invocation = installedClientInvocation(client, args, environment, options);
|
|
28
25
|
const result = await execute(invocation.command, invocation.args, {
|
|
29
26
|
env: localClientEnvironment(environment),
|
|
30
27
|
timeout: 5000,
|
|
@@ -72,6 +72,14 @@ export async function ensureInstalledRuntime(env = process.env) {
|
|
|
72
72
|
const modules = join(root, "runtime-packages", metadata.version, "node_modules");
|
|
73
73
|
const destination = join(modules, "totemlayer");
|
|
74
74
|
await privateDirectory(runtime);
|
|
75
|
+
// Installation and service activation can happen in separate MCP callbacks.
|
|
76
|
+
// Retain the upgrade until registration has actually reloaded the supervisor.
|
|
77
|
+
const refreshPath = join(runtime, "service-refresh.json");
|
|
78
|
+
if (previousVersion && previousVersion !== metadata.version)
|
|
79
|
+
await privateWrite(refreshPath, { version: metadata.version });
|
|
80
|
+
const refresh = await privateRead(refreshPath).catch((error) => {
|
|
81
|
+
if (error.code !== "ENOENT") throw error;
|
|
82
|
+
});
|
|
75
83
|
await privateDirectory(modules);
|
|
76
84
|
let exists = false;
|
|
77
85
|
try {
|
|
@@ -204,7 +212,7 @@ if (process.env.TOTEM_RUNTIME_ENV_LOADED === entry) {
|
|
|
204
212
|
supervisor,
|
|
205
213
|
directory: runtime,
|
|
206
214
|
version: metadata.version,
|
|
207
|
-
upgraded:
|
|
215
|
+
upgraded: refresh?.version === metadata.version,
|
|
208
216
|
};
|
|
209
217
|
}
|
|
210
218
|
const xml = (value) =>
|
|
@@ -293,23 +301,51 @@ export async function installRuntimeService(runtime, env = process.env, options
|
|
|
293
301
|
const run = options.execute ?? execute;
|
|
294
302
|
const node = process.execPath;
|
|
295
303
|
const home = env.HOME ?? env.USERPROFILE ?? homedir();
|
|
296
|
-
|
|
304
|
+
const acknowledged = async (result) => {
|
|
305
|
+
if (runtime.version)
|
|
306
|
+
await privateWrite(join(runtime.directory, "service-refresh.json"), { version: null });
|
|
307
|
+
return result;
|
|
308
|
+
};
|
|
309
|
+
if (runtime.upgraded) {
|
|
297
310
|
try {
|
|
298
311
|
const status = await privateRead(join(runtime.directory, "supervisor.json"));
|
|
299
|
-
const { readSupervisorOwner } = await import("./automatic-runtime.mjs");
|
|
312
|
+
const { readSupervisorOwner, processIdentity } = await import("./automatic-runtime.mjs");
|
|
300
313
|
const owner = (await readSupervisorOwner(join(runtime.directory, "supervisor.lock")))?.owner;
|
|
301
|
-
if (
|
|
302
|
-
|
|
314
|
+
if (
|
|
315
|
+
Number.isSafeInteger(status.pid) &&
|
|
316
|
+
status.pid > 1 &&
|
|
317
|
+
owner?.pid === status.pid &&
|
|
318
|
+
owner.identity &&
|
|
319
|
+
(await (options.processIdentity ?? processIdentity)(status.pid)) === owner.identity
|
|
320
|
+
) {
|
|
321
|
+
const invocation =
|
|
322
|
+
platform === "win32"
|
|
323
|
+
? powershellInvocation(
|
|
324
|
+
`(Get-CimInstance Win32_Process -Filter 'ProcessId=${status.pid}').CommandLine`,
|
|
325
|
+
env,
|
|
326
|
+
)
|
|
327
|
+
: { command: "ps", args: ["-p", String(status.pid), "-o", "args="] };
|
|
328
|
+
const processInfo = await run(invocation.command, invocation.args, {
|
|
303
329
|
encoding: "utf8",
|
|
304
330
|
timeout: 3000,
|
|
305
331
|
maxBuffer: 8192,
|
|
306
332
|
});
|
|
307
|
-
|
|
308
|
-
|
|
333
|
+
const args = [...processInfo.stdout.trim().matchAll(/"([^"]*)"|(\S+)/g)].map(
|
|
334
|
+
(match) => match[1] ?? match[2],
|
|
335
|
+
);
|
|
336
|
+
const exact =
|
|
337
|
+
platform === "win32"
|
|
338
|
+
? args.length === 2 &&
|
|
339
|
+
args[0].toLowerCase() === node.toLowerCase() &&
|
|
340
|
+
args[1].toLowerCase() === runtime.supervisor.toLowerCase()
|
|
341
|
+
: processInfo.stdout.trim() === `${node} ${runtime.supervisor}`;
|
|
342
|
+
if (exact) {
|
|
343
|
+
const signal = options.processSignal ?? process.kill;
|
|
344
|
+
signal(status.pid, "SIGTERM");
|
|
309
345
|
const deadline = Date.now() + 20000;
|
|
310
346
|
while (Date.now() < deadline) {
|
|
311
347
|
try {
|
|
312
|
-
|
|
348
|
+
signal(status.pid, 0);
|
|
313
349
|
} catch (error) {
|
|
314
350
|
if (error.code === "ESRCH") break;
|
|
315
351
|
throw error;
|
|
@@ -424,12 +460,17 @@ export async function installRuntimeService(runtime, env = process.env, options
|
|
|
424
460
|
startupCode = "STARTUP_DISABLED";
|
|
425
461
|
throw new Error("Automatic startup was disabled by the owner.");
|
|
426
462
|
}
|
|
427
|
-
//
|
|
463
|
+
// Restart the registered entry in place. Unregister/bootstrap races with
|
|
464
|
+
// launchd's own KeepAlive recovery and can temporarily lose registration.
|
|
428
465
|
try {
|
|
429
|
-
await run("launchctl", ["print", `${domain}/${label}`]);
|
|
466
|
+
const loaded = await run("launchctl", ["print", `${domain}/${label}`]);
|
|
467
|
+
if (
|
|
468
|
+
!loaded.stdout.includes(`program = ${node}`) ||
|
|
469
|
+
!loaded.stdout.includes(runtime.supervisor)
|
|
470
|
+
)
|
|
471
|
+
throw new Error("The loaded launch agent does not match this runtime.");
|
|
430
472
|
if (runtime.upgraded) {
|
|
431
|
-
await run("launchctl", ["
|
|
432
|
-
await run("launchctl", ["bootstrap", domain, path]);
|
|
473
|
+
await run("launchctl", ["kickstart", "-k", `${domain}/${label}`]);
|
|
433
474
|
}
|
|
434
475
|
} catch {
|
|
435
476
|
await run("launchctl", ["bootstrap", domain, path]);
|
|
@@ -441,7 +482,7 @@ export async function installRuntimeService(runtime, env = process.env, options
|
|
|
441
482
|
!verified.stdout.includes(runtime.supervisor)
|
|
442
483
|
)
|
|
443
484
|
throw new Error("The loaded launch agent does not match this runtime.");
|
|
444
|
-
return { mode: "launchd", persistent: true, label, path };
|
|
485
|
+
return acknowledged({ mode: "launchd", persistent: true, label, path });
|
|
445
486
|
}
|
|
446
487
|
if (platform === "linux") {
|
|
447
488
|
const path = join(home, ".config", "systemd", "user", unit);
|
|
@@ -462,12 +503,12 @@ export async function installRuntimeService(runtime, env = process.env, options
|
|
|
462
503
|
await run("systemctl", ["--user", "daemon-reload"]);
|
|
463
504
|
await run("systemctl", ["--user", "enable", "--now", unit]);
|
|
464
505
|
if (runtime.upgraded) await run("systemctl", ["--user", "restart", unit]);
|
|
465
|
-
return { mode: "systemd-user", persistent: true, label: unit, path };
|
|
506
|
+
return acknowledged({ mode: "systemd-user", persistent: true, label: unit, path });
|
|
466
507
|
}
|
|
467
508
|
if (platform === "win32") {
|
|
468
509
|
windowsRegistration = await windowsStartup(runtime, node, task, env, run);
|
|
469
510
|
if (windowsRegistration.mode === "scheduled-task")
|
|
470
|
-
return { mode: "scheduled-task", persistent: true, label: task };
|
|
511
|
+
return acknowledged({ mode: "scheduled-task", persistent: true, label: task });
|
|
471
512
|
}
|
|
472
513
|
} catch {
|
|
473
514
|
/* Ephemeral/headless hosts often have no user service manager. */
|
|
@@ -481,7 +522,7 @@ export async function installRuntimeService(runtime, env = process.env, options
|
|
|
481
522
|
child.on("error", () => {});
|
|
482
523
|
child.unref();
|
|
483
524
|
return windowsRegistration?.mode === "startup-shortcut"
|
|
484
|
-
? { mode: "startup-shortcut", persistent: true, label: task }
|
|
525
|
+
? acknowledged({ mode: "startup-shortcut", persistent: true, label: task })
|
|
485
526
|
: {
|
|
486
527
|
mode: "background-supervisor",
|
|
487
528
|
persistent: false,
|
package/bin/sdk/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createLocalInspector, InspectionError } from "./inspector.js";
|
|
2
2
|
import type { ActionProposal, AssistantActionCheck, AssistantActionCheckList, AssistantActionReceiptResult, AssistantCheckOptions } from "./action-types.js";
|
|
3
|
-
import type { MissionCoordinationUpdate, MissionSessionReport, Agent, AssistantContext, AssistantMissionPermissions, Attachment, Chat, ChatCommand, ChatDetail, ChatEvent, ChatRun, RuntimeHeartbeat, Conversation, ListenOptions, ListMessages, ListMissions, ListTasks, Message, MessageContent, MessageHandler, Metadata, MissionArtifact, MissionDetail, MissionEvent, MissionEventInput, MissionSummary, MissionWorkItem, Page, Pagination, RequestOptions, RepoClaim, RepoClaimState, RepositoryCoordination, SearchAgents, SendMessage, Subscription, Task, TaskState, TotemOptions, WriteOptions, NetworkActivityPage, NetworkActionInput, NetworkActionRequest, NetworkProtectedAction } from "./types.js";
|
|
3
|
+
import type { MissionCoordinationUpdate, MissionSessionReport, Agent, AgentMemory, AgentMemoryHistory, AgentMemorySearch, AgentProfileInput, AssistantContext, AssistantMissionPermissions, Attachment, Chat, ChatCommand, ChatDetail, ChatEvent, ChatRun, RuntimeHeartbeat, Conversation, ListenOptions, ListMessages, ListMissions, ListTasks, Message, MessageContent, MessageHandler, Metadata, MissionArtifact, MissionDetail, MissionEvent, MissionEventInput, MissionSummary, MissionWorkItem, Page, Pagination, RequestOptions, RepoClaim, RepoClaimState, RepositoryCoordination, SearchAgents, SendMessage, Subscription, Task, TaskState, TotemOptions, WriteOptions, NetworkActivityPage, NetworkActionInput, NetworkActionRequest, NetworkProtectedAction } from "./types.js";
|
|
4
4
|
export * from "./types.js";
|
|
5
5
|
export * from "./errors.js";
|
|
6
6
|
export * from "./actions.js";
|
|
@@ -11,8 +11,29 @@ export declare class Totem {
|
|
|
11
11
|
static connect(options: TotemOptions): Promise<ConnectedAgent>;
|
|
12
12
|
connect(): Promise<ConnectedAgent>;
|
|
13
13
|
agent(options?: RequestOptions): Promise<Agent>;
|
|
14
|
+
profiles: {
|
|
15
|
+
create: (input: AgentProfileInput, options?: RequestOptions) => Promise<Agent & {
|
|
16
|
+
identityAgentId: string;
|
|
17
|
+
}>;
|
|
18
|
+
};
|
|
19
|
+
memory: {
|
|
20
|
+
search: (query?: AgentMemorySearch, options?: RequestOptions) => Promise<AgentMemoryHistory>;
|
|
21
|
+
get: (options?: RequestOptions) => Promise<AgentMemory>;
|
|
22
|
+
update: (input: {
|
|
23
|
+
notes: string;
|
|
24
|
+
workingMemory: string;
|
|
25
|
+
expectedVersion: number;
|
|
26
|
+
automatic?: boolean;
|
|
27
|
+
forgetIds?: string[];
|
|
28
|
+
reinforceIds?: string[];
|
|
29
|
+
}, options?: RequestOptions) => Promise<AgentMemory>;
|
|
30
|
+
};
|
|
31
|
+
/** Read durable approval and work updates from the authenticated agent's harness. */
|
|
14
32
|
activity: {
|
|
15
|
-
list: (query?: {
|
|
33
|
+
list: (query?: {
|
|
34
|
+
cursor?: string;
|
|
35
|
+
limit?: number;
|
|
36
|
+
}, options?: RequestOptions) => Promise<NetworkActivityPage>;
|
|
16
37
|
};
|
|
17
38
|
/** Native Network authorization. Human approval remains in the receiving owner's conversation. */
|
|
18
39
|
actions: {
|
|
@@ -77,8 +98,7 @@ export declare class Totem {
|
|
|
77
98
|
list: (query?: ListMessages, options?: RequestOptions) => Promise<Page<Message>>;
|
|
78
99
|
iterate: (query?: ListMessages, options?: RequestOptions) => AsyncIterable<Message>;
|
|
79
100
|
get: (id: string, options?: RequestOptions) => Promise<Message>;
|
|
80
|
-
reply: (id: string, text: string, options?: WriteOptions & {
|
|
81
|
-
metadata?: Metadata;
|
|
101
|
+
reply: (id: string, text: string, options?: WriteOptions & Pick<SendMessage, "metadata" | "authorization" | "ttlSeconds"> & {
|
|
82
102
|
attachments?: string[];
|
|
83
103
|
}) => Promise<Message>;
|
|
84
104
|
replyContent: (id: string, content: MessageContent, options?: WriteOptions) => Promise<Message>;
|
|
@@ -227,11 +247,24 @@ export declare class Totem {
|
|
|
227
247
|
}>;
|
|
228
248
|
};
|
|
229
249
|
runtime: {
|
|
230
|
-
activations: (options?: RequestOptions) => Promise<{
|
|
231
|
-
|
|
250
|
+
activations: (options?: RequestOptions) => Promise<{
|
|
251
|
+
data: Array<{
|
|
252
|
+
id: string;
|
|
253
|
+
state: string;
|
|
254
|
+
ready: boolean;
|
|
255
|
+
}>;
|
|
256
|
+
}>;
|
|
257
|
+
reportActivation: (input: {
|
|
258
|
+
id: string;
|
|
259
|
+
state: "starting" | "ready" | "blocked" | "paused";
|
|
260
|
+
errorCode?: "READINESS_UNSUPPORTED" | "LISTENER_START_FAILED" | "AUTHORIZATION_REJECTED";
|
|
261
|
+
}, options?: RequestOptions) => Promise<{
|
|
262
|
+
recorded: boolean;
|
|
263
|
+
}>;
|
|
232
264
|
start: (input: {
|
|
233
265
|
runId: string;
|
|
234
266
|
provider: string;
|
|
267
|
+
automatic?: boolean;
|
|
235
268
|
hostKind?: "local" | "hosted";
|
|
236
269
|
}, options?: RequestOptions) => Promise<{
|
|
237
270
|
runId: string;
|
|
@@ -243,6 +276,7 @@ export declare class Totem {
|
|
|
243
276
|
challenge: string;
|
|
244
277
|
completed: boolean;
|
|
245
278
|
protocol: string;
|
|
279
|
+
provider?: string | null;
|
|
246
280
|
}>;
|
|
247
281
|
replyProbe: (input: {
|
|
248
282
|
runId: string;
|
|
@@ -255,6 +289,7 @@ export declare class Totem {
|
|
|
255
289
|
heartbeat: (input?: {
|
|
256
290
|
label?: string;
|
|
257
291
|
runId?: string;
|
|
292
|
+
listenerKind?: "mcp";
|
|
258
293
|
state?: "starting" | "online" | "offline" | "error" | "blocked" | "reconnecting";
|
|
259
294
|
errorCode?: string;
|
|
260
295
|
}, options?: RequestOptions) => Promise<RuntimeHeartbeat>;
|
|
@@ -316,6 +351,15 @@ export declare class ConnectedAgent {
|
|
|
316
351
|
readonly address: string;
|
|
317
352
|
readonly client: Totem;
|
|
318
353
|
constructor(client: Totem, identity: Agent);
|
|
354
|
+
get listenerStatus(): {
|
|
355
|
+
state: string;
|
|
356
|
+
listenerReady: boolean;
|
|
357
|
+
executionAuthorized: false;
|
|
358
|
+
runId?: string;
|
|
359
|
+
code?: string;
|
|
360
|
+
automatic?: boolean;
|
|
361
|
+
hostKind?: "local" | "hosted";
|
|
362
|
+
};
|
|
319
363
|
get lastError(): Error | undefined;
|
|
320
364
|
send(input: SendMessage, options?: WriteOptions): Promise<Message>;
|
|
321
365
|
send(to: string, text: string, options?: WriteOptions): Promise<Message>;
|
package/bin/sdk/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{TotemError}from"./errors.js";import{createLocalInspector,InspectionError}from"./inspector.js";import{idempotencyKey,sleep,Transport}from"./transport.js";import{assertInputFields,assistantCheckOptions,signalOnlyOptions}from"./request-options.js";export*from"./types.js";export*from"./errors.js";export*from"./actions.js";import{idempotencyKey as idempotencyKey2}from"./transport.js";function content(input){if("from"in input)throw new TotemError("Sender identity comes from the API key.",{code:"INVALID_MESSAGE"});return input.content??{type:"text",text:input.text??input.message}}async function*paginate(fetchPage,cursor){const seen=new Set;if(cursor)seen.add(cursor);do{const page=await fetchPage(cursor);for(const item of page.data)yield item;cursor=page.nextCursor??void 0;if(cursor&&seen.has(cursor))throw new TotemError("Totem returned a repeated pagination cursor.",{code:"INVALID_RESPONSE"});if(cursor)seen.add(cursor)}while(cursor)}class Totem{#transport;#address;constructor(options){this.#transport=new Transport(options);this.#address=options.address?.trim().toLowerCase()}static async connect(options){return new Totem(options).connect()}async connect(){const identity=await this.agent();if(this.#address&&this.#address!==identity.address.toLowerCase()){throw new TotemError("The configured address does not match the authenticated agent.",{code:"IDENTITY_MISMATCH"})}return new ConnectedAgent(this,identity)}async agent(options){return(await this.#transport.request("GET","/agents/me",options)).data}activity={list:(query={},options)=>this.#transport.request("GET","/activity",{...signalOnlyOptions(options),query})};actions={list:(query={},options)=>this.#transport.request("GET","/actions",{...signalOnlyOptions(options),query}),request:async(input,options={})=>{assertInputFields(input,["to","action","description","conversationId","taskId","ttlSeconds"]);const{idempotencyKey:key,...requestOptions}=options;return(await this.#transport.request("POST","/actions",{...signalOnlyOptions(requestOptions),body:input,idempotencyKey:key??idempotencyKey()})).data},get:async(id,options)=>(await this.#transport.request("GET",`/actions/${encodeURIComponent(id)}`,signalOnlyOptions(options))).data,dispatch:async(id,input,options)=>{assertInputFields(input,["digest","action"]);return(await this.#transport.request("POST",`/actions/${encodeURIComponent(id)}/dispatch`,{...signalOnlyOptions(options),body:input})).data},complete:async(id,input,options)=>{assertInputFields(input,["executionId","outcome","summary"]);return(await this.#transport.request("POST",`/actions/${encodeURIComponent(id)}/complete`,{...signalOnlyOptions(options),body:input,retrySafe:true})).data},cancel:async(id,options)=>(await this.#transport.request("POST",`/actions/${encodeURIComponent(id)}/cancel`,{...signalOnlyOptions(options),retrySafe:true})).data,run:async(id,action,execute,options)=>{const requestOptions=signalOnlyOptions(options);const current=await this.actions.get(id,requestOptions);const dispatched=await this.actions.dispatch(id,{action,digest:current.digest},requestOptions);if(!dispatched.executionAuthorized||!dispatched.executionId)throw new TotemError("No execution permission was granted.",{code:"NETWORK_ACTION_DISPATCH_INVALID"});let result;try{result=await execute(structuredClone(dispatched.action),dispatched.executionId)}catch(cause){try{await this.actions.complete(id,{executionId:dispatched.executionId,outcome:"uncertain",summary:"The local executor stopped without a confirmed outcome. Inspect before repeating any effect."})}catch{}throw new TotemError("Action outcome uncertain. Inspect it; do not repeat.",{code:"NETWORK_ACTION_OUTCOME_UNCERTAIN",cause})}try{const receipt=await this.actions.complete(id,{executionId:dispatched.executionId,outcome:"succeeded",summary:result.summary},requestOptions);return{value:result.value,receipt}}catch(cause){throw new TotemError("Action finished; receipt pending. Reconcile it without reexecuting.",{code:"NETWORK_ACTION_RECEIPT_PENDING",cause})}}};assistant={context:async options=>(await this.#transport.request("GET","/assistant/context",signalOnlyOptions(options))).data,checks:{create:async(missionId,input,options)=>{assertInputFields(input,["workItemId","leaseToken","action"]);return(await this.#transport.request("POST",`/assistant/missions/${encodeURIComponent(missionId)}/action-checks`,{...assistantCheckOptions(options),body:input})).data},list:async(missionId,workItemId,options)=>(await this.#transport.request("GET",`/assistant/missions/${encodeURIComponent(missionId)}/action-checks`,{...signalOnlyOptions(options),query:{workItemId}})).data,recordReceipt:async(missionId,suiteId,input,options)=>{assertInputFields(input,["workItemId"]);return(await this.#transport.request("POST",`/assistant/missions/${encodeURIComponent(missionId)}/action-checks/${encodeURIComponent(suiteId)}/receipt`,{...signalOnlyOptions(options),body:input})).data}},permissions:async(missionId,options)=>(await this.#transport.request("GET",`/assistant/missions/${encodeURIComponent(missionId)}/permissions`,signalOnlyOptions(options))).data};async resolve(address,options){return(await this.#transport.request("GET",`/resolve/${encodeURIComponent(address)}`,options)).data}agents={search:(query={},options)=>this.#transport.request("GET","/agents/search",{...options,query}),iterate:(query={},options)=>paginate(cursor=>this.agents.search({...query,cursor},options),query.cursor)};attachments={upload:async(name,mediaType,bytes,options={})=>{if(!bytes.byteLength||bytes.byteLength>2*1024*1024)throw new TotemError("Attachments must be nonempty and no larger than 2 MiB.",{code:"ATTACHMENT_SIZE_INVALID"});return(await this.#transport.request("POST","/attachments",{...options,binaryBody:bytes,headers:{"Content-Type":mediaType,"X-Totem-File-Name":encodeURIComponent(name)}})).data},download:(id,options)=>this.#transport.download(`/attachments/${encodeURIComponent(id)}`,options)};messages={send:async(input,options={})=>{const body={...input,content:content(input)};delete body.text;delete body.message;return(await this.#transport.request("POST","/messages",{...options,body,idempotencyKey:options.idempotencyKey??idempotencyKey()})).data},list:(query={},options)=>this.#transport.request("GET","/messages",{...options,query}),iterate:(query={},options)=>paginate(cursor=>this.messages.list({...query,cursor},options),query.cursor),get:async(id,options)=>(await this.#transport.request("GET",`/messages/${encodeURIComponent(id)}`,options)).data,reply:async(id,text,options={})=>(await this.#transport.request("POST",`/messages/${encodeURIComponent(id)}/reply`,{...options,body:{content:{type:"text",text,...options.attachments?{attachments:options.attachments}:{}},...options.metadata?{metadata:options.metadata}:{}},idempotencyKey:options.idempotencyKey??`sdk:reply:${id}`})).data,replyContent:async(id,content2,options={})=>(await this.#transport.request("POST",`/messages/${encodeURIComponent(id)}/reply`,{...options,body:{content:content2},idempotencyKey:options.idempotencyKey??idempotencyKey()})).data,ack:async(id,options)=>(await this.#transport.request("POST",`/messages/${encodeURIComponent(id)}/ack`,{...options,body:{},retrySafe:true})).data};send(input,textOrOptions,options){if(typeof input==="string")return this.messages.send({to:input,text:textOrOptions},options);return this.messages.send(input,textOrOptions)}conversations={create:async(input,options)=>{const result=await this.#transport.request("POST","/conversations",{...options,body:input});return new ConversationHandle(this,result.data,input.with)},list:(query={},options)=>this.#transport.request("GET","/conversations",{...options,query}),get:async(id,options)=>(await this.#transport.request("GET",`/conversations/${encodeURIComponent(id)}`,options)).data,messages:(id,query={},options)=>this.#transport.request("GET",`/conversations/${encodeURIComponent(id)}/messages`,{...options,query})};startConversation(input,options){return this.conversations.create(input,options)}missions={list:async options=>(await this.#transport.request("GET","/missions",options)).data.missions,listPage:async(query={},options)=>{const page=await this.#transport.request("GET","/missions",{...options,query});return{data:page.data.missions,nextCursor:page.nextCursor??null}},iterate:(query={},options)=>paginate(cursor=>this.missions.listPage({...query,cursor},options),query.cursor),get:async(id,options)=>{const result=await this.#transport.request("GET",`/missions/${encodeURIComponent(id)}`,options);return{...result.data.mission,members:result.data.members,workItems:result.data.workItems,artifacts:result.data.artifacts,events:result.data.events,budget:result.data.budget,coordination:result.data.coordination}},coordination:async(id,version,options)=>(await this.#transport.request("GET",`/missions/${encodeURIComponent(id)}/coordination`,{...options,query:{version}})).data,sessions:{report:async(id,input,options)=>(await this.#transport.request("POST",`/missions/${encodeURIComponent(id)}/sessions`,{...options,body:input,retrySafe:true})).data},workItems:async(id,options)=>(await this.#transport.request("GET",`/missions/${encodeURIComponent(id)}/work-items`,options)).data.workItems,workItemsPage:async(id,query={},options)=>{const page=await this.#transport.request("GET",`/missions/${encodeURIComponent(id)}/work-items`,{...options,query});return{data:page.data.workItems,nextCursor:page.nextCursor??null}},iterateWorkItems:(id,query={},options)=>paginate(cursor=>this.missions.workItemsPage(id,{...query,cursor},options),query.cursor),assign:async(id,input,options={})=>(await this.#transport.request("POST",`/missions/${encodeURIComponent(id)}/work-items`,{...options,body:input,idempotencyKey:options.idempotencyKey??idempotencyKey()})).data,claim:async(id,workItemId,options)=>(await this.#transport.request("POST",`/missions/${encodeURIComponent(id)}/work-items/${encodeURIComponent(workItemId)}/claim`,{...options,body:{}})).data,renew:async(id,workItemId,leaseToken,options)=>(await this.#transport.request("POST",`/missions/${encodeURIComponent(id)}/work-items/${encodeURIComponent(workItemId)}/renew`,{...options,body:{leaseToken},retrySafe:true})).data,complete:async(id,workItemId,input,options={})=>(await this.#transport.request("POST",`/missions/${encodeURIComponent(id)}/work-items/${encodeURIComponent(workItemId)}/complete`,{...options,body:input,idempotencyKey:options.idempotencyKey??idempotencyKey()})).data,reserveEffect:async(id,input,options={})=>(await this.#transport.request("POST",`/missions/${encodeURIComponent(id)}/effects`,{...options,body:input,idempotencyKey:options.idempotencyKey??idempotencyKey()})).data,listEffects:async(id,workItemId,options)=>(await this.#transport.request("GET",`/missions/${encodeURIComponent(id)}/effects`,{...options,query:{workItemId}})).data.effects,dispatchEffect:async(id,effectId,leaseToken,options)=>(await this.#transport.request("POST",`/missions/${encodeURIComponent(id)}/effects/${encodeURIComponent(effectId)}/dispatch`,{...options,body:{leaseToken}})).data,settleEffect:async(id,effectId,input,options)=>(await this.#transport.request("POST",`/missions/${encodeURIComponent(id)}/effects/${encodeURIComponent(effectId)}/settle`,{...options,body:input,retrySafe:true})).data,events:{list:(id,query={},options)=>this.#transport.request("GET",`/missions/${encodeURIComponent(id)}/events`,{...options,query}),iterate:(id,query={},options)=>paginate(cursor=>this.missions.events.list(id,{...query,cursor},options),query.cursor),post:async(id,input,options={})=>{if("from"in input||"sender"in input||"actor"in input)throw new TotemError("Mission event actors come from the agent credential.",{code:"INVALID_EVENT"});return(await this.#transport.request("POST",`/missions/${encodeURIComponent(id)}/events`,{...options,body:input,idempotencyKey:options.idempotencyKey??idempotencyKey()})).data},ack:async(id,eventId,options)=>(await this.#transport.request("POST",`/missions/${encodeURIComponent(id)}/events/${encodeURIComponent(eventId)}/ack`,{...options,body:{},retrySafe:true})).data}};chats={list:(query={},options)=>this.#transport.request("GET","/chats",{...options,query}),get:async(id,options={})=>(await this.#transport.request("GET",`/chats/${encodeURIComponent(id)}`,{signal:options.signal,query:{cursor:options.cursor,before:options.before,limit:options.limit}})).data,updates:async(id,text,options={})=>(await this.#transport.request("POST",`/chats/${encodeURIComponent(id)}/updates`,{...options,body:{text},idempotencyKey:options.idempotencyKey??idempotencyKey()})).data};chatCommands={list:(query={},options)=>this.#transport.request("GET","/chat-commands",{...options,query}),claim:async(id,options)=>(await this.#transport.request("POST",`/chat-commands/${encodeURIComponent(id)}/claim`,{...options,body:{}})).data,renew:async(id,leaseToken,options)=>(await this.#transport.request("POST",`/chat-commands/${encodeURIComponent(id)}/renew`,{...options,body:{leaseToken},retrySafe:true})).data,complete:async(id,input,options={})=>(await this.#transport.request("POST",`/chat-commands/${encodeURIComponent(id)}/complete`,{...options,body:input,idempotencyKey:options.idempotencyKey??idempotencyKey()})).data,fail:async(id,input,options)=>(await this.#transport.request("POST",`/chat-commands/${encodeURIComponent(id)}/fail`,{...options,body:input,retrySafe:true})).data};runtime={activations:async options=>(await this.#transport.request("GET","/runtime/activations",options)).data,reportActivation:async(input,options)=>(await this.#transport.request("POST","/runtime/activations",{...options,body:input,retrySafe:true})).data,start:async(input,options)=>(await this.#transport.request("POST","/runtime/start",{...options,body:input,retrySafe:true})).data,probe:async(runId,options)=>(await this.#transport.request("GET","/runtime/probe",{...options,query:{runId}})).data,replyProbe:async(input,options)=>(await this.#transport.request("POST","/runtime/probe",{...options,body:input,retrySafe:true})).data,heartbeat:async(input={},options)=>(await this.#transport.request("POST","/runtime/heartbeat",{...options,body:input,retrySafe:true})).data};tasks={create:async(input,options={})=>(await this.#transport.request("POST","/tasks",{...options,body:{to:input.to,content:content(input),conversationId:input.conversationId,metadata:input.metadata,ttlSeconds:input.ttlSeconds,authorization:input.authorization},idempotencyKey:options.idempotencyKey??idempotencyKey()})).data,get:async(id,options)=>(await this.#transport.request("GET",`/tasks/${encodeURIComponent(id)}`,options)).data,list:(query={},options)=>this.#transport.request("GET","/tasks",{...options,query}),iterate:(query={},options)=>paginate(cursor=>this.tasks.list({...query,cursor},options),query.cursor),postUpdate:async(id,text,options={})=>(await this.#transport.request("POST",`/tasks/${encodeURIComponent(id)}/messages`,{...options,body:{content:{type:"text",text}},idempotencyKey:options.idempotencyKey??idempotencyKey()})).data,update:async(id,input,options)=>(await this.#transport.request("PATCH",`/tasks/${encodeURIComponent(id)}`,{...options,body:input})).data,cancel:async(id,options)=>(await this.#transport.request("POST",`/tasks/${encodeURIComponent(id)}/cancel`,{...options,body:{}})).data};repositories={getTask:async(taskId,options)=>(await this.#transport.request("GET",`/tasks/${encodeURIComponent(taskId)}/repository`,options)).data,claim:async(taskId,input,options)=>(await this.#transport.request("POST",`/tasks/${encodeURIComponent(taskId)}/repository`,{...options,body:input})).data,update:async(taskId,input,options)=>(await this.#transport.request("PATCH",`/tasks/${encodeURIComponent(taskId)}/repository`,{...options,body:input})).data};github={repositories:async options=>(await this.#transport.request("GET","/github/repositories",options)).data,openPullRequest:async(taskId,input,options)=>(await this.#transport.request("POST",`/github/pull-requests?taskId=${encodeURIComponent(taskId)}`,{...options,body:input})).data}}class ConversationHandle{id;conversation;#totem;#recipient;constructor(totem,conversation,recipient){this.#totem=totem;this.conversation=conversation;this.id=conversation.id;this.#recipient=recipient}send(text,options){return this.#totem.messages.send({to:this.#recipient,text,conversationId:this.id},options)}messages(query={},options){return this.#totem.conversations.messages(this.id,query,options)}}class ConnectedAgent{identity;address;client;#subscription;#lastError;constructor(client,identity){this.client=client;this.identity=identity;this.address=identity.address}get lastError(){return this.#lastError}send(input,textOrOptions,options){return typeof input==="string"?this.client.send(input,textOrOptions,options):this.client.send(input,textOrOptions)}startConversation(input){return this.client.startConversation(input)}on(event,handler,options={}){if(event!=="message")throw new TotemError("Only message subscriptions are supported.",{code:"INVALID_EVENT"});if(this.#subscription)throw new TotemError("This agent already has a message listener.",{code:"ALREADY_LISTENING"});const interval=options.pollIntervalMs??1e3;if(!Number.isFinite(interval)||interval<10)throw new TotemError("pollIntervalMs must be at least 10.",{code:"INVALID_CONFIG"});const controller=new AbortController;const inspector=options.inspector??createLocalInspector();const blocked=new Set;const report=cause=>{this.#lastError=cause instanceof Error?cause:new Error(String(cause));try{options.onError?.(this.#lastError)}catch{}};const done=(async()=>{while(!controller.signal.aborted){try{for await(const message of this.client.messages.iterate({direction:"inbound",unread:true,limit:100},{signal:controller.signal})){if(controller.signal.aborted)break;if(blocked.has(message.id))continue;try{if(message.content.type==="encrypted")throw new TotemError("Use E2eeTotem and pinned endpoint keys to process encrypted messages.",{code:"E2EE_CLIENT_REQUIRED"});await inspector.assertAllowed("input",{content:message.content,metadata:message.metadata??{}},{signal:controller.signal});const files=message.attachments??[];const ids=new Set(files.map(file=>file.id));if(message.content.type==="text"&&message.content.attachments?.some(id=>!ids.has(id)))throw new InspectionError("INSPECTION_UNSUPPORTED_FILE");for(const file of files){const downloaded=await this.client.attachments.download(file.id,{signal:controller.signal});await inspector.inspectFile(file.name,file.mediaType,downloaded.bytes,{signal:controller.signal})}const incoming={...message,text:message.content.type==="text"?message.content.text:JSON.stringify(message.content.data),reply:(text,writeOptions={})=>inspector.runAction({id:message.id,text},input=>this.client.messages.reply(input.id,input.text,{...writeOptions,idempotencyKey:writeOptions.idempotencyKey??`reply:${message.id}:0`}),{signal:controller.signal}),ack:()=>this.client.messages.ack(message.id)};await handler(incoming);await incoming.ack()}catch(error){if(error instanceof InspectionError&&error.code!=="INSPECTION_UNAVAILABLE")blocked.add(message.id);report(error)}}}catch(error){if(!controller.signal.aborted)report(error)}if(!controller.signal.aborted){try{await sleep(interval,controller.signal)}catch{}}}})();const subscription={done,stop:async()=>{controller.abort();await done;if(this.#subscription===subscription)this.#subscription=void 0}};this.#subscription=subscription;return subscription}async stop(){await this.#subscription?.stop()}}class TotemAgent extends Totem{}export{ConnectedAgent,ConversationHandle,InspectionError,Totem,TotemAgent,createLocalInspector,idempotencyKey2 as idempotencyKey};
|
|
1
|
+
import{TotemError}from"./errors.js";import{createAutomaticNetworkListener,DEFAULT_LISTENER_POLL_MS}from"./listener.js";import{createLocalInspector,InspectionError}from"./inspector.js";import{idempotencyKey,sleep,Transport}from"./transport.js";import{assertInputFields,assistantCheckOptions,signalOnlyOptions}from"./request-options.js";export*from"./types.js";export*from"./errors.js";export*from"./actions.js";import{idempotencyKey as idempotencyKey2}from"./transport.js";function content(input){if("from"in input)throw new TotemError("Sender identity comes from the API key.",{code:"INVALID_MESSAGE"});return input.content??{type:"text",text:input.text??input.message}}async function*paginate(fetchPage,cursor){const seen=new Set;if(cursor)seen.add(cursor);do{const page=await fetchPage(cursor);for(const item of page.data)yield item;cursor=page.nextCursor??void 0;if(cursor&&seen.has(cursor))throw new TotemError("Totem returned a repeated pagination cursor.",{code:"INVALID_RESPONSE"});if(cursor)seen.add(cursor)}while(cursor)}class Totem{#transport;#address;constructor(options){this.#transport=new Transport(options);this.#address=options.address?.trim().toLowerCase()}static async connect(options){return new Totem(options).connect()}async connect(){const identity=await this.agent();if(this.#address&&this.#address!==identity.address.toLowerCase()){throw new TotemError("The configured address does not match the authenticated agent.",{code:"IDENTITY_MISMATCH"})}return new ConnectedAgent(this,identity)}async agent(options){return(await this.#transport.request("GET","/agents/me",options)).data}profiles={create:async(input,options)=>{assertInputFields(input,["name","localPart","description","creationContext"]);return(await this.#transport.request("POST","/agent-profiles",{...signalOnlyOptions(options),body:input})).data}};memory={search:async(query={},options)=>{assertInputFields(query,["query","includeHistory","before","limit"]);return(await this.#transport.request("GET","/memory/search",{...signalOnlyOptions(options),query})).data},get:async options=>(await this.#transport.request("GET","/memory",signalOnlyOptions(options))).data,update:async(input,options)=>{assertInputFields(input,["notes","workingMemory","expectedVersion","reinforceIds","automatic","forgetIds"]);return(await this.#transport.request("PATCH","/memory",{...signalOnlyOptions(options),body:input,retrySafe:true})).data}};activity={list:(query={},options)=>this.#transport.request("GET","/activity",{...signalOnlyOptions(options),query})};actions={list:(query={},options)=>this.#transport.request("GET","/actions",{...signalOnlyOptions(options),query}),request:async(input,options={})=>{assertInputFields(input,["to","action","description","conversationId","taskId","ttlSeconds"]);const{idempotencyKey:key,...requestOptions}=options;return(await this.#transport.request("POST","/actions",{...signalOnlyOptions(requestOptions),body:input,idempotencyKey:key??idempotencyKey()})).data},get:async(id,options)=>(await this.#transport.request("GET",`/actions/${encodeURIComponent(id)}`,signalOnlyOptions(options))).data,dispatch:async(id,input,options)=>{assertInputFields(input,["digest","action"]);return(await this.#transport.request("POST",`/actions/${encodeURIComponent(id)}/dispatch`,{...signalOnlyOptions(options),body:input})).data},complete:async(id,input,options)=>{assertInputFields(input,["executionId","outcome","summary"]);return(await this.#transport.request("POST",`/actions/${encodeURIComponent(id)}/complete`,{...signalOnlyOptions(options),body:input,retrySafe:true})).data},cancel:async(id,options)=>(await this.#transport.request("POST",`/actions/${encodeURIComponent(id)}/cancel`,{...signalOnlyOptions(options),retrySafe:true})).data,run:async(id,action,execute,options)=>{const requestOptions=signalOnlyOptions(options);const current=await this.actions.get(id,requestOptions);const dispatched=await this.actions.dispatch(id,{action,digest:current.digest},requestOptions);if(!dispatched.executionAuthorized||!dispatched.executionId)throw new TotemError("No execution permission was granted.",{code:"NETWORK_ACTION_DISPATCH_INVALID"});let result;try{result=await execute(structuredClone(dispatched.action),dispatched.executionId)}catch(cause){try{await this.actions.complete(id,{executionId:dispatched.executionId,outcome:"uncertain",summary:"The local executor stopped without a confirmed outcome. Inspect before repeating any effect."})}catch{}throw new TotemError("Action outcome uncertain. Inspect it; do not repeat.",{code:"NETWORK_ACTION_OUTCOME_UNCERTAIN",cause})}try{const receipt=await this.actions.complete(id,{executionId:dispatched.executionId,outcome:"succeeded",summary:result.summary},requestOptions);return{value:result.value,receipt}}catch(cause){throw new TotemError("Action finished; receipt pending. Reconcile it without reexecuting.",{code:"NETWORK_ACTION_RECEIPT_PENDING",cause})}}};assistant={context:async options=>(await this.#transport.request("GET","/assistant/context",signalOnlyOptions(options))).data,checks:{create:async(missionId,input,options)=>{assertInputFields(input,["workItemId","leaseToken","action"]);return(await this.#transport.request("POST",`/assistant/missions/${encodeURIComponent(missionId)}/action-checks`,{...assistantCheckOptions(options),body:input})).data},list:async(missionId,workItemId,options)=>(await this.#transport.request("GET",`/assistant/missions/${encodeURIComponent(missionId)}/action-checks`,{...signalOnlyOptions(options),query:{workItemId}})).data,recordReceipt:async(missionId,suiteId,input,options)=>{assertInputFields(input,["workItemId"]);return(await this.#transport.request("POST",`/assistant/missions/${encodeURIComponent(missionId)}/action-checks/${encodeURIComponent(suiteId)}/receipt`,{...signalOnlyOptions(options),body:input})).data}},permissions:async(missionId,options)=>(await this.#transport.request("GET",`/assistant/missions/${encodeURIComponent(missionId)}/permissions`,signalOnlyOptions(options))).data};async resolve(address,options){return(await this.#transport.request("GET",`/resolve/${encodeURIComponent(address)}`,options)).data}agents={search:(query={},options)=>this.#transport.request("GET","/agents/search",{...options,query}),iterate:(query={},options)=>paginate(cursor=>this.agents.search({...query,cursor},options),query.cursor)};attachments={upload:async(name,mediaType,bytes,options={})=>{if(!bytes.byteLength||bytes.byteLength>2*1024*1024)throw new TotemError("Attachments must be nonempty and no larger than 2 MiB.",{code:"ATTACHMENT_SIZE_INVALID"});return(await this.#transport.request("POST","/attachments",{...options,binaryBody:bytes,headers:{"Content-Type":mediaType,"X-Totem-File-Name":encodeURIComponent(name)}})).data},download:(id,options)=>this.#transport.download(`/attachments/${encodeURIComponent(id)}`,options)};messages={send:async(input,options={})=>{const body={...input,content:content(input)};delete body.text;delete body.message;return(await this.#transport.request("POST","/messages",{...options,body,idempotencyKey:options.idempotencyKey??idempotencyKey()})).data},list:(query={},options)=>this.#transport.request("GET","/messages",{...options,query}),iterate:(query={},options)=>paginate(cursor=>this.messages.list({...query,cursor},options),query.cursor),get:async(id,options)=>(await this.#transport.request("GET",`/messages/${encodeURIComponent(id)}`,options)).data,reply:async(id,text,options={})=>(await this.#transport.request("POST",`/messages/${encodeURIComponent(id)}/reply`,{...options,body:{content:{type:"text",text,...options.attachments?{attachments:options.attachments}:{}},...options.metadata?{metadata:options.metadata}:{},...options.authorization?{authorization:options.authorization}:{},...options.ttlSeconds===void 0?{}:{ttlSeconds:options.ttlSeconds}},idempotencyKey:options.idempotencyKey??`sdk:reply:${id}`})).data,replyContent:async(id,content2,options={})=>(await this.#transport.request("POST",`/messages/${encodeURIComponent(id)}/reply`,{...options,body:{content:content2},idempotencyKey:options.idempotencyKey??idempotencyKey()})).data,ack:async(id,options)=>(await this.#transport.request("POST",`/messages/${encodeURIComponent(id)}/ack`,{...options,body:{},retrySafe:true})).data};send(input,textOrOptions,options){if(typeof input==="string")return this.messages.send({to:input,text:textOrOptions},options);return this.messages.send(input,textOrOptions)}conversations={create:async(input,options)=>{const result=await this.#transport.request("POST","/conversations",{...options,body:input});return new ConversationHandle(this,result.data,input.with)},list:(query={},options)=>this.#transport.request("GET","/conversations",{...options,query}),get:async(id,options)=>(await this.#transport.request("GET",`/conversations/${encodeURIComponent(id)}`,options)).data,messages:(id,query={},options)=>this.#transport.request("GET",`/conversations/${encodeURIComponent(id)}/messages`,{...options,query})};startConversation(input,options){return this.conversations.create(input,options)}missions={list:async options=>(await this.#transport.request("GET","/missions",options)).data.missions,listPage:async(query={},options)=>{const page=await this.#transport.request("GET","/missions",{...options,query});return{data:page.data.missions,nextCursor:page.nextCursor??null}},iterate:(query={},options)=>paginate(cursor=>this.missions.listPage({...query,cursor},options),query.cursor),get:async(id,options)=>{const result=await this.#transport.request("GET",`/missions/${encodeURIComponent(id)}`,options);return{...result.data.mission,members:result.data.members,workItems:result.data.workItems,artifacts:result.data.artifacts,events:result.data.events,budget:result.data.budget,coordination:result.data.coordination}},coordination:async(id,version,options)=>(await this.#transport.request("GET",`/missions/${encodeURIComponent(id)}/coordination`,{...options,query:{version}})).data,sessions:{report:async(id,input,options)=>(await this.#transport.request("POST",`/missions/${encodeURIComponent(id)}/sessions`,{...options,body:input,retrySafe:true})).data},workItems:async(id,options)=>(await this.#transport.request("GET",`/missions/${encodeURIComponent(id)}/work-items`,options)).data.workItems,workItemsPage:async(id,query={},options)=>{const page=await this.#transport.request("GET",`/missions/${encodeURIComponent(id)}/work-items`,{...options,query});return{data:page.data.workItems,nextCursor:page.nextCursor??null}},iterateWorkItems:(id,query={},options)=>paginate(cursor=>this.missions.workItemsPage(id,{...query,cursor},options),query.cursor),assign:async(id,input,options={})=>(await this.#transport.request("POST",`/missions/${encodeURIComponent(id)}/work-items`,{...options,body:input,idempotencyKey:options.idempotencyKey??idempotencyKey()})).data,claim:async(id,workItemId,options)=>(await this.#transport.request("POST",`/missions/${encodeURIComponent(id)}/work-items/${encodeURIComponent(workItemId)}/claim`,{...options,body:{}})).data,renew:async(id,workItemId,leaseToken,options)=>(await this.#transport.request("POST",`/missions/${encodeURIComponent(id)}/work-items/${encodeURIComponent(workItemId)}/renew`,{...options,body:{leaseToken},retrySafe:true})).data,complete:async(id,workItemId,input,options={})=>(await this.#transport.request("POST",`/missions/${encodeURIComponent(id)}/work-items/${encodeURIComponent(workItemId)}/complete`,{...options,body:input,idempotencyKey:options.idempotencyKey??idempotencyKey()})).data,reserveEffect:async(id,input,options={})=>(await this.#transport.request("POST",`/missions/${encodeURIComponent(id)}/effects`,{...options,body:input,idempotencyKey:options.idempotencyKey??idempotencyKey()})).data,listEffects:async(id,workItemId,options)=>(await this.#transport.request("GET",`/missions/${encodeURIComponent(id)}/effects`,{...options,query:{workItemId}})).data.effects,dispatchEffect:async(id,effectId,leaseToken,options)=>(await this.#transport.request("POST",`/missions/${encodeURIComponent(id)}/effects/${encodeURIComponent(effectId)}/dispatch`,{...options,body:{leaseToken}})).data,settleEffect:async(id,effectId,input,options)=>(await this.#transport.request("POST",`/missions/${encodeURIComponent(id)}/effects/${encodeURIComponent(effectId)}/settle`,{...options,body:input,retrySafe:true})).data,events:{list:(id,query={},options)=>this.#transport.request("GET",`/missions/${encodeURIComponent(id)}/events`,{...options,query}),iterate:(id,query={},options)=>paginate(cursor=>this.missions.events.list(id,{...query,cursor},options),query.cursor),post:async(id,input,options={})=>{if("from"in input||"sender"in input||"actor"in input)throw new TotemError("Mission event actors come from the agent credential.",{code:"INVALID_EVENT"});return(await this.#transport.request("POST",`/missions/${encodeURIComponent(id)}/events`,{...options,body:input,idempotencyKey:options.idempotencyKey??idempotencyKey()})).data},ack:async(id,eventId,options)=>(await this.#transport.request("POST",`/missions/${encodeURIComponent(id)}/events/${encodeURIComponent(eventId)}/ack`,{...options,body:{},retrySafe:true})).data}};chats={list:(query={},options)=>this.#transport.request("GET","/chats",{...options,query}),get:async(id,options={})=>(await this.#transport.request("GET",`/chats/${encodeURIComponent(id)}`,{signal:options.signal,query:{cursor:options.cursor,before:options.before,limit:options.limit}})).data,updates:async(id,text,options={})=>(await this.#transport.request("POST",`/chats/${encodeURIComponent(id)}/updates`,{...options,body:{text},idempotencyKey:options.idempotencyKey??idempotencyKey()})).data};chatCommands={list:(query={},options)=>this.#transport.request("GET","/chat-commands",{...options,query}),claim:async(id,options)=>(await this.#transport.request("POST",`/chat-commands/${encodeURIComponent(id)}/claim`,{...options,body:{}})).data,renew:async(id,leaseToken,options)=>(await this.#transport.request("POST",`/chat-commands/${encodeURIComponent(id)}/renew`,{...options,body:{leaseToken},retrySafe:true})).data,complete:async(id,input,options={})=>(await this.#transport.request("POST",`/chat-commands/${encodeURIComponent(id)}/complete`,{...options,body:input,idempotencyKey:options.idempotencyKey??idempotencyKey()})).data,fail:async(id,input,options)=>(await this.#transport.request("POST",`/chat-commands/${encodeURIComponent(id)}/fail`,{...options,body:input,retrySafe:true})).data};runtime={activations:async options=>(await this.#transport.request("GET","/runtime/activations",options)).data,reportActivation:async(input,options)=>(await this.#transport.request("POST","/runtime/activations",{...options,body:input,retrySafe:true})).data,start:async(input,options)=>(await this.#transport.request("POST","/runtime/start",{...options,body:input,retrySafe:true})).data,probe:async(runId,options)=>(await this.#transport.request("GET","/runtime/probe",{...options,query:{runId}})).data,replyProbe:async(input,options)=>(await this.#transport.request("POST","/runtime/probe",{...options,body:input,retrySafe:true})).data,heartbeat:async(input={},options)=>(await this.#transport.request("POST","/runtime/heartbeat",{...options,body:input,retrySafe:true})).data};tasks={create:async(input,options={})=>(await this.#transport.request("POST","/tasks",{...options,body:{to:input.to,content:content(input),conversationId:input.conversationId,metadata:input.metadata,ttlSeconds:input.ttlSeconds,authorization:input.authorization},idempotencyKey:options.idempotencyKey??idempotencyKey()})).data,get:async(id,options)=>(await this.#transport.request("GET",`/tasks/${encodeURIComponent(id)}`,options)).data,list:(query={},options)=>this.#transport.request("GET","/tasks",{...options,query}),iterate:(query={},options)=>paginate(cursor=>this.tasks.list({...query,cursor},options),query.cursor),postUpdate:async(id,text,options={})=>(await this.#transport.request("POST",`/tasks/${encodeURIComponent(id)}/messages`,{...options,body:{content:{type:"text",text}},idempotencyKey:options.idempotencyKey??idempotencyKey()})).data,update:async(id,input,options)=>(await this.#transport.request("PATCH",`/tasks/${encodeURIComponent(id)}`,{...options,body:input})).data,cancel:async(id,options)=>(await this.#transport.request("POST",`/tasks/${encodeURIComponent(id)}/cancel`,{...options,body:{}})).data};repositories={getTask:async(taskId,options)=>(await this.#transport.request("GET",`/tasks/${encodeURIComponent(taskId)}/repository`,options)).data,claim:async(taskId,input,options)=>(await this.#transport.request("POST",`/tasks/${encodeURIComponent(taskId)}/repository`,{...options,body:input})).data,update:async(taskId,input,options)=>(await this.#transport.request("PATCH",`/tasks/${encodeURIComponent(taskId)}/repository`,{...options,body:input})).data};github={repositories:async options=>(await this.#transport.request("GET","/github/repositories",options)).data,openPullRequest:async(taskId,input,options)=>(await this.#transport.request("POST",`/github/pull-requests?taskId=${encodeURIComponent(taskId)}`,{...options,body:input})).data}}class ConversationHandle{id;conversation;#totem;#recipient;constructor(totem,conversation,recipient){this.#totem=totem;this.conversation=conversation;this.id=conversation.id;this.#recipient=recipient}send(text,options){return this.#totem.messages.send({to:this.#recipient,text,conversationId:this.id},options)}messages(query={},options){return this.#totem.conversations.messages(this.id,query,options)}}class ConnectedAgent{identity;address;client;#subscription;#lastError;#receiver;constructor(client,identity){this.client=client;this.identity=identity;this.address=identity.address;this.#receiver=createAutomaticNetworkListener({client,expectedAgentId:identity.id,notify:(_logger,data)=>{const status=data;if(status.code)this.#lastError=new TotemError("The automatic listener needs attention.",{code:status.code})}});this.#receiver.start()}get listenerStatus(){return this.#receiver.status()}get lastError(){return this.#lastError}send(input,textOrOptions,options){return typeof input==="string"?this.client.send(input,textOrOptions,options):this.client.send(input,textOrOptions)}startConversation(input){return this.client.startConversation(input)}on(event,handler,options={}){if(event!=="message")throw new TotemError("Only message subscriptions are supported.",{code:"INVALID_EVENT"});if(this.#subscription)throw new TotemError("This agent already has a message listener.",{code:"ALREADY_LISTENING"});const interval=options.pollIntervalMs??DEFAULT_LISTENER_POLL_MS;if(!Number.isFinite(interval)||interval<10)throw new TotemError("pollIntervalMs must be at least 10.",{code:"INVALID_CONFIG"});const controller=new AbortController;const inspector=options.inspector??createLocalInspector();const blocked=new Set;const report=cause=>{this.#lastError=cause instanceof Error?cause:new Error(String(cause));try{options.onError?.(this.#lastError)}catch{}};const done=(async()=>{while(!controller.signal.aborted){try{for await(const message of this.client.messages.iterate({direction:"inbound",unread:true,limit:100},{signal:controller.signal})){if(controller.signal.aborted)break;if(blocked.has(message.id))continue;try{if(message.content.type==="encrypted")throw new TotemError("Use E2eeTotem and pinned endpoint keys to process encrypted messages.",{code:"E2EE_CLIENT_REQUIRED"});await inspector.assertAllowed("input",{content:message.content,metadata:message.metadata??{}},{signal:controller.signal});const files=message.attachments??[];const ids=new Set(files.map(file=>file.id));if(message.content.type==="text"&&message.content.attachments?.some(id=>!ids.has(id)))throw new InspectionError("INSPECTION_UNSUPPORTED_FILE");for(const file of files){const downloaded=await this.client.attachments.download(file.id,{signal:controller.signal});await inspector.inspectFile(file.name,file.mediaType,downloaded.bytes,{signal:controller.signal})}const incoming={...message,text:message.content.type==="text"?message.content.text:JSON.stringify(message.content.data),reply:(text,writeOptions={})=>inspector.runAction({id:message.id,text},input=>this.client.messages.reply(input.id,input.text,{...writeOptions,idempotencyKey:writeOptions.idempotencyKey??`reply:${message.id}:0`}),{signal:controller.signal}),ack:()=>this.client.messages.ack(message.id)};await handler(incoming);await incoming.ack()}catch(error){if(error instanceof InspectionError&&error.code!=="INSPECTION_UNAVAILABLE")blocked.add(message.id);report(error)}}}catch(error){if(!controller.signal.aborted)report(error)}if(!controller.signal.aborted){try{await sleep(interval,controller.signal)}catch{}}}})();const subscription={done,stop:async()=>{controller.abort();await done;if(this.#subscription===subscription)this.#subscription=void 0}};this.#subscription=subscription;return subscription}async stop(){await Promise.all([this.#subscription?.stop(),this.#receiver.stop()])}}class TotemAgent extends Totem{}export{ConnectedAgent,ConversationHandle,InspectionError,Totem,TotemAgent,createLocalInspector,idempotencyKey2 as idempotencyKey};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { Totem } from "./index.js";
|
|
2
|
+
export declare const DEFAULT_LISTENER_POLL_MS = 5000;
|
|
3
|
+
export type ListenerStatus = {
|
|
4
|
+
state: string;
|
|
5
|
+
listenerReady: boolean;
|
|
6
|
+
executionAuthorized: false;
|
|
7
|
+
runId?: string;
|
|
8
|
+
code?: string;
|
|
9
|
+
automatic?: boolean;
|
|
10
|
+
hostKind?: "local" | "hosted";
|
|
11
|
+
};
|
|
12
|
+
export type ListenerOptions = {
|
|
13
|
+
client: Totem;
|
|
14
|
+
authorize?: (signal: AbortSignal) => Promise<unknown>;
|
|
15
|
+
enabled?: () => Promise<boolean>;
|
|
16
|
+
notify: (logger: string, data: unknown, id?: string) => void | Promise<void>;
|
|
17
|
+
hostKind?: "local" | "hosted";
|
|
18
|
+
pollMs?: number;
|
|
19
|
+
cursors?: {
|
|
20
|
+
cursor?: string;
|
|
21
|
+
messageCursor?: string;
|
|
22
|
+
};
|
|
23
|
+
signal?: AbortSignal;
|
|
24
|
+
expectedAgentId?: string;
|
|
25
|
+
};
|
|
26
|
+
/** The same durable feed is used by tools and the connection-owned receiver. */
|
|
27
|
+
export declare function readTotemActivity(client: Totem, { cursor, messageCursor, limit, }: {
|
|
28
|
+
cursor?: string;
|
|
29
|
+
messageCursor?: string;
|
|
30
|
+
limit?: number;
|
|
31
|
+
}, signal: AbortSignal): Promise<{
|
|
32
|
+
activity: import("./types.js").NetworkActivityPage;
|
|
33
|
+
messages: {
|
|
34
|
+
cursor: string;
|
|
35
|
+
data: import("./types.js").Message[];
|
|
36
|
+
nextCursor?: string | null;
|
|
37
|
+
blocked?: Array<{
|
|
38
|
+
sequence: string;
|
|
39
|
+
}>;
|
|
40
|
+
};
|
|
41
|
+
executionAuthorized: boolean;
|
|
42
|
+
}>;
|
|
43
|
+
/** Lives with the active host connection. Never invokes a model or acknowledges work. */
|
|
44
|
+
export declare function createAutomaticNetworkListener({ client, authorize, enabled, notify, hostKind, pollMs, cursors, signal, expectedAgentId, }: ListenerOptions): {
|
|
45
|
+
start(): void;
|
|
46
|
+
stop(): Promise<void>;
|
|
47
|
+
status(): {
|
|
48
|
+
state: string;
|
|
49
|
+
listenerReady: boolean;
|
|
50
|
+
executionAuthorized: false;
|
|
51
|
+
runId?: string;
|
|
52
|
+
code?: string;
|
|
53
|
+
automatic?: boolean;
|
|
54
|
+
hostKind?: "local" | "hosted";
|
|
55
|
+
};
|
|
56
|
+
done(): Promise<void>;
|
|
57
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{sleep}from"./transport.js";const DEFAULT_LISTENER_POLL_MS=5e3;async function readTotemActivity(client,{cursor,messageCursor,limit=25},signal){const[activity,messagesPage]=await Promise.all([client.activity.list({cursor,limit},{signal}),client.messages.list({direction:"all",cursor:messageCursor,limit},{signal})]);const messages=messagesPage;const scanned=[...messages.data,...messages.blocked??[]];const last=scanned.reduce((value,row)=>BigInt(row.sequence)>BigInt(value)?row.sequence:value,messageCursor??"0");return{activity,messages:{...messages,cursor:last},executionAuthorized:false}}function createAutomaticNetworkListener({client,authorize=async()=>{},enabled=async()=>true,notify,hostKind="local",pollMs=DEFAULT_LISTENER_POLL_MS,cursors={},signal,expectedAgentId}){const abort=new AbortController;let pending;let runId;let state={state:"disconnected",listenerReady:false,executionAuthorized:false};let position={...cursors};const options={signal:abort.signal};signal?.addEventListener("abort",()=>abort.abort(),{once:true});if(signal?.aborted)abort.abort();async function status(next){if(state.state===next.state&&state.runId===next.runId)return;state={listenerReady:false,executionAuthorized:false,...next};await notify("totem.listener",state)}async function loop(){try{if(!await enabled()){await status({state:"paused"});return}while(!abort.signal.aborted){let delay=pollMs;try{if(!await enabled()){await status({state:"paused"});break}await authorize(abort.signal);if(!runId){const identity=await client.agent(options);if(expectedAgentId&&identity.id!==expectedAgentId||!/^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/i.test(identity.id)||!["PRODUCTION","SANDBOX"].includes(identity.environment))throw Object.assign(new Error("Unverified agent identity"),{code:"AGENT_IDENTITY_MISMATCH",status:403});const started=await client.runtime.start({runId:globalThis.crypto.randomUUID(),provider:"mcp",hostKind,automatic:true},options);runId=started.runId;const probe=await client.runtime.probe(runId,options);if(probe.provider!=="mcp")throw Object.assign(new Error("Preserve the supervised runtime"),{code:"LISTENER_ALREADY_ACTIVE"});if(!probe.completed)await client.runtime.replyProbe({runId,reply:`TOTEM_READY:${probe.challenge}`},options)}await client.runtime.heartbeat({runId,state:"online",listenerKind:"mcp"},options);await status({state:"listening",listenerReady:true,runId,automatic:true,hostKind});const page=await readTotemActivity(client,position,abort.signal);if(page.activity.data.length||page.messages.data.length||page.messages.blocked?.length){await notify("totem.activity",page,`${page.activity.cursor}:${page.messages.cursor}`)}position={cursor:page.activity.cursor,messageCursor:page.messages.cursor};if(page.activity.nextCursor||page.messages.nextCursor)delay=0}catch(caught){const error=caught;if(abort.signal.aborted)break;if([401,403].includes(error.status??0)||error.code==="AGENT_IDENTITY_MISMATCH"){await status({state:"authorization_rejected",code:error.code});break}runId=void 0;const paused=["LISTENER_PAUSED","LISTENER_STOPPED"].includes(error.code??"");await status({state:paused?"paused":error.code==="LISTENER_ALREADY_ACTIVE"?"managed":"reconnecting",code:error.code??"NETWORK_ERROR"});delay=Math.max(pollMs,Math.min(3e4,(error.retryAfterSeconds??5)*1e3))}await sleep(delay,abort.signal)}}catch(caught){const error=caught;if(!abort.signal.aborted)state={state:"disconnected",listenerReady:false,executionAuthorized:false,code:error.code??"MCP_TRANSPORT_CLOSED"}}finally{state={...state,state:["listening","disconnected"].includes(state.state)?"disconnected":state.state,listenerReady:false}}}return{start(){pending??=loop()},async stop(){abort.abort();await pending},status(){return{...state}},async done(){await pending}}}export{DEFAULT_LISTENER_POLL_MS,createAutomaticNetworkListener,readTotemActivity};
|
package/bin/sdk/types.d.ts
CHANGED
|
@@ -1,3 +1,39 @@
|
|
|
1
|
+
export interface AgentMemoryControls {
|
|
2
|
+
enabled: boolean;
|
|
3
|
+
knowledgeRetentionDays: number;
|
|
4
|
+
workingRetentionDays: number;
|
|
5
|
+
}
|
|
6
|
+
export interface AgentMemoryEntry {
|
|
7
|
+
id: string;
|
|
8
|
+
section: "notes" | "workingMemory";
|
|
9
|
+
text: string;
|
|
10
|
+
reinforcedAt: string;
|
|
11
|
+
pinned: boolean;
|
|
12
|
+
expiresAt: string | null;
|
|
13
|
+
sourceKey?: string;
|
|
14
|
+
sourceEventId?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface AgentMemory {
|
|
18
|
+
agentId: string;
|
|
19
|
+
notes: string;
|
|
20
|
+
workingMemory: string;
|
|
21
|
+
version: number;
|
|
22
|
+
updatedAt: string | null;
|
|
23
|
+
updatedBy: string | null;
|
|
24
|
+
controls: AgentMemoryControls;
|
|
25
|
+
entries: AgentMemoryEntry[];
|
|
26
|
+
forgottenCount: number;
|
|
27
|
+
active: { pendingMessages: number; queuedChats: number; openTasks: number };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface AgentProfileInput {
|
|
31
|
+
name: string;
|
|
32
|
+
localPart: string;
|
|
33
|
+
description?: string;
|
|
34
|
+
creationContext: { background?: string; decisions?: string; nextSteps?: string };
|
|
35
|
+
}
|
|
36
|
+
|
|
1
37
|
export type Metadata = Record<string, unknown>;
|
|
2
38
|
export type JsonValue = null | boolean | number | string | JsonValue[] | {
|
|
3
39
|
[key: string]: JsonValue;
|
|
@@ -22,6 +58,8 @@ export interface AgentRuntimeStatus {
|
|
|
22
58
|
}
|
|
23
59
|
|
|
24
60
|
export interface Agent {
|
|
61
|
+
/** Own authenticated identity only; automatically loaded at connection. */
|
|
62
|
+
memory?: AgentMemory;
|
|
25
63
|
/** Absent from public discovery; a connected MCP client alone is not a ready listener. */
|
|
26
64
|
runtime?: AgentRuntimeStatus;
|
|
27
65
|
actionMode?: "ask_me" | "full_access";
|
|
@@ -652,6 +690,7 @@ export type MessageHandler = (message: IncomingMessage) => void | Promise<void>;
|
|
|
652
690
|
export interface ListenOptions {
|
|
653
691
|
/** Customer-side policy. Screens message/file plaintext before invoking the handler. */
|
|
654
692
|
inspector?: import("./inspector.js").LocalInspector;
|
|
693
|
+
/** Healthy inbox checks default to 5000 ms; an explicit host override is preserved. */
|
|
655
694
|
pollIntervalMs?: number;
|
|
656
695
|
/** Receives handler and transport failures; polling continues afterward. */
|
|
657
696
|
onError?: (error: Error) => void;
|
|
@@ -703,6 +742,8 @@ export interface ChatDetail {
|
|
|
703
742
|
previousCursor?: string | null;
|
|
704
743
|
}
|
|
705
744
|
export interface RuntimeHeartbeat {
|
|
745
|
+
listenerReady?: boolean;
|
|
746
|
+
listenerKind?: "mcp" | "runtime";
|
|
706
747
|
operationalStatesSupported?: boolean;
|
|
707
748
|
ready?: boolean;
|
|
708
749
|
state?: string;
|
|
@@ -726,3 +767,24 @@ export interface NetworkActivityPage extends Page<NetworkActivity> {
|
|
|
726
767
|
actionMode: "ask_me" | "full_access";
|
|
727
768
|
actionModeVersion: number;
|
|
728
769
|
}
|
|
770
|
+
|
|
771
|
+
export interface AgentMemorySearch {
|
|
772
|
+
query?: string;
|
|
773
|
+
includeHistory?: boolean;
|
|
774
|
+
before?: number;
|
|
775
|
+
limit?: number;
|
|
776
|
+
}
|
|
777
|
+
export interface AgentMemoryHistory {
|
|
778
|
+
data: Array<{
|
|
779
|
+
id: string;
|
|
780
|
+
version: number;
|
|
781
|
+
notes: string;
|
|
782
|
+
workingMemory: string;
|
|
783
|
+
source: string;
|
|
784
|
+
sourceEventId: string | null;
|
|
785
|
+
createdAt: string;
|
|
786
|
+
current: boolean;
|
|
787
|
+
excerpt: string;
|
|
788
|
+
}>;
|
|
789
|
+
nextCursor: string | null;
|
|
790
|
+
}
|