@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.
Files changed (36) hide show
  1. package/.rynx-plugin/plugin.json +37 -0
  2. package/dist/cli-mirror/mirror-index.d.ts +4 -2
  3. package/dist/cli-mirror/mirror-index.js +8 -2
  4. package/dist/host-adapters.d.ts +23 -10
  5. package/dist/host-adapters.js +130 -30
  6. package/dist/index.d.ts +4 -4
  7. package/dist/index.js +3 -3
  8. package/dist/lark-config-store.d.ts +30 -0
  9. package/dist/lark-config-store.js +135 -0
  10. package/dist/lark-runtime-state-store.d.ts +18 -0
  11. package/dist/lark-runtime-state-store.js +95 -0
  12. package/dist/lark-setup.d.ts +27 -5
  13. package/dist/lark-setup.js +24 -7
  14. package/dist/lark.d.ts +9 -2
  15. package/dist/lark.js +44 -77
  16. package/dist/runtime.d.ts +5 -8
  17. package/dist/runtime.js +131297 -141
  18. package/dist/settings.d.ts +7 -0
  19. package/dist/{runtime.mjs → settings.js} +6608 -10525
  20. package/package.json +27 -10
  21. package/ui/assets/index-B2dhMlMU.css +1 -0
  22. package/ui/assets/index-D9N-AkLX.js +201 -0
  23. package/ui/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
  24. package/ui/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
  25. package/ui/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
  26. package/ui/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
  27. package/ui/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
  28. package/ui/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
  29. package/ui/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
  30. package/ui/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
  31. package/ui/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
  32. package/ui/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
  33. package/ui/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
  34. package/ui/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
  35. package/ui/index.html +13 -0
  36. 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
- constructor({ codexSessionStore, conversationSessionStore, conversationDirectory, }: {
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
- constructor({ codexSessionStore, conversationSessionStore, conversationDirectory, }) {
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: record.model,
38
+ model: session.execution.model,
33
39
  };
34
40
  }
35
41
  /** All Codex threads currently bound to a Feishu conversation (those whose
@@ -1,7 +1,14 @@
1
- import { type AgentCapabilities, type AgentRuntimeId, type CapabilityResult, type ConversationRuntime, type ConversationStreamRequest, type ModelListResponse, type SessionEvent, type ThreadGoal } from "@rynx-ai/core";
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 = Pick<ConversationRuntime, "runLive">;
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, /goal, and /fork. */
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<import("@rynx-ai/core").ConversationSessionRecord>;
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<import("@rynx-ai/core").ConversationSessionRecord>;
75
- applySettings(sessionKey: string, settings: Parameters<FileLarkActiveSessionStore["applySettings"]>[1]): Promise<import("@rynx-ai/core").ConversationSessionRecord>;
76
- setAgent(sessionKey: string, agentName: string): Promise<import("@rynx-ai/core").ConversationSessionRecord>;
77
- freeze(sessionKey: string, execution: Parameters<FileLarkActiveSessionStore["freeze"]>[1]): Promise<import("@rynx-ai/core").ConversationSessionRecord>;
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
  }
@@ -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, /goal, and /fork. */
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 operationId = operation.getStore();
228
- if (!operationId)
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
- if (current)
241
- return { ...current, source: "existing" };
242
- return this.withAllocation(sessionKey, undefined, () => super.ensure(sessionKey));
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 rotate(sessionKey) {
295
+ async get(sessionKey) {
249
296
  return this.runMutation(async () => {
250
297
  const current = await super.get(sessionKey);
251
- return this.withAllocation(sessionKey, current?.sessionId, () => super.rotate(sessionKey));
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 bindCwd(sessionKey, cwd) {
308
+ async rotate(sessionKey) {
255
309
  return this.runMutation(async () => {
256
310
  const current = await super.get(sessionKey);
257
- return this.withAllocation(sessionKey, current?.sessionId, () => super.bindCwd(sessionKey, cwd));
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
- return this.withAllocation(sessionKey, current?.sessionId, () => super.applySettings(sessionKey, settings));
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
- return this.withAllocation(sessionKey, current?.sessionId, () => super.setAgent(sessionKey, agentName));
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
- return this.withAllocation(sessionKey, undefined, () => super.freeze(sessionKey, execution));
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
- withAllocation(sessionKey, previousSessionId, run) {
284
- return this.operation.run(this.provisioner.allocationOperationId(sessionKey, previousSessionId), run);
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. The manifest executable is the self-contained
2
- * `dist/runtime.mjs`, loaded only by @rynx-ai/plugin-runner; this module exports
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. The manifest executable is the self-contained
2
- * `dist/runtime.mjs`, loaded only by @rynx-ai/plugin-runner; this module exports
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
+ }