@vibearound/plugin-channel-sdk 0.1.2 → 0.3.0
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/errors.d.ts +16 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +33 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/renderer.d.ts.map +1 -1
- package/dist/renderer.js +31 -12
- package/dist/renderer.js.map +1 -1
- package/dist/run-plugin.d.ts +95 -0
- package/dist/run-plugin.d.ts.map +1 -0
- package/dist/run-plugin.js +132 -0
- package/dist/run-plugin.js.map +1 -0
- package/package.json +1 -1
- package/src/errors.ts +29 -0
- package/src/index.ts +15 -0
- package/src/renderer.ts +71 -11
- package/src/run-plugin.ts +249 -0
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error normalization for channel plugins.
|
|
3
|
+
*
|
|
4
|
+
* Every plugin had an ad-hoc ladder that tried `instanceof Error`, then
|
|
5
|
+
* `typeof error === "object"`, then fell back to `String(error)`. That
|
|
6
|
+
* ladder lived in five slightly-different forms across bot.ts files and
|
|
7
|
+
* drifted over time. Centralize it here.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Extract a human-readable message from an unknown thrown value.
|
|
11
|
+
*
|
|
12
|
+
* Prefers `Error.message`, falls back to a non-circular JSON stringify for
|
|
13
|
+
* objects, and finally to `String(e)` for primitives.
|
|
14
|
+
*/
|
|
15
|
+
export declare function extractErrorMessage(e: unknown): string;
|
|
16
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,CAatD"}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error normalization for channel plugins.
|
|
3
|
+
*
|
|
4
|
+
* Every plugin had an ad-hoc ladder that tried `instanceof Error`, then
|
|
5
|
+
* `typeof error === "object"`, then fell back to `String(error)`. That
|
|
6
|
+
* ladder lived in five slightly-different forms across bot.ts files and
|
|
7
|
+
* drifted over time. Centralize it here.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Extract a human-readable message from an unknown thrown value.
|
|
11
|
+
*
|
|
12
|
+
* Prefers `Error.message`, falls back to a non-circular JSON stringify for
|
|
13
|
+
* objects, and finally to `String(e)` for primitives.
|
|
14
|
+
*/
|
|
15
|
+
export function extractErrorMessage(e) {
|
|
16
|
+
if (e instanceof Error)
|
|
17
|
+
return e.message;
|
|
18
|
+
if (typeof e === "string")
|
|
19
|
+
return e;
|
|
20
|
+
if (e && typeof e === "object") {
|
|
21
|
+
const msg = e.message;
|
|
22
|
+
if (typeof msg === "string")
|
|
23
|
+
return msg;
|
|
24
|
+
try {
|
|
25
|
+
return JSON.stringify(e);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return String(e);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return String(e);
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAAC,CAAU;IAC5C,IAAI,CAAC,YAAY,KAAK;QAAE,OAAO,CAAC,CAAC,OAAO,CAAC;IACzC,IAAI,OAAO,CAAC,KAAK,QAAQ;QAAE,OAAO,CAAC,CAAC;IACpC,IAAI,CAAC,IAAI,OAAO,CAAC,KAAK,QAAQ,EAAE,CAAC;QAC/B,MAAM,GAAG,GAAI,CAA2B,CAAC,OAAO,CAAC;QACjD,IAAI,OAAO,GAAG,KAAK,QAAQ;YAAE,OAAO,GAAG,CAAC;QACxC,IAAI,CAAC;YACH,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;QAC3B,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC;QACnB,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC;AACnB,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -54,6 +54,9 @@
|
|
|
54
54
|
*/
|
|
55
55
|
export { connectToHost, normalizeExtMethod, redirectConsoleToStderr } from "./connection.js";
|
|
56
56
|
export type { PluginInfo, ConnectResult, AgentInfo } from "./connection.js";
|
|
57
|
+
export { extractErrorMessage } from "./errors.js";
|
|
58
|
+
export { runChannelPlugin } from "./run-plugin.js";
|
|
59
|
+
export type { ChannelBot, ChannelPluginLogger, ChannelStreamHandler, CreateBotContext, RunChannelPluginSpec, VerboseOptions, } from "./run-plugin.js";
|
|
57
60
|
export { BlockRenderer } from "./renderer.js";
|
|
58
61
|
export type { Agent, Client, ContentBlock, SessionNotification, RequestPermissionRequest, RequestPermissionResponse, BlockKind, VerboseConfig, BlockRendererOptions, PluginCapabilities, PluginManifest, PluginInitMeta, } from "./types.js";
|
|
59
62
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AAGH,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAC7F,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAG5E,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AAGH,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAC7F,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAG5E,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAGlD,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACnD,YAAY,EACV,UAAU,EACV,mBAAmB,EACnB,oBAAoB,EACpB,gBAAgB,EAChB,oBAAoB,EACpB,cAAc,GACf,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAI9C,YAAY,EAEV,KAAK,EACL,MAAM,EACN,YAAY,EACZ,mBAAmB,EACnB,wBAAwB,EACxB,yBAAyB,EAEzB,SAAS,EACT,aAAa,EACb,oBAAoB,EACpB,kBAAkB,EAClB,cAAc,EACd,cAAc,GACf,MAAM,YAAY,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -54,6 +54,10 @@
|
|
|
54
54
|
*/
|
|
55
55
|
// Connection helpers
|
|
56
56
|
export { connectToHost, normalizeExtMethod, redirectConsoleToStderr } from "./connection.js";
|
|
57
|
+
// Error normalization
|
|
58
|
+
export { extractErrorMessage } from "./errors.js";
|
|
59
|
+
// Plugin runner (absorbs the main.ts boilerplate)
|
|
60
|
+
export { runChannelPlugin } from "./run-plugin.js";
|
|
57
61
|
// Block renderer
|
|
58
62
|
export { BlockRenderer } from "./renderer.js";
|
|
59
63
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AAEH,qBAAqB;AACrB,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAG7F,iBAAiB;AACjB,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AAEH,qBAAqB;AACrB,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAG7F,sBAAsB;AACtB,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAElD,kDAAkD;AAClD,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAUnD,iBAAiB;AACjB,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC"}
|
package/dist/renderer.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../src/renderer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AACpE,OAAO,KAAK,EAAE,SAAS,EAAE,oBAAoB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../src/renderer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AACpE,OAAO,KAAK,EAAE,SAAS,EAAE,oBAAoB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AA6EjF;;;;;;GAMG;AACH,8BAAsB,aAAa,CAAC,IAAI,GAAG,MAAM;IAC/C,SAAS,CAAC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IAC3C,SAAS,CAAC,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IAC7C,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC;IAE1C,OAAO,CAAC,MAAM,CAAyC;gBAE3C,OAAO,GAAE,oBAAyB;IAa9C;;;;;OAKG;IACH,SAAS,CAAC,QAAQ,CAAC,SAAS,CAC1B,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,SAAS,EACf,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;IAEvB;;;;;;;;OAQG;IACH,SAAS,CAAC,SAAS,CAAC,CAClB,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,IAAI,EACT,IAAI,EAAE,SAAS,EACf,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,OAAO,GACd,OAAO,CAAC,IAAI,CAAC;IAEhB;;;;;;;;;OASG;IACH,SAAS,CAAC,aAAa,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,MAAM;IAQnF;;;;OAIG;IACH,SAAS,CAAC,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI3D;;;OAGG;IACH,SAAS,CAAC,gBAAgB,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI7E;;;;;;;OAOG;IACH,SAAS,CAAC,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM;IAQzD;;;;;;;OAOG;IACH,eAAe,CAAC,YAAY,EAAE,mBAAmB,GAAG,IAAI;IAgDxD;;;;;OAKG;IACH,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAWrC;;;;;OAKG;IACG,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAqBjD;;;;;OAKG;IACG,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAWlE,OAAO,CAAC,aAAa;IA8BrB,OAAO,CAAC,aAAa;IASrB,OAAO,CAAC,KAAK;IA+Bb,OAAO,CAAC,YAAY;YAMN,UAAU;CAqBzB"}
|
package/dist/renderer.js
CHANGED
|
@@ -136,13 +136,23 @@ export class BlockRenderer {
|
|
|
136
136
|
*/
|
|
137
137
|
onSessionUpdate(notification) {
|
|
138
138
|
const sessionId = notification.sessionId;
|
|
139
|
-
//
|
|
140
|
-
|
|
141
|
-
|
|
139
|
+
// Narrow through the local ConsumedSessionUpdate union — see the type
|
|
140
|
+
// declaration above this class for why we re-declare it locally instead
|
|
141
|
+
// of importing the SDK's own union. Variants other than the four we
|
|
142
|
+
// handle are treated as no-ops.
|
|
143
|
+
const rawUpdate = notification.update;
|
|
144
|
+
const variant = rawUpdate.sessionUpdate;
|
|
145
|
+
if (variant !== "agent_message_chunk" &&
|
|
146
|
+
variant !== "agent_thought_chunk" &&
|
|
147
|
+
variant !== "tool_call" &&
|
|
148
|
+
variant !== "tool_call_update") {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
const update = rawUpdate;
|
|
142
152
|
const channelId = this.sessionIdToChannelId(sessionId);
|
|
143
|
-
switch (
|
|
153
|
+
switch (update.sessionUpdate) {
|
|
144
154
|
case "agent_message_chunk": {
|
|
145
|
-
const delta =
|
|
155
|
+
const delta = update.content?.text ?? "";
|
|
146
156
|
if (delta)
|
|
147
157
|
this.appendToBlock(channelId, "text", delta);
|
|
148
158
|
break;
|
|
@@ -150,7 +160,7 @@ export class BlockRenderer {
|
|
|
150
160
|
case "agent_thought_chunk": {
|
|
151
161
|
if (!this.verbose.showThinking)
|
|
152
162
|
return; // skip — no block, no boundary
|
|
153
|
-
const delta =
|
|
163
|
+
const delta = update.content?.text ?? "";
|
|
154
164
|
if (delta)
|
|
155
165
|
this.appendToBlock(channelId, "thinking", delta);
|
|
156
166
|
break;
|
|
@@ -158,21 +168,20 @@ export class BlockRenderer {
|
|
|
158
168
|
case "tool_call": {
|
|
159
169
|
if (!this.verbose.showToolUse)
|
|
160
170
|
return; // skip
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
this.appendToBlock(channelId, "tool", `🔧 ${title}\n`);
|
|
171
|
+
if (update.title)
|
|
172
|
+
this.appendToBlock(channelId, "tool", `🔧 ${update.title}\n`);
|
|
164
173
|
break;
|
|
165
174
|
}
|
|
166
175
|
case "tool_call_update": {
|
|
167
176
|
if (!this.verbose.showToolUse)
|
|
168
177
|
return; // skip
|
|
169
|
-
const title =
|
|
170
|
-
|
|
171
|
-
if (status === "completed" || status === "error") {
|
|
178
|
+
const title = update.title ?? "tool";
|
|
179
|
+
if (update.status === "completed" || update.status === "error") {
|
|
172
180
|
this.appendToBlock(channelId, "tool", `✅ ${title}\n`);
|
|
173
181
|
}
|
|
174
182
|
break;
|
|
175
183
|
}
|
|
184
|
+
// Unknown / unconsumed variant — ignore.
|
|
176
185
|
}
|
|
177
186
|
}
|
|
178
187
|
/**
|
|
@@ -271,6 +280,16 @@ export class BlockRenderer {
|
|
|
271
280
|
const block = state.blocks.at(-1);
|
|
272
281
|
if (!block || block.sealed || !block.content)
|
|
273
282
|
return;
|
|
283
|
+
// Send-only mode: subclasses that don't override `editBlock` (e.g.
|
|
284
|
+
// QQ Bot, where the platform has no edit support) would otherwise
|
|
285
|
+
// POST a new message for every debounced flush, so the user sees a
|
|
286
|
+
// partial chunk followed by the full message as two separate
|
|
287
|
+
// deliveries. Defer intermediate sends; only `onTurnEnd` and block
|
|
288
|
+
// boundary transitions inside `appendToBlock` (which seal the block
|
|
289
|
+
// first) will actually POST.
|
|
290
|
+
if (!this.editBlock) {
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
274
293
|
const now = Date.now();
|
|
275
294
|
if (now - state.lastEditMs < this.minEditIntervalMs) {
|
|
276
295
|
// Throttled — reschedule for the remaining window
|
package/dist/renderer.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"renderer.js","sourceRoot":"","sources":["../src/renderer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;
|
|
1
|
+
{"version":3,"file":"renderer.js","sourceRoot":"","sources":["../src/renderer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AAqEH,8EAA8E;AAC9E,YAAY;AACZ,8EAA8E;AAE9E,MAAM,yBAAyB,GAAG,GAAG,CAAC;AACtC,MAAM,4BAA4B,GAAG,IAAI,CAAC;AAE1C,8EAA8E;AAC9E,gBAAgB;AAChB,8EAA8E;AAE9E;;;;;;GAMG;AACH,MAAM,OAAgB,aAAa;IACd,eAAe,CAAS;IACxB,iBAAiB,CAAS;IAC1B,OAAO,CAAgB;IAElC,MAAM,GAAG,IAAI,GAAG,EAA8B,CAAC;IAEvD,YAAY,UAAgC,EAAE;QAC5C,IAAI,CAAC,eAAe,GAAG,OAAO,CAAC,eAAe,IAAI,yBAAyB,CAAC;QAC5E,IAAI,CAAC,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,IAAI,4BAA4B,CAAC;QACnF,IAAI,CAAC,OAAO,GAAG;YACb,YAAY,EAAE,OAAO,CAAC,OAAO,EAAE,YAAY,IAAI,KAAK;YACpD,WAAW,EAAE,OAAO,CAAC,OAAO,EAAE,WAAW,IAAI,KAAK;SACnD,CAAC;IACJ,CAAC;IAmCD;;;;;;;;;OASG;IACO,aAAa,CAAC,IAAe,EAAE,OAAe,EAAE,OAAgB;QACxE,QAAQ,IAAI,EAAE,CAAC;YACb,KAAK,UAAU,CAAC,CAAC,OAAO,MAAM,OAAO,EAAE,CAAC;YACxC,KAAK,MAAM,CAAC,CAAK,OAAO,OAAO,CAAC,IAAI,EAAE,CAAC;YACvC,KAAK,MAAM,CAAC,CAAK,OAAO,OAAO,CAAC;QAClC,CAAC;IACH,CAAC;IAED;;;;OAIG;IACO,cAAc,CAAC,UAAkB;QACzC,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;IAC3B,CAAC;IAED;;;OAGG;IACO,gBAAgB,CAAC,UAAkB,EAAE,MAAc;QAC3D,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;IAC3B,CAAC;IAED;;;;;;;OAOG;IACO,oBAAoB,CAAC,SAAiB;QAC9C,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,8EAA8E;IAC9E,aAAa;IACb,8EAA8E;IAE9E;;;;;;;OAOG;IACH,eAAe,CAAC,YAAiC;QAC/C,MAAM,SAAS,GAAG,YAAY,CAAC,SAAS,CAAC;QACzC,sEAAsE;QACtE,wEAAwE;QACxE,oEAAoE;QACpE,gCAAgC;QAChC,MAAM,SAAS,GAAG,YAAY,CAAC,MAA8C,CAAC;QAC9E,MAAM,OAAO,GAAG,SAAS,CAAC,aAAa,CAAC;QACxC,IACE,OAAO,KAAK,qBAAqB;YACjC,OAAO,KAAK,qBAAqB;YACjC,OAAO,KAAK,WAAW;YACvB,OAAO,KAAK,kBAAkB,EAC9B,CAAC;YACD,OAAO;QACT,CAAC;QACD,MAAM,MAAM,GAAG,SAAkC,CAAC;QAClD,MAAM,SAAS,GAAG,IAAI,CAAC,oBAAoB,CAAC,SAAS,CAAC,CAAC;QAEvD,QAAQ,MAAM,CAAC,aAAa,EAAE,CAAC;YAC7B,KAAK,qBAAqB,CAAC,CAAC,CAAC;gBAC3B,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,EAAE,IAAI,IAAI,EAAE,CAAC;gBACzC,IAAI,KAAK;oBAAE,IAAI,CAAC,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;gBACxD,MAAM;YACR,CAAC;YACD,KAAK,qBAAqB,CAAC,CAAC,CAAC;gBAC3B,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,YAAY;oBAAE,OAAO,CAAC,+BAA+B;gBACvE,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,EAAE,IAAI,IAAI,EAAE,CAAC;gBACzC,IAAI,KAAK;oBAAE,IAAI,CAAC,aAAa,CAAC,SAAS,EAAE,UAAU,EAAE,KAAK,CAAC,CAAC;gBAC5D,MAAM;YACR,CAAC;YACD,KAAK,WAAW,CAAC,CAAC,CAAC;gBACjB,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,WAAW;oBAAE,OAAO,CAAC,OAAO;gBAC9C,IAAI,MAAM,CAAC,KAAK;oBAAE,IAAI,CAAC,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC,KAAK,IAAI,CAAC,CAAC;gBAChF,MAAM;YACR,CAAC;YACD,KAAK,kBAAkB,CAAC,CAAC,CAAC;gBACxB,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,WAAW;oBAAE,OAAO,CAAC,OAAO;gBAC9C,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC;gBACrC,IAAI,MAAM,CAAC,MAAM,KAAK,WAAW,IAAI,MAAM,CAAC,MAAM,KAAK,OAAO,EAAE,CAAC;oBAC/D,IAAI,CAAC,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,KAAK,IAAI,CAAC,CAAC;gBACxD,CAAC;gBACD,MAAM;YACR,CAAC;YACD,yCAAyC;QAC3C,CAAC;IACH,CAAC;IAED;;;;;OAKG;IACH,YAAY,CAAC,SAAiB;QAC5B,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACvC,IAAI,GAAG,EAAE,UAAU;YAAE,YAAY,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAClD,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,SAAS,EAAE;YACzB,MAAM,EAAE,EAAE;YACV,UAAU,EAAE,IAAI;YAChB,UAAU,EAAE,CAAC;YACb,SAAS,EAAE,OAAO,CAAC,OAAO,EAAE;SAC7B,CAAC,CAAC;IACL,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,SAAS,CAAC,SAAiB;QAC/B,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACzC,IAAI,CAAC,KAAK;YAAE,OAAO;QAEnB,IAAI,KAAK,CAAC,UAAU,EAAE,CAAC;YACrB,YAAY,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;YAC/B,KAAK,CAAC,UAAU,GAAG,IAAI,CAAC;QAC1B,CAAC;QAED,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QACjC,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;YACzB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;YACnB,IAAI,CAAC,YAAY,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QACjC,CAAC;QAED,oDAAoD;QACpD,MAAM,KAAK,CAAC,SAAS,CAAC;QACtB,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QAC9B,MAAM,IAAI,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC;IACvC,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,WAAW,CAAC,SAAiB,EAAE,KAAa;QAChD,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACzC,IAAI,KAAK,EAAE,UAAU;YAAE,YAAY,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;QACtD,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QAC9B,MAAM,IAAI,CAAC,gBAAgB,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;IAChD,CAAC;IAED,8EAA8E;IAC9E,8BAA8B;IAC9B,8EAA8E;IAEtE,aAAa,CAAC,SAAiB,EAAE,IAAe,EAAE,KAAa;QACrE,IAAI,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACvC,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,8EAA8E;YAC9E,KAAK,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC;YACtF,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;QACpC,CAAC;QAED,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QAEjC,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;YAC/C,yBAAyB;YACzB,IAAI,CAAC,OAAO,IAAI,KAAK,CAAC;QACxB,CAAC;aAAM,CAAC;YACN,wDAAwD;YACxD,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;gBACzB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;gBACnB,+EAA+E;gBAC/E,IAAI,KAAK,CAAC,UAAU,EAAE,CAAC;oBACrB,YAAY,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;oBAC/B,KAAK,CAAC,UAAU,GAAG,IAAI,CAAC;gBAC1B,CAAC;gBACD,IAAI,CAAC,YAAY,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;YACjC,CAAC;YACD,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;QACpG,CAAC;QAED,IAAI,CAAC,aAAa,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;IACvC,CAAC;IAEO,aAAa,CAAC,SAAiB,EAAE,KAAyB;QAChE,IAAI,KAAK,CAAC,UAAU;YAAE,OAAO,CAAC,oBAAoB;QAElD,KAAK,CAAC,UAAU,GAAG,UAAU,CAAC,GAAG,EAAE;YACjC,KAAK,CAAC,UAAU,GAAG,IAAI,CAAC;YACxB,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;QAC/B,CAAC,EAAE,IAAI,CAAC,eAAe,CAAC,CAAC;IAC3B,CAAC;IAEO,KAAK,CAAC,SAAiB,EAAE,KAAyB;QACxD,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QAClC,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO;YAAE,OAAO;QAErD,mEAAmE;QACnE,kEAAkE;QAClE,mEAAmE;QACnE,6DAA6D;QAC7D,mEAAmE;QACnE,oEAAoE;QACpE,6BAA6B;QAC7B,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;YACpB,OAAO;QACT,CAAC;QAED,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,IAAI,GAAG,GAAG,KAAK,CAAC,UAAU,GAAG,IAAI,CAAC,iBAAiB,EAAE,CAAC;YACpD,kDAAkD;YAClD,MAAM,KAAK,GAAG,IAAI,CAAC,iBAAiB,GAAG,CAAC,GAAG,GAAG,KAAK,CAAC,UAAU,CAAC,CAAC;YAChE,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC;gBACtB,KAAK,CAAC,UAAU,GAAG,UAAU,CAAC,GAAG,EAAE;oBACjC,KAAK,CAAC,UAAU,GAAG,IAAI,CAAC;oBACxB,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;gBAC/B,CAAC,EAAE,KAAK,CAAC,CAAC;YACZ,CAAC;YACD,OAAO;QACT,CAAC;QAED,IAAI,CAAC,YAAY,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IAClC,CAAC;IAEO,YAAY,CAAC,KAAyB,EAAE,KAAyB;QACvE,KAAK,CAAC,SAAS,GAAG,KAAK,CAAC,SAAS;aAC9B,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;aACzC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC,CAAC,uCAAuC;IAC7D,CAAC;IAEO,KAAK,CAAC,UAAU,CAAC,KAAyB,EAAE,KAAyB;QAC3E,MAAM,OAAO,GAAG,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;QAC5E,IAAI,CAAC,OAAO;YAAE,OAAO;QAErB,IAAI,CAAC;YACH,IAAI,KAAK,CAAC,GAAG,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC;gBAC1C,0DAA0D;gBAC1D,KAAK,CAAC,QAAQ,GAAG,IAAI,CAAC;gBACtB,KAAK,CAAC,GAAG,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;gBACvE,KAAK,CAAC,QAAQ,GAAG,KAAK,CAAC;gBACvB,KAAK,CAAC,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YAChC,CAAC;iBAAM,IAAI,KAAK,CAAC,GAAG,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,QAAQ,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;gBACnE,oCAAoC;gBACpC,MAAM,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;gBACpF,KAAK,CAAC,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YAChC,CAAC;YACD,uDAAuD;QACzD,CAAC;QAAC,MAAM,CAAC;YACP,KAAK,CAAC,QAAQ,GAAG,KAAK,CAAC;QACzB,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* runChannelPlugin — shared main.ts boilerplate for every channel plugin.
|
|
3
|
+
*
|
|
4
|
+
* Each channel plugin used to have a ~120-line `main.ts` that was 85%
|
|
5
|
+
* identical across Slack, Telegram, Discord, DingTalk, WeCom, etc:
|
|
6
|
+
* connect to host, validate config keys, wire the standard sessionUpdate
|
|
7
|
+
* and extNotification handlers, create the stream handler, start the bot,
|
|
8
|
+
* wait for disconnect, stop. This helper absorbs that boilerplate so each
|
|
9
|
+
* plugin's `main.ts` reduces to ~20 lines — a factory for the bot and a
|
|
10
|
+
* factory for the stream handler.
|
|
11
|
+
*
|
|
12
|
+
* ## Usage
|
|
13
|
+
*
|
|
14
|
+
* ```ts
|
|
15
|
+
* import { runChannelPlugin } from "@vibearound/plugin-channel-sdk";
|
|
16
|
+
* import { SlackBot } from "./bot.js";
|
|
17
|
+
* import { AgentStreamHandler } from "./agent-stream.js";
|
|
18
|
+
*
|
|
19
|
+
* runChannelPlugin({
|
|
20
|
+
* name: "vibearound-slack",
|
|
21
|
+
* version: "0.1.0",
|
|
22
|
+
* requiredConfig: ["bot_token", "app_token"],
|
|
23
|
+
* createBot: ({ config, agent, log, cacheDir }) =>
|
|
24
|
+
* new SlackBot(
|
|
25
|
+
* { bot_token: config.bot_token as string, app_token: config.app_token as string },
|
|
26
|
+
* agent,
|
|
27
|
+
* log,
|
|
28
|
+
* cacheDir,
|
|
29
|
+
* ),
|
|
30
|
+
* createStreamHandler: (bot, log, verbose) =>
|
|
31
|
+
* new AgentStreamHandler(bot, log, verbose),
|
|
32
|
+
* });
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
import type { Agent } from "@agentclientprotocol/sdk";
|
|
36
|
+
import type { SessionNotification } from "./types.js";
|
|
37
|
+
export type ChannelPluginLogger = (level: string, msg: string) => void;
|
|
38
|
+
export interface ChannelStreamHandler {
|
|
39
|
+
onSessionUpdate(params: SessionNotification): void;
|
|
40
|
+
onSystemText(text: string): void;
|
|
41
|
+
onAgentReady(agent: string, version: string): void;
|
|
42
|
+
onSessionReady(sessionId: string): void;
|
|
43
|
+
}
|
|
44
|
+
export interface ChannelBot<THandler extends ChannelStreamHandler = ChannelStreamHandler> {
|
|
45
|
+
setStreamHandler(handler: THandler): void;
|
|
46
|
+
start(): Promise<void> | void;
|
|
47
|
+
stop(): Promise<void> | void;
|
|
48
|
+
}
|
|
49
|
+
export interface CreateBotContext {
|
|
50
|
+
config: Record<string, unknown>;
|
|
51
|
+
agent: Agent;
|
|
52
|
+
log: ChannelPluginLogger;
|
|
53
|
+
cacheDir: string;
|
|
54
|
+
}
|
|
55
|
+
export interface VerboseOptions {
|
|
56
|
+
showThinking: boolean;
|
|
57
|
+
showToolUse: boolean;
|
|
58
|
+
}
|
|
59
|
+
export interface RunChannelPluginSpec<TBot extends ChannelBot<THandler>, THandler extends ChannelStreamHandler> {
|
|
60
|
+
/** Plugin name reported during ACP initialize (e.g. "vibearound-slack"). */
|
|
61
|
+
name: string;
|
|
62
|
+
/** Plugin version reported during ACP initialize. */
|
|
63
|
+
version: string;
|
|
64
|
+
/**
|
|
65
|
+
* Config keys that MUST be present on `meta.config`. Plugin startup
|
|
66
|
+
* fails with a clear error if any are missing. Keep to primitives
|
|
67
|
+
* (strings/booleans); deeper validation belongs in the bot constructor.
|
|
68
|
+
*/
|
|
69
|
+
requiredConfig?: string[];
|
|
70
|
+
/** Factory: build the platform bot from host-supplied config + agent. */
|
|
71
|
+
createBot: (ctx: CreateBotContext) => TBot | Promise<TBot>;
|
|
72
|
+
/**
|
|
73
|
+
* Factory: build the agent stream handler for this plugin. The handler
|
|
74
|
+
* is wired to the bot via `bot.setStreamHandler(handler)` before the
|
|
75
|
+
* bot is started.
|
|
76
|
+
*/
|
|
77
|
+
createStreamHandler: (bot: TBot, log: ChannelPluginLogger, verbose: VerboseOptions) => THandler;
|
|
78
|
+
/**
|
|
79
|
+
* Optional hook invoked after the bot has been constructed but before
|
|
80
|
+
* `start()` is called. Use this for one-off initialization that needs
|
|
81
|
+
* to log diagnostic info (e.g. Telegram's `probe()`).
|
|
82
|
+
*/
|
|
83
|
+
afterCreate?: (bot: TBot, log: ChannelPluginLogger) => Promise<void> | void;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Run a channel plugin to completion.
|
|
87
|
+
*
|
|
88
|
+
* Performs the ACP initialize handshake, validates required config,
|
|
89
|
+
* constructs the bot + stream handler, starts the bot, waits for the host
|
|
90
|
+
* connection to close, then stops the bot and exits the process.
|
|
91
|
+
*
|
|
92
|
+
* Never returns under normal operation — the process exits at the end.
|
|
93
|
+
*/
|
|
94
|
+
export declare function runChannelPlugin<TBot extends ChannelBot<THandler>, THandler extends ChannelStreamHandler>(spec: RunChannelPluginSpec<TBot, THandler>): Promise<void>;
|
|
95
|
+
//# sourceMappingURL=run-plugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-plugin.d.ts","sourceRoot":"","sources":["../src/run-plugin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAIH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAC;AAItD,OAAO,KAAK,EAGV,mBAAmB,EACpB,MAAM,YAAY,CAAC;AAMpB,MAAM,MAAM,mBAAmB,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;AAEvE,MAAM,WAAW,oBAAoB;IACnC,eAAe,CAAC,MAAM,EAAE,mBAAmB,GAAG,IAAI,CAAC;IACnD,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACnD,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CACzC;AAED,MAAM,WAAW,UAAU,CAAC,QAAQ,SAAS,oBAAoB,GAAG,oBAAoB;IACtF,gBAAgB,CAAC,OAAO,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC1C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC9B,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CAC9B;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,KAAK,EAAE,KAAK,CAAC;IACb,GAAG,EAAE,mBAAmB,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,YAAY,EAAE,OAAO,CAAC;IACtB,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,oBAAoB,CACnC,IAAI,SAAS,UAAU,CAAC,QAAQ,CAAC,EACjC,QAAQ,SAAS,oBAAoB;IAErC,4EAA4E;IAC5E,IAAI,EAAE,MAAM,CAAC;IAEb,qDAAqD;IACrD,OAAO,EAAE,MAAM,CAAC;IAEhB;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAE1B,yEAAyE;IACzE,SAAS,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE3D;;;;OAIG;IACH,mBAAmB,EAAE,CACnB,GAAG,EAAE,IAAI,EACT,GAAG,EAAE,mBAAmB,EACxB,OAAO,EAAE,cAAc,KACpB,QAAQ,CAAC;IAEd;;;;OAIG;IACH,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,mBAAmB,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CAC7E;AAMD;;;;;;;;GAQG;AACH,wBAAsB,gBAAgB,CACpC,IAAI,SAAS,UAAU,CAAC,QAAQ,CAAC,EACjC,QAAQ,SAAS,oBAAoB,EACrC,IAAI,EAAE,oBAAoB,CAAC,IAAI,EAAE,QAAQ,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAY3D"}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* runChannelPlugin — shared main.ts boilerplate for every channel plugin.
|
|
3
|
+
*
|
|
4
|
+
* Each channel plugin used to have a ~120-line `main.ts` that was 85%
|
|
5
|
+
* identical across Slack, Telegram, Discord, DingTalk, WeCom, etc:
|
|
6
|
+
* connect to host, validate config keys, wire the standard sessionUpdate
|
|
7
|
+
* and extNotification handlers, create the stream handler, start the bot,
|
|
8
|
+
* wait for disconnect, stop. This helper absorbs that boilerplate so each
|
|
9
|
+
* plugin's `main.ts` reduces to ~20 lines — a factory for the bot and a
|
|
10
|
+
* factory for the stream handler.
|
|
11
|
+
*
|
|
12
|
+
* ## Usage
|
|
13
|
+
*
|
|
14
|
+
* ```ts
|
|
15
|
+
* import { runChannelPlugin } from "@vibearound/plugin-channel-sdk";
|
|
16
|
+
* import { SlackBot } from "./bot.js";
|
|
17
|
+
* import { AgentStreamHandler } from "./agent-stream.js";
|
|
18
|
+
*
|
|
19
|
+
* runChannelPlugin({
|
|
20
|
+
* name: "vibearound-slack",
|
|
21
|
+
* version: "0.1.0",
|
|
22
|
+
* requiredConfig: ["bot_token", "app_token"],
|
|
23
|
+
* createBot: ({ config, agent, log, cacheDir }) =>
|
|
24
|
+
* new SlackBot(
|
|
25
|
+
* { bot_token: config.bot_token as string, app_token: config.app_token as string },
|
|
26
|
+
* agent,
|
|
27
|
+
* log,
|
|
28
|
+
* cacheDir,
|
|
29
|
+
* ),
|
|
30
|
+
* createStreamHandler: (bot, log, verbose) =>
|
|
31
|
+
* new AgentStreamHandler(bot, log, verbose),
|
|
32
|
+
* });
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
import os from "node:os";
|
|
36
|
+
import path from "node:path";
|
|
37
|
+
import { connectToHost, normalizeExtMethod } from "./connection.js";
|
|
38
|
+
import { extractErrorMessage } from "./errors.js";
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
// Implementation
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
/**
|
|
43
|
+
* Run a channel plugin to completion.
|
|
44
|
+
*
|
|
45
|
+
* Performs the ACP initialize handshake, validates required config,
|
|
46
|
+
* constructs the bot + stream handler, starts the bot, waits for the host
|
|
47
|
+
* connection to close, then stops the bot and exits the process.
|
|
48
|
+
*
|
|
49
|
+
* Never returns under normal operation — the process exits at the end.
|
|
50
|
+
*/
|
|
51
|
+
export async function runChannelPlugin(spec) {
|
|
52
|
+
const prefix = `[${spec.name.replace(/^vibearound-/, "")}-plugin]`;
|
|
53
|
+
const log = (level, msg) => {
|
|
54
|
+
process.stderr.write(`${prefix}[${level}] ${msg}\n`);
|
|
55
|
+
};
|
|
56
|
+
try {
|
|
57
|
+
await runInner(spec, log);
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
log("error", `fatal: ${extractErrorMessage(err)}`);
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
async function runInner(spec, log) {
|
|
65
|
+
log("info", "initializing ACP connection...");
|
|
66
|
+
let streamHandler = null;
|
|
67
|
+
const { agent, meta, agentInfo, conn } = await connectToHost({ name: spec.name, version: spec.version }, () => ({
|
|
68
|
+
async sessionUpdate(params) {
|
|
69
|
+
streamHandler?.onSessionUpdate(params);
|
|
70
|
+
},
|
|
71
|
+
async requestPermission(params) {
|
|
72
|
+
const first = params.options?.[0];
|
|
73
|
+
if (first) {
|
|
74
|
+
return { outcome: { outcome: "selected", optionId: first.optionId } };
|
|
75
|
+
}
|
|
76
|
+
throw new Error("No permission options provided");
|
|
77
|
+
},
|
|
78
|
+
async extNotification(method, params) {
|
|
79
|
+
switch (normalizeExtMethod(method)) {
|
|
80
|
+
case "channel/system_text": {
|
|
81
|
+
const text = params.text;
|
|
82
|
+
streamHandler?.onSystemText(text);
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
case "channel/agent_ready": {
|
|
86
|
+
const agentName = params.agent;
|
|
87
|
+
const version = params.version;
|
|
88
|
+
log("info", `agent_ready: ${agentName} v${version}`);
|
|
89
|
+
streamHandler?.onAgentReady(agentName, version);
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
case "channel/session_ready": {
|
|
93
|
+
const sessionId = params.sessionId;
|
|
94
|
+
log("info", `session_ready: ${sessionId}`);
|
|
95
|
+
streamHandler?.onSessionReady(sessionId);
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
default:
|
|
99
|
+
log("warn", `unhandled ext_notification: ${method}`);
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
}));
|
|
103
|
+
const config = meta.config;
|
|
104
|
+
// Validate required config keys up front so a misconfigured plugin fails
|
|
105
|
+
// with a clear error instead of some downstream "undefined is not a
|
|
106
|
+
// string" crash in the bot constructor.
|
|
107
|
+
for (const key of spec.requiredConfig ?? []) {
|
|
108
|
+
if (config[key] === undefined || config[key] === null || config[key] === "") {
|
|
109
|
+
throw new Error(`${key} is required in config`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
const cacheDir = meta.cacheDir ?? path.join(os.homedir(), ".vibearound", ".cache");
|
|
113
|
+
log("info", `initialized, host=${agentInfo.name ?? "unknown"} cacheDir=${cacheDir}`);
|
|
114
|
+
const bot = await spec.createBot({ config, agent, log, cacheDir });
|
|
115
|
+
if (spec.afterCreate) {
|
|
116
|
+
await spec.afterCreate(bot, log);
|
|
117
|
+
}
|
|
118
|
+
const verboseRaw = config.verbose;
|
|
119
|
+
const verbose = {
|
|
120
|
+
showThinking: verboseRaw?.show_thinking ?? false,
|
|
121
|
+
showToolUse: verboseRaw?.show_tool_use ?? false,
|
|
122
|
+
};
|
|
123
|
+
streamHandler = spec.createStreamHandler(bot, log, verbose);
|
|
124
|
+
bot.setStreamHandler(streamHandler);
|
|
125
|
+
await bot.start();
|
|
126
|
+
log("info", "plugin started");
|
|
127
|
+
await conn.closed;
|
|
128
|
+
log("info", "connection closed, shutting down");
|
|
129
|
+
await bot.stop();
|
|
130
|
+
process.exit(0);
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=run-plugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-plugin.js","sourceRoot":"","sources":["../src/run-plugin.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/errors.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error normalization for channel plugins.
|
|
3
|
+
*
|
|
4
|
+
* Every plugin had an ad-hoc ladder that tried `instanceof Error`, then
|
|
5
|
+
* `typeof error === "object"`, then fell back to `String(error)`. That
|
|
6
|
+
* ladder lived in five slightly-different forms across bot.ts files and
|
|
7
|
+
* drifted over time. Centralize it here.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Extract a human-readable message from an unknown thrown value.
|
|
12
|
+
*
|
|
13
|
+
* Prefers `Error.message`, falls back to a non-circular JSON stringify for
|
|
14
|
+
* objects, and finally to `String(e)` for primitives.
|
|
15
|
+
*/
|
|
16
|
+
export function extractErrorMessage(e: unknown): string {
|
|
17
|
+
if (e instanceof Error) return e.message;
|
|
18
|
+
if (typeof e === "string") return e;
|
|
19
|
+
if (e && typeof e === "object") {
|
|
20
|
+
const msg = (e as { message?: unknown }).message;
|
|
21
|
+
if (typeof msg === "string") return msg;
|
|
22
|
+
try {
|
|
23
|
+
return JSON.stringify(e);
|
|
24
|
+
} catch {
|
|
25
|
+
return String(e);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return String(e);
|
|
29
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -57,9 +57,24 @@
|
|
|
57
57
|
export { connectToHost, normalizeExtMethod, redirectConsoleToStderr } from "./connection.js";
|
|
58
58
|
export type { PluginInfo, ConnectResult, AgentInfo } from "./connection.js";
|
|
59
59
|
|
|
60
|
+
// Error normalization
|
|
61
|
+
export { extractErrorMessage } from "./errors.js";
|
|
62
|
+
|
|
63
|
+
// Plugin runner (absorbs the main.ts boilerplate)
|
|
64
|
+
export { runChannelPlugin } from "./run-plugin.js";
|
|
65
|
+
export type {
|
|
66
|
+
ChannelBot,
|
|
67
|
+
ChannelPluginLogger,
|
|
68
|
+
ChannelStreamHandler,
|
|
69
|
+
CreateBotContext,
|
|
70
|
+
RunChannelPluginSpec,
|
|
71
|
+
VerboseOptions,
|
|
72
|
+
} from "./run-plugin.js";
|
|
73
|
+
|
|
60
74
|
// Block renderer
|
|
61
75
|
export { BlockRenderer } from "./renderer.js";
|
|
62
76
|
|
|
77
|
+
|
|
63
78
|
// Types (re-exports ACP SDK types + SDK-specific types)
|
|
64
79
|
export type {
|
|
65
80
|
// ACP SDK
|
package/src/renderer.ts
CHANGED
|
@@ -56,6 +56,44 @@
|
|
|
56
56
|
import type { SessionNotification } from "@agentclientprotocol/sdk";
|
|
57
57
|
import type { BlockKind, BlockRendererOptions, VerboseConfig } from "./types.js";
|
|
58
58
|
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
// Local ACP session-update narrowing
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
//
|
|
63
|
+
// The ACP SDK's `SessionNotification.update` is a discriminated union keyed on
|
|
64
|
+
// the `sessionUpdate` field, but the shape we get back at runtime varies by
|
|
65
|
+
// version. We only consume four variants here, so we define a narrow local
|
|
66
|
+
// view that documents exactly the fields this renderer depends on. A mismatch
|
|
67
|
+
// against the upstream type will show up as a compile error when the SDK is
|
|
68
|
+
// bumped, instead of silently producing `undefined` at runtime.
|
|
69
|
+
|
|
70
|
+
interface AgentMessageChunk {
|
|
71
|
+
sessionUpdate: "agent_message_chunk";
|
|
72
|
+
content?: { text?: string };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
interface AgentThoughtChunk {
|
|
76
|
+
sessionUpdate: "agent_thought_chunk";
|
|
77
|
+
content?: { text?: string };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
interface ToolCall {
|
|
81
|
+
sessionUpdate: "tool_call";
|
|
82
|
+
title?: string;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
interface ToolCallUpdate {
|
|
86
|
+
sessionUpdate: "tool_call_update";
|
|
87
|
+
title?: string;
|
|
88
|
+
status?: string;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
type ConsumedSessionUpdate =
|
|
92
|
+
| AgentMessageChunk
|
|
93
|
+
| AgentThoughtChunk
|
|
94
|
+
| ToolCall
|
|
95
|
+
| ToolCallUpdate;
|
|
96
|
+
|
|
59
97
|
// ---------------------------------------------------------------------------
|
|
60
98
|
// Internal state types
|
|
61
99
|
// ---------------------------------------------------------------------------
|
|
@@ -210,38 +248,49 @@ export abstract class BlockRenderer<TRef = string> {
|
|
|
210
248
|
*/
|
|
211
249
|
onSessionUpdate(notification: SessionNotification): void {
|
|
212
250
|
const sessionId = notification.sessionId;
|
|
213
|
-
//
|
|
214
|
-
|
|
215
|
-
|
|
251
|
+
// Narrow through the local ConsumedSessionUpdate union — see the type
|
|
252
|
+
// declaration above this class for why we re-declare it locally instead
|
|
253
|
+
// of importing the SDK's own union. Variants other than the four we
|
|
254
|
+
// handle are treated as no-ops.
|
|
255
|
+
const rawUpdate = notification.update as unknown as { sessionUpdate: string };
|
|
256
|
+
const variant = rawUpdate.sessionUpdate;
|
|
257
|
+
if (
|
|
258
|
+
variant !== "agent_message_chunk" &&
|
|
259
|
+
variant !== "agent_thought_chunk" &&
|
|
260
|
+
variant !== "tool_call" &&
|
|
261
|
+
variant !== "tool_call_update"
|
|
262
|
+
) {
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
const update = rawUpdate as ConsumedSessionUpdate;
|
|
216
266
|
const channelId = this.sessionIdToChannelId(sessionId);
|
|
217
267
|
|
|
218
|
-
switch (
|
|
268
|
+
switch (update.sessionUpdate) {
|
|
219
269
|
case "agent_message_chunk": {
|
|
220
|
-
const delta =
|
|
270
|
+
const delta = update.content?.text ?? "";
|
|
221
271
|
if (delta) this.appendToBlock(channelId, "text", delta);
|
|
222
272
|
break;
|
|
223
273
|
}
|
|
224
274
|
case "agent_thought_chunk": {
|
|
225
275
|
if (!this.verbose.showThinking) return; // skip — no block, no boundary
|
|
226
|
-
const delta =
|
|
276
|
+
const delta = update.content?.text ?? "";
|
|
227
277
|
if (delta) this.appendToBlock(channelId, "thinking", delta);
|
|
228
278
|
break;
|
|
229
279
|
}
|
|
230
280
|
case "tool_call": {
|
|
231
281
|
if (!this.verbose.showToolUse) return; // skip
|
|
232
|
-
|
|
233
|
-
if (title) this.appendToBlock(channelId, "tool", `🔧 ${title}\n`);
|
|
282
|
+
if (update.title) this.appendToBlock(channelId, "tool", `🔧 ${update.title}\n`);
|
|
234
283
|
break;
|
|
235
284
|
}
|
|
236
285
|
case "tool_call_update": {
|
|
237
286
|
if (!this.verbose.showToolUse) return; // skip
|
|
238
|
-
const title =
|
|
239
|
-
|
|
240
|
-
if (status === "completed" || status === "error") {
|
|
287
|
+
const title = update.title ?? "tool";
|
|
288
|
+
if (update.status === "completed" || update.status === "error") {
|
|
241
289
|
this.appendToBlock(channelId, "tool", `✅ ${title}\n`);
|
|
242
290
|
}
|
|
243
291
|
break;
|
|
244
292
|
}
|
|
293
|
+
// Unknown / unconsumed variant — ignore.
|
|
245
294
|
}
|
|
246
295
|
}
|
|
247
296
|
|
|
@@ -349,6 +398,17 @@ export abstract class BlockRenderer<TRef = string> {
|
|
|
349
398
|
const block = state.blocks.at(-1);
|
|
350
399
|
if (!block || block.sealed || !block.content) return;
|
|
351
400
|
|
|
401
|
+
// Send-only mode: subclasses that don't override `editBlock` (e.g.
|
|
402
|
+
// QQ Bot, where the platform has no edit support) would otherwise
|
|
403
|
+
// POST a new message for every debounced flush, so the user sees a
|
|
404
|
+
// partial chunk followed by the full message as two separate
|
|
405
|
+
// deliveries. Defer intermediate sends; only `onTurnEnd` and block
|
|
406
|
+
// boundary transitions inside `appendToBlock` (which seal the block
|
|
407
|
+
// first) will actually POST.
|
|
408
|
+
if (!this.editBlock) {
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
|
|
352
412
|
const now = Date.now();
|
|
353
413
|
if (now - state.lastEditMs < this.minEditIntervalMs) {
|
|
354
414
|
// Throttled — reschedule for the remaining window
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* runChannelPlugin — shared main.ts boilerplate for every channel plugin.
|
|
3
|
+
*
|
|
4
|
+
* Each channel plugin used to have a ~120-line `main.ts` that was 85%
|
|
5
|
+
* identical across Slack, Telegram, Discord, DingTalk, WeCom, etc:
|
|
6
|
+
* connect to host, validate config keys, wire the standard sessionUpdate
|
|
7
|
+
* and extNotification handlers, create the stream handler, start the bot,
|
|
8
|
+
* wait for disconnect, stop. This helper absorbs that boilerplate so each
|
|
9
|
+
* plugin's `main.ts` reduces to ~20 lines — a factory for the bot and a
|
|
10
|
+
* factory for the stream handler.
|
|
11
|
+
*
|
|
12
|
+
* ## Usage
|
|
13
|
+
*
|
|
14
|
+
* ```ts
|
|
15
|
+
* import { runChannelPlugin } from "@vibearound/plugin-channel-sdk";
|
|
16
|
+
* import { SlackBot } from "./bot.js";
|
|
17
|
+
* import { AgentStreamHandler } from "./agent-stream.js";
|
|
18
|
+
*
|
|
19
|
+
* runChannelPlugin({
|
|
20
|
+
* name: "vibearound-slack",
|
|
21
|
+
* version: "0.1.0",
|
|
22
|
+
* requiredConfig: ["bot_token", "app_token"],
|
|
23
|
+
* createBot: ({ config, agent, log, cacheDir }) =>
|
|
24
|
+
* new SlackBot(
|
|
25
|
+
* { bot_token: config.bot_token as string, app_token: config.app_token as string },
|
|
26
|
+
* agent,
|
|
27
|
+
* log,
|
|
28
|
+
* cacheDir,
|
|
29
|
+
* ),
|
|
30
|
+
* createStreamHandler: (bot, log, verbose) =>
|
|
31
|
+
* new AgentStreamHandler(bot, log, verbose),
|
|
32
|
+
* });
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
import os from "node:os";
|
|
37
|
+
import path from "node:path";
|
|
38
|
+
import type { Agent } from "@agentclientprotocol/sdk";
|
|
39
|
+
|
|
40
|
+
import { connectToHost, normalizeExtMethod } from "./connection.js";
|
|
41
|
+
import { extractErrorMessage } from "./errors.js";
|
|
42
|
+
import type {
|
|
43
|
+
RequestPermissionRequest,
|
|
44
|
+
RequestPermissionResponse,
|
|
45
|
+
SessionNotification,
|
|
46
|
+
} from "./types.js";
|
|
47
|
+
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
// Public types
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
|
|
52
|
+
export type ChannelPluginLogger = (level: string, msg: string) => void;
|
|
53
|
+
|
|
54
|
+
export interface ChannelStreamHandler {
|
|
55
|
+
onSessionUpdate(params: SessionNotification): void;
|
|
56
|
+
onSystemText(text: string): void;
|
|
57
|
+
onAgentReady(agent: string, version: string): void;
|
|
58
|
+
onSessionReady(sessionId: string): void;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface ChannelBot<THandler extends ChannelStreamHandler = ChannelStreamHandler> {
|
|
62
|
+
setStreamHandler(handler: THandler): void;
|
|
63
|
+
start(): Promise<void> | void;
|
|
64
|
+
stop(): Promise<void> | void;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface CreateBotContext {
|
|
68
|
+
config: Record<string, unknown>;
|
|
69
|
+
agent: Agent;
|
|
70
|
+
log: ChannelPluginLogger;
|
|
71
|
+
cacheDir: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface VerboseOptions {
|
|
75
|
+
showThinking: boolean;
|
|
76
|
+
showToolUse: boolean;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface RunChannelPluginSpec<
|
|
80
|
+
TBot extends ChannelBot<THandler>,
|
|
81
|
+
THandler extends ChannelStreamHandler,
|
|
82
|
+
> {
|
|
83
|
+
/** Plugin name reported during ACP initialize (e.g. "vibearound-slack"). */
|
|
84
|
+
name: string;
|
|
85
|
+
|
|
86
|
+
/** Plugin version reported during ACP initialize. */
|
|
87
|
+
version: string;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Config keys that MUST be present on `meta.config`. Plugin startup
|
|
91
|
+
* fails with a clear error if any are missing. Keep to primitives
|
|
92
|
+
* (strings/booleans); deeper validation belongs in the bot constructor.
|
|
93
|
+
*/
|
|
94
|
+
requiredConfig?: string[];
|
|
95
|
+
|
|
96
|
+
/** Factory: build the platform bot from host-supplied config + agent. */
|
|
97
|
+
createBot: (ctx: CreateBotContext) => TBot | Promise<TBot>;
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Factory: build the agent stream handler for this plugin. The handler
|
|
101
|
+
* is wired to the bot via `bot.setStreamHandler(handler)` before the
|
|
102
|
+
* bot is started.
|
|
103
|
+
*/
|
|
104
|
+
createStreamHandler: (
|
|
105
|
+
bot: TBot,
|
|
106
|
+
log: ChannelPluginLogger,
|
|
107
|
+
verbose: VerboseOptions,
|
|
108
|
+
) => THandler;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Optional hook invoked after the bot has been constructed but before
|
|
112
|
+
* `start()` is called. Use this for one-off initialization that needs
|
|
113
|
+
* to log diagnostic info (e.g. Telegram's `probe()`).
|
|
114
|
+
*/
|
|
115
|
+
afterCreate?: (bot: TBot, log: ChannelPluginLogger) => Promise<void> | void;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ---------------------------------------------------------------------------
|
|
119
|
+
// Implementation
|
|
120
|
+
// ---------------------------------------------------------------------------
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Run a channel plugin to completion.
|
|
124
|
+
*
|
|
125
|
+
* Performs the ACP initialize handshake, validates required config,
|
|
126
|
+
* constructs the bot + stream handler, starts the bot, waits for the host
|
|
127
|
+
* connection to close, then stops the bot and exits the process.
|
|
128
|
+
*
|
|
129
|
+
* Never returns under normal operation — the process exits at the end.
|
|
130
|
+
*/
|
|
131
|
+
export async function runChannelPlugin<
|
|
132
|
+
TBot extends ChannelBot<THandler>,
|
|
133
|
+
THandler extends ChannelStreamHandler,
|
|
134
|
+
>(spec: RunChannelPluginSpec<TBot, THandler>): Promise<void> {
|
|
135
|
+
const prefix = `[${spec.name.replace(/^vibearound-/, "")}-plugin]`;
|
|
136
|
+
const log: ChannelPluginLogger = (level, msg) => {
|
|
137
|
+
process.stderr.write(`${prefix}[${level}] ${msg}\n`);
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
await runInner(spec, log);
|
|
142
|
+
} catch (err) {
|
|
143
|
+
log("error", `fatal: ${extractErrorMessage(err)}`);
|
|
144
|
+
process.exit(1);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async function runInner<
|
|
149
|
+
TBot extends ChannelBot<THandler>,
|
|
150
|
+
THandler extends ChannelStreamHandler,
|
|
151
|
+
>(
|
|
152
|
+
spec: RunChannelPluginSpec<TBot, THandler>,
|
|
153
|
+
log: ChannelPluginLogger,
|
|
154
|
+
): Promise<void> {
|
|
155
|
+
log("info", "initializing ACP connection...");
|
|
156
|
+
|
|
157
|
+
let streamHandler: THandler | null = null;
|
|
158
|
+
|
|
159
|
+
const { agent, meta, agentInfo, conn } = await connectToHost(
|
|
160
|
+
{ name: spec.name, version: spec.version },
|
|
161
|
+
() => ({
|
|
162
|
+
async sessionUpdate(params: SessionNotification): Promise<void> {
|
|
163
|
+
streamHandler?.onSessionUpdate(params);
|
|
164
|
+
},
|
|
165
|
+
|
|
166
|
+
async requestPermission(
|
|
167
|
+
params: RequestPermissionRequest,
|
|
168
|
+
): Promise<RequestPermissionResponse> {
|
|
169
|
+
const first = params.options?.[0];
|
|
170
|
+
if (first) {
|
|
171
|
+
return { outcome: { outcome: "selected", optionId: first.optionId } };
|
|
172
|
+
}
|
|
173
|
+
throw new Error("No permission options provided");
|
|
174
|
+
},
|
|
175
|
+
|
|
176
|
+
async extNotification(
|
|
177
|
+
method: string,
|
|
178
|
+
params: Record<string, unknown>,
|
|
179
|
+
): Promise<void> {
|
|
180
|
+
switch (normalizeExtMethod(method)) {
|
|
181
|
+
case "channel/system_text": {
|
|
182
|
+
const text = params.text as string;
|
|
183
|
+
streamHandler?.onSystemText(text);
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
case "channel/agent_ready": {
|
|
187
|
+
const agentName = params.agent as string;
|
|
188
|
+
const version = params.version as string;
|
|
189
|
+
log("info", `agent_ready: ${agentName} v${version}`);
|
|
190
|
+
streamHandler?.onAgentReady(agentName, version);
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
case "channel/session_ready": {
|
|
194
|
+
const sessionId = params.sessionId as string;
|
|
195
|
+
log("info", `session_ready: ${sessionId}`);
|
|
196
|
+
streamHandler?.onSessionReady(sessionId);
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
default:
|
|
200
|
+
log("warn", `unhandled ext_notification: ${method}`);
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
}),
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
const config = meta.config;
|
|
207
|
+
|
|
208
|
+
// Validate required config keys up front so a misconfigured plugin fails
|
|
209
|
+
// with a clear error instead of some downstream "undefined is not a
|
|
210
|
+
// string" crash in the bot constructor.
|
|
211
|
+
for (const key of spec.requiredConfig ?? []) {
|
|
212
|
+
if (config[key] === undefined || config[key] === null || config[key] === "") {
|
|
213
|
+
throw new Error(`${key} is required in config`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const cacheDir =
|
|
218
|
+
meta.cacheDir ?? path.join(os.homedir(), ".vibearound", ".cache");
|
|
219
|
+
|
|
220
|
+
log(
|
|
221
|
+
"info",
|
|
222
|
+
`initialized, host=${agentInfo.name ?? "unknown"} cacheDir=${cacheDir}`,
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
const bot = await spec.createBot({ config, agent, log, cacheDir });
|
|
226
|
+
|
|
227
|
+
if (spec.afterCreate) {
|
|
228
|
+
await spec.afterCreate(bot, log);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const verboseRaw = config.verbose as
|
|
232
|
+
| { show_thinking?: boolean; show_tool_use?: boolean }
|
|
233
|
+
| undefined;
|
|
234
|
+
const verbose: VerboseOptions = {
|
|
235
|
+
showThinking: verboseRaw?.show_thinking ?? false,
|
|
236
|
+
showToolUse: verboseRaw?.show_tool_use ?? false,
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
streamHandler = spec.createStreamHandler(bot, log, verbose);
|
|
240
|
+
bot.setStreamHandler(streamHandler);
|
|
241
|
+
|
|
242
|
+
await bot.start();
|
|
243
|
+
log("info", "plugin started");
|
|
244
|
+
|
|
245
|
+
await conn.closed;
|
|
246
|
+
log("info", "connection closed, shutting down");
|
|
247
|
+
await bot.stop();
|
|
248
|
+
process.exit(0);
|
|
249
|
+
}
|