@twelvehart/orcats 0.0.0-seed.0 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +48 -0
- package/NOTICE +5 -0
- package/README.md +399 -0
- package/bin/orcats +4 -0
- package/dist/backends/acp-client.d.ts +74 -0
- package/dist/backends/acp-client.d.ts.map +1 -0
- package/dist/backends/acp-run.d.ts +31 -0
- package/dist/backends/acp-run.d.ts.map +1 -0
- package/dist/backends/claude-run.d.ts +35 -0
- package/dist/backends/claude-run.d.ts.map +1 -0
- package/dist/backends/claude-stream-json.d.ts +15 -0
- package/dist/backends/claude-stream-json.d.ts.map +1 -0
- package/dist/backends/codex-jsonl.d.ts +34 -0
- package/dist/backends/codex-jsonl.d.ts.map +1 -0
- package/dist/backends/codex-run.d.ts +40 -0
- package/dist/backends/codex-run.d.ts.map +1 -0
- package/dist/backends/codex.d.ts +5 -0
- package/dist/backends/codex.d.ts.map +1 -0
- package/dist/backends/conversation-config.d.ts +12 -0
- package/dist/backends/conversation-config.d.ts.map +1 -0
- package/dist/backends/index.d.ts +14 -0
- package/dist/backends/index.d.ts.map +1 -0
- package/dist/backends/opencode-run.d.ts +48 -0
- package/dist/backends/opencode-run.d.ts.map +1 -0
- package/dist/backends/opencode-sse.d.ts +35 -0
- package/dist/backends/opencode-sse.d.ts.map +1 -0
- package/dist/backends/pi-rpc.d.ts +21 -0
- package/dist/backends/pi-rpc.d.ts.map +1 -0
- package/dist/backends/pi-run.d.ts +21 -0
- package/dist/backends/pi-run.d.ts.map +1 -0
- package/dist/backends/select.d.ts +17 -0
- package/dist/backends/select.d.ts.map +1 -0
- package/dist/backends/subprocess-run.d.ts +68 -0
- package/dist/backends/subprocess-run.d.ts.map +1 -0
- package/dist/backends/types.d.ts +17 -0
- package/dist/backends/types.d.ts.map +1 -0
- package/dist/backends/unsupported.d.ts +9 -0
- package/dist/backends/unsupported.d.ts.map +1 -0
- package/dist/baseline/index.d.ts +56 -0
- package/dist/baseline/index.d.ts.map +1 -0
- package/dist/cli/args.d.ts +22 -0
- package/dist/cli/args.d.ts.map +1 -0
- package/dist/cli/embedded.d.ts +7 -0
- package/dist/cli/embedded.d.ts.map +1 -0
- package/dist/cli/main.d.ts +6 -0
- package/dist/cli/main.d.ts.map +1 -0
- package/dist/cli/version.d.ts +2 -0
- package/dist/cli/version.d.ts.map +1 -0
- package/dist/conversation/ask-user.d.ts +17 -0
- package/dist/conversation/ask-user.d.ts.map +1 -0
- package/dist/conversation/conversation.d.ts +46 -0
- package/dist/conversation/conversation.d.ts.map +1 -0
- package/dist/conversation/harness.d.ts +14 -0
- package/dist/conversation/harness.d.ts.map +1 -0
- package/dist/conversation/index.d.ts +5 -0
- package/dist/conversation/index.d.ts.map +1 -0
- package/dist/conversation/queue.d.ts +15 -0
- package/dist/conversation/queue.d.ts.map +1 -0
- package/dist/flow/accessors.d.ts +13 -0
- package/dist/flow/accessors.d.ts.map +1 -0
- package/dist/flow/approvals.d.ts +6 -0
- package/dist/flow/approvals.d.ts.map +1 -0
- package/dist/flow/args.d.ts +4 -0
- package/dist/flow/args.d.ts.map +1 -0
- package/dist/flow/context.d.ts +36 -0
- package/dist/flow/context.d.ts.map +1 -0
- package/dist/flow/index.d.ts +5 -0
- package/dist/flow/index.d.ts.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/loop/builder/index.d.ts +17 -0
- package/dist/loop/builder/index.d.ts.map +1 -0
- package/dist/loop/builder/types.d.ts +97 -0
- package/dist/loop/builder/types.d.ts.map +1 -0
- package/dist/loop/context/compaction.d.ts +38 -0
- package/dist/loop/context/compaction.d.ts.map +1 -0
- package/dist/loop/context/index.d.ts +4 -0
- package/dist/loop/context/index.d.ts.map +1 -0
- package/dist/loop/context/offload.d.ts +34 -0
- package/dist/loop/context/offload.d.ts.map +1 -0
- package/dist/loop/context/types.d.ts +13 -0
- package/dist/loop/context/types.d.ts.map +1 -0
- package/dist/loop/engine/bridge.d.ts +21 -0
- package/dist/loop/engine/bridge.d.ts.map +1 -0
- package/dist/loop/engine/index.d.ts +73 -0
- package/dist/loop/engine/index.d.ts.map +1 -0
- package/dist/loop/execution.d.ts +68 -0
- package/dist/loop/execution.d.ts.map +1 -0
- package/dist/loop/fanout.d.ts +148 -0
- package/dist/loop/fanout.d.ts.map +1 -0
- package/dist/loop/firing.d.ts +54 -0
- package/dist/loop/firing.d.ts.map +1 -0
- package/dist/loop/graph/analyze.d.ts +8 -0
- package/dist/loop/graph/analyze.d.ts.map +1 -0
- package/dist/loop/graph/index.d.ts +3 -0
- package/dist/loop/graph/index.d.ts.map +1 -0
- package/dist/loop/graph/types.d.ts +35 -0
- package/dist/loop/graph/types.d.ts.map +1 -0
- package/dist/loop/index.d.ts +7 -0
- package/dist/loop/index.d.ts.map +1 -0
- package/dist/loop/io/index.d.ts +4 -0
- package/dist/loop/io/index.d.ts.map +1 -0
- package/dist/loop/io/linear.d.ts +142 -0
- package/dist/loop/io/linear.d.ts.map +1 -0
- package/dist/loop/io/sink.d.ts +47 -0
- package/dist/loop/io/sink.d.ts.map +1 -0
- package/dist/loop/io/source.d.ts +64 -0
- package/dist/loop/io/source.d.ts.map +1 -0
- package/dist/loop/serve.d.ts +97 -0
- package/dist/loop/serve.d.ts.map +1 -0
- package/dist/loop/state/index.d.ts +5 -0
- package/dist/loop/state/index.d.ts.map +1 -0
- package/dist/loop/state/manifest.d.ts +36 -0
- package/dist/loop/state/manifest.d.ts.map +1 -0
- package/dist/loop/state/port.d.ts +33 -0
- package/dist/loop/state/port.d.ts.map +1 -0
- package/dist/loop/state/snapshot.d.ts +15 -0
- package/dist/loop/state/snapshot.d.ts.map +1 -0
- package/dist/loop/state/sqlite.d.ts +31 -0
- package/dist/loop/state/sqlite.d.ts.map +1 -0
- package/dist/loop/termination-contract.d.ts +28 -0
- package/dist/loop/termination-contract.d.ts.map +1 -0
- package/dist/loop/termination.d.ts +33 -0
- package/dist/loop/termination.d.ts.map +1 -0
- package/dist/model/backend-config.d.ts +30 -0
- package/dist/model/backend-config.d.ts.map +1 -0
- package/dist/model/brand.d.ts +9 -0
- package/dist/model/brand.d.ts.map +1 -0
- package/dist/model/errors.d.ts +11 -0
- package/dist/model/errors.d.ts.map +1 -0
- package/dist/model/index.d.ts +8 -0
- package/dist/model/index.d.ts.map +1 -0
- package/dist/model/json-schema.d.ts +4 -0
- package/dist/model/json-schema.d.ts.map +1 -0
- package/dist/model/result.d.ts +3 -0
- package/dist/model/result.d.ts.map +1 -0
- package/dist/model/schemas.d.ts +435 -0
- package/dist/model/schemas.d.ts.map +1 -0
- package/dist/model/structured.d.ts +9 -0
- package/dist/model/structured.d.ts.map +1 -0
- package/dist/monitor/index.d.ts +139 -0
- package/dist/monitor/index.d.ts.map +1 -0
- package/dist/plan/index.d.ts +3 -0
- package/dist/plan/index.d.ts.map +1 -0
- package/dist/plan/persistent.d.ts +23 -0
- package/dist/plan/persistent.d.ts.map +1 -0
- package/dist/plan/verdict.d.ts +9 -0
- package/dist/plan/verdict.d.ts.map +1 -0
- package/dist/review/index.d.ts +4 -0
- package/dist/review/index.d.ts.map +1 -0
- package/dist/review/loop.d.ts +117 -0
- package/dist/review/loop.d.ts.map +1 -0
- package/dist/review/reviewers.d.ts +13 -0
- package/dist/review/reviewers.d.ts.map +1 -0
- package/dist/review/tool.d.ts +20 -0
- package/dist/review/tool.d.ts.map +1 -0
- package/dist/run-output/index.d.ts +112 -0
- package/dist/run-output/index.d.ts.map +1 -0
- package/dist/runner/index.d.ts +2 -0
- package/dist/runner/index.d.ts.map +1 -0
- package/dist/runner/typecheck.d.ts +23 -0
- package/dist/runner/typecheck.d.ts.map +1 -0
- package/dist/test-utils/fakes.d.ts +76 -0
- package/dist/test-utils/fakes.d.ts.map +1 -0
- package/dist/test-utils/fixtures.d.ts +4 -0
- package/dist/test-utils/fixtures.d.ts.map +1 -0
- package/dist/test-utils/index.d.ts +4 -0
- package/dist/test-utils/index.d.ts.map +1 -0
- package/dist/test-utils/tier2.d.ts +8 -0
- package/dist/test-utils/tier2.d.ts.map +1 -0
- package/dist/tools/fs.d.ts +11 -0
- package/dist/tools/fs.d.ts.map +1 -0
- package/dist/tools/git.d.ts +10 -0
- package/dist/tools/git.d.ts.map +1 -0
- package/dist/tools/github.d.ts +13 -0
- package/dist/tools/github.d.ts.map +1 -0
- package/dist/tools/index.d.ts +7 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/linear.d.ts +117 -0
- package/dist/tools/linear.d.ts.map +1 -0
- package/dist/tools/process.d.ts +40 -0
- package/dist/tools/process.d.ts.map +1 -0
- package/dist/tools/terminal.d.ts +23 -0
- package/dist/tools/terminal.d.ts.map +1 -0
- package/package.json +89 -9
- package/src/backends/acp-client.ts +324 -0
- package/src/backends/acp-run.ts +636 -0
- package/src/backends/claude-run.ts +226 -0
- package/src/backends/claude-stream-json.ts +273 -0
- package/src/backends/codex-jsonl.ts +339 -0
- package/src/backends/codex-run.ts +177 -0
- package/src/backends/codex.ts +84 -0
- package/src/backends/conversation-config.ts +26 -0
- package/src/backends/index.ts +13 -0
- package/src/backends/opencode-run.ts +432 -0
- package/src/backends/opencode-sse.ts +360 -0
- package/src/backends/pi-rpc.ts +242 -0
- package/src/backends/pi-run.ts +154 -0
- package/src/backends/select.ts +77 -0
- package/src/backends/subprocess-run.ts +305 -0
- package/src/backends/types.ts +29 -0
- package/src/backends/unsupported.ts +48 -0
- package/src/baseline/index.ts +437 -0
- package/src/cli/args.ts +109 -0
- package/src/cli/embedded.ts +206 -0
- package/src/cli/main.ts +332 -0
- package/src/cli/version.ts +1 -0
- package/src/conversation/ask-user.ts +105 -0
- package/src/conversation/conversation.ts +109 -0
- package/src/conversation/harness.ts +39 -0
- package/src/conversation/index.ts +4 -0
- package/src/conversation/queue.ts +86 -0
- package/src/flow/accessors.ts +43 -0
- package/src/flow/approvals.ts +10 -0
- package/src/flow/args.ts +26 -0
- package/src/flow/context.ts +106 -0
- package/src/flow/index.ts +4 -0
- package/src/index.ts +17 -0
- package/src/loop/builder/index.ts +346 -0
- package/src/loop/builder/types.ts +131 -0
- package/src/loop/context/compaction.ts +175 -0
- package/src/loop/context/index.ts +3 -0
- package/src/loop/context/offload.ts +79 -0
- package/src/loop/context/types.ts +17 -0
- package/src/loop/engine/bridge.ts +61 -0
- package/src/loop/engine/index.ts +166 -0
- package/src/loop/execution.ts +453 -0
- package/src/loop/fanout.ts +444 -0
- package/src/loop/firing.ts +188 -0
- package/src/loop/graph/analyze.ts +128 -0
- package/src/loop/graph/index.ts +2 -0
- package/src/loop/graph/types.ts +45 -0
- package/src/loop/index.ts +11 -0
- package/src/loop/io/index.ts +3 -0
- package/src/loop/io/linear.ts +714 -0
- package/src/loop/io/sink.ts +170 -0
- package/src/loop/io/source.ts +255 -0
- package/src/loop/serve.ts +307 -0
- package/src/loop/state/index.ts +4 -0
- package/src/loop/state/manifest.ts +56 -0
- package/src/loop/state/port.ts +43 -0
- package/src/loop/state/snapshot.ts +129 -0
- package/src/loop/state/sqlite.ts +265 -0
- package/src/loop/termination-contract.ts +89 -0
- package/src/loop/termination.ts +156 -0
- package/src/model/backend-config.ts +34 -0
- package/src/model/brand.ts +11 -0
- package/src/model/errors.ts +28 -0
- package/src/model/index.ts +7 -0
- package/src/model/json-schema.ts +14 -0
- package/src/model/result.ts +28 -0
- package/src/model/schemas.ts +218 -0
- package/src/model/structured.ts +26 -0
- package/src/monitor/index.ts +367 -0
- package/src/plan/index.ts +2 -0
- package/src/plan/persistent.ts +98 -0
- package/src/plan/verdict.ts +3 -0
- package/src/review/index.ts +3 -0
- package/src/review/loop.ts +365 -0
- package/src/review/reviewers.ts +92 -0
- package/src/review/tool.ts +41 -0
- package/src/run-output/index.ts +319 -0
- package/src/runner/index.ts +1 -0
- package/src/runner/typecheck.ts +72 -0
- package/src/test-utils/fakes.ts +260 -0
- package/src/test-utils/fixtures.ts +33 -0
- package/src/test-utils/index.ts +3 -0
- package/src/test-utils/tier2.ts +12 -0
- package/src/tools/fs.ts +44 -0
- package/src/tools/git.ts +34 -0
- package/src/tools/github.ts +25 -0
- package/src/tools/index.ts +6 -0
- package/src/tools/linear.ts +428 -0
- package/src/tools/process.ts +156 -0
- package/src/tools/terminal.ts +116 -0
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, realpathSync, rmSync, rmdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import * as root from "../index.ts";
|
|
4
|
+
import * as loop from "../loop/index.ts";
|
|
5
|
+
import * as model from "../model/index.ts";
|
|
6
|
+
|
|
7
|
+
const PackageName = "@twelvehart/orcats";
|
|
8
|
+
const EmbeddedKey = Symbol.for("@twelvehart/orcats.embedded");
|
|
9
|
+
const RuntimeExports = {
|
|
10
|
+
".": "./index.cjs",
|
|
11
|
+
"./loop": "./loop.cjs",
|
|
12
|
+
"./model": "./model.cjs"
|
|
13
|
+
} as const;
|
|
14
|
+
|
|
15
|
+
interface EnsureOrcaResolvableOptions {
|
|
16
|
+
readonly cleanup?: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function ensureOrcaResolvable(scriptPath: string, options: EnsureOrcaResolvableOptions = {}): boolean {
|
|
20
|
+
const scriptDir = dirname(scriptPath);
|
|
21
|
+
registerEmbeddedRegistry();
|
|
22
|
+
const includeSelfReference = isBunExecutable();
|
|
23
|
+
const missingPackages = [PackageName].filter(
|
|
24
|
+
(specifier) => !hasProjectPackage(specifier, scriptDir, includeSelfReference)
|
|
25
|
+
);
|
|
26
|
+
if (missingPackages.length === 0) {
|
|
27
|
+
scheduleExistingEmbeddedCleanup(scriptDir);
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
registerEmbeddedOrca(scriptDir, missingPackages, options.cleanup ?? true);
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function canResolveOrca(scriptPath: string): boolean {
|
|
35
|
+
const scriptDir = dirname(scriptPath);
|
|
36
|
+
return canResolveFrom(PackageName, scriptDir);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function canResolveFrom(specifier: string, fromDir: string): boolean {
|
|
40
|
+
if (isBunExecutable()) {
|
|
41
|
+
const probe = Bun.spawnSync(
|
|
42
|
+
[
|
|
43
|
+
process.execPath,
|
|
44
|
+
"--eval",
|
|
45
|
+
`Bun.resolveSync(${JSON.stringify(specifier)}, ${JSON.stringify(fromDir)});`,
|
|
46
|
+
],
|
|
47
|
+
{
|
|
48
|
+
stdin: "ignore",
|
|
49
|
+
stdout: "ignore",
|
|
50
|
+
stderr: "ignore",
|
|
51
|
+
},
|
|
52
|
+
);
|
|
53
|
+
return probe.exitCode === 0;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
Bun.resolveSync(specifier, fromDir);
|
|
58
|
+
return true;
|
|
59
|
+
} catch {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function hasProjectPackage(specifier: string, fromDir: string, includeSelfReference: boolean): boolean {
|
|
65
|
+
if (!canResolveFrom(specifier, fromDir)) {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
for (let dir = fromDir; ; dir = dirname(dir)) {
|
|
69
|
+
if (existsSync(join(dir, "node_modules", ...specifier.split("/"), "package.json"))) {
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
if (includeSelfReference && packageJsonName(join(dir, "package.json")) === specifier) {
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
const parent = dirname(dir);
|
|
76
|
+
if (parent === dir) {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function isBunExecutable(): boolean {
|
|
83
|
+
const bunPath = Bun.which("bun");
|
|
84
|
+
if (bunPath === null) {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
return realpathSync(process.execPath) === realpathSync(bunPath);
|
|
89
|
+
} catch {
|
|
90
|
+
return process.execPath === bunPath;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function packageJsonName(path: string): string | undefined {
|
|
95
|
+
try {
|
|
96
|
+
const parsed = JSON.parse(readFileSync(path, "utf8")) as { name?: unknown };
|
|
97
|
+
return typeof parsed.name === "string" ? parsed.name : undefined;
|
|
98
|
+
} catch {
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function registerEmbeddedRegistry(): void {
|
|
104
|
+
Reflect.set(globalThis, EmbeddedKey, {
|
|
105
|
+
root: { ...root },
|
|
106
|
+
loop: { ...loop },
|
|
107
|
+
model: { ...model }
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function registerEmbeddedOrca(scriptDir: string, packageNames: readonly string[], cleanup: boolean): void {
|
|
112
|
+
const nodeModulesDir = join(scriptDir, "node_modules");
|
|
113
|
+
const createdNodeModules = !existsSync(nodeModulesDir);
|
|
114
|
+
const packageDirs: string[] = [];
|
|
115
|
+
const scopeDirs = new Set<string>();
|
|
116
|
+
|
|
117
|
+
for (const packageName of packageNames) {
|
|
118
|
+
const packageDir = join(nodeModulesDir, ...packageName.split("/"));
|
|
119
|
+
if (existsSync(packageDir)) {
|
|
120
|
+
throw new Error(`orcats: found ${packageDir} but could not resolve "${packageName}" from ${scriptDir}`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
mkdirSync(packageDir, { recursive: true });
|
|
124
|
+
writeFileSync(join(packageDir, "package.json"), `${packageJson(packageName)}\n`);
|
|
125
|
+
writeFileSync(join(packageDir, "index.cjs"), embeddedModule("root"));
|
|
126
|
+
writeFileSync(join(packageDir, "loop.cjs"), embeddedModule("loop"));
|
|
127
|
+
writeFileSync(join(packageDir, "model.cjs"), embeddedModule("model"));
|
|
128
|
+
packageDirs.push(packageDir);
|
|
129
|
+
|
|
130
|
+
if (packageName.startsWith("@")) {
|
|
131
|
+
scopeDirs.add(join(nodeModulesDir, packageName.split("/")[0] ?? ""));
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (cleanup) {
|
|
136
|
+
scheduleCleanup(packageDirs, [...scopeDirs], nodeModulesDir, createdNodeModules);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function packageJson(packageName: string): string {
|
|
141
|
+
return JSON.stringify({
|
|
142
|
+
name: packageName,
|
|
143
|
+
orcatsEmbedded: true,
|
|
144
|
+
main: "index.cjs",
|
|
145
|
+
exports: RuntimeExports
|
|
146
|
+
}, null, 2);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function embeddedModule(name: "root" | "loop" | "model"): string {
|
|
150
|
+
return `const registry = globalThis[Symbol.for("@twelvehart/orcats.embedded")];\nif (!registry) throw new Error("orcats embedded library is not registered");\nmodule.exports = registry.${name};\n`;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function scheduleExistingEmbeddedCleanup(scriptDir: string): void {
|
|
154
|
+
const nodeModulesDir = join(scriptDir, "node_modules");
|
|
155
|
+
const packageDirs = [PackageName]
|
|
156
|
+
.map((packageName) => join(nodeModulesDir, ...packageName.split("/")))
|
|
157
|
+
.filter(isEmbeddedPackageDir);
|
|
158
|
+
if (packageDirs.length === 0) {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
const scopeDirs = packageDirs
|
|
162
|
+
.map((packageDir) => dirname(packageDir))
|
|
163
|
+
.filter((scopeDir) => dirname(scopeDir) === nodeModulesDir);
|
|
164
|
+
scheduleCleanup(packageDirs, scopeDirs, nodeModulesDir, true);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function isEmbeddedPackageDir(packageDir: string): boolean {
|
|
168
|
+
try {
|
|
169
|
+
const parsed = JSON.parse(readFileSync(join(packageDir, "package.json"), "utf8")) as {
|
|
170
|
+
orcatsEmbedded?: unknown;
|
|
171
|
+
};
|
|
172
|
+
return parsed.orcatsEmbedded === true;
|
|
173
|
+
} catch {
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function scheduleCleanup(
|
|
179
|
+
packageDirs: readonly string[],
|
|
180
|
+
scopeDirs: readonly string[],
|
|
181
|
+
nodeModulesDir: string,
|
|
182
|
+
createdNodeModules: boolean
|
|
183
|
+
): void {
|
|
184
|
+
let cleaned = false;
|
|
185
|
+
process.once("exit", () => {
|
|
186
|
+
if (cleaned) return;
|
|
187
|
+
cleaned = true;
|
|
188
|
+
for (const packageDir of packageDirs) {
|
|
189
|
+
rmSync(packageDir, { recursive: true, force: true });
|
|
190
|
+
}
|
|
191
|
+
for (const scopeDir of scopeDirs) {
|
|
192
|
+
try {
|
|
193
|
+
rmdirSync(scopeDir);
|
|
194
|
+
} catch {
|
|
195
|
+
// Another scoped package may exist or have been created before exit.
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
if (createdNodeModules) {
|
|
199
|
+
try {
|
|
200
|
+
rmdirSync(nodeModulesDir);
|
|
201
|
+
} catch {
|
|
202
|
+
// Another process may have created a sibling package before exit.
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
}
|
package/src/cli/main.ts
ADDED
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
import { pathToFileURL } from "node:url";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { realpathSync } from "node:fs";
|
|
4
|
+
import { runTypecheck } from "../runner/index.ts";
|
|
5
|
+
import { FLOW_ARGS_ENV } from "../flow/args.ts";
|
|
6
|
+
import { unsupportedFeature, type RuntimeError } from "../model/index.ts";
|
|
7
|
+
import {
|
|
8
|
+
discoverLoops,
|
|
9
|
+
formatLoopListing,
|
|
10
|
+
listLoops,
|
|
11
|
+
loadDefinition,
|
|
12
|
+
serve,
|
|
13
|
+
type ModuleImporter
|
|
14
|
+
} from "../loop/index.ts";
|
|
15
|
+
import { decodeLoopEvent, runLoopFiring } from "../loop/firing.ts";
|
|
16
|
+
import {
|
|
17
|
+
createRunPresenter,
|
|
18
|
+
createRunReporter,
|
|
19
|
+
withRunReporter,
|
|
20
|
+
type RunReporter,
|
|
21
|
+
} from "../run-output/index.ts";
|
|
22
|
+
import { parseCliArgs, type CliArgs } from "./args.ts";
|
|
23
|
+
import { ORCA_VERSION } from "./version.ts";
|
|
24
|
+
|
|
25
|
+
const USAGE = [
|
|
26
|
+
"Usage: orcats [--backend <name>] [--no-typecheck] <flow.ts> [-- <task args>]",
|
|
27
|
+
" orcats run <loop> run a loop once; exit status reflects the stop reason",
|
|
28
|
+
" orcats serve <loop> host a loop's trigger, spawning a child process per firing",
|
|
29
|
+
" orcats loops list defined loops with their source and sink",
|
|
30
|
+
" orcats --version"
|
|
31
|
+
].join("\n");
|
|
32
|
+
|
|
33
|
+
const DEFERRED_DBOS_NOTE =
|
|
34
|
+
"durable DBOS mode is deferred — see openspec/changes/add-loop-builder/design.md §D5 (DBOS Bun-compatibility spike). " +
|
|
35
|
+
"Run without --durable/--postgres-url and without `--state dbos` to use the service-free default adapter.";
|
|
36
|
+
// Private parent->child handshake for the embedded-fallback respawn. The value is the
|
|
37
|
+
// PARENT's pid, not a constant flag: a genuine child's process.ppid equals it, while a stale
|
|
38
|
+
// value inherited from an unrelated shell or a prior orcats process does not. Validating against
|
|
39
|
+
// ppid keeps a leaked ORCA_EMBEDDED_RESPAWNED from making a fresh invocation skip the bootstrap
|
|
40
|
+
// + respawn (which would then fail to resolve @twelvehart/orcats from a bare directory).
|
|
41
|
+
const EMBEDDED_RESPAWN_ENV = "ORCA_EMBEDDED_RESPAWNED";
|
|
42
|
+
|
|
43
|
+
/** True only when this process is the embedded-fallback child spawned by THIS run's parent. */
|
|
44
|
+
function isEmbeddedRespawnChild(): boolean {
|
|
45
|
+
const token = process.env[EMBEDDED_RESPAWN_ENV];
|
|
46
|
+
return token !== undefined && token === String(process.ppid);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Durable DBOS mode is not selectable in this change (spec distribution; design D5). */
|
|
50
|
+
export function deferredDurableError(args: CliArgs): RuntimeError | undefined {
|
|
51
|
+
if (args.durable === true || args.postgresUrl !== undefined || args.stateAdapter === "dbos") {
|
|
52
|
+
return unsupportedFeature("durable", DEFERRED_DBOS_NOTE);
|
|
53
|
+
}
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function main(argv: readonly string[] = process.argv.slice(2)): Promise<void> {
|
|
58
|
+
const args = parseCliArgs(argv);
|
|
59
|
+
|
|
60
|
+
if (args.version) {
|
|
61
|
+
console.log(`orcats ${ORCA_VERSION}`);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Reject deferred durable mode before any typecheck/import so the pointer surfaces immediately.
|
|
66
|
+
const deferred = deferredDurableError(args);
|
|
67
|
+
if (deferred !== undefined) {
|
|
68
|
+
process.stderr.write(`orcats: ${describeError(deferred)}\n`);
|
|
69
|
+
process.exitCode = 1;
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (args.help || (args.command === undefined && args.script === undefined)) {
|
|
74
|
+
console.log(USAGE);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if ((args.command === "run" || args.command === "serve") && args.loop === undefined) {
|
|
79
|
+
process.stderr.write(`orcats: ${args.command} requires a <loop> (module path or registered name)\n`);
|
|
80
|
+
process.exitCode = 1;
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const reporter = createCliReporter();
|
|
85
|
+
|
|
86
|
+
if (!(await preflight(args, reporter))) {
|
|
87
|
+
return; // typecheck failed; exit code already set
|
|
88
|
+
}
|
|
89
|
+
if (await respawnIfEmbeddedFallbackNeeded(args, argv)) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (args.command === "loops") {
|
|
94
|
+
await runLoops();
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
if (args.command === "run" && args.loop !== undefined) {
|
|
98
|
+
await runLoop(args.loop, reporter);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
if (args.command === "serve" && args.loop !== undefined) {
|
|
102
|
+
await runServe(args.loop);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (args.script !== undefined) {
|
|
106
|
+
await runFlowScript(args.script, argv, reporter);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Shared preflight for every command: typecheck (unless skipped) + backend/flow-arg env wiring. */
|
|
111
|
+
async function preflight(args: CliArgs, reporter: RunReporter): Promise<boolean> {
|
|
112
|
+
if (isEmbeddedRespawnChild()) {
|
|
113
|
+
return true; // the spawning parent already ran preflight; the child inherits its env
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
reporter.emit({ type: "preflight", name: "typecheck", status: "started" });
|
|
117
|
+
const typecheck = await runTypecheck({ cwd: process.cwd(), skip: args.skipTypecheck });
|
|
118
|
+
if (typecheck.isErr()) {
|
|
119
|
+
const error = typecheck.error;
|
|
120
|
+
reporter.emit({ type: "preflight", name: "typecheck", status: "failed", reason: describeError(error) });
|
|
121
|
+
if (error._tag === "TypecheckFailed") {
|
|
122
|
+
process.stderr.write(error.stdout);
|
|
123
|
+
process.stderr.write(error.stderr);
|
|
124
|
+
} else {
|
|
125
|
+
process.stderr.write(`${JSON.stringify(error)}\n`);
|
|
126
|
+
}
|
|
127
|
+
process.exitCode = 1;
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (typecheck.value.skipped) {
|
|
132
|
+
reporter.emit({
|
|
133
|
+
type: "preflight",
|
|
134
|
+
name: "typecheck",
|
|
135
|
+
status: "skipped",
|
|
136
|
+
...(typecheck.value.reason === undefined ? {} : { reason: typecheck.value.reason }),
|
|
137
|
+
});
|
|
138
|
+
if (typecheck.value.reason === "tsc-not-found") {
|
|
139
|
+
process.stderr.write(
|
|
140
|
+
"orcats: missing project typecheck setup; skipping typecheck. Add typescript, tsconfig.json, and a local @twelvehart/orcats package dependency to enable it.\n"
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
process.env.ORCA_TYPECHECK_SKIPPED = "1";
|
|
144
|
+
} else {
|
|
145
|
+
reporter.emit({ type: "preflight", name: "typecheck", status: "passed" });
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (args.backend) {
|
|
149
|
+
process.env.ORCA_BACKEND = args.backend;
|
|
150
|
+
}
|
|
151
|
+
process.env[FLOW_ARGS_ENV] = JSON.stringify(args.flowArgs);
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** `orcats loops`: discover and list defined loops without firing any Source / backend / Sink. */
|
|
156
|
+
async function runLoops(): Promise<void> {
|
|
157
|
+
const importLoop = await loopImporter();
|
|
158
|
+
const discovered = await discoverLoops({ dir: resolve(process.cwd(), ".orca", "loops"), import: importLoop });
|
|
159
|
+
if (discovered.isErr()) {
|
|
160
|
+
process.stderr.write(`orcats: ${describeError(discovered.error)}\n`);
|
|
161
|
+
process.exitCode = 1;
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
console.log(formatLoopListing(listLoops(discovered.value.values())));
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** `orcats run <loop>`: resolve the loop, run it once, exit with a status reflecting the stop reason. */
|
|
168
|
+
async function runLoop(target: string, reporter: RunReporter): Promise<void> {
|
|
169
|
+
const importLoop = await loopImporter();
|
|
170
|
+
const loaded = await loadDefinition(target, { cwd: process.cwd(), import: importLoop });
|
|
171
|
+
if (loaded.isErr()) {
|
|
172
|
+
process.stderr.write(`orcats: ${describeError(loaded.error)}\n`);
|
|
173
|
+
process.exitCode = 1;
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
const definition = loaded.value;
|
|
177
|
+
const firing = await runLoopFiring(definition, decodeLoopEvent(), { reporter });
|
|
178
|
+
process.exitCode = firing.exitCode;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** `orcats serve <loop>`: a thin supervisor owning the trigger, spawning a child per firing (D8). */
|
|
182
|
+
async function runServe(target: string): Promise<void> {
|
|
183
|
+
const importLoop = await loopImporter();
|
|
184
|
+
const loaded = await loadDefinition(target, { cwd: process.cwd(), import: importLoop });
|
|
185
|
+
if (loaded.isErr()) {
|
|
186
|
+
process.stderr.write(`orcats: ${describeError(loaded.error)}\n`);
|
|
187
|
+
process.exitCode = 1;
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
const definition = loaded.value;
|
|
191
|
+
const supervisor = await serve(definition, { loopRef: target });
|
|
192
|
+
if (supervisor.isErr()) {
|
|
193
|
+
process.stderr.write(`orcats: ${describeError(supervisor.error)}\n`);
|
|
194
|
+
process.exitCode = 1;
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
process.stderr.write(
|
|
198
|
+
`orcats: serving loop "${definition.name}" (source=${definition.source.kind}); press Ctrl-C to stop\n`
|
|
199
|
+
);
|
|
200
|
+
await waitForShutdown();
|
|
201
|
+
await supervisor.value.stop();
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** Legacy flow-script path, with one respawn when the embedded fallback is needed. */
|
|
205
|
+
async function runFlowScript(script: string, argv: readonly string[], reporter: RunReporter): Promise<void> {
|
|
206
|
+
const resolvedScript = resolve(script);
|
|
207
|
+
const { ensureOrcaResolvable } = await import("./embedded.ts");
|
|
208
|
+
const shouldRespawn = !isEmbeddedRespawnChild() && !isBunExecutable();
|
|
209
|
+
const registeredFallback = ensureOrcaResolvable(resolvedScript, { cleanup: !shouldRespawn });
|
|
210
|
+
if (registeredFallback && shouldRespawn) {
|
|
211
|
+
respawnWithEmbeddedFallback(argv);
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
reporter.emit({ type: "run_started", label: resolvedScript });
|
|
215
|
+
try {
|
|
216
|
+
await withRunReporter(reporter, () => import(pathToFileURL(resolvedScript).href));
|
|
217
|
+
reporter.emit({ type: "run_finished", label: resolvedScript, status: "success" });
|
|
218
|
+
} catch (error) {
|
|
219
|
+
reporter.emit({ type: "run_finished", label: resolvedScript, status: "failed", error: describeError(error) });
|
|
220
|
+
throw error;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** An embedded-aware importer: register the standalone package fallback, then import the module. */
|
|
225
|
+
async function loopImporter(): Promise<ModuleImporter> {
|
|
226
|
+
const { ensureOrcaResolvable } = await import("./embedded.ts");
|
|
227
|
+
return async (absolutePath) => {
|
|
228
|
+
ensureOrcaResolvable(absolutePath);
|
|
229
|
+
const module: unknown = await import(pathToFileURL(absolutePath).href);
|
|
230
|
+
return module as Record<string, unknown>;
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async function respawnIfEmbeddedFallbackNeeded(args: CliArgs, argv: readonly string[]): Promise<boolean> {
|
|
235
|
+
if (isEmbeddedRespawnChild() || isBunExecutable()) {
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const { ensureOrcaResolvable } = await import("./embedded.ts");
|
|
240
|
+
let registeredFallback = false;
|
|
241
|
+
if (args.script !== undefined) {
|
|
242
|
+
registeredFallback = ensureOrcaResolvable(resolve(args.script), { cleanup: false });
|
|
243
|
+
} else if (args.command === "run" || args.command === "serve") {
|
|
244
|
+
registeredFallback = ensureOrcaResolvable(loopFallbackProbe(args.loop), { cleanup: false });
|
|
245
|
+
} else if (args.command === "loops") {
|
|
246
|
+
registeredFallback = ensureOrcaResolvable(resolve(".orca", "loops", "__orca_fallback_probe__.ts"), { cleanup: false });
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (!registeredFallback) {
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
respawnWithEmbeddedFallback(argv);
|
|
253
|
+
return true;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function loopFallbackProbe(loopRef: string | undefined): string {
|
|
257
|
+
if (loopRef !== undefined && (loopRef.includes("/") || loopRef.endsWith(".ts"))) {
|
|
258
|
+
return resolve(loopRef);
|
|
259
|
+
}
|
|
260
|
+
return resolve(".orca", "loops", "__orca_fallback_probe__.ts");
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function isBunExecutable(): boolean {
|
|
264
|
+
const bunPath = Bun.which("bun");
|
|
265
|
+
if (bunPath === null) {
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
try {
|
|
269
|
+
return realpathSync(process.execPath) === realpathSync(bunPath);
|
|
270
|
+
} catch {
|
|
271
|
+
return process.execPath === bunPath;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function respawnWithEmbeddedFallback(argv: readonly string[]): void {
|
|
276
|
+
const child = Bun.spawnSync([process.execPath, ...argv], {
|
|
277
|
+
cwd: process.cwd(),
|
|
278
|
+
env: {
|
|
279
|
+
...process.env,
|
|
280
|
+
// Stamp the handshake with our pid; the child validates it against process.ppid.
|
|
281
|
+
[EMBEDDED_RESPAWN_ENV]: String(process.pid)
|
|
282
|
+
},
|
|
283
|
+
stdin: "inherit",
|
|
284
|
+
stdout: "inherit",
|
|
285
|
+
stderr: "inherit"
|
|
286
|
+
});
|
|
287
|
+
process.exitCode = child.exitCode;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/** Resolve when the supervisor receives a termination signal. */
|
|
291
|
+
function waitForShutdown(): Promise<void> {
|
|
292
|
+
return new Promise<void>((resolveShutdown) => {
|
|
293
|
+
const stop = (): void => {
|
|
294
|
+
process.off("SIGINT", stop);
|
|
295
|
+
process.off("SIGTERM", stop);
|
|
296
|
+
resolveShutdown();
|
|
297
|
+
};
|
|
298
|
+
process.once("SIGINT", stop);
|
|
299
|
+
process.once("SIGTERM", stop);
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function createCliReporter(): RunReporter {
|
|
304
|
+
return createRunReporter({
|
|
305
|
+
sinks: [
|
|
306
|
+
createRunPresenter({
|
|
307
|
+
isTTY: process.stderr.isTTY,
|
|
308
|
+
writeDiagnostic: (message) => {
|
|
309
|
+
process.stderr.write(message);
|
|
310
|
+
},
|
|
311
|
+
}),
|
|
312
|
+
],
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function describeError(error: unknown): string {
|
|
317
|
+
if (typeof error === "object" && error !== null && "message" in error && typeof error.message === "string") {
|
|
318
|
+
return error.message;
|
|
319
|
+
}
|
|
320
|
+
if (typeof error === "object" && error !== null && "reason" in error && typeof error.reason === "string") {
|
|
321
|
+
return error.reason;
|
|
322
|
+
}
|
|
323
|
+
try {
|
|
324
|
+
return JSON.stringify(error);
|
|
325
|
+
} catch {
|
|
326
|
+
return String(error);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (import.meta.main) {
|
|
331
|
+
await main();
|
|
332
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const ORCA_VERSION = "0.2.3";
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
export interface AskUserRequest {
|
|
2
|
+
readonly question: string;
|
|
3
|
+
readonly rawInput?: unknown;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export type AskUserResponder = (request: AskUserRequest) => Promise<string> | string;
|
|
7
|
+
|
|
8
|
+
export interface AskUserMcpServer {
|
|
9
|
+
readonly url: string;
|
|
10
|
+
ask(request: AskUserRequest): Promise<string>;
|
|
11
|
+
close(): Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface AskUserMcpServerOptions {
|
|
15
|
+
readonly responder: AskUserResponder;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** The tool name Codex's MCP client discovers and calls (namespaced `orcats.ask_user`). */
|
|
19
|
+
export const ASK_USER_TOOL_NAME = "ask_user";
|
|
20
|
+
|
|
21
|
+
const ASK_USER_TOOL = {
|
|
22
|
+
name: ASK_USER_TOOL_NAME,
|
|
23
|
+
description: "Ask the human operator a single free-form clarifying question.",
|
|
24
|
+
inputSchema: {
|
|
25
|
+
type: "object",
|
|
26
|
+
properties: { question: { type: "string" } },
|
|
27
|
+
required: ["question"]
|
|
28
|
+
}
|
|
29
|
+
} as const;
|
|
30
|
+
|
|
31
|
+
export function createAskUserMcpServer(options: AskUserMcpServerOptions): AskUserMcpServer {
|
|
32
|
+
const server = Bun.serve({
|
|
33
|
+
port: 0,
|
|
34
|
+
async fetch(request) {
|
|
35
|
+
if (request.method !== "POST") {
|
|
36
|
+
return new Response("not found", { status: 404 });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const body = (await request.json()) as {
|
|
40
|
+
readonly id?: string | number;
|
|
41
|
+
readonly method?: string;
|
|
42
|
+
readonly params?: {
|
|
43
|
+
readonly arguments?: { readonly question?: string };
|
|
44
|
+
};
|
|
45
|
+
readonly question?: string;
|
|
46
|
+
};
|
|
47
|
+
const id = body.id ?? null;
|
|
48
|
+
|
|
49
|
+
if (body.method?.startsWith("notifications/")) {
|
|
50
|
+
return new Response(null, { status: 202 });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (body.method === "initialize") {
|
|
54
|
+
return Response.json({
|
|
55
|
+
jsonrpc: "2.0",
|
|
56
|
+
id,
|
|
57
|
+
result: {
|
|
58
|
+
protocolVersion: "2024-11-05",
|
|
59
|
+
capabilities: { tools: {} },
|
|
60
|
+
serverInfo: { name: "orcats", version: "1.0.0" }
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (body.method === "tools/list") {
|
|
66
|
+
return Response.json({
|
|
67
|
+
jsonrpc: "2.0",
|
|
68
|
+
id,
|
|
69
|
+
result: { tools: [ASK_USER_TOOL] }
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (body.method === "tools/call") {
|
|
74
|
+
const question = body.params?.arguments?.question ?? "";
|
|
75
|
+
const answer = await options.responder({ question, rawInput: body });
|
|
76
|
+
return Response.json({
|
|
77
|
+
jsonrpc: "2.0",
|
|
78
|
+
id,
|
|
79
|
+
result: { content: [{ type: "text", text: answer }] }
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (body.method !== undefined) {
|
|
84
|
+
return Response.json({
|
|
85
|
+
jsonrpc: "2.0",
|
|
86
|
+
id,
|
|
87
|
+
error: { code: -32601, message: `Method not found: ${body.method}` }
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const answer = await options.responder({ question: body.question ?? "", rawInput: body });
|
|
92
|
+
return Response.json({ answer });
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
url: server.url.href.replace(/\/$/, ""),
|
|
98
|
+
async ask(request) {
|
|
99
|
+
return options.responder(request);
|
|
100
|
+
},
|
|
101
|
+
async close() {
|
|
102
|
+
return server.stop(true);
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
}
|