@symerian/symi 3.0.18 → 3.0.19

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 (116) hide show
  1. package/dist/build-info.json +3 -3
  2. package/dist/canvas-host/a2ui/.bundle.hash +1 -1
  3. package/package.json +1 -1
  4. package/extensions/copilot-proxy/README.md +0 -24
  5. package/extensions/copilot-proxy/index.ts +0 -154
  6. package/extensions/copilot-proxy/node_modules/.bin/symi +0 -21
  7. package/extensions/copilot-proxy/package.json +0 -15
  8. package/extensions/copilot-proxy/symi.plugin.json +0 -9
  9. package/extensions/device-pair/index.ts +0 -642
  10. package/extensions/device-pair/symi.plugin.json +0 -20
  11. package/extensions/diagnostics-otel/index.ts +0 -15
  12. package/extensions/diagnostics-otel/node_modules/.bin/acorn +0 -21
  13. package/extensions/diagnostics-otel/node_modules/.bin/symi +0 -21
  14. package/extensions/diagnostics-otel/package.json +0 -27
  15. package/extensions/diagnostics-otel/src/service.test.ts +0 -290
  16. package/extensions/diagnostics-otel/src/service.ts +0 -666
  17. package/extensions/diagnostics-otel/symi.plugin.json +0 -8
  18. package/extensions/google-antigravity-auth/README.md +0 -24
  19. package/extensions/google-antigravity-auth/index.ts +0 -424
  20. package/extensions/google-antigravity-auth/node_modules/.bin/symi +0 -21
  21. package/extensions/google-antigravity-auth/package.json +0 -15
  22. package/extensions/google-antigravity-auth/symi.plugin.json +0 -9
  23. package/extensions/google-gemini-cli-auth/README.md +0 -35
  24. package/extensions/google-gemini-cli-auth/index.ts +0 -75
  25. package/extensions/google-gemini-cli-auth/node_modules/.bin/symi +0 -21
  26. package/extensions/google-gemini-cli-auth/oauth.test.ts +0 -162
  27. package/extensions/google-gemini-cli-auth/oauth.ts +0 -636
  28. package/extensions/google-gemini-cli-auth/package.json +0 -15
  29. package/extensions/google-gemini-cli-auth/symi.plugin.json +0 -9
  30. package/extensions/learning-loop/index.ts +0 -159
  31. package/extensions/learning-loop/node_modules/.bin/symi +0 -21
  32. package/extensions/learning-loop/package.json +0 -18
  33. package/extensions/learning-loop/src/analytics/gateway-methods.ts +0 -230
  34. package/extensions/learning-loop/src/analytics/metrics-aggregator.ts +0 -153
  35. package/extensions/learning-loop/src/capture/run-tracker.ts +0 -181
  36. package/extensions/learning-loop/src/capture/serializer.ts +0 -74
  37. package/extensions/learning-loop/src/db.ts +0 -583
  38. package/extensions/learning-loop/src/feedback/explicit-feedback.ts +0 -58
  39. package/extensions/learning-loop/src/feedback/implicit-signals.ts +0 -89
  40. package/extensions/learning-loop/src/graph/edge-inference.ts +0 -189
  41. package/extensions/learning-loop/src/graph/graph-retrieval.ts +0 -144
  42. package/extensions/learning-loop/src/graph/graph-store.ts +0 -183
  43. package/extensions/learning-loop/src/hooks.ts +0 -244
  44. package/extensions/learning-loop/src/injection/cache.ts +0 -73
  45. package/extensions/learning-loop/src/injection/context-injector.ts +0 -104
  46. package/extensions/learning-loop/src/injection/prompt-builder.ts +0 -43
  47. package/extensions/learning-loop/src/learning/embedding-bridge.ts +0 -54
  48. package/extensions/learning-loop/src/learning/learning-extractor.ts +0 -217
  49. package/extensions/learning-loop/src/learning/learning-store.ts +0 -158
  50. package/extensions/learning-loop/src/learning/retrieval.ts +0 -87
  51. package/extensions/learning-loop/src/math/confidence-intervals.ts +0 -62
  52. package/extensions/learning-loop/src/math/ewma.ts +0 -51
  53. package/extensions/learning-loop/src/math/weighted-scorer.ts +0 -42
  54. package/extensions/learning-loop/src/schema.ts +0 -176
  55. package/extensions/learning-loop/src/scoring/normalization.ts +0 -32
  56. package/extensions/learning-loop/src/scoring/quality-engine.ts +0 -78
  57. package/extensions/learning-loop/src/scoring/signal-extractors.ts +0 -155
  58. package/extensions/learning-loop/src/test/context-injector.test.ts +0 -142
  59. package/extensions/learning-loop/src/test/fixes.test.ts +0 -1286
  60. package/extensions/learning-loop/src/test/graph.test.ts +0 -711
  61. package/extensions/learning-loop/src/test/integration.test.ts +0 -312
  62. package/extensions/learning-loop/src/test/learning-store.test.ts +0 -191
  63. package/extensions/learning-loop/src/test/math.test.ts +0 -148
  64. package/extensions/learning-loop/src/test/quality-engine.test.ts +0 -231
  65. package/extensions/learning-loop/src/test/run-tracker.test.ts +0 -143
  66. package/extensions/learning-loop/src/types.ts +0 -281
  67. package/extensions/learning-loop/symi.plugin.json +0 -46
  68. package/extensions/llm-task/README.md +0 -97
  69. package/extensions/llm-task/index.ts +0 -6
  70. package/extensions/llm-task/package.json +0 -12
  71. package/extensions/llm-task/src/llm-task-tool.test.ts +0 -138
  72. package/extensions/llm-task/src/llm-task-tool.ts +0 -249
  73. package/extensions/llm-task/symi.plugin.json +0 -21
  74. package/extensions/memory-lancedb/config.ts +0 -161
  75. package/extensions/memory-lancedb/index.test.ts +0 -330
  76. package/extensions/memory-lancedb/index.ts +0 -670
  77. package/extensions/memory-lancedb/node_modules/.bin/arrow2csv +0 -21
  78. package/extensions/memory-lancedb/node_modules/.bin/openai +0 -21
  79. package/extensions/memory-lancedb/node_modules/.bin/symi +0 -21
  80. package/extensions/memory-lancedb/package.json +0 -20
  81. package/extensions/memory-lancedb/symi.plugin.json +0 -71
  82. package/extensions/minimax-portal-auth/README.md +0 -33
  83. package/extensions/minimax-portal-auth/index.ts +0 -161
  84. package/extensions/minimax-portal-auth/node_modules/.bin/symi +0 -21
  85. package/extensions/minimax-portal-auth/oauth.ts +0 -247
  86. package/extensions/minimax-portal-auth/package.json +0 -15
  87. package/extensions/minimax-portal-auth/symi.plugin.json +0 -9
  88. package/extensions/model-equalizer/index.ts +0 -80
  89. package/extensions/model-equalizer/skills/model-equalizer/SKILL.md +0 -58
  90. package/extensions/model-equalizer/src/detection.ts +0 -62
  91. package/extensions/model-equalizer/src/enhancer.ts +0 -63
  92. package/extensions/model-equalizer/src/test/detection.test.ts +0 -218
  93. package/extensions/model-equalizer/src/test/enhancer.test.ts +0 -137
  94. package/extensions/model-equalizer/src/test/integration.test.ts +0 -185
  95. package/extensions/model-equalizer/src/types.ts +0 -24
  96. package/extensions/model-equalizer/symi.plugin.json +0 -12
  97. package/extensions/phone-control/index.ts +0 -421
  98. package/extensions/phone-control/symi.plugin.json +0 -10
  99. package/extensions/pipeline/README.md +0 -75
  100. package/extensions/pipeline/SKILL.md +0 -97
  101. package/extensions/pipeline/index.ts +0 -18
  102. package/extensions/pipeline/package.json +0 -11
  103. package/extensions/pipeline/src/pipeline-tool.test.ts +0 -345
  104. package/extensions/pipeline/src/pipeline-tool.ts +0 -266
  105. package/extensions/pipeline/src/windows-spawn.test.ts +0 -148
  106. package/extensions/pipeline/src/windows-spawn.ts +0 -193
  107. package/extensions/pipeline/symi.plugin.json +0 -10
  108. package/extensions/qwen-portal-auth/README.md +0 -24
  109. package/extensions/qwen-portal-auth/index.ts +0 -134
  110. package/extensions/qwen-portal-auth/oauth.ts +0 -190
  111. package/extensions/qwen-portal-auth/symi.plugin.json +0 -9
  112. package/extensions/talk-voice/index.ts +0 -150
  113. package/extensions/talk-voice/symi.plugin.json +0 -10
  114. package/extensions/thread-ownership/index.test.ts +0 -180
  115. package/extensions/thread-ownership/index.ts +0 -133
  116. package/extensions/thread-ownership/symi.plugin.json +0 -28
@@ -1,266 +0,0 @@
1
- import { spawn } from "node:child_process";
2
- import path from "node:path";
3
- import { Type } from "@sinclair/typebox";
4
- import type { SymiPluginApi } from "../../../src/plugins/types.js";
5
- import { resolveWindowsPipelineSpawn } from "./windows-spawn.js";
6
-
7
- type PipelineEnvelope =
8
- | {
9
- ok: true;
10
- status: "ok" | "needs_approval" | "cancelled";
11
- output: unknown[];
12
- requiresApproval: null | {
13
- type: "approval_request";
14
- prompt: string;
15
- items: unknown[];
16
- resumeToken?: string;
17
- };
18
- }
19
- | {
20
- ok: false;
21
- error: { type?: string; message: string };
22
- };
23
-
24
- function normalizeForCwdSandbox(p: string): string {
25
- const normalized = path.normalize(p);
26
- return process.platform === "win32" ? normalized.toLowerCase() : normalized;
27
- }
28
-
29
- function resolveCwd(cwdRaw: unknown): string {
30
- if (typeof cwdRaw !== "string" || !cwdRaw.trim()) {
31
- return process.cwd();
32
- }
33
- const cwd = cwdRaw.trim();
34
- if (path.isAbsolute(cwd)) {
35
- throw new Error("cwd must be a relative path");
36
- }
37
- const base = process.cwd();
38
- const resolved = path.resolve(base, cwd);
39
-
40
- const rel = path.relative(normalizeForCwdSandbox(base), normalizeForCwdSandbox(resolved));
41
- if (rel === "" || rel === ".") {
42
- return resolved;
43
- }
44
- if (rel.startsWith("..") || path.isAbsolute(rel)) {
45
- throw new Error("cwd must stay within the gateway working directory");
46
- }
47
- return resolved;
48
- }
49
-
50
- async function runPipelineSubprocessOnce(params: {
51
- execPath: string;
52
- argv: string[];
53
- cwd: string;
54
- timeoutMs: number;
55
- maxStdoutBytes: number;
56
- }) {
57
- const { execPath, argv, cwd } = params;
58
- const timeoutMs = Math.max(200, params.timeoutMs);
59
- const maxStdoutBytes = Math.max(1024, params.maxStdoutBytes);
60
-
61
- const env = { ...process.env, LOBSTER_MODE: "tool" } as Record<string, string | undefined>;
62
- const nodeOptions = env.NODE_OPTIONS ?? "";
63
- if (nodeOptions.includes("--inspect")) {
64
- delete env.NODE_OPTIONS;
65
- }
66
- const spawnTarget =
67
- process.platform === "win32"
68
- ? resolveWindowsPipelineSpawn(execPath, argv, env)
69
- : { command: execPath, argv };
70
-
71
- return await new Promise<{ stdout: string }>((resolve, reject) => {
72
- const child = spawn(spawnTarget.command, spawnTarget.argv, {
73
- cwd,
74
- stdio: ["ignore", "pipe", "pipe"],
75
- env,
76
- windowsHide: spawnTarget.windowsHide,
77
- });
78
-
79
- let stdout = "";
80
- let stdoutBytes = 0;
81
- let stderr = "";
82
- let settled = false;
83
-
84
- const settle = (
85
- result: { ok: true; value: { stdout: string } } | { ok: false; error: Error },
86
- ) => {
87
- if (settled) {
88
- return;
89
- }
90
- settled = true;
91
- clearTimeout(timer);
92
- if (result.ok) {
93
- resolve(result.value);
94
- } else {
95
- reject(result.error);
96
- }
97
- };
98
-
99
- const failAndTerminate = (message: string) => {
100
- try {
101
- child.kill("SIGKILL");
102
- } finally {
103
- settle({ ok: false, error: new Error(message) });
104
- }
105
- };
106
-
107
- child.stdout?.setEncoding("utf8");
108
- child.stderr?.setEncoding("utf8");
109
-
110
- child.stdout?.on("data", (chunk) => {
111
- const str = String(chunk);
112
- stdoutBytes += Buffer.byteLength(str, "utf8");
113
- if (stdoutBytes > maxStdoutBytes) {
114
- failAndTerminate("pipeline output exceeded maxStdoutBytes");
115
- return;
116
- }
117
- stdout += str;
118
- });
119
-
120
- child.stderr?.on("data", (chunk) => {
121
- stderr += String(chunk);
122
- });
123
-
124
- const timer = setTimeout(() => {
125
- failAndTerminate("pipeline subprocess timed out");
126
- }, timeoutMs);
127
-
128
- child.once("error", (err) => {
129
- settle({ ok: false, error: err });
130
- });
131
-
132
- child.once("exit", (code) => {
133
- if (code !== 0) {
134
- settle({
135
- ok: false,
136
- error: new Error(`pipeline failed (${code ?? "?"}): ${stderr.trim() || stdout.trim()}`),
137
- });
138
- return;
139
- }
140
- settle({ ok: true, value: { stdout } });
141
- });
142
- });
143
- }
144
-
145
- function parseEnvelope(stdout: string): PipelineEnvelope {
146
- const trimmed = stdout.trim();
147
-
148
- const tryParse = (input: string) => {
149
- try {
150
- return JSON.parse(input) as unknown;
151
- } catch {
152
- return undefined;
153
- }
154
- };
155
-
156
- let parsed: unknown = tryParse(trimmed);
157
-
158
- // Some environments can leak extra stdout (e.g. warnings/logs) before the
159
- // final JSON envelope. Be tolerant and parse the last JSON-looking suffix.
160
- if (parsed === undefined) {
161
- const suffixMatch = trimmed.match(/({[\s\S]*}|\[[\s\S]*])\s*$/);
162
- if (suffixMatch?.[1]) {
163
- parsed = tryParse(suffixMatch[1]);
164
- }
165
- }
166
-
167
- if (parsed === undefined) {
168
- throw new Error("pipeline returned invalid JSON");
169
- }
170
-
171
- if (!parsed || typeof parsed !== "object") {
172
- throw new Error("pipeline returned invalid JSON envelope");
173
- }
174
-
175
- const ok = (parsed as { ok?: unknown }).ok;
176
- if (ok === true || ok === false) {
177
- return parsed as PipelineEnvelope;
178
- }
179
-
180
- throw new Error("pipeline returned invalid JSON envelope");
181
- }
182
-
183
- function buildPipelineArgv(action: string, params: Record<string, unknown>): string[] {
184
- if (action === "run") {
185
- const pipeline = typeof params.pipeline === "string" ? params.pipeline : "";
186
- if (!pipeline.trim()) {
187
- throw new Error("pipeline required");
188
- }
189
- const argv = ["run", "--mode", "tool", pipeline];
190
- const argsJson = typeof params.argsJson === "string" ? params.argsJson : "";
191
- if (argsJson.trim()) {
192
- argv.push("--args-json", argsJson);
193
- }
194
- return argv;
195
- }
196
- if (action === "resume") {
197
- const token = typeof params.token === "string" ? params.token : "";
198
- if (!token.trim()) {
199
- throw new Error("token required");
200
- }
201
- const approve = params.approve;
202
- if (typeof approve !== "boolean") {
203
- throw new Error("approve required");
204
- }
205
- return ["resume", "--token", token, "--approve", approve ? "yes" : "no"];
206
- }
207
- throw new Error(`Unknown action: ${action}`);
208
- }
209
-
210
- export function createPipelineTool(api: SymiPluginApi) {
211
- return {
212
- name: "pipeline",
213
- label: "Pipeline Workflow",
214
- description:
215
- "Run pipelines as a local-first workflow runtime (typed JSON envelope + resumable approvals).",
216
- parameters: Type.Object({
217
- // NOTE: Prefer string enums in tool schemas; some providers reject unions/anyOf.
218
- action: Type.Unsafe<"run" | "resume">({ type: "string", enum: ["run", "resume"] }),
219
- pipeline: Type.Optional(Type.String()),
220
- argsJson: Type.Optional(Type.String()),
221
- token: Type.Optional(Type.String()),
222
- approve: Type.Optional(Type.Boolean()),
223
- cwd: Type.Optional(
224
- Type.String({
225
- description:
226
- "Relative working directory (optional). Must stay within the gateway working directory.",
227
- }),
228
- ),
229
- timeoutMs: Type.Optional(Type.Number()),
230
- maxStdoutBytes: Type.Optional(Type.Number()),
231
- }),
232
- async execute(_id: string, params: Record<string, unknown>) {
233
- const action = typeof params.action === "string" ? params.action.trim() : "";
234
- if (!action) {
235
- throw new Error("action required");
236
- }
237
-
238
- const execPath = "lobster";
239
- const cwd = resolveCwd(params.cwd);
240
- const timeoutMs = typeof params.timeoutMs === "number" ? params.timeoutMs : 20_000;
241
- const maxStdoutBytes =
242
- typeof params.maxStdoutBytes === "number" ? params.maxStdoutBytes : 512_000;
243
-
244
- const argv = buildPipelineArgv(action, params);
245
-
246
- if (api.runtime?.version && api.logger?.debug) {
247
- api.logger.debug(`pipeline plugin runtime=${api.runtime.version}`);
248
- }
249
-
250
- const { stdout } = await runPipelineSubprocessOnce({
251
- execPath,
252
- argv,
253
- cwd,
254
- timeoutMs,
255
- maxStdoutBytes,
256
- });
257
-
258
- const envelope = parseEnvelope(stdout);
259
-
260
- return {
261
- content: [{ type: "text", text: JSON.stringify(envelope, null, 2) }],
262
- details: envelope,
263
- };
264
- },
265
- };
266
- }
@@ -1,148 +0,0 @@
1
- import fs from "node:fs/promises";
2
- import os from "node:os";
3
- import path from "node:path";
4
- import { afterEach, beforeEach, describe, expect, it } from "vitest";
5
- import { resolveWindowsPipelineSpawn } from "./windows-spawn.js";
6
-
7
- function setProcessPlatform(platform: NodeJS.Platform) {
8
- Object.defineProperty(process, "platform", {
9
- value: platform,
10
- configurable: true,
11
- });
12
- }
13
-
14
- describe("resolveWindowsPipelineSpawn", () => {
15
- let tempDir = "";
16
- const originalPlatform = Object.getOwnPropertyDescriptor(process, "platform");
17
- const originalPath = process.env.PATH;
18
- const originalPathAlt = process.env.Path;
19
- const originalPathExt = process.env.PATHEXT;
20
- const originalPathExtAlt = process.env.Pathext;
21
-
22
- beforeEach(async () => {
23
- tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "symi-pipeline-win-spawn-"));
24
- setProcessPlatform("win32");
25
- });
26
-
27
- afterEach(async () => {
28
- if (originalPlatform) {
29
- Object.defineProperty(process, "platform", originalPlatform);
30
- }
31
- if (originalPath === undefined) {
32
- delete process.env.PATH;
33
- } else {
34
- process.env.PATH = originalPath;
35
- }
36
- if (originalPathAlt === undefined) {
37
- delete process.env.Path;
38
- } else {
39
- process.env.Path = originalPathAlt;
40
- }
41
- if (originalPathExt === undefined) {
42
- delete process.env.PATHEXT;
43
- } else {
44
- process.env.PATHEXT = originalPathExt;
45
- }
46
- if (originalPathExtAlt === undefined) {
47
- delete process.env.Pathext;
48
- } else {
49
- process.env.Pathext = originalPathExtAlt;
50
- }
51
- if (tempDir) {
52
- await fs.rm(tempDir, { recursive: true, force: true });
53
- tempDir = "";
54
- }
55
- });
56
-
57
- it("unwraps cmd shim with %dp0% token", async () => {
58
- const scriptPath = path.join(tempDir, "shim-dist", "lobster-cli.cjs");
59
- const shimPath = path.join(tempDir, "shim", "lobster.cmd");
60
- await fs.mkdir(path.dirname(scriptPath), { recursive: true });
61
- await fs.mkdir(path.dirname(shimPath), { recursive: true });
62
- await fs.writeFile(scriptPath, "module.exports = {};\n", "utf8");
63
- await fs.writeFile(
64
- shimPath,
65
- `@echo off\r\n"%dp0%\\..\\shim-dist\\lobster-cli.cjs" %*\r\n`,
66
- "utf8",
67
- );
68
-
69
- const target = resolveWindowsPipelineSpawn(shimPath, ["run", "noop"], process.env);
70
- expect(target.command).toBe(process.execPath);
71
- expect(target.argv).toEqual([scriptPath, "run", "noop"]);
72
- expect(target.windowsHide).toBe(true);
73
- });
74
-
75
- it("unwraps cmd shim with %~dp0% token", async () => {
76
- const scriptPath = path.join(tempDir, "shim-dist", "lobster-cli.cjs");
77
- const shimPath = path.join(tempDir, "shim", "lobster.cmd");
78
- await fs.mkdir(path.dirname(scriptPath), { recursive: true });
79
- await fs.mkdir(path.dirname(shimPath), { recursive: true });
80
- await fs.writeFile(scriptPath, "module.exports = {};\n", "utf8");
81
- await fs.writeFile(
82
- shimPath,
83
- `@echo off\r\n"%~dp0%\\..\\shim-dist\\lobster-cli.cjs" %*\r\n`,
84
- "utf8",
85
- );
86
-
87
- const target = resolveWindowsPipelineSpawn(shimPath, ["run", "noop"], process.env);
88
- expect(target.command).toBe(process.execPath);
89
- expect(target.argv).toEqual([scriptPath, "run", "noop"]);
90
- expect(target.windowsHide).toBe(true);
91
- });
92
-
93
- it("ignores node.exe shim entries and picks pipeline script", async () => {
94
- const shimDir = path.join(tempDir, "shim-with-node");
95
- const scriptPath = path.join(tempDir, "shim-dist-node", "lobster-cli.cjs");
96
- const shimPath = path.join(shimDir, "lobster.cmd");
97
- await fs.mkdir(path.dirname(scriptPath), { recursive: true });
98
- await fs.mkdir(shimDir, { recursive: true });
99
- await fs.writeFile(path.join(shimDir, "node.exe"), "", "utf8");
100
- await fs.writeFile(scriptPath, "module.exports = {};\n", "utf8");
101
- await fs.writeFile(
102
- shimPath,
103
- `@echo off\r\n"%~dp0%\\node.exe" "%~dp0%\\..\\shim-dist-node\\lobster-cli.cjs" %*\r\n`,
104
- "utf8",
105
- );
106
-
107
- const target = resolveWindowsPipelineSpawn(shimPath, ["run", "noop"], process.env);
108
- expect(target.command).toBe(process.execPath);
109
- expect(target.argv).toEqual([scriptPath, "run", "noop"]);
110
- expect(target.windowsHide).toBe(true);
111
- });
112
-
113
- it("resolves pipeline .cmd from PATH and unwraps npm layout shim", async () => {
114
- const binDir = path.join(tempDir, "node_modules", ".bin");
115
- const packageDir = path.join(tempDir, "node_modules", "lobster");
116
- const scriptPath = path.join(packageDir, "dist", "cli.js");
117
- const shimPath = path.join(binDir, "lobster.cmd");
118
- await fs.mkdir(path.dirname(scriptPath), { recursive: true });
119
- await fs.mkdir(binDir, { recursive: true });
120
- await fs.writeFile(shimPath, "@echo off\r\n", "utf8");
121
- await fs.writeFile(
122
- path.join(packageDir, "package.json"),
123
- JSON.stringify({ name: "lobster", version: "0.0.0", bin: { lobster: "dist/cli.js" } }),
124
- "utf8",
125
- );
126
- await fs.writeFile(scriptPath, "module.exports = {};\n", "utf8");
127
-
128
- const env = {
129
- ...process.env,
130
- PATH: `${binDir};${process.env.PATH ?? ""}`,
131
- PATHEXT: ".CMD;.EXE",
132
- };
133
- const target = resolveWindowsPipelineSpawn("lobster", ["run", "noop"], env);
134
- expect(target.command).toBe(process.execPath);
135
- expect(target.argv).toEqual([scriptPath, "run", "noop"]);
136
- expect(target.windowsHide).toBe(true);
137
- });
138
-
139
- it("fails fast when wrapper cannot be resolved without shell execution", async () => {
140
- const badShimPath = path.join(tempDir, "bad-shim", "lobster.cmd");
141
- await fs.mkdir(path.dirname(badShimPath), { recursive: true });
142
- await fs.writeFile(badShimPath, "@echo off\r\nREM no entrypoint\r\n", "utf8");
143
-
144
- expect(() => resolveWindowsPipelineSpawn(badShimPath, ["run", "noop"], process.env)).toThrow(
145
- /without shell execution/,
146
- );
147
- });
148
- });
@@ -1,193 +0,0 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
-
4
- type SpawnTarget = {
5
- command: string;
6
- argv: string[];
7
- windowsHide?: boolean;
8
- };
9
-
10
- function isFilePath(value: string): boolean {
11
- try {
12
- const stat = fs.statSync(value);
13
- return stat.isFile();
14
- } catch {
15
- return false;
16
- }
17
- }
18
-
19
- function resolveWindowsExecutablePath(execPath: string, env: NodeJS.ProcessEnv): string {
20
- if (execPath.includes("/") || execPath.includes("\\") || path.isAbsolute(execPath)) {
21
- return execPath;
22
- }
23
-
24
- const pathValue = env.PATH ?? env.Path ?? process.env.PATH ?? process.env.Path ?? "";
25
- const pathEntries = pathValue
26
- .split(";")
27
- .map((entry) => entry.trim())
28
- .filter(Boolean);
29
-
30
- const hasExtension = path.extname(execPath).length > 0;
31
- const pathExtRaw =
32
- env.PATHEXT ??
33
- env.Pathext ??
34
- process.env.PATHEXT ??
35
- process.env.Pathext ??
36
- ".EXE;.CMD;.BAT;.COM";
37
- const pathExt = hasExtension
38
- ? [""]
39
- : pathExtRaw
40
- .split(";")
41
- .map((ext) => ext.trim())
42
- .filter(Boolean)
43
- .map((ext) => (ext.startsWith(".") ? ext : `.${ext}`));
44
-
45
- for (const dir of pathEntries) {
46
- for (const ext of pathExt) {
47
- for (const candidateExt of [ext, ext.toLowerCase(), ext.toUpperCase()]) {
48
- const candidate = path.join(dir, `${execPath}${candidateExt}`);
49
- if (isFilePath(candidate)) {
50
- return candidate;
51
- }
52
- }
53
- }
54
- }
55
-
56
- return execPath;
57
- }
58
-
59
- function resolveBinEntry(binField: string | Record<string, string> | undefined): string | null {
60
- if (typeof binField === "string") {
61
- const trimmed = binField.trim();
62
- return trimmed || null;
63
- }
64
- if (!binField || typeof binField !== "object") {
65
- return null;
66
- }
67
-
68
- const preferred = binField.lobster;
69
- if (typeof preferred === "string" && preferred.trim()) {
70
- return preferred.trim();
71
- }
72
-
73
- for (const value of Object.values(binField)) {
74
- if (typeof value === "string" && value.trim()) {
75
- return value.trim();
76
- }
77
- }
78
- return null;
79
- }
80
-
81
- function resolvePipelineScriptFromPackageJson(wrapperPath: string): string | null {
82
- const wrapperDir = path.dirname(wrapperPath);
83
- const packageDirs = [
84
- // Local install: <repo>/node_modules/.bin/lobster.cmd -> ../lobster
85
- path.resolve(wrapperDir, "..", "lobster"),
86
- // Global npm install: <npm-prefix>/lobster.cmd -> ./node_modules/lobster
87
- path.resolve(wrapperDir, "node_modules", "lobster"),
88
- ];
89
-
90
- for (const packageDir of packageDirs) {
91
- const packageJsonPath = path.join(packageDir, "package.json");
92
- if (!isFilePath(packageJsonPath)) {
93
- continue;
94
- }
95
-
96
- try {
97
- const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8")) as {
98
- bin?: string | Record<string, string>;
99
- };
100
- const scriptRel = resolveBinEntry(packageJson.bin);
101
- if (!scriptRel) {
102
- continue;
103
- }
104
- const scriptPath = path.resolve(packageDir, scriptRel);
105
- if (isFilePath(scriptPath)) {
106
- return scriptPath;
107
- }
108
- } catch {
109
- // Ignore malformed package metadata; caller will throw a guided error.
110
- }
111
- }
112
-
113
- return null;
114
- }
115
-
116
- function resolvePipelineScriptFromCmdShim(wrapperPath: string): string | null {
117
- if (!isFilePath(wrapperPath)) {
118
- return null;
119
- }
120
-
121
- try {
122
- const content = fs.readFileSync(wrapperPath, "utf8");
123
- const candidates: string[] = [];
124
- const extractRelativeFromToken = (token: string): string | null => {
125
- const match = token.match(/%~?dp0%\s*[\\/]*(.*)$/i);
126
- if (!match) {
127
- return null;
128
- }
129
- const relative = match[1];
130
- if (!relative) {
131
- return null;
132
- }
133
- return relative;
134
- };
135
-
136
- const matches = content.matchAll(/"([^"\r\n]*)"/g);
137
- for (const match of matches) {
138
- const token = match[1] ?? "";
139
- const relative = extractRelativeFromToken(token);
140
- if (!relative) {
141
- continue;
142
- }
143
-
144
- const normalizedRelative = relative
145
- .trim()
146
- .replace(/[\\/]+/g, path.sep)
147
- .replace(/^[\\/]+/, "");
148
- const candidate = path.resolve(path.dirname(wrapperPath), normalizedRelative);
149
- if (isFilePath(candidate)) {
150
- candidates.push(candidate);
151
- }
152
- }
153
-
154
- const nonNode = candidates.find((candidate) => {
155
- const base = path.basename(candidate).toLowerCase();
156
- return base !== "node.exe" && base !== "node";
157
- });
158
- if (nonNode) {
159
- return nonNode;
160
- }
161
- } catch {
162
- // Ignore unreadable shims; caller will throw a guided error.
163
- }
164
-
165
- return null;
166
- }
167
-
168
- export function resolveWindowsPipelineSpawn(
169
- execPath: string,
170
- argv: string[],
171
- env: NodeJS.ProcessEnv,
172
- ): SpawnTarget {
173
- const resolvedExecPath = resolveWindowsExecutablePath(execPath, env);
174
- const ext = path.extname(resolvedExecPath).toLowerCase();
175
- if (ext !== ".cmd" && ext !== ".bat") {
176
- return { command: resolvedExecPath, argv };
177
- }
178
-
179
- const scriptPath =
180
- resolvePipelineScriptFromCmdShim(resolvedExecPath) ??
181
- resolvePipelineScriptFromPackageJson(resolvedExecPath);
182
- if (!scriptPath) {
183
- throw new Error(
184
- `${path.basename(resolvedExecPath)} wrapper resolved, but no Node entrypoint could be resolved without shell execution. Ensure the pipeline runtime is installed and runnable on PATH (prefer lobster.exe).`,
185
- );
186
- }
187
-
188
- const entryExt = path.extname(scriptPath).toLowerCase();
189
- if (entryExt === ".exe") {
190
- return { command: scriptPath, argv, windowsHide: true };
191
- }
192
- return { command: process.execPath, argv: [scriptPath, ...argv], windowsHide: true };
193
- }
@@ -1,10 +0,0 @@
1
- {
2
- "id": "pipeline",
3
- "name": "Pipeline",
4
- "description": "Typed workflow tool with resumable approvals.",
5
- "configSchema": {
6
- "type": "object",
7
- "additionalProperties": false,
8
- "properties": {}
9
- }
10
- }
@@ -1,24 +0,0 @@
1
- # Qwen OAuth (Symi plugin)
2
-
3
- OAuth provider plugin for **Qwen** (free-tier OAuth).
4
-
5
- ## Enable
6
-
7
- Bundled plugins are disabled by default. Enable this one:
8
-
9
- ```bash
10
- symi plugins enable qwen-portal-auth
11
- ```
12
-
13
- Restart the Gateway after enabling.
14
-
15
- ## Authenticate
16
-
17
- ```bash
18
- symi models auth login --provider qwen-portal --set-default
19
- ```
20
-
21
- ## Notes
22
-
23
- - Qwen OAuth uses a device-code login flow.
24
- - Tokens auto-refresh; re-run login if refresh fails or access is revoked.