@starlove/orbit 1.3.4 → 1.4.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/Dockerfile +2 -1
- package/README.md +10 -9
- package/SKILL-ORBIT.md +41 -17
- package/dist/full/fonts/Chakra_Petch/ChakraPetch-Bold.ttf +0 -0
- package/dist/full/fonts/Chakra_Petch/ChakraPetch-BoldItalic.ttf +0 -0
- package/dist/full/fonts/Chakra_Petch/ChakraPetch-Italic.ttf +0 -0
- package/dist/full/fonts/Chakra_Petch/ChakraPetch-Light.ttf +0 -0
- package/dist/full/fonts/Chakra_Petch/ChakraPetch-LightItalic.ttf +0 -0
- package/dist/full/fonts/Chakra_Petch/ChakraPetch-Medium.ttf +0 -0
- package/dist/full/fonts/Chakra_Petch/ChakraPetch-MediumItalic.ttf +0 -0
- package/dist/full/fonts/Chakra_Petch/ChakraPetch-Regular.ttf +0 -0
- package/dist/full/fonts/Chakra_Petch/ChakraPetch-SemiBold.ttf +0 -0
- package/dist/full/fonts/Chakra_Petch/ChakraPetch-SemiBoldItalic.ttf +0 -0
- package/dist/full/fonts/Chakra_Petch/OFL.txt +93 -0
- package/dist/full/fonts/Inter/Inter-Italic-VariableFont_opsz,wght.ttf +0 -0
- package/dist/full/fonts/Inter/Inter-VariableFont_opsz,wght.ttf +0 -0
- package/dist/full/fonts/Inter/OFL.txt +93 -0
- package/dist/full/fonts/Inter/README.txt +118 -0
- package/dist/full/fonts/JetBrains_Mono/JetBrainsMono-Italic-VariableFont_wght.ttf +0 -0
- package/dist/full/fonts/JetBrains_Mono/JetBrainsMono-VariableFont_wght.ttf +0 -0
- package/dist/full/fonts/JetBrains_Mono/OFL.txt +93 -0
- package/dist/full/fonts/JetBrains_Mono/README.txt +79 -0
- package/dist/full/fonts/OpenRing-Regular.ttf +0 -0
- package/dist/full/fonts/Open_Sans/OFL.txt +93 -0
- package/dist/full/fonts/Open_Sans/OpenSans-Italic-VariableFont_wdth,wght.ttf +0 -0
- package/dist/full/fonts/Open_Sans/OpenSans-VariableFont_wdth,wght.ttf +0 -0
- package/dist/full/fonts/Open_Sans/README.txt +100 -0
- package/dist/full/fonts/Orbitron/OFL.txt +93 -0
- package/dist/full/fonts/Orbitron/Orbitron-VariableFont_wght.ttf +0 -0
- package/dist/full/fonts/Orbitron/README.txt +68 -0
- package/dist/full/fonts/Rajdhani/OFL.txt +93 -0
- package/dist/full/fonts/Rajdhani/Rajdhani-Bold.ttf +0 -0
- package/dist/full/fonts/Rajdhani/Rajdhani-Light.ttf +0 -0
- package/dist/full/fonts/Rajdhani/Rajdhani-Medium.ttf +0 -0
- package/dist/full/fonts/Rajdhani/Rajdhani-Regular.ttf +0 -0
- package/dist/full/fonts/Rajdhani/Rajdhani-SemiBold.ttf +0 -0
- package/dist/full/fonts/runescape-uf.ttf +0 -0
- package/dist/full/index.html +137 -27
- package/dist/full/js/app.62905fcb.js +952 -0
- package/dist/full/js/app.62905fcb.js.map +7 -0
- package/dist/full/styles.4e60d824.css +1 -0
- package/dist/full/version.json +2 -2
- package/docs/AGENT_PROTOCOL.md +20 -13
- package/docs/ORBIT_DISPATCH.md +73 -38
- package/docs/UPDATING.md +114 -0
- package/package.json +5 -7
- package/public/fonts/Chakra_Petch/ChakraPetch-Bold.ttf +0 -0
- package/public/fonts/Chakra_Petch/ChakraPetch-BoldItalic.ttf +0 -0
- package/public/fonts/Chakra_Petch/ChakraPetch-Italic.ttf +0 -0
- package/public/fonts/Chakra_Petch/ChakraPetch-Light.ttf +0 -0
- package/public/fonts/Chakra_Petch/ChakraPetch-LightItalic.ttf +0 -0
- package/public/fonts/Chakra_Petch/ChakraPetch-Medium.ttf +0 -0
- package/public/fonts/Chakra_Petch/ChakraPetch-MediumItalic.ttf +0 -0
- package/public/fonts/Chakra_Petch/ChakraPetch-Regular.ttf +0 -0
- package/public/fonts/Chakra_Petch/ChakraPetch-SemiBold.ttf +0 -0
- package/public/fonts/Chakra_Petch/ChakraPetch-SemiBoldItalic.ttf +0 -0
- package/public/fonts/Chakra_Petch/OFL.txt +93 -0
- package/public/fonts/Inter/Inter-Italic-VariableFont_opsz,wght.ttf +0 -0
- package/public/fonts/Inter/Inter-VariableFont_opsz,wght.ttf +0 -0
- package/public/fonts/Inter/OFL.txt +93 -0
- package/public/fonts/Inter/README.txt +118 -0
- package/public/fonts/JetBrains_Mono/JetBrainsMono-Italic-VariableFont_wght.ttf +0 -0
- package/public/fonts/JetBrains_Mono/JetBrainsMono-VariableFont_wght.ttf +0 -0
- package/public/fonts/JetBrains_Mono/OFL.txt +93 -0
- package/public/fonts/JetBrains_Mono/README.txt +79 -0
- package/public/fonts/OpenRing-Regular.ttf +0 -0
- package/public/fonts/Open_Sans/OFL.txt +93 -0
- package/public/fonts/Open_Sans/OpenSans-Italic-VariableFont_wdth,wght.ttf +0 -0
- package/public/fonts/Open_Sans/OpenSans-VariableFont_wdth,wght.ttf +0 -0
- package/public/fonts/Open_Sans/README.txt +100 -0
- package/public/fonts/Orbitron/OFL.txt +93 -0
- package/public/fonts/Orbitron/Orbitron-VariableFont_wght.ttf +0 -0
- package/public/fonts/Orbitron/README.txt +68 -0
- package/public/fonts/Rajdhani/OFL.txt +93 -0
- package/public/fonts/Rajdhani/Rajdhani-Bold.ttf +0 -0
- package/public/fonts/Rajdhani/Rajdhani-Light.ttf +0 -0
- package/public/fonts/Rajdhani/Rajdhani-Medium.ttf +0 -0
- package/public/fonts/Rajdhani/Rajdhani-Regular.ttf +0 -0
- package/public/fonts/Rajdhani/Rajdhani-SemiBold.ttf +0 -0
- package/public/fonts/runescape-uf.ttf +0 -0
- package/public/index.html +135 -25
- package/public/js/artifact-drawer.js +79 -0
- package/public/js/board-export.js +161 -0
- package/public/js/board-menu.js +219 -17
- package/public/js/card-actions.js +1 -1
- package/public/js/create-card.js +60 -23
- package/public/js/drawer.js +164 -4
- package/public/js/font-preference.js +229 -0
- package/public/js/format.js +0 -10
- package/public/js/icons.js +16 -0
- package/public/js/kanban.js +123 -1
- package/public/js/local-backend.js +31 -4
- package/public/js/main.js +7 -1
- package/public/js/modal.js +3 -1
- package/public/js/panel.js +58 -0
- package/public/js/settings-tabs.js +1 -1
- package/public/js/settings.js +606 -275
- package/public/js/sse.js +3 -0
- package/public/js/state.js +1 -1
- package/public/js/text-editing.js +128 -0
- package/public/js/ticket-detail.js +377 -148
- package/public/js/ticket-typeahead.js +49 -0
- package/public/js/toast.js +19 -4
- package/public/js/topbar-notes.js +3 -3
- package/public/js/typeahead.js +206 -0
- package/public/js/update-check.js +58 -0
- package/public/styles.css +5455 -3538
- package/src/cli/orbit.js +9 -5
- package/src/core/agent.js +12 -4
- package/src/core/agents-md.js +3 -3
- package/src/core/artifacts.js +149 -0
- package/src/core/board.js +3 -2
- package/src/core/boards.js +0 -1
- package/src/core/css-bundle.js +1 -0
- package/src/core/db.js +6 -0
- package/src/core/dispatch.js +344 -34
- package/src/core/http.js +9 -2
- package/src/core/paths.js +26 -2
- package/src/core/router.js +33 -0
- package/src/core/search.js +3 -2
- package/src/core/snapshots.js +10 -5
- package/src/core/tickets.js +34 -3
- package/src/core/util.js +3 -2
- package/src/core/version-check.js +154 -0
- package/src/mcp/orbit-client.js +18 -2
- package/src/mcp-server.js +19 -14
- package/src/server.js +28 -3
- package/dist/full/js/app.fb7767dc.js +0 -742
- package/dist/full/js/app.fb7767dc.js.map +0 -7
- package/dist/full/styles.bbc542f5.css +0 -1
package/src/core/dispatch.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { spawn, spawnSync } from "node:child_process";
|
|
2
2
|
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
-
import { extname, join, resolve } from "node:path";
|
|
3
|
+
import { basename, dirname, extname, join, resolve } from "node:path";
|
|
4
4
|
import { localOwnerActor } from "./auth.js";
|
|
5
5
|
import { DISPATCH_RUNS_DIR } from "./paths.js";
|
|
6
6
|
import { getContextPackFull } from "./agent.js";
|
|
@@ -52,14 +52,34 @@ function resolveTicket(db, boardId, ticketRef) {
|
|
|
52
52
|
return ticket;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
function
|
|
56
|
-
if (!
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
55
|
+
function parseTicketUrl(value) {
|
|
56
|
+
if (!value) return null;
|
|
57
|
+
let url;
|
|
58
|
+
try {
|
|
59
|
+
url = new URL(String(value));
|
|
60
|
+
} catch {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
const hash = String(url.hash || "").replace(/^#/, "");
|
|
64
|
+
const match = hash.match(/(?:^|\/)b\/([^/]+)\/t\/([^/?#]+)/);
|
|
65
|
+
if (!match) return null;
|
|
66
|
+
url.hash = "";
|
|
67
|
+
url.search = "";
|
|
68
|
+
url.pathname = url.pathname.replace(/\/+$/, "");
|
|
69
|
+
return {
|
|
70
|
+
serverUrl: url.toString().replace(/\/+$/, ""),
|
|
71
|
+
board: decodeURIComponent(match[1]),
|
|
72
|
+
ticket: decodeURIComponent(match[2])
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function remoteTarget(options) {
|
|
77
|
+
const parsed = parseTicketUrl(options.ticketUrl || options.ticket);
|
|
78
|
+
return {
|
|
79
|
+
serverUrl: options.serverUrl || parsed?.serverUrl || process.env.ORBIT_SERVER_URL || process.env.ORBIT_API_URL || "",
|
|
80
|
+
board: options.board || parsed?.board || process.env.ORBIT_DEFAULT_BOARD || "",
|
|
81
|
+
ticket: parsed?.ticket || options.ticket || ""
|
|
82
|
+
};
|
|
63
83
|
}
|
|
64
84
|
|
|
65
85
|
function resolveExecutable(command) {
|
|
@@ -210,6 +230,7 @@ function buildRunRecordJson({
|
|
|
210
230
|
commit_shas: [],
|
|
211
231
|
residual_risks: [],
|
|
212
232
|
server_url: sanitizeUrlBearingValue(serverUrl),
|
|
233
|
+
transport: options.remote || options.serverUrl || options.ticketUrl ? "remote" : "local",
|
|
213
234
|
mode: options.noSpawn ? "prepare-only" : "spawn",
|
|
214
235
|
command: `${options.hermesBin || "hermes"} ${hermesArgs.map((arg) => (arg.includes(" ") ? q(arg) : arg)).join(" ")}`,
|
|
215
236
|
created_at: new Date().toISOString(),
|
|
@@ -377,6 +398,10 @@ function buildHandoff({ boardRow, context, profile, repoRoot, worktreePath, bran
|
|
|
377
398
|
const childText = children.length
|
|
378
399
|
? children.map((child) => `- #${child.number} ${child.title} (${child.state_name})`).join("\n")
|
|
379
400
|
: "None.";
|
|
401
|
+
const recentComments = (context.recent_comments || [])
|
|
402
|
+
.slice(-5)
|
|
403
|
+
.map((comment) => `- ${comment.author || "unknown"} (${comment.kind || "comment"}): ${comment.body || ""}`)
|
|
404
|
+
.join("\n");
|
|
380
405
|
const verificationText = verificationCommands.map((command) => `- ${command}`).join("\n");
|
|
381
406
|
|
|
382
407
|
return `# Orbit Agent Handoff
|
|
@@ -400,29 +425,30 @@ Implement the ticket faithfully. Treat this ticket as the source of truth for co
|
|
|
400
425
|
## Read first
|
|
401
426
|
1. AGENTS.md in the repository.
|
|
402
427
|
2. SKILL-ORBIT.md in the repository.
|
|
403
|
-
3. This
|
|
404
|
-
4. The ticket description,
|
|
428
|
+
3. This linked markdown handoff.
|
|
429
|
+
4. The ticket description, terse visible AI Plan / Work Summary, Revisions, blockers, parent/child cards, and board Journal entries below.
|
|
405
430
|
|
|
406
431
|
## Ticket description
|
|
407
432
|
${ticket.description || "(No description.)"}
|
|
408
433
|
|
|
409
|
-
##
|
|
410
|
-
AI
|
|
434
|
+
## Human-visible card fields
|
|
435
|
+
AI Plan: ${ticket.ai_plan || "(Empty.)"}
|
|
436
|
+
|
|
437
|
+
Linked plan artifact: ${ticket.plan_artifact_path || "(None.)"}
|
|
411
438
|
|
|
412
|
-
|
|
439
|
+
Work Summary: ${ticket.implementation_summary || "(Empty.)"}
|
|
413
440
|
|
|
414
|
-
|
|
441
|
+
Linked implementation artifact: ${ticket.implementation_artifact_path || "(None.)"}
|
|
442
|
+
|
|
443
|
+
Revisions: ${ticket.implementation_updates || "(Empty.)"}
|
|
415
444
|
|
|
416
445
|
## Board agent instructions
|
|
417
446
|
${board.agent_instructions || "(No board-level agent instructions.)"}
|
|
418
447
|
|
|
419
|
-
## Board notes
|
|
420
|
-
${board.project_notes || "(No board notes.)"}
|
|
421
|
-
|
|
422
448
|
## Board journal: lessons and decisions
|
|
423
|
-
Board
|
|
449
|
+
Board Journal entries are the most important code, pattern, project, and workflow context loaded for every agent working with this board. Treat decisions and lessons as project constraints, not persona or roleplay instructions.
|
|
424
450
|
|
|
425
|
-
${boardEntries || "(No active board
|
|
451
|
+
${boardEntries || "(No active board Journal entries.)"}
|
|
426
452
|
|
|
427
453
|
## Parent ticket
|
|
428
454
|
${parent ? `#${parent.number} ${parent.title} (${parent.state_name})` : "None."}
|
|
@@ -433,6 +459,7 @@ ${childText}
|
|
|
433
459
|
## Unresolved blockers
|
|
434
460
|
${blockerText}
|
|
435
461
|
|
|
462
|
+
${recentComments ? `## Recent ticket comments\n${recentComments}\n` : ""}
|
|
436
463
|
## Scope boundaries
|
|
437
464
|
- Keep changes local to this repository/worktree.
|
|
438
465
|
- Do not push, deploy, publish, or open/merge PRs.
|
|
@@ -445,7 +472,7 @@ Allowed without asking: read/search files, inspect git status/diff/log, edit fil
|
|
|
445
472
|
Blocked or requires explicit human approval: Docker, git push, deploy/publish/release commands, destructive git operations (reset/clean/rebase/force checkout/worktree removal), package install/publish/version commands, recursive/force filesystem deletion, SSH/SCP/rsync, cloud CLIs, GitHub CLI, direct curl/wget network calls, sudo, and secret exfiltration.
|
|
446
473
|
|
|
447
474
|
## Declared verification commands
|
|
448
|
-
Run the commands that apply before handoff. If a conditional command does not apply, say why in
|
|
475
|
+
Run the commands that apply before handoff. If a conditional command does not apply, say why in the completion comment or Work Summary.
|
|
449
476
|
|
|
450
477
|
${verificationText}
|
|
451
478
|
|
|
@@ -460,10 +487,11 @@ ${verificationText}
|
|
|
460
487
|
When done:
|
|
461
488
|
1. Run the relevant automated tests and any focused manual/API sanity checks.
|
|
462
489
|
2. Commit the work locally on the dispatch branch.
|
|
463
|
-
3.
|
|
464
|
-
4.
|
|
465
|
-
5.
|
|
466
|
-
6.
|
|
490
|
+
3. Put a terse, human-readable result summary in Work Summary (implementation_summary).
|
|
491
|
+
4. If full detail matters, write a markdown report artifact and link it with implementation_artifact_path.
|
|
492
|
+
5. Leave Revisions alone unless this run revises earlier implementation; promote important code/pattern/project lessons and decisions to board Journal entries.
|
|
493
|
+
6. Add a compact completion comment/run receipt on the ticket.
|
|
494
|
+
7. Move the ticket to Review, not Done, unless the human operator explicitly requested auto-completion.
|
|
467
495
|
`;
|
|
468
496
|
}
|
|
469
497
|
|
|
@@ -472,10 +500,19 @@ function buildShortPrompt({ boardRow, ticket, profile, serverUrl }) {
|
|
|
472
500
|
`You are ${profile}, dispatched by Orbit.`,
|
|
473
501
|
`Work board ${boardRow.slug}, ticket #${ticket.number}: ${ticket.title}.`,
|
|
474
502
|
"Read AGENTS.md and SKILL-ORBIT.md in this repository first.",
|
|
475
|
-
"The full generated handoff is
|
|
503
|
+
"The full generated handoff is linked from the ticket as a markdown artifact; treat the Orbit ticket plus linked artifact as source of truth.",
|
|
476
504
|
serverUrl ? `Orbit server URL: ${serverUrl}` : "Use local Orbit/MCP/API access available from this repo.",
|
|
477
505
|
"Follow the autonomous policy in the handoff. Do not push, deploy, publish, run Docker, or expose secrets.",
|
|
478
|
-
"When complete, commit locally, update
|
|
506
|
+
"When complete, commit locally, update a terse Work Summary plus any linked markdown report on the ticket, use Revisions only for post-initial corrections, comment the run receipt, and move the ticket to Review."
|
|
507
|
+
].join("\n");
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
function buildPlanExcerpt({ ticket, profile, handoffPath }) {
|
|
511
|
+
return [
|
|
512
|
+
`- ${profile} dispatched to work #${ticket.number}: ${ticket.title}.`,
|
|
513
|
+
`- Full generated handoff is linked as markdown: ${handoffPath}`,
|
|
514
|
+
"- Follow blockers, scope boundaries, autonomous policy, and declared verification in that artifact.",
|
|
515
|
+
"- Keep AI Plan and Work Summary terse; put full detail in linked markdown/report artifacts."
|
|
479
516
|
].join("\n");
|
|
480
517
|
}
|
|
481
518
|
|
|
@@ -490,10 +527,281 @@ function ensureWorktree({ repoRoot, boardSlug, ticketNumber, profile, branchName
|
|
|
490
527
|
return { branchName: runBranch, worktreePath: runWorktree };
|
|
491
528
|
}
|
|
492
529
|
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
530
|
+
function remoteRunId(ticket, profile) {
|
|
531
|
+
return `orbit-${ticket.number}-${profile}-${shortId()}`;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
function remoteRunDir(repoRoot, runId) {
|
|
535
|
+
return join(repoRoot, ".orbit", "dispatch-runs", runId);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
function remoteWorktreePath(repoRoot, runId) {
|
|
539
|
+
return join(dirname(repoRoot), ".worktrees", basename(repoRoot), runId);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
function contextFromDispatchPacket(packet, blockers) {
|
|
543
|
+
const blockerList = Array.isArray(blockers?.blockers) ? blockers.blockers : packet.blockers?.blockers || [];
|
|
544
|
+
return {
|
|
545
|
+
ticket: packet.ticket,
|
|
546
|
+
board: packet.board,
|
|
547
|
+
board_manual: { entries: [] },
|
|
548
|
+
parent_ticket: packet.parent_ticket || null,
|
|
549
|
+
child_tickets: [],
|
|
550
|
+
blockers: blockerList,
|
|
551
|
+
recent_comments: packet.recent_comments || []
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
async function createRemoteClient(options, target) {
|
|
556
|
+
if (options.remoteClient) return options.remoteClient;
|
|
557
|
+
const { createHttpOrbitClient } = await import("../mcp/orbit-client.js");
|
|
558
|
+
return createHttpOrbitClient({
|
|
559
|
+
...process.env,
|
|
560
|
+
ORBIT_MODE: "remote",
|
|
561
|
+
ORBIT_API_URL: target.serverUrl,
|
|
562
|
+
ORBIT_DEFAULT_BOARD: target.board
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
async function dispatchTicketRemote(options) {
|
|
567
|
+
const target = remoteTarget(options);
|
|
568
|
+
if (!target.serverUrl) throw new Error("Remote dispatch requires --server-url or a hosted Orbit ticket URL.");
|
|
569
|
+
if (!target.board) throw new Error("Remote dispatch requires --board or a hosted Orbit ticket URL containing /b/<slug>.");
|
|
570
|
+
if (!target.ticket) throw new Error("Remote dispatch requires --ticket or a hosted Orbit ticket URL containing /t/<ticket>.");
|
|
571
|
+
|
|
572
|
+
const profile = options.profile || "agent";
|
|
573
|
+
const policyName = options.policy === undefined ? DEFAULT_POLICY : options.policy;
|
|
574
|
+
const verificationCommands = normalizeVerificationCommands(options.verifyCommands);
|
|
575
|
+
const remoteClient = await createRemoteClient(options, target);
|
|
576
|
+
await remoteClient.boardSetActive?.({ slug: target.board });
|
|
577
|
+
|
|
578
|
+
let packet;
|
|
579
|
+
if (/^\d+$/.test(String(target.ticket))) {
|
|
580
|
+
const found = await remoteClient.readTicket({ board_slug: target.board, number: Number(target.ticket) });
|
|
581
|
+
const ticketId = found.ticket?.id || found.results?.[0]?.id || found.id;
|
|
582
|
+
if (!ticketId) throw new Error(`Ticket not found on remote board: ${target.ticket}`);
|
|
583
|
+
packet = await remoteClient.getAgentDispatchPacket({ board_slug: target.board, ticket_id: ticketId, comment_limit: 5 });
|
|
584
|
+
} else {
|
|
585
|
+
packet = await remoteClient.getAgentDispatchPacket({ board_slug: target.board, ticket_id: target.ticket, comment_limit: 5 });
|
|
586
|
+
}
|
|
587
|
+
const blockers = await remoteClient.getTicketBlockers({ board_slug: target.board, ticket_id: packet.ticket.id });
|
|
588
|
+
const blockerList = Array.isArray(blockers?.blockers) ? blockers.blockers : [];
|
|
589
|
+
if (blockerList.length && !options.force) {
|
|
590
|
+
throw new Error(`Ticket #${packet.ticket.number} is blocked: ${blockerList.map((b) => `#${b.number} ${b.title}`).join(", ")}`);
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
const boardRow = {
|
|
594
|
+
id: packet.board.id,
|
|
595
|
+
slug: packet.board.slug,
|
|
596
|
+
name: packet.board.name,
|
|
597
|
+
repo_path: packet.board.repo_path || packet.board.system_path || "",
|
|
598
|
+
default_branch: packet.board.default_branch || "main"
|
|
599
|
+
};
|
|
600
|
+
const ticket = packet.ticket;
|
|
601
|
+
const repoRoot = resolveRepoRoot(options, boardRow);
|
|
602
|
+
|
|
603
|
+
if (options.dryRun) {
|
|
604
|
+
return {
|
|
605
|
+
dry_run: true,
|
|
606
|
+
no_spawn: Boolean(options.noSpawn),
|
|
607
|
+
run_id: null,
|
|
608
|
+
board: { id: boardRow.id, slug: boardRow.slug, name: boardRow.name },
|
|
609
|
+
ticket: { id: ticket.id, number: ticket.number, title: ticket.title },
|
|
610
|
+
profile,
|
|
611
|
+
policy: policyName || "none",
|
|
612
|
+
repo_root: repoRoot,
|
|
613
|
+
worktree_path: repoRoot,
|
|
614
|
+
branch: options.branch || "",
|
|
615
|
+
handoff_path: null,
|
|
616
|
+
policy_bin: null,
|
|
617
|
+
pid: null,
|
|
618
|
+
spawned: false,
|
|
619
|
+
prompt: "",
|
|
620
|
+
run_record: "",
|
|
621
|
+
run_record_path: null,
|
|
622
|
+
verification_commands: verificationCommands,
|
|
623
|
+
remote: true
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
const inProgressId = packet.relevant_state_ids?.in_progress;
|
|
628
|
+
if (!options.noSpawn && !inProgressId) throw new Error("Remote board is missing an In Progress state id in the dispatch packet.");
|
|
629
|
+
const hermesExecutable = options.noSpawn ? null : preflightHermes(options, profile);
|
|
630
|
+
const runId = remoteRunId(ticket, profile);
|
|
631
|
+
const runDir = remoteRunDir(repoRoot, runId);
|
|
632
|
+
const baseCommit = optionalCommandOutput("git", ["-C", repoRoot, "rev-parse", "HEAD"]);
|
|
633
|
+
const environmentVariance = collectEnvironmentVariance();
|
|
634
|
+
let branchName = options.branch || "";
|
|
635
|
+
let worktreePath = "";
|
|
636
|
+
|
|
637
|
+
if (options.worktree) {
|
|
638
|
+
const worktree = ensureWorktree({
|
|
639
|
+
repoRoot,
|
|
640
|
+
boardSlug: boardRow.slug,
|
|
641
|
+
ticketNumber: ticket.number,
|
|
642
|
+
profile,
|
|
643
|
+
branchName: options.branch || `orbit/${ticket.number}-${cleanName(profile)}-remote-${shortId()}`,
|
|
644
|
+
worktreePath: options.worktreePath || remoteWorktreePath(repoRoot, runId)
|
|
645
|
+
});
|
|
646
|
+
branchName = worktree.branchName;
|
|
647
|
+
worktreePath = worktree.worktreePath;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
mkdirSync(runDir, { recursive: true });
|
|
651
|
+
const policyBin = createPolicyBin(runDir, policyName);
|
|
652
|
+
const context = contextFromDispatchPacket(packet, blockers);
|
|
653
|
+
const displayServerUrl = sanitizeUrlBearingValue(target.serverUrl);
|
|
654
|
+
const handoff = `${buildHandoff({
|
|
655
|
+
boardRow,
|
|
656
|
+
context,
|
|
657
|
+
profile,
|
|
658
|
+
repoRoot,
|
|
659
|
+
worktreePath,
|
|
660
|
+
branchName,
|
|
661
|
+
baseCommit,
|
|
662
|
+
policyName,
|
|
663
|
+
runId,
|
|
664
|
+
serverUrl: displayServerUrl,
|
|
665
|
+
verificationCommands
|
|
666
|
+
})}\n## Remote Orbit environment for spawned agents\n- ORBIT_MODE=remote\n- ORBIT_DEFAULT_BOARD=${boardRow.slug}\n- ORBIT_API_URL is provided by dispatch when spawning; URL-bearing values are redacted from run records.\n`;
|
|
667
|
+
const handoffPath = join(runDir, "handoff.md");
|
|
668
|
+
writeFileSync(handoffPath, handoff, "utf8");
|
|
669
|
+
|
|
670
|
+
const prompt = buildShortPrompt({ boardRow, ticket, profile, serverUrl: displayServerUrl });
|
|
671
|
+
const hermesArgs = [];
|
|
672
|
+
if (profile) hermesArgs.push("-p", profile);
|
|
673
|
+
if (options.yolo) hermesArgs.push("--yolo");
|
|
674
|
+
hermesArgs.push("chat", "-q", prompt);
|
|
675
|
+
const runRecordPath = join(runDir, "run-record.json");
|
|
676
|
+
const writeRunRecordFile = options.writeRunRecordFile || ((path, runRecordJson) => {
|
|
677
|
+
writeFileSync(path, `${JSON.stringify(runRecordJson, null, 2)}\n`, "utf8");
|
|
678
|
+
});
|
|
679
|
+
const writeRunRecordJson = (status, childProcess = null) => {
|
|
680
|
+
const runRecordJson = buildRunRecordJson({
|
|
681
|
+
runId,
|
|
682
|
+
status,
|
|
683
|
+
boardRow,
|
|
684
|
+
ticket,
|
|
685
|
+
profile,
|
|
686
|
+
policyName,
|
|
687
|
+
repoRoot,
|
|
688
|
+
worktreePath,
|
|
689
|
+
branchName,
|
|
690
|
+
baseCommit,
|
|
691
|
+
handoffPath,
|
|
692
|
+
policyBin,
|
|
693
|
+
child: childProcess,
|
|
694
|
+
hermesArgs,
|
|
695
|
+
verificationCommands,
|
|
696
|
+
options: { ...options, remote: true },
|
|
697
|
+
serverUrl: target.serverUrl,
|
|
698
|
+
environmentVariance
|
|
699
|
+
});
|
|
700
|
+
writeRunRecordFile(runRecordPath, runRecordJson);
|
|
701
|
+
return runRecordJson;
|
|
702
|
+
};
|
|
703
|
+
|
|
704
|
+
let status = "prepared";
|
|
705
|
+
writeRunRecordJson(status, null);
|
|
706
|
+
const planSummary = buildPlanExcerpt({ ticket, profile, handoffPath });
|
|
707
|
+
let linkedHandoffPath = handoffPath;
|
|
708
|
+
if (typeof remoteClient.uploadMarkdownArtifact === "function") {
|
|
709
|
+
const uploaded = await remoteClient.uploadMarkdownArtifact({
|
|
710
|
+
ticket_id: ticket.id,
|
|
711
|
+
board_slug: boardRow.slug,
|
|
712
|
+
filename: `${runId}-handoff.md`,
|
|
713
|
+
source_path: handoffPath,
|
|
714
|
+
content: readFileSync(handoffPath, "utf8")
|
|
715
|
+
});
|
|
716
|
+
if (uploaded?.path) linkedHandoffPath = uploaded.path;
|
|
717
|
+
}
|
|
718
|
+
const ticketPatch = { ticket_id: ticket.id, board_slug: boardRow.slug, ai_plan: planSummary, plan_artifact_path: linkedHandoffPath };
|
|
719
|
+
if (!options.noSpawn) ticketPatch.state_id = inProgressId;
|
|
720
|
+
await remoteClient.updateTicket(ticketPatch);
|
|
721
|
+
|
|
722
|
+
const cwd = worktreePath || repoRoot;
|
|
723
|
+
const env = {
|
|
724
|
+
...process.env,
|
|
725
|
+
ORBIT_MODE: "remote",
|
|
726
|
+
ORBIT_API_URL: target.serverUrl,
|
|
727
|
+
ORBIT_DEFAULT_BOARD: boardRow.slug,
|
|
728
|
+
ORBIT_DISPATCH_RUN_ID: runId,
|
|
729
|
+
ORBIT_BOARD: boardRow.slug,
|
|
730
|
+
ORBIT_TICKET: String(ticket.number),
|
|
731
|
+
ORBIT_TICKET_ID: ticket.id,
|
|
732
|
+
ORBIT_HANDOFF_PATH: handoffPath,
|
|
733
|
+
ORBIT_AUTONOMOUS_POLICY: policyName || "none",
|
|
734
|
+
...(policyBin ? { PATH: `${policyBin}:${process.env.PATH || ""}` } : {})
|
|
735
|
+
};
|
|
736
|
+
|
|
737
|
+
let child = null;
|
|
738
|
+
if (!options.noSpawn) {
|
|
739
|
+
child = spawn(hermesExecutable.command, [...hermesExecutable.argsPrefix, ...hermesArgs], {
|
|
740
|
+
cwd,
|
|
741
|
+
env,
|
|
742
|
+
detached: !options.foreground,
|
|
743
|
+
stdio: options.foreground ? "inherit" : "ignore"
|
|
744
|
+
});
|
|
745
|
+
if (!options.foreground) child.unref();
|
|
746
|
+
status = "launched";
|
|
747
|
+
writeRunRecordJson(status, child);
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
const runRecord = [
|
|
751
|
+
`Orbit remote dispatch run ${options.noSpawn ? "prepared" : "started"}.`,
|
|
752
|
+
`- run_id: ${runId}`,
|
|
753
|
+
`- profile: ${profile}`,
|
|
754
|
+
`- status: ${status}`,
|
|
755
|
+
`- transport: remote hosted board`,
|
|
756
|
+
`- policy: ${policyName || "none"}`,
|
|
757
|
+
`- ticket: #${ticket.number} ${ticket.title}`,
|
|
758
|
+
`- branch: ${branchName || "current branch"}`,
|
|
759
|
+
`- base_commit: ${baseCommit || "unknown"}`,
|
|
760
|
+
`- worktree: ${worktreePath || repoRoot}`,
|
|
761
|
+
`- handoff: ${handoffPath}`,
|
|
762
|
+
linkedHandoffPath !== handoffPath ? `- hosted_handoff: ${linkedHandoffPath}` : null,
|
|
763
|
+
`- run_record_json: ${runRecordPath}`,
|
|
764
|
+
`- policy_bin: ${policyBin || "none"}`,
|
|
765
|
+
`- pid: ${child?.pid || "not spawned"}`,
|
|
766
|
+
`- verification_commands: ${verificationCommands.join("; ")}`,
|
|
767
|
+
`- command: ${options.hermesBin || "hermes"} ${hermesArgs.map((arg) => (arg.includes(" ") ? q(arg) : arg)).join(" ")}`,
|
|
768
|
+
options.noSpawn ? "- mode: prepare-only; no agent spawned and ticket state left unchanged" : "- mode: spawned; ticket moved to In Progress"
|
|
769
|
+
].filter(Boolean).join("\n");
|
|
770
|
+
|
|
771
|
+
await remoteClient.addComment({ ticket_id: ticket.id, board_slug: boardRow.slug, author: "orbit dispatch", kind: "agent_note", body: runRecord });
|
|
496
772
|
|
|
773
|
+
return {
|
|
774
|
+
dry_run: false,
|
|
775
|
+
no_spawn: Boolean(options.noSpawn),
|
|
776
|
+
run_id: runId,
|
|
777
|
+
board: { id: boardRow.id, slug: boardRow.slug, name: boardRow.name },
|
|
778
|
+
ticket: { id: ticket.id, number: ticket.number, title: ticket.title },
|
|
779
|
+
profile,
|
|
780
|
+
policy: policyName || "none",
|
|
781
|
+
repo_root: repoRoot,
|
|
782
|
+
worktree_path: worktreePath || repoRoot,
|
|
783
|
+
branch: branchName || "",
|
|
784
|
+
handoff_path: handoffPath,
|
|
785
|
+
linked_handoff_path: linkedHandoffPath,
|
|
786
|
+
policy_bin: policyBin,
|
|
787
|
+
pid: child?.pid || null,
|
|
788
|
+
spawned: Boolean(child),
|
|
789
|
+
prompt,
|
|
790
|
+
run_record: runRecord,
|
|
791
|
+
run_record_path: runRecordPath,
|
|
792
|
+
verification_commands: verificationCommands,
|
|
793
|
+
remote: true
|
|
794
|
+
};
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
export async function dispatchTicketAsync(options) {
|
|
798
|
+
if (options.remote || options.serverUrl || options.ticketUrl || parseTicketUrl(options.ticket)) {
|
|
799
|
+
return dispatchTicketRemote({ ...options, remote: true });
|
|
800
|
+
}
|
|
801
|
+
return dispatchTicket(options);
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
export function dispatchTicket(options) {
|
|
497
805
|
const profile = options.profile || "agent";
|
|
498
806
|
const policyName = options.policy === undefined ? DEFAULT_POLICY : options.policy;
|
|
499
807
|
const verificationCommands = normalizeVerificationCommands(options.verifyCommands);
|
|
@@ -616,10 +924,11 @@ export function dispatchTicket(options) {
|
|
|
616
924
|
writeRunRecordJson(status, null);
|
|
617
925
|
|
|
618
926
|
touchBoardActive(boardRow.id);
|
|
927
|
+
const planSummary = buildPlanExcerpt({ ticket, profile, handoffPath });
|
|
619
928
|
if (options.noSpawn) {
|
|
620
|
-
updateTicket(ticket.id, { ai_plan:
|
|
929
|
+
updateTicket(ticket.id, { ai_plan: planSummary, plan_artifact_path: handoffPath }, ctx);
|
|
621
930
|
} else {
|
|
622
|
-
updateTicket(ticket.id, { ai_plan:
|
|
931
|
+
updateTicket(ticket.id, { ai_plan: planSummary, plan_artifact_path: handoffPath, state_id: inProgress.id }, ctx);
|
|
623
932
|
}
|
|
624
933
|
|
|
625
934
|
const cwd = worktreePath || repoRoot;
|
|
@@ -657,7 +966,7 @@ export function dispatchTicket(options) {
|
|
|
657
966
|
`- branch: ${branchName || "current branch"}`,
|
|
658
967
|
`- base_commit: ${baseCommit || "unknown"}`,
|
|
659
968
|
`- worktree: ${worktreePath || repoRoot}`,
|
|
660
|
-
`- handoff:
|
|
969
|
+
`- handoff: linked markdown artifact${options.keepHandoffFile ? ` and ${handoffPath}` : ""}`,
|
|
661
970
|
`- run_record_json: ${runRecordPath}`,
|
|
662
971
|
`- policy_bin: ${policyBin || "none"}`,
|
|
663
972
|
`- pid: ${child?.pid || "not spawned"}`,
|
|
@@ -669,7 +978,7 @@ export function dispatchTicket(options) {
|
|
|
669
978
|
createComment(ticket.id, { author: "orbit dispatch", kind: "agent_note", body: runRecord }, ctx);
|
|
670
979
|
|
|
671
980
|
if (!options.keepHandoffFile) {
|
|
672
|
-
// The canonical copy is the
|
|
981
|
+
// The canonical copy is the linked markdown artifact. Keep run metadata and
|
|
673
982
|
// policy wrappers. Local-only agents may still use ORBIT_HANDOFF_PATH as a
|
|
674
983
|
// bootstrap hint before they read the card, so leave the file in place.
|
|
675
984
|
}
|
|
@@ -686,6 +995,7 @@ export function dispatchTicket(options) {
|
|
|
686
995
|
worktree_path: worktreePath || repoRoot,
|
|
687
996
|
branch: branchName || "",
|
|
688
997
|
handoff_path: handoffPath,
|
|
998
|
+
linked_handoff_path: handoffPath,
|
|
689
999
|
policy_bin: policyBin,
|
|
690
1000
|
pid: child?.pid || null,
|
|
691
1001
|
spawned: Boolean(child),
|
package/src/core/http.js
CHANGED
|
@@ -75,13 +75,20 @@ function shouldBundleSourceStyles(pathname, filePath) {
|
|
|
75
75
|
return pathname === "/styles.css" && filePath === join(ROOT_DIR, "public", "styles.css");
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
export function staticAssetPathname(pathname) {
|
|
79
|
+
return pathname === "/app" || pathname.startsWith("/app/")
|
|
80
|
+
? pathname.slice(4) || "/"
|
|
81
|
+
: pathname;
|
|
82
|
+
}
|
|
83
|
+
|
|
78
84
|
export async function serveStatic(req, res, pathname) {
|
|
79
85
|
if (req.method !== "GET" && req.method !== "HEAD") {
|
|
80
86
|
sendEmpty(res, 405);
|
|
81
87
|
return;
|
|
82
88
|
}
|
|
83
89
|
|
|
84
|
-
const
|
|
90
|
+
const staticPathname = staticAssetPathname(pathname);
|
|
91
|
+
const requested = staticPathname === "/" ? "/index.html" : staticPathname;
|
|
85
92
|
const filePath = resolvePath(PUBLIC_DIR, `.${decodeURIComponent(requested)}`);
|
|
86
93
|
if (!filePath.startsWith(PUBLIC_DIR)) {
|
|
87
94
|
sendEmpty(res, 403);
|
|
@@ -89,7 +96,7 @@ export async function serveStatic(req, res, pathname) {
|
|
|
89
96
|
}
|
|
90
97
|
|
|
91
98
|
try {
|
|
92
|
-
const data = shouldBundleSourceStyles(
|
|
99
|
+
const data = shouldBundleSourceStyles(staticPathname, filePath)
|
|
93
100
|
? Buffer.from((await bundleCss({ entryPoint: filePath, outfile: filePath })).cssFile.contents)
|
|
94
101
|
: await readFile(filePath);
|
|
95
102
|
res.writeHead(200, {
|
package/src/core/paths.js
CHANGED
|
@@ -6,9 +6,33 @@ import { fileURLToPath } from "node:url";
|
|
|
6
6
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
7
7
|
|
|
8
8
|
export const ROOT_DIR = resolve(__dirname, "..", "..");
|
|
9
|
-
|
|
9
|
+
|
|
10
10
|
const _distFull = join(ROOT_DIR, "dist", "full");
|
|
11
|
-
|
|
11
|
+
const _publicDir = join(ROOT_DIR, "public");
|
|
12
|
+
|
|
13
|
+
/** Where static assets (HTML/JS/CSS) are served from.
|
|
14
|
+
*
|
|
15
|
+
* public/ — editable source; `pnpm dev` bundles @starlove/ui imports on the fly.
|
|
16
|
+
* dist/full — prebuilt, hashed bundles from `pnpm build`; what Docker/npm ship.
|
|
17
|
+
*
|
|
18
|
+
* Auto (default): public/ when it exists, else dist/full. Repo dev therefore
|
|
19
|
+
* always sees live CSS/JS edits. Docker and other dist-only installs fall back
|
|
20
|
+
* to the bundle without needing an env var.
|
|
21
|
+
*
|
|
22
|
+
* Override: ORBIT_USE_DIST=1 force dist/full, ORBIT_USE_DIST=0 force public/. */
|
|
23
|
+
function resolvePublicDir() {
|
|
24
|
+
const distExists = existsSync(_distFull);
|
|
25
|
+
const publicExists = existsSync(_publicDir);
|
|
26
|
+
const pref = process.env.ORBIT_USE_DIST;
|
|
27
|
+
|
|
28
|
+
if (pref === "1" && distExists) return _distFull;
|
|
29
|
+
if (pref === "0" && publicExists) return _publicDir;
|
|
30
|
+
if (publicExists) return _publicDir;
|
|
31
|
+
if (distExists) return _distFull;
|
|
32
|
+
return _publicDir;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const PUBLIC_DIR = resolvePublicDir();
|
|
12
36
|
export const MCP_SERVER_PATH = join(ROOT_DIR, "src", "mcp-server.js");
|
|
13
37
|
/** Repo root for default board placement; cwd unless PROJECT_ROOT is set. */
|
|
14
38
|
export const PROJECT_ROOT = process.env.PROJECT_ROOT ? resolve(process.env.PROJECT_ROOT) : resolve(process.cwd());
|
package/src/core/router.js
CHANGED
|
@@ -42,10 +42,12 @@ import {
|
|
|
42
42
|
deleteTicket,
|
|
43
43
|
getTicketStatusHistory,
|
|
44
44
|
restoreTicket,
|
|
45
|
+
updateCommentAiOmission,
|
|
45
46
|
updateTicket
|
|
46
47
|
} from "./tickets.js";
|
|
47
48
|
import { archivedTicketsForBoard } from "./queries.js";
|
|
48
49
|
import { httpError } from "./util.js";
|
|
50
|
+
import { getVersionInfo, startSelfUpdate } from "./version-check.js";
|
|
49
51
|
import { pickFolder } from "./system-picker.js";
|
|
50
52
|
import {
|
|
51
53
|
createRelation,
|
|
@@ -72,6 +74,7 @@ import {
|
|
|
72
74
|
readTicket
|
|
73
75
|
} from "./agent.js";
|
|
74
76
|
import { createReviewVerdict, getReviewVerdict, listReviewVerdicts } from "./review-verdicts.js";
|
|
77
|
+
import { getTicketMarkdownArtifact, storeTicketMarkdownArtifact } from "./artifacts.js";
|
|
75
78
|
|
|
76
79
|
/** Build a ctx for an already-resolved registry row + actor. */
|
|
77
80
|
function ctxFromBoardRow(boardRow, actor) {
|
|
@@ -124,6 +127,21 @@ export async function handleApi(req, res, url) {
|
|
|
124
127
|
return;
|
|
125
128
|
}
|
|
126
129
|
|
|
130
|
+
// Update check (no actor needed). The client polls this on load and on a
|
|
131
|
+
// manual "Check now" (?refresh=1 bypasses the server's cached result).
|
|
132
|
+
if (req.method === "GET" && url.pathname === "/api/version") {
|
|
133
|
+
sendJson(res, 200, await getVersionInfo({ force: url.searchParams.get("refresh") === "1" }));
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Opt-in self-update. Hidden (404) unless the operator set ORBIT_SELF_UPDATE=1,
|
|
138
|
+
// since it pulls + rebuilds + restarts the running server.
|
|
139
|
+
if (req.method === "POST" && url.pathname === "/api/update") {
|
|
140
|
+
if (process.env.ORBIT_SELF_UPDATE !== "1") throw httpError(404, "not_found");
|
|
141
|
+
sendJson(res, 202, startSelfUpdate());
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
127
145
|
const actor = actorFromHttpRequest(req);
|
|
128
146
|
|
|
129
147
|
// Bootstrap: read-only registry + one chosen board. No global state mutated.
|
|
@@ -309,11 +327,26 @@ export async function handleApi(req, res, url) {
|
|
|
309
327
|
sendJson(res, 200, readComments(ticketId, ctx));
|
|
310
328
|
return;
|
|
311
329
|
}
|
|
330
|
+
if (sub === "artifacts/markdown" && req.method === "GET") {
|
|
331
|
+
sendJson(res, 200, getTicketMarkdownArtifact(ticketId, url.searchParams.get("path"), ctx));
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
if (sub === "artifacts/markdown" && req.method === "POST") {
|
|
335
|
+
const body = await readJson(req);
|
|
336
|
+
sendJson(res, 201, storeTicketMarkdownArtifact(ticketId, body, ctx));
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
312
339
|
if (sub === "comments" && req.method === "POST") {
|
|
313
340
|
const body = await readJson(req);
|
|
314
341
|
sendMutationJson(res, 201, createComment(ticketId, body, ctx), ctx);
|
|
315
342
|
return;
|
|
316
343
|
}
|
|
344
|
+
const commentPath = sub.match(/^comments\/([^/]+)$/);
|
|
345
|
+
if (commentPath && req.method === "PATCH") {
|
|
346
|
+
const body = await readJson(req);
|
|
347
|
+
sendMutationJson(res, 200, updateCommentAiOmission(ticketId, decodeURIComponent(commentPath[1]), body, ctx), ctx);
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
317
350
|
if (sub === "review-verdicts" && req.method === "GET") {
|
|
318
351
|
sendJson(res, 200, { review_verdicts: listReviewVerdicts(ticketId, ctx, { limit: url.searchParams.get("limit") }) });
|
|
319
352
|
return;
|
package/src/core/search.js
CHANGED
|
@@ -57,7 +57,7 @@ export function searchTickets(args, ctx) {
|
|
|
57
57
|
const numberRank = ticketNumber !== null ? "CASE WHEN t.number = ? THEN -1 ELSE 0 END" : "0";
|
|
58
58
|
const numberPredicate = ticketNumber !== null ? "OR t.number = ?" : "";
|
|
59
59
|
const fallbackArgs = ticketNumber !== null ? [ticketNumber, board.id] : [board.id];
|
|
60
|
-
fallbackArgs.push(like, like, like, like, like, like);
|
|
60
|
+
fallbackArgs.push(like, like, like, like, like, like, like, like);
|
|
61
61
|
if (ticketNumber !== null) fallbackArgs.push(ticketNumber);
|
|
62
62
|
fallbackArgs.push(limit);
|
|
63
63
|
|
|
@@ -71,7 +71,8 @@ export function searchTickets(args, ctx) {
|
|
|
71
71
|
LEFT JOIN comments c ON c.ticket_id = t.id
|
|
72
72
|
WHERE t.board_id = ? AND t.archived_at IS NULL
|
|
73
73
|
AND (t.title LIKE ? OR t.description LIKE ? OR t.ai_plan LIKE ?
|
|
74
|
-
OR t.implementation_summary LIKE ? OR t.implementation_updates LIKE ?
|
|
74
|
+
OR t.implementation_summary LIKE ? OR t.implementation_updates LIKE ?
|
|
75
|
+
OR t.plan_artifact_path LIKE ? OR t.implementation_artifact_path LIKE ? OR c.body LIKE ?
|
|
75
76
|
${numberPredicate})
|
|
76
77
|
ORDER BY t.updated_at DESC, t.id ASC
|
|
77
78
|
LIMIT ?`
|