@agent-native/dispatch 0.7.0 → 0.8.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/README.md +37 -0
- package/dist/actions/apply-dream-proposal.d.ts +3 -0
- package/dist/actions/apply-dream-proposal.d.ts.map +1 -0
- package/dist/actions/apply-dream-proposal.js +11 -0
- package/dist/actions/apply-dream-proposal.js.map +1 -0
- package/dist/actions/create-dream-report.d.ts +3 -0
- package/dist/actions/create-dream-report.d.ts.map +1 -0
- package/dist/actions/create-dream-report.js +67 -0
- package/dist/actions/create-dream-report.js.map +1 -0
- package/dist/actions/create-workspace-resource.js +3 -3
- package/dist/actions/create-workspace-resource.js.map +1 -1
- package/dist/actions/delete-workspace-resource.js +1 -1
- package/dist/actions/delete-workspace-resource.js.map +1 -1
- package/dist/actions/ensure-dream-job.d.ts +3 -0
- package/dist/actions/ensure-dream-job.d.ts.map +1 -0
- package/dist/actions/ensure-dream-job.js +73 -0
- package/dist/actions/ensure-dream-job.js.map +1 -0
- package/dist/actions/get-dream-settings.d.ts +3 -0
- package/dist/actions/get-dream-settings.d.ts.map +1 -0
- package/dist/actions/get-dream-settings.js +11 -0
- package/dist/actions/get-dream-settings.js.map +1 -0
- package/dist/actions/get-dream.d.ts +3 -0
- package/dist/actions/get-dream.d.ts.map +1 -0
- package/dist/actions/get-dream.js +13 -0
- package/dist/actions/get-dream.js.map +1 -0
- package/dist/actions/get-workspace-resource-effective-context.d.ts +3 -0
- package/dist/actions/get-workspace-resource-effective-context.d.ts.map +1 -0
- package/dist/actions/get-workspace-resource-effective-context.js +27 -0
- package/dist/actions/get-workspace-resource-effective-context.js.map +1 -0
- package/dist/actions/index.d.ts.map +1 -1
- package/dist/actions/index.js +30 -4
- package/dist/actions/index.js.map +1 -1
- package/dist/actions/list-dream-candidates.d.ts +3 -0
- package/dist/actions/list-dream-candidates.d.ts.map +1 -0
- package/dist/actions/list-dream-candidates.js +68 -0
- package/dist/actions/list-dream-candidates.js.map +1 -0
- package/dist/actions/list-dreams.d.ts +3 -0
- package/dist/actions/list-dreams.d.ts.map +1 -0
- package/dist/actions/list-dreams.js +17 -0
- package/dist/actions/list-dreams.js.map +1 -0
- package/dist/actions/list-workspace-resources-for-app.d.ts +3 -0
- package/dist/actions/list-workspace-resources-for-app.d.ts.map +1 -0
- package/dist/actions/list-workspace-resources-for-app.js +12 -0
- package/dist/actions/list-workspace-resources-for-app.js.map +1 -0
- package/dist/actions/list-workspace-resources.js +1 -1
- package/dist/actions/list-workspace-resources.js.map +1 -1
- package/dist/actions/navigate.d.ts +1 -0
- package/dist/actions/navigate.d.ts.map +1 -1
- package/dist/actions/navigate.js +2 -1
- package/dist/actions/navigate.js.map +1 -1
- package/dist/actions/preview-dream-proposal.d.ts +3 -0
- package/dist/actions/preview-dream-proposal.d.ts.map +1 -0
- package/dist/actions/preview-dream-proposal.js +13 -0
- package/dist/actions/preview-dream-proposal.js.map +1 -0
- package/dist/actions/preview-workspace-resource-change.d.ts +3 -0
- package/dist/actions/preview-workspace-resource-change.d.ts.map +1 -0
- package/dist/actions/preview-workspace-resource-change.js +24 -0
- package/dist/actions/preview-workspace-resource-change.js.map +1 -0
- package/dist/actions/reject-dream-proposal.d.ts +3 -0
- package/dist/actions/reject-dream-proposal.d.ts.map +1 -0
- package/dist/actions/reject-dream-proposal.js +12 -0
- package/dist/actions/reject-dream-proposal.js.map +1 -0
- package/dist/actions/restore-starter-workspace-resources.d.ts +3 -0
- package/dist/actions/restore-starter-workspace-resources.d.ts.map +1 -0
- package/dist/actions/restore-starter-workspace-resources.js +14 -0
- package/dist/actions/restore-starter-workspace-resources.js.map +1 -0
- package/dist/actions/send-code-agent-remote-command.d.ts +3 -0
- package/dist/actions/send-code-agent-remote-command.d.ts.map +1 -0
- package/dist/actions/send-code-agent-remote-command.js +53 -0
- package/dist/actions/send-code-agent-remote-command.js.map +1 -0
- package/dist/actions/set-dream-settings.d.ts +3 -0
- package/dist/actions/set-dream-settings.d.ts.map +1 -0
- package/dist/actions/set-dream-settings.js +41 -0
- package/dist/actions/set-dream-settings.js.map +1 -0
- package/dist/actions/update-workspace-resource.js +1 -1
- package/dist/actions/update-workspace-resource.js.map +1 -1
- package/dist/actions/view-screen.d.ts.map +1 -1
- package/dist/actions/view-screen.js +73 -2
- package/dist/actions/view-screen.js.map +1 -1
- package/dist/components/approval-value-block.d.ts +7 -0
- package/dist/components/approval-value-block.d.ts.map +1 -0
- package/dist/components/approval-value-block.js +22 -0
- package/dist/components/approval-value-block.js.map +1 -0
- package/dist/components/create-app-popover.d.ts.map +1 -1
- package/dist/components/create-app-popover.js +3 -2
- package/dist/components/create-app-popover.js.map +1 -1
- package/dist/components/layout/Layout.d.ts.map +1 -1
- package/dist/components/layout/Layout.js +8 -1
- package/dist/components/layout/Layout.js.map +1 -1
- package/dist/components/ui/chart.d.ts +1 -1
- package/dist/components/workspace-app-card.d.ts.map +1 -1
- package/dist/components/workspace-app-card.js +25 -4
- package/dist/components/workspace-app-card.js.map +1 -1
- package/dist/components/workspace-resource-effective-stack.d.ts +11 -0
- package/dist/components/workspace-resource-effective-stack.d.ts.map +1 -0
- package/dist/components/workspace-resource-effective-stack.js +59 -0
- package/dist/components/workspace-resource-effective-stack.js.map +1 -0
- package/dist/components/workspace-resource-impact-preview.d.ts +9 -0
- package/dist/components/workspace-resource-impact-preview.d.ts.map +1 -0
- package/dist/components/workspace-resource-impact-preview.js +39 -0
- package/dist/components/workspace-resource-impact-preview.js.map +1 -0
- package/dist/db/migrations.d.ts.map +1 -1
- package/dist/db/migrations.js +59 -0
- package/dist/db/migrations.js.map +1 -1
- package/dist/db/schema.d.ts +714 -0
- package/dist/db/schema.d.ts.map +1 -1
- package/dist/db/schema.js +44 -2
- package/dist/db/schema.js.map +1 -1
- package/dist/hooks/use-navigation-state.d.ts +3 -0
- package/dist/hooks/use-navigation-state.d.ts.map +1 -1
- package/dist/hooks/use-navigation-state.js +23 -3
- package/dist/hooks/use-navigation-state.js.map +1 -1
- package/dist/lib/utils.d.ts +2 -1
- package/dist/lib/utils.d.ts.map +1 -1
- package/dist/lib/utils.js +5 -1
- package/dist/lib/utils.js.map +1 -1
- package/dist/routes/index.d.ts.map +1 -1
- package/dist/routes/index.js +1 -0
- package/dist/routes/index.js.map +1 -1
- package/dist/routes/pages/approval.d.ts.map +1 -1
- package/dist/routes/pages/approval.js +4 -1
- package/dist/routes/pages/approval.js.map +1 -1
- package/dist/routes/pages/approvals.js +1 -1
- package/dist/routes/pages/approvals.js.map +1 -1
- package/dist/routes/pages/dream-settings.d.ts +34 -0
- package/dist/routes/pages/dream-settings.d.ts.map +1 -0
- package/dist/routes/pages/dream-settings.js +68 -0
- package/dist/routes/pages/dream-settings.js.map +1 -0
- package/dist/routes/pages/dreams.d.ts +5 -0
- package/dist/routes/pages/dreams.d.ts.map +1 -0
- package/dist/routes/pages/dreams.js +435 -0
- package/dist/routes/pages/dreams.js.map +1 -0
- package/dist/routes/pages/workspace.d.ts.map +1 -1
- package/dist/routes/pages/workspace.js +187 -35
- package/dist/routes/pages/workspace.js.map +1 -1
- package/dist/server/lib/app-creation-store.d.ts.map +1 -1
- package/dist/server/lib/app-creation-store.js +3 -2
- package/dist/server/lib/app-creation-store.js.map +1 -1
- package/dist/server/lib/dispatch-integrations.d.ts +1 -1
- package/dist/server/lib/dispatch-integrations.d.ts.map +1 -1
- package/dist/server/lib/dispatch-integrations.js +9 -4
- package/dist/server/lib/dispatch-integrations.js.map +1 -1
- package/dist/server/lib/dispatch-remote-commands.d.ts +83 -0
- package/dist/server/lib/dispatch-remote-commands.d.ts.map +1 -0
- package/dist/server/lib/dispatch-remote-commands.js +256 -0
- package/dist/server/lib/dispatch-remote-commands.js.map +1 -0
- package/dist/server/lib/dispatch-store.d.ts +26 -0
- package/dist/server/lib/dispatch-store.d.ts.map +1 -1
- package/dist/server/lib/dispatch-store.js +17 -1
- package/dist/server/lib/dispatch-store.js.map +1 -1
- package/dist/server/lib/dreams-store.d.ts +398 -0
- package/dist/server/lib/dreams-store.d.ts.map +1 -0
- package/dist/server/lib/dreams-store.js +2330 -0
- package/dist/server/lib/dreams-store.js.map +1 -0
- package/dist/server/lib/thread-debug-store.d.ts +2 -2
- package/dist/server/lib/vault-store.d.ts +1 -1
- package/dist/server/lib/workspace-resources-store.d.ts +181 -17
- package/dist/server/lib/workspace-resources-store.d.ts.map +1 -1
- package/dist/server/lib/workspace-resources-store.js +737 -108
- package/dist/server/lib/workspace-resources-store.js.map +1 -1
- package/package.json +4 -2
- package/src/actions/apply-dream-proposal.ts +12 -0
- package/src/actions/create-dream-report.ts +76 -0
- package/src/actions/create-workspace-resource.ts +3 -3
- package/src/actions/delete-workspace-resource.ts +1 -1
- package/src/actions/ensure-dream-job.ts +76 -0
- package/src/actions/get-dream-settings.ts +12 -0
- package/src/actions/get-dream.ts +14 -0
- package/src/actions/get-workspace-resource-effective-context.ts +34 -0
- package/src/actions/index.spec.ts +26 -0
- package/src/actions/index.ts +31 -4
- package/src/actions/list-dream-candidates.ts +77 -0
- package/src/actions/list-dreams.ts +17 -0
- package/src/actions/list-workspace-resources-for-app.ts +13 -0
- package/src/actions/list-workspace-resources.ts +1 -1
- package/src/actions/navigate.ts +2 -1
- package/src/actions/preview-dream-proposal.ts +14 -0
- package/src/actions/preview-workspace-resource-change.ts +25 -0
- package/src/actions/reject-dream-proposal.ts +12 -0
- package/src/actions/restore-starter-workspace-resources.ts +17 -0
- package/src/actions/send-code-agent-remote-command.ts +59 -0
- package/src/actions/set-dream-settings.spec.ts +81 -0
- package/src/actions/set-dream-settings.ts +44 -0
- package/src/actions/update-workspace-resource.ts +1 -1
- package/src/actions/view-screen.ts +90 -2
- package/src/components/approval-value-block.spec.tsx +59 -0
- package/src/components/approval-value-block.tsx +33 -0
- package/src/components/create-app-popover.tsx +3 -2
- package/src/components/layout/Layout.tsx +8 -0
- package/src/components/workspace-app-card.tsx +166 -1
- package/src/components/workspace-resource-effective-stack.spec.tsx +125 -0
- package/src/components/workspace-resource-effective-stack.tsx +141 -0
- package/src/components/workspace-resource-impact-preview.spec.tsx +147 -0
- package/src/components/workspace-resource-impact-preview.tsx +116 -0
- package/src/db/migrations.ts +59 -0
- package/src/db/schema.ts +46 -2
- package/src/hooks/use-navigation-state.ts +24 -5
- package/src/lib/utils.ts +6 -1
- package/src/routes/index.ts +1 -0
- package/src/routes/pages/approval.tsx +14 -1
- package/src/routes/pages/approvals.tsx +1 -1
- package/src/routes/pages/dream-settings.spec.ts +130 -0
- package/src/routes/pages/dream-settings.ts +103 -0
- package/src/routes/pages/dreams.tsx +1828 -0
- package/src/routes/pages/workspace.tsx +577 -97
- package/src/server/lib/app-creation-store.ts +3 -2
- package/src/server/lib/dispatch-integrations.ts +10 -3
- package/src/server/lib/dispatch-remote-commands.spec.ts +167 -0
- package/src/server/lib/dispatch-remote-commands.ts +375 -0
- package/src/server/lib/dispatch-store.ts +37 -1
- package/src/server/lib/dreams-store.spec.ts +1492 -0
- package/src/server/lib/dreams-store.ts +3168 -0
- package/src/server/lib/workspace-resource-approval-lifecycle.spec.ts +236 -0
- package/src/server/lib/workspace-resources-store.spec.ts +1106 -0
- package/src/server/lib/workspace-resources-store.ts +1001 -134
- package/dist/actions/sync-workspace-resources-to-all.d.ts +0 -3
- package/dist/actions/sync-workspace-resources-to-all.d.ts.map +0 -1
- package/dist/actions/sync-workspace-resources-to-all.js +0 -9
- package/dist/actions/sync-workspace-resources-to-all.js.map +0 -1
- package/dist/actions/sync-workspace-resources-to-app.d.ts +0 -3
- package/dist/actions/sync-workspace-resources-to-app.d.ts.map +0 -1
- package/dist/actions/sync-workspace-resources-to-app.js +0 -11
- package/dist/actions/sync-workspace-resources-to-app.js.map +0 -1
- package/src/actions/sync-workspace-resources-to-all.ts +0 -10
- package/src/actions/sync-workspace-resources-to-app.ts +0 -12
|
@@ -1564,6 +1564,7 @@ function buildWorkspaceAppPrompt(input: {
|
|
|
1564
1564
|
? `Dispatch vault keys selected for this app: ${selectedKeys.join(", ")}`
|
|
1565
1565
|
: "Dispatch vault keys selected for this app: none",
|
|
1566
1566
|
`Dispatch workspace resources selected for this app:\n${resourceList}`,
|
|
1567
|
+
`Dispatch workspace resources with scope=all are global. After the app exists, sync workspace resources to appId "${appId}" so global skills, guardrail instructions, and reference resources reach the new app even when no per-app resources were selected.`,
|
|
1567
1568
|
"",
|
|
1568
1569
|
`Use the workspace app layout: create it under apps/${appId}, mount it at /${appId}, keep it on the shared workspace database/hosting model, and avoid table-name collisions by namespacing any new domain tables to the app.`,
|
|
1569
1570
|
`Important routing rule: from outside the app, link to /${appId}; inside apps/${appId}, React Router routes are app-local. Use <Link to="/review"> and navigate("/review"), not "/${appId}/review"; APP_BASE_PATH supplies the mounted prefix, and hardcoding it causes doubled URLs like /${appId}/${appId}/review.`,
|
|
@@ -1574,8 +1575,8 @@ function buildWorkspaceAppPrompt(input: {
|
|
|
1574
1575
|
? `Dispatch will create pending vault requests for the selected keys for appId "${appId}" after this app creation request is accepted. Do not grant or sync vault keys directly from the app-creation branch.`
|
|
1575
1576
|
: "Do not grant or request any Dispatch vault keys unless the user asks later.",
|
|
1576
1577
|
selectedResources.length
|
|
1577
|
-
? `Dispatch will create workspace resource grants for the selected resources for appId "${appId}". After the app exists, sync workspace resources so the app receives
|
|
1578
|
-
: "Do not grant any Dispatch workspace resources unless the user asks later.",
|
|
1578
|
+
? `Dispatch will create workspace resource grants for the selected resources for appId "${appId}". After the app exists, sync workspace resources so the app receives both global and selected shared resources.`
|
|
1579
|
+
: "Do not grant any selected-only Dispatch workspace resources unless the user asks later.",
|
|
1579
1580
|
"",
|
|
1580
1581
|
"Agent-native rules (these are the framework's contract — not optional):",
|
|
1581
1582
|
`- Persist ALL data in SQL via Drizzle. Add tables to apps/${appId}/server/db/schema.ts and migrations to apps/${appId}/server/plugins/db.ts. NEVER use localStorage, sessionStorage, IndexedDB, or in-memory state for anything the user expects to persist — agent and UI must read the same source of truth.`,
|
|
@@ -5,6 +5,7 @@ import type {
|
|
|
5
5
|
import { resolveOrgIdForEmail } from "@agent-native/core/org";
|
|
6
6
|
import crypto from "node:crypto";
|
|
7
7
|
import { consumeLinkToken, resolveLinkedOwner } from "./dispatch-store.js";
|
|
8
|
+
import { handleRemoteCodeCommand } from "./dispatch-remote-commands.js";
|
|
8
9
|
|
|
9
10
|
type SlackSenderProfile = {
|
|
10
11
|
email: string | null;
|
|
@@ -195,11 +196,17 @@ export async function resolveDispatchOwner(
|
|
|
195
196
|
|
|
196
197
|
export async function beforeDispatchProcess(
|
|
197
198
|
incoming: IncomingMessage,
|
|
198
|
-
|
|
199
|
+
adapter: PlatformAdapter,
|
|
199
200
|
): Promise<{ handled: true; responseText?: string } | { handled: false }> {
|
|
200
201
|
const trimmed = incoming.text.trim();
|
|
201
|
-
const
|
|
202
|
-
|
|
202
|
+
const commandText =
|
|
203
|
+
contextString(incoming.platformContext.rawText) || trimmed;
|
|
204
|
+
const match = commandText.match(/^\/link(?:@\w+)?\s+([a-zA-Z0-9_-]+)$/);
|
|
205
|
+
if (!match) {
|
|
206
|
+
return handleRemoteCodeCommand(incoming, adapter, {
|
|
207
|
+
resolveOwner: () => resolveDispatchOwner(incoming),
|
|
208
|
+
});
|
|
209
|
+
}
|
|
203
210
|
|
|
204
211
|
try {
|
|
205
212
|
const owner = await consumeLinkToken({
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import type {
|
|
3
|
+
IncomingMessage,
|
|
4
|
+
PlatformAdapter,
|
|
5
|
+
} from "@agent-native/core/server";
|
|
6
|
+
import {
|
|
7
|
+
handleRemoteCodeCommand,
|
|
8
|
+
parseTelegramCodeCommand,
|
|
9
|
+
type RemoteCodeCommandEnvelope,
|
|
10
|
+
} from "./dispatch-remote-commands.js";
|
|
11
|
+
|
|
12
|
+
function telegramIncoming(text: string, rawText = text): IncomingMessage {
|
|
13
|
+
return {
|
|
14
|
+
platform: "telegram",
|
|
15
|
+
externalThreadId: "chat-123",
|
|
16
|
+
text,
|
|
17
|
+
senderId: "user-1",
|
|
18
|
+
senderName: "User One",
|
|
19
|
+
platformContext: { chatId: 123, messageId: 456, rawText },
|
|
20
|
+
timestamp: 1710000000000,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const adapter = {
|
|
25
|
+
platform: "telegram",
|
|
26
|
+
label: "Telegram",
|
|
27
|
+
} as PlatformAdapter;
|
|
28
|
+
|
|
29
|
+
describe("parseTelegramCodeCommand", () => {
|
|
30
|
+
it("parses a prompt as a create command", () => {
|
|
31
|
+
expect(
|
|
32
|
+
parseTelegramCodeCommand(
|
|
33
|
+
telegramIncoming(
|
|
34
|
+
"fix the failing tests",
|
|
35
|
+
"/code fix the failing tests",
|
|
36
|
+
),
|
|
37
|
+
),
|
|
38
|
+
).toEqual({ type: "create", prompt: "fix the failing tests" });
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("parses run management commands", () => {
|
|
42
|
+
expect(
|
|
43
|
+
parseTelegramCodeCommand(telegramIncoming("list", "/code list")),
|
|
44
|
+
).toEqual({
|
|
45
|
+
type: "list",
|
|
46
|
+
});
|
|
47
|
+
expect(
|
|
48
|
+
parseTelegramCodeCommand(telegramIncoming("status 2", "/code status 2")),
|
|
49
|
+
).toEqual({ type: "status", runRef: "2" });
|
|
50
|
+
expect(
|
|
51
|
+
parseTelegramCodeCommand(
|
|
52
|
+
telegramIncoming(
|
|
53
|
+
"continue run_123 add docs",
|
|
54
|
+
"/code continue run_123 add docs",
|
|
55
|
+
),
|
|
56
|
+
),
|
|
57
|
+
).toEqual({ type: "continue", runRef: "run_123", text: "add docs" });
|
|
58
|
+
expect(
|
|
59
|
+
parseTelegramCodeCommand(
|
|
60
|
+
telegramIncoming("approve req_1", "/code approve req_1"),
|
|
61
|
+
),
|
|
62
|
+
).toEqual({ type: "approve", approvalId: "req_1" });
|
|
63
|
+
expect(
|
|
64
|
+
parseTelegramCodeCommand(
|
|
65
|
+
telegramIncoming("deny req_1", "/code deny req_1"),
|
|
66
|
+
),
|
|
67
|
+
).toEqual({ type: "deny", approvalId: "req_1" });
|
|
68
|
+
expect(
|
|
69
|
+
parseTelegramCodeCommand(telegramIncoming("stop 1", "/code stop 1")),
|
|
70
|
+
).toEqual({
|
|
71
|
+
type: "stop",
|
|
72
|
+
runRef: "1",
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("does not hijack Telegram messages after the adapter strips other commands", () => {
|
|
77
|
+
expect(
|
|
78
|
+
parseTelegramCodeCommand({
|
|
79
|
+
platform: "telegram",
|
|
80
|
+
text: "list",
|
|
81
|
+
platformContext: {},
|
|
82
|
+
}),
|
|
83
|
+
).toBeNull();
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
describe("handleRemoteCodeCommand", () => {
|
|
88
|
+
it("routes code commands to the remote relay with owner and source context", async () => {
|
|
89
|
+
const relay = vi.fn(async () => ({
|
|
90
|
+
ok: true,
|
|
91
|
+
runId: "run_123",
|
|
92
|
+
hostOnline: true,
|
|
93
|
+
}));
|
|
94
|
+
|
|
95
|
+
const result = await handleRemoteCodeCommand(
|
|
96
|
+
telegramIncoming("ship it", "/code ship it"),
|
|
97
|
+
adapter,
|
|
98
|
+
{
|
|
99
|
+
resolveOwner: () => "owner@example.test",
|
|
100
|
+
relay,
|
|
101
|
+
},
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
expect(result).toEqual({
|
|
105
|
+
handled: true,
|
|
106
|
+
responseText: "Queued code run (run_123).",
|
|
107
|
+
});
|
|
108
|
+
expect(relay).toHaveBeenCalledWith({
|
|
109
|
+
kind: "code-agent",
|
|
110
|
+
ownerEmail: "owner@example.test",
|
|
111
|
+
command: { type: "create", prompt: "ship it" },
|
|
112
|
+
source: {
|
|
113
|
+
platform: "telegram",
|
|
114
|
+
externalThreadId: "chat-123",
|
|
115
|
+
senderId: "user-1",
|
|
116
|
+
senderName: "User One",
|
|
117
|
+
messageId: "456",
|
|
118
|
+
timestamp: 1710000000000,
|
|
119
|
+
},
|
|
120
|
+
} satisfies RemoteCodeCommandEnvelope);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("keeps offline hosts pending without pretending the run is active", async () => {
|
|
124
|
+
const result = await handleRemoteCodeCommand(
|
|
125
|
+
telegramIncoming("fix it", "/code fix it"),
|
|
126
|
+
adapter,
|
|
127
|
+
{
|
|
128
|
+
resolveOwner: () => "owner@example.test",
|
|
129
|
+
relay: async () => ({
|
|
130
|
+
ok: true,
|
|
131
|
+
commandId: "cmd_123",
|
|
132
|
+
hostOnline: false,
|
|
133
|
+
hostStatus: "asleep",
|
|
134
|
+
}),
|
|
135
|
+
},
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
expect(result).toEqual({
|
|
139
|
+
handled: true,
|
|
140
|
+
responseText:
|
|
141
|
+
"Queued code run (cmd_123). Your computer looks offline or asleep, so it will pick this up when it wakes.",
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("formats recent run lists compactly", async () => {
|
|
146
|
+
const result = await handleRemoteCodeCommand(
|
|
147
|
+
telegramIncoming("list", "/code list"),
|
|
148
|
+
adapter,
|
|
149
|
+
{
|
|
150
|
+
resolveOwner: () => "owner@example.test",
|
|
151
|
+
relay: async () => ({
|
|
152
|
+
ok: true,
|
|
153
|
+
runs: [
|
|
154
|
+
{ id: "run_a", title: "Fix auth", status: "running" },
|
|
155
|
+
{ id: "run_b", title: "Add docs", status: "completed" },
|
|
156
|
+
],
|
|
157
|
+
}),
|
|
158
|
+
},
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
expect(result).toEqual({
|
|
162
|
+
handled: true,
|
|
163
|
+
responseText:
|
|
164
|
+
"Recent code-agent runs:\n1. Fix auth — running (run_a)\n2. Add docs — completed (run_b)",
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
});
|
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
IncomingMessage,
|
|
3
|
+
PlatformAdapter,
|
|
4
|
+
} from "@agent-native/core/server";
|
|
5
|
+
|
|
6
|
+
export type RemoteCodeCommand =
|
|
7
|
+
| { type: "create"; prompt: string }
|
|
8
|
+
| { type: "list" }
|
|
9
|
+
| { type: "status"; runRef?: string }
|
|
10
|
+
| { type: "continue"; runRef: string; text: string }
|
|
11
|
+
| { type: "approve"; approvalId: string }
|
|
12
|
+
| { type: "deny"; approvalId: string }
|
|
13
|
+
| { type: "stop"; runRef: string }
|
|
14
|
+
| { type: "help"; reason?: string };
|
|
15
|
+
|
|
16
|
+
export interface RemoteCodeCommandEnvelope {
|
|
17
|
+
kind: "code-agent";
|
|
18
|
+
ownerEmail: string;
|
|
19
|
+
command: Exclude<RemoteCodeCommand, { type: "help" }>;
|
|
20
|
+
source: {
|
|
21
|
+
platform: string;
|
|
22
|
+
externalThreadId: string;
|
|
23
|
+
senderId?: string;
|
|
24
|
+
senderName?: string;
|
|
25
|
+
messageId?: string;
|
|
26
|
+
timestamp: number;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface RemoteCodeRunSummary {
|
|
31
|
+
id?: string;
|
|
32
|
+
runId?: string;
|
|
33
|
+
title?: string;
|
|
34
|
+
prompt?: string;
|
|
35
|
+
status?: string;
|
|
36
|
+
updatedAt?: string | number | Date;
|
|
37
|
+
createdAt?: string | number | Date;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface RemoteCodeCommandResult {
|
|
41
|
+
ok?: boolean;
|
|
42
|
+
status?: string;
|
|
43
|
+
hostOnline?: boolean;
|
|
44
|
+
hostStatus?: string;
|
|
45
|
+
commandId?: string;
|
|
46
|
+
requestId?: string;
|
|
47
|
+
runId?: string;
|
|
48
|
+
run?: RemoteCodeRunSummary;
|
|
49
|
+
runs?: RemoteCodeRunSummary[];
|
|
50
|
+
message?: string;
|
|
51
|
+
error?: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export type RemoteCodeCommandRelay = (
|
|
55
|
+
envelope: RemoteCodeCommandEnvelope,
|
|
56
|
+
) => Promise<RemoteCodeCommandResult>;
|
|
57
|
+
|
|
58
|
+
export interface HandleRemoteCodeCommandOptions {
|
|
59
|
+
resolveOwner: () => Promise<string> | string;
|
|
60
|
+
relay?: RemoteCodeCommandRelay;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const CODE_COMMAND_RE = /^\/code(?:@[a-zA-Z0-9_]+)?(?:\s+|$)/i;
|
|
64
|
+
|
|
65
|
+
export function parseTelegramCodeCommand(
|
|
66
|
+
incoming: Pick<IncomingMessage, "platform" | "text" | "platformContext">,
|
|
67
|
+
): RemoteCodeCommand | null {
|
|
68
|
+
if (incoming.platform !== "telegram") return null;
|
|
69
|
+
|
|
70
|
+
const rawText = rawTelegramText(incoming);
|
|
71
|
+
if (!rawText || !CODE_COMMAND_RE.test(rawText)) return null;
|
|
72
|
+
|
|
73
|
+
return parseCodeCommandBody(rawText.replace(CODE_COMMAND_RE, "").trim());
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export async function handleRemoteCodeCommand(
|
|
77
|
+
incoming: IncomingMessage,
|
|
78
|
+
_adapter: PlatformAdapter,
|
|
79
|
+
options: HandleRemoteCodeCommandOptions,
|
|
80
|
+
): Promise<{ handled: true; responseText?: string } | { handled: false }> {
|
|
81
|
+
const command = parseTelegramCodeCommand(incoming);
|
|
82
|
+
if (!command) return { handled: false };
|
|
83
|
+
|
|
84
|
+
if (command.type === "help") {
|
|
85
|
+
return { handled: true, responseText: formatCodeCommandHelp(command) };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
const ownerEmail = await options.resolveOwner();
|
|
90
|
+
const envelope = createRemoteCodeCommandEnvelope(
|
|
91
|
+
incoming,
|
|
92
|
+
ownerEmail,
|
|
93
|
+
command,
|
|
94
|
+
);
|
|
95
|
+
const relay = options.relay ?? enqueueRemoteCodeCommand;
|
|
96
|
+
const result = await relay(envelope);
|
|
97
|
+
return {
|
|
98
|
+
handled: true,
|
|
99
|
+
responseText: formatRemoteCodeCommandResult(command, result),
|
|
100
|
+
};
|
|
101
|
+
} catch (error) {
|
|
102
|
+
return {
|
|
103
|
+
handled: true,
|
|
104
|
+
responseText:
|
|
105
|
+
error instanceof Error
|
|
106
|
+
? `I couldn't route that code command: ${error.message}`
|
|
107
|
+
: "I couldn't route that code command.",
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function createRemoteCodeCommandEnvelope(
|
|
113
|
+
incoming: IncomingMessage,
|
|
114
|
+
ownerEmail: string,
|
|
115
|
+
command: Exclude<RemoteCodeCommand, { type: "help" }>,
|
|
116
|
+
): RemoteCodeCommandEnvelope {
|
|
117
|
+
return {
|
|
118
|
+
kind: "code-agent",
|
|
119
|
+
ownerEmail,
|
|
120
|
+
command,
|
|
121
|
+
source: {
|
|
122
|
+
platform: incoming.platform,
|
|
123
|
+
externalThreadId: incoming.externalThreadId,
|
|
124
|
+
senderId: incoming.senderId,
|
|
125
|
+
senderName: incoming.senderName,
|
|
126
|
+
messageId: contextString(incoming.platformContext.messageId),
|
|
127
|
+
timestamp: incoming.timestamp,
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export async function enqueueRemoteCodeCommand(
|
|
133
|
+
envelope: RemoteCodeCommandEnvelope,
|
|
134
|
+
): Promise<RemoteCodeCommandResult> {
|
|
135
|
+
const helperResult = await tryCoreRemoteCommandHelper(envelope);
|
|
136
|
+
if (helperResult) return helperResult;
|
|
137
|
+
|
|
138
|
+
const endpoint = `${resolveRemoteRelayBaseUrl()}/_agent-native/integrations/remote/enqueue`;
|
|
139
|
+
const response = await fetch(endpoint, {
|
|
140
|
+
method: "POST",
|
|
141
|
+
headers: { "Content-Type": "application/json" },
|
|
142
|
+
body: JSON.stringify(envelope),
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
let body: unknown = null;
|
|
146
|
+
try {
|
|
147
|
+
body = await response.json();
|
|
148
|
+
} catch {
|
|
149
|
+
body = null;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (!response.ok) {
|
|
153
|
+
const message =
|
|
154
|
+
typeof body === "object" && body && "error" in body
|
|
155
|
+
? String((body as { error?: unknown }).error)
|
|
156
|
+
: `remote relay returned ${response.status}`;
|
|
157
|
+
throw new Error(message);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return normalizeRemoteCodeCommandResult(body);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function formatRemoteCodeCommandResult(
|
|
164
|
+
command: Exclude<RemoteCodeCommand, { type: "help" }>,
|
|
165
|
+
result: RemoteCodeCommandResult,
|
|
166
|
+
): string {
|
|
167
|
+
if (result.message?.trim()) return result.message.trim();
|
|
168
|
+
if (result.error?.trim())
|
|
169
|
+
return `Code command failed: ${result.error.trim()}`;
|
|
170
|
+
|
|
171
|
+
if (command.type === "list") return formatRunList(result.runs ?? []);
|
|
172
|
+
if (command.type === "status") return formatStatus(command, result);
|
|
173
|
+
|
|
174
|
+
const id =
|
|
175
|
+
result.runId ||
|
|
176
|
+
result.run?.runId ||
|
|
177
|
+
result.run?.id ||
|
|
178
|
+
result.commandId ||
|
|
179
|
+
result.requestId;
|
|
180
|
+
const suffix = id ? ` (${id})` : "";
|
|
181
|
+
const offline = isOfflineOrSleeping(result);
|
|
182
|
+
|
|
183
|
+
if (command.type === "create") {
|
|
184
|
+
return offline
|
|
185
|
+
? `Queued code run${suffix}. Your computer looks offline or asleep, so it will pick this up when it wakes.`
|
|
186
|
+
: `Queued code run${suffix}.`;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (command.type === "continue") {
|
|
190
|
+
return offline
|
|
191
|
+
? `Queued follow-up for ${command.runRef}. Your computer looks offline or asleep, so it will pick this up when it wakes.`
|
|
192
|
+
: `Queued follow-up for ${command.runRef}.`;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (command.type === "approve") {
|
|
196
|
+
return `Approved code-agent request ${command.approvalId}.`;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (command.type === "deny") {
|
|
200
|
+
return `Denied code-agent request ${command.approvalId}.`;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (command.type === "stop") {
|
|
204
|
+
return offline
|
|
205
|
+
? `Queued stop request for ${command.runRef}. Your computer looks offline or asleep, so it will receive the stop request when it wakes.`
|
|
206
|
+
: `Stop requested for ${command.runRef}.`;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return "Code command routed.";
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function parseCodeCommandBody(body: string): RemoteCodeCommand {
|
|
213
|
+
if (!body) return { type: "help" };
|
|
214
|
+
|
|
215
|
+
const [verbRaw = "", ...restParts] = body.split(/\s+/);
|
|
216
|
+
const verb = verbRaw.toLowerCase();
|
|
217
|
+
const rest = restParts.join(" ").trim();
|
|
218
|
+
|
|
219
|
+
if (verb === "help") return { type: "help" };
|
|
220
|
+
if (verb === "list") return { type: "list" };
|
|
221
|
+
if (verb === "status") {
|
|
222
|
+
return rest ? { type: "status", runRef: rest } : { type: "status" };
|
|
223
|
+
}
|
|
224
|
+
if (verb === "continue") {
|
|
225
|
+
const { first, rest: text } = splitFirst(rest);
|
|
226
|
+
if (!first || !text) {
|
|
227
|
+
return { type: "help", reason: "continue needs a run id and text" };
|
|
228
|
+
}
|
|
229
|
+
return { type: "continue", runRef: first, text };
|
|
230
|
+
}
|
|
231
|
+
if (verb === "approve") {
|
|
232
|
+
return rest
|
|
233
|
+
? { type: "approve", approvalId: rest }
|
|
234
|
+
: { type: "help", reason: "approve needs a request id" };
|
|
235
|
+
}
|
|
236
|
+
if (verb === "deny") {
|
|
237
|
+
return rest
|
|
238
|
+
? { type: "deny", approvalId: rest }
|
|
239
|
+
: { type: "help", reason: "deny needs a request id" };
|
|
240
|
+
}
|
|
241
|
+
if (verb === "stop") {
|
|
242
|
+
return rest
|
|
243
|
+
? { type: "stop", runRef: rest }
|
|
244
|
+
: { type: "help", reason: "stop needs a run id or list index" };
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return { type: "create", prompt: body };
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function rawTelegramText(
|
|
251
|
+
incoming: Pick<IncomingMessage, "text" | "platformContext">,
|
|
252
|
+
): string | null {
|
|
253
|
+
const context = incoming.platformContext;
|
|
254
|
+
return (
|
|
255
|
+
contextString(context.rawText) ||
|
|
256
|
+
contextString(context.originalText) ||
|
|
257
|
+
contextString(context.messageText) ||
|
|
258
|
+
(CODE_COMMAND_RE.test(incoming.text) ? incoming.text : null)
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function formatCodeCommandHelp(
|
|
263
|
+
command?: Extract<RemoteCodeCommand, { type: "help" }>,
|
|
264
|
+
): string {
|
|
265
|
+
const prefix = command?.reason ? `${command.reason}.\n\n` : "";
|
|
266
|
+
return `${prefix}Use /code <prompt>, /code list, /code status [run], /code continue <run> <text>, /code approve <id>, /code deny <id>, or /code stop <run>.`;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function formatRunList(runs: RemoteCodeRunSummary[]): string {
|
|
270
|
+
if (!runs.length) return "No recent code-agent runs found.";
|
|
271
|
+
const lines = runs.slice(0, 8).map((run, index) => {
|
|
272
|
+
const id = run.runId || run.id || "unknown";
|
|
273
|
+
const title = run.title || run.prompt || "Untitled run";
|
|
274
|
+
const status = run.status || "unknown";
|
|
275
|
+
return `${index + 1}. ${title} — ${status} (${id})`;
|
|
276
|
+
});
|
|
277
|
+
return `Recent code-agent runs:\n${lines.join("\n")}`;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function formatStatus(
|
|
281
|
+
command: Extract<RemoteCodeCommand, { type: "status" }>,
|
|
282
|
+
result: RemoteCodeCommandResult,
|
|
283
|
+
): string {
|
|
284
|
+
const run = result.run;
|
|
285
|
+
const hostStatus =
|
|
286
|
+
result.hostStatus || (result.hostOnline ? "online" : "offline");
|
|
287
|
+
if (!run) {
|
|
288
|
+
const target = command.runRef ? ` for ${command.runRef}` : "";
|
|
289
|
+
return `Code-agent host is ${hostStatus}${target}.`;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const id = run.runId || run.id || command.runRef || "unknown";
|
|
293
|
+
const title = run.title || run.prompt || "Untitled run";
|
|
294
|
+
const status = run.status || result.status || "unknown";
|
|
295
|
+
const updated = formatDate(run.updatedAt || run.createdAt);
|
|
296
|
+
return [
|
|
297
|
+
`Code run ${id}: ${status}`,
|
|
298
|
+
`Task: ${title}`,
|
|
299
|
+
`Host: ${hostStatus}`,
|
|
300
|
+
updated ? `Updated: ${updated}` : "",
|
|
301
|
+
]
|
|
302
|
+
.filter(Boolean)
|
|
303
|
+
.join("\n");
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function isOfflineOrSleeping(result: RemoteCodeCommandResult): boolean {
|
|
307
|
+
if (result.hostOnline === false) return true;
|
|
308
|
+
const status = result.hostStatus?.toLowerCase();
|
|
309
|
+
return status === "offline" || status === "asleep" || status === "sleeping";
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function splitFirst(value: string): { first: string; rest: string } {
|
|
313
|
+
const trimmed = value.trim();
|
|
314
|
+
const match = trimmed.match(/^(\S+)(?:\s+([\s\S]+))?$/);
|
|
315
|
+
return { first: match?.[1] || "", rest: match?.[2]?.trim() || "" };
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function contextString(value: unknown): string | undefined {
|
|
319
|
+
if (typeof value === "string" && value.trim()) return value.trim();
|
|
320
|
+
if (typeof value === "number" && Number.isFinite(value)) return String(value);
|
|
321
|
+
return undefined;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function formatDate(value: RemoteCodeRunSummary["updatedAt"]): string | null {
|
|
325
|
+
if (!value) return null;
|
|
326
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
327
|
+
if (Number.isNaN(date.getTime())) return null;
|
|
328
|
+
return date.toISOString();
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
async function tryCoreRemoteCommandHelper(
|
|
332
|
+
envelope: RemoteCodeCommandEnvelope,
|
|
333
|
+
): Promise<RemoteCodeCommandResult | null> {
|
|
334
|
+
const core = (await import("@agent-native/core/server")) as Record<
|
|
335
|
+
string,
|
|
336
|
+
unknown
|
|
337
|
+
>;
|
|
338
|
+
const helper =
|
|
339
|
+
core.enqueueRemoteCommand ||
|
|
340
|
+
core.enqueueIntegrationRemoteCommand ||
|
|
341
|
+
core.enqueueRemoteIntegrationCommand;
|
|
342
|
+
if (typeof helper !== "function") return null;
|
|
343
|
+
return normalizeRemoteCodeCommandResult(await helper(envelope));
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function normalizeRemoteCodeCommandResult(
|
|
347
|
+
value: unknown,
|
|
348
|
+
): RemoteCodeCommandResult {
|
|
349
|
+
if (!value || typeof value !== "object") return { ok: true };
|
|
350
|
+
const result = value as RemoteCodeCommandResult;
|
|
351
|
+
return {
|
|
352
|
+
ok: result.ok,
|
|
353
|
+
status: contextString(result.status),
|
|
354
|
+
hostOnline:
|
|
355
|
+
typeof result.hostOnline === "boolean" ? result.hostOnline : undefined,
|
|
356
|
+
hostStatus: contextString(result.hostStatus),
|
|
357
|
+
commandId: contextString(result.commandId),
|
|
358
|
+
requestId: contextString(result.requestId),
|
|
359
|
+
runId: contextString(result.runId),
|
|
360
|
+
run: result.run,
|
|
361
|
+
runs: Array.isArray(result.runs) ? result.runs : undefined,
|
|
362
|
+
message: contextString(result.message),
|
|
363
|
+
error: contextString(result.error),
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function resolveRemoteRelayBaseUrl(): string {
|
|
368
|
+
const raw =
|
|
369
|
+
process.env.WEBHOOK_BASE_URL ||
|
|
370
|
+
process.env.APP_URL ||
|
|
371
|
+
process.env.URL ||
|
|
372
|
+
"http://localhost:3000";
|
|
373
|
+
if (/^https?:\/\//i.test(raw)) return raw.replace(/\/$/, "");
|
|
374
|
+
return `https://${raw.replace(/\/$/, "")}`;
|
|
375
|
+
}
|
|
@@ -423,7 +423,7 @@ async function notifyApprovers(requestId: string, summary: string) {
|
|
|
423
423
|
}).catch(() => {});
|
|
424
424
|
}
|
|
425
425
|
|
|
426
|
-
async function createApprovalRequest(input: {
|
|
426
|
+
export async function createApprovalRequest(input: {
|
|
427
427
|
changeType: string;
|
|
428
428
|
targetType: string;
|
|
429
429
|
targetId?: string | null;
|
|
@@ -564,6 +564,42 @@ async function applyApprovedRequest(request: DispatchApprovalRequest) {
|
|
|
564
564
|
request.reviewedBy || currentOwnerEmail(),
|
|
565
565
|
);
|
|
566
566
|
}
|
|
567
|
+
if (request.changeType === "dream-proposal.apply") {
|
|
568
|
+
const { applyApprovedDreamProposal } = await import("./dreams-store.js");
|
|
569
|
+
return applyApprovedDreamProposal(
|
|
570
|
+
payload.proposalId,
|
|
571
|
+
request.reviewedBy || currentOwnerEmail(),
|
|
572
|
+
requestCtx,
|
|
573
|
+
);
|
|
574
|
+
}
|
|
575
|
+
if (request.changeType === "workspace-resource.create") {
|
|
576
|
+
const { applyWorkspaceResourceCreate } =
|
|
577
|
+
await import("./workspace-resources-store.js");
|
|
578
|
+
return applyWorkspaceResourceCreate(
|
|
579
|
+
payload.input,
|
|
580
|
+
request.reviewedBy || currentOwnerEmail(),
|
|
581
|
+
requestCtx,
|
|
582
|
+
);
|
|
583
|
+
}
|
|
584
|
+
if (request.changeType === "workspace-resource.update") {
|
|
585
|
+
const { applyWorkspaceResourceUpdate } =
|
|
586
|
+
await import("./workspace-resources-store.js");
|
|
587
|
+
return applyWorkspaceResourceUpdate(
|
|
588
|
+
payload.id,
|
|
589
|
+
payload.input,
|
|
590
|
+
request.reviewedBy || currentOwnerEmail(),
|
|
591
|
+
requestCtx,
|
|
592
|
+
);
|
|
593
|
+
}
|
|
594
|
+
if (request.changeType === "workspace-resource.delete") {
|
|
595
|
+
const { applyWorkspaceResourceDelete } =
|
|
596
|
+
await import("./workspace-resources-store.js");
|
|
597
|
+
return applyWorkspaceResourceDelete(
|
|
598
|
+
payload.id,
|
|
599
|
+
request.reviewedBy || currentOwnerEmail(),
|
|
600
|
+
requestCtx,
|
|
601
|
+
);
|
|
602
|
+
}
|
|
567
603
|
throw new Error(`Unsupported approval request type: ${request.changeType}`);
|
|
568
604
|
}
|
|
569
605
|
|