openclaw-openagent 1.0.12 → 1.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +2 -0
- package/dist/src/config/config-schema.d.ts +9 -0
- package/dist/src/config/config-schema.js +6 -0
- package/dist/src/messaging/executor.d.ts +2 -3
- package/dist/src/messaging/executor.js +4 -31
- package/dist/src/plugin-ui/assets/openagent-override.js +124 -18
- package/dist/src/proxy/auth-proxy.js +5 -0
- package/dist/src/runtime/update-checker.d.ts +18 -0
- package/dist/src/runtime/update-checker.js +216 -0
- package/index.ts +3 -0
- package/package.json +1 -3
- package/src/app/channel-tools.ts +0 -249
- package/src/app/discovery-tools.ts +0 -273
- package/src/app/download-file-tool.ts +0 -133
- package/src/app/hooks.ts +0 -144
- package/src/app/index.ts +0 -84
- package/src/app/messaging-tools.ts +0 -79
- package/src/app/ops-tools.ts +0 -155
- package/src/app/remote-agent-tool.ts +0 -762
- package/src/app/types.ts +0 -68
- package/src/app/upload-file-tool.ts +0 -411
- package/src/app/verbose-preflight.ts +0 -190
- package/src/auth/config.ts +0 -266
- package/src/auth/credential-manager.ts +0 -146
- package/src/auth/index.ts +0 -24
- package/src/auth/verify.ts +0 -99
- package/src/channel.ts +0 -566
- package/src/compat.ts +0 -94
- package/src/config/config-schema.ts +0 -39
- package/src/messaging/aggregator.ts +0 -120
- package/src/messaging/collector.ts +0 -89
- package/src/messaging/executor.ts +0 -72
- package/src/messaging/inbound.ts +0 -150
- package/src/messaging/index.ts +0 -11
- package/src/messaging/mention-protocol.ts +0 -94
- package/src/messaging/process-c2c-request.ts +0 -564
- package/src/messaging/process-message.ts +0 -373
- package/src/messaging/scheduler.ts +0 -55
- package/src/messaging/types.ts +0 -38
- package/src/plugin-ui/adapters/oc-2026-04.js +0 -103
- package/src/plugin-ui/adapters/oc-2026-05.js +0 -125
- package/src/plugin-ui/adapters/oc-2026-06.js +0 -125
- package/src/plugin-ui/adapters/oc-unknown.js +0 -48
- package/src/plugin-ui/assets/agentbook-icon.svg +0 -5
- package/src/plugin-ui/assets/bg.png +0 -0
- package/src/plugin-ui/assets/icon.png +0 -0
- package/src/plugin-ui/assets/magic.svg +0 -5
- package/src/plugin-ui/assets/openagent-override.js +0 -11156
- package/src/plugin-ui/build.cjs +0 -463
- package/src/plugin-ui/index.ts +0 -18
- package/src/plugin-ui/modules/agent-book/panel/agent-book.js +0 -570
- package/src/plugin-ui/modules/agent-book/panel/agent-card.js +0 -170
- package/src/plugin-ui/modules/agent-book/panel/agent-data.js +0 -643
- package/src/plugin-ui/modules/agent-book/panel/inject-ui.js +0 -665
- package/src/plugin-ui/modules/agent-book/panel/mention-state.js +0 -206
- package/src/plugin-ui/modules/agent-book/panel/styles.js +0 -833
- package/src/plugin-ui/modules/agent-book/panel/theme-adapter.js +0 -86
- package/src/plugin-ui/modules/agent-book/remote-agent-tool/components-core.js +0 -295
- package/src/plugin-ui/modules/agent-book/remote-agent-tool/thought-chain-card.js +0 -211
- package/src/plugin-ui/modules/agent-book/scanner.js +0 -131
- package/src/plugin-ui/modules/agent-book/travelcard/travel-cards.js +0 -500
- package/src/plugin-ui/modules/agent-book/travelcard/travel-engine.js +0 -652
- package/src/plugin-ui/modules/agent-book/travelcard/travel-styles.js +0 -256
- package/src/plugin-ui/modules/loader/bootstrap.js +0 -38
- package/src/plugin-ui/modules/loader/shared-state.js +0 -838
- package/src/plugin-ui/modules/loader/ws-intercept.js +0 -199
- package/src/plugin-ui/modules/remote-agent/chunk-parser.js +0 -161
- package/src/plugin-ui/modules/remote-agent/execution-card.js +0 -530
- package/src/plugin-ui/modules/remote-agent/markdown-renderer.js +0 -256
- package/src/plugin-ui/modules/remote-agent/media-links.js +0 -151
- package/src/plugin-ui/modules/remote-agent/native-style-adapter.js +0 -134
- package/src/plugin-ui/modules/remote-agent/output-card.js +0 -342
- package/src/plugin-ui/modules/remote-agent/progress-store.js +0 -363
- package/src/plugin-ui/modules/remote-agent/render-hooks.js +0 -1700
- package/src/plugin-ui/modules/remote-agent/styles.js +0 -896
- package/src/plugin-ui/modules/remote-agent/tool-card-model.js +0 -130
- package/src/plugin-ui/postinstall-deploy.cjs +0 -52
- package/src/plugin-ui/ui-extension-loader/backup.ts +0 -92
- package/src/plugin-ui/ui-extension-loader/index.ts +0 -276
- package/src/plugin-ui/ui-extension-loader/locator.ts +0 -152
- package/src/plugin-ui/ui-extension-loader/manifest.ts +0 -57
- package/src/plugin-ui/ui-extension-loader/registry-regex.ts +0 -944
- package/src/plugin-ui/ui-extension-loader/removed-extensions.ts +0 -70
- package/src/plugin-ui/ui-extension-loader/types.ts +0 -72
- package/src/proxy/auth-proxy.ts +0 -409
- package/src/runtime/account.ts +0 -549
- package/src/runtime/index.ts +0 -7
- package/src/runtime/plugin-runtime.ts +0 -94
- package/src/runtime/registry.ts +0 -71
- package/src/sdk/CLASS_MAP.md +0 -143
- package/src/sdk/index.d.ts +0 -126
- package/src/sdk/index.js +0 -23990
- package/src/sdk/modules/cloud-search-module.js +0 -1117
- package/src/sdk/modules/follow-module.js +0 -1069
- package/src/sdk/modules/group-module.js +0 -7397
- package/src/sdk/modules/relationship-module.js +0 -2269
- package/src/sdk/modules/signaling-module.js +0 -1468
- package/src/sdk/modules/tim-upload-plugin.js +0 -730
- package/src/sdk/node-env/http-request.js +0 -90
- package/src/sdk/node-env/index.js +0 -57
- package/src/sdk/node-env/storage.js +0 -114
- package/src/sdk/package.json +0 -10
- package/src/sdk/tsconfig.json +0 -16
- package/src/state/pending-invocation-store.ts +0 -43
- package/src/state/store.ts +0 -756
- package/src/tim/c2c.ts +0 -451
- package/src/tim/channels.ts +0 -364
- package/src/tim/client.ts +0 -330
- package/src/tim/index.ts +0 -18
- package/src/tim/messages.ts +0 -166
- package/src/tim/sdk-logger-init.ts +0 -50
- package/src/tools.ts +0 -10
- package/src/transport/factory.ts +0 -95
- package/src/transport/oasn/index.ts +0 -17
- package/src/transport/oasn/oasn-agent-card.ts +0 -111
- package/src/transport/oasn/oasn-discovery.ts +0 -108
- package/src/transport/oasn/oasn-files.ts +0 -210
- package/src/transport/oasn/oasn-http.ts +0 -529
- package/src/transport/oasn/oasn-invocation.ts +0 -604
- package/src/transport/oasn/oasn-normalize.ts +0 -133
- package/src/transport/oasn/oasn-register.ts +0 -33
- package/src/transport/oasn/oasn-transport.ts +0 -318
- package/src/transport/oasn/oasn-types.ts +0 -404
- package/src/transport/tim/index.ts +0 -8
- package/src/transport/tim/tim-transport.ts +0 -515
- package/src/transport/types.ts +0 -538
- package/src/types/openclaw-plugin-sdk-media-store.d.ts +0 -9
- package/src/types/openclaw.d.ts +0 -97
- package/src/types/tencentcloud-chat.d.ts +0 -15
- package/src/util/http.ts +0 -113
- package/src/util/logger.ts +0 -131
- package/src/util/url-resolver.ts +0 -77
package/src/state/store.ts
DELETED
|
@@ -1,756 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* State Store — SQLite persistence via sql.js (pure WASM)
|
|
3
|
-
*
|
|
4
|
-
* Persists channel state and message dedup records.
|
|
5
|
-
* sql.js operates in-memory; explicit persist() writes to disk.
|
|
6
|
-
* No native compilation needed (unlike better-sqlite3).
|
|
7
|
-
*
|
|
8
|
-
* v3: All tables use (account_id, channel_id) composite key.
|
|
9
|
-
* See §0.4 of refactoring-plan.md for field semantics.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import fs from 'node:fs';
|
|
13
|
-
import path from 'node:path';
|
|
14
|
-
import os from 'node:os';
|
|
15
|
-
|
|
16
|
-
import { logger } from '../util/logger.js';
|
|
17
|
-
|
|
18
|
-
const DB_PATH = path.join(os.homedir(), '.openclaw', 'openagent.db');
|
|
19
|
-
|
|
20
|
-
// ── Types ──
|
|
21
|
-
|
|
22
|
-
export interface ChannelState {
|
|
23
|
-
account_id: string;
|
|
24
|
-
channel_id: string;
|
|
25
|
-
channel_name: string;
|
|
26
|
-
joined: number;
|
|
27
|
-
last_visit: number;
|
|
28
|
-
last_reply: number;
|
|
29
|
-
last_msg_seq: number;
|
|
30
|
-
created_at: number;
|
|
31
|
-
updated_at: number;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export interface ChannelStateDisplay {
|
|
35
|
-
id: string;
|
|
36
|
-
name: string;
|
|
37
|
-
lastVisit: string;
|
|
38
|
-
lastReply: string;
|
|
39
|
-
lastMsgSeq: number;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* §11.1 OASN invocation 幂等持久化记录。
|
|
44
|
-
*
|
|
45
|
-
* 同一个 tool_call_id 多次重试时必须复用首次生成的 idempotency_key,
|
|
46
|
-
* 否则服务端会创建多个 Invocation(叠加 §11.2 没有 cancel API 的限制,会泄漏调用配额)。
|
|
47
|
-
*
|
|
48
|
-
* 流程:
|
|
49
|
-
* 1. sendTask 前查询 tool_call_id 是否已有未完成记录 → 复用 idempotency_key
|
|
50
|
-
* 2. POST /api/invocations 成功后 → upsert 写入 invocation_id + status
|
|
51
|
-
* 3. 终态 → markTerminal(status);abort → markAbandoned()
|
|
52
|
-
* 4. 进程启动时扫描 status NOT IN (succeeded,failed,timed_out,cancelled,abandoned)
|
|
53
|
-
* → 调 GET /api/invocations/{id} 续轮询
|
|
54
|
-
*/
|
|
55
|
-
export interface PendingInvocationRecord {
|
|
56
|
-
/** LLM 工具调用唯一 id(上层传入) */
|
|
57
|
-
tool_call_id: string;
|
|
58
|
-
/** 首次生成的 UUID,重试必须复用 */
|
|
59
|
-
idempotency_key: string;
|
|
60
|
-
/** POST 返回后回填,POST 之前为 null */
|
|
61
|
-
invocation_id: string | null;
|
|
62
|
-
session_id: string | null;
|
|
63
|
-
target_agent_id: string | null;
|
|
64
|
-
/** accepted / queued / running / succeeded / failed / timed_out / cancelled / abandoned */
|
|
65
|
-
status: string;
|
|
66
|
-
created_at: number;
|
|
67
|
-
updated_at: number;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* 远程 ServiceAgent 会话续传记录。
|
|
72
|
-
*
|
|
73
|
-
* 一个 OpenClaw 本地会话里,同一个远程 Agent 的第一次 invocation 会返回
|
|
74
|
-
* session_id;后续 mode='session' 的调用应带回这个 session_id,才能保留
|
|
75
|
-
* 远端上下文。account_id + openclaw_session_key + target_agent_id 共同隔离,
|
|
76
|
-
* 避免不同账号、不同本地聊天、不同远程 Agent 之间串 session。
|
|
77
|
-
*/
|
|
78
|
-
export interface RemoteAgentSessionRecord {
|
|
79
|
-
openclaw_session_key: string;
|
|
80
|
-
target_agent_id: string;
|
|
81
|
-
session_id: string;
|
|
82
|
-
created_at: number;
|
|
83
|
-
updated_at: number;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
interface SqlJsDatabase {
|
|
87
|
-
run(sql: string, params?: unknown[]): void;
|
|
88
|
-
prepare(sql: string): SqlJsStatement;
|
|
89
|
-
export(): Uint8Array;
|
|
90
|
-
close(): void;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
interface SqlJsStatement {
|
|
94
|
-
bind(params?: unknown[]): boolean;
|
|
95
|
-
step(): boolean;
|
|
96
|
-
getAsObject(): Record<string, unknown>;
|
|
97
|
-
free(): void;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// ── StateStore ──
|
|
101
|
-
|
|
102
|
-
export class StateStore {
|
|
103
|
-
private db: SqlJsDatabase | null = null;
|
|
104
|
-
private accountId: string = 'default';
|
|
105
|
-
private _seenCount = 0;
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Initialize the SQLite database.
|
|
109
|
-
* Must be called before any other methods.
|
|
110
|
-
*
|
|
111
|
-
* @param currentAccountId - The account ID of the currently starting account.
|
|
112
|
-
* Used for schema migration: old data without account_id will be assigned this ID.
|
|
113
|
-
*/
|
|
114
|
-
async init(currentAccountId: string): Promise<StateStore> {
|
|
115
|
-
this.accountId = currentAccountId || 'default';
|
|
116
|
-
|
|
117
|
-
const initSqlJs = ((await import('sql.js')) as { default: (config?: unknown) => Promise<{ Database: new (data?: ArrayLike<number>) => SqlJsDatabase }> }).default;
|
|
118
|
-
const SQL = await initSqlJs();
|
|
119
|
-
|
|
120
|
-
// Load existing DB file if present
|
|
121
|
-
if (fs.existsSync(DB_PATH)) {
|
|
122
|
-
const buffer = fs.readFileSync(DB_PATH);
|
|
123
|
-
this.db = new SQL.Database(buffer);
|
|
124
|
-
} else {
|
|
125
|
-
this.db = new SQL.Database();
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
this._migrate();
|
|
129
|
-
return this;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
private _migrate(): void {
|
|
133
|
-
if (!this.db) return;
|
|
134
|
-
|
|
135
|
-
// Detect old schema (no account_id column)
|
|
136
|
-
const hasAccountId = this._tableHasColumn('channel_state', 'account_id');
|
|
137
|
-
|
|
138
|
-
if (hasAccountId) {
|
|
139
|
-
// Already v3 schema — ensure all tables exist
|
|
140
|
-
this._createV3Tables();
|
|
141
|
-
} else {
|
|
142
|
-
// Old v2 schema detected — migrate
|
|
143
|
-
this._migrateFromV2();
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
private _tableHasColumn(table: string, column: string): boolean {
|
|
148
|
-
if (!this.db) return false;
|
|
149
|
-
try {
|
|
150
|
-
const stmt = this.db.prepare(`PRAGMA table_info(${table})`);
|
|
151
|
-
while (stmt.step()) {
|
|
152
|
-
const row = stmt.getAsObject();
|
|
153
|
-
if (row['name'] === column) {
|
|
154
|
-
stmt.free();
|
|
155
|
-
return true;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
stmt.free();
|
|
159
|
-
} catch {
|
|
160
|
-
// Table doesn't exist
|
|
161
|
-
}
|
|
162
|
-
return false;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
private _tableExists(table: string): boolean {
|
|
166
|
-
if (!this.db) return false;
|
|
167
|
-
try {
|
|
168
|
-
const stmt = this.db.prepare(
|
|
169
|
-
`SELECT name FROM sqlite_master WHERE type='table' AND name=?`
|
|
170
|
-
);
|
|
171
|
-
stmt.bind([table]);
|
|
172
|
-
const exists = stmt.step();
|
|
173
|
-
stmt.free();
|
|
174
|
-
return exists;
|
|
175
|
-
} catch {
|
|
176
|
-
return false;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
private _migrateFromV2(): void {
|
|
181
|
-
if (!this.db) return;
|
|
182
|
-
logger.info(`[state] Migrating v2 schema to v3 (account_id='${this.accountId}')`);
|
|
183
|
-
|
|
184
|
-
// 1. Rename old tables
|
|
185
|
-
if (this._tableExists('channel_state')) {
|
|
186
|
-
this.db.run('ALTER TABLE channel_state RENAME TO _v2_channel_state');
|
|
187
|
-
}
|
|
188
|
-
if (this._tableExists('seen_messages')) {
|
|
189
|
-
this.db.run('ALTER TABLE seen_messages RENAME TO _v2_seen_messages');
|
|
190
|
-
}
|
|
191
|
-
if (this._tableExists('channel_skills')) {
|
|
192
|
-
this.db.run('ALTER TABLE channel_skills RENAME TO _v2_channel_skills');
|
|
193
|
-
}
|
|
194
|
-
if (this._tableExists('channel_purpose')) {
|
|
195
|
-
this.db.run('ALTER TABLE channel_purpose RENAME TO _v2_channel_purpose');
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// 2. Create v3 tables
|
|
199
|
-
this._createV3Tables();
|
|
200
|
-
|
|
201
|
-
// 3. Copy old data with current account_id
|
|
202
|
-
if (this._tableExists('_v2_channel_state')) {
|
|
203
|
-
this.db.run(`
|
|
204
|
-
INSERT INTO channel_state (account_id, channel_id, channel_name, joined, last_visit, last_reply, last_msg_seq, created_at, updated_at)
|
|
205
|
-
SELECT ?, channel_id, channel_name, joined, last_visit, last_reply, last_msg_seq, created_at, updated_at
|
|
206
|
-
FROM _v2_channel_state
|
|
207
|
-
`, [this.accountId]);
|
|
208
|
-
}
|
|
209
|
-
if (this._tableExists('_v2_seen_messages')) {
|
|
210
|
-
this.db.run(`
|
|
211
|
-
INSERT INTO seen_messages (account_id, message_id, channel_id, seen_at)
|
|
212
|
-
SELECT ?, message_id, channel_id, seen_at
|
|
213
|
-
FROM _v2_seen_messages
|
|
214
|
-
`, [this.accountId]);
|
|
215
|
-
}
|
|
216
|
-
if (this._tableExists('_v2_channel_skills')) {
|
|
217
|
-
this.db.run(`
|
|
218
|
-
INSERT INTO channel_skills (account_id, channel_id, skill_content, updated_at)
|
|
219
|
-
SELECT ?, channel_id, skill_content, updated_at
|
|
220
|
-
FROM _v2_channel_skills
|
|
221
|
-
`, [this.accountId]);
|
|
222
|
-
}
|
|
223
|
-
if (this._tableExists('_v2_channel_purpose')) {
|
|
224
|
-
this.db.run(`
|
|
225
|
-
INSERT INTO channel_purpose (account_id, channel_id, purpose, updated_at)
|
|
226
|
-
SELECT ?, channel_id, purpose, updated_at
|
|
227
|
-
FROM _v2_channel_purpose
|
|
228
|
-
`, [this.accountId]);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
// 4. Drop old tables
|
|
232
|
-
this.db.run('DROP TABLE IF EXISTS _v2_channel_state');
|
|
233
|
-
this.db.run('DROP TABLE IF EXISTS _v2_seen_messages');
|
|
234
|
-
this.db.run('DROP TABLE IF EXISTS _v2_channel_skills');
|
|
235
|
-
this.db.run('DROP TABLE IF EXISTS _v2_channel_purpose');
|
|
236
|
-
|
|
237
|
-
this.persist();
|
|
238
|
-
logger.info('[state] Migration complete');
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
private _createV3Tables(): void {
|
|
242
|
-
if (!this.db) return;
|
|
243
|
-
|
|
244
|
-
this.db.run(`
|
|
245
|
-
CREATE TABLE IF NOT EXISTS channel_state (
|
|
246
|
-
account_id TEXT NOT NULL,
|
|
247
|
-
channel_id TEXT NOT NULL,
|
|
248
|
-
channel_name TEXT NOT NULL DEFAULT '',
|
|
249
|
-
joined INTEGER NOT NULL DEFAULT 0,
|
|
250
|
-
last_visit INTEGER NOT NULL DEFAULT 0,
|
|
251
|
-
last_reply INTEGER NOT NULL DEFAULT 0,
|
|
252
|
-
last_msg_seq INTEGER NOT NULL DEFAULT 0,
|
|
253
|
-
created_at INTEGER NOT NULL DEFAULT 0,
|
|
254
|
-
updated_at INTEGER NOT NULL DEFAULT 0,
|
|
255
|
-
PRIMARY KEY (account_id, channel_id)
|
|
256
|
-
)
|
|
257
|
-
`);
|
|
258
|
-
this.db.run(`
|
|
259
|
-
CREATE TABLE IF NOT EXISTS seen_messages (
|
|
260
|
-
account_id TEXT NOT NULL,
|
|
261
|
-
message_id TEXT NOT NULL,
|
|
262
|
-
channel_id TEXT NOT NULL,
|
|
263
|
-
seen_at INTEGER NOT NULL DEFAULT 0,
|
|
264
|
-
PRIMARY KEY (account_id, message_id)
|
|
265
|
-
)
|
|
266
|
-
`);
|
|
267
|
-
this.db.run(`
|
|
268
|
-
CREATE TABLE IF NOT EXISTS channel_skills (
|
|
269
|
-
account_id TEXT NOT NULL,
|
|
270
|
-
channel_id TEXT NOT NULL,
|
|
271
|
-
skill_content TEXT NOT NULL DEFAULT '',
|
|
272
|
-
updated_at INTEGER NOT NULL DEFAULT 0,
|
|
273
|
-
PRIMARY KEY (account_id, channel_id)
|
|
274
|
-
)
|
|
275
|
-
`);
|
|
276
|
-
this.db.run(`
|
|
277
|
-
CREATE TABLE IF NOT EXISTS channel_purpose (
|
|
278
|
-
account_id TEXT NOT NULL,
|
|
279
|
-
channel_id TEXT NOT NULL,
|
|
280
|
-
purpose TEXT NOT NULL DEFAULT '',
|
|
281
|
-
updated_at INTEGER NOT NULL DEFAULT 0,
|
|
282
|
-
PRIMARY KEY (account_id, channel_id)
|
|
283
|
-
)
|
|
284
|
-
`);
|
|
285
|
-
// §11.1 OASN invocation 幂等持久化。
|
|
286
|
-
// tool_call_id 作为主键 —— 它对应 LLM 一次工具调用,全局唯一。
|
|
287
|
-
// status 索引便于启动时筛非终态记录续轮询。
|
|
288
|
-
this.db.run(`
|
|
289
|
-
CREATE TABLE IF NOT EXISTS pending_invocations (
|
|
290
|
-
account_id TEXT NOT NULL,
|
|
291
|
-
tool_call_id TEXT NOT NULL,
|
|
292
|
-
idempotency_key TEXT NOT NULL,
|
|
293
|
-
invocation_id TEXT,
|
|
294
|
-
session_id TEXT,
|
|
295
|
-
target_agent_id TEXT,
|
|
296
|
-
status TEXT NOT NULL DEFAULT 'pending',
|
|
297
|
-
created_at INTEGER NOT NULL DEFAULT 0,
|
|
298
|
-
updated_at INTEGER NOT NULL DEFAULT 0,
|
|
299
|
-
PRIMARY KEY (account_id, tool_call_id)
|
|
300
|
-
)
|
|
301
|
-
`);
|
|
302
|
-
this.db.run(`
|
|
303
|
-
CREATE INDEX IF NOT EXISTS idx_pending_invocations_status
|
|
304
|
-
ON pending_invocations (account_id, status)
|
|
305
|
-
`);
|
|
306
|
-
this.db.run(`
|
|
307
|
-
CREATE INDEX IF NOT EXISTS idx_pending_invocations_invocation
|
|
308
|
-
ON pending_invocations (account_id, invocation_id)
|
|
309
|
-
`);
|
|
310
|
-
this.db.run(`
|
|
311
|
-
CREATE TABLE IF NOT EXISTS remote_agent_sessions (
|
|
312
|
-
account_id TEXT NOT NULL,
|
|
313
|
-
openclaw_session_key TEXT NOT NULL,
|
|
314
|
-
target_agent_id TEXT NOT NULL,
|
|
315
|
-
session_id TEXT NOT NULL,
|
|
316
|
-
created_at INTEGER NOT NULL DEFAULT 0,
|
|
317
|
-
updated_at INTEGER NOT NULL DEFAULT 0,
|
|
318
|
-
PRIMARY KEY (account_id, openclaw_session_key, target_agent_id)
|
|
319
|
-
)
|
|
320
|
-
`);
|
|
321
|
-
this.db.run(`
|
|
322
|
-
CREATE INDEX IF NOT EXISTS idx_remote_agent_sessions_updated
|
|
323
|
-
ON remote_agent_sessions (account_id, updated_at)
|
|
324
|
-
`);
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
// ── Write to disk ──
|
|
328
|
-
|
|
329
|
-
persist(): void {
|
|
330
|
-
try {
|
|
331
|
-
if (!this.db) return;
|
|
332
|
-
const data = this.db.export();
|
|
333
|
-
fs.mkdirSync(path.dirname(DB_PATH), { recursive: true });
|
|
334
|
-
fs.writeFileSync(DB_PATH, Buffer.from(data));
|
|
335
|
-
} catch (err) {
|
|
336
|
-
logger.error(`[state] persist error: ${(err as Error).message}`);
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
// ── Channel State ──
|
|
341
|
-
|
|
342
|
-
/**
|
|
343
|
-
* Get all joined channels for the current account.
|
|
344
|
-
*/
|
|
345
|
-
getJoinedChannels(): ChannelState[] {
|
|
346
|
-
if (!this.db) return [];
|
|
347
|
-
const stmt = this.db.prepare(
|
|
348
|
-
'SELECT * FROM channel_state WHERE account_id = ? AND joined = 1'
|
|
349
|
-
);
|
|
350
|
-
stmt.bind([this.accountId]);
|
|
351
|
-
const results: ChannelState[] = [];
|
|
352
|
-
while (stmt.step()) results.push(stmt.getAsObject() as unknown as ChannelState);
|
|
353
|
-
stmt.free();
|
|
354
|
-
return results;
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
getJoinedChannelIds(): string[] {
|
|
358
|
-
return this.getJoinedChannels().map(c => c.channel_id);
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
getChannel(channelId: string): ChannelState | null {
|
|
362
|
-
if (!this.db) return null;
|
|
363
|
-
const stmt = this.db.prepare(
|
|
364
|
-
'SELECT * FROM channel_state WHERE account_id = ? AND channel_id = ?'
|
|
365
|
-
);
|
|
366
|
-
stmt.bind([this.accountId, channelId]);
|
|
367
|
-
const result = stmt.step() ? (stmt.getAsObject() as unknown as ChannelState) : null;
|
|
368
|
-
stmt.free();
|
|
369
|
-
return result;
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
/**
|
|
373
|
-
* Mark a channel as joined.
|
|
374
|
-
* Authoritative writer: openagent_join_channel tool (on success).
|
|
375
|
-
*/
|
|
376
|
-
markJoined(channelId: string, channelName: string): void {
|
|
377
|
-
if (!this.db) return;
|
|
378
|
-
const now = Date.now();
|
|
379
|
-
this.db.run(
|
|
380
|
-
`INSERT INTO channel_state (account_id, channel_id, channel_name, joined, last_visit, created_at, updated_at)
|
|
381
|
-
VALUES (?, ?, ?, 1, ?, ?, ?)
|
|
382
|
-
ON CONFLICT(account_id, channel_id) DO UPDATE SET joined=1, channel_name=?, updated_at=?`,
|
|
383
|
-
[this.accountId, channelId, channelName || '', now, now, now, channelName || '', now]
|
|
384
|
-
);
|
|
385
|
-
this.persist();
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
/**
|
|
389
|
-
* Mark a channel as left.
|
|
390
|
-
* Authoritative writer: openagent_leave_channel tool (on success).
|
|
391
|
-
*/
|
|
392
|
-
markLeft(channelId: string): void {
|
|
393
|
-
if (!this.db) return;
|
|
394
|
-
this.db.run(
|
|
395
|
-
'UPDATE channel_state SET joined=0, updated_at=? WHERE account_id=? AND channel_id=?',
|
|
396
|
-
[Date.now(), this.accountId, channelId]
|
|
397
|
-
);
|
|
398
|
-
this.persist();
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
/**
|
|
402
|
-
* Update last_visit timestamp for a channel.
|
|
403
|
-
* Authoritative writer: Collector — on ingest() accepting a message.
|
|
404
|
-
*/
|
|
405
|
-
updateLastVisit(channelId: string): void {
|
|
406
|
-
if (!this.db) return;
|
|
407
|
-
const now = Date.now();
|
|
408
|
-
this.db.run(
|
|
409
|
-
'UPDATE channel_state SET last_visit=?, updated_at=? WHERE account_id=? AND channel_id=?',
|
|
410
|
-
[now, now, this.accountId, channelId]
|
|
411
|
-
);
|
|
412
|
-
this.persist();
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
/**
|
|
416
|
-
* Update last_reply timestamp for a channel.
|
|
417
|
-
* Authoritative writer: openagent_send_message tool — on sendMessage() success.
|
|
418
|
-
*/
|
|
419
|
-
updateLastReply(channelId: string): void {
|
|
420
|
-
if (!this.db) return;
|
|
421
|
-
const now = Date.now();
|
|
422
|
-
this.db.run(
|
|
423
|
-
'UPDATE channel_state SET last_reply=?, updated_at=? WHERE account_id=? AND channel_id=?',
|
|
424
|
-
[now, now, this.accountId, channelId]
|
|
425
|
-
);
|
|
426
|
-
this.persist();
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
/**
|
|
430
|
-
* Update last_msg_seq for a channel.
|
|
431
|
-
* Authoritative writer: Collector — on ingest() taking msg.sequence.
|
|
432
|
-
*/
|
|
433
|
-
updateLastMsgSeq(channelId: string, seq: number): void {
|
|
434
|
-
if (!this.db) return;
|
|
435
|
-
this.db.run(
|
|
436
|
-
'UPDATE channel_state SET last_msg_seq=?, updated_at=? WHERE account_id=? AND channel_id=?',
|
|
437
|
-
[seq, Date.now(), this.accountId, channelId]
|
|
438
|
-
);
|
|
439
|
-
this.persist();
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
// ── Message Dedup (Collector use) ──
|
|
443
|
-
|
|
444
|
-
hasSeen(messageId: string): boolean {
|
|
445
|
-
if (!this.db) return false;
|
|
446
|
-
const stmt = this.db.prepare(
|
|
447
|
-
'SELECT 1 FROM seen_messages WHERE account_id=? AND message_id=?'
|
|
448
|
-
);
|
|
449
|
-
stmt.bind([this.accountId, messageId]);
|
|
450
|
-
const found = stmt.step();
|
|
451
|
-
stmt.free();
|
|
452
|
-
return found;
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
markSeen(messageId: string, channelId: string): void {
|
|
456
|
-
if (!this.db) return;
|
|
457
|
-
this.db.run(
|
|
458
|
-
'INSERT OR IGNORE INTO seen_messages (account_id, message_id, channel_id, seen_at) VALUES (?, ?, ?, ?)',
|
|
459
|
-
[this.accountId, messageId, channelId, Date.now()]
|
|
460
|
-
);
|
|
461
|
-
// Batch persist: seen_messages are written frequently
|
|
462
|
-
this._seenCount += 1;
|
|
463
|
-
if (this._seenCount % 100 === 0) this.persist();
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
// ── Channel Skills ──
|
|
467
|
-
|
|
468
|
-
getSkill(channelId: string): string | null {
|
|
469
|
-
if (!this.db) return null;
|
|
470
|
-
const stmt = this.db.prepare(
|
|
471
|
-
'SELECT skill_content FROM channel_skills WHERE account_id = ? AND channel_id = ?'
|
|
472
|
-
);
|
|
473
|
-
stmt.bind([this.accountId, channelId]);
|
|
474
|
-
const result = stmt.step() ? stmt.getAsObject() : null;
|
|
475
|
-
stmt.free();
|
|
476
|
-
return (result?.['skill_content'] as string) || null;
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
setSkill(channelId: string, skillContent: string): void {
|
|
480
|
-
if (!this.db) return;
|
|
481
|
-
const now = Date.now();
|
|
482
|
-
this.db.run(
|
|
483
|
-
`INSERT INTO channel_skills (account_id, channel_id, skill_content, updated_at)
|
|
484
|
-
VALUES (?, ?, ?, ?)
|
|
485
|
-
ON CONFLICT(account_id, channel_id) DO UPDATE SET skill_content=?, updated_at=?`,
|
|
486
|
-
[this.accountId, channelId, skillContent, now, skillContent, now]
|
|
487
|
-
);
|
|
488
|
-
this.persist();
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
getAllSkills(): Array<{ channel_id: string; skill_content: string }> {
|
|
492
|
-
if (!this.db) return [];
|
|
493
|
-
const stmt = this.db.prepare(
|
|
494
|
-
'SELECT channel_id, skill_content FROM channel_skills WHERE account_id = ?'
|
|
495
|
-
);
|
|
496
|
-
stmt.bind([this.accountId]);
|
|
497
|
-
const results: Array<{ channel_id: string; skill_content: string }> = [];
|
|
498
|
-
while (stmt.step()) {
|
|
499
|
-
const row = stmt.getAsObject();
|
|
500
|
-
results.push({
|
|
501
|
-
channel_id: row['channel_id'] as string,
|
|
502
|
-
skill_content: row['skill_content'] as string,
|
|
503
|
-
});
|
|
504
|
-
}
|
|
505
|
-
stmt.free();
|
|
506
|
-
return results;
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
// ── Channel Purpose ──
|
|
510
|
-
|
|
511
|
-
getPurpose(channelId: string): string | null {
|
|
512
|
-
if (!this.db) return null;
|
|
513
|
-
const stmt = this.db.prepare(
|
|
514
|
-
'SELECT purpose FROM channel_purpose WHERE account_id = ? AND channel_id = ?'
|
|
515
|
-
);
|
|
516
|
-
stmt.bind([this.accountId, channelId]);
|
|
517
|
-
const result = stmt.step() ? stmt.getAsObject() : null;
|
|
518
|
-
stmt.free();
|
|
519
|
-
return (result?.['purpose'] as string) || null;
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
setPurpose(channelId: string, purpose: string): void {
|
|
523
|
-
if (!this.db) return;
|
|
524
|
-
const now = Date.now();
|
|
525
|
-
this.db.run(
|
|
526
|
-
`INSERT INTO channel_purpose (account_id, channel_id, purpose, updated_at)
|
|
527
|
-
VALUES (?, ?, ?, ?)
|
|
528
|
-
ON CONFLICT(account_id, channel_id) DO UPDATE SET purpose=?, updated_at=?`,
|
|
529
|
-
[this.accountId, channelId, purpose, now, purpose, now]
|
|
530
|
-
);
|
|
531
|
-
this.persist();
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
removePurpose(channelId: string): void {
|
|
535
|
-
if (!this.db) return;
|
|
536
|
-
this.db.run(
|
|
537
|
-
'DELETE FROM channel_purpose WHERE account_id = ? AND channel_id = ?',
|
|
538
|
-
[this.accountId, channelId]
|
|
539
|
-
);
|
|
540
|
-
this.persist();
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
/**
|
|
544
|
-
* Clean up old dedup records (older than 24h)
|
|
545
|
-
*/
|
|
546
|
-
cleanupSeen(): void {
|
|
547
|
-
if (!this.db) return;
|
|
548
|
-
const cutoff = Date.now() - 86_400_000;
|
|
549
|
-
this.db.run(
|
|
550
|
-
'DELETE FROM seen_messages WHERE account_id = ? AND seen_at < ?',
|
|
551
|
-
[this.accountId, cutoff]
|
|
552
|
-
);
|
|
553
|
-
this.persist();
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
/**
|
|
557
|
-
* Close the database connection
|
|
558
|
-
*/
|
|
559
|
-
close(): void {
|
|
560
|
-
if (this.db) {
|
|
561
|
-
this.persist(); // Final persist
|
|
562
|
-
this.db.close();
|
|
563
|
-
this.db = null;
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
// ═════════════════════════════════════════════════════════════════
|
|
568
|
-
// OASN remote_agent_sessions CRUD
|
|
569
|
-
//
|
|
570
|
-
// 保存远端 ServiceAgent 返回的 session_id,用于同一 OpenClaw 会话内后续
|
|
571
|
-
// mode='session' 调用续传。run 模式不读写这张表。
|
|
572
|
-
// ═════════════════════════════════════════════════════════════════
|
|
573
|
-
|
|
574
|
-
getRemoteAgentSession(openclawSessionKey: string, targetAgentId: string): string | null {
|
|
575
|
-
if (!this.db) return null;
|
|
576
|
-
const scopeKey = openclawSessionKey.trim();
|
|
577
|
-
const agentId = targetAgentId.trim();
|
|
578
|
-
if (!scopeKey || !agentId) return null;
|
|
579
|
-
|
|
580
|
-
const stmt = this.db.prepare(
|
|
581
|
-
`SELECT session_id
|
|
582
|
-
FROM remote_agent_sessions
|
|
583
|
-
WHERE account_id = ? AND openclaw_session_key = ? AND target_agent_id = ?`,
|
|
584
|
-
);
|
|
585
|
-
stmt.bind([this.accountId, scopeKey, agentId]);
|
|
586
|
-
const row = stmt.step() ? stmt.getAsObject() : null;
|
|
587
|
-
stmt.free();
|
|
588
|
-
const sessionId = row?.['session_id'];
|
|
589
|
-
return typeof sessionId === 'string' && sessionId.trim() ? sessionId : null;
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
upsertRemoteAgentSession(record: {
|
|
593
|
-
openclaw_session_key: string;
|
|
594
|
-
target_agent_id: string;
|
|
595
|
-
session_id: string;
|
|
596
|
-
}): void {
|
|
597
|
-
if (!this.db) return;
|
|
598
|
-
const scopeKey = record.openclaw_session_key.trim();
|
|
599
|
-
const agentId = record.target_agent_id.trim();
|
|
600
|
-
const sessionId = record.session_id.trim();
|
|
601
|
-
if (!scopeKey || !agentId || !sessionId) return;
|
|
602
|
-
|
|
603
|
-
const now = Date.now();
|
|
604
|
-
this.db.run(
|
|
605
|
-
`INSERT INTO remote_agent_sessions
|
|
606
|
-
(account_id, openclaw_session_key, target_agent_id, session_id, created_at, updated_at)
|
|
607
|
-
VALUES (?, ?, ?, ?, ?, ?)
|
|
608
|
-
ON CONFLICT(account_id, openclaw_session_key, target_agent_id) DO UPDATE SET
|
|
609
|
-
session_id = excluded.session_id,
|
|
610
|
-
updated_at = excluded.updated_at`,
|
|
611
|
-
[this.accountId, scopeKey, agentId, sessionId, now, now],
|
|
612
|
-
);
|
|
613
|
-
this.persist();
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
// ═════════════════════════════════════════════════════════════════
|
|
617
|
-
// §11.1 OASN pending_invocations CRUD(OasnInvocation.PendingInvocationStore 适配)
|
|
618
|
-
//
|
|
619
|
-
// 设计依据:docs/specs/2026-06-16-oasn-transport-abstraction-design.md §11.1
|
|
620
|
-
//
|
|
621
|
-
// 表内字段 account_id 用 this.accountId 自动注入,调用方无需关心多账号隔离。
|
|
622
|
-
// ═════════════════════════════════════════════════════════════════
|
|
623
|
-
|
|
624
|
-
/**
|
|
625
|
-
* 查 tool_call_id 是否有未完成记录。命中 → 复用其 idempotency_key 防止重复创建 Invocation。
|
|
626
|
-
*
|
|
627
|
-
* 仅当 status 为非终态(pending/accepted/queued/running)时返回,避免幂等键被复用到新调用上。
|
|
628
|
-
*/
|
|
629
|
-
getPendingInvocation(toolCallId: string): { idempotency_key: string; invocation_id?: string } | null {
|
|
630
|
-
if (!this.db) return null;
|
|
631
|
-
const stmt = this.db.prepare(
|
|
632
|
-
`SELECT idempotency_key, invocation_id, status
|
|
633
|
-
FROM pending_invocations
|
|
634
|
-
WHERE account_id = ? AND tool_call_id = ?`,
|
|
635
|
-
);
|
|
636
|
-
stmt.bind([this.accountId, toolCallId]);
|
|
637
|
-
let row: Record<string, unknown> | null = null;
|
|
638
|
-
if (stmt.step()) row = stmt.getAsObject();
|
|
639
|
-
stmt.free();
|
|
640
|
-
if (!row) return null;
|
|
641
|
-
const status = String(row['status']);
|
|
642
|
-
if (this._isTerminalStatus(status)) return null;
|
|
643
|
-
const result: { idempotency_key: string; invocation_id?: string } = {
|
|
644
|
-
idempotency_key: String(row['idempotency_key']),
|
|
645
|
-
};
|
|
646
|
-
const invId = row['invocation_id'];
|
|
647
|
-
if (typeof invId === 'string' && invId.length > 0) result.invocation_id = invId;
|
|
648
|
-
return result;
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
/**
|
|
652
|
-
* 插入或更新一条 pending 记录。
|
|
653
|
-
*
|
|
654
|
-
* 调用时机:
|
|
655
|
-
* - sendTask 之前(尚无 invocation_id,status='pending')
|
|
656
|
-
* - POST /api/invocations 返回后(回填 invocation_id + status)
|
|
657
|
-
*/
|
|
658
|
-
upsertPendingInvocation(record: {
|
|
659
|
-
tool_call_id: string;
|
|
660
|
-
idempotency_key: string;
|
|
661
|
-
invocation_id?: string;
|
|
662
|
-
session_id?: string;
|
|
663
|
-
target_agent_id?: string;
|
|
664
|
-
status: string;
|
|
665
|
-
}): void {
|
|
666
|
-
if (!this.db) return;
|
|
667
|
-
const now = Date.now();
|
|
668
|
-
this.db.run(
|
|
669
|
-
`INSERT INTO pending_invocations
|
|
670
|
-
(account_id, tool_call_id, idempotency_key, invocation_id, session_id, target_agent_id, status, created_at, updated_at)
|
|
671
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
672
|
-
ON CONFLICT(account_id, tool_call_id) DO UPDATE SET
|
|
673
|
-
invocation_id = COALESCE(excluded.invocation_id, pending_invocations.invocation_id),
|
|
674
|
-
session_id = COALESCE(excluded.session_id, pending_invocations.session_id),
|
|
675
|
-
target_agent_id = COALESCE(excluded.target_agent_id, pending_invocations.target_agent_id),
|
|
676
|
-
status = excluded.status,
|
|
677
|
-
updated_at = excluded.updated_at`,
|
|
678
|
-
[
|
|
679
|
-
this.accountId,
|
|
680
|
-
record.tool_call_id,
|
|
681
|
-
record.idempotency_key,
|
|
682
|
-
record.invocation_id ?? null,
|
|
683
|
-
record.session_id ?? null,
|
|
684
|
-
record.target_agent_id ?? null,
|
|
685
|
-
record.status,
|
|
686
|
-
now,
|
|
687
|
-
now,
|
|
688
|
-
],
|
|
689
|
-
);
|
|
690
|
-
this.persist();
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
/**
|
|
694
|
-
* 终态标记。透过 invocation_id 查找(OasnInvocation 在终态回调时只持有 invocation_id)。
|
|
695
|
-
*/
|
|
696
|
-
markPendingInvocationTerminal(invocationId: string, status: string): void {
|
|
697
|
-
if (!this.db) return;
|
|
698
|
-
this.db.run(
|
|
699
|
-
`UPDATE pending_invocations
|
|
700
|
-
SET status = ?, updated_at = ?
|
|
701
|
-
WHERE account_id = ? AND invocation_id = ?`,
|
|
702
|
-
[status, Date.now(), this.accountId, invocationId],
|
|
703
|
-
);
|
|
704
|
-
this.persist();
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
/**
|
|
708
|
-
* §11.2 abort 标记。
|
|
709
|
-
*
|
|
710
|
-
* 服务端没有 cancel API,本地只能记录"已放弃跟踪",便于排查 / 配额对账。
|
|
711
|
-
*/
|
|
712
|
-
markPendingInvocationAbandoned(invocationId: string): void {
|
|
713
|
-
this.markPendingInvocationTerminal(invocationId, 'abandoned');
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
/**
|
|
717
|
-
* 列出非终态记录(启动时续轮询用)。
|
|
718
|
-
*
|
|
719
|
-
* 返回顺序:按 created_at 升序,先创建的先续。
|
|
720
|
-
*/
|
|
721
|
-
listUnsettledInvocations(): PendingInvocationRecord[] {
|
|
722
|
-
if (!this.db) return [];
|
|
723
|
-
const stmt = this.db.prepare(
|
|
724
|
-
`SELECT tool_call_id, idempotency_key, invocation_id, session_id, target_agent_id, status, created_at, updated_at
|
|
725
|
-
FROM pending_invocations
|
|
726
|
-
WHERE account_id = ?
|
|
727
|
-
AND status NOT IN ('succeeded','failed','timed_out','cancelled','abandoned')
|
|
728
|
-
ORDER BY created_at ASC`,
|
|
729
|
-
);
|
|
730
|
-
stmt.bind([this.accountId]);
|
|
731
|
-
const results: PendingInvocationRecord[] = [];
|
|
732
|
-
while (stmt.step()) {
|
|
733
|
-
const row = stmt.getAsObject();
|
|
734
|
-
results.push({
|
|
735
|
-
tool_call_id: row['tool_call_id'] as string,
|
|
736
|
-
idempotency_key: row['idempotency_key'] as string,
|
|
737
|
-
invocation_id: (row['invocation_id'] as string | null) ?? null,
|
|
738
|
-
session_id: (row['session_id'] as string | null) ?? null,
|
|
739
|
-
target_agent_id: (row['target_agent_id'] as string | null) ?? null,
|
|
740
|
-
status: row['status'] as string,
|
|
741
|
-
created_at: Number(row['created_at']),
|
|
742
|
-
updated_at: Number(row['updated_at']),
|
|
743
|
-
});
|
|
744
|
-
}
|
|
745
|
-
stmt.free();
|
|
746
|
-
return results;
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
private _isTerminalStatus(status: string): boolean {
|
|
750
|
-
return status === 'succeeded'
|
|
751
|
-
|| status === 'failed'
|
|
752
|
-
|| status === 'timed_out'
|
|
753
|
-
|| status === 'cancelled'
|
|
754
|
-
|| status === 'abandoned';
|
|
755
|
-
}
|
|
756
|
-
}
|