agent-relay-server 0.61.2 → 0.62.1
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 +479 -1
- package/package.json +2 -2
- package/public/assets/{MessageBubble-DVvlvi4B.js → MessageBubble-DRrxfjyT.js} +2 -2
- package/public/assets/{MessageBubble-DVvlvi4B.js.map → MessageBubble-DRrxfjyT.js.map} +1 -1
- package/public/assets/PlanGraphPanel-Dp2eYazz.js +2 -0
- package/public/assets/PlanGraphPanel-Dp2eYazz.js.map +1 -0
- package/public/assets/{activity-IYl3MEXC.js → activity--t0U2yzq.js} +2 -2
- package/public/assets/{activity-IYl3MEXC.js.map → activity--t0U2yzq.js.map} +1 -1
- package/public/assets/{agent-profiles-BfGTTbeY.js → agent-profiles-BMtJYzxn.js} +2 -2
- package/public/assets/{agent-profiles-BfGTTbeY.js.map → agent-profiles-BMtJYzxn.js.map} +1 -1
- package/public/assets/{agents-BquvREQr.js → agents-Ck9AePlc.js} +2 -2
- package/public/assets/{agents-BquvREQr.js.map → agents-Ck9AePlc.js.map} +1 -1
- package/public/assets/{analytics-De0cEZGx.js → analytics-CzfK387L.js} +3 -3
- package/public/assets/{analytics-De0cEZGx.js.map → analytics-CzfK387L.js.map} +1 -1
- package/public/assets/automation-Cgw8vtE8.js +2 -0
- package/public/assets/automation-Cgw8vtE8.js.map +1 -0
- package/public/assets/{badge-DhLyCPag.js → badge-CZhIdlTJ.js} +2 -2
- package/public/assets/{badge-DhLyCPag.js.map → badge-CZhIdlTJ.js.map} +1 -1
- package/public/assets/{branch-state-badge-DSyWKluD.js → branch-state-badge-DnEEPtOP.js} +2 -2
- package/public/assets/{branch-state-badge-DSyWKluD.js.map → branch-state-badge-DnEEPtOP.js.map} +1 -1
- package/public/assets/{button-K20wAZG1.js → button-2taQeNHW.js} +2 -2
- package/public/assets/{button-K20wAZG1.js.map → button-2taQeNHW.js.map} +1 -1
- package/public/assets/card-DzIsAI41.js +2 -0
- package/public/assets/{card-CcZ9hLI7.js.map → card-DzIsAI41.js.map} +1 -1
- package/public/assets/channels-Dc3L2MQs.js +2 -0
- package/public/assets/{channels-BG0Nib7y.js.map → channels-Dc3L2MQs.js.map} +1 -1
- package/public/assets/chat-Fc6iJ7sa.js +2 -0
- package/public/assets/{chat-Dx6qWNxb.js.map → chat-Fc6iJ7sa.js.map} +1 -1
- package/public/assets/{connectors-DWh7MwV_.js → connectors-Dc2ttGWs.js} +2 -2
- package/public/assets/{connectors-DWh7MwV_.js.map → connectors-Dc2ttGWs.js.map} +1 -1
- package/public/assets/copy-button-HEkwi6xX.js +2 -0
- package/public/assets/{copy-button-eF3cKp4Q.js.map → copy-button-HEkwi6xX.js.map} +1 -1
- package/public/assets/display-DzNvOu1j.js.map +1 -1
- package/public/assets/dist-1rXN5FN9.js +2 -0
- package/public/assets/{dist-YpHfnnIA.js.map → dist-1rXN5FN9.js.map} +1 -1
- package/public/assets/{dist-1dhfdhm9.js → dist-AbEQuFoK.js} +2 -2
- package/public/assets/{dist-1dhfdhm9.js.map → dist-AbEQuFoK.js.map} +1 -1
- package/public/assets/{dist-COQuunEv.js → dist-B7p1zkvf.js} +2 -2
- package/public/assets/{dist-COQuunEv.js.map → dist-B7p1zkvf.js.map} +1 -1
- package/public/assets/{dist-Ci5Y37nj.js → dist-CG10hM1-.js} +2 -2
- package/public/assets/{dist-Ci5Y37nj.js.map → dist-CG10hM1-.js.map} +1 -1
- package/public/assets/dist-Cfm5_zPB.js +2 -0
- package/public/assets/{dist-qd198ib9.js.map → dist-Cfm5_zPB.js.map} +1 -1
- package/public/assets/{dist-BrQsqBbc.js → dist-Cgw85UcE.js} +2 -2
- package/public/assets/{dist-BrQsqBbc.js.map → dist-Cgw85UcE.js.map} +1 -1
- package/public/assets/{dist-CoROafNg.js → dist-D3_TJKTC.js} +2 -2
- package/public/assets/{dist-CoROafNg.js.map → dist-D3_TJKTC.js.map} +1 -1
- package/public/assets/{dist-I-MFUv51.js → dist-DQkLq0hs.js} +2 -2
- package/public/assets/{dist-I-MFUv51.js.map → dist-DQkLq0hs.js.map} +1 -1
- package/public/assets/{dist-UQGKwqZH.js → dist-D_sl7u_f.js} +2 -2
- package/public/assets/{dist-UQGKwqZH.js.map → dist-D_sl7u_f.js.map} +1 -1
- package/public/assets/{dist-yGOq6sB3.js → dist-Ilb3r1im.js} +2 -2
- package/public/assets/{dist-yGOq6sB3.js.map → dist-Ilb3r1im.js.map} +1 -1
- package/public/assets/dist-Pf_3Gf38.js +2 -0
- package/public/assets/{dist-BLBF7qqr.js.map → dist-Pf_3Gf38.js.map} +1 -1
- package/public/assets/{es2015-CRxz9lOj.js → es2015-GSepNaIS.js} +2 -2
- package/public/assets/{es2015-CRxz9lOj.js.map → es2015-GSepNaIS.js.map} +1 -1
- package/public/assets/{formatted-body-impl-DMm6jeqi.js → formatted-body-impl-Bg-gx7rG.js} +2 -2
- package/public/assets/{formatted-body-impl-DMm6jeqi.js.map → formatted-body-impl-Bg-gx7rG.js.map} +1 -1
- package/public/assets/index-BeDncjN4.js +22 -0
- package/public/assets/index-BeDncjN4.js.map +1 -0
- package/public/assets/index-DiR-fqy_.css +2 -0
- package/public/assets/{input-BHYNpXAG.js → input-BsNqlONJ.js} +2 -2
- package/public/assets/{input-BHYNpXAG.js.map → input-BsNqlONJ.js.map} +1 -1
- package/public/assets/{insights-C7wKt1_l.js → insights-c08aW-7n.js} +2 -2
- package/public/assets/{insights-C7wKt1_l.js.map → insights-c08aW-7n.js.map} +1 -1
- package/public/assets/{integrations-CG9qyHUf.js → integrations-BwpgcqnF.js} +2 -2
- package/public/assets/{integrations-CG9qyHUf.js.map → integrations-BwpgcqnF.js.map} +1 -1
- package/public/assets/{lib-Df4aph5u.js → lib-bXPu6xCL.js} +2 -2
- package/public/assets/{lib-Df4aph5u.js.map → lib-bXPu6xCL.js.map} +1 -1
- package/public/assets/lucide-react-DWYjU-AF.js +9 -0
- package/public/assets/{lucide-react-L2UxFDic.js.map → lucide-react-DWYjU-AF.js.map} +1 -1
- package/public/assets/{maintenance-CjpvnV_U.js → maintenance-CTKP0kWB.js} +2 -2
- package/public/assets/{maintenance-CjpvnV_U.js.map → maintenance-CTKP0kWB.js.map} +1 -1
- package/public/assets/{managed-agents-CdfuBKJA.js → managed-agents-BofCxo8d.js} +2 -2
- package/public/assets/{managed-agents-CdfuBKJA.js.map → managed-agents-BofCxo8d.js.map} +1 -1
- package/public/assets/{markdown-preview-impl-Cf4xCmWt.js → markdown-preview-impl-CQ-6BIb8.js} +2 -2
- package/public/assets/{markdown-preview-impl-Cf4xCmWt.js.map → markdown-preview-impl-CQ-6BIb8.js.map} +1 -1
- package/public/assets/memory-DvfXjMoi.js +2 -0
- package/public/assets/{memory-xKWduhyr.js.map → memory-DvfXjMoi.js.map} +1 -1
- package/public/assets/{messages-BpZYOUtJ.js → messages-C9VsPzZ_.js} +2 -2
- package/public/assets/{messages-BpZYOUtJ.js.map → messages-C9VsPzZ_.js.map} +1 -1
- package/public/assets/orchestrators-MffVueeE.js +2 -0
- package/public/assets/{orchestrators-BrJvGIOc.js.map → orchestrators-MffVueeE.js.map} +1 -1
- package/public/assets/{overview-BUORwePi.js → overview-BSVajefM.js} +2 -2
- package/public/assets/{overview-BUORwePi.js.map → overview-BSVajefM.js.map} +1 -1
- package/public/assets/pairs-B5711-Cs.js +2 -0
- package/public/assets/{pairs-DR0GizDN.js.map → pairs-B5711-Cs.js.map} +1 -1
- package/public/assets/{react-dom-CrP_AtcL.js → react-dom-CPghDLpB.js} +2 -2
- package/public/assets/{react-dom-CrP_AtcL.js.map → react-dom-CPghDLpB.js.map} +1 -1
- package/public/assets/{security-DLwSqTc_.js → security-DkUwK9Vj.js} +2 -2
- package/public/assets/{security-DLwSqTc_.js.map → security-DkUwK9Vj.js.map} +1 -1
- package/public/assets/{select-pEru111r.js → select-DrM2RRiT.js} +2 -2
- package/public/assets/{select-pEru111r.js.map → select-DrM2RRiT.js.map} +1 -1
- package/public/assets/{settings-B-ueGvwV.js → settings-DdKCb-vy.js} +3 -3
- package/public/assets/{settings-B-ueGvwV.js.map → settings-DdKCb-vy.js.map} +1 -1
- package/public/assets/store-iTiheuTD.js +9 -0
- package/public/assets/store-iTiheuTD.js.map +1 -0
- package/public/assets/tasks-e1y1PFfO.js +2 -0
- package/public/assets/{tasks-C2rRQm6E.js.map → tasks-e1y1PFfO.js.map} +1 -1
- package/public/assets/teams-BUcxoGC_.js +2 -0
- package/public/assets/teams-BUcxoGC_.js.map +1 -0
- package/public/assets/{terminal-viewer-impl-BM_3qoeN.js → terminal-viewer-impl-CmZjQOB2.js} +3 -3
- package/public/assets/{terminal-viewer-impl-BM_3qoeN.js.map → terminal-viewer-impl-CmZjQOB2.js.map} +1 -1
- package/public/assets/{use-keyboard-viewport-DrRH_QzG.js → use-keyboard-viewport-NKw1O5gC.js} +2 -2
- package/public/assets/{use-keyboard-viewport-DrRH_QzG.js.map → use-keyboard-viewport-NKw1O5gC.js.map} +1 -1
- package/public/assets/{work-queue-CkIYDAZD.js → work-queue-CXBQx43n.js} +2 -2
- package/public/assets/{work-queue-CkIYDAZD.js.map → work-queue-CXBQx43n.js.map} +1 -1
- package/public/assets/{workspaces-CYm5bLg9.js → workspaces-D2EfZh9a.js} +3 -3
- package/public/assets/{workspaces-CYm5bLg9.js.map → workspaces-D2EfZh9a.js.map} +1 -1
- package/public/index.html +21 -21
- package/src/automations.ts +40 -7
- package/src/branch-landed.ts +5 -0
- package/src/bus.ts +6 -1
- package/src/cli/workspace.ts +9 -17
- package/src/db/activity.ts +191 -3
- package/src/db/agents.ts +14 -5
- package/src/db/integrations.ts +1 -0
- package/src/db/issue-associations.ts +128 -0
- package/src/db/issue-lifecycle.ts +187 -0
- package/src/db/issue-refs.ts +254 -0
- package/src/db/mappers.ts +2 -0
- package/src/db/migrations.ts +53 -0
- package/src/db/schema.ts +3 -2
- package/src/issue-tracker/adapter.ts +24 -0
- package/src/issue-tracker/github.ts +136 -0
- package/src/issue-tracker/index.ts +13 -0
- package/src/issue-tracker/registry.ts +26 -0
- package/src/maintenance.ts +22 -169
- package/src/mcp-issue-activity-tools.ts +114 -0
- package/src/mcp-issue-association-tools.ts +130 -0
- package/src/mcp-issue-lifecycle-tools.ts +55 -0
- package/src/mcp-issue-ref-tools.ts +174 -0
- package/src/mcp-team-tools.ts +1 -1
- package/src/mcp.ts +3 -3
- package/src/provider-config-store.ts +30 -8
- package/src/routes/activity.ts +6 -1
- package/src/routes/commands.ts +29 -0
- package/src/routes/index.ts +19 -0
- package/src/routes/issue-activity.ts +78 -0
- package/src/routes/issue-associations.ts +76 -0
- package/src/routes/issue-lifecycle.ts +34 -0
- package/src/routes/issue-refs.ts +137 -0
- package/src/routes/orchestrator-bootstrap.ts +2 -1
- package/src/routes/orchestrator.ts +42 -1
- package/src/routes/tokens.ts +1 -1
- package/src/runtime-tokens.ts +15 -11
- package/src/security.ts +51 -3
- package/src/services/issue-activity.ts +30 -0
- package/src/services/issue-associations.ts +72 -0
- package/src/services/issue-lifecycle.ts +308 -0
- package/src/services/issue-refs.ts +32 -0
- package/src/services/plan-graph.ts +40 -2
- package/src/services/team.ts +4 -0
- package/src/token-db.ts +27 -7
- package/src/workspace-actions.ts +8 -1
- package/src/workspace-auto-merge.ts +228 -0
- package/src/workspace-probe.ts +125 -0
- package/public/assets/PlanGraphPanel-Dby7wq9c.js +0 -2
- package/public/assets/PlanGraphPanel-Dby7wq9c.js.map +0 -1
- package/public/assets/automation-Ddbermbi.js +0 -2
- package/public/assets/automation-Ddbermbi.js.map +0 -1
- package/public/assets/card-CcZ9hLI7.js +0 -2
- package/public/assets/channels-BG0Nib7y.js +0 -2
- package/public/assets/chat-Dx6qWNxb.js +0 -2
- package/public/assets/copy-button-eF3cKp4Q.js +0 -2
- package/public/assets/dist-BLBF7qqr.js +0 -2
- package/public/assets/dist-YpHfnnIA.js +0 -2
- package/public/assets/dist-qd198ib9.js +0 -2
- package/public/assets/index-B3wOeAPO.js +0 -21
- package/public/assets/index-B3wOeAPO.js.map +0 -1
- package/public/assets/index-Dn_GDXbi.css +0 -2
- package/public/assets/lucide-react-L2UxFDic.js +0 -9
- package/public/assets/memory-xKWduhyr.js +0 -2
- package/public/assets/orchestrators-BrJvGIOc.js +0 -2
- package/public/assets/pairs-DR0GizDN.js +0 -2
- package/public/assets/store-B8xP6z6T.js +0 -9
- package/public/assets/store-B8xP6z6T.js.map +0 -1
- package/public/assets/tasks-C2rRQm6E.js +0 -2
- package/public/assets/teams-Bo0sdBTp.js +0 -2
- package/public/assets/teams-Bo0sdBTp.js.map +0 -1
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { getDb, ValidationError } from "./connection.ts";
|
|
2
|
+
|
|
3
|
+
export const ISSUE_LIFECYCLE_ACTIONS = ["close", "reopen"] as const;
|
|
4
|
+
export const ISSUE_LIFECYCLE_STATUSES = ["succeeded", "failed"] as const;
|
|
5
|
+
export type IssueLifecycleAction = (typeof ISSUE_LIFECYCLE_ACTIONS)[number];
|
|
6
|
+
export type IssueLifecycleStatus = (typeof ISSUE_LIFECYCLE_STATUSES)[number];
|
|
7
|
+
|
|
8
|
+
export interface IssueLifecycleActionRecord {
|
|
9
|
+
id: number;
|
|
10
|
+
issueRefId: string;
|
|
11
|
+
action: IssueLifecycleAction;
|
|
12
|
+
landKey: string;
|
|
13
|
+
workspaceId?: string;
|
|
14
|
+
branch?: string;
|
|
15
|
+
baseRef?: string;
|
|
16
|
+
commitSha?: string;
|
|
17
|
+
commitSubject?: string;
|
|
18
|
+
agentId?: string;
|
|
19
|
+
source?: string;
|
|
20
|
+
comment?: string;
|
|
21
|
+
status: IssueLifecycleStatus;
|
|
22
|
+
error?: string;
|
|
23
|
+
createdAt: number;
|
|
24
|
+
updatedAt: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface UpsertIssueLifecycleActionInput {
|
|
28
|
+
issueRefId: string;
|
|
29
|
+
action: IssueLifecycleAction;
|
|
30
|
+
landKey: string;
|
|
31
|
+
workspaceId?: string;
|
|
32
|
+
branch?: string;
|
|
33
|
+
baseRef?: string;
|
|
34
|
+
commitSha?: string;
|
|
35
|
+
commitSubject?: string;
|
|
36
|
+
agentId?: string;
|
|
37
|
+
source?: string;
|
|
38
|
+
comment?: string;
|
|
39
|
+
status: IssueLifecycleStatus;
|
|
40
|
+
error?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface IssueLifecycleActionRow {
|
|
44
|
+
id: number;
|
|
45
|
+
issue_ref_id: string;
|
|
46
|
+
action: IssueLifecycleAction;
|
|
47
|
+
land_key: string;
|
|
48
|
+
workspace_id: string | null;
|
|
49
|
+
branch: string | null;
|
|
50
|
+
base_ref: string | null;
|
|
51
|
+
commit_sha: string | null;
|
|
52
|
+
commit_subject: string | null;
|
|
53
|
+
agent_id: string | null;
|
|
54
|
+
source: string | null;
|
|
55
|
+
comment: string | null;
|
|
56
|
+
status: IssueLifecycleStatus;
|
|
57
|
+
error: string | null;
|
|
58
|
+
created_at: number;
|
|
59
|
+
updated_at: number;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function cleanText(value: string | undefined, field: string, max: number): string | undefined {
|
|
63
|
+
if (value === undefined) return undefined;
|
|
64
|
+
const cleaned = value.trim();
|
|
65
|
+
if (!cleaned) return undefined;
|
|
66
|
+
if (cleaned.length > max) throw new ValidationError(`${field} must be ${max} characters or fewer`);
|
|
67
|
+
return cleaned;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function cleanRequiredText(value: string, field: string, max: number): string {
|
|
71
|
+
const cleaned = cleanText(value, field, max);
|
|
72
|
+
if (!cleaned) throw new ValidationError(`${field} required`);
|
|
73
|
+
return cleaned;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function cleanAction(value: IssueLifecycleAction): IssueLifecycleAction {
|
|
77
|
+
if (!ISSUE_LIFECYCLE_ACTIONS.includes(value)) throw new ValidationError(`action must be one of: ${ISSUE_LIFECYCLE_ACTIONS.join(", ")}`);
|
|
78
|
+
return value;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function cleanStatus(value: IssueLifecycleStatus): IssueLifecycleStatus {
|
|
82
|
+
if (!ISSUE_LIFECYCLE_STATUSES.includes(value)) throw new ValidationError(`status must be one of: ${ISSUE_LIFECYCLE_STATUSES.join(", ")}`);
|
|
83
|
+
return value;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function rowToIssueLifecycleAction(row: IssueLifecycleActionRow): IssueLifecycleActionRecord {
|
|
87
|
+
return {
|
|
88
|
+
id: row.id,
|
|
89
|
+
issueRefId: row.issue_ref_id,
|
|
90
|
+
action: row.action,
|
|
91
|
+
landKey: row.land_key,
|
|
92
|
+
...(row.workspace_id ? { workspaceId: row.workspace_id } : {}),
|
|
93
|
+
...(row.branch ? { branch: row.branch } : {}),
|
|
94
|
+
...(row.base_ref ? { baseRef: row.base_ref } : {}),
|
|
95
|
+
...(row.commit_sha ? { commitSha: row.commit_sha } : {}),
|
|
96
|
+
...(row.commit_subject ? { commitSubject: row.commit_subject } : {}),
|
|
97
|
+
...(row.agent_id ? { agentId: row.agent_id } : {}),
|
|
98
|
+
...(row.source ? { source: row.source } : {}),
|
|
99
|
+
...(row.comment ? { comment: row.comment } : {}),
|
|
100
|
+
status: row.status,
|
|
101
|
+
...(row.error ? { error: row.error } : {}),
|
|
102
|
+
createdAt: row.created_at,
|
|
103
|
+
updatedAt: row.updated_at,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function getSuccessfulIssueLifecycleAction(issueRefId: string, action: IssueLifecycleAction, landKey: string): IssueLifecycleActionRecord | null {
|
|
108
|
+
const row = getDb().query(`
|
|
109
|
+
SELECT * FROM issue_lifecycle_actions
|
|
110
|
+
WHERE issue_ref_id = ? AND action = ? AND land_key = ? AND status = 'succeeded'
|
|
111
|
+
`).get(
|
|
112
|
+
cleanRequiredText(issueRefId, "issueRefId", 120),
|
|
113
|
+
cleanAction(action),
|
|
114
|
+
cleanRequiredText(landKey, "landKey", 240),
|
|
115
|
+
) as IssueLifecycleActionRow | undefined;
|
|
116
|
+
return row ? rowToIssueLifecycleAction(row) : null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function listSuccessfulCloseActionsForCommit(input: { commitSha?: string; commitSubject?: string }): IssueLifecycleActionRecord[] {
|
|
120
|
+
const commitSha = cleanText(input.commitSha, "commitSha", 64);
|
|
121
|
+
const commitSubject = cleanText(input.commitSubject, "commitSubject", 500);
|
|
122
|
+
if (!commitSha && !commitSubject) return [];
|
|
123
|
+
const conditions: string[] = ["action = 'close'", "status = 'succeeded'"];
|
|
124
|
+
const params: string[] = [];
|
|
125
|
+
if (commitSha) {
|
|
126
|
+
conditions.push("commit_sha = ?");
|
|
127
|
+
params.push(commitSha);
|
|
128
|
+
}
|
|
129
|
+
if (commitSubject) {
|
|
130
|
+
conditions.push("commit_subject = ?");
|
|
131
|
+
params.push(commitSubject);
|
|
132
|
+
}
|
|
133
|
+
const rows = getDb().query(`
|
|
134
|
+
SELECT * FROM issue_lifecycle_actions
|
|
135
|
+
WHERE ${conditions.join(" AND ")}
|
|
136
|
+
ORDER BY updated_at DESC, id DESC
|
|
137
|
+
`).all(...params) as IssueLifecycleActionRow[];
|
|
138
|
+
return rows.map(rowToIssueLifecycleAction);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function upsertIssueLifecycleAction(input: UpsertIssueLifecycleActionInput): IssueLifecycleActionRecord {
|
|
142
|
+
const now = Date.now();
|
|
143
|
+
const issueRefId = cleanRequiredText(input.issueRefId, "issueRefId", 120);
|
|
144
|
+
const action = cleanAction(input.action);
|
|
145
|
+
const landKey = cleanRequiredText(input.landKey, "landKey", 240);
|
|
146
|
+
getDb().query(`
|
|
147
|
+
INSERT INTO issue_lifecycle_actions (
|
|
148
|
+
issue_ref_id, action, land_key, workspace_id, branch, base_ref, commit_sha, commit_subject,
|
|
149
|
+
agent_id, source, comment, status, error, created_at, updated_at
|
|
150
|
+
)
|
|
151
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
152
|
+
ON CONFLICT(issue_ref_id, action, land_key) DO UPDATE SET
|
|
153
|
+
workspace_id = excluded.workspace_id,
|
|
154
|
+
branch = excluded.branch,
|
|
155
|
+
base_ref = excluded.base_ref,
|
|
156
|
+
commit_sha = excluded.commit_sha,
|
|
157
|
+
commit_subject = excluded.commit_subject,
|
|
158
|
+
agent_id = excluded.agent_id,
|
|
159
|
+
source = excluded.source,
|
|
160
|
+
comment = excluded.comment,
|
|
161
|
+
status = excluded.status,
|
|
162
|
+
error = excluded.error,
|
|
163
|
+
updated_at = excluded.updated_at
|
|
164
|
+
`).run(
|
|
165
|
+
issueRefId,
|
|
166
|
+
action,
|
|
167
|
+
landKey,
|
|
168
|
+
cleanText(input.workspaceId, "workspaceId", 160) ?? null,
|
|
169
|
+
cleanText(input.branch, "branch", 240) ?? null,
|
|
170
|
+
cleanText(input.baseRef, "baseRef", 120) ?? null,
|
|
171
|
+
cleanText(input.commitSha, "commitSha", 64) ?? null,
|
|
172
|
+
cleanText(input.commitSubject, "commitSubject", 500) ?? null,
|
|
173
|
+
cleanText(input.agentId, "agentId", 200) ?? null,
|
|
174
|
+
cleanText(input.source, "source", 120) ?? null,
|
|
175
|
+
cleanText(input.comment, "comment", 2000) ?? null,
|
|
176
|
+
cleanStatus(input.status),
|
|
177
|
+
cleanText(input.error, "error", 2000) ?? null,
|
|
178
|
+
now,
|
|
179
|
+
now,
|
|
180
|
+
);
|
|
181
|
+
const row = getDb().query(`
|
|
182
|
+
SELECT * FROM issue_lifecycle_actions
|
|
183
|
+
WHERE issue_ref_id = ? AND action = ? AND land_key = ?
|
|
184
|
+
`).get(issueRefId, action, landKey) as IssueLifecycleActionRow | undefined;
|
|
185
|
+
if (!row) throw new ValidationError("issue lifecycle action could not be recorded");
|
|
186
|
+
return rowToIssueLifecycleAction(row);
|
|
187
|
+
}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { getDb, ValidationError } from "./connection.ts";
|
|
3
|
+
import { ISSUE_PROVIDERS, ISSUE_REF_STATUSES, type IssueProvider, type IssueRef, type IssueRefStatus } from "../types";
|
|
4
|
+
|
|
5
|
+
interface IssueRefRow {
|
|
6
|
+
id: string;
|
|
7
|
+
provider: IssueProvider;
|
|
8
|
+
repo: string;
|
|
9
|
+
external_key: string;
|
|
10
|
+
url: string;
|
|
11
|
+
title: string | null;
|
|
12
|
+
status: IssueRefStatus;
|
|
13
|
+
status_synced_at: number | null;
|
|
14
|
+
created_at: number;
|
|
15
|
+
updated_at: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface UpsertIssueRefInput {
|
|
19
|
+
provider: IssueProvider;
|
|
20
|
+
repo: string;
|
|
21
|
+
externalKey: string;
|
|
22
|
+
url: string;
|
|
23
|
+
title?: string;
|
|
24
|
+
status?: IssueRefStatus;
|
|
25
|
+
statusSyncedAt?: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface ResolveIssueRefByExternalKeyInput {
|
|
29
|
+
provider: IssueProvider;
|
|
30
|
+
repo: string;
|
|
31
|
+
externalKey: string;
|
|
32
|
+
url?: string;
|
|
33
|
+
title?: string;
|
|
34
|
+
status?: IssueRefStatus;
|
|
35
|
+
statusSyncedAt?: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface ResolveIssueRefFromStringInput {
|
|
39
|
+
ref: string;
|
|
40
|
+
defaultRepo?: string;
|
|
41
|
+
provider?: IssueProvider;
|
|
42
|
+
title?: string;
|
|
43
|
+
status?: IssueRefStatus;
|
|
44
|
+
statusSyncedAt?: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface ListIssueRefsOptions {
|
|
48
|
+
provider?: IssueProvider;
|
|
49
|
+
repo?: string;
|
|
50
|
+
status?: IssueRefStatus;
|
|
51
|
+
limit?: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface UpdateIssueRefInput {
|
|
55
|
+
url?: string;
|
|
56
|
+
title?: string;
|
|
57
|
+
status?: IssueRefStatus;
|
|
58
|
+
statusSyncedAt?: number;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function issueRefId(): string {
|
|
62
|
+
return `issue_${randomUUID()}`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function rowToIssueRef(row: IssueRefRow): IssueRef {
|
|
66
|
+
return {
|
|
67
|
+
id: row.id,
|
|
68
|
+
provider: row.provider,
|
|
69
|
+
repo: row.repo,
|
|
70
|
+
externalKey: row.external_key,
|
|
71
|
+
url: row.url,
|
|
72
|
+
...(row.title ? { title: row.title } : {}),
|
|
73
|
+
status: row.status,
|
|
74
|
+
...(row.status_synced_at !== null ? { statusSyncedAt: row.status_synced_at } : {}),
|
|
75
|
+
createdAt: row.created_at,
|
|
76
|
+
updatedAt: row.updated_at,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function cleanProvider(value: IssueProvider): IssueProvider {
|
|
81
|
+
if (!ISSUE_PROVIDERS.includes(value)) {
|
|
82
|
+
throw new ValidationError(`provider must be one of: ${ISSUE_PROVIDERS.join(", ")}`);
|
|
83
|
+
}
|
|
84
|
+
return value;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function cleanStatus(value: IssueRefStatus | undefined): IssueRefStatus {
|
|
88
|
+
if (value === undefined) return "unknown";
|
|
89
|
+
if (!ISSUE_REF_STATUSES.includes(value)) {
|
|
90
|
+
throw new ValidationError(`status must be one of: ${ISSUE_REF_STATUSES.join(", ")}`);
|
|
91
|
+
}
|
|
92
|
+
return value;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function requiredText(value: string | undefined, field: string, max: number): string {
|
|
96
|
+
if (typeof value !== "string") throw new ValidationError(`${field} required`);
|
|
97
|
+
const cleaned = value.trim();
|
|
98
|
+
if (!cleaned) throw new ValidationError(`${field} required`);
|
|
99
|
+
if (cleaned.length > max) throw new ValidationError(`${field} must be ${max} characters or fewer`);
|
|
100
|
+
return cleaned;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function optionalText(value: string | undefined, field: string, max: number): string | undefined {
|
|
104
|
+
if (value === undefined) return undefined;
|
|
105
|
+
const cleaned = requiredText(value, field, max);
|
|
106
|
+
return cleaned || undefined;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function cleanSyncedAt(value: number | undefined): number | undefined {
|
|
110
|
+
if (value === undefined) return undefined;
|
|
111
|
+
if (!Number.isSafeInteger(value) || value < 0) throw new ValidationError("statusSyncedAt must be a non-negative integer");
|
|
112
|
+
return value;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function githubIssueUrl(repo: string, externalKey: string): string {
|
|
116
|
+
return `https://github.com/${repo}/issues/${externalKey}`;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function resolveUrl(provider: IssueProvider, repo: string, externalKey: string, url: string | undefined): string {
|
|
120
|
+
const cleaned = optionalText(url, "url", 2000);
|
|
121
|
+
if (cleaned) return cleaned;
|
|
122
|
+
if (provider === "github") return githubIssueUrl(repo, externalKey);
|
|
123
|
+
throw new ValidationError("url required for non-github issue refs");
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function getIssueRef(id: string): IssueRef | null {
|
|
127
|
+
const row = getDb().query("SELECT * FROM issue_refs WHERE id = ?").get(id) as IssueRefRow | undefined;
|
|
128
|
+
return row ? rowToIssueRef(row) : null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function getIssueRefByExternalKey(provider: IssueProvider, repo: string, externalKey: string): IssueRef | null {
|
|
132
|
+
const row = getDb().query("SELECT * FROM issue_refs WHERE provider = ? AND repo = ? AND external_key = ?")
|
|
133
|
+
.get(cleanProvider(provider), requiredText(repo, "repo", 500), requiredText(externalKey, "externalKey", 240)) as IssueRefRow | undefined;
|
|
134
|
+
return row ? rowToIssueRef(row) : null;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function upsertIssueRef(input: UpsertIssueRefInput): IssueRef {
|
|
138
|
+
const provider = cleanProvider(input.provider);
|
|
139
|
+
const repo = requiredText(input.repo, "repo", 500);
|
|
140
|
+
const externalKey = requiredText(input.externalKey, "externalKey", 240);
|
|
141
|
+
const url = resolveUrl(provider, repo, externalKey, input.url);
|
|
142
|
+
const existing = getIssueRefByExternalKey(provider, repo, externalKey);
|
|
143
|
+
const title = optionalText(input.title, "title", 500);
|
|
144
|
+
const status = input.status === undefined && existing ? existing.status : cleanStatus(input.status);
|
|
145
|
+
const statusSyncedAt = cleanSyncedAt(input.statusSyncedAt);
|
|
146
|
+
const now = Date.now();
|
|
147
|
+
if (existing) {
|
|
148
|
+
getDb().query(`
|
|
149
|
+
UPDATE issue_refs
|
|
150
|
+
SET url = ?, title = ?, status = ?, status_synced_at = ?, updated_at = ?
|
|
151
|
+
WHERE id = ?
|
|
152
|
+
`).run(url, title ?? existing.title ?? null, status, statusSyncedAt ?? existing.statusSyncedAt ?? null, now, existing.id);
|
|
153
|
+
return getIssueRef(existing.id)!;
|
|
154
|
+
}
|
|
155
|
+
const id = issueRefId();
|
|
156
|
+
getDb().query(`
|
|
157
|
+
INSERT INTO issue_refs (id, provider, repo, external_key, url, title, status, status_synced_at, created_at, updated_at)
|
|
158
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
159
|
+
`).run(id, provider, repo, externalKey, url, title ?? null, status, statusSyncedAt ?? null, now, now);
|
|
160
|
+
return getIssueRef(id)!;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function resolveIssueRefByExternalKey(input: ResolveIssueRefByExternalKeyInput): IssueRef {
|
|
164
|
+
const provider = cleanProvider(input.provider);
|
|
165
|
+
const repo = requiredText(input.repo, "repo", 500);
|
|
166
|
+
const externalKey = requiredText(input.externalKey, "externalKey", 240);
|
|
167
|
+
const existing = getIssueRefByExternalKey(provider, repo, externalKey);
|
|
168
|
+
if (existing && input.url === undefined && input.title === undefined && input.status === undefined && input.statusSyncedAt === undefined) {
|
|
169
|
+
return existing;
|
|
170
|
+
}
|
|
171
|
+
return upsertIssueRef({
|
|
172
|
+
provider,
|
|
173
|
+
repo,
|
|
174
|
+
externalKey,
|
|
175
|
+
url: input.url ?? existing?.url ?? resolveUrl(provider, repo, externalKey, undefined),
|
|
176
|
+
title: input.title ?? existing?.title,
|
|
177
|
+
status: input.status ?? existing?.status,
|
|
178
|
+
statusSyncedAt: input.statusSyncedAt ?? existing?.statusSyncedAt,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function resolveIssueRefFromString(input: ResolveIssueRefFromStringInput): IssueRef {
|
|
183
|
+
const ref = requiredText(input.ref, "ref", 1000);
|
|
184
|
+
if (ref.startsWith("issue_")) {
|
|
185
|
+
const existing = getIssueRef(ref);
|
|
186
|
+
if (!existing) throw new ValidationError(`issue ref ${ref} not found`);
|
|
187
|
+
return existing;
|
|
188
|
+
}
|
|
189
|
+
const provider = cleanProvider(input.provider ?? "github");
|
|
190
|
+
if (provider !== "github") throw new ValidationError("string issue refs are only supported for github in this foundation resolver");
|
|
191
|
+
const urlMatch = ref.match(/^https:\/\/github\.com\/([^/]+)\/([^/]+)\/issues\/([0-9]+)(?:[/?#].*)?$/);
|
|
192
|
+
const ownerRepoMatch = ref.match(/^([A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+)#([0-9]+)$/);
|
|
193
|
+
const bareMatch = ref.match(/^#([0-9]+)$/);
|
|
194
|
+
const repo = urlMatch ? `${urlMatch[1]}/${urlMatch[2]}` : ownerRepoMatch ? ownerRepoMatch[1] : bareMatch ? input.defaultRepo : undefined;
|
|
195
|
+
const externalKey = urlMatch ? urlMatch[3] : ownerRepoMatch ? ownerRepoMatch[2] : bareMatch ? bareMatch[1] : undefined;
|
|
196
|
+
if (!repo || !externalKey) throw new ValidationError("ref must be issue_<id>, #number, owner/repo#number, or a GitHub issue URL");
|
|
197
|
+
return resolveIssueRefByExternalKey({
|
|
198
|
+
provider,
|
|
199
|
+
repo: requiredText(repo, "repo", 500),
|
|
200
|
+
externalKey,
|
|
201
|
+
url: githubIssueUrl(repo, externalKey),
|
|
202
|
+
title: input.title,
|
|
203
|
+
status: input.status,
|
|
204
|
+
statusSyncedAt: input.statusSyncedAt,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function listIssueRefs(opts: ListIssueRefsOptions = {}): IssueRef[] {
|
|
209
|
+
const conditions: string[] = [];
|
|
210
|
+
const params: Array<string | number> = [];
|
|
211
|
+
if (opts.provider) {
|
|
212
|
+
conditions.push("provider = ?");
|
|
213
|
+
params.push(cleanProvider(opts.provider));
|
|
214
|
+
}
|
|
215
|
+
if (opts.repo) {
|
|
216
|
+
conditions.push("repo = ?");
|
|
217
|
+
params.push(requiredText(opts.repo, "repo", 500));
|
|
218
|
+
}
|
|
219
|
+
if (opts.status) {
|
|
220
|
+
conditions.push("status = ?");
|
|
221
|
+
params.push(cleanStatus(opts.status));
|
|
222
|
+
}
|
|
223
|
+
const limit = opts.limit ?? 100;
|
|
224
|
+
if (!Number.isSafeInteger(limit) || limit <= 0 || limit > 500) throw new ValidationError("limit must be between 1 and 500");
|
|
225
|
+
params.push(limit);
|
|
226
|
+
const where = conditions.length ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
227
|
+
const rows = getDb().query(`
|
|
228
|
+
SELECT * FROM issue_refs
|
|
229
|
+
${where}
|
|
230
|
+
ORDER BY updated_at DESC, created_at DESC, id DESC
|
|
231
|
+
LIMIT ?
|
|
232
|
+
`).all(...params) as IssueRefRow[];
|
|
233
|
+
return rows.map(rowToIssueRef);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export function updateIssueRef(id: string, input: UpdateIssueRefInput): IssueRef | null {
|
|
237
|
+
const existing = getIssueRef(requiredText(id, "id", 120));
|
|
238
|
+
if (!existing) return null;
|
|
239
|
+
const url = optionalText(input.url, "url", 2000) ?? existing.url;
|
|
240
|
+
const title = input.title === undefined ? existing.title : optionalText(input.title, "title", 500);
|
|
241
|
+
const status = input.status === undefined ? existing.status : cleanStatus(input.status);
|
|
242
|
+
const statusSyncedAt = input.statusSyncedAt === undefined ? existing.statusSyncedAt : cleanSyncedAt(input.statusSyncedAt);
|
|
243
|
+
getDb().query(`
|
|
244
|
+
UPDATE issue_refs
|
|
245
|
+
SET url = ?, title = ?, status = ?, status_synced_at = ?, updated_at = ?
|
|
246
|
+
WHERE id = ?
|
|
247
|
+
`).run(url, title ?? null, status, statusSyncedAt ?? null, Date.now(), existing.id);
|
|
248
|
+
return getIssueRef(existing.id);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export function deleteIssueRef(id: string): boolean {
|
|
252
|
+
const result = getDb().query("DELETE FROM issue_refs WHERE id = ?").run(requiredText(id, "id", 120));
|
|
253
|
+
return result.changes > 0;
|
|
254
|
+
}
|
package/src/db/mappers.ts
CHANGED
|
@@ -377,6 +377,8 @@ export function rowToActivityEvent(row: any): ActivityEvent {
|
|
|
377
377
|
pairId: row.pair_id ?? undefined,
|
|
378
378
|
taskId: row.task_id ?? undefined,
|
|
379
379
|
agentId: row.agent_id ?? undefined,
|
|
380
|
+
issueRefId: row.issue_ref_id ?? undefined,
|
|
381
|
+
issueAction: row.issue_action ?? undefined,
|
|
380
382
|
metadata: parseJson(row.metadata, {}),
|
|
381
383
|
createdAt: row.created_at,
|
|
382
384
|
};
|
package/src/db/migrations.ts
CHANGED
|
@@ -117,6 +117,53 @@ export function applyMigrations(): void {
|
|
|
117
117
|
normalizeExistingMessageReactions();
|
|
118
118
|
|
|
119
119
|
// Migrations
|
|
120
|
+
getDb().run(`
|
|
121
|
+
CREATE TABLE IF NOT EXISTS issue_refs (
|
|
122
|
+
id TEXT PRIMARY KEY,
|
|
123
|
+
provider TEXT NOT NULL,
|
|
124
|
+
repo TEXT NOT NULL,
|
|
125
|
+
external_key TEXT NOT NULL,
|
|
126
|
+
url TEXT NOT NULL,
|
|
127
|
+
title TEXT,
|
|
128
|
+
status TEXT NOT NULL DEFAULT 'unknown',
|
|
129
|
+
status_synced_at INTEGER,
|
|
130
|
+
created_at INTEGER NOT NULL,
|
|
131
|
+
updated_at INTEGER NOT NULL,
|
|
132
|
+
UNIQUE(provider, repo, external_key)
|
|
133
|
+
)
|
|
134
|
+
`);
|
|
135
|
+
getDb().run("CREATE INDEX IF NOT EXISTS idx_issue_refs_external ON issue_refs(provider, repo, external_key)");
|
|
136
|
+
getDb().run("CREATE INDEX IF NOT EXISTS idx_issue_refs_updated ON issue_refs(updated_at DESC)");
|
|
137
|
+
getDb().run(`
|
|
138
|
+
CREATE TABLE IF NOT EXISTS issue_associations (
|
|
139
|
+
issue_ref_id TEXT NOT NULL REFERENCES issue_refs(id) ON DELETE CASCADE,
|
|
140
|
+
entity_type TEXT NOT NULL CHECK (entity_type IN ('team', 'plan-node', 'epic')),
|
|
141
|
+
entity_id TEXT NOT NULL,
|
|
142
|
+
created_at INTEGER NOT NULL,
|
|
143
|
+
PRIMARY KEY (issue_ref_id, entity_type, entity_id)
|
|
144
|
+
)
|
|
145
|
+
`);
|
|
146
|
+
getDb().run("CREATE INDEX IF NOT EXISTS idx_issue_associations_entity ON issue_associations(entity_type, entity_id, created_at DESC)");
|
|
147
|
+
getDb().run("CREATE INDEX IF NOT EXISTS idx_issue_associations_issue ON issue_associations(issue_ref_id, created_at DESC)");
|
|
148
|
+
getDb().run(`CREATE TABLE IF NOT EXISTS issue_lifecycle_actions (
|
|
149
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT, issue_ref_id TEXT NOT NULL REFERENCES issue_refs(id) ON DELETE CASCADE,
|
|
150
|
+
action TEXT NOT NULL CHECK (action IN ('close', 'reopen')), land_key TEXT NOT NULL, workspace_id TEXT, branch TEXT, base_ref TEXT, commit_sha TEXT, commit_subject TEXT,
|
|
151
|
+
agent_id TEXT, source TEXT, comment TEXT, status TEXT NOT NULL CHECK (status IN ('succeeded', 'failed')),
|
|
152
|
+
error TEXT, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL, UNIQUE(issue_ref_id, action, land_key))`);
|
|
153
|
+
getDb().run("CREATE INDEX IF NOT EXISTS idx_issue_lifecycle_commit ON issue_lifecycle_actions(commit_sha, action, status)");
|
|
154
|
+
getDb().run("CREATE INDEX IF NOT EXISTS idx_issue_lifecycle_subject ON issue_lifecycle_actions(commit_subject, action, status)");
|
|
155
|
+
|
|
156
|
+
const activityCols = getDb().query("PRAGMA table_info(activity_events)").all() as any[];
|
|
157
|
+
const activityColNames = activityCols.map((c: any) => c.name);
|
|
158
|
+
if (!activityColNames.includes("issue_ref_id")) {
|
|
159
|
+
getDb().run("ALTER TABLE activity_events ADD COLUMN issue_ref_id TEXT");
|
|
160
|
+
}
|
|
161
|
+
if (!activityColNames.includes("issue_action")) {
|
|
162
|
+
getDb().run("ALTER TABLE activity_events ADD COLUMN issue_action TEXT");
|
|
163
|
+
}
|
|
164
|
+
getDb().run("CREATE INDEX IF NOT EXISTS idx_activity_issue ON activity_events(issue_ref_id, created_at)");
|
|
165
|
+
getDb().run("CREATE INDEX IF NOT EXISTS idx_activity_agent_issue ON activity_events(agent_id, issue_ref_id, created_at)");
|
|
166
|
+
|
|
120
167
|
getDb().run(`
|
|
121
168
|
CREATE TABLE IF NOT EXISTS workspace_merge_pauses (
|
|
122
169
|
id TEXT PRIMARY KEY,
|
|
@@ -228,6 +275,12 @@ export function applyMigrations(): void {
|
|
|
228
275
|
if (!tokenColNames.includes("profile_id")) {
|
|
229
276
|
getDb().run("ALTER TABLE tokens ADD COLUMN profile_id TEXT");
|
|
230
277
|
}
|
|
278
|
+
// #484: revocation reason distinguishes a stale-on-restart revocation (recoverable via
|
|
279
|
+
// orchestrator re-mint for a still-live process) from a hard kill (admin/rotation). A NULL
|
|
280
|
+
// reason on a legacy revoked token is treated as a hard kill — never re-mintable.
|
|
281
|
+
if (!tokenColNames.includes("revoked_reason")) {
|
|
282
|
+
getDb().run("ALTER TABLE tokens ADD COLUMN revoked_reason TEXT");
|
|
283
|
+
}
|
|
231
284
|
|
|
232
285
|
getDb().run(`
|
|
233
286
|
CREATE TABLE IF NOT EXISTS message_delivery_attempts (
|
package/src/db/schema.ts
CHANGED
|
@@ -671,13 +671,13 @@ export function initDb(path: string = "agent-relay.db"): Database {
|
|
|
671
671
|
message_id INTEGER,
|
|
672
672
|
pair_id TEXT,
|
|
673
673
|
task_id INTEGER,
|
|
674
|
-
agent_id TEXT,
|
|
674
|
+
agent_id TEXT, issue_ref_id TEXT, issue_action TEXT,
|
|
675
675
|
metadata TEXT NOT NULL DEFAULT '{}',
|
|
676
676
|
created_at INTEGER NOT NULL
|
|
677
677
|
);
|
|
678
678
|
CREATE INDEX IF NOT EXISTS idx_activity_operator ON activity_events(operator_id, created_at);
|
|
679
679
|
CREATE INDEX IF NOT EXISTS idx_activity_created ON activity_events(created_at);
|
|
680
|
-
CREATE INDEX IF NOT EXISTS idx_activity_agent ON activity_events(agent_id, created_at);
|
|
680
|
+
CREATE INDEX IF NOT EXISTS idx_activity_agent ON activity_events(agent_id, created_at); CREATE INDEX IF NOT EXISTS idx_activity_issue ON activity_events(issue_ref_id, created_at); CREATE INDEX IF NOT EXISTS idx_activity_agent_issue ON activity_events(agent_id, issue_ref_id, created_at);
|
|
681
681
|
|
|
682
682
|
CREATE TABLE IF NOT EXISTS channels (
|
|
683
683
|
id TEXT PRIMARY KEY,
|
|
@@ -787,6 +787,7 @@ export function initDb(path: string = "agent-relay.db"): Database {
|
|
|
787
787
|
issued_at INTEGER NOT NULL,
|
|
788
788
|
expires_at INTEGER,
|
|
789
789
|
revoked_at INTEGER,
|
|
790
|
+
revoked_reason TEXT,
|
|
790
791
|
created_by TEXT
|
|
791
792
|
);
|
|
792
793
|
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { IssueRef, IssueRefStatus } from "../types";
|
|
2
|
+
|
|
3
|
+
export interface IssueTrackerStatus {
|
|
4
|
+
status: IssueRefStatus;
|
|
5
|
+
title: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface IssueTrackerCloseOptions {
|
|
9
|
+
comment?: string;
|
|
10
|
+
labels?: string[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface IssueTrackerReopenOptions {
|
|
14
|
+
comment?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface IssueTrackerAdapter {
|
|
18
|
+
resolveLink(ref: IssueRef): string;
|
|
19
|
+
getStatus(ref: IssueRef): Promise<IssueTrackerStatus>;
|
|
20
|
+
close(ref: IssueRef, options?: IssueTrackerCloseOptions): Promise<void>;
|
|
21
|
+
reopen(ref: IssueRef, options?: IssueTrackerReopenOptions): Promise<void>;
|
|
22
|
+
comment(ref: IssueRef, body: string): Promise<void>;
|
|
23
|
+
applyLabels(ref: IssueRef, labels: string[]): Promise<void>;
|
|
24
|
+
}
|