@insta-dev01/insta-plugin-openclaw 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +171 -0
- package/dist/index.js.map +1 -0
- package/dist/src/channel/config.d.ts +9 -0
- package/dist/src/channel/config.d.ts.map +1 -0
- package/dist/src/channel/config.js +10 -0
- package/dist/src/channel/config.js.map +1 -0
- package/dist/src/channel/connection.d.ts +34 -0
- package/dist/src/channel/connection.d.ts.map +1 -0
- package/dist/src/channel/connection.js +281 -0
- package/dist/src/channel/connection.js.map +1 -0
- package/dist/src/channel/dispatcher.d.ts +43 -0
- package/dist/src/channel/dispatcher.d.ts.map +1 -0
- package/dist/src/channel/dispatcher.js +324 -0
- package/dist/src/channel/dispatcher.js.map +1 -0
- package/dist/src/channel/index.d.ts +5 -0
- package/dist/src/channel/index.d.ts.map +1 -0
- package/dist/src/channel/index.js +135 -0
- package/dist/src/channel/index.js.map +1 -0
- package/dist/src/channel/logger.d.ts +10 -0
- package/dist/src/channel/logger.d.ts.map +1 -0
- package/dist/src/channel/logger.js +30 -0
- package/dist/src/channel/logger.js.map +1 -0
- package/dist/src/channel/protocol.d.ts +15 -0
- package/dist/src/channel/protocol.d.ts.map +1 -0
- package/dist/src/channel/protocol.js +204 -0
- package/dist/src/channel/protocol.js.map +1 -0
- package/dist/src/channel/registrar.d.ts +21 -0
- package/dist/src/channel/registrar.d.ts.map +1 -0
- package/dist/src/channel/registrar.js +115 -0
- package/dist/src/channel/registrar.js.map +1 -0
- package/dist/src/channel/registration-store.d.ts +21 -0
- package/dist/src/channel/registration-store.d.ts.map +1 -0
- package/{src/channel/registration-store.ts → dist/src/channel/registration-store.js} +21 -46
- package/dist/src/channel/registration-store.js.map +1 -0
- package/dist/src/channel/types.d.ts +80 -0
- package/dist/src/channel/types.d.ts.map +1 -0
- package/dist/src/channel/types.js +3 -0
- package/dist/src/channel/types.js.map +1 -0
- package/dist/src/core/index.d.ts +5 -0
- package/dist/src/core/index.d.ts.map +1 -0
- package/dist/src/core/index.js +3 -0
- package/dist/src/core/index.js.map +1 -0
- package/dist/src/core/register-identity.d.ts +58 -0
- package/dist/src/core/register-identity.d.ts.map +1 -0
- package/dist/src/core/register-identity.js +251 -0
- package/dist/src/core/register-identity.js.map +1 -0
- package/dist/src/core/task-api.d.ts +31 -0
- package/dist/src/core/task-api.d.ts.map +1 -0
- package/dist/src/core/task-api.js +116 -0
- package/dist/src/core/task-api.js.map +1 -0
- package/dist/src/core/urls.d.ts +33 -0
- package/dist/src/core/urls.d.ts.map +1 -0
- package/dist/src/core/urls.js +40 -0
- package/dist/src/core/urls.js.map +1 -0
- package/dist/src/tools/get-plugin-profile.d.ts +7 -0
- package/dist/src/tools/get-plugin-profile.d.ts.map +1 -0
- package/dist/src/tools/get-plugin-profile.js +132 -0
- package/dist/src/tools/get-plugin-profile.js.map +1 -0
- package/dist/src/tools/grab-task.d.ts +7 -0
- package/dist/src/tools/grab-task.d.ts.map +1 -0
- package/dist/src/tools/grab-task.js +100 -0
- package/dist/src/tools/grab-task.js.map +1 -0
- package/dist/src/tools/list-tasks.d.ts +7 -0
- package/dist/src/tools/list-tasks.d.ts.map +1 -0
- package/dist/src/tools/list-tasks.js +92 -0
- package/dist/src/tools/list-tasks.js.map +1 -0
- package/dist/src/tools/propose-registration.d.ts +14 -0
- package/dist/src/tools/propose-registration.d.ts.map +1 -0
- package/dist/src/tools/propose-registration.js +103 -0
- package/dist/src/tools/propose-registration.js.map +1 -0
- package/dist/src/tools/register-identity.d.ts +11 -0
- package/dist/src/tools/register-identity.d.ts.map +1 -0
- package/dist/src/tools/register-identity.js +101 -0
- package/dist/src/tools/register-identity.js.map +1 -0
- package/dist/src/tools/submit-deliverable.d.ts +17 -0
- package/dist/src/tools/submit-deliverable.d.ts.map +1 -0
- package/dist/src/tools/submit-deliverable.js +215 -0
- package/dist/src/tools/submit-deliverable.js.map +1 -0
- package/dist/src/tools/upload-artifact.d.ts +14 -0
- package/dist/src/tools/upload-artifact.d.ts.map +1 -0
- package/dist/src/tools/upload-artifact.js +166 -0
- package/dist/src/tools/upload-artifact.js.map +1 -0
- package/dist/src/utils/file-lock.d.ts +4 -0
- package/dist/src/utils/file-lock.d.ts.map +1 -0
- package/dist/src/utils/file-lock.js +43 -0
- package/dist/src/utils/file-lock.js.map +1 -0
- package/dist/src/utils/profile.d.ts +17 -0
- package/dist/src/utils/profile.d.ts.map +1 -0
- package/dist/src/utils/profile.js +26 -0
- package/dist/src/utils/profile.js.map +1 -0
- package/dist/src/utils/session.d.ts +3 -0
- package/dist/src/utils/session.d.ts.map +1 -0
- package/dist/src/utils/session.js +26 -0
- package/dist/src/utils/session.js.map +1 -0
- package/package.json +17 -5
- package/.env.example +0 -23
- package/channel/346/265/201/347/250/213/345/233/276.md +0 -477
- package/index.ts +0 -198
- package/src/channel/config.ts +0 -27
- package/src/channel/connection.ts +0 -341
- package/src/channel/dispatcher.ts +0 -374
- package/src/channel/index.ts +0 -173
- package/src/channel/logger.ts +0 -36
- package/src/channel/protocol.ts +0 -265
- package/src/channel/registrar.ts +0 -172
- package/src/channel/types.ts +0 -102
- package/src/core/index.ts +0 -13
- package/src/core/register-identity.ts +0 -326
- package/src/core/task-api.ts +0 -168
- package/src/core/urls.ts +0 -52
- package/src/prompt/job.md +0 -21
- package/src/tools/get-plugin-profile.ts +0 -152
- package/src/tools/grab-task.ts +0 -133
- package/src/tools/list-tasks.ts +0 -135
- package/src/tools/propose-registration.ts +0 -116
- package/src/tools/register-identity.ts +0 -121
- package/src/tools/submit-deliverable.ts +0 -268
- package/src/tools/upload-artifact.ts +0 -222
- package/src/utils/file-lock.ts +0 -43
- package/src/utils/profile.ts +0 -45
- package/src/utils/session.ts +0 -30
- package/tests/profile.test.ts +0 -70
- package/tests/session.test.ts +0 -53
- package/tsconfig.json +0 -49
- package/vitest.config.ts +0 -26
|
@@ -1,374 +0,0 @@
|
|
|
1
|
-
import type { WebSocket } from "ws";
|
|
2
|
-
import type { DebugLogger } from "./logger.js";
|
|
3
|
-
import {
|
|
4
|
-
createInProgressEvent,
|
|
5
|
-
createOutputItemAddedEvent,
|
|
6
|
-
createOutputTextDeltaEvent,
|
|
7
|
-
createContentPartDoneEvent,
|
|
8
|
-
createCompletedEvent,
|
|
9
|
-
createFailedEvent,
|
|
10
|
-
createEnvelope,
|
|
11
|
-
} from "./protocol.js";
|
|
12
|
-
|
|
13
|
-
// ── Types ─────────────────────────────────────────────────────────────────────
|
|
14
|
-
|
|
15
|
-
export type SDKCallback = (chunk: string | null, error: Error | null, isComplete: boolean) => void;
|
|
16
|
-
|
|
17
|
-
interface RequestContext {
|
|
18
|
-
messageId: string;
|
|
19
|
-
responseId: string;
|
|
20
|
-
itemId: string;
|
|
21
|
-
content: string;
|
|
22
|
-
requestTimestamp: number;
|
|
23
|
-
responseBuffer: string;
|
|
24
|
-
firstChunkReceived: boolean;
|
|
25
|
-
timeoutTimer: NodeJS.Timeout | null;
|
|
26
|
-
abortController: AbortController | null;
|
|
27
|
-
status: "pending" | "processing" | "completed" | "failed" | "timeout";
|
|
28
|
-
ws: WebSocket;
|
|
29
|
-
sessionId: string;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export interface DispatcherConfig {
|
|
33
|
-
requestTimeout: number;
|
|
34
|
-
maxConcurrentRequests: number;
|
|
35
|
-
debug: boolean;
|
|
36
|
-
systemPrompt?: string;
|
|
37
|
-
accountId?: string;
|
|
38
|
-
cfg?: unknown;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// ── SDKDispatcher ─────────────────────────────────────────────────────────────
|
|
42
|
-
|
|
43
|
-
export class SDKDispatcher {
|
|
44
|
-
private contexts = new Map<string, RequestContext>();
|
|
45
|
-
|
|
46
|
-
constructor(
|
|
47
|
-
private config: DispatcherConfig,
|
|
48
|
-
private logger: DebugLogger,
|
|
49
|
-
private accountId?: string,
|
|
50
|
-
private channelRuntime?: unknown,
|
|
51
|
-
) {
|
|
52
|
-
this.logger.info("SDKDispatcher initialized", {
|
|
53
|
-
requestTimeout: config.requestTimeout,
|
|
54
|
-
maxConcurrentRequests: config.maxConcurrentRequests,
|
|
55
|
-
hasChannelRuntime: !!channelRuntime,
|
|
56
|
-
accountId,
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
async dispatchRequest(
|
|
61
|
-
request: { content: string; messageId: string; sessionId?: string },
|
|
62
|
-
ws: WebSocket,
|
|
63
|
-
): Promise<void> {
|
|
64
|
-
const sessionId = request.sessionId ?? "";
|
|
65
|
-
|
|
66
|
-
if (!request.content?.trim()) {
|
|
67
|
-
const responseId = this.newId("resp");
|
|
68
|
-
await this.sendFailedEvent(ws, responseId, "INVALID_REQUEST", "Request content must be non-empty", sessionId);
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
if (this.activeCount() >= this.config.maxConcurrentRequests) {
|
|
73
|
-
const responseId = this.newId("resp");
|
|
74
|
-
await this.sendFailedEvent(ws, responseId, "RATE_LIMIT", "Maximum concurrent requests exceeded", sessionId);
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const responseId = this.newId("resp");
|
|
79
|
-
const itemId = this.newId("item");
|
|
80
|
-
const abortController = new AbortController();
|
|
81
|
-
|
|
82
|
-
const ctx: RequestContext = {
|
|
83
|
-
messageId: request.messageId,
|
|
84
|
-
responseId,
|
|
85
|
-
itemId,
|
|
86
|
-
content: request.content,
|
|
87
|
-
requestTimestamp: Date.now(),
|
|
88
|
-
responseBuffer: "",
|
|
89
|
-
firstChunkReceived: false,
|
|
90
|
-
timeoutTimer: null,
|
|
91
|
-
abortController,
|
|
92
|
-
status: "pending",
|
|
93
|
-
ws,
|
|
94
|
-
sessionId,
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
this.contexts.set(request.messageId, ctx);
|
|
98
|
-
this.logger.info("Dispatching request", {
|
|
99
|
-
messageId: request.messageId,
|
|
100
|
-
responseId,
|
|
101
|
-
sessionId,
|
|
102
|
-
contentLength: request.content.length,
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
ctx.timeoutTimer = setTimeout(() => this.handleTimeout(request.messageId), this.config.requestTimeout);
|
|
106
|
-
ctx.status = "processing";
|
|
107
|
-
|
|
108
|
-
try {
|
|
109
|
-
await this.realSDKDispatch(request.content, this.createCallback(request.messageId), abortController.signal, sessionId);
|
|
110
|
-
} catch (err) {
|
|
111
|
-
if (err instanceof Error && err.name === "AbortError") return;
|
|
112
|
-
await this.handleError(ctx, err instanceof Error ? err : new Error(String(err)));
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
activeCount(): number {
|
|
117
|
-
let n = 0;
|
|
118
|
-
for (const c of this.contexts.values()) {
|
|
119
|
-
if (c.status === "pending" || c.status === "processing") n++;
|
|
120
|
-
}
|
|
121
|
-
return n;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// ── Private ─────────────────────────────────────────────────────────────────
|
|
125
|
-
|
|
126
|
-
private newId(prefix: string): string {
|
|
127
|
-
return `${prefix}_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
private buildSessionKey(sessionId?: string): string {
|
|
131
|
-
const account = this.accountId ?? "default";
|
|
132
|
-
return sessionId ? `instaclaw:${account}:${sessionId}` : `instaclaw:${account}`;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
private async sendFailedEvent(ws: WebSocket, responseId: string, code: string, message: string, sessionId?: string): Promise<void> {
|
|
136
|
-
try {
|
|
137
|
-
if (ws.readyState === 1) {
|
|
138
|
-
ws.send(createEnvelope(createFailedEvent(responseId, code, message, null, sessionId)));
|
|
139
|
-
}
|
|
140
|
-
} catch (err) {
|
|
141
|
-
this.logger.error("Failed to send failed event", err);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
private async realSDKDispatch(
|
|
146
|
-
content: string,
|
|
147
|
-
callback: SDKCallback,
|
|
148
|
-
signal?: AbortSignal,
|
|
149
|
-
sessionId?: string,
|
|
150
|
-
): Promise<void> {
|
|
151
|
-
const runtime = this.channelRuntime as {
|
|
152
|
-
reply?: {
|
|
153
|
-
dispatchReplyWithBufferedBlockDispatcher?: (opts: unknown) => Promise<void>;
|
|
154
|
-
};
|
|
155
|
-
} | undefined;
|
|
156
|
-
|
|
157
|
-
if (!runtime?.reply?.dispatchReplyWithBufferedBlockDispatcher) {
|
|
158
|
-
this.logger.warn("channelRuntime not available — falling back to echo");
|
|
159
|
-
await new Promise<void>((resolve) => {
|
|
160
|
-
setTimeout(() => {
|
|
161
|
-
if (!signal?.aborted) callback("[no AI runtime] echo: " + content.substring(0, 50), null, false);
|
|
162
|
-
setTimeout(() => {
|
|
163
|
-
if (!signal?.aborted) callback(null, null, true);
|
|
164
|
-
resolve();
|
|
165
|
-
}, 50);
|
|
166
|
-
}, 50);
|
|
167
|
-
});
|
|
168
|
-
return;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const baseSystemPrompt = (this.config.systemPrompt ?? "");
|
|
172
|
-
const sessionInstruction = sessionId
|
|
173
|
-
? `\n\n【当前会话信息】\n当前雇佣会话 sessionId 为:${sessionId}\n当你调用 insta_upload_artifact 上传文件时,必须将此 sessionId 作为参数传入,以便系统自动将产物与本次雇佣会话关联。`
|
|
174
|
-
: "";
|
|
175
|
-
const resolvedSystemPrompt = (baseSystemPrompt + sessionInstruction).trim() || undefined;
|
|
176
|
-
|
|
177
|
-
const msgCtx = {
|
|
178
|
-
Body: content,
|
|
179
|
-
AccountId: this.accountId,
|
|
180
|
-
SessionKey: this.buildSessionKey(sessionId),
|
|
181
|
-
...(resolvedSystemPrompt ? { SystemPrompt: resolvedSystemPrompt } : {}),
|
|
182
|
-
};
|
|
183
|
-
|
|
184
|
-
let alreadyCompleted = false;
|
|
185
|
-
const safeComplete = () => {
|
|
186
|
-
if (alreadyCompleted || signal?.aborted) return;
|
|
187
|
-
alreadyCompleted = true;
|
|
188
|
-
callback(null, null, true);
|
|
189
|
-
};
|
|
190
|
-
|
|
191
|
-
let streamedAnyChunks = false;
|
|
192
|
-
let previousAccumulatedText = "";
|
|
193
|
-
|
|
194
|
-
await runtime.reply.dispatchReplyWithBufferedBlockDispatcher({
|
|
195
|
-
ctx: msgCtx,
|
|
196
|
-
cfg: this.config.cfg,
|
|
197
|
-
replyOptions: {
|
|
198
|
-
abortSignal: signal,
|
|
199
|
-
onPartialReply: (payload: { text?: string }) => {
|
|
200
|
-
if (signal?.aborted) return;
|
|
201
|
-
if (payload.text && payload.text.length > previousAccumulatedText.length) {
|
|
202
|
-
const delta = payload.text.substring(previousAccumulatedText.length);
|
|
203
|
-
previousAccumulatedText = payload.text;
|
|
204
|
-
if (delta) {
|
|
205
|
-
streamedAnyChunks = true;
|
|
206
|
-
callback(delta, null, false);
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
},
|
|
210
|
-
},
|
|
211
|
-
dispatcherOptions: {
|
|
212
|
-
deliver: async (payload: { text?: string }) => {
|
|
213
|
-
if (signal?.aborted) return;
|
|
214
|
-
if (!streamedAnyChunks && payload.text) {
|
|
215
|
-
callback(payload.text, null, false);
|
|
216
|
-
}
|
|
217
|
-
safeComplete();
|
|
218
|
-
},
|
|
219
|
-
onError: (err: unknown) => {
|
|
220
|
-
callback(null, err instanceof Error ? err : new Error(String(err)), false);
|
|
221
|
-
},
|
|
222
|
-
},
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
safeComplete();
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
private createCallback(messageId: string): SDKCallback {
|
|
229
|
-
return (chunk, error, isComplete) => {
|
|
230
|
-
try {
|
|
231
|
-
const ctx = this.contexts.get(messageId);
|
|
232
|
-
if (!ctx) {
|
|
233
|
-
this.logger.warn("Callback for unknown messageId", { messageId });
|
|
234
|
-
return;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
if (error) {
|
|
238
|
-
this.handleError(ctx, error).catch((e) => this.logger.error("Error in handleError", e));
|
|
239
|
-
return;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
if (isComplete) {
|
|
243
|
-
this.handleCompletion(ctx).catch((e) => this.logger.error("Error in handleCompletion", e));
|
|
244
|
-
return;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
if (chunk) {
|
|
248
|
-
this.handleChunk(ctx, chunk).catch((e) => this.logger.error("Error in handleChunk", e));
|
|
249
|
-
}
|
|
250
|
-
} catch (e) {
|
|
251
|
-
this.logger.error("Callback threw", e);
|
|
252
|
-
}
|
|
253
|
-
};
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
private handleTimeout(messageId: string): void {
|
|
257
|
-
const ctx = this.contexts.get(messageId);
|
|
258
|
-
if (!ctx) return;
|
|
259
|
-
|
|
260
|
-
this.logger.warn("Request timeout", { messageId, responseId: ctx.responseId });
|
|
261
|
-
ctx.status = "timeout";
|
|
262
|
-
|
|
263
|
-
this.generateFailedEvent(ctx, "TIMEOUT", `Request timed out after ${this.config.requestTimeout}ms`, {
|
|
264
|
-
timeoutDuration: this.config.requestTimeout,
|
|
265
|
-
}).catch((e) => this.logger.error("Failed to generate timeout event", e));
|
|
266
|
-
|
|
267
|
-
ctx.abortController?.abort();
|
|
268
|
-
this.cleanupContext(messageId);
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
private sendWs(ctx: RequestContext, data: string): void {
|
|
272
|
-
if (ctx.ws.readyState === 1) {
|
|
273
|
-
ctx.ws.send(data);
|
|
274
|
-
} else {
|
|
275
|
-
this.logger.warn("WebSocket not open, dropping event", { responseId: ctx.responseId, readyState: ctx.ws.readyState });
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
private async generateInProgressEvent(ctx: RequestContext): Promise<void> {
|
|
280
|
-
try {
|
|
281
|
-
this.sendWs(ctx, createEnvelope(createInProgressEvent(ctx.responseId, ctx.sessionId)));
|
|
282
|
-
} catch (e) { this.logger.error("Failed to send in_progress", e); }
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
private async generateItemAddedEvent(ctx: RequestContext): Promise<void> {
|
|
286
|
-
try {
|
|
287
|
-
this.sendWs(ctx, createEnvelope(createOutputItemAddedEvent(ctx.responseId, ctx.itemId, 0)));
|
|
288
|
-
} catch (e) { this.logger.error("Failed to send item_added", e); }
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
private async generateDeltaEvent(ctx: RequestContext, text: string): Promise<void> {
|
|
292
|
-
try {
|
|
293
|
-
this.sendWs(ctx, createEnvelope(createOutputTextDeltaEvent(ctx.responseId, ctx.itemId, 0, text)));
|
|
294
|
-
} catch (e) { this.logger.error("Failed to send delta", e); }
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
private async generateContentPartDoneEvent(ctx: RequestContext): Promise<void> {
|
|
298
|
-
try {
|
|
299
|
-
this.sendWs(ctx, createEnvelope(createContentPartDoneEvent(ctx.responseId, ctx.itemId, 0)));
|
|
300
|
-
} catch (e) { this.logger.error("Failed to send content_part.done", e); }
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
private async generateCompletedEvent(ctx: RequestContext): Promise<void> {
|
|
304
|
-
try {
|
|
305
|
-
this.sendWs(ctx, createEnvelope(createCompletedEvent(ctx.responseId, ctx.sessionId)));
|
|
306
|
-
} catch (e) { this.logger.error("Failed to send completed", e); }
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
private async generateFailedEvent(ctx: RequestContext, code: string, message: string, details?: unknown): Promise<void> {
|
|
310
|
-
try {
|
|
311
|
-
this.sendWs(ctx, createEnvelope(createFailedEvent(ctx.responseId, code, message, details, ctx.sessionId)));
|
|
312
|
-
} catch (e) { this.logger.error("Failed to send failed event", e); }
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
private async handleChunk(ctx: RequestContext, chunk: string): Promise<void> {
|
|
316
|
-
try {
|
|
317
|
-
if (!ctx.firstChunkReceived) {
|
|
318
|
-
ctx.firstChunkReceived = true;
|
|
319
|
-
await this.generateInProgressEvent(ctx);
|
|
320
|
-
await this.generateItemAddedEvent(ctx);
|
|
321
|
-
}
|
|
322
|
-
ctx.responseBuffer += chunk;
|
|
323
|
-
await this.generateDeltaEvent(ctx, chunk);
|
|
324
|
-
} catch (e) { this.logger.error("Error handling chunk", e); }
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
private async handleCompletion(ctx: RequestContext): Promise<void> {
|
|
328
|
-
try {
|
|
329
|
-
// If the SDK produced no chunks at all (e.g. empty reply), still emit
|
|
330
|
-
// the protocol preamble so the client sees a well-formed response.
|
|
331
|
-
if (!ctx.firstChunkReceived) {
|
|
332
|
-
ctx.firstChunkReceived = true;
|
|
333
|
-
await this.generateInProgressEvent(ctx);
|
|
334
|
-
await this.generateItemAddedEvent(ctx);
|
|
335
|
-
}
|
|
336
|
-
await this.generateContentPartDoneEvent(ctx);
|
|
337
|
-
await this.generateCompletedEvent(ctx);
|
|
338
|
-
ctx.status = "completed";
|
|
339
|
-
this.logger.info("Request completed", {
|
|
340
|
-
messageId: ctx.messageId,
|
|
341
|
-
responseId: ctx.responseId,
|
|
342
|
-
duration: Date.now() - ctx.requestTimestamp,
|
|
343
|
-
});
|
|
344
|
-
} catch (e) {
|
|
345
|
-
this.logger.error("Error in handleCompletion", e);
|
|
346
|
-
} finally {
|
|
347
|
-
this.cleanupContext(ctx.messageId);
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
private async handleError(ctx: RequestContext, error: Error): Promise<void> {
|
|
352
|
-
try {
|
|
353
|
-
let code = "SDK_ERROR";
|
|
354
|
-
if (error.message.includes("timeout")) code = "TIMEOUT";
|
|
355
|
-
await this.generateFailedEvent(ctx, code, error.message, { stack: error.stack });
|
|
356
|
-
ctx.status = "failed";
|
|
357
|
-
} catch (e) {
|
|
358
|
-
this.logger.error("Error in handleError", e);
|
|
359
|
-
} finally {
|
|
360
|
-
this.cleanupContext(ctx.messageId);
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
private cleanupContext(messageId: string): void {
|
|
365
|
-
const ctx = this.contexts.get(messageId);
|
|
366
|
-
if (!ctx) return;
|
|
367
|
-
if (ctx.timeoutTimer) {
|
|
368
|
-
clearTimeout(ctx.timeoutTimer);
|
|
369
|
-
ctx.timeoutTimer = null;
|
|
370
|
-
}
|
|
371
|
-
ctx.abortController = null;
|
|
372
|
-
this.contexts.delete(messageId);
|
|
373
|
-
}
|
|
374
|
-
}
|
package/src/channel/index.ts
DELETED
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
import type { ChannelPlugin } from "openclaw/plugin-sdk";
|
|
2
|
-
import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/account-id";
|
|
3
|
-
import { CHANNEL_ID, DEBUG_ENABLED, POLL_INTERVAL_MS } from "./config.js";
|
|
4
|
-
import { monitorProvider } from "./connection.js";
|
|
5
|
-
import { triggerRegistrationAgent } from "./registrar.js";
|
|
6
|
-
import { setBaseDir, isAgentRunning, getPendingRegistration } from "./registration-store.js";
|
|
7
|
-
import { readProfile } from "../utils/profile.js";
|
|
8
|
-
import { createEnvelope, textToEventSequence } from "./protocol.js";
|
|
9
|
-
import { DebugLogger } from "./logger.js";
|
|
10
|
-
|
|
11
|
-
// ── Active WS connections (accountId → WebSocket) ─────────────────────────────
|
|
12
|
-
|
|
13
|
-
const activeConnections = new Map<string, import("ws").WebSocket>();
|
|
14
|
-
|
|
15
|
-
export function registerConnection(accountId: string, ws: import("ws").WebSocket): void {
|
|
16
|
-
activeConnections.set(accountId || "default", ws);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export function unregisterConnection(accountId: string): void {
|
|
20
|
-
activeConnections.delete(accountId || "default");
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// ── Plugin ────────────────────────────────────────────────────────────────────
|
|
24
|
-
|
|
25
|
-
export const plugin: ChannelPlugin = {
|
|
26
|
-
id: CHANNEL_ID,
|
|
27
|
-
|
|
28
|
-
meta: {
|
|
29
|
-
id: CHANNEL_ID,
|
|
30
|
-
label: "InstaClaw",
|
|
31
|
-
selectionLabel: "InstaClaw",
|
|
32
|
-
docsPath: "/plugins/insta-plugin-openclaw",
|
|
33
|
-
blurb: "Instagram Claw Connector",
|
|
34
|
-
},
|
|
35
|
-
|
|
36
|
-
capabilities: {
|
|
37
|
-
chatTypes: ["direct"],
|
|
38
|
-
media: false,
|
|
39
|
-
polls: false,
|
|
40
|
-
threads: false,
|
|
41
|
-
reactions: false,
|
|
42
|
-
edit: false,
|
|
43
|
-
reply: false,
|
|
44
|
-
},
|
|
45
|
-
|
|
46
|
-
config: {
|
|
47
|
-
listAccountIds: (_cfg) => [DEFAULT_ACCOUNT_ID],
|
|
48
|
-
|
|
49
|
-
resolveAccount: (_cfg, accountId) => ({
|
|
50
|
-
accountId: accountId ?? DEFAULT_ACCOUNT_ID,
|
|
51
|
-
}),
|
|
52
|
-
|
|
53
|
-
defaultAccountId: () => DEFAULT_ACCOUNT_ID,
|
|
54
|
-
|
|
55
|
-
isConfigured: (_account) => true,
|
|
56
|
-
|
|
57
|
-
describeAccount: (account) => ({
|
|
58
|
-
accountId: (account as { accountId: string }).accountId,
|
|
59
|
-
}),
|
|
60
|
-
},
|
|
61
|
-
|
|
62
|
-
gateway: {
|
|
63
|
-
startAccount: async (ctx) => {
|
|
64
|
-
const { cfg, accountId, abortSignal } = ctx;
|
|
65
|
-
const resolvedAccountId = accountId ?? DEFAULT_ACCOUNT_ID;
|
|
66
|
-
|
|
67
|
-
// Resolve base directory for local profile storage
|
|
68
|
-
const cfgAny = cfg as Record<string, unknown>;
|
|
69
|
-
const baseDir =
|
|
70
|
-
(cfgAny["agentDir"] as string | undefined) ??
|
|
71
|
-
(cfgAny["workspaceDir"] as string | undefined) ??
|
|
72
|
-
process.env["INSTA_BASE_DIR"] ??
|
|
73
|
-
process.cwd();
|
|
74
|
-
|
|
75
|
-
const logger = new DebugLogger(DEBUG_ENABLED, `[InstaPlugin:${resolvedAccountId}:startup]`);
|
|
76
|
-
|
|
77
|
-
// Bug 1 fix: cache the resolved baseDir so the gateway method
|
|
78
|
-
// (instaclaw.registration.submit) writes profile.json to the same path
|
|
79
|
-
// that this polling loop reads from.
|
|
80
|
-
setBaseDir(baseDir);
|
|
81
|
-
|
|
82
|
-
// Abort-aware sleep helper
|
|
83
|
-
const sleep = (ms: number) =>
|
|
84
|
-
new Promise<void>((resolve) => {
|
|
85
|
-
if (abortSignal.aborted) { resolve(); return; }
|
|
86
|
-
const t = setTimeout(resolve, ms);
|
|
87
|
-
abortSignal.addEventListener("abort", () => { clearTimeout(t); resolve(); }, { once: true });
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
logger.info("Gateway startup — entering credential polling loop", { baseDir, pollIntervalMs: POLL_INTERVAL_MS });
|
|
91
|
-
|
|
92
|
-
while (!abortSignal.aborted) {
|
|
93
|
-
const profile = await readProfile(baseDir).catch((err) => {
|
|
94
|
-
logger.error("Failed to read profile", err as Error);
|
|
95
|
-
return null;
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
const hasCredentials = !!(profile?.app_key && profile?.app_secret);
|
|
99
|
-
|
|
100
|
-
if (hasCredentials) {
|
|
101
|
-
logger.info("Credentials found, establishing WebSocket connection");
|
|
102
|
-
await monitorProvider(
|
|
103
|
-
{ clientId: profile!.app_key, clientSecret: profile!.app_secret },
|
|
104
|
-
resolvedAccountId,
|
|
105
|
-
abortSignal,
|
|
106
|
-
ctx.channelRuntime,
|
|
107
|
-
);
|
|
108
|
-
// monitorProvider resolves when the connection is cleanly shut down (abort).
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// ── No credentials yet ────────────────────────────────────────────────
|
|
113
|
-
//
|
|
114
|
-
// Bug 2 fix: use store-based flags instead of a local boolean so the
|
|
115
|
-
// loop can recover if the agent completes without creating a proposal.
|
|
116
|
-
//
|
|
117
|
-
// isAgentRunning() → dispatch is in-flight, skip
|
|
118
|
-
// getPendingRegistration() → proposal exists, wait for user, skip
|
|
119
|
-
// neither → safe to trigger (first run OR recovery)
|
|
120
|
-
const agentRunning = isAgentRunning();
|
|
121
|
-
const hasPending = !!getPendingRegistration();
|
|
122
|
-
|
|
123
|
-
logger.info("No credentials in profile — waiting for registration", {
|
|
124
|
-
profileExists: !!profile,
|
|
125
|
-
agentRunning,
|
|
126
|
-
hasPending,
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
if (!agentRunning && !hasPending) {
|
|
130
|
-
const result = await triggerRegistrationAgent(ctx.channelRuntime, cfg, logger, resolvedAccountId);
|
|
131
|
-
logger.info("Registration agent trigger result", { status: result.status, error: result.error });
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
await sleep(POLL_INTERVAL_MS);
|
|
135
|
-
}
|
|
136
|
-
},
|
|
137
|
-
},
|
|
138
|
-
|
|
139
|
-
outbound: {
|
|
140
|
-
deliveryMode: "gateway" as const,
|
|
141
|
-
|
|
142
|
-
sendText: async (ctx) => {
|
|
143
|
-
const { cfg, to, text, accountId } = ctx;
|
|
144
|
-
void cfg; // credentials are managed via gateway; cfg not needed here
|
|
145
|
-
|
|
146
|
-
if (!text?.trim()) {
|
|
147
|
-
throw new Error("Cannot send empty message");
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
const wsKey = accountId ?? DEFAULT_ACCOUNT_ID;
|
|
151
|
-
const ws = activeConnections.get(wsKey);
|
|
152
|
-
|
|
153
|
-
if (!ws) {
|
|
154
|
-
throw new Error(`WebSocket not found for account "${wsKey}". Gateway may not be connected.`);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
if (ws.readyState !== 1 /* OPEN */) {
|
|
158
|
-
throw new Error(`WebSocket not open (state: ${ws.readyState}) for account "${wsKey}"`);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const events = textToEventSequence(text);
|
|
162
|
-
for (const event of events) {
|
|
163
|
-
ws.send(createEnvelope(event));
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
return {
|
|
167
|
-
channel: CHANNEL_ID,
|
|
168
|
-
messageId: `msg_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`,
|
|
169
|
-
to,
|
|
170
|
-
};
|
|
171
|
-
},
|
|
172
|
-
},
|
|
173
|
-
};
|
package/src/channel/logger.ts
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
export class DebugLogger {
|
|
2
|
-
constructor(
|
|
3
|
-
private enabled: boolean,
|
|
4
|
-
private prefix: string = "[InstaPlugin]",
|
|
5
|
-
) {}
|
|
6
|
-
|
|
7
|
-
info(message: string, ...args: unknown[]): void {
|
|
8
|
-
console.log(`${new Date().toISOString()} [INFO] ${this.prefix}`, message, ...args);
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
debug(message: string, ...args: unknown[]): void {
|
|
12
|
-
if (this.enabled) {
|
|
13
|
-
console.log(`${new Date().toISOString()} [DEBUG] ${this.prefix}`, message, ...args);
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
warn(message: string, ...args: unknown[]): void {
|
|
18
|
-
console.warn(`${new Date().toISOString()} [WARN] ${this.prefix}`, message, ...args);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
error(message: string, errorOrContext?: unknown, ...args: unknown[]): void {
|
|
22
|
-
const ts = new Date().toISOString();
|
|
23
|
-
if (errorOrContext instanceof Error) {
|
|
24
|
-
console.error(
|
|
25
|
-
`${ts} [ERROR] ${this.prefix}`,
|
|
26
|
-
message,
|
|
27
|
-
"\nError:", errorOrContext.message,
|
|
28
|
-
"\nStack:", errorOrContext.stack,
|
|
29
|
-
...args,
|
|
30
|
-
);
|
|
31
|
-
} else {
|
|
32
|
-
const tail = errorOrContext === undefined ? args : [errorOrContext, ...args];
|
|
33
|
-
console.error(`${ts} [ERROR] ${this.prefix}`, message, ...tail);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|