cueloop 0.1.0-alpha.80 → 0.1.0-alpha.81

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cueloop",
3
- "version": "0.1.0-alpha.80",
3
+ "version": "0.1.0-alpha.81",
4
4
  "description": "Terminal review surface for coding agents: annotate plans and diffs, return a message, send structured feedback back",
5
5
  "homepage": "https://github.com/mmurakaru/cueloop#readme",
6
6
  "bugs": {
@@ -27,13 +27,15 @@
27
27
  "access": "public"
28
28
  },
29
29
  "scripts": {
30
- "build:binary": "bun build --compile --external cpu-features ./src/main.ts --outfile dist/cueloop"
30
+ "build:binary": "bun build --compile --minify --external cpu-features ./src/main.ts --outfile dist/cueloop"
31
31
  },
32
32
  "dependencies": {
33
- "@cueloop/adapters": "0.1.0-alpha.80",
34
- "@cueloop/client": "0.1.0-alpha.80",
35
- "@cueloop/daemon": "0.1.0-alpha.80",
36
- "@cueloop/schema": "0.1.0-alpha.80",
33
+ "@cueloop/adapters": "0.1.0-alpha.81",
34
+ "@cueloop/client": "0.1.0-alpha.81",
35
+ "@cueloop/daemon": "0.1.0-alpha.81",
36
+ "@cueloop/schema": "0.1.0-alpha.81",
37
+ "@modelcontextprotocol/server": "2.0.0",
38
+ "@valibot/to-json-schema": "1.4.0",
37
39
  "valibot": "^1.4.2"
38
40
  },
39
41
  "engines": {
@@ -0,0 +1,21 @@
1
+ import { CodexHookInputSchema, runCodexHook } from "@cueloop/adapters/codex/hook";
2
+ import * as v from "valibot";
3
+
4
+ /** Read one Codex lifecycle event and return its optional tool rewrite. */
5
+ export async function codexHookCommand(): Promise<number> {
6
+ try {
7
+ const input = v.parse(
8
+ CodexHookInputSchema,
9
+ JSON.parse(await new Response(Bun.stdin.stream()).text()),
10
+ );
11
+ const output = runCodexHook(input);
12
+
13
+ if (output) console.log(JSON.stringify(output));
14
+
15
+ return 0;
16
+ } catch (error) {
17
+ console.error(`cueloop Codex hook failed: ${String(error)}`);
18
+
19
+ return 1;
20
+ }
21
+ }
@@ -0,0 +1,104 @@
1
+ import { McpServer } from "@modelcontextprotocol/server";
2
+ import { serveStdio } from "@modelcontextprotocol/server/stdio";
3
+ import { toJsonSchema } from "@valibot/to-json-schema";
4
+ import * as v from "valibot";
5
+ import { createCodexDeliveryService } from "@cueloop/adapters/codex/delivery-service";
6
+ import { createCodexSessionRegistry } from "@cueloop/adapters/codex/session-registry";
7
+ import { runHarnessBridge } from "@cueloop/adapters/harness-bridge";
8
+ import { cueloopHome } from "@cueloop/daemon/paths";
9
+ import { WORKFLOW_KINDS } from "@cueloop/schema";
10
+ import { CLI_VERSION } from "./version";
11
+
12
+ const OpenThreadInputSchema = v.object({
13
+ workflow: v.picklist(WORKFLOW_KINDS),
14
+ content: v.optional(v.string()),
15
+ proposal: v.optional(v.string()),
16
+ pullRequestReference: v.optional(v.string()),
17
+ title: v.optional(v.string()),
18
+ harnessSessionId: v.pipe(v.string(), v.minLength(1)),
19
+ cwd: v.string(),
20
+ hookToken: v.pipe(v.string(), v.minLength(1)),
21
+ });
22
+ const RefineCorpusInputSchema = v.object({});
23
+
24
+ function mcpInputSchema<TSchema extends v.BaseSchema<unknown, unknown, v.BaseIssue<unknown>>>(
25
+ schema: TSchema,
26
+ ) {
27
+ const convert = () => toJsonSchema(schema);
28
+
29
+ return {
30
+ ...schema,
31
+ "~standard": {
32
+ ...schema["~standard"],
33
+ jsonSchema: { input: convert, output: convert },
34
+ },
35
+ };
36
+ }
37
+
38
+ /** Expose the shared Thread workflows to a standard Codex session. */
39
+ export function createCodexMcpServer(home = cueloopHome()): McpServer {
40
+ const server = new McpServer({ name: "cueloop", version: CLI_VERSION });
41
+ const sessions = createCodexSessionRegistry(home);
42
+
43
+ server.registerTool(
44
+ "open_thread",
45
+ {
46
+ description:
47
+ "Submit or revise a cueloop Thread for plan, reply, prototype, diff, review, or refine.",
48
+ inputSchema: mcpInputSchema(OpenThreadInputSchema),
49
+ },
50
+ async (input) => {
51
+ try {
52
+ if (!sessions.authorized(input.harnessSessionId, input.cwd, input.hookToken)) {
53
+ throw new Error(
54
+ "cueloop Codex session is not authorized; trust the plugin hooks and restart Codex",
55
+ );
56
+ }
57
+ const { hookToken: _hookToken, ...threadInput } = input;
58
+ const result = await runHarnessBridge(
59
+ { operation: "open", harness: "codex", ...threadInput },
60
+ home,
61
+ );
62
+
63
+ return { content: [{ type: "text", text: JSON.stringify(result) }] };
64
+ } catch (error) {
65
+ return { isError: true, content: [{ type: "text", text: String(error) }] };
66
+ }
67
+ },
68
+ );
69
+ server.registerTool(
70
+ "refine_corpus",
71
+ {
72
+ description: "Analyze past cueloop Threads before proposing a refine writeback.",
73
+ inputSchema: mcpInputSchema(RefineCorpusInputSchema),
74
+ },
75
+ async () => {
76
+ try {
77
+ const result = await runHarnessBridge({ operation: "refine" }, home);
78
+
79
+ return {
80
+ content: [{ type: "text", text: result.operation === "refine" ? result.report : "" }],
81
+ };
82
+ } catch (error) {
83
+ return { isError: true, content: [{ type: "text", text: String(error) }] };
84
+ }
85
+ },
86
+ );
87
+
88
+ return server;
89
+ }
90
+
91
+ /** Keep the MCP transport and durable Codex Message poller alive together. */
92
+ export async function codexMcpCommand(): Promise<number> {
93
+ const delivery = createCodexDeliveryService();
94
+
95
+ delivery.start();
96
+ const server = serveStdio(() => createCodexMcpServer(), {
97
+ onerror: (error) => console.error(`cueloop Codex MCP: ${String(error)}`),
98
+ });
99
+ await new Promise<void>((resolve) => process.stdin.once("end", resolve));
100
+ delivery.stop();
101
+ await server.close();
102
+
103
+ return 0;
104
+ }
@@ -0,0 +1,16 @@
1
+ import { runHarnessBridge } from "@cueloop/adapters/harness-bridge";
2
+
3
+ export async function harnessCommand(): Promise<number> {
4
+ try {
5
+ const request = JSON.parse(await new Response(Bun.stdin.stream()).text());
6
+ const response = await runHarnessBridge(request);
7
+
8
+ console.log(JSON.stringify(response));
9
+
10
+ return 0;
11
+ } catch (error) {
12
+ console.error(`cueloop harness bridge failed: ${String(error)}`);
13
+
14
+ return 1;
15
+ }
16
+ }
package/src/main.ts CHANGED
@@ -112,7 +112,9 @@ const commandHandlers: CommandHandlers = {
112
112
  prototype: (rest) => prototypeCommand(rest),
113
113
  serve: (rest) => serveEntry(rest),
114
114
  share: (rest) => shareEntry(rest),
115
- wake: async (rest) => (await import("./wake-command")).wakeCommand(rest),
115
+ harness: async () => (await import("./harness-command")).harnessCommand(),
116
+ "codex-hook": async () => (await import("./codex-hook-command")).codexHookCommand(),
117
+ mcp: async () => (await import("./codex-mcp-command")).codexMcpCommand(),
116
118
  actions: async (rest) => (await import("./actions-command")).actionsCommand(rest),
117
119
  refine: async (rest) => (await import("./refine-command")).refineCommand(rest),
118
120
  update: async (rest) => (await import("./update-command")).updateCommand(rest),
@@ -344,7 +346,6 @@ function printHelp(): void {
344
346
  " cueloop actions list list the quick-action vocabulary (for annotate --action)",
345
347
  " cueloop refine mine past reviews into a markdown report + writeback proposals",
346
348
  " cueloop update [--dry-run] update the installed cueloop binary (--dry-run reports the target only)",
347
- " cueloop wake <id> [--harness codex --thread <id>] resume the agent with the message (spawn detached)",
348
349
  " cueloop review-post <id> <pr> post a resolved session's message back to the PR",
349
350
  " cueloop daemon run the daemon in the foreground",
350
351
  " cueloop stop stop the local daemon",
package/src/pr.ts CHANGED
@@ -8,8 +8,8 @@
8
8
 
9
9
  import type { Thread } from "@cueloop/schema";
10
10
  import { join } from "node:path";
11
- import { GitHubForgeReviewPort } from "@cueloop/adapters/forge-review";
12
- import { DeliveredMessageStore } from "@cueloop/adapters/delivered-message-store";
11
+ import { createGitHubForgeReviewPort } from "@cueloop/adapters/forge-review";
12
+ import { createDeliveredMessageStore } from "@cueloop/adapters/delivered-message-store";
13
13
  import { DaemonClient } from "@cueloop/daemon/client";
14
14
  import { cueloopHome } from "@cueloop/daemon/paths";
15
15
  import { openReview } from "@cueloop/daemon/thread-review";
@@ -20,9 +20,9 @@ function ghBin(): string {
20
20
  return process.env.CUELOOP_GH || "gh";
21
21
  }
22
22
 
23
- function forge(): GitHubForgeReviewPort {
24
- return new GitHubForgeReviewPort(
25
- new DeliveredMessageStore(join(cueloopHome(), "forge-delivered-messages.json")),
23
+ function forge(): ReturnType<typeof createGitHubForgeReviewPort> {
24
+ return createGitHubForgeReviewPort(
25
+ createDeliveredMessageStore(join(cueloopHome(), "forge-delivered-messages.json")),
26
26
  ghBin(),
27
27
  );
28
28
  }
@@ -1,7 +1,10 @@
1
1
  /** CLI wrapper around the shared corpus analysis used by harness refine workflows. */
2
2
 
3
3
  import { cueloopHome } from "@cueloop/daemon/paths";
4
- import { LocalRefineCorpusPort } from "@cueloop/adapters/refine-corpus";
4
+ import {
5
+ createLocalRefineCorpusPort,
6
+ type RefineCorpusAnalysis,
7
+ } from "@cueloop/adapters/refine-corpus";
5
8
  import { DaemonClient } from "@cueloop/daemon/client";
6
9
  import { parseArgs, stringFlag } from "./args";
7
10
 
@@ -11,10 +14,10 @@ export async function refineCommand(argv: string[]): Promise<number> {
11
14
  const home = stringFlag(flags, "home") ?? cueloopHome();
12
15
  const rawLimit = stringFlag(flags, "limit");
13
16
  const client = await DaemonClient.connect({ home, autostart: true });
14
- let result: Awaited<ReturnType<LocalRefineCorpusPort["analyzeRefineCorpus"]>>;
17
+ let result: RefineCorpusAnalysis;
15
18
 
16
19
  try {
17
- const corpus = new LocalRefineCorpusPort(
20
+ const corpus = createLocalRefineCorpusPort(
18
21
  client,
19
22
  home,
20
23
  rawLimit === undefined ? undefined : Number(rawLimit),
@@ -7,13 +7,14 @@
7
7
  import {
8
8
  ARTIFACT_TYPES,
9
9
  isArtifactType,
10
+ manualThreadOpenCommand,
10
11
  newAnnotationId,
11
12
  type Anchor,
12
13
  type Annotation,
13
14
  } from "@cueloop/schema";
14
15
  import * as v from "valibot";
15
16
  import { DaemonClient } from "@cueloop/daemon/client";
16
- import { openHerdrPaneForReview } from "@cueloop/daemon/herdr-pane";
17
+ import { createTerminalThreadSurfacePort } from "@cueloop/adapters/terminal-thread-surface-port";
17
18
  import { openReview, messageResponse } from "@cueloop/daemon/thread-review";
18
19
  import { loadConfig, quickActionBody, resolveQuickAction } from "@cueloop/client/config";
19
20
  import { slashItemsFrom } from "@cueloop/client/slash-palette";
@@ -61,8 +62,16 @@ async function sessionCreate({ client, flags }: SessionContext): Promise<number>
61
62
  notes,
62
63
  });
63
64
 
64
- // herdr auto-open: render the review in a tab (no-op outside herdr).
65
- await openHerdrPaneForReview(review.session, client);
65
+ const panel = review.session.artifact.type === "diff" ? "changes" : "thread";
66
+ const openResult = await createTerminalThreadSurfacePort(client).openThreads(
67
+ review.id,
68
+ panel,
69
+ review.session,
70
+ );
71
+
72
+ if (openResult !== "opened" && openResult !== "focused") {
73
+ console.error(manualThreadOpenCommand(review.id));
74
+ }
66
75
  out(review.session);
67
76
 
68
77
  return 0;
@@ -1,14 +0,0 @@
1
- /** cueloop wake argument guards: a missing session id and a codex wake without a thread id both fail with a usage code before any daemon or harness work. */
2
-
3
- import { describe, expect, test } from "bun:test";
4
- import { wakeCommand } from "./wake-command";
5
-
6
- describe("wakeCommand argument guards", () => {
7
- test("a missing session id is a usage error", async () => {
8
- expect(await wakeCommand([])).toBe(2);
9
- });
10
-
11
- test("a codex wake without a thread id is a usage error", async () => {
12
- expect(await wakeCommand(["ses_1", "--harness", "codex"])).toBe(2);
13
- });
14
- });
@@ -1,44 +0,0 @@
1
- /**
2
- * `cueloop wake <session-id>` - the production caller for the non-blocking wake.
3
- * A detached child of a coding-agent session parks on the review's message and
4
- * injects it back into that same live session when the human decides, so the
5
- * agent resumes itself without a blocked, pinned tool call. The plan skill (and
6
- * the Claude Code hook's non-blocking path) spawn this right after a
7
- * non-blocking review: `cueloop wake <session-id> &`.
8
- *
9
- * Claude Code is the default and reads its inbox socket from the environment.
10
- * Codex needs its running thread id (`--harness codex --thread <thread-id>`).
11
- * pi wakes in-process from its extension and does not use this command.
12
- */
13
-
14
- import { parseArgs, stringFlag } from "./args";
15
-
16
- export async function wakeCommand(argv: string[]): Promise<number> {
17
- const { positional, flags } = parseArgs(argv);
18
- const sessionId = positional[0];
19
-
20
- if (sessionId === undefined) {
21
- console.error(
22
- "usage: cueloop wake <session-id> [--harness claude-code|codex] [--thread <codex-thread-id>]",
23
- );
24
-
25
- return 2;
26
- }
27
- const harness = stringFlag(flags, "harness") ?? "claude-code";
28
-
29
- if (harness === "codex") {
30
- const threadId = stringFlag(flags, "thread");
31
-
32
- if (threadId === undefined) {
33
- console.error("cueloop wake --harness codex needs --thread <codex-thread-id>");
34
-
35
- return 2;
36
- }
37
- const { runCodexWake } = await import("@cueloop/adapters/codex/wake");
38
-
39
- return (await runCodexWake(sessionId, threadId)) ? 0 : 1;
40
- }
41
- const { runInboxWake } = await import("@cueloop/adapters/claude-code/wake");
42
-
43
- return (await runInboxWake(sessionId)) ? 0 : 1;
44
- }