@sjawhar/opencode-legion-envoy 0.9.0 → 0.10.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.
@@ -1,7 +1,7 @@
1
1
  // @bun
2
2
  // src/server.ts
3
- import { existsSync as existsSync3 } from "fs";
4
- import path4 from "path";
3
+ import { existsSync } from "fs";
4
+ import path3 from "path";
5
5
  import { fileURLToPath } from "url";
6
6
 
7
7
  // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/core/core.js
@@ -86,6 +86,9 @@ function getEnumValues(entries) {
86
86
  const values = Object.entries(entries).filter(([k, _]) => numericValues.indexOf(+k) === -1).map(([_, v]) => v);
87
87
  return values;
88
88
  }
89
+ function joinValues(array, separator = "|") {
90
+ return array.map((val) => stringifyPrimitive(val)).join(separator);
91
+ }
89
92
  function jsonStringifyReplacer(_, value) {
90
93
  if (typeof value === "bigint")
91
94
  return value.toString();
@@ -237,6 +240,13 @@ function normalizeParams(_params) {
237
240
  return { ...params, error: () => params.error };
238
241
  return params;
239
242
  }
243
+ function stringifyPrimitive(value) {
244
+ if (typeof value === "bigint")
245
+ return value.toString() + "n";
246
+ if (typeof value === "string")
247
+ return `"${value}"`;
248
+ return `${value}`;
249
+ }
240
250
  function optionalKeys(shape) {
241
251
  return Object.keys(shape).filter((k) => {
242
252
  return shape[k]._zod.optin === "optional" && shape[k]._zod.optout === "optional";
@@ -459,6 +469,27 @@ function getLengthableOrigin(input) {
459
469
  return "string";
460
470
  return "unknown";
461
471
  }
472
+ function parsedType(data) {
473
+ const t = typeof data;
474
+ switch (t) {
475
+ case "number": {
476
+ return Number.isNaN(data) ? "nan" : "number";
477
+ }
478
+ case "object": {
479
+ if (data === null) {
480
+ return "null";
481
+ }
482
+ if (Array.isArray(data)) {
483
+ return "array";
484
+ }
485
+ const obj = data;
486
+ if (obj && Object.getPrototypeOf(obj) !== Object.prototype && "constructor" in obj && obj.constructor) {
487
+ return obj.constructor.name;
488
+ }
489
+ }
490
+ }
491
+ return t;
492
+ }
462
493
  function issue(...args) {
463
494
  const [iss, input, inst] = args;
464
495
  if (typeof iss === "string") {
@@ -2473,6 +2504,112 @@ function handleRefineResult(result, payload, input, inst) {
2473
2504
  payload.issues.push(issue(_iss));
2474
2505
  }
2475
2506
  }
2507
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/en.js
2508
+ var error = () => {
2509
+ const Sizable = {
2510
+ string: { unit: "characters", verb: "to have" },
2511
+ file: { unit: "bytes", verb: "to have" },
2512
+ array: { unit: "items", verb: "to have" },
2513
+ set: { unit: "items", verb: "to have" },
2514
+ map: { unit: "entries", verb: "to have" }
2515
+ };
2516
+ function getSizing(origin) {
2517
+ return Sizable[origin] ?? null;
2518
+ }
2519
+ const FormatDictionary = {
2520
+ regex: "input",
2521
+ email: "email address",
2522
+ url: "URL",
2523
+ emoji: "emoji",
2524
+ uuid: "UUID",
2525
+ uuidv4: "UUIDv4",
2526
+ uuidv6: "UUIDv6",
2527
+ nanoid: "nanoid",
2528
+ guid: "GUID",
2529
+ cuid: "cuid",
2530
+ cuid2: "cuid2",
2531
+ ulid: "ULID",
2532
+ xid: "XID",
2533
+ ksuid: "KSUID",
2534
+ datetime: "ISO datetime",
2535
+ date: "ISO date",
2536
+ time: "ISO time",
2537
+ duration: "ISO duration",
2538
+ ipv4: "IPv4 address",
2539
+ ipv6: "IPv6 address",
2540
+ mac: "MAC address",
2541
+ cidrv4: "IPv4 range",
2542
+ cidrv6: "IPv6 range",
2543
+ base64: "base64-encoded string",
2544
+ base64url: "base64url-encoded string",
2545
+ json_string: "JSON string",
2546
+ e164: "E.164 number",
2547
+ jwt: "JWT",
2548
+ template_literal: "input"
2549
+ };
2550
+ const TypeDictionary = {
2551
+ nan: "NaN"
2552
+ };
2553
+ return (issue) => {
2554
+ switch (issue.code) {
2555
+ case "invalid_type": {
2556
+ const expected = TypeDictionary[issue.expected] ?? issue.expected;
2557
+ const receivedType = parsedType(issue.input);
2558
+ const received = TypeDictionary[receivedType] ?? receivedType;
2559
+ return `Invalid input: expected ${expected}, received ${received}`;
2560
+ }
2561
+ case "invalid_value":
2562
+ if (issue.values.length === 1)
2563
+ return `Invalid input: expected ${stringifyPrimitive(issue.values[0])}`;
2564
+ return `Invalid option: expected one of ${joinValues(issue.values, "|")}`;
2565
+ case "too_big": {
2566
+ const adj = issue.inclusive ? "<=" : "<";
2567
+ const sizing = getSizing(issue.origin);
2568
+ if (sizing)
2569
+ return `Too big: expected ${issue.origin ?? "value"} to have ${adj}${issue.maximum.toString()} ${sizing.unit ?? "elements"}`;
2570
+ return `Too big: expected ${issue.origin ?? "value"} to be ${adj}${issue.maximum.toString()}`;
2571
+ }
2572
+ case "too_small": {
2573
+ const adj = issue.inclusive ? ">=" : ">";
2574
+ const sizing = getSizing(issue.origin);
2575
+ if (sizing) {
2576
+ return `Too small: expected ${issue.origin} to have ${adj}${issue.minimum.toString()} ${sizing.unit}`;
2577
+ }
2578
+ return `Too small: expected ${issue.origin} to be ${adj}${issue.minimum.toString()}`;
2579
+ }
2580
+ case "invalid_format": {
2581
+ const _issue = issue;
2582
+ if (_issue.format === "starts_with") {
2583
+ return `Invalid string: must start with "${_issue.prefix}"`;
2584
+ }
2585
+ if (_issue.format === "ends_with")
2586
+ return `Invalid string: must end with "${_issue.suffix}"`;
2587
+ if (_issue.format === "includes")
2588
+ return `Invalid string: must include "${_issue.includes}"`;
2589
+ if (_issue.format === "regex")
2590
+ return `Invalid string: must match pattern ${_issue.pattern}`;
2591
+ return `Invalid ${FormatDictionary[_issue.format] ?? issue.format}`;
2592
+ }
2593
+ case "not_multiple_of":
2594
+ return `Invalid number: must be a multiple of ${issue.divisor}`;
2595
+ case "unrecognized_keys":
2596
+ return `Unrecognized key${issue.keys.length > 1 ? "s" : ""}: ${joinValues(issue.keys, ", ")}`;
2597
+ case "invalid_key":
2598
+ return `Invalid key in ${issue.origin}`;
2599
+ case "invalid_union":
2600
+ return "Invalid input";
2601
+ case "invalid_element":
2602
+ return `Invalid value in ${issue.origin}`;
2603
+ default:
2604
+ return `Invalid input`;
2605
+ }
2606
+ };
2607
+ };
2608
+ function en_default() {
2609
+ return {
2610
+ localeError: error()
2611
+ };
2612
+ }
2476
2613
  // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/core/registries.js
2477
2614
  var _a;
2478
2615
  var $output = Symbol("ZodOutput");
@@ -3415,10 +3552,45 @@ var numberProcessor = (schema, ctx, _json, _params) => {
3415
3552
  var booleanProcessor = (_schema, _ctx, json, _params) => {
3416
3553
  json.type = "boolean";
3417
3554
  };
3555
+ var bigintProcessor = (_schema, ctx, _json, _params) => {
3556
+ if (ctx.unrepresentable === "throw") {
3557
+ throw new Error("BigInt cannot be represented in JSON Schema");
3558
+ }
3559
+ };
3560
+ var symbolProcessor = (_schema, ctx, _json, _params) => {
3561
+ if (ctx.unrepresentable === "throw") {
3562
+ throw new Error("Symbols cannot be represented in JSON Schema");
3563
+ }
3564
+ };
3565
+ var nullProcessor = (_schema, ctx, json, _params) => {
3566
+ if (ctx.target === "openapi-3.0") {
3567
+ json.type = "string";
3568
+ json.nullable = true;
3569
+ json.enum = [null];
3570
+ } else {
3571
+ json.type = "null";
3572
+ }
3573
+ };
3574
+ var undefinedProcessor = (_schema, ctx, _json, _params) => {
3575
+ if (ctx.unrepresentable === "throw") {
3576
+ throw new Error("Undefined cannot be represented in JSON Schema");
3577
+ }
3578
+ };
3579
+ var voidProcessor = (_schema, ctx, _json, _params) => {
3580
+ if (ctx.unrepresentable === "throw") {
3581
+ throw new Error("Void cannot be represented in JSON Schema");
3582
+ }
3583
+ };
3418
3584
  var neverProcessor = (_schema, _ctx, json, _params) => {
3419
3585
  json.not = {};
3420
3586
  };
3587
+ var anyProcessor = (_schema, _ctx, _json, _params) => {};
3421
3588
  var unknownProcessor = (_schema, _ctx, _json, _params) => {};
3589
+ var dateProcessor = (_schema, ctx, _json, _params) => {
3590
+ if (ctx.unrepresentable === "throw") {
3591
+ throw new Error("Date cannot be represented in JSON Schema");
3592
+ }
3593
+ };
3422
3594
  var enumProcessor = (schema, _ctx, json, _params) => {
3423
3595
  const def = schema._zod.def;
3424
3596
  const values = getEnumValues(def.entries);
@@ -3466,16 +3638,71 @@ var literalProcessor = (schema, ctx, json, _params) => {
3466
3638
  json.enum = vals;
3467
3639
  }
3468
3640
  };
3641
+ var nanProcessor = (_schema, ctx, _json, _params) => {
3642
+ if (ctx.unrepresentable === "throw") {
3643
+ throw new Error("NaN cannot be represented in JSON Schema");
3644
+ }
3645
+ };
3646
+ var templateLiteralProcessor = (schema, _ctx, json, _params) => {
3647
+ const _json = json;
3648
+ const pattern = schema._zod.pattern;
3649
+ if (!pattern)
3650
+ throw new Error("Pattern not found in template literal");
3651
+ _json.type = "string";
3652
+ _json.pattern = pattern.source;
3653
+ };
3654
+ var fileProcessor = (schema, _ctx, json, _params) => {
3655
+ const _json = json;
3656
+ const file = {
3657
+ type: "string",
3658
+ format: "binary",
3659
+ contentEncoding: "binary"
3660
+ };
3661
+ const { minimum, maximum, mime } = schema._zod.bag;
3662
+ if (minimum !== undefined)
3663
+ file.minLength = minimum;
3664
+ if (maximum !== undefined)
3665
+ file.maxLength = maximum;
3666
+ if (mime) {
3667
+ if (mime.length === 1) {
3668
+ file.contentMediaType = mime[0];
3669
+ Object.assign(_json, file);
3670
+ } else {
3671
+ Object.assign(_json, file);
3672
+ _json.anyOf = mime.map((m) => ({ contentMediaType: m }));
3673
+ }
3674
+ } else {
3675
+ Object.assign(_json, file);
3676
+ }
3677
+ };
3678
+ var successProcessor = (_schema, _ctx, json, _params) => {
3679
+ json.type = "boolean";
3680
+ };
3469
3681
  var customProcessor = (_schema, ctx, _json, _params) => {
3470
3682
  if (ctx.unrepresentable === "throw") {
3471
3683
  throw new Error("Custom types cannot be represented in JSON Schema");
3472
3684
  }
3473
3685
  };
3686
+ var functionProcessor = (_schema, ctx, _json, _params) => {
3687
+ if (ctx.unrepresentable === "throw") {
3688
+ throw new Error("Function types cannot be represented in JSON Schema");
3689
+ }
3690
+ };
3474
3691
  var transformProcessor = (_schema, ctx, _json, _params) => {
3475
3692
  if (ctx.unrepresentable === "throw") {
3476
3693
  throw new Error("Transforms cannot be represented in JSON Schema");
3477
3694
  }
3478
3695
  };
3696
+ var mapProcessor = (_schema, ctx, _json, _params) => {
3697
+ if (ctx.unrepresentable === "throw") {
3698
+ throw new Error("Map cannot be represented in JSON Schema");
3699
+ }
3700
+ };
3701
+ var setProcessor = (_schema, ctx, _json, _params) => {
3702
+ if (ctx.unrepresentable === "throw") {
3703
+ throw new Error("Set cannot be represented in JSON Schema");
3704
+ }
3705
+ };
3479
3706
  var arrayProcessor = (schema, ctx, _json, params) => {
3480
3707
  const json = _json;
3481
3708
  const def = schema._zod.def;
@@ -3553,6 +3780,48 @@ var intersectionProcessor = (schema, ctx, json, params) => {
3553
3780
  ];
3554
3781
  json.allOf = allOf;
3555
3782
  };
3783
+ var tupleProcessor = (schema, ctx, _json, params) => {
3784
+ const json = _json;
3785
+ const def = schema._zod.def;
3786
+ json.type = "array";
3787
+ const prefixPath = ctx.target === "draft-2020-12" ? "prefixItems" : "items";
3788
+ const restPath = ctx.target === "draft-2020-12" ? "items" : ctx.target === "openapi-3.0" ? "items" : "additionalItems";
3789
+ const prefixItems = def.items.map((x, i) => process2(x, ctx, {
3790
+ ...params,
3791
+ path: [...params.path, prefixPath, i]
3792
+ }));
3793
+ const rest = def.rest ? process2(def.rest, ctx, {
3794
+ ...params,
3795
+ path: [...params.path, restPath, ...ctx.target === "openapi-3.0" ? [def.items.length] : []]
3796
+ }) : null;
3797
+ if (ctx.target === "draft-2020-12") {
3798
+ json.prefixItems = prefixItems;
3799
+ if (rest) {
3800
+ json.items = rest;
3801
+ }
3802
+ } else if (ctx.target === "openapi-3.0") {
3803
+ json.items = {
3804
+ anyOf: prefixItems
3805
+ };
3806
+ if (rest) {
3807
+ json.items.anyOf.push(rest);
3808
+ }
3809
+ json.minItems = prefixItems.length;
3810
+ if (!rest) {
3811
+ json.maxItems = prefixItems.length;
3812
+ }
3813
+ } else {
3814
+ json.items = prefixItems;
3815
+ if (rest) {
3816
+ json.additionalItems = rest;
3817
+ }
3818
+ }
3819
+ const { minimum, maximum } = schema._zod.bag;
3820
+ if (typeof minimum === "number")
3821
+ json.minItems = minimum;
3822
+ if (typeof maximum === "number")
3823
+ json.maxItems = maximum;
3824
+ };
3556
3825
  var recordProcessor = (schema, ctx, _json, params) => {
3557
3826
  const json = _json;
3558
3827
  const def = schema._zod.def;
@@ -3648,12 +3917,99 @@ var readonlyProcessor = (schema, ctx, json, params) => {
3648
3917
  seen.ref = def.innerType;
3649
3918
  json.readOnly = true;
3650
3919
  };
3920
+ var promiseProcessor = (schema, ctx, _json, params) => {
3921
+ const def = schema._zod.def;
3922
+ process2(def.innerType, ctx, params);
3923
+ const seen = ctx.seen.get(schema);
3924
+ seen.ref = def.innerType;
3925
+ };
3651
3926
  var optionalProcessor = (schema, ctx, _json, params) => {
3652
3927
  const def = schema._zod.def;
3653
3928
  process2(def.innerType, ctx, params);
3654
3929
  const seen = ctx.seen.get(schema);
3655
3930
  seen.ref = def.innerType;
3656
3931
  };
3932
+ var lazyProcessor = (schema, ctx, _json, params) => {
3933
+ const innerType = schema._zod.innerType;
3934
+ process2(innerType, ctx, params);
3935
+ const seen = ctx.seen.get(schema);
3936
+ seen.ref = innerType;
3937
+ };
3938
+ var allProcessors = {
3939
+ string: stringProcessor,
3940
+ number: numberProcessor,
3941
+ boolean: booleanProcessor,
3942
+ bigint: bigintProcessor,
3943
+ symbol: symbolProcessor,
3944
+ null: nullProcessor,
3945
+ undefined: undefinedProcessor,
3946
+ void: voidProcessor,
3947
+ never: neverProcessor,
3948
+ any: anyProcessor,
3949
+ unknown: unknownProcessor,
3950
+ date: dateProcessor,
3951
+ enum: enumProcessor,
3952
+ literal: literalProcessor,
3953
+ nan: nanProcessor,
3954
+ template_literal: templateLiteralProcessor,
3955
+ file: fileProcessor,
3956
+ success: successProcessor,
3957
+ custom: customProcessor,
3958
+ function: functionProcessor,
3959
+ transform: transformProcessor,
3960
+ map: mapProcessor,
3961
+ set: setProcessor,
3962
+ array: arrayProcessor,
3963
+ object: objectProcessor,
3964
+ union: unionProcessor,
3965
+ intersection: intersectionProcessor,
3966
+ tuple: tupleProcessor,
3967
+ record: recordProcessor,
3968
+ nullable: nullableProcessor,
3969
+ nonoptional: nonoptionalProcessor,
3970
+ default: defaultProcessor,
3971
+ prefault: prefaultProcessor,
3972
+ catch: catchProcessor,
3973
+ pipe: pipeProcessor,
3974
+ readonly: readonlyProcessor,
3975
+ promise: promiseProcessor,
3976
+ optional: optionalProcessor,
3977
+ lazy: lazyProcessor
3978
+ };
3979
+ function toJSONSchema(input, params) {
3980
+ if ("_idmap" in input) {
3981
+ const registry = input;
3982
+ const ctx = initializeContext({ ...params, processors: allProcessors });
3983
+ const defs = {};
3984
+ for (const entry of registry._idmap.entries()) {
3985
+ const [_, schema] = entry;
3986
+ process2(schema, ctx);
3987
+ }
3988
+ const schemas = {};
3989
+ const external = {
3990
+ registry,
3991
+ uri: params?.uri,
3992
+ defs
3993
+ };
3994
+ ctx.external = external;
3995
+ for (const entry of registry._idmap.entries()) {
3996
+ const [key, schema] = entry;
3997
+ extractDefs(ctx, schema);
3998
+ schemas[key] = finalize(ctx, schema);
3999
+ }
4000
+ if (Object.keys(defs).length > 0) {
4001
+ const defsSegment = ctx.target === "draft-2020-12" ? "$defs" : "definitions";
4002
+ schemas.__shared = {
4003
+ [defsSegment]: defs
4004
+ };
4005
+ }
4006
+ return { schemas };
4007
+ }
4008
+ const ctx = initializeContext({ ...params, processors: allProcessors });
4009
+ process2(input, ctx);
4010
+ extractDefs(ctx, input);
4011
+ return finalize(ctx, input);
4012
+ }
3657
4013
  // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/classic/iso.js
3658
4014
  var ZodISODateTime = /* @__PURE__ */ $constructor("ZodISODateTime", (inst, def) => {
3659
4015
  $ZodISODateTime.init(inst, def);
@@ -4372,6 +4728,8 @@ function refine(fn, _params = {}) {
4372
4728
  function superRefine(fn) {
4373
4729
  return _superRefine(fn);
4374
4730
  }
4731
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/classic/external.js
4732
+ config(en_default());
4375
4733
  // ../contracts/src/envelope.ts
4376
4734
  var isSubject = (value) => typeof value === "string" && value.length > 0;
4377
4735
  var EnvelopeSchema = object({
@@ -4669,11 +5027,382 @@ function normalizeEnvoyUrl(value) {
4669
5027
  return value.replace(/\/+$/, "");
4670
5028
  }
4671
5029
 
5030
+ // ../envoy-client/src/dispatch-contract.ts
5031
+ var DISPATCH_TOOL_NAME = "dispatch";
5032
+ var DISPATCH_CONTEXT_MAX = 1200;
5033
+ var DISPATCH_QUESTION_MAX = 800;
5034
+ var DISPATCH_URGENCIES = ["low", "med", "high", "blocking"];
5035
+ var DISPATCH_TOOL_DESCRIPTION = "Raise a durable question to the human as a Dispatch thread (a GitHub issue shown on the dashboard), or continue an existing thread with a follow-up question. The reader has NOT seen your transcript. Open a thread with `subject`; continue one with `thread`. The reply arrives in this session as a steer.";
5036
+ var DISPATCH_ARGUMENTS = {
5037
+ subject: "Open a new thread: one line naming the decision needed (the issue title). Omit when continuing a thread with `thread`.",
5038
+ thread: `Continue an existing thread: "<n>" (an issue in the working directory's repo) or "owner/name#<n>". When set, omit subject, urgency, repo, and parent.`,
5039
+ context: `What you are doing, what you found, why you are stuck \u2014 at most ${DISPATCH_CONTEXT_MAX} characters, at most three short paragraphs or a bullet list. The reader has NOT seen your transcript: no nouns you coined this session, no internal identifiers unless the question is about them. GitHub references (#N, owner/repo#N, URLs) may be bare; the dashboard unfurls them.`,
5040
+ question: `The ask, at most ${DISPATCH_QUESTION_MAX} characters, as a list: current state \u2192 desired state \u2192 your recommendation and why; options go in \`ask\`.`,
5041
+ ask: "Structured questions rendered as buttons on the dashboard. Each: { question, header?, options: [{ label, description? }], multiple?, custom? }. Use this whenever the answer is one of N choices.",
5042
+ urgency: "low | med | high | blocking (default med). Opening a thread only.",
5043
+ repo: "owner/name. Opening a thread only; defaults to the working directory's GitHub repo.",
5044
+ parent: "<n> | owner/name#<n>[#<commentId>]. Opening a thread only: link the thread as a sub-issue of an existing issue and append a breadcrumb to the comment."
5045
+ };
5046
+
5047
+ class DispatchArgumentError extends Error {
5048
+ name = "DispatchArgumentError";
5049
+ }
5050
+ var QuestionOptionSchema = strictObject({
5051
+ label: string2().min(1),
5052
+ description: string2().optional()
5053
+ });
5054
+ var DispatchQuestionSchema = strictObject({
5055
+ question: string2().min(1),
5056
+ header: string2().optional(),
5057
+ options: array(QuestionOptionSchema).optional(),
5058
+ multiple: boolean2().optional(),
5059
+ custom: boolean2().optional()
5060
+ });
5061
+ var prose = {
5062
+ context: string2(),
5063
+ question: string2(),
5064
+ ask: array(DispatchQuestionSchema).optional()
5065
+ };
5066
+ var OpenThreadCallSchema = strictObject({
5067
+ subject: string2(),
5068
+ ...prose,
5069
+ urgency: _enum(DISPATCH_URGENCIES).optional(),
5070
+ repo: string2().optional(),
5071
+ parent: string2().optional()
5072
+ });
5073
+ var ContinueThreadCallSchema = strictObject({ thread: string2(), ...prose });
5074
+ var dispatchToolShape = {
5075
+ subject: string2().describe(DISPATCH_ARGUMENTS.subject).optional(),
5076
+ thread: string2().describe(DISPATCH_ARGUMENTS.thread).optional(),
5077
+ context: string2().describe(DISPATCH_ARGUMENTS.context),
5078
+ question: string2().describe(DISPATCH_ARGUMENTS.question),
5079
+ ask: array(DispatchQuestionSchema).describe(DISPATCH_ARGUMENTS.ask).optional(),
5080
+ urgency: _enum(DISPATCH_URGENCIES).describe(DISPATCH_ARGUMENTS.urgency).optional(),
5081
+ repo: string2().describe(DISPATCH_ARGUMENTS.repo).optional(),
5082
+ parent: string2().describe(DISPATCH_ARGUMENTS.parent).optional()
5083
+ };
5084
+ var DISPATCH_TOOL_JSON_SCHEMA = toJSONSchema(object(dispatchToolShape));
5085
+ function isContinueCall(call) {
5086
+ return "thread" in call;
5087
+ }
5088
+ function present(raw) {
5089
+ return Object.fromEntries(Object.entries(raw).filter(([, value]) => value !== undefined));
5090
+ }
5091
+ function describeIssues(error) {
5092
+ return error.issues.map((issue) => `${issue.path.join(".") || "(root)"}: ${issue.message}`).join("; ");
5093
+ }
5094
+ function parseDispatchCall(raw) {
5095
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
5096
+ throw new DispatchArgumentError("dispatch: invalid arguments \u2014 expected an object");
5097
+ }
5098
+ const args = present(raw);
5099
+ const hasSubject = "subject" in args;
5100
+ const hasThread = "thread" in args;
5101
+ if (hasSubject && hasThread) {
5102
+ throw new DispatchArgumentError("dispatch: pass either subject (open a thread) or thread (continue one), not both");
5103
+ }
5104
+ if (!hasSubject && !hasThread) {
5105
+ throw new DispatchArgumentError("dispatch: subject or thread is required");
5106
+ }
5107
+ if (hasThread && (("urgency" in args) || ("repo" in args) || ("parent" in args))) {
5108
+ throw new DispatchArgumentError("dispatch: thread cannot be combined with urgency, repo, or parent");
5109
+ }
5110
+ const parsed = hasThread ? ContinueThreadCallSchema.safeParse(args) : OpenThreadCallSchema.safeParse(args);
5111
+ if (!parsed.success) {
5112
+ throw new DispatchArgumentError(`dispatch: invalid arguments \u2014 ${describeIssues(parsed.error)}`);
5113
+ }
5114
+ return parsed.data;
5115
+ }
5116
+
5117
+ // ../envoy-client/src/dispatch-cwd.ts
5118
+ import { execFile } from "child_process";
5119
+ import { promisify } from "util";
5120
+
5121
+ // ../envoy-client/src/machine.ts
5122
+ import { hostname } from "os";
5123
+ function machineID() {
5124
+ return process.env["ENVOY_MACHINE_ID"] || hostname();
5125
+ }
5126
+
5127
+ // ../envoy-client/src/dispatch-cwd.ts
5128
+ var execFileAsync = promisify(execFile);
5129
+ var defaultExec = (file, args, options) => execFileAsync(file, args, { cwd: options.cwd, timeout: 5000 });
5130
+ async function tryExec(exec, file, args, cwd) {
5131
+ try {
5132
+ const { stdout } = await exec(file, args, { cwd });
5133
+ return stdout;
5134
+ } catch {
5135
+ return null;
5136
+ }
5137
+ }
5138
+ function parseRemoteList(stdout) {
5139
+ const remotes = new Map;
5140
+ for (const line of stdout.split(`
5141
+ `)) {
5142
+ const trimmed = line.trim();
5143
+ if (!trimmed)
5144
+ continue;
5145
+ const match = trimmed.match(/^(\S+)\s+(\S+)/);
5146
+ const [, name, url] = match ?? [];
5147
+ if (name && url)
5148
+ remotes.set(name, url);
5149
+ }
5150
+ return remotes;
5151
+ }
5152
+ function selectRemoteUrl(remotes) {
5153
+ const origin = remotes.get("origin");
5154
+ if (origin)
5155
+ return origin;
5156
+ const candidates = [...remotes.entries()].filter(([name]) => name !== "upstream");
5157
+ return candidates.length === 1 ? candidates[0]?.[1] ?? null : null;
5158
+ }
5159
+ var GITHUB_REMOTE_PATTERNS = [
5160
+ /^https:\/\/(?:[^@/\s]+@)?github\.com\/([^/]+)\/([^/]+?)(?:\.git)?\/?$/i,
5161
+ /^git@github\.com:([^/]+)\/([^/]+?)(?:\.git)?\/?$/i,
5162
+ /^ssh:\/\/git@github\.com\/([^/]+)\/([^/]+?)(?:\.git)?\/?$/i
5163
+ ];
5164
+ function parseGitHubRemoteUrl(url) {
5165
+ const trimmed = url.trim();
5166
+ for (const pattern of GITHUB_REMOTE_PATTERNS) {
5167
+ const match = trimmed.match(pattern);
5168
+ if (match)
5169
+ return `${match[1]}/${match[2]}`;
5170
+ }
5171
+ return null;
5172
+ }
5173
+ async function resolveCwdRepo(cwd, exec) {
5174
+ const jjOutput = await tryExec(exec, "jj", ["git", "remote", "list"], cwd);
5175
+ if (jjOutput !== null) {
5176
+ const url = selectRemoteUrl(parseRemoteList(jjOutput));
5177
+ return url ? parseGitHubRemoteUrl(url) : null;
5178
+ }
5179
+ const originUrl = await tryExec(exec, "git", ["remote", "get-url", "origin"], cwd);
5180
+ return originUrl ? parseGitHubRemoteUrl(originUrl) : null;
5181
+ }
5182
+ async function resolveOrigin(env, exec, cwd) {
5183
+ const origin = {
5184
+ cwd,
5185
+ machine: machineID()
5186
+ };
5187
+ const pane = env["TMUX_PANE"];
5188
+ if (pane) {
5189
+ const output = await tryExec(exec, "tmux", ["display-message", "-p", "-t", pane, "#S:#I.#P #{pane_id}"], cwd);
5190
+ const [target, paneId] = output?.trim().split(" ") ?? [];
5191
+ if (target)
5192
+ origin.tmux = target;
5193
+ if (paneId)
5194
+ origin.pane = paneId;
5195
+ }
5196
+ return origin;
5197
+ }
5198
+
5199
+ // ../envoy-client/src/errors.ts
5200
+ function messageFor(error) {
5201
+ return error instanceof Error ? error.message : String(error);
5202
+ }
5203
+
5204
+ // ../envoy-client/src/dispatch-client.ts
5205
+ function ghTokenGetter(cwd, exec = defaultExec) {
5206
+ return async () => {
5207
+ try {
5208
+ const { stdout } = await exec("gh", ["auth", "token"], { cwd });
5209
+ const value = stdout.trim();
5210
+ return value.length > 0 ? value : null;
5211
+ } catch {
5212
+ return null;
5213
+ }
5214
+ };
5215
+ }
5216
+
5217
+ class DispatchServiceError extends Error {
5218
+ kind;
5219
+ name = "DispatchServiceError";
5220
+ constructor(kind, message) {
5221
+ super(message);
5222
+ this.kind = kind;
5223
+ }
5224
+ }
5225
+ function parseResponseBody(body, contentType) {
5226
+ if (!contentType.includes("text/event-stream"))
5227
+ return JSON.parse(body);
5228
+ for (const line of body.split(`
5229
+ `)) {
5230
+ const payload = line.match(/^data:\s*(.+)$/)?.[1];
5231
+ if (payload !== undefined)
5232
+ return JSON.parse(payload);
5233
+ }
5234
+ return null;
5235
+ }
5236
+ function isDispatchServiceResult(value) {
5237
+ return typeof value === "object" && value !== null && "thread" in value && typeof value.thread === "number" && "url" in value && typeof value.url === "string";
5238
+ }
5239
+ async function callDispatch(options, args) {
5240
+ const token = await options.getToken();
5241
+ if (!token) {
5242
+ throw new DispatchServiceError("auth", `dispatch: gh auth token returned empty in ${args.origin.cwd} \u2014 check your gh-app setup`);
5243
+ }
5244
+ const fetchImpl = options.fetchImpl ?? fetch;
5245
+ let response;
5246
+ try {
5247
+ response = await fetchImpl(options.serviceUrl, {
5248
+ method: "POST",
5249
+ headers: {
5250
+ Authorization: `Bearer ${token}`,
5251
+ "Content-Type": "application/json",
5252
+ Accept: "application/json, text/event-stream"
5253
+ },
5254
+ body: JSON.stringify({
5255
+ jsonrpc: "2.0",
5256
+ id: 1,
5257
+ method: "tools/call",
5258
+ params: { name: DISPATCH_TOOL_NAME, arguments: args }
5259
+ })
5260
+ });
5261
+ } catch (error) {
5262
+ throw new DispatchServiceError("transport", `dispatch service unreachable at ${options.serviceUrl}: ${messageFor(error)}`);
5263
+ }
5264
+ const body = await response.text();
5265
+ if (response.status === 401) {
5266
+ throw new DispatchServiceError("auth", `dispatch service rejected the GitHub token (401): ${body.slice(0, 200)}`);
5267
+ }
5268
+ if (!response.ok) {
5269
+ throw new DispatchServiceError("transport", `dispatch service returned ${response.status} ${response.statusText}: ${body.slice(0, 200)}`);
5270
+ }
5271
+ let parsed;
5272
+ try {
5273
+ parsed = parseResponseBody(body, response.headers.get("content-type") ?? "");
5274
+ } catch (error) {
5275
+ throw new DispatchServiceError("transport", `dispatch service sent an unreadable response: ${messageFor(error)}`);
5276
+ }
5277
+ const rpc = parsed ?? {};
5278
+ if (rpc.error) {
5279
+ throw new DispatchServiceError("tool", rpc.error.message ?? "dispatch service returned an error");
5280
+ }
5281
+ const text = (rpc.result?.content ?? []).map((item) => item.text ?? "").filter(Boolean).join(`
5282
+ `);
5283
+ if (rpc.result?.isError)
5284
+ throw new DispatchServiceError("tool", text || "dispatch failed");
5285
+ let result;
5286
+ try {
5287
+ result = JSON.parse(text);
5288
+ } catch {
5289
+ throw new DispatchServiceError("transport", `dispatch service returned a non-JSON result: ${text.slice(0, 200)}`);
5290
+ }
5291
+ if (!isDispatchServiceResult(result)) {
5292
+ throw new DispatchServiceError("transport", `dispatch service result lacks thread/url: ${text.slice(0, 200)}`);
5293
+ }
5294
+ return result;
5295
+ }
5296
+
5297
+ // ../envoy-client/src/dispatch-call.ts
5298
+ var QUALIFIED_REF = /^[^/\s#]+\/[^/\s#]+#\d+/;
5299
+ async function prepareDispatchCall(input) {
5300
+ const { call, cwd } = input;
5301
+ const continuing = isContinueCall(call);
5302
+ const needsRepo = continuing ? !QUALIFIED_REF.test(call.thread) : call.repo === undefined && !QUALIFIED_REF.test(call.parent ?? "");
5303
+ let repo;
5304
+ if (needsRepo) {
5305
+ const resolved = await resolveCwdRepo(cwd, input.exec);
5306
+ if (resolved === null) {
5307
+ throw new DispatchArgumentError(continuing ? `dispatch: ${cwd} has no GitHub remote; pass thread=owner/name#<n>` : `dispatch: ${cwd} has no GitHub remote; pass repo=owner/name`);
5308
+ }
5309
+ repo = resolved;
5310
+ }
5311
+ const resolvedOrigin = await resolveOrigin(input.env, input.exec, cwd);
5312
+ const origin = {
5313
+ ...resolvedOrigin,
5314
+ host: input.host,
5315
+ ...input.sessionId ? { sessionId: input.sessionId } : {},
5316
+ ...input.sessionTitle ? { sessionTitle: input.sessionTitle } : {}
5317
+ };
5318
+ return { ...call, ...repo === undefined ? {} : { repo }, origin };
5319
+ }
5320
+ async function executeDispatch(input) {
5321
+ const exec = input.exec ?? defaultExec;
5322
+ const prepared = await prepareDispatchCall({
5323
+ call: input.call,
5324
+ cwd: input.cwd,
5325
+ host: input.host,
5326
+ ...input.sessionId === undefined ? {} : { sessionId: input.sessionId },
5327
+ ...input.sessionTitle === undefined ? {} : { sessionTitle: input.sessionTitle },
5328
+ env: input.env ?? process.env,
5329
+ exec
5330
+ });
5331
+ return callDispatch({
5332
+ serviceUrl: input.serviceUrl,
5333
+ getToken: input.getToken ?? ghTokenGetter(input.cwd, exec),
5334
+ ...input.fetchImpl ? { fetchImpl: input.fetchImpl } : {}
5335
+ }, prepared);
5336
+ }
5337
+
5338
+ // ../envoy-client/src/dispatch-config.ts
5339
+ import { readFileSync } from "fs";
5340
+ import { homedir } from "os";
5341
+ import * as path from "path";
5342
+ var DEFAULT_SERVER_URL = "http://localhost:8766";
5343
+ var EnvoyFileSchema = looseObject({
5344
+ $schema: string2().optional(),
5345
+ natsUrls: array(string2()).optional(),
5346
+ dispatch: strictObject({
5347
+ enabled: boolean2().optional(),
5348
+ serverUrl: url().optional()
5349
+ }).optional()
5350
+ });
5351
+ function describeSchemaIssue(filePath, error) {
5352
+ const issue = error.issues[0];
5353
+ if (!issue)
5354
+ return `${filePath}: invalid dispatch config`;
5355
+ if (issue.code === "unrecognized_keys") {
5356
+ const keys = issue.keys.map((key) => `dispatch.${key}`).join(", ");
5357
+ return `${filePath}: unrecognized dispatch key(s): ${keys}`;
5358
+ }
5359
+ return `${filePath}: ${issue.path.join(".")}: ${issue.message}`;
5360
+ }
5361
+ function readEnvoyFile(filePath) {
5362
+ let raw;
5363
+ try {
5364
+ raw = readFileSync(filePath, "utf-8");
5365
+ } catch {
5366
+ return { kind: "absent" };
5367
+ }
5368
+ let parsedJson;
5369
+ try {
5370
+ parsedJson = JSON.parse(raw);
5371
+ } catch (err) {
5372
+ return { kind: "invalid", reason: `${filePath}: invalid JSON (${messageFor(err)})` };
5373
+ }
5374
+ const parsed = EnvoyFileSchema.safeParse(parsedJson);
5375
+ if (!parsed.success) {
5376
+ return { kind: "invalid", reason: describeSchemaIssue(filePath, parsed.error) };
5377
+ }
5378
+ return { kind: "valid", settings: parsed.data.dispatch ?? null };
5379
+ }
5380
+ function resolveDispatchConfig(env, options = {}) {
5381
+ const explicit = env["DISPATCH_MCP_URL"];
5382
+ if (explicit)
5383
+ return { url: explicit, error: null };
5384
+ const home = options.home ?? env["HOME"] ?? homedir();
5385
+ const cwd = options.cwd ?? process.cwd();
5386
+ const userFile = readEnvoyFile(path.join(home, ".config", "opencode", "envoy.json"));
5387
+ const repoFile = readEnvoyFile(path.join(cwd, ".opencode", "envoy.json"));
5388
+ for (const file of [userFile, repoFile]) {
5389
+ if (file.kind === "invalid")
5390
+ return { url: null, error: file.reason };
5391
+ }
5392
+ const merged = {
5393
+ ...userFile.kind === "valid" ? userFile.settings : null,
5394
+ ...repoFile.kind === "valid" ? repoFile.settings : null
5395
+ };
5396
+ if (merged.enabled !== true)
5397
+ return { url: null, error: null };
5398
+ const baseUrl = (merged.serverUrl ?? DEFAULT_SERVER_URL).replace(/\/+$/, "");
5399
+ return { url: `${baseUrl}/mcp`, error: null };
5400
+ }
5401
+
4672
5402
  // ../envoy-client/src/dispatch-subscribe.ts
4673
5403
  var ISSUE_URL_RE = /https?:\/\/github\.com\/([^/\s"]+)\/([^/\s"]+)\/issues\/(\d+)/i;
4674
- var DISPATCH_TOOL_RE = /(^|[-._])dispatch$/i;
4675
5404
  function isDispatchTool(tool) {
4676
- return DISPATCH_TOOL_RE.test(tool);
5405
+ return tool === DISPATCH_TOOL_NAME;
4677
5406
  }
4678
5407
  function dispatchThreadTopic(owner, repo, thread) {
4679
5408
  return `notifications.github.${owner}.${repo}.issue.${thread}.>`;
@@ -4692,12 +5421,6 @@ function dispatchSubscriptionTopic(tool, output) {
4692
5421
  return dispatchThreadTopic(owner, repo, thread);
4693
5422
  }
4694
5423
 
4695
- // ../envoy-client/src/machine.ts
4696
- import { hostname } from "os";
4697
- function machineID() {
4698
- return process.env["ENVOY_MACHINE_ID"] || hostname();
4699
- }
4700
-
4701
5424
  // ../envoy-client/src/tool-contract.ts
4702
5425
  var EnvoyToolOperation = {
4703
5426
  subscribe: "subscribe",
@@ -4861,135 +5584,17 @@ function toEnvelope(value) {
4861
5584
  }
4862
5585
 
4863
5586
  // src/server.ts
4864
- import { tool as tool2 } from "@opencode-ai/plugin/tool";
4865
-
4866
- // src/config/index.ts
4867
- import { existsSync, readFileSync } from "fs";
4868
- import os from "os";
4869
- import path from "path";
4870
-
4871
- // ../envoy-client/src/errors.ts
4872
- function messageFor(error) {
4873
- return error instanceof Error ? error.message : String(error);
4874
- }
4875
-
4876
- // src/config/schema.ts
4877
- import { tool } from "@opencode-ai/plugin";
4878
- var z = tool.schema;
4879
- var DispatchConfigSchema = z.object({
4880
- enabled: z.boolean().optional(),
4881
- serverUrl: z.string().url().optional()
4882
- }).strict();
4883
- var EnvoyConfigSchema = z.object({
4884
- $schema: z.string().optional(),
4885
- natsUrls: z.array(z.string()).optional(),
4886
- dispatch: DispatchConfigSchema.optional()
4887
- }).passthrough();
4888
-
4889
- // src/config/index.ts
4890
- class EnvoyConfigError extends Error {
4891
- filePath;
4892
- constructor(filePath, detail) {
4893
- super(`[envoy-plugin] invalid config at ${filePath}: ${detail}`);
4894
- this.name = "EnvoyConfigError";
4895
- this.filePath = filePath;
4896
- }
4897
- }
4898
- function readConfigFile(filePath) {
4899
- if (!existsSync(filePath))
4900
- return null;
4901
- let raw;
4902
- try {
4903
- raw = JSON.parse(readFileSync(filePath, "utf-8"));
4904
- } catch (error) {
4905
- throw new EnvoyConfigError(filePath, messageFor(error));
4906
- }
4907
- const parsed = EnvoyConfigSchema.safeParse(raw);
4908
- if (!parsed.success) {
4909
- const issues = parsed.error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`).join(", ");
4910
- throw new EnvoyConfigError(filePath, issues);
4911
- }
4912
- return parsed.data;
4913
- }
4914
- function mergeConfig(base, override) {
4915
- return {
4916
- ...base,
4917
- ...override,
4918
- dispatch: base.dispatch || override.dispatch ? {
4919
- ...base.dispatch,
4920
- ...override.dispatch
4921
- } : undefined
4922
- };
4923
- }
4924
- async function loadEnvoyConfig(directory, options = {}) {
4925
- const homeDir = options.homeDir ?? os.homedir();
4926
- const userConfigPath = path.join(homeDir, ".config", "opencode", "envoy.json");
4927
- const repoConfigPath = path.join(directory, ".opencode", "envoy.json");
4928
- let merged = {};
4929
- const userConfig = readConfigFile(userConfigPath);
4930
- if (userConfig)
4931
- merged = mergeConfig(merged, userConfig);
4932
- const repoConfig = readConfigFile(repoConfigPath);
4933
- if (repoConfig)
4934
- merged = mergeConfig(merged, repoConfig);
4935
- return merged;
4936
- }
4937
-
4938
- // src/dispatch-mcp.ts
4939
- import { existsSync as existsSync2 } from "fs";
4940
- import path2 from "path";
4941
- var DEFAULT_SERVER_URL = "http://localhost:8766";
4942
- function defaultShimPath() {
4943
- const packageRoot = path2.join(import.meta.dir, "..");
4944
- const candidates = [
4945
- path2.join(packageRoot, "bin", "dispatch-mcp-shim.js"),
4946
- path2.join(packageRoot, "bin", "dispatch-mcp-shim.ts")
4947
- ];
4948
- const found = candidates.find((candidate) => existsSync2(candidate));
4949
- if (!found) {
4950
- throw new Error(`dispatch MCP shim not found; tried: ${candidates.join(", ")}`);
4951
- }
4952
- return found;
4953
- }
4954
- function buildDispatchMcpEntry(opts) {
4955
- if (!opts.dispatch?.enabled)
4956
- return null;
4957
- const baseUrl = (opts.dispatch.serverUrl ?? DEFAULT_SERVER_URL).replace(/\/+$/, "");
4958
- const shimPath = opts.shimPath ?? defaultShimPath();
4959
- const runtime = opts.runtime ?? "bun";
4960
- return {
4961
- type: "local",
4962
- command: [runtime, shimPath],
4963
- environment: {
4964
- DISPATCH_MCP_URL: `${baseUrl}/mcp`
4965
- },
4966
- enabled: true
4967
- };
4968
- }
4969
- function injectEnvoyMcp(cfg, entry) {
4970
- cfg.mcp = cfg.mcp ?? {};
4971
- const existing = cfg.mcp.envoy;
4972
- if (existing !== undefined) {
4973
- if (JSON.stringify(existing) === JSON.stringify(entry)) {
4974
- return {};
4975
- }
4976
- return {
4977
- warning: "[envoy-plugin] envoy MCP entry already present in config; not overriding"
4978
- };
4979
- }
4980
- cfg.mcp.envoy = entry;
4981
- return {};
4982
- }
5587
+ import { tool } from "@opencode-ai/plugin/tool";
4983
5588
 
4984
5589
  // src/log.ts
4985
5590
  import { appendFile, mkdir } from "fs/promises";
4986
- import os2 from "os";
4987
- import path3 from "path";
5591
+ import os from "os";
5592
+ import path2 from "path";
4988
5593
  function defaultLogDir() {
4989
- const home = os2.homedir();
5594
+ const home = os.homedir();
4990
5595
  if (!home)
4991
- return path3.join(os2.tmpdir(), "opencode-log");
4992
- return path3.join(home, ".local", "share", "opencode", "log");
5596
+ return path2.join(os.tmpdir(), "opencode-log");
5597
+ return path2.join(home, ".local", "share", "opencode", "log");
4993
5598
  }
4994
5599
  function format(level, message) {
4995
5600
  return `${new Date().toISOString()} ${level} ${message}
@@ -4997,7 +5602,7 @@ function format(level, message) {
4997
5602
  }
4998
5603
  function createLogger(options = {}) {
4999
5604
  const dir = options.logDir ?? defaultLogDir();
5000
- const file = path3.join(dir, options.fileName ?? "envoy-plugin.log");
5605
+ const file = path2.join(dir, options.fileName ?? "envoy-plugin.log");
5001
5606
  let chain = Promise.resolve();
5002
5607
  const append = (line) => {
5003
5608
  chain = chain.then(() => mkdir(dir, { recursive: true })).then(() => appendFile(file, line)).catch(() => {});
@@ -5020,7 +5625,7 @@ function createLogger(options = {}) {
5020
5625
  var logger = createLogger();
5021
5626
 
5022
5627
  // src/port.ts
5023
- import { execFile } from "child_process";
5628
+ import { execFile as execFile2 } from "child_process";
5024
5629
 
5025
5630
  // src/ss.ts
5026
5631
  function portFromSsOutput(output, pid) {
@@ -5042,15 +5647,15 @@ function portFromSsOutput(output, pid) {
5042
5647
  }
5043
5648
 
5044
5649
  // src/port.ts
5045
- var defaultExec = (command, args, options) => new Promise((resolve, reject) => {
5046
- execFile(command, args, { encoding: options.encoding }, (error, stdout) => {
5650
+ var defaultExec2 = (command, args, options) => new Promise((resolve, reject) => {
5651
+ execFile2(command, args, { encoding: options.encoding }, (error, stdout) => {
5047
5652
  if (error)
5048
5653
  reject(error);
5049
5654
  else
5050
5655
  resolve(stdout);
5051
5656
  });
5052
5657
  });
5053
- async function resolvePort(serverUrl, exec = defaultExec) {
5658
+ async function resolvePort(serverUrl, exec = defaultExec2) {
5054
5659
  const urlPort = Number.parseInt(serverUrl.port, 10);
5055
5660
  if (Number.isFinite(urlPort) && urlPort > 0)
5056
5661
  return urlPort;
@@ -5064,11 +5669,11 @@ async function resolvePort(serverUrl, exec = defaultExec) {
5064
5669
  }
5065
5670
 
5066
5671
  // src/server.ts
5067
- var moduleDirectory = path4.dirname(fileURLToPath(import.meta.url));
5672
+ var moduleDirectory = path3.dirname(fileURLToPath(import.meta.url));
5068
5673
  var skillsDirectory = [
5069
- path4.resolve(moduleDirectory, "../../skills"),
5070
- path4.resolve(moduleDirectory, "../../../skills")
5071
- ].find((dir) => existsSync3(dir));
5674
+ path3.resolve(moduleDirectory, "../../skills"),
5675
+ path3.resolve(moduleDirectory, "../../../skills")
5676
+ ].find((dir) => existsSync(dir));
5072
5677
  var [
5073
5678
  subscribeSpec,
5074
5679
  unsubscribeSpec,
@@ -5079,9 +5684,33 @@ var [
5079
5684
  whoamiSpec,
5080
5685
  sessionsSpec
5081
5686
  ] = envoyToolSpecs;
5687
+ var dispatchQuestionOption = tool.schema.strictObject({
5688
+ label: tool.schema.string().min(1),
5689
+ description: tool.schema.string().optional()
5690
+ });
5691
+ var dispatchQuestion = tool.schema.strictObject({
5692
+ question: tool.schema.string().min(1),
5693
+ header: tool.schema.string().optional(),
5694
+ options: tool.schema.array(dispatchQuestionOption).optional(),
5695
+ multiple: tool.schema.boolean().optional(),
5696
+ custom: tool.schema.boolean().optional()
5697
+ });
5698
+ var dispatchArgs = {
5699
+ subject: tool.schema.string().describe(DISPATCH_ARGUMENTS.subject).optional(),
5700
+ thread: tool.schema.string().describe(DISPATCH_ARGUMENTS.thread).optional(),
5701
+ context: tool.schema.string().describe(DISPATCH_ARGUMENTS.context),
5702
+ question: tool.schema.string().describe(DISPATCH_ARGUMENTS.question),
5703
+ ask: tool.schema.array(dispatchQuestion).describe(DISPATCH_ARGUMENTS.ask).optional(),
5704
+ urgency: tool.schema.enum(DISPATCH_URGENCIES).describe(DISPATCH_ARGUMENTS.urgency).optional(),
5705
+ repo: tool.schema.string().describe(DISPATCH_ARGUMENTS.repo).optional(),
5706
+ parent: tool.schema.string().describe(DISPATCH_ARGUMENTS.parent).optional()
5707
+ };
5082
5708
  var server_default = async (input) => {
5083
5709
  const cwd = process.cwd();
5084
- const config = await loadEnvoyConfig(cwd);
5710
+ const dispatchConfig = resolveDispatchConfig(process.env, { cwd });
5711
+ if (dispatchConfig.error !== null)
5712
+ throw new Error(`[envoy-plugin] ${dispatchConfig.error}`);
5713
+ const dispatchServiceUrl = dispatchConfig.url;
5085
5714
  const envoyDefaults = envoyDefaultsFromEnvironment(process.env);
5086
5715
  const envoy = createEnvoyClient({ baseUrl: envoyDefaults.envoyUrl, fetch: globalThis.fetch });
5087
5716
  let activeSessionID = null;
@@ -5152,6 +5781,25 @@ var server_default = async (input) => {
5152
5781
  clearInterval(timer);
5153
5782
  clearInterval(heartbeatInterval);
5154
5783
  });
5784
+ const dispatchTool = dispatchServiceUrl === null ? {} : {
5785
+ [DISPATCH_TOOL_NAME]: tool({
5786
+ description: DISPATCH_TOOL_DESCRIPTION,
5787
+ args: dispatchArgs,
5788
+ async execute(args, ctx) {
5789
+ ctx.metadata({ title: "Dispatch" });
5790
+ const call = parseDispatchCall(args);
5791
+ const result = await executeDispatch({
5792
+ call,
5793
+ cwd: ctx.directory,
5794
+ host: "opencode",
5795
+ sessionId: ctx.sessionID,
5796
+ sessionTitle: await fetchTitle(ctx.sessionID) ?? undefined,
5797
+ serviceUrl: dispatchServiceUrl
5798
+ });
5799
+ return JSON.stringify(result);
5800
+ }
5801
+ })
5802
+ };
5155
5803
  return {
5156
5804
  config: (cfg) => {
5157
5805
  if (skillsDirectory) {
@@ -5161,14 +5809,6 @@ var server_default = async (input) => {
5161
5809
  cfg.skills.paths.push(skillsDirectory);
5162
5810
  }
5163
5811
  }
5164
- const entry = buildDispatchMcpEntry({
5165
- dispatch: config.dispatch
5166
- });
5167
- if (!entry)
5168
- return;
5169
- const { warning } = injectEnvoyMcp(cfg, entry);
5170
- if (warning)
5171
- logger.warn(warning);
5172
5812
  },
5173
5813
  event: async ({
5174
5814
  event
@@ -5242,9 +5882,10 @@ var server_default = async (input) => {
5242
5882
  clearInterval(heartbeatInterval);
5243
5883
  },
5244
5884
  tool: {
5245
- envoy_subscribe: tool2({
5885
+ ...dispatchTool,
5886
+ envoy_subscribe: tool({
5246
5887
  description: subscribeSpec.description,
5247
- args: { topics: tool2.schema.array(tool2.schema.string()) },
5888
+ args: { topics: tool.schema.array(tool.schema.string()) },
5248
5889
  async execute(args, ctx) {
5249
5890
  ctx.metadata({ title: "Envoy subscribe" });
5250
5891
  return JSON.stringify(await envoy.subscribe({
@@ -5257,16 +5898,16 @@ var server_default = async (input) => {
5257
5898
  }));
5258
5899
  }
5259
5900
  }),
5260
- envoy_unsubscribe: tool2({
5901
+ envoy_unsubscribe: tool({
5261
5902
  description: unsubscribeSpec.description,
5262
- args: { topics: tool2.schema.array(tool2.schema.string()).optional() },
5903
+ args: { topics: tool.schema.array(tool.schema.string()).optional() },
5263
5904
  async execute(args, ctx) {
5264
5905
  ctx.metadata({ title: "Envoy unsubscribe" });
5265
5906
  await envoy.unsubscribe({ sessionID: ctx.sessionID, topics: args.topics ?? [] });
5266
5907
  return "ok";
5267
5908
  }
5268
5909
  }),
5269
- envoy_list: tool2({
5910
+ envoy_list: tool({
5270
5911
  description: listSpec.description,
5271
5912
  args: {},
5272
5913
  async execute(_args, ctx) {
@@ -5274,9 +5915,9 @@ var server_default = async (input) => {
5274
5915
  return JSON.stringify(await envoy.getInterest(ctx.sessionID));
5275
5916
  }
5276
5917
  }),
5277
- envoy_send: tool2({
5918
+ envoy_send: tool({
5278
5919
  description: sendSpec.description,
5279
- args: { session_id: tool2.schema.string(), message: tool2.schema.string() },
5920
+ args: { session_id: tool.schema.string(), message: tool.schema.string() },
5280
5921
  async execute(args, ctx) {
5281
5922
  ctx.metadata({ title: "Envoy send" });
5282
5923
  return JSON.stringify(await envoy.send({
@@ -5286,9 +5927,9 @@ var server_default = async (input) => {
5286
5927
  }));
5287
5928
  }
5288
5929
  }),
5289
- envoy_publish: tool2({
5930
+ envoy_publish: tool({
5290
5931
  description: publishSpec.description,
5291
- args: { topic: tool2.schema.string(), message: tool2.schema.string() },
5932
+ args: { topic: tool.schema.string(), message: tool.schema.string() },
5292
5933
  async execute(args, ctx) {
5293
5934
  ctx.metadata({ title: "Envoy publish" });
5294
5935
  return JSON.stringify(await envoy.publish({
@@ -5298,15 +5939,15 @@ var server_default = async (input) => {
5298
5939
  }));
5299
5940
  }
5300
5941
  }),
5301
- envoy_role_set: tool2({
5942
+ envoy_role_set: tool({
5302
5943
  description: roleSetSpec.description,
5303
- args: { role: tool2.schema.string() },
5944
+ args: { role: tool.schema.string() },
5304
5945
  async execute(args, ctx) {
5305
5946
  ctx.metadata({ title: "Set Envoy role" });
5306
5947
  return JSON.stringify(await envoy.setRole({ sessionID: ctx.sessionID, role: args.role }));
5307
5948
  }
5308
5949
  }),
5309
- envoy_whoami: tool2({
5950
+ envoy_whoami: tool({
5310
5951
  description: whoamiSpec.description,
5311
5952
  args: {},
5312
5953
  async execute(_args, ctx) {
@@ -5321,9 +5962,9 @@ var server_default = async (input) => {
5321
5962
  }, null, 2);
5322
5963
  }
5323
5964
  }),
5324
- envoy_sessions: tool2({
5965
+ envoy_sessions: tool({
5325
5966
  description: sessionsSpec.description,
5326
- args: { machine: tool2.schema.string().optional() },
5967
+ args: { machine: tool.schema.string().optional() },
5327
5968
  async execute(args, ctx) {
5328
5969
  ctx.metadata({ title: "Envoy sessions" });
5329
5970
  const sessions = await envoy.listSessions();