@bojackduy/opencode-loopd 1.9.1 → 1.9.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 (3) hide show
  1. package/dist/server.js +312 -16
  2. package/dist/tui.js +422 -253
  3. package/package.json +4 -4
package/dist/server.js CHANGED
@@ -1,4 +1,16 @@
1
1
  // @bun
2
+ var __esm = (fn, res, err) => () => {
3
+ if (fn)
4
+ try {
5
+ res = fn(fn = 0);
6
+ } catch (e) {
7
+ err = [e];
8
+ }
9
+ if (err)
10
+ throw err[0];
11
+ return res;
12
+ };
13
+
2
14
  // src/domain/runtime.ts
3
15
  function createRuntimeState(goalID) {
4
16
  const now = new Date().toISOString();
@@ -34,6 +46,7 @@ function acquireLease(rt, timeoutMs) {
34
46
  idleConfirmFailedAt: undefined,
35
47
  idleConfirmFailedGeneration: undefined,
36
48
  idleStuckNotifiedGeneration: undefined,
49
+ workerAbortedAt: undefined,
37
50
  activePromptObservedAt: undefined,
38
51
  activeAssistantMessageID: undefined,
39
52
  activeAssistantCompletedAt: undefined,
@@ -112,14 +125,10 @@ function removeToolCall(rt, callID) {
112
125
  }
113
126
  var PARENT_NOTIFY_DEDUPE_MS = 60000;
114
127
 
115
- // src/application/control-worker.ts
116
- import { randomUUID } from "crypto";
117
-
118
128
  // src/infrastructure/state-repository.ts
119
129
  import { promises as fs } from "fs";
120
130
  import path from "path";
121
131
  import os from "os";
122
- var CURRENT_VERSION = 6;
123
132
  function emptyState() {
124
133
  return { version: CURRENT_VERSION, revision: 0, goals: [], runtimes: [], commandLedger: [] };
125
134
  }
@@ -139,7 +148,6 @@ function lockDir(directory) {
139
148
  function lockFile(directory, key) {
140
149
  return path.join(lockDir(directory), `${key}.lock`);
141
150
  }
142
- var LOCK_STALE_MS = 1e4;
143
151
  async function acquireLock(directory, key, operation) {
144
152
  const dir = lockDir(directory);
145
153
  await fs.mkdir(dir, { recursive: true });
@@ -475,6 +483,15 @@ async function peekGoalInbox(directory, goalID) {
475
483
  function delay(ms) {
476
484
  return new Promise((resolve) => setTimeout(resolve, ms));
477
485
  }
486
+ var CURRENT_VERSION = 6, LOCK_STALE_MS = 1e4;
487
+ var init_state_repository = () => {};
488
+
489
+ // src/server/plugin.ts
490
+ import { define } from "@opencode-ai/plugin/v2/promise";
491
+
492
+ // src/application/control-worker.ts
493
+ init_state_repository();
494
+ import { randomUUID } from "crypto";
478
495
 
479
496
  // src/application/goal-policy.ts
480
497
  function resolveGoalCreationConfig(input) {
@@ -595,12 +612,17 @@ function createControlWorker(options) {
595
612
  if (running)
596
613
  return;
597
614
  running = true;
598
- processPending();
615
+ processPending().catch((error) => {
616
+ logServerEvent(directory, "control.worker.error", { detail: describeError(error) }).catch(() => {});
617
+ });
599
618
  pollTimer = setInterval(() => {
600
619
  if (running && lastProcessDone) {
601
620
  lastProcessDone = false;
602
621
  processPending().then(() => {
603
622
  lastProcessDone = true;
623
+ }, (error) => {
624
+ lastProcessDone = true;
625
+ logServerEvent(directory, "control.worker.error", { detail: describeError(error) }).catch(() => {});
604
626
  });
605
627
  }
606
628
  }, pollMs);
@@ -749,6 +771,21 @@ function createControlWorker(options) {
749
771
  };
750
772
  break;
751
773
  }
774
+ case "nudge": {
775
+ if (!request.goalID) {
776
+ response = { ...base, ok: false, message: "goalID is required", errorCode: "bad_request" };
777
+ break;
778
+ }
779
+ const result = await goalSvc.nudge(directory, request.goalID);
780
+ const state = await readState(directory);
781
+ response = {
782
+ ...base,
783
+ ok: result.ok,
784
+ message: result.message,
785
+ stateRevision: state.revision
786
+ };
787
+ break;
788
+ }
752
789
  case "clear": {
753
790
  await goalSvc.clear(directory, request.goalID);
754
791
  const state = await readState(directory);
@@ -770,12 +807,27 @@ function createControlWorker(options) {
770
807
  response = { ...base, ok: false, message: "goalID is required", errorCode: "bad_request" };
771
808
  break;
772
809
  }
773
- await appendGoalInbox(directory, request.goalID, "user", text);
810
+ const sent = await goalSvc.sendUserMessage(directory, request.goalID, text);
774
811
  const state = await readState(directory);
775
- const goal = state.goals.find((g) => g.id === request.goalID);
776
812
  response = {
777
813
  ...base,
778
- message: `sent to "${goal?.name || request.goalID}"`,
814
+ ok: sent.ok,
815
+ message: sent.message,
816
+ stateRevision: state.revision
817
+ };
818
+ break;
819
+ }
820
+ case "abort_worker": {
821
+ if (!request.goalID) {
822
+ response = { ...base, ok: false, message: "goalID is required", errorCode: "bad_request" };
823
+ break;
824
+ }
825
+ const result = await goalSvc.abortWorker(directory, request.goalID);
826
+ const state = await readState(directory);
827
+ response = {
828
+ ...base,
829
+ ok: result.ok,
830
+ message: result.message,
779
831
  stateRevision: state.revision
780
832
  };
781
833
  break;
@@ -896,6 +948,7 @@ function createControlWorker(options) {
896
948
  }
897
949
 
898
950
  // src/application/loop-engine.ts
951
+ init_state_repository();
899
952
  import { randomUUID as randomUUID2 } from "crypto";
900
953
 
901
954
  // src/domain/goal.ts
@@ -1835,6 +1888,7 @@ function createLoopEngine(options) {
1835
1888
 
1836
1889
  // src/application/goal-service.ts
1837
1890
  import { randomUUID as randomUUID3 } from "crypto";
1891
+ init_state_repository();
1838
1892
  import * as path2 from "path";
1839
1893
  import { promises as fs2 } from "fs";
1840
1894
 
@@ -2053,6 +2107,16 @@ function createWorkerManager(host) {
2053
2107
  });
2054
2108
  return result;
2055
2109
  },
2110
+ async sendBare(worker, goal, runtime, text) {
2111
+ const result = await host.promptWorker({
2112
+ sessionID: worker.workerSessionID,
2113
+ prompt: text,
2114
+ messageID: runtime.activePromptMessageID,
2115
+ agent: goal.config.agent,
2116
+ model: parseModelRef(goal.config.model)
2117
+ });
2118
+ return result;
2119
+ },
2056
2120
  async isIdle(workerSessionID) {
2057
2121
  const status = await host.sessionStatus(workerSessionID);
2058
2122
  return status === "idle";
@@ -2511,6 +2575,20 @@ function createGoalService(host) {
2511
2575
  timestamp: new Date().toISOString(),
2512
2576
  revision: state.revision
2513
2577
  });
2578
+ if (opts?.bare) {
2579
+ const bareWords = await drainGoalInbox(directory, goalID);
2580
+ const bareText = bareWords.join(`
2581
+ `).trim();
2582
+ if (bareText) {
2583
+ try {
2584
+ await workers.sendBare(session, freshGoal, freshRuntime, bareText);
2585
+ } catch (error) {
2586
+ await recordPromptFailure(directory, goalID, error);
2587
+ throw error;
2588
+ }
2589
+ return;
2590
+ }
2591
+ }
2514
2592
  const inboxMessages = await drainGoalInbox(directory, goalID);
2515
2593
  const allEvents = await readEvents(directory, 200);
2516
2594
  const progressHistory = allEvents.filter((e) => e.goalID === goalID && e.type === "goal.progress").map((e) => ({
@@ -2818,6 +2896,40 @@ function createGoalService(host) {
2818
2896
  await continueTurnUnlocked(directory, goalID, { force: true });
2819
2897
  return { ok: true, message: `Re-prompted worker for "${freshGoal.name}".` };
2820
2898
  }
2899
+ async function sendUnlocked(directory, goalID, text) {
2900
+ const trimmed = text.trim();
2901
+ if (!trimmed)
2902
+ return { ok: false, message: "Nothing to send." };
2903
+ const preState = await readState(directory);
2904
+ const goal = preState.goals.find((g) => g.id === goalID);
2905
+ if (!goal)
2906
+ return { ok: false, message: "Goal not found." };
2907
+ await appendGoalInbox(directory, goalID, "user", trimmed);
2908
+ if (goal.status !== "active") {
2909
+ return { ok: true, message: `Queued for "${goal.name}" (goal is ${goal.status}; delivers on the next active turn).` };
2910
+ }
2911
+ const cleared = await mutateState(directory, `goal.send:${goalID}`, async (s) => {
2912
+ const rt = s.runtimes.find((r) => r.goalID === goalID);
2913
+ if (!rt)
2914
+ return s;
2915
+ rt.phase = "idle";
2916
+ rt.activeRunID = undefined;
2917
+ rt.idleCandidateAt = undefined;
2918
+ rt.activePromptMessageID = undefined;
2919
+ rt.activeToolCallIDs = [];
2920
+ rt.updatedAt = new Date().toISOString();
2921
+ return s;
2922
+ });
2923
+ const freshGoal = cleared.goals.find((g) => g.id === goalID);
2924
+ if (!freshGoal || !freshGoal.workerSessionID) {
2925
+ return { ok: true, message: `Queued for "${goal.name}" (no worker session yet; delivers on the next turn).` };
2926
+ }
2927
+ await continueTurnUnlocked(directory, goalID, { force: true, bare: true });
2928
+ return { ok: true, message: `Sent to "${freshGoal.name}" as its own turn.` };
2929
+ }
2930
+ function sendUserMessage(directory, goalID, text) {
2931
+ return withGoalOperation(goalID, () => sendUnlocked(directory, goalID, text));
2932
+ }
2821
2933
  function continueTurn(directory, goalID, opts) {
2822
2934
  return withGoalOperation(goalID, () => continueTurnUnlocked(directory, goalID, opts));
2823
2935
  }
@@ -2836,13 +2948,52 @@ function createGoalService(host) {
2836
2948
  function nudge(directory, goalID) {
2837
2949
  return withGoalOperation(goalID, () => nudgeUnlocked(directory, goalID));
2838
2950
  }
2951
+ async function abortWorkerUnlocked(directory, goalID) {
2952
+ const preState = await readState(directory);
2953
+ const goal = preState.goals.find((g) => g.id === goalID);
2954
+ if (!goal)
2955
+ return { ok: false, message: "Goal not found." };
2956
+ const workerID = sessions.get(goalID)?.workerSessionID || goal.workerSessionID;
2957
+ if (!workerID)
2958
+ return { ok: false, message: `Goal "${goal.name}" has no worker session to abort.` };
2959
+ try {
2960
+ await workers.abortWorker(workerID);
2961
+ } catch {}
2962
+ sessions.delete(goalID);
2963
+ await mutateState(directory, `goal.abort-worker:${goalID}`, async (s) => {
2964
+ const rt = s.runtimes.find((r) => r.goalID === goalID);
2965
+ if (rt) {
2966
+ Object.assign(rt, releaseLease(rt));
2967
+ rt.activeRunID = undefined;
2968
+ rt.activePromptMessageID = undefined;
2969
+ rt.activeToolCallIDs = [];
2970
+ rt.idleCandidateAt = undefined;
2971
+ rt.idleCandidateGeneration = undefined;
2972
+ rt.workerAbortedAt = new Date().toISOString();
2973
+ rt.updatedAt = new Date().toISOString();
2974
+ }
2975
+ const g = s.goals.find((item) => item.id === goalID);
2976
+ if (g)
2977
+ g.updatedAt = new Date().toISOString();
2978
+ return s;
2979
+ });
2980
+ await logServerEvent(directory, "worker.aborted-manual", { goalID, workerSessionID: workerID });
2981
+ return {
2982
+ ok: true,
2983
+ message: `Worker run for "${goal.name}" aborted, session kept for inspection (status unchanged: ${goal.status}).` + (goal.status === "active" ? " Engine continues the same session next turn." : "")
2984
+ };
2985
+ }
2986
+ function abortWorker(directory, goalID) {
2987
+ return withGoalOperation(goalID, () => abortWorkerUnlocked(directory, goalID));
2988
+ }
2839
2989
  function accountUsage(directory, goalID) {
2840
2990
  return withGoalOperation(goalID, () => accountUsageUnlocked(directory, goalID));
2841
2991
  }
2842
- return { start, continueTurn, nudge, pause, resume, retry, clear, getWorker, getActiveWorkers, reconcile, accountUsage };
2992
+ return { start, continueTurn, nudge, pause, resume, retry, clear, getWorker, getActiveWorkers, reconcile, accountUsage, abortWorker, sendUserMessage };
2843
2993
  }
2844
2994
 
2845
2995
  // src/application/schedule-worker.ts
2996
+ init_state_repository();
2846
2997
  import { randomUUID as randomUUID4 } from "crypto";
2847
2998
  function createScheduleWorker(options) {
2848
2999
  const { directory, goalService } = options;
@@ -2957,6 +3108,7 @@ function createScheduleWorker(options) {
2957
3108
  }
2958
3109
 
2959
3110
  // src/server/goal-tools.ts
3111
+ init_state_repository();
2960
3112
  import { randomUUID as randomUUID5 } from "crypto";
2961
3113
  import { tool } from "@opencode-ai/plugin/tool";
2962
3114
  // src/domain/verification.ts
@@ -3534,6 +3686,7 @@ async function runCompletionChecks(checks, cwd) {
3534
3686
  }
3535
3687
 
3536
3688
  // src/server/owner-tools.ts
3689
+ init_state_repository();
3537
3690
  import { tool as tool2 } from "@opencode-ai/plugin/tool";
3538
3691
  import { promises as fs3 } from "fs";
3539
3692
  function withTimeout2(promise, ms) {
@@ -3698,6 +3851,7 @@ function ownerTools(options) {
3698
3851
  unknownStatusCount: runtime.unknownStatusCount,
3699
3852
  lastUnknownStatusAt: runtime.lastUnknownStatusAt,
3700
3853
  workerUnreachableNotifiedAt: runtime.workerUnreachableNotifiedAt,
3854
+ workerAbortedAt: runtime.workerAbortedAt,
3701
3855
  retryAfter: runtime.retryAfter,
3702
3856
  forceFinishRequested: runtime.forceFinishRequested,
3703
3857
  scheduleRunCount: runtime.scheduleRunCount,
@@ -3760,7 +3914,7 @@ function ownerTools(options) {
3760
3914
  }
3761
3915
  }),
3762
3916
  send_goal_input: tool2({
3763
- description: "Send an inbox message to the worker (injected as ## USER INSTRUCTIONS on next turn). Use to answer `question`, redirect, or refine scope. Does NOT itself re-prompt \u2014 the engine re-prompts on next idle/maintenance; use nudge_goal if the worker is stuck with no activity.",
3917
+ description: "Send bare words to the worker as their own turn (no steering wrapper). Delivers immediately if the goal is active, otherwise queues for the next active turn. Use to answer `question`, redirect, or nudge with a short message.",
3764
3918
  args: {
3765
3919
  goal_id: tool2.schema.string().optional().describe("Goal ID. Omit to target the first active goal."),
3766
3920
  message: tool2.schema.string().describe("Message to send to the worker.")
@@ -3775,14 +3929,14 @@ function ownerTools(options) {
3775
3929
  output: JSON.stringify({ ok: false, message: "No matching active goal for this session." })
3776
3930
  };
3777
3931
  }
3778
- await appendGoalInbox(directory, goal.id, "user", args.message);
3932
+ const result = await goalService.sendUserMessage(directory, goal.id, args.message);
3779
3933
  return {
3780
- title: "Message sent",
3934
+ title: result.ok ? "Message sent" : "Send failed",
3781
3935
  output: JSON.stringify({
3782
- ok: true,
3936
+ ok: result.ok,
3783
3937
  goalID: goal.id,
3784
3938
  goalName: goal.name,
3785
- message: `Message delivered to "${goal.name}". It will appear in the worker's next turn.`
3939
+ message: result.message
3786
3940
  })
3787
3941
  };
3788
3942
  }
@@ -3915,6 +4069,141 @@ function ownerTools(options) {
3915
4069
  }
3916
4070
  }
3917
4071
  }),
4072
+ abort_goal_worker: tool2({
4073
+ description: "Abort the worker session only (N / :abort in TUI). Keeps goal+transcript+session browsable, clears the run lease. Use for compaction-spin or stuck runs. Status unchanged; active goals resume next turn in the same session.",
4074
+ args: {
4075
+ goal_id: tool2.schema.string().optional().describe("Goal ID. Omit to abort the first active goal.")
4076
+ },
4077
+ execute: async (args, context) => {
4078
+ const state = await readState(directory);
4079
+ const ownerID = context?.sessionID;
4080
+ const goal = args.goal_id ? state.goals.find((g) => g.id === args.goal_id && g.ownerSessionID === ownerID) : state.goals.find((g) => g.ownerSessionID === ownerID && g.status !== "complete");
4081
+ if (!goal) {
4082
+ return {
4083
+ title: "No goal found",
4084
+ output: JSON.stringify({ ok: false, message: "No matching active goal for this session." })
4085
+ };
4086
+ }
4087
+ try {
4088
+ const result = await goalService.abortWorker(directory, goal.id);
4089
+ return {
4090
+ title: result.ok ? "Worker aborted" : "Abort failed",
4091
+ output: JSON.stringify({ ...result, goalID: goal.id, goalName: goal.name })
4092
+ };
4093
+ } catch (error) {
4094
+ return {
4095
+ title: "Abort failed",
4096
+ output: JSON.stringify({
4097
+ ok: false,
4098
+ goalName: goal.name,
4099
+ message: error instanceof Error ? error.message : String(error)
4100
+ })
4101
+ };
4102
+ }
4103
+ }
4104
+ }),
4105
+ force_complete_goal: tool2({
4106
+ description: "Force-complete a goal with summary/evidence, bypassing checks (:force in TUI). Use when the worker produced the right artifact but checks are stale or you have verified manually.",
4107
+ args: {
4108
+ goal_id: tool2.schema.string().optional().describe("Goal ID. Omit to target the first non-complete goal."),
4109
+ summary: tool2.schema.string().describe("What was completed."),
4110
+ evidence: tool2.schema.string().describe("Concrete evidence of completion.")
4111
+ },
4112
+ execute: async (args, context) => {
4113
+ const state = await readState(directory);
4114
+ const ownerID = context?.sessionID;
4115
+ const goal = args.goal_id ? state.goals.find((g) => g.id === args.goal_id && g.ownerSessionID === ownerID) : state.goals.find((g) => g.ownerSessionID === ownerID && g.status !== "complete");
4116
+ if (!goal) {
4117
+ return {
4118
+ title: "No goal found",
4119
+ output: JSON.stringify({ ok: false, message: "No matching active goal for this session." })
4120
+ };
4121
+ }
4122
+ await Promise.resolve().then(() => init_state_repository());
4123
+ await Promise.resolve();
4124
+ const { randomUUID } = await import("crypto");
4125
+ const st = await readState(directory);
4126
+ const g = st.goals.find((x) => x.id === goal.id);
4127
+ if (!g)
4128
+ return { title: "No goal", output: JSON.stringify({ ok: false, message: "Goal not found." }) };
4129
+ if (g.status === "complete") {
4130
+ return { title: "Already complete", output: JSON.stringify({ ok: true, message: `Goal "${g.name}" already complete.` }) };
4131
+ }
4132
+ g.status = "complete";
4133
+ g.updatedAt = new Date().toISOString();
4134
+ g.completionEvidence = { summary: args.summary, evidence: args.evidence, at: new Date().toISOString() };
4135
+ const rt = st.runtimes.find((r) => r.goalID === goal.id);
4136
+ if (rt) {
4137
+ Object.assign(rt, releaseLease(rt));
4138
+ rt.activeRunID = undefined;
4139
+ rt.lastError = undefined;
4140
+ rt.updatedAt = new Date().toISOString();
4141
+ }
4142
+ await writeState(directory, st);
4143
+ await appendEvent(directory, {
4144
+ version: 1,
4145
+ eventID: randomUUID(),
4146
+ goalID: goal.id,
4147
+ type: "goal.completed",
4148
+ summary: args.summary,
4149
+ evidence: args.evidence,
4150
+ timestamp: new Date().toISOString(),
4151
+ revision: st.revision
4152
+ });
4153
+ return { title: "Goal force-completed", output: JSON.stringify({ ok: true, goalID: goal.id, goalName: g.name }) };
4154
+ }
4155
+ }),
4156
+ force_block_goal: tool2({
4157
+ description: "Force-block a goal with reason/needed (:block in TUI). Use when the goal is stuck on an external blocker and should stop retrying.",
4158
+ args: {
4159
+ goal_id: tool2.schema.string().optional().describe("Goal ID. Omit to target the first non-complete goal."),
4160
+ reason: tool2.schema.string().describe("Why the goal is blocked."),
4161
+ needed: tool2.schema.string().describe("What is needed to unblock.")
4162
+ },
4163
+ execute: async (args, context) => {
4164
+ const state = await readState(directory);
4165
+ const ownerID = context?.sessionID;
4166
+ const goal = args.goal_id ? state.goals.find((g) => g.id === args.goal_id && g.ownerSessionID === ownerID) : state.goals.find((g) => g.ownerSessionID === ownerID && g.status !== "complete");
4167
+ if (!goal) {
4168
+ return {
4169
+ title: "No goal found",
4170
+ output: JSON.stringify({ ok: false, message: "No matching active goal for this session." })
4171
+ };
4172
+ }
4173
+ await Promise.resolve().then(() => init_state_repository());
4174
+ await Promise.resolve();
4175
+ const { randomUUID } = await import("crypto");
4176
+ const st = await readState(directory);
4177
+ const g = st.goals.find((x) => x.id === goal.id);
4178
+ if (!g)
4179
+ return { title: "No goal", output: JSON.stringify({ ok: false, message: "Goal not found." }) };
4180
+ if (g.status === "blocked") {
4181
+ return { title: "Already blocked", output: JSON.stringify({ ok: true, message: `Goal "${g.name}" already blocked.` }) };
4182
+ }
4183
+ g.status = "blocked";
4184
+ g.updatedAt = new Date().toISOString();
4185
+ g.blocker = { reason: args.reason, needed: args.needed, at: new Date().toISOString() };
4186
+ const rt = st.runtimes.find((r) => r.goalID === goal.id);
4187
+ if (rt) {
4188
+ Object.assign(rt, releaseLease(rt));
4189
+ rt.activeRunID = undefined;
4190
+ rt.lastError = undefined;
4191
+ rt.updatedAt = new Date().toISOString();
4192
+ }
4193
+ await writeState(directory, st);
4194
+ await appendEvent(directory, {
4195
+ version: 1,
4196
+ eventID: randomUUID(),
4197
+ goalID: goal.id,
4198
+ type: "goal.blocked",
4199
+ reason: args.reason,
4200
+ needed: args.needed,
4201
+ timestamp: new Date().toISOString(),
4202
+ revision: st.revision
4203
+ });
4204
+ return { title: "Goal blocked", output: JSON.stringify({ ok: true, goalID: goal.id, goalName: g.name }) };
4205
+ }
4206
+ }),
3918
4207
  clear_goal: tool2({
3919
4208
  description: "Clear a goal: aborts worker and removes goal+runtime+ledger (cannot be undone). Use when the goal is no longer needed or to free a stuck writer slot after inspection.",
3920
4209
  args: {
@@ -3955,7 +4244,9 @@ function ownerTools(options) {
3955
4244
  })
3956
4245
  };
3957
4246
  }
4247
+
3958
4248
  // src/server/plugin.ts
4249
+ init_state_repository();
3959
4250
  var PLUGIN_ID = "opencode-loopd.server";
3960
4251
  var server = async ({ client, directory }, pluginOptions) => {
3961
4252
  const defaults = parsePluginDefaults(pluginOptions);
@@ -4099,9 +4390,14 @@ function parsePluginDefaults(options) {
4099
4390
  defaultChecks: checks.length > 0 ? checks : undefined
4100
4391
  };
4101
4392
  }
4393
+ var v2 = define({
4394
+ id: PLUGIN_ID,
4395
+ async setup() {}
4396
+ });
4102
4397
  var plugin_default = {
4103
4398
  id: PLUGIN_ID,
4104
- server
4399
+ server,
4400
+ setup: v2.setup
4105
4401
  };
4106
4402
  export {
4107
4403
  plugin_default as default