cueloop 0.1.0-alpha.80 → 0.1.0-alpha.82
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 +8 -6
- package/src/codex-hook-command.ts +21 -0
- package/src/codex-mcp-command.ts +104 -0
- package/src/harness-command.ts +16 -0
- package/src/main.ts +3 -2
- package/src/pr.ts +5 -5
- package/src/refine-command.ts +6 -3
- package/src/thread-commands.ts +12 -3
- package/src/wake-command.test.ts +0 -14
- package/src/wake-command.ts +0 -44
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cueloop",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.82",
|
|
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.
|
|
34
|
-
"@cueloop/client": "0.1.0-alpha.
|
|
35
|
-
"@cueloop/daemon": "0.1.0-alpha.
|
|
36
|
-
"@cueloop/schema": "0.1.0-alpha.
|
|
33
|
+
"@cueloop/adapters": "0.1.0-alpha.82",
|
|
34
|
+
"@cueloop/client": "0.1.0-alpha.82",
|
|
35
|
+
"@cueloop/daemon": "0.1.0-alpha.82",
|
|
36
|
+
"@cueloop/schema": "0.1.0-alpha.82",
|
|
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
|
-
|
|
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 {
|
|
12
|
-
import {
|
|
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():
|
|
24
|
-
return
|
|
25
|
-
|
|
23
|
+
function forge(): ReturnType<typeof createGitHubForgeReviewPort> {
|
|
24
|
+
return createGitHubForgeReviewPort(
|
|
25
|
+
createDeliveredMessageStore(join(cueloopHome(), "forge-delivered-messages.json")),
|
|
26
26
|
ghBin(),
|
|
27
27
|
);
|
|
28
28
|
}
|
package/src/refine-command.ts
CHANGED
|
@@ -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 {
|
|
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:
|
|
17
|
+
let result: RefineCorpusAnalysis;
|
|
15
18
|
|
|
16
19
|
try {
|
|
17
|
-
const corpus =
|
|
20
|
+
const corpus = createLocalRefineCorpusPort(
|
|
18
21
|
client,
|
|
19
22
|
home,
|
|
20
23
|
rawLimit === undefined ? undefined : Number(rawLimit),
|
package/src/thread-commands.ts
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
65
|
-
await
|
|
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;
|
package/src/wake-command.test.ts
DELETED
|
@@ -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
|
-
});
|
package/src/wake-command.ts
DELETED
|
@@ -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
|
-
}
|