opencara 0.100.3 → 0.102.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +248 -6
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -177,16 +177,47 @@ var HelloAckSchema = z4.object({
|
|
|
177
177
|
});
|
|
178
178
|
var PingSchema = z4.object({ type: z4.literal("ping") });
|
|
179
179
|
var PongSchema = z4.object({ type: z4.literal("pong") });
|
|
180
|
+
var AgentCallEnvelope = {
|
|
181
|
+
type: z4.literal("agent-call"),
|
|
182
|
+
runId: z4.string(),
|
|
183
|
+
callId: z4.string()
|
|
184
|
+
};
|
|
185
|
+
var IssueBodySetCallSchema = z4.object({
|
|
186
|
+
...AgentCallEnvelope,
|
|
187
|
+
kind: z4.literal("issue.body.set"),
|
|
188
|
+
issueNumber: z4.number().int(),
|
|
189
|
+
bodyMd: z4.string()
|
|
190
|
+
});
|
|
191
|
+
var FlowNodeConfigSetCallSchema = z4.object({
|
|
192
|
+
...AgentCallEnvelope,
|
|
193
|
+
kind: z4.literal("flow.node.config.set"),
|
|
194
|
+
flowSlug: z4.string().min(1),
|
|
195
|
+
nodeId: z4.string().min(1),
|
|
196
|
+
config: z4.record(z4.string(), z4.unknown())
|
|
197
|
+
});
|
|
198
|
+
var TemplateNodeConfigSetCallSchema = z4.object({
|
|
199
|
+
...AgentCallEnvelope,
|
|
200
|
+
kind: z4.literal("template.node.config.set"),
|
|
201
|
+
templateSlug: z4.string().min(1),
|
|
202
|
+
nodeId: z4.string().min(1),
|
|
203
|
+
config: z4.record(z4.string(), z4.unknown())
|
|
204
|
+
});
|
|
205
|
+
var AgentCallSchema = z4.discriminatedUnion("kind", [
|
|
206
|
+
IssueBodySetCallSchema,
|
|
207
|
+
FlowNodeConfigSetCallSchema,
|
|
208
|
+
TemplateNodeConfigSetCallSchema
|
|
209
|
+
]);
|
|
180
210
|
var ServerToDeviceMessageSchema = z4.discriminatedUnion("type", [
|
|
181
211
|
JobAssignmentSchema,
|
|
182
212
|
HelloAckSchema,
|
|
183
213
|
PingSchema
|
|
184
214
|
]);
|
|
185
|
-
var DeviceToServerMessageSchema = z4.
|
|
215
|
+
var DeviceToServerMessageSchema = z4.union([
|
|
186
216
|
HelloMessageSchema,
|
|
187
217
|
LogFrameSchema,
|
|
188
218
|
RunDoneSchema,
|
|
189
|
-
PongSchema
|
|
219
|
+
PongSchema,
|
|
220
|
+
AgentCallSchema
|
|
190
221
|
]);
|
|
191
222
|
var HostRegisterRequestSchema = z4.object({
|
|
192
223
|
hostId: z4.string(),
|
|
@@ -199,6 +230,36 @@ var HostRegisterResponseSchema = z4.object({
|
|
|
199
230
|
pollIntervalMs: z4.number().int().positive()
|
|
200
231
|
});
|
|
201
232
|
|
|
233
|
+
// ../shared/dist/issues.js
|
|
234
|
+
import { z as z5 } from "zod";
|
|
235
|
+
var IssueLabelSchema = z5.object({
|
|
236
|
+
name: z5.string(),
|
|
237
|
+
color: z5.string()
|
|
238
|
+
});
|
|
239
|
+
var IssueAssigneeSchema = z5.object({
|
|
240
|
+
login: z5.string(),
|
|
241
|
+
id: z5.number()
|
|
242
|
+
});
|
|
243
|
+
var IssueSummarySchema = z5.object({
|
|
244
|
+
id: z5.string(),
|
|
245
|
+
number: z5.number().int(),
|
|
246
|
+
title: z5.string(),
|
|
247
|
+
state: z5.string(),
|
|
248
|
+
stateReason: z5.string().nullable(),
|
|
249
|
+
labels: z5.array(IssueLabelSchema),
|
|
250
|
+
assignees: z5.array(IssueAssigneeSchema),
|
|
251
|
+
authorLogin: z5.string().nullable(),
|
|
252
|
+
htmlUrl: z5.string(),
|
|
253
|
+
createdAt: z5.string().datetime(),
|
|
254
|
+
updatedAt: z5.string().datetime(),
|
|
255
|
+
closedAt: z5.string().datetime().nullable()
|
|
256
|
+
});
|
|
257
|
+
var IssueDetailSchema = IssueSummarySchema.extend({
|
|
258
|
+
bodyMd: z5.string().nullable(),
|
|
259
|
+
draftBodyMd: z5.string().nullable(),
|
|
260
|
+
draftUpdatedAt: z5.string().datetime().nullable()
|
|
261
|
+
});
|
|
262
|
+
|
|
202
263
|
// src/commands/register.ts
|
|
203
264
|
var POLL_INTERVAL_MS = 2e3;
|
|
204
265
|
async function register(opts = {}) {
|
|
@@ -366,8 +427,72 @@ function runJob(spec, stdinJson, handlers) {
|
|
|
366
427
|
});
|
|
367
428
|
}
|
|
368
429
|
|
|
430
|
+
// src/runner/agentCallParser.ts
|
|
431
|
+
import { z as z6 } from "zod";
|
|
432
|
+
var MAX_BUFFER_BYTES = 64 * 1024;
|
|
433
|
+
var FENCE_RE = /```opencara-call\r?\n([\s\S]*?)\r?\n```/;
|
|
434
|
+
var VARIANT_SCHEMAS = {
|
|
435
|
+
"issue.body.set": IssueBodySetCallSchema.omit({ type: true, runId: true }),
|
|
436
|
+
"flow.node.config.set": FlowNodeConfigSetCallSchema.omit({
|
|
437
|
+
type: true,
|
|
438
|
+
runId: true
|
|
439
|
+
}),
|
|
440
|
+
"template.node.config.set": TemplateNodeConfigSetCallSchema.omit({
|
|
441
|
+
type: true,
|
|
442
|
+
runId: true
|
|
443
|
+
})
|
|
444
|
+
};
|
|
445
|
+
var KindSchema = z6.enum([
|
|
446
|
+
"issue.body.set",
|
|
447
|
+
"flow.node.config.set",
|
|
448
|
+
"template.node.config.set"
|
|
449
|
+
]);
|
|
450
|
+
var AgentCallParser = class {
|
|
451
|
+
constructor(emit) {
|
|
452
|
+
this.emit = emit;
|
|
453
|
+
}
|
|
454
|
+
emit;
|
|
455
|
+
buffer = "";
|
|
456
|
+
feed(chunk) {
|
|
457
|
+
this.buffer += chunk;
|
|
458
|
+
if (this.buffer.length > MAX_BUFFER_BYTES) {
|
|
459
|
+
const cutoff = Math.floor(MAX_BUFFER_BYTES / 2);
|
|
460
|
+
const nl = this.buffer.indexOf("\n", this.buffer.length - cutoff);
|
|
461
|
+
this.buffer = nl >= 0 ? this.buffer.slice(nl + 1) : this.buffer.slice(-cutoff);
|
|
462
|
+
}
|
|
463
|
+
while (true) {
|
|
464
|
+
const m = FENCE_RE.exec(this.buffer);
|
|
465
|
+
if (!m) break;
|
|
466
|
+
const inner = m[1] ?? "";
|
|
467
|
+
const consumedTo = m.index + m[0].length;
|
|
468
|
+
this.buffer = this.buffer.slice(consumedTo);
|
|
469
|
+
this.tryEmit(inner);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
tryEmit(inner) {
|
|
473
|
+
let parsed;
|
|
474
|
+
try {
|
|
475
|
+
parsed = JSON.parse(inner);
|
|
476
|
+
} catch {
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
if (!parsed || typeof parsed !== "object") return;
|
|
480
|
+
const raw = parsed;
|
|
481
|
+
const kindResult = KindSchema.safeParse(raw.kind);
|
|
482
|
+
if (!kindResult.success) return;
|
|
483
|
+
const withCallId = {
|
|
484
|
+
...raw,
|
|
485
|
+
callId: typeof raw.callId === "string" && raw.callId.length > 0 ? raw.callId : `call_${Date.now().toString(36)}`
|
|
486
|
+
};
|
|
487
|
+
const schema = VARIANT_SCHEMAS[kindResult.data];
|
|
488
|
+
const result = schema.safeParse(withCallId);
|
|
489
|
+
if (!result.success) return;
|
|
490
|
+
this.emit(result.data);
|
|
491
|
+
}
|
|
492
|
+
};
|
|
493
|
+
|
|
369
494
|
// src/commands/run.ts
|
|
370
|
-
var PKG_VERSION = "0.
|
|
495
|
+
var PKG_VERSION = "0.102.0";
|
|
371
496
|
var LOG_FLUSH_MS = 800;
|
|
372
497
|
var MAX_CHUNK_SIZE = 4 * 1024;
|
|
373
498
|
async function run(opts = {}) {
|
|
@@ -387,7 +512,12 @@ async function run(opts = {}) {
|
|
|
387
512
|
type: "hello",
|
|
388
513
|
platform: platform(),
|
|
389
514
|
version: PKG_VERSION,
|
|
390
|
-
|
|
515
|
+
// Advertise the new opencara-call stdout protocol so the
|
|
516
|
+
// server can later gate the skill prompt to capable CLIs.
|
|
517
|
+
// Older CLIs without this capability would still be sent the
|
|
518
|
+
// skill markdown today (it doesn't crash; the fenced block
|
|
519
|
+
// just shows up in stdout unparsed).
|
|
520
|
+
capabilities: ["agent-call"],
|
|
391
521
|
systemInfo: collectSystemInfo()
|
|
392
522
|
});
|
|
393
523
|
},
|
|
@@ -433,11 +563,29 @@ async function executeJob(job, client) {
|
|
|
433
563
|
if (flushTimer) return;
|
|
434
564
|
flushTimer = setTimeout(flush, LOG_FLUSH_MS);
|
|
435
565
|
};
|
|
566
|
+
const callParser = new AgentCallParser((call) => {
|
|
567
|
+
switch (call.kind) {
|
|
568
|
+
case "issue.body.set":
|
|
569
|
+
client.send({ type: "agent-call", runId, ...call });
|
|
570
|
+
return;
|
|
571
|
+
case "flow.node.config.set":
|
|
572
|
+
client.send({ type: "agent-call", runId, ...call });
|
|
573
|
+
return;
|
|
574
|
+
case "template.node.config.set":
|
|
575
|
+
client.send({ type: "agent-call", runId, ...call });
|
|
576
|
+
return;
|
|
577
|
+
default: {
|
|
578
|
+
const exhaustive = call;
|
|
579
|
+
void exhaustive;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
});
|
|
436
583
|
try {
|
|
437
584
|
const result = await runJob(job.spec, job.stdinJson, {
|
|
438
585
|
onLog: (stream, chunk) => {
|
|
439
586
|
pending[stream] += chunk;
|
|
440
587
|
scheduleFlush();
|
|
588
|
+
if (stream === "stdout") callParser.feed(chunk);
|
|
441
589
|
}
|
|
442
590
|
});
|
|
443
591
|
flush();
|
|
@@ -540,6 +688,97 @@ async function logout() {
|
|
|
540
688
|
console.log("Removed local credentials.");
|
|
541
689
|
}
|
|
542
690
|
|
|
691
|
+
// src/commands/internal.ts
|
|
692
|
+
import { execFileSync } from "node:child_process";
|
|
693
|
+
import { mkdtempSync, rmSync, existsSync as existsSync2, realpathSync } from "node:fs";
|
|
694
|
+
import { tmpdir } from "node:os";
|
|
695
|
+
import { join as join2, sep } from "node:path";
|
|
696
|
+
async function internal(argv) {
|
|
697
|
+
const sub = argv[0];
|
|
698
|
+
const rest = argv.slice(1);
|
|
699
|
+
if (sub === "worktree") {
|
|
700
|
+
const op = rest[0];
|
|
701
|
+
const opArgs = rest.slice(1);
|
|
702
|
+
if (op === "create") return worktreeCreate(opArgs);
|
|
703
|
+
if (op === "remove") return worktreeRemove(opArgs);
|
|
704
|
+
fail(`unknown worktree op: ${op ?? "(none)"}`);
|
|
705
|
+
}
|
|
706
|
+
fail(`unknown internal subcommand: ${sub ?? "(none)"}`);
|
|
707
|
+
}
|
|
708
|
+
function worktreeCreate(args) {
|
|
709
|
+
const repo = pickFlag(args, "--repo");
|
|
710
|
+
const branch = pickFlag(args, "--branch");
|
|
711
|
+
const fromRaw = pickFlag(args, "--from-branch") ?? "";
|
|
712
|
+
const fromBranch = fromRaw.length > 0 ? fromRaw : null;
|
|
713
|
+
if (!repo || !branch) {
|
|
714
|
+
fail("worktree create requires --repo OWNER/NAME and --branch <name>");
|
|
715
|
+
}
|
|
716
|
+
if (!/^[\w.-]+\/[\w.-]+$/.test(repo)) {
|
|
717
|
+
fail(`invalid --repo '${repo}' (expected OWNER/NAME)`);
|
|
718
|
+
}
|
|
719
|
+
const token = process.env["GH_TOKEN"];
|
|
720
|
+
if (!token) {
|
|
721
|
+
fail("worktree create needs GH_TOKEN in env (the orchestrator injects this per run)");
|
|
722
|
+
}
|
|
723
|
+
if (!/^[\w-]+$/.test(token)) {
|
|
724
|
+
fail("GH_TOKEN contains unexpected characters; refusing to use");
|
|
725
|
+
}
|
|
726
|
+
const dir = mkdtempSync(join2(tmpdir(), "opencara-wt-"));
|
|
727
|
+
const HELPER_SNIPPET = '!f() { echo username=x-access-token; echo "password=$GH_TOKEN"; }; f';
|
|
728
|
+
const cleanUrl = `https://github.com/${repo}.git`;
|
|
729
|
+
const cloneArgs = ["-c", `credential.helper=${HELPER_SNIPPET}`, "clone", "--depth=1"];
|
|
730
|
+
if (fromBranch) {
|
|
731
|
+
cloneArgs.push("--branch", fromBranch);
|
|
732
|
+
}
|
|
733
|
+
cloneArgs.push(cleanUrl, ".");
|
|
734
|
+
try {
|
|
735
|
+
git(dir, cloneArgs);
|
|
736
|
+
git(dir, ["checkout", "-b", branch]);
|
|
737
|
+
git(dir, ["config", "credential.helper", HELPER_SNIPPET]);
|
|
738
|
+
} catch (err) {
|
|
739
|
+
try {
|
|
740
|
+
rmSync(dir, { recursive: true, force: true });
|
|
741
|
+
} catch {
|
|
742
|
+
}
|
|
743
|
+
throw err;
|
|
744
|
+
}
|
|
745
|
+
process.stdout.write(`${JSON.stringify({ workdir: dir, branch })}
|
|
746
|
+
`);
|
|
747
|
+
}
|
|
748
|
+
function worktreeRemove(args) {
|
|
749
|
+
const workdir = pickFlag(args, "--workdir");
|
|
750
|
+
if (!workdir) {
|
|
751
|
+
fail("worktree remove requires --workdir <path>");
|
|
752
|
+
}
|
|
753
|
+
if (!existsSync2(workdir)) {
|
|
754
|
+
return;
|
|
755
|
+
}
|
|
756
|
+
const tmp = realpathSync(tmpdir());
|
|
757
|
+
let resolved;
|
|
758
|
+
try {
|
|
759
|
+
resolved = realpathSync(workdir);
|
|
760
|
+
} catch (err) {
|
|
761
|
+
if (err.code === "ENOENT") return;
|
|
762
|
+
fail(`worktree remove: cannot resolve ${workdir}: ${err.message}`);
|
|
763
|
+
}
|
|
764
|
+
if (resolved !== tmp && !resolved.startsWith(tmp + sep)) {
|
|
765
|
+
fail(`worktree remove: refuses to remove ${resolved} (not under ${tmp})`);
|
|
766
|
+
}
|
|
767
|
+
rmSync(resolved, { recursive: true, force: true });
|
|
768
|
+
}
|
|
769
|
+
function git(cwd, args) {
|
|
770
|
+
execFileSync("git", args, { cwd, stdio: ["ignore", "ignore", "inherit"] });
|
|
771
|
+
}
|
|
772
|
+
function pickFlag(argv, name) {
|
|
773
|
+
const i = argv.indexOf(name);
|
|
774
|
+
if (i === -1) return void 0;
|
|
775
|
+
return argv[i + 1];
|
|
776
|
+
}
|
|
777
|
+
function fail(msg) {
|
|
778
|
+
console.error(msg);
|
|
779
|
+
process.exit(1);
|
|
780
|
+
}
|
|
781
|
+
|
|
543
782
|
// src/bin.ts
|
|
544
783
|
async function main() {
|
|
545
784
|
const argv = process.argv.slice(2);
|
|
@@ -553,6 +792,9 @@ async function main() {
|
|
|
553
792
|
case "logout":
|
|
554
793
|
await logout();
|
|
555
794
|
return;
|
|
795
|
+
case "internal":
|
|
796
|
+
await internal(argv.slice(1));
|
|
797
|
+
return;
|
|
556
798
|
case "--help":
|
|
557
799
|
case "-h":
|
|
558
800
|
printHelp();
|
|
@@ -566,10 +808,10 @@ async function main() {
|
|
|
566
808
|
}
|
|
567
809
|
await run({
|
|
568
810
|
forcePair: argv.includes("--force-pair"),
|
|
569
|
-
url:
|
|
811
|
+
url: pickFlag2(argv, "--url")
|
|
570
812
|
});
|
|
571
813
|
}
|
|
572
|
-
function
|
|
814
|
+
function pickFlag2(argv, name) {
|
|
573
815
|
const i = argv.indexOf(name);
|
|
574
816
|
if (i === -1) return void 0;
|
|
575
817
|
return argv[i + 1];
|