totemlayer 0.1.21 → 0.1.25
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 +34 -32
- package/bin/agent-runner.mjs +27 -2
- package/bin/agent-setup.mjs +33 -6
- package/bin/automatic-runtime.mjs +105 -9
- package/bin/bound-login.mjs +107 -0
- package/bin/connected-agent-service.mjs +119 -78
- package/bin/device-login.mjs +108 -9
- package/bin/installation.mjs +175 -76
- package/bin/mission-agent-service.mjs +23 -5
- package/bin/mission-coordination.mjs +311 -0
- package/bin/mission-worker.mjs +271 -20
- package/bin/model-adapter.mjs +30 -8
- package/bin/network-runtime.mjs +115 -15
- package/bin/runtime-diagnostics.mjs +4 -0
- package/bin/runtime-installation.mjs +1 -0
- package/bin/sdk/action-types.d.ts +236 -0
- package/bin/sdk/actions.d.ts +19 -0
- package/bin/sdk/errors.d.ts +30 -0
- package/bin/sdk/errors.js +14 -1
- package/bin/sdk/index.d.ts +300 -0
- package/bin/sdk/index.js +57 -11
- package/bin/sdk/inspector.d.ts +40 -0
- package/bin/sdk/request-options.d.ts +13 -0
- package/bin/sdk/transport.d.ts +24 -0
- package/bin/sdk/types.d.ts +640 -0
- package/bin/totem.mjs +2 -2
- package/package.json +1 -1
- package/scripts/prepare.mjs +9 -20
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import { createLocalInspector, InspectionError } from "./inspector.js";
|
|
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 } from "./types.js";
|
|
4
|
+
export * from "./types.js";
|
|
5
|
+
export * from "./errors.js";
|
|
6
|
+
export * from "./actions.js";
|
|
7
|
+
export { idempotencyKey } from "./transport.js";
|
|
8
|
+
export declare class Totem {
|
|
9
|
+
#private;
|
|
10
|
+
constructor(options: TotemOptions);
|
|
11
|
+
static connect(options: TotemOptions): Promise<ConnectedAgent>;
|
|
12
|
+
connect(): Promise<ConnectedAgent>;
|
|
13
|
+
agent(options?: RequestOptions): Promise<Agent>;
|
|
14
|
+
assistant: {
|
|
15
|
+
context: (options?: RequestOptions) => Promise<AssistantContext>;
|
|
16
|
+
checks: {
|
|
17
|
+
create: (missionId: string, input: {
|
|
18
|
+
workItemId: string;
|
|
19
|
+
leaseToken: string;
|
|
20
|
+
action: ActionProposal;
|
|
21
|
+
}, options: AssistantCheckOptions) => Promise<AssistantActionCheck>;
|
|
22
|
+
list: (missionId: string, workItemId: string, options?: RequestOptions) => Promise<AssistantActionCheckList>;
|
|
23
|
+
recordReceipt: (missionId: string, suiteId: string, input: {
|
|
24
|
+
workItemId: string;
|
|
25
|
+
}, options?: RequestOptions) => Promise<AssistantActionReceiptResult>;
|
|
26
|
+
};
|
|
27
|
+
permissions: (missionId: string, options?: RequestOptions) => Promise<AssistantMissionPermissions>;
|
|
28
|
+
};
|
|
29
|
+
resolve(address: string, options?: RequestOptions): Promise<Agent>;
|
|
30
|
+
agents: {
|
|
31
|
+
search: (query?: SearchAgents, options?: RequestOptions) => Promise<Page<Agent>>;
|
|
32
|
+
iterate: (query?: SearchAgents, options?: RequestOptions) => AsyncIterable<Agent>;
|
|
33
|
+
};
|
|
34
|
+
attachments: {
|
|
35
|
+
upload: (name: string, mediaType: string, bytes: Uint8Array, options?: RequestOptions) => Promise<Attachment>;
|
|
36
|
+
download: (id: string, options?: RequestOptions) => Promise<{
|
|
37
|
+
bytes: Uint8Array;
|
|
38
|
+
mediaType: string;
|
|
39
|
+
name: string;
|
|
40
|
+
sha256: string | null;
|
|
41
|
+
}>;
|
|
42
|
+
};
|
|
43
|
+
messages: {
|
|
44
|
+
send: (input: SendMessage, options?: WriteOptions) => Promise<Message>;
|
|
45
|
+
list: (query?: ListMessages, options?: RequestOptions) => Promise<Page<Message>>;
|
|
46
|
+
iterate: (query?: ListMessages, options?: RequestOptions) => AsyncIterable<Message>;
|
|
47
|
+
get: (id: string, options?: RequestOptions) => Promise<Message>;
|
|
48
|
+
reply: (id: string, text: string, options?: WriteOptions & {
|
|
49
|
+
metadata?: Metadata;
|
|
50
|
+
attachments?: string[];
|
|
51
|
+
}) => Promise<Message>;
|
|
52
|
+
replyContent: (id: string, content: MessageContent, options?: WriteOptions) => Promise<Message>;
|
|
53
|
+
ack: (id: string, options?: RequestOptions) => Promise<Message>;
|
|
54
|
+
};
|
|
55
|
+
send(input: SendMessage, options?: WriteOptions): Promise<Message>;
|
|
56
|
+
send(to: string, text: string, options?: WriteOptions): Promise<Message>;
|
|
57
|
+
conversations: {
|
|
58
|
+
create: (input: {
|
|
59
|
+
with: string;
|
|
60
|
+
metadata?: Metadata;
|
|
61
|
+
}, options?: RequestOptions) => Promise<ConversationHandle>;
|
|
62
|
+
list: (query?: Pagination, options?: RequestOptions) => Promise<Page<Conversation>>;
|
|
63
|
+
get: (id: string, options?: RequestOptions) => Promise<Conversation>;
|
|
64
|
+
messages: (id: string, query?: Pagination, options?: RequestOptions) => Promise<Page<Message>>;
|
|
65
|
+
};
|
|
66
|
+
startConversation(input: {
|
|
67
|
+
with: string;
|
|
68
|
+
metadata?: Metadata;
|
|
69
|
+
}, options?: RequestOptions): Promise<ConversationHandle>;
|
|
70
|
+
missions: {
|
|
71
|
+
list: (options?: RequestOptions) => Promise<MissionSummary[]>;
|
|
72
|
+
listPage: (query?: ListMissions, options?: RequestOptions) => Promise<Page<MissionSummary>>;
|
|
73
|
+
iterate: (query?: ListMissions, options?: RequestOptions) => AsyncIterable<MissionSummary>;
|
|
74
|
+
get: (id: string, options?: RequestOptions) => Promise<MissionDetail>;
|
|
75
|
+
coordination: (id: string, version?: string, options?: RequestOptions) => Promise<MissionCoordinationUpdate>;
|
|
76
|
+
sessions: {
|
|
77
|
+
report: (id: string, input: MissionSessionReport, options?: RequestOptions) => Promise<{
|
|
78
|
+
sessionId: string;
|
|
79
|
+
sequence: number;
|
|
80
|
+
}>;
|
|
81
|
+
};
|
|
82
|
+
workItems: (id: string, options?: RequestOptions) => Promise<MissionWorkItem[]>;
|
|
83
|
+
/** Empty data can still have a nextCursor: readiness is checked after scanning candidates. */
|
|
84
|
+
workItemsPage: (id: string, query?: Pagination, options?: RequestOptions) => Promise<Page<MissionWorkItem>>;
|
|
85
|
+
iterateWorkItems: (id: string, query?: Pagination, options?: RequestOptions) => AsyncIterable<MissionWorkItem>;
|
|
86
|
+
assign: (id: string, input: {
|
|
87
|
+
ownerMemberId: string;
|
|
88
|
+
objective: string;
|
|
89
|
+
requiredOutput: string;
|
|
90
|
+
dependsOnIds?: string[];
|
|
91
|
+
parentWorkItemId: string;
|
|
92
|
+
metadata?: Metadata;
|
|
93
|
+
}, options?: WriteOptions) => Promise<MissionWorkItem>;
|
|
94
|
+
claim: (id: string, workItemId: string, options?: WriteOptions) => Promise<{
|
|
95
|
+
workItem: MissionWorkItem;
|
|
96
|
+
leaseToken: string;
|
|
97
|
+
leaseExpiresAt: string;
|
|
98
|
+
}>;
|
|
99
|
+
renew: (id: string, workItemId: string, leaseToken: string, options?: RequestOptions) => Promise<{
|
|
100
|
+
workItem: MissionWorkItem;
|
|
101
|
+
leaseExpiresAt: string;
|
|
102
|
+
}>;
|
|
103
|
+
complete: (id: string, workItemId: string, input: {
|
|
104
|
+
leaseToken: string;
|
|
105
|
+
result?: Metadata;
|
|
106
|
+
artifact?: Pick<MissionArtifact, "kind" | "title"> & {
|
|
107
|
+
summary?: string;
|
|
108
|
+
uri?: string;
|
|
109
|
+
contentText?: string;
|
|
110
|
+
metadata?: Metadata;
|
|
111
|
+
};
|
|
112
|
+
event?: MissionEventInput;
|
|
113
|
+
}, options?: WriteOptions) => Promise<{
|
|
114
|
+
workItem: MissionWorkItem;
|
|
115
|
+
artifact?: MissionArtifact;
|
|
116
|
+
}>;
|
|
117
|
+
reserveEffect: (id: string, input: {
|
|
118
|
+
workItemId: string;
|
|
119
|
+
action: string;
|
|
120
|
+
resource: string;
|
|
121
|
+
leaseToken: string;
|
|
122
|
+
estimatedCostCents?: number;
|
|
123
|
+
parameters?: Metadata;
|
|
124
|
+
}, options?: WriteOptions) => Promise<{
|
|
125
|
+
id: string;
|
|
126
|
+
state: string;
|
|
127
|
+
action: string;
|
|
128
|
+
resource: string;
|
|
129
|
+
}>;
|
|
130
|
+
listEffects: (id: string, workItemId: string, options?: RequestOptions) => Promise<Array<{
|
|
131
|
+
id: string;
|
|
132
|
+
workItemId: string;
|
|
133
|
+
action: string;
|
|
134
|
+
resource: string;
|
|
135
|
+
state: string;
|
|
136
|
+
estimatedCostCents: number;
|
|
137
|
+
actualCostCents: number | null;
|
|
138
|
+
parameters?: Metadata | null;
|
|
139
|
+
receipt?: Metadata | null;
|
|
140
|
+
externalRef?: string | null;
|
|
141
|
+
createdAt: string;
|
|
142
|
+
updatedAt: string;
|
|
143
|
+
}>>;
|
|
144
|
+
dispatchEffect: (id: string, effectId: string, leaseToken: string, options?: RequestOptions) => Promise<{
|
|
145
|
+
effect: {
|
|
146
|
+
id: string;
|
|
147
|
+
state: string;
|
|
148
|
+
};
|
|
149
|
+
launchPermitted: true;
|
|
150
|
+
}>;
|
|
151
|
+
settleEffect: (id: string, effectId: string, input: {
|
|
152
|
+
state: "succeeded" | "failed" | "uncertain";
|
|
153
|
+
actualCostCents?: number;
|
|
154
|
+
receipt?: Metadata;
|
|
155
|
+
}, options?: RequestOptions) => Promise<{
|
|
156
|
+
id: string;
|
|
157
|
+
state: string;
|
|
158
|
+
}>;
|
|
159
|
+
events: {
|
|
160
|
+
list: (id: string, query?: Pagination, options?: RequestOptions) => Promise<Page<MissionEvent>>;
|
|
161
|
+
iterate: (id: string, query?: Pagination, options?: RequestOptions) => AsyncIterable<MissionEvent>;
|
|
162
|
+
post: (id: string, input: MissionEventInput, options?: WriteOptions) => Promise<MissionEvent>;
|
|
163
|
+
ack: (id: string, eventId: string, options?: RequestOptions) => Promise<MissionEvent>;
|
|
164
|
+
};
|
|
165
|
+
};
|
|
166
|
+
chats: {
|
|
167
|
+
list: (query?: Pagination, options?: RequestOptions) => Promise<Page<Chat>>;
|
|
168
|
+
get: (id: string, options?: RequestOptions & Pagination & {
|
|
169
|
+
before?: string;
|
|
170
|
+
}) => Promise<ChatDetail>;
|
|
171
|
+
/** Publish as the authenticated agent; this never creates a human command. */
|
|
172
|
+
updates: (id: string, text: string, options?: WriteOptions) => Promise<ChatEvent>;
|
|
173
|
+
};
|
|
174
|
+
chatCommands: {
|
|
175
|
+
list: (query?: Pagination, options?: RequestOptions) => Promise<Page<ChatCommand>>;
|
|
176
|
+
claim: (id: string, options?: RequestOptions) => Promise<{
|
|
177
|
+
run: ChatRun;
|
|
178
|
+
leaseToken: string;
|
|
179
|
+
}>;
|
|
180
|
+
renew: (id: string, leaseToken: string, options?: RequestOptions) => Promise<{
|
|
181
|
+
run: ChatRun;
|
|
182
|
+
}>;
|
|
183
|
+
complete: (id: string, input: {
|
|
184
|
+
leaseToken: string;
|
|
185
|
+
text: string;
|
|
186
|
+
}, options?: WriteOptions) => Promise<{
|
|
187
|
+
event: ChatEvent;
|
|
188
|
+
run: ChatRun;
|
|
189
|
+
}>;
|
|
190
|
+
fail: (id: string, input: {
|
|
191
|
+
leaseToken: string;
|
|
192
|
+
error: string;
|
|
193
|
+
}, options?: RequestOptions) => Promise<{
|
|
194
|
+
run: ChatRun;
|
|
195
|
+
}>;
|
|
196
|
+
};
|
|
197
|
+
runtime: {
|
|
198
|
+
activations: (options?: RequestOptions) => Promise<{ data: Array<{ id: string; state: string; ready: boolean }> }>;
|
|
199
|
+
reportActivation: (input: { id: string; state: "starting" | "ready" | "blocked" | "paused"; errorCode?: "READINESS_UNSUPPORTED" | "LISTENER_START_FAILED" | "AUTHORIZATION_REJECTED" }, options?: RequestOptions) => Promise<{ recorded: boolean }>;
|
|
200
|
+
start: (input: {
|
|
201
|
+
runId: string;
|
|
202
|
+
provider: string;
|
|
203
|
+
hostKind?: "local" | "hosted";
|
|
204
|
+
}, options?: RequestOptions) => Promise<{
|
|
205
|
+
runId: string;
|
|
206
|
+
state: string;
|
|
207
|
+
ready: boolean;
|
|
208
|
+
}>;
|
|
209
|
+
probe: (runId: string, options?: RequestOptions) => Promise<{
|
|
210
|
+
runId: string;
|
|
211
|
+
challenge: string;
|
|
212
|
+
completed: boolean;
|
|
213
|
+
protocol: string;
|
|
214
|
+
}>;
|
|
215
|
+
replyProbe: (input: {
|
|
216
|
+
runId: string;
|
|
217
|
+
reply: string;
|
|
218
|
+
}, options?: RequestOptions) => Promise<{
|
|
219
|
+
runId: string;
|
|
220
|
+
state: string;
|
|
221
|
+
ready: boolean;
|
|
222
|
+
}>;
|
|
223
|
+
heartbeat: (input?: {
|
|
224
|
+
label?: string;
|
|
225
|
+
runId?: string;
|
|
226
|
+
state?: "starting" | "online" | "offline" | "error";
|
|
227
|
+
errorCode?: string;
|
|
228
|
+
}, options?: RequestOptions) => Promise<RuntimeHeartbeat>;
|
|
229
|
+
};
|
|
230
|
+
tasks: {
|
|
231
|
+
create: (input: SendMessage, options?: WriteOptions) => Promise<Task>;
|
|
232
|
+
get: (id: string, options?: RequestOptions) => Promise<Task>;
|
|
233
|
+
list: (query?: ListTasks, options?: RequestOptions) => Promise<Page<Task>>;
|
|
234
|
+
iterate: (query?: ListTasks, options?: RequestOptions) => AsyncIterable<Task>;
|
|
235
|
+
postUpdate: (id: string, text: string, options?: WriteOptions) => Promise<Message>;
|
|
236
|
+
update: (id: string, input: {
|
|
237
|
+
state: Exclude<TaskState, "submitted" | "canceled">;
|
|
238
|
+
artifacts?: Task["artifacts"];
|
|
239
|
+
expectedVersion?: number;
|
|
240
|
+
}, options?: RequestOptions) => Promise<Task>;
|
|
241
|
+
cancel: (id: string, options?: RequestOptions) => Promise<Task>;
|
|
242
|
+
};
|
|
243
|
+
repositories: {
|
|
244
|
+
getTask: (taskId: string, options?: RequestOptions) => Promise<RepositoryCoordination>;
|
|
245
|
+
claim: (taskId: string, input: {
|
|
246
|
+
branch: string;
|
|
247
|
+
paths: string[];
|
|
248
|
+
expectedVersion?: number;
|
|
249
|
+
}, options?: RequestOptions) => Promise<RepoClaim>;
|
|
250
|
+
update: (taskId: string, input: {
|
|
251
|
+
state: Exclude<RepoClaimState, "planned">;
|
|
252
|
+
headCommit?: string;
|
|
253
|
+
pullRequestUrl?: string;
|
|
254
|
+
expectedVersion: number;
|
|
255
|
+
}, options?: RequestOptions) => Promise<RepoClaim>;
|
|
256
|
+
};
|
|
257
|
+
github: {
|
|
258
|
+
repositories: (options?: RequestOptions) => Promise<Array<{
|
|
259
|
+
repository: string;
|
|
260
|
+
repositoryId: string;
|
|
261
|
+
installationId: string;
|
|
262
|
+
}>>;
|
|
263
|
+
openPullRequest: (taskId: string, input: {
|
|
264
|
+
title: string;
|
|
265
|
+
body?: string;
|
|
266
|
+
}, options?: RequestOptions) => Promise<{
|
|
267
|
+
pullRequestUrl: string;
|
|
268
|
+
claim: RepoClaim;
|
|
269
|
+
}>;
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
export declare class ConversationHandle {
|
|
273
|
+
#private;
|
|
274
|
+
readonly id: string;
|
|
275
|
+
readonly conversation: Conversation;
|
|
276
|
+
constructor(totem: Totem, conversation: Conversation, recipient: string);
|
|
277
|
+
send(text: string, options?: WriteOptions): Promise<Message>;
|
|
278
|
+
messages(query?: Pagination, options?: RequestOptions): Promise<Page<Message>>;
|
|
279
|
+
}
|
|
280
|
+
export declare class ConnectedAgent {
|
|
281
|
+
#private;
|
|
282
|
+
readonly identity: Agent;
|
|
283
|
+
readonly address: string;
|
|
284
|
+
readonly client: Totem;
|
|
285
|
+
constructor(client: Totem, identity: Agent);
|
|
286
|
+
get lastError(): Error | undefined;
|
|
287
|
+
send(input: SendMessage, options?: WriteOptions): Promise<Message>;
|
|
288
|
+
send(to: string, text: string, options?: WriteOptions): Promise<Message>;
|
|
289
|
+
startConversation(input: {
|
|
290
|
+
with: string;
|
|
291
|
+
metadata?: Metadata;
|
|
292
|
+
}): Promise<ConversationHandle>;
|
|
293
|
+
on(event: "message", handler: MessageHandler, options?: ListenOptions): Subscription;
|
|
294
|
+
stop(): Promise<void>;
|
|
295
|
+
}
|
|
296
|
+
/** Compatibility name for callers who prefer `new TotemAgent({ address, apiKey })`. */
|
|
297
|
+
export declare class TotemAgent extends Totem {
|
|
298
|
+
}
|
|
299
|
+
export { createLocalInspector, InspectionError };
|
|
300
|
+
export type { LocalInspector, LocalInspectorOptions, InspectionOptions, InspectionReceipt, InspectionKind, } from "./inspector.js";
|
package/bin/sdk/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { TotemError } from "./errors.js";
|
|
2
|
+
import { createLocalInspector, InspectionError } from "./inspector.js";
|
|
2
3
|
import { idempotencyKey, sleep, Transport } from "./transport.js";
|
|
3
4
|
import { assertInputFields, assistantCheckOptions, signalOnlyOptions } from "./request-options.js";
|
|
4
5
|
export * from "./types.js";
|
|
@@ -112,6 +113,11 @@ export class Totem {
|
|
|
112
113
|
},
|
|
113
114
|
idempotencyKey: options.idempotencyKey ?? `sdk:reply:${id}`,
|
|
114
115
|
})).data,
|
|
116
|
+
replyContent: async (id, content, options = {}) => (await this.#transport.request("POST", `/messages/${encodeURIComponent(id)}/reply`, {
|
|
117
|
+
...options,
|
|
118
|
+
body: { content },
|
|
119
|
+
idempotencyKey: options.idempotencyKey ?? idempotencyKey(),
|
|
120
|
+
})).data,
|
|
115
121
|
ack: async (id, options) => (await this.#transport.request("POST", `/messages/${encodeURIComponent(id)}/ack`, {
|
|
116
122
|
...options,
|
|
117
123
|
body: {},
|
|
@@ -155,8 +161,20 @@ export class Totem {
|
|
|
155
161
|
artifacts: result.data.artifacts,
|
|
156
162
|
events: result.data.events,
|
|
157
163
|
budget: result.data.budget,
|
|
164
|
+
coordination: result.data.coordination,
|
|
158
165
|
};
|
|
159
166
|
},
|
|
167
|
+
coordination: async (id, version, options) => (await this.#transport.request("GET", `/missions/${encodeURIComponent(id)}/coordination`, {
|
|
168
|
+
...options,
|
|
169
|
+
query: { version },
|
|
170
|
+
})).data,
|
|
171
|
+
sessions: {
|
|
172
|
+
report: async (id, input, options) => (await this.#transport.request("POST", `/missions/${encodeURIComponent(id)}/sessions`, {
|
|
173
|
+
...options,
|
|
174
|
+
body: input,
|
|
175
|
+
retrySafe: true,
|
|
176
|
+
})).data,
|
|
177
|
+
},
|
|
160
178
|
workItems: async (id, options) => (await this.#transport.request("GET", `/missions/${encodeURIComponent(id)}/work-items`, options)).data.workItems,
|
|
161
179
|
/** Empty data can still have a nextCursor: readiness is checked after scanning candidates. */
|
|
162
180
|
workItemsPage: async (id, query = {}, options) => {
|
|
@@ -223,6 +241,11 @@ export class Totem {
|
|
|
223
241
|
fail: async (id, input, options) => (await this.#transport.request("POST", `/chat-commands/${encodeURIComponent(id)}/fail`, { ...options, body: input, retrySafe: true })).data,
|
|
224
242
|
};
|
|
225
243
|
runtime = {
|
|
244
|
+
activations: async (options) => (await this.#transport.request("GET", "/runtime/activations", options)).data,
|
|
245
|
+
reportActivation: async (input, options) => (await this.#transport.request("POST", "/runtime/activations", { ...options, body: input, retrySafe: true })).data,
|
|
246
|
+
start: async (input, options) => (await this.#transport.request("POST", "/runtime/start", { ...options, body: input, retrySafe: true })).data,
|
|
247
|
+
probe: async (runId, options) => (await this.#transport.request("GET", "/runtime/probe", { ...options, query: { runId } })).data,
|
|
248
|
+
replyProbe: async (input, options) => (await this.#transport.request("POST", "/runtime/probe", { ...options, body: input, retrySafe: true })).data,
|
|
226
249
|
heartbeat: async (input = {}, options) => (await this.#transport.request("POST", "/runtime/heartbeat", {
|
|
227
250
|
...options,
|
|
228
251
|
body: input,
|
|
@@ -317,6 +340,8 @@ export class ConnectedAgent {
|
|
|
317
340
|
if (!Number.isFinite(interval) || interval < 10)
|
|
318
341
|
throw new TotemError("pollIntervalMs must be at least 10.", { code: "INVALID_CONFIG" });
|
|
319
342
|
const controller = new AbortController();
|
|
343
|
+
const inspector = options.inspector ?? createLocalInspector();
|
|
344
|
+
const blocked = new Set();
|
|
320
345
|
const report = (cause) => {
|
|
321
346
|
this.#lastError = cause instanceof Error ? cause : new Error(String(cause));
|
|
322
347
|
try {
|
|
@@ -332,22 +357,42 @@ export class ConnectedAgent {
|
|
|
332
357
|
for await (const message of this.client.messages.iterate({ direction: "inbound", unread: true, limit: 100 }, { signal: controller.signal })) {
|
|
333
358
|
if (controller.signal.aborted)
|
|
334
359
|
break;
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
text: message.content.type === "text"
|
|
338
|
-
? message.content.text
|
|
339
|
-
: JSON.stringify(message.content.data),
|
|
340
|
-
reply: (text, writeOptions = {}) => this.client.messages.reply(message.id, text, {
|
|
341
|
-
...writeOptions,
|
|
342
|
-
idempotencyKey: writeOptions.idempotencyKey ?? `reply:${message.id}:0`,
|
|
343
|
-
}),
|
|
344
|
-
ack: () => this.client.messages.ack(message.id),
|
|
345
|
-
};
|
|
360
|
+
if (blocked.has(message.id))
|
|
361
|
+
continue;
|
|
346
362
|
try {
|
|
363
|
+
if (message.content.type === "encrypted")
|
|
364
|
+
throw new TotemError("Use E2eeTotem and pinned endpoint keys to process encrypted messages.", { code: "E2EE_CLIENT_REQUIRED" });
|
|
365
|
+
await inspector.assertAllowed("input", { content: message.content, metadata: message.metadata ?? {} }, { signal: controller.signal });
|
|
366
|
+
const files = message.attachments ?? [];
|
|
367
|
+
const ids = new Set(files.map((file) => file.id));
|
|
368
|
+
if (message.content.type === "text" &&
|
|
369
|
+
message.content.attachments?.some((id) => !ids.has(id)))
|
|
370
|
+
throw new InspectionError("INSPECTION_UNSUPPORTED_FILE");
|
|
371
|
+
for (const file of files) {
|
|
372
|
+
const downloaded = await this.client.attachments.download(file.id, {
|
|
373
|
+
signal: controller.signal,
|
|
374
|
+
});
|
|
375
|
+
await inspector.inspectFile(file.name, file.mediaType, downloaded.bytes, {
|
|
376
|
+
signal: controller.signal,
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
const incoming = {
|
|
380
|
+
...message,
|
|
381
|
+
text: message.content.type === "text"
|
|
382
|
+
? message.content.text
|
|
383
|
+
: JSON.stringify(message.content.data),
|
|
384
|
+
reply: (text, writeOptions = {}) => inspector.runAction({ id: message.id, text }, (input) => this.client.messages.reply(input.id, input.text, {
|
|
385
|
+
...writeOptions,
|
|
386
|
+
idempotencyKey: writeOptions.idempotencyKey ?? `reply:${message.id}:0`,
|
|
387
|
+
}), { signal: controller.signal }),
|
|
388
|
+
ack: () => this.client.messages.ack(message.id),
|
|
389
|
+
};
|
|
347
390
|
await handler(incoming);
|
|
348
391
|
await incoming.ack();
|
|
349
392
|
}
|
|
350
393
|
catch (error) {
|
|
394
|
+
if (error instanceof InspectionError && error.code !== "INSPECTION_UNAVAILABLE")
|
|
395
|
+
blocked.add(message.id);
|
|
351
396
|
report(error);
|
|
352
397
|
}
|
|
353
398
|
}
|
|
@@ -385,3 +430,4 @@ export class ConnectedAgent {
|
|
|
385
430
|
/** Compatibility name for callers who prefer `new TotemAgent({ address, apiKey })`. */
|
|
386
431
|
export class TotemAgent extends Totem {
|
|
387
432
|
}
|
|
433
|
+
export { createLocalInspector, InspectionError };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/** Customer-side screening. A passing inspection is never an authorization grant. */
|
|
2
|
+
export type InspectionKind = "input" | "tool-result" | "output" | "action" | "file";
|
|
3
|
+
export type InspectionCode = "INSPECTION_BLOCKED" | "INSPECTION_UNAVAILABLE" | "INSPECTION_LIMIT" | "INSPECTION_UNSUPPORTED_FILE";
|
|
4
|
+
export declare class InspectionError extends Error {
|
|
5
|
+
readonly code: InspectionCode;
|
|
6
|
+
readonly status = 422;
|
|
7
|
+
constructor(code: InspectionCode);
|
|
8
|
+
}
|
|
9
|
+
export interface InspectionOptions {
|
|
10
|
+
/** Trusted original user intent; never replace this with a peer's instructions. */
|
|
11
|
+
task?: string;
|
|
12
|
+
signal?: AbortSignal;
|
|
13
|
+
}
|
|
14
|
+
export interface InspectionReceipt {
|
|
15
|
+
version: 1;
|
|
16
|
+
policy: "totem-local-v1";
|
|
17
|
+
kind: InspectionKind;
|
|
18
|
+
mode: "rules" | "rules+classifier";
|
|
19
|
+
/** SHA-256 of the entire kind, task, and value actually screened. No plaintext. */
|
|
20
|
+
digest: string;
|
|
21
|
+
}
|
|
22
|
+
export interface LocalInspectorOptions {
|
|
23
|
+
maxBytes?: number;
|
|
24
|
+
classifier?: {
|
|
25
|
+
/** Exact loopback OpenAI-compatible chat endpoint. Redirects are prohibited. */
|
|
26
|
+
endpoint: string;
|
|
27
|
+
model: string;
|
|
28
|
+
timeoutMs?: number;
|
|
29
|
+
};
|
|
30
|
+
/** Customer-controlled transport, primarily for isolated tests. */
|
|
31
|
+
fetch?: typeof globalThis.fetch;
|
|
32
|
+
}
|
|
33
|
+
export interface LocalInspector {
|
|
34
|
+
readonly mode: InspectionReceipt["mode"];
|
|
35
|
+
assertAllowed(kind: InspectionKind, value: unknown, options?: InspectionOptions): Promise<InspectionReceipt>;
|
|
36
|
+
inspectFile(name: string, mediaType: string, bytes: Uint8Array, options?: InspectionOptions): Promise<InspectionReceipt>;
|
|
37
|
+
/** Execute only the immutable argument snapshot that passed inspection. */
|
|
38
|
+
runAction<T, R>(value: T, operation: (inspected: T) => Promise<R>, options?: InspectionOptions): Promise<R>;
|
|
39
|
+
}
|
|
40
|
+
export declare function createLocalInspector(options?: LocalInspectorOptions): LocalInspector;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { RequestOptions } from "./types.js";
|
|
2
|
+
export declare function requireActionChecksKey(apiKey: string): string;
|
|
3
|
+
export declare function assistantCheckOptions(options: RequestOptions & {
|
|
4
|
+
actionChecksApiKey: string;
|
|
5
|
+
}): {
|
|
6
|
+
headers: {
|
|
7
|
+
"X-Totem-Action-Key": string;
|
|
8
|
+
};
|
|
9
|
+
signal?: AbortSignal;
|
|
10
|
+
};
|
|
11
|
+
export declare function assertInputFields(input: object, allowed: string[]): void;
|
|
12
|
+
/** These APIs derive caller identity from credentials, never from caller options. */
|
|
13
|
+
export declare function signalOnlyOptions(options?: RequestOptions): RequestOptions;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { RequestOptions, TotemOptions } from "./types.js";
|
|
2
|
+
export declare function idempotencyKey(): string;
|
|
3
|
+
export declare function sleep(ms: number, signal?: AbortSignal): Promise<void>;
|
|
4
|
+
export declare class Transport {
|
|
5
|
+
#private;
|
|
6
|
+
constructor(options: TotemOptions, api?: {
|
|
7
|
+
basePath: "/api/action-checks";
|
|
8
|
+
responseEnvelope: "object";
|
|
9
|
+
} | undefined);
|
|
10
|
+
request<T>(method: "GET" | "POST" | "PATCH", path: string, options?: RequestOptions & {
|
|
11
|
+
body?: unknown;
|
|
12
|
+
query?: object;
|
|
13
|
+
idempotencyKey?: string;
|
|
14
|
+
retrySafe?: boolean;
|
|
15
|
+
binaryBody?: Uint8Array;
|
|
16
|
+
headers?: Record<string, string>;
|
|
17
|
+
}): Promise<T>;
|
|
18
|
+
download(path: string, options?: RequestOptions): Promise<{
|
|
19
|
+
bytes: Uint8Array;
|
|
20
|
+
mediaType: string;
|
|
21
|
+
name: string;
|
|
22
|
+
sha256: string | null;
|
|
23
|
+
}>;
|
|
24
|
+
}
|