agent-relay-server 0.114.0 → 0.116.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/docs/openapi.json +39 -1
- package/package.json +3 -3
- package/public/assets/MessageBubble-BJllE_2c.js +4 -0
- package/public/assets/MessageBubble-BJllE_2c.js.map +1 -0
- package/public/assets/PlanGraphPanel-CoK024n9.js +2 -0
- package/public/assets/PlanGraphPanel-CoK024n9.js.map +1 -0
- package/public/assets/{activity-Dw15HMdi.js → activity-CBjg7NSP.js} +2 -2
- package/public/assets/{activity-Dw15HMdi.js.map → activity-CBjg7NSP.js.map} +1 -1
- package/public/assets/{agent-profiles-DNtXbS77.js → agent-profiles-DzkjQaCS.js} +2 -2
- package/public/assets/{agent-profiles-DNtXbS77.js.map → agent-profiles-DzkjQaCS.js.map} +1 -1
- package/public/assets/agents-Bu1i1jH-.js +2 -0
- package/public/assets/{agents-DOwnLE42.js.map → agents-Bu1i1jH-.js.map} +1 -1
- package/public/assets/{analytics-CRwxJWuV.js → analytics-Cc2GoEq_.js} +3 -3
- package/public/assets/{analytics-CRwxJWuV.js.map → analytics-Cc2GoEq_.js.map} +1 -1
- package/public/assets/{automation-BQk8XQEj.js → automation-CcMveyvi.js} +2 -2
- package/public/assets/{automation-BQk8XQEj.js.map → automation-CcMveyvi.js.map} +1 -1
- package/public/assets/{badge-oaCc6hVH.js → badge-BhUZ1cAb.js} +2 -2
- package/public/assets/{badge-oaCc6hVH.js.map → badge-BhUZ1cAb.js.map} +1 -1
- package/public/assets/branch-state-badge-CYWBVZDI.js +2 -0
- package/public/assets/{branch-state-badge-JDaHO2fG.js.map → branch-state-badge-CYWBVZDI.js.map} +1 -1
- package/public/assets/{button-Cx0NiToX.js → button-C-V4oij1.js} +2 -2
- package/public/assets/{button-Cx0NiToX.js.map → button-C-V4oij1.js.map} +1 -1
- package/public/assets/{card-DkHdS68K.js → card-DAkf5Kzw.js} +2 -2
- package/public/assets/{card-DkHdS68K.js.map → card-DAkf5Kzw.js.map} +1 -1
- package/public/assets/{channels-wQha9YA6.js → channels-IdC7jyLt.js} +2 -2
- package/public/assets/{channels-wQha9YA6.js.map → channels-IdC7jyLt.js.map} +1 -1
- package/public/assets/chat-CP4x6Gcr.js +2 -0
- package/public/assets/{chat-BTmgzO_g.js.map → chat-CP4x6Gcr.js.map} +1 -1
- package/public/assets/{connectors-BF-XDm0s.js → connectors-Dngl_FJz.js} +2 -2
- package/public/assets/{connectors-BF-XDm0s.js.map → connectors-Dngl_FJz.js.map} +1 -1
- package/public/assets/{copy-button-CsCBPQzj.js → copy-button-BStzXgtd.js} +2 -2
- package/public/assets/{copy-button-CsCBPQzj.js.map → copy-button-BStzXgtd.js.map} +1 -1
- package/public/assets/{dist-BJnh7VkB.js → dist-CBMWGhWc.js} +2 -2
- package/public/assets/{dist-BJnh7VkB.js.map → dist-CBMWGhWc.js.map} +1 -1
- package/public/assets/{dist-Pak9TL_I.js → dist-CDZZxxJq.js} +2 -2
- package/public/assets/{dist-Pak9TL_I.js.map → dist-CDZZxxJq.js.map} +1 -1
- package/public/assets/{dist-AbnkNPcD.js → dist-CQ0Z-TDi.js} +2 -2
- package/public/assets/{dist-AbnkNPcD.js.map → dist-CQ0Z-TDi.js.map} +1 -1
- package/public/assets/{dist-REUlCFee.js → dist-ChDX_t12.js} +2 -2
- package/public/assets/{dist-REUlCFee.js.map → dist-ChDX_t12.js.map} +1 -1
- package/public/assets/{dist-DLOrqIs-.js → dist-CmFeEXMe.js} +2 -2
- package/public/assets/{dist-DLOrqIs-.js.map → dist-CmFeEXMe.js.map} +1 -1
- package/public/assets/{dist-DBti5--U.js → dist-Cs8X0P1d.js} +2 -2
- package/public/assets/{dist-DBti5--U.js.map → dist-Cs8X0P1d.js.map} +1 -1
- package/public/assets/dist-D2jvo5oA.js +2 -0
- package/public/assets/{dist-QY_EIkcd.js.map → dist-D2jvo5oA.js.map} +1 -1
- package/public/assets/{dist-1g1lCjVb.js → dist-DV83j9tm.js} +2 -2
- package/public/assets/{dist-1g1lCjVb.js.map → dist-DV83j9tm.js.map} +1 -1
- package/public/assets/{dist-DJpDTmGg.js → dist-DyNJNwpi.js} +2 -2
- package/public/assets/{dist-DJpDTmGg.js.map → dist-DyNJNwpi.js.map} +1 -1
- package/public/assets/dist-mm7c7nBt.js +2 -0
- package/public/assets/{dist-DjJMNrsj.js.map → dist-mm7c7nBt.js.map} +1 -1
- package/public/assets/dist-y03IwOrd.js +2 -0
- package/public/assets/{dist-C7VlX6hz.js.map → dist-y03IwOrd.js.map} +1 -1
- package/public/assets/{es2015-GhJvD7eR.js → es2015-VnZ2kVIz.js} +2 -2
- package/public/assets/{es2015-GhJvD7eR.js.map → es2015-VnZ2kVIz.js.map} +1 -1
- package/public/assets/formatted-body-impl-CBHoD82a.js +3 -0
- package/public/assets/formatted-body-impl-CBHoD82a.js.map +1 -0
- package/public/assets/{index-DYwuv_Na.css → index-DcmG5gLR.css} +1 -1
- package/public/assets/index-DocqZ4Q0.js +23 -0
- package/public/assets/index-DocqZ4Q0.js.map +1 -0
- package/public/assets/{input-CPfrBmej.js → input-CtLT7WXi.js} +2 -2
- package/public/assets/{input-CPfrBmej.js.map → input-CtLT7WXi.js.map} +1 -1
- package/public/assets/{insights-DwS0svZy.js → insights-Du9X-nwA.js} +2 -2
- package/public/assets/{insights-DwS0svZy.js.map → insights-Du9X-nwA.js.map} +1 -1
- package/public/assets/{integrations-De1_UHQE.js → integrations-BlSadMFC.js} +2 -2
- package/public/assets/{integrations-De1_UHQE.js.map → integrations-BlSadMFC.js.map} +1 -1
- package/public/assets/{lib-Bi-xeh6t.js → lib-BLoevhQO.js} +3 -3
- package/public/assets/{lib-Bi-xeh6t.js.map → lib-BLoevhQO.js.map} +1 -1
- package/public/assets/lucide-react-Dc3ZwCBi.js +9 -0
- package/public/assets/{lucide-react-De5edQpl.js.map → lucide-react-Dc3ZwCBi.js.map} +1 -1
- package/public/assets/{maintenance-B5qrcsDt.js → maintenance-KeCPGZG4.js} +2 -2
- package/public/assets/{maintenance-B5qrcsDt.js.map → maintenance-KeCPGZG4.js.map} +1 -1
- package/public/assets/{managed-agents-BGMWAUfG.js → managed-agents-YKqARLQn.js} +4 -4
- package/public/assets/{managed-agents-BGMWAUfG.js.map → managed-agents-YKqARLQn.js.map} +1 -1
- package/public/assets/{markdown-preview-impl-DsZ4Ek80.js → markdown-preview-impl-Bo8ql-lO.js} +2 -2
- package/public/assets/{markdown-preview-impl-DsZ4Ek80.js.map → markdown-preview-impl-Bo8ql-lO.js.map} +1 -1
- package/public/assets/memory-BlftQACU.js +2 -0
- package/public/assets/{memory-CouN7Y4r.js.map → memory-BlftQACU.js.map} +1 -1
- package/public/assets/{messages-D9mYFxhU.js → messages-DQu0Np7r.js} +2 -2
- package/public/assets/{messages-D9mYFxhU.js.map → messages-DQu0Np7r.js.map} +1 -1
- package/public/assets/{orchestrators-B_SD_mhI.js → orchestrators-CW00iA0W.js} +2 -2
- package/public/assets/{orchestrators-B_SD_mhI.js.map → orchestrators-CW00iA0W.js.map} +1 -1
- package/public/assets/{overview-C8hVsCqz.js → overview-DyiH9eVP.js} +2 -2
- package/public/assets/{overview-C8hVsCqz.js.map → overview-DyiH9eVP.js.map} +1 -1
- package/public/assets/{pairs-B6vWKXnX.js → pairs-Ee8Npq67.js} +2 -2
- package/public/assets/{pairs-B6vWKXnX.js.map → pairs-Ee8Npq67.js.map} +1 -1
- package/public/assets/{projects-C2gHOdEY.js → projects-CqeJsfB6.js} +2 -2
- package/public/assets/{projects-C2gHOdEY.js.map → projects-CqeJsfB6.js.map} +1 -1
- package/public/assets/{prompt-settings-D4SXk0af.js → prompt-settings-0fHJtGD5.js} +2 -2
- package/public/assets/{prompt-settings-D4SXk0af.js.map → prompt-settings-0fHJtGD5.js.map} +1 -1
- package/public/assets/{react-dom-FNIdlE_z.js → react-dom-cLkVFBsS.js} +2 -2
- package/public/assets/{react-dom-FNIdlE_z.js.map → react-dom-cLkVFBsS.js.map} +1 -1
- package/public/assets/{registry-CMPhWcx3.js → registry-QTLN8Jy-.js} +2 -2
- package/public/assets/{registry-CMPhWcx3.js.map → registry-QTLN8Jy-.js.map} +1 -1
- package/public/assets/{security-C1fE4DVg.js → security-B0FTbGXU.js} +2 -2
- package/public/assets/{security-C1fE4DVg.js.map → security-B0FTbGXU.js.map} +1 -1
- package/public/assets/{select-D9j1MBVi.js → select-DhY0OvwP.js} +2 -2
- package/public/assets/{select-D9j1MBVi.js.map → select-DhY0OvwP.js.map} +1 -1
- package/public/assets/settings-B2UEpdBi.js +6 -0
- package/public/assets/{settings-CduSwUr9.js.map → settings-B2UEpdBi.js.map} +1 -1
- package/public/assets/store-DwgQJjEv.js +9 -0
- package/public/assets/store-DwgQJjEv.js.map +1 -0
- package/public/assets/tasks-BvDfBcxZ.js +2 -0
- package/public/assets/tasks-BvDfBcxZ.js.map +1 -0
- package/public/assets/{teams-ZEXvcAz_.js → teams-CIqsg55S.js} +2 -2
- package/public/assets/{teams-ZEXvcAz_.js.map → teams-CIqsg55S.js.map} +1 -1
- package/public/assets/{terminal-viewer-impl-tktfofs3.js → terminal-viewer-impl-Cig30gnt.js} +3 -3
- package/public/assets/{terminal-viewer-impl-tktfofs3.js.map → terminal-viewer-impl-Cig30gnt.js.map} +1 -1
- package/public/assets/types-DSwhOFcJ.js.map +1 -1
- package/public/assets/{use-keyboard-viewport-wzkGlTqU.js → use-keyboard-viewport-DucYmqmP.js} +2 -2
- package/public/assets/{use-keyboard-viewport-wzkGlTqU.js.map → use-keyboard-viewport-DucYmqmP.js.map} +1 -1
- package/public/assets/{user-avatar-_Dq16BWV.js → user-avatar-Ct6HEqZm.js} +2 -2
- package/public/assets/{user-avatar-_Dq16BWV.js.map → user-avatar-Ct6HEqZm.js.map} +1 -1
- package/public/assets/{work-queue-Bk8g7t9K.js → work-queue-XWcDwe8G.js} +2 -2
- package/public/assets/{work-queue-Bk8g7t9K.js.map → work-queue-XWcDwe8G.js.map} +1 -1
- package/public/assets/workspaces-EI4N2N7L.js +3 -0
- package/public/assets/{workspaces-D2bYW3zl.js.map → workspaces-EI4N2N7L.js.map} +1 -1
- package/public/index.html +21 -21
- package/runner/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/scripts/orchestrator-spawn-smoke.ts +30 -13
- package/src/branch-landed.ts +14 -1
- package/src/config-store.ts +15 -3
- package/src/db/mappers.ts +3 -0
- package/src/db/migrations.ts +41 -2
- package/src/db/plans.ts +11 -0
- package/src/db/schema.ts +3 -1
- package/src/db/task-entity.ts +78 -3
- package/src/mcp/index.ts +1 -1
- package/src/mcp-plan-tools.ts +3 -0
- package/src/mcp-task-tools.ts +16 -7
- package/src/routes/index.ts +2 -1
- package/src/routes/plans.ts +12 -2
- package/src/routes/tasks.ts +13 -5
- package/src/services/plan-graph.ts +8 -1
- package/src/services/spawn-agent.ts +25 -22
- package/src/services/task-entity.ts +6 -1
- package/public/assets/MessageBubble-CxqPFLGP.js +0 -4
- package/public/assets/MessageBubble-CxqPFLGP.js.map +0 -1
- package/public/assets/PlanGraphPanel-gQ_lb6zP.js +0 -2
- package/public/assets/PlanGraphPanel-gQ_lb6zP.js.map +0 -1
- package/public/assets/agents-DOwnLE42.js +0 -2
- package/public/assets/branch-state-badge-JDaHO2fG.js +0 -2
- package/public/assets/chat-BTmgzO_g.js +0 -2
- package/public/assets/dist-C7VlX6hz.js +0 -2
- package/public/assets/dist-DjJMNrsj.js +0 -2
- package/public/assets/dist-QY_EIkcd.js +0 -2
- package/public/assets/formatted-body-impl-2ZWd80_S.js +0 -3
- package/public/assets/formatted-body-impl-2ZWd80_S.js.map +0 -1
- package/public/assets/index-D4mb_LKw.js +0 -23
- package/public/assets/index-D4mb_LKw.js.map +0 -1
- package/public/assets/lucide-react-De5edQpl.js +0 -9
- package/public/assets/memory-CouN7Y4r.js +0 -2
- package/public/assets/settings-CduSwUr9.js +0 -6
- package/public/assets/store-Bk6Lku_S.js +0 -9
- package/public/assets/store-Bk6Lku_S.js.map +0 -1
- package/public/assets/tasks-Bi456f7f.js +0 -2
- package/public/assets/tasks-Bi456f7f.js.map +0 -1
- package/public/assets/workspaces-D2bYW3zl.js +0 -3
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
2
3
|
import { hostname } from "node:os";
|
|
3
4
|
import { RELAY_TOKEN_HEADER } from "agent-relay-sdk";
|
|
4
5
|
|
|
@@ -16,7 +17,7 @@ type Agent = {
|
|
|
16
17
|
status: string;
|
|
17
18
|
ready: boolean;
|
|
18
19
|
tags?: string[];
|
|
19
|
-
meta?: { cwd?: string; provider?: string };
|
|
20
|
+
meta?: { cwd?: string; provider?: string; workspace?: { repoRoot?: string; sourceCwd?: string } };
|
|
20
21
|
createdAt?: number;
|
|
21
22
|
};
|
|
22
23
|
|
|
@@ -25,10 +26,9 @@ type ApprovalMode = "open" | "guarded" | "read-only";
|
|
|
25
26
|
const args = process.argv.slice(2);
|
|
26
27
|
let relayUrl = process.env.AGENT_RELAY_URL || "http://127.0.0.1:4850";
|
|
27
28
|
let orchestratorId = process.env.AGENT_RELAY_SMOKE_ORCHESTRATOR || "";
|
|
28
|
-
// Default cwd is resolved
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
// fails cwd containment.
|
|
29
|
+
// Default cwd is resolved to the local git repo root below. For remote
|
|
30
|
+
// orchestrators, pass --cwd or AGENT_RELAY_SMOKE_CWD with a repo path on that
|
|
31
|
+
// host so isolated workspace creation has a valid source repository.
|
|
32
32
|
let cwd = process.env.AGENT_RELAY_SMOKE_CWD || "";
|
|
33
33
|
let timeoutMs = Number(process.env.AGENT_RELAY_SMOKE_TIMEOUT_MS || 90_000);
|
|
34
34
|
let providers = (process.env.AGENT_RELAY_SMOKE_PROVIDERS || "codex,claude").split(",").filter(Boolean);
|
|
@@ -83,11 +83,24 @@ async function waitFor<T>(label: string, fn: () => Promise<T | null>): Promise<T
|
|
|
83
83
|
throw new Error(`${label} timed out after ${timeoutMs}ms`);
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
function localRepoRoot(): string | null {
|
|
87
|
+
const result = spawnSync("git", ["rev-parse", "--show-toplevel"], {
|
|
88
|
+
cwd: process.cwd(),
|
|
89
|
+
encoding: "utf8",
|
|
90
|
+
});
|
|
91
|
+
if (result.status !== 0) return null;
|
|
92
|
+
return result.stdout.trim() || null;
|
|
93
|
+
}
|
|
94
|
+
|
|
86
95
|
function findSpawnedAgent(agents: Agent[], provider: string, label: string, startedAt: number): Agent | null {
|
|
87
96
|
return agents
|
|
88
97
|
.filter((agent) => agent.ready && agent.status !== "offline")
|
|
89
98
|
.filter((agent) => agent.label === label)
|
|
90
|
-
.filter((agent) =>
|
|
99
|
+
.filter((agent) =>
|
|
100
|
+
agent.meta?.cwd === cwd ||
|
|
101
|
+
agent.meta?.workspace?.sourceCwd === cwd ||
|
|
102
|
+
agent.meta?.workspace?.repoRoot === cwd
|
|
103
|
+
)
|
|
91
104
|
.filter((agent) => (agent.tags || []).includes(provider))
|
|
92
105
|
.filter((agent) => (agent.tags || []).includes("dashboard-spawned"))
|
|
93
106
|
.filter((agent) => !agent.createdAt || agent.createdAt >= startedAt - 5_000)
|
|
@@ -118,8 +131,8 @@ const orchestrators = await api<Orchestrator[]>("GET", "/orchestrators");
|
|
|
118
131
|
// Default selection prefers the orchestrator on the same host as the relay (the
|
|
119
132
|
// gate runs there): a local spawn is faster and avoids cross-host teardown
|
|
120
133
|
// flakiness. Fall back to any online orchestrator. An explicit --orchestrator
|
|
121
|
-
// always wins — pair it with --cwd
|
|
122
|
-
//
|
|
134
|
+
// always wins — pair it with --cwd to target a remote host such as a macOS
|
|
135
|
+
// macmini.
|
|
123
136
|
const onlineOrchestrators = orchestrators.filter((orch) => orch.status === "online");
|
|
124
137
|
const localOrchestrator = onlineOrchestrators.find((orch) => orch.id === hostname());
|
|
125
138
|
const orchestrator = orchestratorId
|
|
@@ -129,13 +142,17 @@ if (!orchestrator) throw new Error(orchestratorId ? `orchestrator not found: ${o
|
|
|
129
142
|
if (orchestrator.status !== "online") throw new Error(`orchestrator ${orchestrator.id} is ${orchestrator.status}`);
|
|
130
143
|
|
|
131
144
|
// Resolve the spawn cwd against the chosen orchestrator's own base directory so
|
|
132
|
-
// the smoke
|
|
133
|
-
// override still wins (and is subject to the
|
|
145
|
+
// the smoke exercises isolated workspace creation from a real git repository.
|
|
146
|
+
// An explicit --cwd / env override still wins (and is subject to the
|
|
147
|
+
// orchestrator's containment check).
|
|
134
148
|
if (!cwd) {
|
|
135
|
-
if (
|
|
136
|
-
throw new Error(`orchestrator ${orchestrator.id}
|
|
149
|
+
if (orchestrator.id !== hostname()) {
|
|
150
|
+
throw new Error(`orchestrator ${orchestrator.id} is remote; pass --cwd or set AGENT_RELAY_SMOKE_CWD to a git repo on that host`);
|
|
151
|
+
}
|
|
152
|
+
cwd = localRepoRoot() || "";
|
|
153
|
+
if (!cwd) {
|
|
154
|
+
throw new Error("could not resolve the local git repo root; pass --cwd or set AGENT_RELAY_SMOKE_CWD");
|
|
137
155
|
}
|
|
138
|
-
cwd = orchestrator.baseDir;
|
|
139
156
|
}
|
|
140
157
|
console.log(`using cwd ${cwd} on ${orchestrator.id} (base ${orchestrator.baseDir})`);
|
|
141
158
|
|
package/src/branch-landed.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { errMessage, type BaseWorktreeSyncResult } from "agent-relay-sdk";
|
|
|
2
2
|
import { emitRelayEvent } from "./events";
|
|
3
3
|
import { routeNotification, INFORMATIONAL_TTL_MS } from "./notification-router";
|
|
4
4
|
import { registerDefaultAudienceRules, audiencePredicates } from "./notification-rules";
|
|
5
|
-
import { createActivityEvent, getAgent, updateWorkspaceStatus } from "./db";
|
|
5
|
+
import { createActivityEvent, deliverableTaskIdsByBranch, deliverableTaskIdsByOwnerAgent, getAgent, getTaskDetail, updateWorkspaceStatus } from "./db";
|
|
6
6
|
import { renderTemplate } from "./prompt-resolver";
|
|
7
7
|
import { resolveCoordinatorAgentId } from "./steward-identity";
|
|
8
8
|
import { stewardFallbackTarget } from "./config";
|
|
@@ -85,6 +85,15 @@ function landedCommitSubjects(input: BranchLandedInput, log: BranchLandedLogFn):
|
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
function resolveLandedTask(workspace: Pick<WorkspaceRecord, "branch" | "ownerAgentId">): { taskId: number; taskLabel: string } | undefined {
|
|
89
|
+
const branchTaskId = workspace.branch ? deliverableTaskIdsByBranch(workspace.branch)[0] : undefined;
|
|
90
|
+
const ownerTaskIds = !branchTaskId && workspace.ownerAgentId ? deliverableTaskIdsByOwnerAgent(workspace.ownerAgentId) : [];
|
|
91
|
+
const taskId = branchTaskId ?? (ownerTaskIds.length === 1 ? ownerTaskIds[0] : undefined);
|
|
92
|
+
if (!taskId) return undefined;
|
|
93
|
+
const task = getTaskDetail(taskId);
|
|
94
|
+
return task ? { taskId: task.id, taskLabel: task.title } : undefined;
|
|
95
|
+
}
|
|
96
|
+
|
|
88
97
|
/**
|
|
89
98
|
* #239 — turn an authoritative land completion into a relay-driven push so the author
|
|
90
99
|
* stops polling to learn it merged. Always emits the durable `branch.landed` event (the
|
|
@@ -100,6 +109,7 @@ export function notifyBranchLanded(input: BranchLandedInput): void {
|
|
|
100
109
|
const shortSha = input.mergedSha ? input.mergedSha.slice(0, 12) : undefined;
|
|
101
110
|
const lifecycleLog = (message: string) => console.warn(message);
|
|
102
111
|
const commitSubjects = landedCommitSubjects(input, lifecycleLog);
|
|
112
|
+
const landedTask = resolveLandedTask(workspace);
|
|
103
113
|
|
|
104
114
|
emitRelayEvent({
|
|
105
115
|
type: "branch.landed",
|
|
@@ -115,6 +125,7 @@ export function notifyBranchLanded(input: BranchLandedInput): void {
|
|
|
115
125
|
author: workspace.ownerAgentId,
|
|
116
126
|
newBranch: input.newBranch,
|
|
117
127
|
pushed: input.pushed,
|
|
128
|
+
...landedTask,
|
|
118
129
|
},
|
|
119
130
|
});
|
|
120
131
|
emitRelayEvent({
|
|
@@ -132,6 +143,7 @@ export function notifyBranchLanded(input: BranchLandedInput): void {
|
|
|
132
143
|
newBranch: input.newBranch,
|
|
133
144
|
pushed: input.pushed,
|
|
134
145
|
notification: "landed-success",
|
|
146
|
+
...landedTask,
|
|
135
147
|
},
|
|
136
148
|
});
|
|
137
149
|
// #482/#833 issue close FIRST, then the #831 Task drive: the Task's close PROJECTION
|
|
@@ -164,6 +176,7 @@ export function notifyBranchLanded(input: BranchLandedInput): void {
|
|
|
164
176
|
author,
|
|
165
177
|
newBranch: input.newBranch,
|
|
166
178
|
pushed: input.pushed,
|
|
179
|
+
...landedTask,
|
|
167
180
|
};
|
|
168
181
|
|
|
169
182
|
// A local-only land (push disabled, or base has no upstream) has NOT reached the
|
package/src/config-store.ts
CHANGED
|
@@ -67,14 +67,14 @@ const WORKER_TOOL_GROUPS = ["core", "workspace", "memory", "artifacts", "blackbo
|
|
|
67
67
|
|
|
68
68
|
function builtInAgentProfiles(): AgentProfile[] {
|
|
69
69
|
return [
|
|
70
|
-
agentProfileDefaults({ name: DEFAULT_SPAWN_PROFILE_NAME, icon: "🤖", description: "Relay-aware spawned agent with bundled Relay context, skills, plugins, and status integration.", base: "
|
|
70
|
+
agentProfileDefaults({ name: DEFAULT_SPAWN_PROFILE_NAME, icon: "🤖", description: "Relay-aware spawned agent with bundled Relay context, skills, plugins, and status integration.", base: "minimal", instructions: { append: [], repoInstructions: "allow", globalInstructions: "allow" }, sharedMcp: { policy: "all" }, hostAssets: { skills: true, plugins: true }, relay: { context: true, skills: true, plugins: true, statusLine: true, mcp: true, tools: { groups: WORKER_TOOL_GROUPS } }, permissions: { filesystem: "host" } }),
|
|
71
71
|
agentProfileDefaults({ name: "minimal", icon: "▫️", description: "Repo-focused spawned agent with Relay affordances disabled and host/global extras avoided where supported.", base: "minimal" }),
|
|
72
72
|
agentProfileDefaults({ name: "isolated-research", icon: "🔬", description: "Strict profile for autonomous research runs: repo context only, no Relay prompt surface, no global extras where supported.", base: "isolated" }),
|
|
73
73
|
agentProfileDefaults({ name: "vanilla", icon: "🫥", description: "Zero host bleed (#552): isolated provider home with host plugins, skills, hooks, MCP, and the CLAUDE.md/AGENTS.md cascade (repo + global) all suppressed at launch. Relay provisions any surface explicitly per profile.", base: "vanilla" }),
|
|
74
74
|
agentProfileDefaults({ name: "lean-worker", icon: "🪶", description: "Lean worker profile (#570): vanilla host-dependency drop with only Relay context/MCP restored and Project knowledge injected from the spawn cwd.", base: "vanilla", relay: { context: true, skills: false, plugins: false, statusLine: false, mcp: true, tools: { groups: WORKER_TOOL_GROUPS } }, projectInstructions: { policy: "all" } }),
|
|
75
|
-
agentProfileDefaults({ name: "coordinator", icon: "🧭", description: "Relay-aware coordination lead for spawning worker teams, maintaining shared state, and driving review and release.", base: "
|
|
75
|
+
agentProfileDefaults({ name: "coordinator", icon: "🧭", description: "Relay-aware coordination lead for spawning worker teams, maintaining shared state, and driving review and release.", base: "minimal", instructions: { append: [renderTemplate("profile.coordinator.charter")], repoInstructions: "allow", globalInstructions: "allow" }, sharedMcp: { policy: "all" }, hostAssets: { skills: true, plugins: true }, relay: { context: true, skills: true, plugins: true, statusLine: true, mcp: true }, permissions: { filesystem: "host" }, maxSpawnedAgents: 8 }),
|
|
76
76
|
agentProfileDefaults({ name: "ops-assistant", icon: "🛠️", description: "Relay-aware ops lieutenant for a coordinator: runs tests, releases, smoke probes, gates, and host checks, then reports crisp outcomes back up.", provider: "codex", base: "host", instructions: { append: [renderTemplate("profile.ops-assistant.brief")], repoInstructions: "allow", globalInstructions: "allow" }, notifications: { responseMode: "results-only" }, permissions: { mode: "open", filesystem: "host" } }),
|
|
77
|
-
agentProfileDefaults({ name: "worker", icon: "🔨", description: "Relay-aware focused implementer with open approval posture and no delegated spawn capability.", base: "
|
|
77
|
+
agentProfileDefaults({ name: "worker", icon: "🔨", description: "Relay-aware focused implementer with open approval posture and no delegated spawn capability.", base: "minimal", instructions: { system: renderTemplate("profile.worker.system"), append: [], repoInstructions: "allow", globalInstructions: "allow" }, sharedMcp: { policy: "all" }, hostAssets: { skills: true, plugins: true }, relay: { context: true, skills: true, plugins: true, statusLine: true, mcp: true, tools: { groups: WORKER_TOOL_GROUPS } }, permissions: { mode: "open", filesystem: "host" } }),
|
|
78
78
|
agentProfileDefaults({ name: "reviewer", icon: "🔍", description: "Relay-aware read-only reviewer for audit and review passes without file writes.", base: "host", mcp: { mode: "none" }, permissions: { mode: "read-only", filesystem: "host" }, relay: { tools: { groups: WORKER_TOOL_GROUPS } } }),
|
|
79
79
|
].map((profile) => ({ ...profile, builtIn: true }));
|
|
80
80
|
}
|
|
@@ -272,6 +272,16 @@ function cleanAgentProfileSharedMcp(value: unknown): AgentProfile["sharedMcp"] {
|
|
|
272
272
|
return { policy: cleanEnum(policy, "sharedMcp.policy", SHARED_MCP_POLICY_NAMES) as NonNullable<AgentProfile["sharedMcp"]>["policy"] };
|
|
273
273
|
}
|
|
274
274
|
|
|
275
|
+
function cleanAgentProfileHostAssets(value: unknown): AgentProfile["hostAssets"] {
|
|
276
|
+
if (value === undefined || value === null || value === false) return undefined;
|
|
277
|
+
if (!isRecord(value)) throw new ValidationError("hostAssets must be an object");
|
|
278
|
+
const hostAssets = {
|
|
279
|
+
...(value.skills === undefined ? {} : { skills: cleanBoolean(value.skills, "hostAssets.skills") }),
|
|
280
|
+
...(value.plugins === undefined ? {} : { plugins: cleanBoolean(value.plugins, "hostAssets.plugins") }),
|
|
281
|
+
};
|
|
282
|
+
return Object.keys(hostAssets).length ? hostAssets : undefined;
|
|
283
|
+
}
|
|
284
|
+
|
|
275
285
|
function cleanAgentProfileNotifications(value: unknown): AgentProfile["notifications"] {
|
|
276
286
|
if (value === undefined || value === null) return undefined;
|
|
277
287
|
if (!isRecord(value)) throw new ValidationError("notifications must be an object");
|
|
@@ -306,6 +316,7 @@ function agentProfileDefaults(input: Pick<AgentProfile, "name" | "base"> & Omit<
|
|
|
306
316
|
...(input.projectSkills ? { projectSkills: input.projectSkills } : {}),
|
|
307
317
|
...(input.projectSettings ? { projectSettings: input.projectSettings } : {}),
|
|
308
318
|
...(input.sharedMcp ? { sharedMcp: input.sharedMcp } : {}),
|
|
319
|
+
...(input.hostAssets ? { hostAssets: input.hostAssets } : {}),
|
|
309
320
|
...(input.notifications ? { notifications: input.notifications } : {}),
|
|
310
321
|
relay: {
|
|
311
322
|
context: input.relay?.context ?? !isolated,
|
|
@@ -389,6 +400,7 @@ function validateAgentProfile(key: string, value: unknown, opts: { allowBuiltIn?
|
|
|
389
400
|
projectSkills: cleanAgentProfileProjectSkills(value.projectSkills),
|
|
390
401
|
projectSettings: cleanAgentProfileProjectSettings(value.projectSettings),
|
|
391
402
|
sharedMcp: cleanAgentProfileSharedMcp(value.sharedMcp),
|
|
403
|
+
hostAssets: cleanAgentProfileHostAssets(value.hostAssets),
|
|
392
404
|
notifications: cleanAgentProfileNotifications(value.notifications),
|
|
393
405
|
relay: {
|
|
394
406
|
context: relay.context === undefined ? defaults.relay.context : cleanBoolean(relay.context, "relay.context"),
|
package/src/db/mappers.ts
CHANGED
|
@@ -277,6 +277,7 @@ export function rowToTask(row: any): Task {
|
|
|
277
277
|
return {
|
|
278
278
|
id: row.id,
|
|
279
279
|
source: row.source,
|
|
280
|
+
label: row.title,
|
|
280
281
|
title: row.title,
|
|
281
282
|
body: row.body,
|
|
282
283
|
severity: row.severity as TaskSeverity,
|
|
@@ -310,6 +311,8 @@ export function rowToTask(row: any): Task {
|
|
|
310
311
|
...(row.commits != null ? { commits: parseJson(row.commits, []) } : {}),
|
|
311
312
|
...(row.done_when != null ? { doneWhen: row.done_when } : {}),
|
|
312
313
|
...(row.team_id != null ? { teamId: row.team_id } : {}),
|
|
314
|
+
...(row.project_id != null ? { projectId: row.project_id } : {}),
|
|
315
|
+
...(row.repo != null ? { repo: row.repo } : {}),
|
|
313
316
|
...(row.created_by != null ? { createdBy: row.created_by } : {}),
|
|
314
317
|
...(row.owner_agent_id != null ? { ownerAgentId: row.owner_agent_id } : {}),
|
|
315
318
|
...(row.dispatch != null ? { dispatch: parseJson(row.dispatch, {}) } : {}),
|
package/src/db/migrations.ts
CHANGED
|
@@ -444,11 +444,11 @@ export function applyMigrations(): void {
|
|
|
444
444
|
created_by TEXT NOT NULL, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL
|
|
445
445
|
)
|
|
446
446
|
`);
|
|
447
|
-
const
|
|
448
|
-
const planColNames = planCols.map((c: any) => c.name);
|
|
447
|
+
const planColNames = (getDb().query("PRAGMA table_info(plans)").all() as any[]).map((c: any) => c.name);
|
|
449
448
|
if (!planColNames.includes("team_id")) {
|
|
450
449
|
getDb().run("ALTER TABLE plans ADD COLUMN team_id TEXT");
|
|
451
450
|
}
|
|
451
|
+
if (!planColNames.includes("archived")) getDb().run("ALTER TABLE plans ADD COLUMN archived INTEGER NOT NULL DEFAULT 0");
|
|
452
452
|
getDb().run("CREATE UNIQUE INDEX IF NOT EXISTS idx_plans_thread_channel ON plans(thread_id, coalesce(channel, ''))");
|
|
453
453
|
getDb().run("CREATE INDEX IF NOT EXISTS idx_plans_updated ON plans(updated_at)");
|
|
454
454
|
getDb().run("CREATE INDEX IF NOT EXISTS idx_plans_team ON plans(team_id)");
|
|
@@ -1049,6 +1049,8 @@ export function applyMigrations(): void {
|
|
|
1049
1049
|
addTaskCol("commits", "commits TEXT");
|
|
1050
1050
|
addTaskCol("done_when", "done_when TEXT");
|
|
1051
1051
|
addTaskCol("team_id", "team_id TEXT");
|
|
1052
|
+
addTaskCol("project_id", "project_id TEXT");
|
|
1053
|
+
addTaskCol("repo", "repo TEXT");
|
|
1052
1054
|
addTaskCol("created_by", "created_by TEXT");
|
|
1053
1055
|
addTaskCol("owner_agent_id", "owner_agent_id TEXT");
|
|
1054
1056
|
addTaskCol("dispatch", "dispatch TEXT");
|
|
@@ -1056,6 +1058,43 @@ export function applyMigrations(): void {
|
|
|
1056
1058
|
getDb().run("CREATE INDEX IF NOT EXISTS idx_tasks_kind ON tasks(kind, status, updated_at)");
|
|
1057
1059
|
getDb().run("CREATE INDEX IF NOT EXISTS idx_tasks_branch ON tasks(branch) WHERE branch IS NOT NULL");
|
|
1058
1060
|
getDb().run("CREATE INDEX IF NOT EXISTS idx_tasks_team ON tasks(team_id) WHERE team_id IS NOT NULL");
|
|
1061
|
+
getDb().run("CREATE INDEX IF NOT EXISTS idx_tasks_project ON tasks(project_id, status, updated_at) WHERE project_id IS NOT NULL");
|
|
1062
|
+
getDb().run("CREATE INDEX IF NOT EXISTS idx_tasks_repo ON tasks(repo, status, updated_at) WHERE repo IS NOT NULL");
|
|
1063
|
+
getDb().run(`
|
|
1064
|
+
UPDATE tasks
|
|
1065
|
+
SET repo = (
|
|
1066
|
+
SELECT ir.repo
|
|
1067
|
+
FROM issue_associations ia
|
|
1068
|
+
JOIN issue_refs ir ON ir.id = ia.issue_ref_id
|
|
1069
|
+
WHERE ia.entity_type = 'task' AND ia.entity_id = CAST(tasks.id AS TEXT)
|
|
1070
|
+
ORDER BY ia.created_at ASC, ir.id ASC
|
|
1071
|
+
LIMIT 1
|
|
1072
|
+
)
|
|
1073
|
+
WHERE kind = 'deliverable'
|
|
1074
|
+
AND (repo IS NULL OR trim(repo) = '')
|
|
1075
|
+
AND EXISTS (
|
|
1076
|
+
SELECT 1 FROM issue_associations ia
|
|
1077
|
+
JOIN issue_refs ir ON ir.id = ia.issue_ref_id
|
|
1078
|
+
WHERE ia.entity_type = 'task' AND ia.entity_id = CAST(tasks.id AS TEXT)
|
|
1079
|
+
)
|
|
1080
|
+
`);
|
|
1081
|
+
getDb().run(`
|
|
1082
|
+
UPDATE tasks
|
|
1083
|
+
SET project_id = (
|
|
1084
|
+
SELECT p.id
|
|
1085
|
+
FROM projects p
|
|
1086
|
+
WHERE p.scope = 'project' AND p.issue_repo = tasks.repo
|
|
1087
|
+
ORDER BY p.created_at ASC, p.id ASC
|
|
1088
|
+
LIMIT 1
|
|
1089
|
+
)
|
|
1090
|
+
WHERE kind = 'deliverable'
|
|
1091
|
+
AND (project_id IS NULL OR trim(project_id) = '')
|
|
1092
|
+
AND repo IS NOT NULL AND trim(repo) <> ''
|
|
1093
|
+
AND EXISTS (
|
|
1094
|
+
SELECT 1 FROM projects p
|
|
1095
|
+
WHERE p.scope = 'project' AND p.issue_repo = tasks.repo
|
|
1096
|
+
)
|
|
1097
|
+
`);
|
|
1059
1098
|
|
|
1060
1099
|
getDb().run(`
|
|
1061
1100
|
CREATE TABLE IF NOT EXISTS task_dependencies (
|
package/src/db/plans.ts
CHANGED
|
@@ -8,6 +8,7 @@ interface PlanRow {
|
|
|
8
8
|
thread_id: number;
|
|
9
9
|
team_id: string | null;
|
|
10
10
|
channel: string | null;
|
|
11
|
+
archived: number;
|
|
11
12
|
version: number;
|
|
12
13
|
title: string | null;
|
|
13
14
|
objective: string | null;
|
|
@@ -52,6 +53,7 @@ function rowToPlan(row: PlanRow): Plan {
|
|
|
52
53
|
threadId: String(row.thread_id),
|
|
53
54
|
...(row.team_id ? { teamId: row.team_id } : {}),
|
|
54
55
|
...(row.channel ? { channel: row.channel } : {}),
|
|
56
|
+
archived: Boolean(row.archived),
|
|
55
57
|
version: row.version,
|
|
56
58
|
...(row.title ? { title: row.title } : {}),
|
|
57
59
|
...(row.objective ? { objective: row.objective } : {}),
|
|
@@ -189,6 +191,15 @@ export function replacePlan(input: Plan, changedBy?: string): Plan {
|
|
|
189
191
|
return getPlan(input.id)!;
|
|
190
192
|
}
|
|
191
193
|
|
|
194
|
+
export function setPlanArchived(id: string, archived: boolean): Plan {
|
|
195
|
+
const existing = getPlan(id);
|
|
196
|
+
if (!existing) throw new ValidationError(`plan ${id} not found`);
|
|
197
|
+
if (existing.archived === archived) return existing;
|
|
198
|
+
const now = Date.now();
|
|
199
|
+
getDb().query("UPDATE plans SET archived = ?, updated_at = ? WHERE id = ?").run(archived ? 1 : 0, now, id);
|
|
200
|
+
return getPlan(id)!;
|
|
201
|
+
}
|
|
202
|
+
|
|
192
203
|
export function deletePlan(id: string, changedBy?: string): boolean {
|
|
193
204
|
const existing = getPlan(id);
|
|
194
205
|
if (!existing) return false;
|
package/src/db/schema.ts
CHANGED
|
@@ -304,7 +304,7 @@ export function initDb(path: string = "agent-relay.db"): Database {
|
|
|
304
304
|
CREATE INDEX IF NOT EXISTS idx_project_edges_to ON project_edges(to_project, kind);
|
|
305
305
|
CREATE TABLE IF NOT EXISTS plans (
|
|
306
306
|
id TEXT PRIMARY KEY, thread_id INTEGER NOT NULL, team_id TEXT, channel TEXT, version INTEGER NOT NULL DEFAULT 1,
|
|
307
|
-
title TEXT, objective TEXT, nodes TEXT NOT NULL DEFAULT '[]', edges TEXT NOT NULL DEFAULT '[]',
|
|
307
|
+
archived INTEGER NOT NULL DEFAULT 0, title TEXT, objective TEXT, nodes TEXT NOT NULL DEFAULT '[]', edges TEXT NOT NULL DEFAULT '[]',
|
|
308
308
|
created_by TEXT NOT NULL, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL
|
|
309
309
|
);
|
|
310
310
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_plans_thread_channel ON plans(thread_id, coalesce(channel, ''));
|
|
@@ -657,6 +657,8 @@ export function initDb(path: string = "agent-relay.db"): Database {
|
|
|
657
657
|
commits TEXT,
|
|
658
658
|
done_when TEXT,
|
|
659
659
|
team_id TEXT,
|
|
660
|
+
project_id TEXT,
|
|
661
|
+
repo TEXT,
|
|
660
662
|
created_by TEXT,
|
|
661
663
|
owner_agent_id TEXT,
|
|
662
664
|
dispatch TEXT,
|
package/src/db/task-entity.ts
CHANGED
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
type TaskGateState,
|
|
29
29
|
type TaskGateStatus,
|
|
30
30
|
type TaskIssueLink,
|
|
31
|
+
type TaskListItem,
|
|
31
32
|
type TaskStatus,
|
|
32
33
|
} from "../types";
|
|
33
34
|
import { parseJson } from "../utils";
|
|
@@ -38,6 +39,7 @@ import { latestTaskDispatchDecision, taskBoundWorkerModel, taskDispatchWorkShape
|
|
|
38
39
|
import { TASK_SELECT, insertTaskEvent, DELIVERABLE_TASK_KIND } from "./task-core.ts";
|
|
39
40
|
import { associateIssue, listIssuesForEntity, removeAssociation } from "./issue-associations.ts";
|
|
40
41
|
import { resolveIssueRefFromString } from "./issue-refs.ts";
|
|
42
|
+
import { getProject } from "./projects.ts";
|
|
41
43
|
|
|
42
44
|
// Execution-status axis (#834): the existing claim FSM vocabulary, reused so there is no
|
|
43
45
|
// enum break. not-started=open, working=in_progress, plus claimed/blocked/done/failed/canceled.
|
|
@@ -100,6 +102,38 @@ function cleanDispatch(value: unknown): TaskDispatch | undefined {
|
|
|
100
102
|
return out;
|
|
101
103
|
}
|
|
102
104
|
|
|
105
|
+
function projectForRepo(repo: string | undefined): string | undefined {
|
|
106
|
+
const cleaned = repo?.trim();
|
|
107
|
+
if (!cleaned) return undefined;
|
|
108
|
+
const row = getDb()
|
|
109
|
+
.query("SELECT id FROM projects WHERE scope = 'project' AND issue_repo = ? ORDER BY created_at ASC, id ASC LIMIT 1")
|
|
110
|
+
.get(cleaned) as { id: string } | undefined;
|
|
111
|
+
return row?.id;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function cleanTaskProject(input: { projectId?: unknown; repo?: unknown }): { projectId?: string; repo?: string } {
|
|
115
|
+
const projectId = optionalText(input.projectId, "projectId", 120);
|
|
116
|
+
const project = projectId ? getProject(projectId) : null;
|
|
117
|
+
const repo = optionalText(input.repo, "repo", 500) ?? project?.issueRepo;
|
|
118
|
+
return {
|
|
119
|
+
...(projectId ? { projectId } : repo ? { projectId: projectForRepo(repo) } : {}),
|
|
120
|
+
...(repo ? { repo } : {}),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function fillTaskProjectFromRepo(id: number, repo: string, now = Date.now()): void {
|
|
125
|
+
const cleaned = optionalText(repo, "repo", 500);
|
|
126
|
+
if (!cleaned) return;
|
|
127
|
+
const projectId = projectForRepo(cleaned);
|
|
128
|
+
getDb().query(`
|
|
129
|
+
UPDATE tasks
|
|
130
|
+
SET repo = COALESCE(NULLIF(trim(repo), ''), ?),
|
|
131
|
+
project_id = COALESCE(NULLIF(trim(project_id), ''), ?),
|
|
132
|
+
updated_at = ?
|
|
133
|
+
WHERE id = ?
|
|
134
|
+
`).run(cleaned, projectId ?? null, now, id);
|
|
135
|
+
}
|
|
136
|
+
|
|
103
137
|
function getRawTask(id: number): Task | null {
|
|
104
138
|
const row = getDb().query(`${TASK_SELECT} WHERE id = ?`).get(id) as any;
|
|
105
139
|
return row ? rowToTask(row) : null;
|
|
@@ -119,6 +153,8 @@ export interface CreateDeliverableTaskInput {
|
|
|
119
153
|
context?: string;
|
|
120
154
|
doneWhen?: string;
|
|
121
155
|
teamId?: string;
|
|
156
|
+
projectId?: string;
|
|
157
|
+
repo?: string;
|
|
122
158
|
createdBy: string;
|
|
123
159
|
source?: string;
|
|
124
160
|
target?: string;
|
|
@@ -134,13 +170,14 @@ export function createDeliverableTask(input: CreateDeliverableTaskInput): TaskDe
|
|
|
134
170
|
const dispatch = cleanDispatch(input.dispatch);
|
|
135
171
|
const complexity = cleanTaskComplexity(input.complexity ?? dispatch?.complexity, "complexity") ?? DEFAULT_TASK_COMPLEXITY;
|
|
136
172
|
const context = optionalText(input.context, "context", 1_000_000);
|
|
173
|
+
const taskProject = cleanTaskProject(input);
|
|
137
174
|
// Legacy NOT-NULL columns have no DB defaults (#834 finding 6) — set them explicitly.
|
|
138
175
|
const result = getDb().query(`
|
|
139
176
|
INSERT INTO tasks (
|
|
140
177
|
source, title, body, severity, status, target, occurrence_count, metadata,
|
|
141
178
|
created_at, updated_at, last_seen_at,
|
|
142
|
-
kind, intent, complexity, context, latest_steer, deliverable_stage, done_when, team_id, created_by, dispatch
|
|
143
|
-
) VALUES (?, ?, ?, 'info', 'open', ?, 1, '{}', ?, ?, ?, '${DELIVERABLE_TASK_KIND}', ?, ?, ?, ?, 'none', ?, ?, ?, ?)
|
|
179
|
+
kind, intent, complexity, context, latest_steer, deliverable_stage, done_when, team_id, project_id, repo, created_by, dispatch
|
|
180
|
+
) VALUES (?, ?, ?, 'info', 'open', ?, 1, '{}', ?, ?, ?, '${DELIVERABLE_TASK_KIND}', ?, ?, ?, ?, 'none', ?, ?, ?, ?, ?, ?)
|
|
144
181
|
`).run(
|
|
145
182
|
optionalText(input.source, "source", 200) ?? "coordinator",
|
|
146
183
|
title,
|
|
@@ -153,6 +190,8 @@ export function createDeliverableTask(input: CreateDeliverableTaskInput): TaskDe
|
|
|
153
190
|
optionalText(input.latestSteer, "latestSteer", 1_000_000) ?? null,
|
|
154
191
|
optionalText(input.doneWhen, "doneWhen", 1_000_000) ?? null,
|
|
155
192
|
optionalText(input.teamId, "teamId", 200) ?? null,
|
|
193
|
+
taskProject.projectId ?? null,
|
|
194
|
+
taskProject.repo ?? null,
|
|
156
195
|
createdBy,
|
|
157
196
|
dispatch ? JSON.stringify(dispatch) : null,
|
|
158
197
|
);
|
|
@@ -163,7 +202,7 @@ export function createDeliverableTask(input: CreateDeliverableTaskInput): TaskDe
|
|
|
163
202
|
severity: "info",
|
|
164
203
|
title: `Task created: ${title}`,
|
|
165
204
|
body: intent,
|
|
166
|
-
metadata: { createdBy, complexity, ...(context ? { context } : {}), ...(dispatch ? { dispatch } : {}) },
|
|
205
|
+
metadata: { createdBy, complexity, ...(context ? { context } : {}), ...(taskProject.projectId ? { projectId: taskProject.projectId } : {}), ...(taskProject.repo ? { repo: taskProject.repo } : {}), ...(dispatch ? { dispatch } : {}) },
|
|
167
206
|
}, now);
|
|
168
207
|
return getTaskDetail(id)!;
|
|
169
208
|
}
|
|
@@ -177,6 +216,8 @@ export interface UpdateDeliverableTaskInput {
|
|
|
177
216
|
doneWhen?: string;
|
|
178
217
|
dispatch?: TaskDispatch;
|
|
179
218
|
teamId?: string;
|
|
219
|
+
projectId?: string;
|
|
220
|
+
repo?: string;
|
|
180
221
|
actor?: string;
|
|
181
222
|
}
|
|
182
223
|
|
|
@@ -192,6 +233,11 @@ export function updateDeliverableTask(id: number, input: UpdateDeliverableTaskIn
|
|
|
192
233
|
if (input.latestSteer !== undefined) push("latest_steer", optionalText(input.latestSteer, "latestSteer", 1_000_000) ?? null);
|
|
193
234
|
if (input.doneWhen !== undefined) push("done_when", optionalText(input.doneWhen, "doneWhen", 1_000_000) ?? null);
|
|
194
235
|
if (input.teamId !== undefined) push("team_id", optionalText(input.teamId, "teamId", 200) ?? null);
|
|
236
|
+
if (input.projectId !== undefined || input.repo !== undefined) {
|
|
237
|
+
const taskProject = cleanTaskProject(input);
|
|
238
|
+
if (input.projectId !== undefined) push("project_id", taskProject.projectId ?? null);
|
|
239
|
+
if (input.repo !== undefined) push("repo", taskProject.repo ?? null);
|
|
240
|
+
}
|
|
195
241
|
if (input.dispatch !== undefined) { const d = cleanDispatch(input.dispatch); push("dispatch", d ? JSON.stringify(d) : null); }
|
|
196
242
|
const now = Date.now();
|
|
197
243
|
if (sets.length > 0) {
|
|
@@ -424,6 +470,7 @@ export function addTaskRef(id: number, input: AddTaskRefInput): TaskDetail {
|
|
|
424
470
|
if (input.issue) {
|
|
425
471
|
const ref = resolveIssueRefFromString({ ref: input.issue, defaultRepo: optionalText(input.defaultRepo, "defaultRepo", 500) });
|
|
426
472
|
associateIssue(ref.id, "task", taskEntityId(String(id)));
|
|
473
|
+
fillTaskProjectFromRepo(id, ref.repo, now);
|
|
427
474
|
insertTaskEvent(id, {
|
|
428
475
|
source: input.actor ?? "agent-relay",
|
|
429
476
|
type: "ref.issue.added",
|
|
@@ -619,6 +666,8 @@ export interface ListDeliverableTasksFilter {
|
|
|
619
666
|
teamId?: string;
|
|
620
667
|
createdBy?: string;
|
|
621
668
|
ownerAgentId?: string;
|
|
669
|
+
projectId?: string;
|
|
670
|
+
repo?: string;
|
|
622
671
|
limit?: number;
|
|
623
672
|
}
|
|
624
673
|
|
|
@@ -630,11 +679,37 @@ export function listDeliverableTasks(filter: ListDeliverableTasksFilter = {}): T
|
|
|
630
679
|
if (filter.teamId) { conditions.push("team_id = ?"); params.push(filter.teamId); }
|
|
631
680
|
if (filter.createdBy) { conditions.push("created_by = ?"); params.push(filter.createdBy); }
|
|
632
681
|
if (filter.ownerAgentId) { conditions.push("owner_agent_id = ?"); params.push(filter.ownerAgentId); }
|
|
682
|
+
if (filter.projectId) { conditions.push("project_id = ?"); params.push(filter.projectId); }
|
|
683
|
+
if (filter.repo) { conditions.push("repo = ?"); params.push(filter.repo); }
|
|
633
684
|
const limit = Math.min(Math.max(filter.limit ?? 100, 1), 500);
|
|
634
685
|
params.push(limit);
|
|
635
686
|
return (getDb().query(`${TASK_SELECT} WHERE ${conditions.join(" AND ")} ORDER BY updated_at DESC LIMIT ?`).all(...params as any[]) as any[]).map(rowToTask);
|
|
636
687
|
}
|
|
637
688
|
|
|
689
|
+
function taskListItem(task: Task): TaskListItem {
|
|
690
|
+
return {
|
|
691
|
+
id: task.id,
|
|
692
|
+
label: task.title,
|
|
693
|
+
title: task.title,
|
|
694
|
+
status: task.status,
|
|
695
|
+
kind: task.kind,
|
|
696
|
+
...(task.deliverableStage ? { deliverableStage: task.deliverableStage } : {}),
|
|
697
|
+
...(task.teamId ? { teamId: task.teamId } : {}),
|
|
698
|
+
...(task.projectId ? { projectId: task.projectId } : {}),
|
|
699
|
+
...(task.repo ? { repo: task.repo } : {}),
|
|
700
|
+
...(task.createdBy ? { createdBy: task.createdBy } : {}),
|
|
701
|
+
...(task.ownerAgentId ? { ownerAgentId: task.ownerAgentId } : {}),
|
|
702
|
+
...(task.branch ? { branch: task.branch } : {}),
|
|
703
|
+
createdAt: task.createdAt,
|
|
704
|
+
updatedAt: task.updatedAt,
|
|
705
|
+
...(task.reapedAt ? { reapedAt: task.reapedAt } : {}),
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
export function listDeliverableTaskSummaries(filter: ListDeliverableTasksFilter = {}): TaskListItem[] {
|
|
710
|
+
return listDeliverableTasks(filter).map(taskListItem);
|
|
711
|
+
}
|
|
712
|
+
|
|
638
713
|
// Task-aware issue resolution for the land→close bridge (#833/#834 finding 4). Deliverable
|
|
639
714
|
// Tasks own `branch`, so a landed branch resolves to its Task(s). The land WIRING is #831;
|
|
640
715
|
// this is the resolution path + entity-type the bridge needs to be ABLE to fire. The issue
|
package/src/mcp/index.ts
CHANGED
|
@@ -54,7 +54,7 @@ export const RELAY_TOOL_GROUPS: Record<string, readonly string[]> = {
|
|
|
54
54
|
artifacts: ["relay_upload_artifact", "relay_attach_artifact"],
|
|
55
55
|
workspace: ["relay_workspace_status", "relay_workspace_ready", "relay_workspace_deps", "relay_workspace_list", "relay_workspace_claim", "relay_workspace_release", "relay_workspace_land", "relay_merge_pause", "relay_merge_pauses", "relay_merge_renew", "relay_merge_resume"],
|
|
56
56
|
"spawn-lifecycle": ["relay_spawn_agent", "relay_spawn_targets", "relay_my_agents", "relay_shutdown_agent", "relay_agent_action", "relay_agent_status", "relay_agent_crash_report", "relay_schedule_timer", "relay_list_timers", "relay_cancel_timer"],
|
|
57
|
-
plans: ["relay_plan_create", "relay_plan_add_node", "relay_plan_update_node", "relay_plan_add_edge", "relay_plan_remove_node", "relay_plan_remove_edge", "relay_plan_delete", "relay_plan_get"],
|
|
57
|
+
plans: ["relay_plan_create", "relay_plan_add_node", "relay_plan_update_node", "relay_plan_add_edge", "relay_plan_remove_node", "relay_plan_remove_edge", "relay_plan_archive", "relay_plan_delete", "relay_plan_get"],
|
|
58
58
|
tasks: ["relay_task_create", "relay_task_get", "relay_task_list", "relay_task_update", "relay_task_set_status", "relay_task_set_stage", "relay_task_record_gate", "relay_task_add_dependency", "relay_task_remove_dependency", "relay_task_add_ref", "relay_task_remove_ref", "relay_task_assign_agent", "relay_task_history", "relay_task_memory_read", "relay_task_memory_replace_working_state", "relay_task_memory_append_ruled_out", "relay_task_memory_append_decision", "relay_task_dispatch", "relay_task_comment", "relay_task_close_bridge", "relay_release_readiness"],
|
|
59
59
|
teams: ["relay_team_get", "relay_team_set_brief", "relay_team_claim", "relay_team_dissolve"],
|
|
60
60
|
blackboard: ["relay_blackboard_write", "relay_blackboard_get", "relay_blackboard_read", "relay_blackboard_search", "relay_blackboard_retract"],
|
package/src/mcp-plan-tools.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { authContextFromMcp } from "./services/auth-context";
|
|
|
4
4
|
import {
|
|
5
5
|
addPlanEdge,
|
|
6
6
|
addPlanNode,
|
|
7
|
+
archivePlanGraph,
|
|
7
8
|
bindPlanNodeToTask,
|
|
8
9
|
createPlan,
|
|
9
10
|
deletePlanGraph,
|
|
@@ -98,6 +99,7 @@ export const PLAN_TOOLS = [
|
|
|
98
99
|
{ name: "relay_plan_add_edge", description: "Add a dependency edge to a plan DAG; rejects cycles.", requiredScopes: ["plans:write"], inputSchema: { type: "object", properties: { planId: { type: "string" }, from: { type: "string" }, to: { type: "string" } }, required: ["planId", "from", "to"], additionalProperties: false } },
|
|
99
100
|
{ name: "relay_plan_remove_node", description: "Remove a node and its connected edges from a plan DAG.", requiredScopes: ["plans:write"], inputSchema: { type: "object", properties: { planId: { type: "string" }, nodeId: { type: "string" } }, required: ["planId", "nodeId"], additionalProperties: false } },
|
|
100
101
|
{ name: "relay_plan_remove_edge", description: "Remove a dependency edge from a plan DAG.", requiredScopes: ["plans:write"], inputSchema: { type: "object", properties: { planId: { type: "string" }, from: { type: "string" }, to: { type: "string" } }, required: ["planId", "from", "to"], additionalProperties: false } },
|
|
102
|
+
{ name: "relay_plan_archive", description: "Soft-archive or unarchive a persisted plan DAG without deleting its history.", requiredScopes: ["plans:write"], inputSchema: { type: "object", properties: { planId: { type: "string" }, archived: { type: "boolean", default: true } }, required: ["planId"], additionalProperties: false } },
|
|
101
103
|
{ name: "relay_plan_delete", description: "Delete a persisted plan DAG and archive its latest version.", requiredScopes: ["plans:write"], inputSchema: { type: "object", properties: { planId: { type: "string" } }, required: ["planId"], additionalProperties: false } },
|
|
102
104
|
{
|
|
103
105
|
name: "relay_plan_bind_node",
|
|
@@ -135,6 +137,7 @@ export function relayPlanTool(auth: McpAuthLike, name: string, args: Record<stri
|
|
|
135
137
|
if (name === "relay_plan_add_edge") return addPlanEdge(stringArg(args.planId, "planId"), { from: args.from, to: args.to }, ctx) as unknown as Record<string, unknown>;
|
|
136
138
|
if (name === "relay_plan_remove_node") return removePlanNode(stringArg(args.planId, "planId"), stringArg(args.nodeId, "nodeId"), ctx) as unknown as Record<string, unknown>;
|
|
137
139
|
if (name === "relay_plan_remove_edge") return removePlanEdge(stringArg(args.planId, "planId"), { from: args.from, to: args.to }, ctx) as unknown as Record<string, unknown>;
|
|
140
|
+
if (name === "relay_plan_archive") return archivePlanGraph(stringArg(args.planId, "planId"), args.archived !== false, ctx) as unknown as Record<string, unknown>;
|
|
138
141
|
if (name === "relay_plan_delete") return deletePlanGraph(stringArg(args.planId, "planId"), ctx);
|
|
139
142
|
if (name === "relay_plan_bind_node") {
|
|
140
143
|
return bindPlanNodeToTask(
|