@rynx-ai/plugin-channel-lark 0.1.10 → 0.1.11-beta.3
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/.rynx-plugin/plugin.json +37 -0
- package/dist/cli-mirror/mirror-index.d.ts +4 -2
- package/dist/cli-mirror/mirror-index.js +8 -2
- package/dist/host-adapters.d.ts +23 -10
- package/dist/host-adapters.js +130 -30
- package/dist/index.d.ts +4 -4
- package/dist/index.js +3 -3
- package/dist/lark-config-store.d.ts +30 -0
- package/dist/lark-config-store.js +135 -0
- package/dist/lark-runtime-state-store.d.ts +18 -0
- package/dist/lark-runtime-state-store.js +95 -0
- package/dist/lark-setup.d.ts +27 -5
- package/dist/lark-setup.js +24 -7
- package/dist/lark.d.ts +9 -2
- package/dist/lark.js +44 -77
- package/dist/runtime.d.ts +5 -8
- package/dist/runtime.js +131297 -141
- package/dist/settings.d.ts +7 -0
- package/dist/{runtime.mjs → settings.js} +6608 -10525
- package/package.json +27 -10
- package/ui/assets/index-B2dhMlMU.css +1 -0
- package/ui/assets/index-D9N-AkLX.js +201 -0
- package/ui/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
- package/ui/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
- package/ui/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
- package/ui/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
- package/ui/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
- package/ui/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
- package/ui/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
- package/ui/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
- package/ui/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
- package/ui/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
- package/ui/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
- package/ui/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
- package/ui/index.html +13 -0
- package/rynx-plugin.json +0 -42
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"id": "lark",
|
|
4
|
+
"version": "0.1.11",
|
|
5
|
+
"displayName": "Lark",
|
|
6
|
+
"description": "Lark and Feishu channel integration managed by the plugin.",
|
|
7
|
+
"apiRange": "^1.0.0",
|
|
8
|
+
"capabilities": [
|
|
9
|
+
"conversation.run",
|
|
10
|
+
"agent.catalog.read",
|
|
11
|
+
"runtime.catalog.read",
|
|
12
|
+
"session.cancel",
|
|
13
|
+
"session.fork",
|
|
14
|
+
"session.goal.read",
|
|
15
|
+
"session.goal.write",
|
|
16
|
+
"session.meta.read",
|
|
17
|
+
"session.meta.title.write",
|
|
18
|
+
"session.provision"
|
|
19
|
+
],
|
|
20
|
+
"runtime": {
|
|
21
|
+
"entry": "./dist/runtime.js"
|
|
22
|
+
},
|
|
23
|
+
"channels": [
|
|
24
|
+
{
|
|
25
|
+
"id": "lark",
|
|
26
|
+
"displayName": "Lark"
|
|
27
|
+
}
|
|
28
|
+
],
|
|
29
|
+
"settings": {
|
|
30
|
+
"ui": {
|
|
31
|
+
"entry": "./ui/index.html"
|
|
32
|
+
},
|
|
33
|
+
"background": {
|
|
34
|
+
"entry": "./dist/settings.js"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CodexSessionStore, ConversationSessionStore } from "@rynx-ai/core";
|
|
1
|
+
import type { CodexSessionStore, ConversationSessionStore, SessionRegistry } from "@rynx-ai/core";
|
|
2
2
|
import type { LarkConversationDirectory } from "../lark-conversation-directory.js";
|
|
3
3
|
/**
|
|
4
4
|
* Resolves a Codex thread id (the rollout-file UUID) back to the Feishu
|
|
@@ -32,10 +32,12 @@ export declare class CodexMirrorIndex {
|
|
|
32
32
|
private readonly codexSessionStore;
|
|
33
33
|
private readonly conversationSessionStore;
|
|
34
34
|
private readonly conversationDirectory;
|
|
35
|
-
|
|
35
|
+
private readonly sessionRegistry;
|
|
36
|
+
constructor({ codexSessionStore, conversationSessionStore, conversationDirectory, sessionRegistry, }: {
|
|
36
37
|
codexSessionStore: Pick<CodexSessionStore, "listAll" | "findByCodexSessionId">;
|
|
37
38
|
conversationSessionStore: Pick<ConversationSessionStore, "findKeyBySessionId">;
|
|
38
39
|
conversationDirectory: Pick<LarkConversationDirectory, "get">;
|
|
40
|
+
sessionRegistry: Pick<SessionRegistry, "get">;
|
|
39
41
|
});
|
|
40
42
|
/** codexSessionId → the full send target, or null when the thread isn't
|
|
41
43
|
* bound, its session isn't routed, or the conversation was never recorded. */
|
|
@@ -2,10 +2,12 @@ export class CodexMirrorIndex {
|
|
|
2
2
|
codexSessionStore;
|
|
3
3
|
conversationSessionStore;
|
|
4
4
|
conversationDirectory;
|
|
5
|
-
|
|
5
|
+
sessionRegistry;
|
|
6
|
+
constructor({ codexSessionStore, conversationSessionStore, conversationDirectory, sessionRegistry, }) {
|
|
6
7
|
this.codexSessionStore = codexSessionStore;
|
|
7
8
|
this.conversationSessionStore = conversationSessionStore;
|
|
8
9
|
this.conversationDirectory = conversationDirectory;
|
|
10
|
+
this.sessionRegistry = sessionRegistry;
|
|
9
11
|
}
|
|
10
12
|
/** codexSessionId → the full send target, or null when the thread isn't
|
|
11
13
|
* bound, its session isn't routed, or the conversation was never recorded. */
|
|
@@ -22,6 +24,10 @@ export class CodexMirrorIndex {
|
|
|
22
24
|
if (!directory) {
|
|
23
25
|
return null;
|
|
24
26
|
}
|
|
27
|
+
const session = this.sessionRegistry.get(record.localThreadId);
|
|
28
|
+
if (!session) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
25
31
|
return {
|
|
26
32
|
codexSessionId,
|
|
27
33
|
localThreadId: record.localThreadId,
|
|
@@ -29,7 +35,7 @@ export class CodexMirrorIndex {
|
|
|
29
35
|
chatId: directory.chatId,
|
|
30
36
|
threadId: directory.threadId,
|
|
31
37
|
participants: directory.participants ?? [],
|
|
32
|
-
model:
|
|
38
|
+
model: session.execution.model,
|
|
33
39
|
};
|
|
34
40
|
}
|
|
35
41
|
/** All Codex threads currently bound to a Feishu conversation (those whose
|
package/dist/host-adapters.d.ts
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
|
-
import { type AgentCapabilities, type AgentRuntimeId, type CapabilityResult, type
|
|
1
|
+
import { type AgentCapabilities, type AgentRuntimeId, type CapabilityResult, type ConversationSessionRecord, type ConversationStreamRequest, type ModelListResponse, type SessionExecution, type SessionEvent, type ThreadGoal } from "@rynx-ai/core";
|
|
2
2
|
import type { PluginAgentSummary, PluginHostClient, PluginResolvedSessionExecution, PluginSessionExecutionSnapshot } from "@rynx-ai/plugin-sdk";
|
|
3
3
|
import { FileLarkActiveSessionStore } from "./lark-session-store.js";
|
|
4
|
-
type LarkConversationRuntime
|
|
4
|
+
import type { LarkConversationRuntime } from "./lark.js";
|
|
5
|
+
interface HostSessionLaunchMeta {
|
|
6
|
+
sourceSessionId?: string;
|
|
7
|
+
agent?: string;
|
|
8
|
+
provider?: AgentRuntimeId;
|
|
9
|
+
model?: string;
|
|
10
|
+
reasoningEffort?: string;
|
|
11
|
+
}
|
|
5
12
|
/** Slow consumers fail the run instead of retaining an unbounded event stream. */
|
|
6
13
|
export declare const LARK_RUN_EVENT_QUEUE_MAX_ITEMS = 256;
|
|
7
14
|
export declare const LARK_RUN_EVENT_QUEUE_MAX_BYTES: number;
|
|
@@ -21,7 +28,7 @@ export declare class HostConversationRuntime implements LarkConversationRuntime
|
|
|
21
28
|
generator: AsyncGenerator<SessionEvent, any, any>;
|
|
22
29
|
}>;
|
|
23
30
|
}
|
|
24
|
-
/** Agent command adapter used by /models
|
|
31
|
+
/** Agent command adapter used by /models and /goal. Session fork is a Core RPC. */
|
|
25
32
|
export declare class HostAgentCapabilities implements AgentCapabilities {
|
|
26
33
|
private readonly host;
|
|
27
34
|
constructor(host: PluginHostClient);
|
|
@@ -29,7 +36,6 @@ export declare class HostAgentCapabilities implements AgentCapabilities {
|
|
|
29
36
|
getGoal(sessionId: string): Promise<CapabilityResult<ThreadGoal | null>>;
|
|
30
37
|
setGoal(sessionId: string, objective: string): Promise<CapabilityResult>;
|
|
31
38
|
clearGoal(sessionId: string): Promise<CapabilityResult>;
|
|
32
|
-
forkSession(fromSessionId: string, toSessionId: string): Promise<CapabilityResult>;
|
|
33
39
|
}
|
|
34
40
|
/** Narrow agent catalog + first-turn execution resolver backed by Host RPC. */
|
|
35
41
|
export declare class HostAgentCatalog {
|
|
@@ -46,10 +52,14 @@ export declare class HostAgentCatalog {
|
|
|
46
52
|
export declare class HostSessionProvisioner {
|
|
47
53
|
private readonly host;
|
|
48
54
|
readonly instanceId: string;
|
|
55
|
+
private readonly resolvedExecutions;
|
|
49
56
|
constructor(host: PluginHostClient, instanceId: string);
|
|
50
|
-
provision(operationId: string): Promise<string>;
|
|
57
|
+
provision(operationId: string, session?: HostSessionLaunchMeta): Promise<string>;
|
|
58
|
+
takeExecution(sessionId: string): SessionExecution | undefined;
|
|
59
|
+
existingExecution(sessionId: string): Promise<SessionExecution | undefined>;
|
|
51
60
|
allocationOperationId(sessionKey: string, previousSessionId?: string): string;
|
|
52
61
|
forkOperationId(fromSessionId: string, newSessionKey: string): string;
|
|
62
|
+
fork(operationId: string, sourceSessionId: string, title?: string): Promise<string>;
|
|
53
63
|
setTitle(sessionId: string, title: string): Promise<void>;
|
|
54
64
|
}
|
|
55
65
|
/**
|
|
@@ -61,6 +71,7 @@ export declare class HostSessionProvisioner {
|
|
|
61
71
|
export declare class HostProvisionedLarkSessionStore extends FileLarkActiveSessionStore {
|
|
62
72
|
private readonly operation;
|
|
63
73
|
private mutationTail;
|
|
74
|
+
private readonly instanceDefaultAgent?;
|
|
64
75
|
constructor(options: {
|
|
65
76
|
filePath: string;
|
|
66
77
|
provisioner: HostSessionProvisioner;
|
|
@@ -69,12 +80,14 @@ export declare class HostProvisionedLarkSessionStore extends FileLarkActiveSessi
|
|
|
69
80
|
});
|
|
70
81
|
private readonly provisioner;
|
|
71
82
|
ensure(sessionKey: string): Promise<import("@rynx-ai/core").EnsureSessionResult>;
|
|
72
|
-
set(sessionKey: string, sessionId: string, cwd?: string): Promise<
|
|
83
|
+
set(sessionKey: string, sessionId: string, cwd?: string): Promise<ConversationSessionRecord>;
|
|
84
|
+
get(sessionKey: string): Promise<ConversationSessionRecord | null>;
|
|
73
85
|
rotate(sessionKey: string): Promise<import("@rynx-ai/core").RotateSessionResult>;
|
|
74
|
-
bindCwd(sessionKey: string, cwd: string): Promise<
|
|
75
|
-
applySettings(sessionKey: string, settings: Parameters<FileLarkActiveSessionStore["applySettings"]>[1]): Promise<
|
|
76
|
-
setAgent(sessionKey: string, agentName: string): Promise<
|
|
77
|
-
freeze(sessionKey: string, execution: Parameters<FileLarkActiveSessionStore["freeze"]>[1]): Promise<
|
|
86
|
+
bindCwd(sessionKey: string, cwd: string): Promise<ConversationSessionRecord>;
|
|
87
|
+
applySettings(sessionKey: string, settings: Parameters<FileLarkActiveSessionStore["applySettings"]>[1]): Promise<ConversationSessionRecord>;
|
|
88
|
+
setAgent(sessionKey: string, agentName: string): Promise<ConversationSessionRecord>;
|
|
89
|
+
freeze(sessionKey: string, execution: Parameters<FileLarkActiveSessionStore["freeze"]>[1]): Promise<ConversationSessionRecord>;
|
|
90
|
+
private pinResolvedExecution;
|
|
78
91
|
private withAllocation;
|
|
79
92
|
private runMutation;
|
|
80
93
|
}
|
package/dist/host-adapters.js
CHANGED
|
@@ -89,14 +89,6 @@ export class HostConversationRuntime {
|
|
|
89
89
|
started = await this.host.call("session.run", {
|
|
90
90
|
sessionId: input.threadId,
|
|
91
91
|
message: input.message,
|
|
92
|
-
...(input.provider ? { provider: input.provider } : {}),
|
|
93
|
-
...(input.model ? { model: input.model } : {}),
|
|
94
|
-
...(input.reasoningEffort ? { reasoningEffort: input.reasoningEffort } : {}),
|
|
95
|
-
...(input.cwd ? { cwd: input.cwd } : {}),
|
|
96
|
-
...(input.agentName ? { agent: input.agentName } : {}),
|
|
97
|
-
...(input.developerInstructions
|
|
98
|
-
? { developerInstructions: input.developerInstructions }
|
|
99
|
-
: {}),
|
|
100
92
|
});
|
|
101
93
|
runId = started.runId;
|
|
102
94
|
if (earlyOverflow) {
|
|
@@ -142,7 +134,7 @@ export class HostConversationRuntime {
|
|
|
142
134
|
};
|
|
143
135
|
}
|
|
144
136
|
}
|
|
145
|
-
/** Agent command adapter used by /models
|
|
137
|
+
/** Agent command adapter used by /models and /goal. Session fork is a Core RPC. */
|
|
146
138
|
export class HostAgentCapabilities {
|
|
147
139
|
host;
|
|
148
140
|
constructor(host) {
|
|
@@ -160,9 +152,6 @@ export class HostAgentCapabilities {
|
|
|
160
152
|
clearGoal(sessionId) {
|
|
161
153
|
return this.host.call("session.goal.clear", { sessionId });
|
|
162
154
|
}
|
|
163
|
-
forkSession(fromSessionId, toSessionId) {
|
|
164
|
-
return this.host.call("session.fork", { fromSessionId, toSessionId });
|
|
165
|
-
}
|
|
166
155
|
}
|
|
167
156
|
/** Narrow agent catalog + first-turn execution resolver backed by Host RPC. */
|
|
168
157
|
export class HostAgentCatalog {
|
|
@@ -184,26 +173,67 @@ export class HostAgentCatalog {
|
|
|
184
173
|
export class HostSessionProvisioner {
|
|
185
174
|
host;
|
|
186
175
|
instanceId;
|
|
176
|
+
resolvedExecutions = new Map();
|
|
187
177
|
constructor(host, instanceId) {
|
|
188
178
|
this.host = host;
|
|
189
179
|
this.instanceId = instanceId;
|
|
190
180
|
}
|
|
191
|
-
async provision(operationId) {
|
|
181
|
+
async provision(operationId, session = {}) {
|
|
192
182
|
const result = await this.host.call("session.ensure", {
|
|
193
183
|
scopeId: this.instanceId,
|
|
194
184
|
operationId,
|
|
195
|
-
session
|
|
185
|
+
session,
|
|
196
186
|
});
|
|
197
187
|
if (!result?.sessionId)
|
|
198
188
|
throw new Error("host did not return a provisioned session id");
|
|
189
|
+
this.resolvedExecutions.set(result.sessionId, {
|
|
190
|
+
runtime: result.execution.provider,
|
|
191
|
+
model: result.execution.model,
|
|
192
|
+
...(result.execution.reasoningEffort
|
|
193
|
+
? { reasoningEffort: result.execution.reasoningEffort }
|
|
194
|
+
: {}),
|
|
195
|
+
...(session.agent ? { agent: session.agent } : {}),
|
|
196
|
+
});
|
|
199
197
|
return result.sessionId;
|
|
200
198
|
}
|
|
199
|
+
takeExecution(sessionId) {
|
|
200
|
+
const execution = this.resolvedExecutions.get(sessionId);
|
|
201
|
+
this.resolvedExecutions.delete(sessionId);
|
|
202
|
+
return execution;
|
|
203
|
+
}
|
|
204
|
+
async existingExecution(sessionId) {
|
|
205
|
+
try {
|
|
206
|
+
const session = await this.host.call("session.meta.get", { sessionId });
|
|
207
|
+
return {
|
|
208
|
+
runtime: session.execution.provider,
|
|
209
|
+
model: session.execution.model,
|
|
210
|
+
...(session.execution.reasoningEffort
|
|
211
|
+
? { reasoningEffort: session.execution.reasoningEffort }
|
|
212
|
+
: {}),
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
catch (error) {
|
|
216
|
+
if (hostErrorCode(error) === "SESSION_NOT_FOUND")
|
|
217
|
+
return undefined;
|
|
218
|
+
throw error;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
201
221
|
allocationOperationId(sessionKey, previousSessionId) {
|
|
202
222
|
return stableOperationId("allocate", this.instanceId, sessionKey, previousSessionId ?? "initial");
|
|
203
223
|
}
|
|
204
224
|
forkOperationId(fromSessionId, newSessionKey) {
|
|
205
225
|
return stableOperationId("fork", this.instanceId, fromSessionId, newSessionKey);
|
|
206
226
|
}
|
|
227
|
+
async fork(operationId, sourceSessionId, title) {
|
|
228
|
+
const result = await this.host.call("session.fork", {
|
|
229
|
+
sourceSessionId,
|
|
230
|
+
operationId,
|
|
231
|
+
...(title ? { title } : {}),
|
|
232
|
+
});
|
|
233
|
+
if (!result?.sessionId)
|
|
234
|
+
throw new Error("host did not return a forked session id");
|
|
235
|
+
return result.sessionId;
|
|
236
|
+
}
|
|
207
237
|
async setTitle(sessionId, title) {
|
|
208
238
|
await this.host.call("session.meta.setTitle", { sessionId, title });
|
|
209
239
|
}
|
|
@@ -217,6 +247,7 @@ export class HostSessionProvisioner {
|
|
|
217
247
|
export class HostProvisionedLarkSessionStore extends FileLarkActiveSessionStore {
|
|
218
248
|
operation;
|
|
219
249
|
mutationTail = Promise.resolve();
|
|
250
|
+
instanceDefaultAgent;
|
|
220
251
|
constructor(options) {
|
|
221
252
|
const operation = new AsyncLocalStorage();
|
|
222
253
|
super({
|
|
@@ -224,52 +255,94 @@ export class HostProvisionedLarkSessionStore extends FileLarkActiveSessionStore
|
|
|
224
255
|
defaultAgent: options.defaultAgent,
|
|
225
256
|
now: options.now,
|
|
226
257
|
createSessionId: async () => {
|
|
227
|
-
const
|
|
228
|
-
if (!
|
|
258
|
+
const allocation = operation.getStore();
|
|
259
|
+
if (!allocation)
|
|
229
260
|
throw new Error("session allocation is missing its stable operation id");
|
|
230
|
-
return options.provisioner.provision(operationId);
|
|
261
|
+
return options.provisioner.provision(allocation.operationId, allocation.session);
|
|
231
262
|
},
|
|
232
263
|
});
|
|
233
264
|
this.operation = operation;
|
|
234
265
|
this.provisioner = options.provisioner;
|
|
266
|
+
this.instanceDefaultAgent = options.defaultAgent;
|
|
235
267
|
}
|
|
236
268
|
provisioner;
|
|
237
269
|
async ensure(sessionKey) {
|
|
238
270
|
return this.runMutation(async () => {
|
|
239
271
|
const current = await super.get(sessionKey);
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
272
|
+
const existingExecution = current
|
|
273
|
+
? await this.provisioner.existingExecution(current.sessionId)
|
|
274
|
+
: undefined;
|
|
275
|
+
if (current && existingExecution) {
|
|
276
|
+
const pinned = current.runtime
|
|
277
|
+
? current
|
|
278
|
+
: await super.freeze(sessionKey, existingExecution);
|
|
279
|
+
return { ...current, ...pinned, source: "existing" };
|
|
280
|
+
}
|
|
281
|
+
if (current) {
|
|
282
|
+
const replacement = await this.withAllocation(sessionKey, current.sessionId, launchMeta(current), () => super.rotate(sessionKey));
|
|
283
|
+
return {
|
|
284
|
+
...await this.pinResolvedExecution(sessionKey, replacement),
|
|
285
|
+
source: "new",
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
const created = await this.withAllocation(sessionKey, undefined, this.instanceDefaultAgent ? { agent: this.instanceDefaultAgent } : {}, () => super.ensure(sessionKey));
|
|
289
|
+
return this.pinResolvedExecution(sessionKey, created);
|
|
243
290
|
});
|
|
244
291
|
}
|
|
245
292
|
async set(sessionKey, sessionId, cwd) {
|
|
246
|
-
return this.runMutation(() => super.set(sessionKey, sessionId, cwd));
|
|
293
|
+
return this.runMutation(async () => this.pinResolvedExecution(sessionKey, await super.set(sessionKey, sessionId, cwd)));
|
|
247
294
|
}
|
|
248
|
-
async
|
|
295
|
+
async get(sessionKey) {
|
|
249
296
|
return this.runMutation(async () => {
|
|
250
297
|
const current = await super.get(sessionKey);
|
|
251
|
-
|
|
298
|
+
if (!current)
|
|
299
|
+
return current;
|
|
300
|
+
const existingExecution = await this.provisioner.existingExecution(current.sessionId);
|
|
301
|
+
if (existingExecution) {
|
|
302
|
+
return current.runtime ? current : super.freeze(sessionKey, existingExecution);
|
|
303
|
+
}
|
|
304
|
+
const replacement = await this.withAllocation(sessionKey, current.sessionId, launchMeta(current), () => super.rotate(sessionKey));
|
|
305
|
+
return this.pinResolvedExecution(sessionKey, replacement);
|
|
252
306
|
});
|
|
253
307
|
}
|
|
254
|
-
async
|
|
308
|
+
async rotate(sessionKey) {
|
|
255
309
|
return this.runMutation(async () => {
|
|
256
310
|
const current = await super.get(sessionKey);
|
|
257
|
-
|
|
311
|
+
const rotated = await this.withAllocation(sessionKey, current?.sessionId, launchMeta(current), () => super.rotate(sessionKey));
|
|
312
|
+
return this.pinResolvedExecution(sessionKey, rotated);
|
|
258
313
|
});
|
|
259
314
|
}
|
|
315
|
+
async bindCwd(sessionKey, cwd) {
|
|
316
|
+
void sessionKey;
|
|
317
|
+
void cwd;
|
|
318
|
+
throw new Error("working directory is managed by the Session Project snapshot");
|
|
319
|
+
}
|
|
260
320
|
async applySettings(sessionKey, settings) {
|
|
261
321
|
return this.runMutation(async () => {
|
|
262
322
|
if (!settings.rotate && (await super.get(sessionKey))) {
|
|
263
323
|
return super.applySettings(sessionKey, settings);
|
|
264
324
|
}
|
|
265
325
|
const current = await super.get(sessionKey);
|
|
266
|
-
|
|
326
|
+
const providerChanged = Boolean(settings.runtime && settings.runtime !== current?.runtime);
|
|
327
|
+
const updated = await this.withAllocation(sessionKey, current?.sessionId, launchMeta({
|
|
328
|
+
...(providerChanged
|
|
329
|
+
? { provider: settings.runtime }
|
|
330
|
+
: (current?.agent ?? this.instanceDefaultAgent)
|
|
331
|
+
? { agent: current?.agent ?? this.instanceDefaultAgent }
|
|
332
|
+
: settings.runtime
|
|
333
|
+
? { provider: settings.runtime }
|
|
334
|
+
: {}),
|
|
335
|
+
...(settings.model ? { model: settings.model } : {}),
|
|
336
|
+
...(settings.reasoningEffort ? { reasoningEffort: settings.reasoningEffort } : {}),
|
|
337
|
+
}), () => super.applySettings(sessionKey, settings));
|
|
338
|
+
return this.pinResolvedExecution(sessionKey, updated);
|
|
267
339
|
});
|
|
268
340
|
}
|
|
269
341
|
async setAgent(sessionKey, agentName) {
|
|
270
342
|
return this.runMutation(async () => {
|
|
271
343
|
const current = await super.get(sessionKey);
|
|
272
|
-
|
|
344
|
+
const updated = await this.withAllocation(sessionKey, current?.sessionId, { agent: agentName }, () => super.setAgent(sessionKey, agentName));
|
|
345
|
+
return this.pinResolvedExecution(sessionKey, updated);
|
|
273
346
|
});
|
|
274
347
|
}
|
|
275
348
|
async freeze(sessionKey, execution) {
|
|
@@ -277,11 +350,22 @@ export class HostProvisionedLarkSessionStore extends FileLarkActiveSessionStore
|
|
|
277
350
|
const current = await super.get(sessionKey);
|
|
278
351
|
if (current)
|
|
279
352
|
return super.freeze(sessionKey, execution);
|
|
280
|
-
|
|
353
|
+
const frozen = await this.withAllocation(sessionKey, undefined, launchMeta(execution), () => super.freeze(sessionKey, execution));
|
|
354
|
+
return this.pinResolvedExecution(sessionKey, frozen);
|
|
281
355
|
});
|
|
282
356
|
}
|
|
283
|
-
|
|
284
|
-
|
|
357
|
+
async pinResolvedExecution(sessionKey, record) {
|
|
358
|
+
const execution = this.provisioner.takeExecution(record.sessionId);
|
|
359
|
+
if (!execution)
|
|
360
|
+
return record;
|
|
361
|
+
const frozen = await super.freeze(sessionKey, execution);
|
|
362
|
+
return { ...record, ...frozen };
|
|
363
|
+
}
|
|
364
|
+
withAllocation(sessionKey, previousSessionId, session, run) {
|
|
365
|
+
return this.operation.run({
|
|
366
|
+
operationId: this.provisioner.allocationOperationId(sessionKey, previousSessionId),
|
|
367
|
+
session,
|
|
368
|
+
}, run);
|
|
285
369
|
}
|
|
286
370
|
async runMutation(run) {
|
|
287
371
|
const previous = this.mutationTail;
|
|
@@ -298,6 +382,22 @@ export class HostProvisionedLarkSessionStore extends FileLarkActiveSessionStore
|
|
|
298
382
|
}
|
|
299
383
|
}
|
|
300
384
|
}
|
|
385
|
+
function launchMeta(input) {
|
|
386
|
+
if (!input)
|
|
387
|
+
return {};
|
|
388
|
+
return {
|
|
389
|
+
...(input.agent ? { agent: input.agent } : {}),
|
|
390
|
+
...(input.provider ? { provider: input.provider } : {}),
|
|
391
|
+
...(input.model ? { model: input.model } : {}),
|
|
392
|
+
...(input.reasoningEffort ? { reasoningEffort: input.reasoningEffort } : {}),
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
function hostErrorCode(error) {
|
|
396
|
+
return error && typeof error === "object" && "code" in error &&
|
|
397
|
+
typeof error.code === "string"
|
|
398
|
+
? error.code
|
|
399
|
+
: undefined;
|
|
400
|
+
}
|
|
301
401
|
export function stableOperationId(kind, ...parts) {
|
|
302
402
|
const digest = createHash("sha256").update(parts.join("\0")).digest("hex");
|
|
303
403
|
return `${kind}-${digest}`;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
/** Public Lark plugin API.
|
|
2
|
-
*
|
|
3
|
-
* no legacy in-process `RynxPlugin` manifest or daemon Channel factory. */
|
|
1
|
+
/** Public Lark plugin API. Rynx loads the manifest's self-contained runtime
|
|
2
|
+
* module; this package exports no in-process daemon Channel factory. */
|
|
4
3
|
export { LocalLarkBotRuntime } from "./lark.js";
|
|
5
|
-
export { authorizeLark, larkConfigSchema, LARK_BOT_SCOPES, LARK_BOT_EVENTS, } from "./lark-setup.js";
|
|
4
|
+
export { authorizeLark, larkConfigSchema, LARK_BOT_SCOPES, LARK_BOT_EVENTS, type LarkAuthorizationMaterial, type LarkSetupField, } from "./lark-setup.js";
|
|
6
5
|
export { activate, createLarkPluginRuntime, LARK_CHANNEL_ID, type LarkPluginRuntimeOptions, } from "./runtime.js";
|
|
6
|
+
export { createLarkPluginSettings, type LarkPluginSettingsOptions, } from "./settings.js";
|
|
7
7
|
export { HostAgentCapabilities, HostAgentCatalog, HostConversationRuntime, HostProvisionedLarkSessionStore, HostSessionProvisioner, stableOperationId, } from "./host-adapters.js";
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
/** Public Lark plugin API.
|
|
2
|
-
*
|
|
3
|
-
* no legacy in-process `RynxPlugin` manifest or daemon Channel factory. */
|
|
1
|
+
/** Public Lark plugin API. Rynx loads the manifest's self-contained runtime
|
|
2
|
+
* module; this package exports no in-process daemon Channel factory. */
|
|
4
3
|
export { LocalLarkBotRuntime } from "./lark.js";
|
|
5
4
|
export { authorizeLark, larkConfigSchema, LARK_BOT_SCOPES, LARK_BOT_EVENTS, } from "./lark-setup.js";
|
|
6
5
|
export { activate, createLarkPluginRuntime, LARK_CHANNEL_ID, } from "./runtime.js";
|
|
6
|
+
export { createLarkPluginSettings, } from "./settings.js";
|
|
7
7
|
export { HostAgentCapabilities, HostAgentCatalog, HostConversationRuntime, HostProvisionedLarkSessionStore, HostSessionProvisioner, stableOperationId, } from "./host-adapters.js";
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { PluginExecutionDefaults } from "@rynx-ai/plugin-sdk";
|
|
2
|
+
export interface LarkInstanceConfig {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
enabled: boolean;
|
|
6
|
+
appId: string;
|
|
7
|
+
appSecret: string;
|
|
8
|
+
defaultAgent?: string;
|
|
9
|
+
executionDefaults: PluginExecutionDefaults;
|
|
10
|
+
}
|
|
11
|
+
export declare class LarkConfigStore {
|
|
12
|
+
private readonly file;
|
|
13
|
+
constructor(dataDir: string);
|
|
14
|
+
list(): LarkInstanceConfig[];
|
|
15
|
+
get(id: string): LarkInstanceConfig | undefined;
|
|
16
|
+
upsert(input: {
|
|
17
|
+
id?: string;
|
|
18
|
+
name: string;
|
|
19
|
+
enabled: boolean;
|
|
20
|
+
appId: string;
|
|
21
|
+
appSecret?: string;
|
|
22
|
+
defaultAgent?: string | null;
|
|
23
|
+
runtime?: PluginExecutionDefaults["runtime"];
|
|
24
|
+
allowedRoots?: string[];
|
|
25
|
+
}): LarkInstanceConfig;
|
|
26
|
+
remove(id: string): boolean;
|
|
27
|
+
setEnabled(id: string, enabled: boolean): LarkInstanceConfig;
|
|
28
|
+
private read;
|
|
29
|
+
private write;
|
|
30
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { chmodSync, existsSync, lstatSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync, } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
const INSTANCE_ID_PATTERN = /^[a-z][a-z0-9-]{0,62}$/;
|
|
5
|
+
export class LarkConfigStore {
|
|
6
|
+
file;
|
|
7
|
+
constructor(dataDir) {
|
|
8
|
+
mkdirSync(dataDir, { recursive: true, mode: 0o700 });
|
|
9
|
+
this.file = path.join(dataDir, "lark-config.json");
|
|
10
|
+
}
|
|
11
|
+
list() {
|
|
12
|
+
return structuredClone(this.read().instances);
|
|
13
|
+
}
|
|
14
|
+
get(id) {
|
|
15
|
+
return this.list().find((instance) => instance.id === id);
|
|
16
|
+
}
|
|
17
|
+
upsert(input) {
|
|
18
|
+
const state = this.read();
|
|
19
|
+
const id = input.id ?? `instance-${randomUUID().slice(0, 8)}`;
|
|
20
|
+
if (!INSTANCE_ID_PATTERN.test(id))
|
|
21
|
+
throw new Error("Lark instance id is invalid");
|
|
22
|
+
const existing = state.instances.find((instance) => instance.id === id);
|
|
23
|
+
const secret = input.appSecret?.trim() || existing?.appSecret;
|
|
24
|
+
if (!secret)
|
|
25
|
+
throw new Error("App Secret is required");
|
|
26
|
+
const appId = input.appId.trim();
|
|
27
|
+
const name = input.name.trim();
|
|
28
|
+
if (!appId)
|
|
29
|
+
throw new Error("App ID is required");
|
|
30
|
+
if (!name)
|
|
31
|
+
throw new Error("instance name is required");
|
|
32
|
+
const defaultAgent = input.defaultAgent === undefined
|
|
33
|
+
? existing?.defaultAgent
|
|
34
|
+
: input.defaultAgent?.trim() || undefined;
|
|
35
|
+
const roots = input.allowedRoots ?? existing?.executionDefaults.allowedRoots ?? [];
|
|
36
|
+
if (!Array.isArray(roots) || roots.some((root) => typeof root !== "string" || !path.isAbsolute(root))) {
|
|
37
|
+
throw new Error("allowed roots must be absolute paths");
|
|
38
|
+
}
|
|
39
|
+
const instance = {
|
|
40
|
+
id,
|
|
41
|
+
name,
|
|
42
|
+
enabled: input.enabled,
|
|
43
|
+
appId,
|
|
44
|
+
appSecret: secret,
|
|
45
|
+
...(defaultAgent ? { defaultAgent } : {}),
|
|
46
|
+
executionDefaults: {
|
|
47
|
+
runtime: input.runtime ?? existing?.executionDefaults.runtime ?? "codex",
|
|
48
|
+
allowedRoots: [...new Set(roots.map((root) => path.resolve(root)))],
|
|
49
|
+
...(defaultAgent ? { defaultAgent } : {}),
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
const index = state.instances.findIndex((item) => item.id === id);
|
|
53
|
+
if (index >= 0)
|
|
54
|
+
state.instances[index] = instance;
|
|
55
|
+
else
|
|
56
|
+
state.instances.push(instance);
|
|
57
|
+
this.write(state);
|
|
58
|
+
return structuredClone(instance);
|
|
59
|
+
}
|
|
60
|
+
remove(id) {
|
|
61
|
+
const state = this.read();
|
|
62
|
+
const filtered = state.instances.filter((instance) => instance.id !== id);
|
|
63
|
+
if (filtered.length === state.instances.length)
|
|
64
|
+
return false;
|
|
65
|
+
this.write({ ...state, instances: filtered });
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
setEnabled(id, enabled) {
|
|
69
|
+
const state = this.read();
|
|
70
|
+
const instance = state.instances.find((item) => item.id === id);
|
|
71
|
+
if (!instance)
|
|
72
|
+
throw new Error(`Lark instance "${id}" not found`);
|
|
73
|
+
instance.enabled = enabled;
|
|
74
|
+
this.write(state);
|
|
75
|
+
return structuredClone(instance);
|
|
76
|
+
}
|
|
77
|
+
read() {
|
|
78
|
+
if (!existsSync(this.file))
|
|
79
|
+
return { schemaVersion: 1, instances: [] };
|
|
80
|
+
const info = lstatSync(this.file);
|
|
81
|
+
if (!info.isFile() || info.isSymbolicLink() || info.size > 1024 * 1024) {
|
|
82
|
+
throw new Error("Lark plugin configuration is invalid");
|
|
83
|
+
}
|
|
84
|
+
return parseState(JSON.parse(readFileSync(this.file, "utf8")));
|
|
85
|
+
}
|
|
86
|
+
write(state) {
|
|
87
|
+
const temporary = `${this.file}.tmp-${randomUUID()}`;
|
|
88
|
+
try {
|
|
89
|
+
writeFileSync(temporary, `${JSON.stringify(state, null, 2)}\n`, {
|
|
90
|
+
encoding: "utf8",
|
|
91
|
+
mode: 0o600,
|
|
92
|
+
flag: "wx",
|
|
93
|
+
});
|
|
94
|
+
chmodSync(temporary, 0o600);
|
|
95
|
+
renameSync(temporary, this.file);
|
|
96
|
+
}
|
|
97
|
+
finally {
|
|
98
|
+
rmSync(temporary, { force: true });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function parseState(value) {
|
|
103
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
104
|
+
throw new Error("Lark plugin configuration must be an object");
|
|
105
|
+
}
|
|
106
|
+
const record = value;
|
|
107
|
+
if (record.schemaVersion !== 1 || !Array.isArray(record.instances)) {
|
|
108
|
+
throw new Error("Lark plugin configuration schema is invalid");
|
|
109
|
+
}
|
|
110
|
+
const ids = new Set();
|
|
111
|
+
const instances = record.instances.map((value) => {
|
|
112
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
113
|
+
throw new Error("Lark instance configuration is invalid");
|
|
114
|
+
}
|
|
115
|
+
const instance = value;
|
|
116
|
+
if (!INSTANCE_ID_PATTERN.test(instance.id) ||
|
|
117
|
+
typeof instance.name !== "string" || !instance.name ||
|
|
118
|
+
typeof instance.enabled !== "boolean" ||
|
|
119
|
+
typeof instance.appId !== "string" || !instance.appId ||
|
|
120
|
+
typeof instance.appSecret !== "string" || !instance.appSecret ||
|
|
121
|
+
(instance.defaultAgent !== undefined &&
|
|
122
|
+
(typeof instance.defaultAgent !== "string" || !instance.defaultAgent.trim())) ||
|
|
123
|
+
!instance.executionDefaults ||
|
|
124
|
+
!["codex", "traex", "claude"].includes(instance.executionDefaults.runtime) ||
|
|
125
|
+
!Array.isArray(instance.executionDefaults.allowedRoots) ||
|
|
126
|
+
!instance.executionDefaults.allowedRoots.every((root) => typeof root === "string" && path.isAbsolute(root))) {
|
|
127
|
+
throw new Error("Lark instance configuration is invalid");
|
|
128
|
+
}
|
|
129
|
+
if (ids.has(instance.id))
|
|
130
|
+
throw new Error(`duplicate Lark instance id "${instance.id}"`);
|
|
131
|
+
ids.add(instance.id);
|
|
132
|
+
return structuredClone(instance);
|
|
133
|
+
});
|
|
134
|
+
return { schemaVersion: 1, instances };
|
|
135
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { PluginHealth } from "@rynx-ai/plugin-sdk";
|
|
2
|
+
export interface LarkRuntimeInstanceState {
|
|
3
|
+
state: "starting" | "running" | "failed" | "stopping" | "stopped";
|
|
4
|
+
connected: boolean;
|
|
5
|
+
lastError: string | null;
|
|
6
|
+
lastEventAt: string | null;
|
|
7
|
+
}
|
|
8
|
+
export interface LarkRuntimeState {
|
|
9
|
+
active: boolean;
|
|
10
|
+
health: PluginHealth;
|
|
11
|
+
instances: Record<string, LarkRuntimeInstanceState>;
|
|
12
|
+
}
|
|
13
|
+
export declare class LarkRuntimeStateStore {
|
|
14
|
+
private readonly file;
|
|
15
|
+
constructor(dataDir: string);
|
|
16
|
+
read(): LarkRuntimeState;
|
|
17
|
+
write(state: LarkRuntimeState): void;
|
|
18
|
+
}
|