stageflow 0.36.0 → 0.39.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 +18 -8
- package/dist/agent/claudeAdapter.js +6 -0
- package/dist/agent/piAdapter.d.ts +9 -1
- package/dist/agent/piAdapter.js +21 -2
- package/dist/agent/piIsolatedMcp.js +14 -2
- package/dist/agent/piIsolatedMcpProbe.d.ts +5 -1
- package/dist/agent/piIsolatedMcpProbe.js +16 -30
- package/dist/agent/port.d.ts +5 -0
- package/dist/agents/agentId.d.ts +8 -0
- package/dist/agents/agentId.js +27 -0
- package/dist/agents/approvalBroker.d.ts +16 -0
- package/dist/agents/approvalBroker.js +45 -0
- package/dist/agents/authorChat.d.ts +19 -0
- package/dist/agents/authorChat.js +63 -0
- package/dist/agents/chatTurn.d.ts +23 -0
- package/dist/agents/chatTurn.js +77 -0
- package/dist/agents/communications/agentmail.d.ts +19 -0
- package/dist/agents/communications/agentmail.js +46 -0
- package/dist/agents/communications/agentmailListener.d.ts +59 -0
- package/dist/agents/communications/agentmailListener.js +183 -0
- package/dist/agents/communications/allowlist.d.ts +10 -0
- package/dist/agents/communications/allowlist.js +36 -0
- package/dist/agents/communications/channelConnections.d.ts +3 -0
- package/dist/agents/communications/channelConnections.js +51 -0
- package/dist/agents/communications/delivery.d.ts +53 -0
- package/dist/agents/communications/delivery.js +142 -0
- package/dist/agents/communications/email.d.ts +26 -0
- package/dist/agents/communications/email.js +20 -0
- package/dist/agents/communications/identity.d.ts +13 -0
- package/dist/agents/communications/identity.js +18 -0
- package/dist/agents/communications/imessageListener.d.ts +41 -0
- package/dist/agents/communications/imessageListener.js +92 -0
- package/dist/agents/communications/inbound.d.ts +63 -0
- package/dist/agents/communications/inbound.js +143 -0
- package/dist/agents/communications/listener.d.ts +36 -0
- package/dist/agents/communications/listener.js +38 -0
- package/dist/agents/communications/photon.d.ts +19 -0
- package/dist/agents/communications/photon.js +38 -0
- package/dist/agents/communications/port.d.ts +27 -0
- package/dist/agents/communications/port.js +6 -0
- package/dist/agents/communications/slack.d.ts +28 -0
- package/dist/agents/communications/slack.js +65 -0
- package/dist/agents/communications/supervisor.d.ts +36 -0
- package/dist/agents/communications/supervisor.js +96 -0
- package/dist/agents/communications/telegram.d.ts +19 -0
- package/dist/agents/communications/telegram.js +52 -0
- package/dist/agents/definitionSummary.d.ts +13 -0
- package/dist/agents/definitionSummary.js +91 -0
- package/dist/agents/delegate.d.ts +3 -0
- package/dist/agents/delegate.js +8 -0
- package/dist/agents/errors.d.ts +10 -0
- package/dist/agents/errors.js +32 -0
- package/dist/agents/guardrails.d.ts +24 -0
- package/dist/agents/guardrails.js +39 -0
- package/dist/agents/historyFit.d.ts +10 -0
- package/dist/agents/historyFit.js +26 -0
- package/dist/agents/hostContext.d.ts +54 -0
- package/dist/agents/hostContext.js +156 -0
- package/dist/agents/ledgers.d.ts +29 -0
- package/dist/agents/ledgers.js +31 -0
- package/dist/agents/lineDiff.d.ts +12 -0
- package/dist/agents/lineDiff.js +46 -0
- package/dist/agents/ndjson.d.ts +9 -0
- package/dist/agents/ndjson.js +47 -0
- package/dist/agents/otlpExport.d.ts +9 -0
- package/dist/agents/otlpExport.js +58 -0
- package/dist/agents/otlpSink.d.ts +7 -0
- package/dist/agents/otlpSink.js +23 -0
- package/dist/agents/policy.d.ts +31 -0
- package/dist/agents/policy.js +84 -0
- package/dist/agents/projectRoot.d.ts +5 -0
- package/dist/agents/projectRoot.js +15 -0
- package/dist/agents/runtime/piRuntime.d.ts +52 -0
- package/dist/agents/runtime/piRuntime.js +474 -0
- package/dist/agents/runtime/types.d.ts +77 -0
- package/dist/agents/runtime/types.js +26 -0
- package/dist/agents/sandbox.d.ts +17 -0
- package/dist/agents/sandbox.js +10 -0
- package/dist/agents/scheduleSource.d.ts +32 -0
- package/dist/agents/scheduleSource.js +100 -0
- package/dist/agents/service.d.ts +298 -0
- package/dist/agents/service.js +1105 -0
- package/dist/agents/sessions.d.ts +38 -0
- package/dist/agents/sessions.js +321 -0
- package/dist/agents/store.d.ts +277 -0
- package/dist/agents/store.js +643 -0
- package/dist/agents/trace.d.ts +27 -0
- package/dist/agents/trace.js +29 -0
- package/dist/agents/turnLifecycle.d.ts +72 -0
- package/dist/agents/turnLifecycle.js +157 -0
- package/dist/agents/webChat/publicChat.d.ts +36 -0
- package/dist/agents/webChat/publicChat.js +290 -0
- package/dist/agents/webChat/rateLimit.d.ts +15 -0
- package/dist/agents/webChat/rateLimit.js +33 -0
- package/dist/agents/webChat/slug.d.ts +2 -0
- package/dist/agents/webChat/slug.js +9 -0
- package/dist/channels/providers/agentmail.js +14 -1
- package/dist/channels/providers/photonImessage.js +36 -0
- package/dist/channels/providers/slack.js +2 -1
- package/dist/channels/providers/telegram.js +2 -1
- package/dist/channels/types.d.ts +27 -0
- package/dist/cli/agentsCommand.d.ts +14 -0
- package/dist/cli/agentsCommand.js +291 -0
- package/dist/cli.js +9 -0
- package/dist/config/browseCatalog.js +4 -11
- package/dist/config/draftPackage.d.ts +1 -0
- package/dist/config/draftPackage.js +4 -0
- package/dist/config/hostConfig.js +7 -0
- package/dist/config/loadAgent.d.ts +266 -0
- package/dist/config/loadAgent.js +374 -0
- package/dist/config/loadPipeline.js +5 -2
- package/dist/config/mcpHostCatalog.d.ts +50 -0
- package/dist/config/mcpHostCatalog.js +405 -0
- package/dist/config/mcpOAuth.d.ts +37 -0
- package/dist/config/mcpOAuth.js +263 -0
- package/dist/config/mcpRedaction.d.ts +42 -0
- package/dist/config/mcpRedaction.js +314 -0
- package/dist/config/mcpToolCache.d.ts +20 -0
- package/dist/config/mcpToolCache.js +43 -0
- package/dist/config/mergePipelineIncludes.d.ts +2 -0
- package/dist/config/mergePipelineIncludes.js +25 -0
- package/dist/config/resolveStageMcpServers.d.ts +35 -3
- package/dist/config/resolveStageMcpServers.js +124 -9
- package/dist/config/validateCatalog.d.ts +1 -1
- package/dist/config/validateCatalog.js +22 -1
- package/dist/mcp/agentTools.d.ts +8 -0
- package/dist/mcp/agentTools.js +132 -0
- package/dist/mcp/catalogTools.js +6 -9
- package/dist/mcp/deps.d.ts +6 -0
- package/dist/mcp/pipelineAuthorSupport.d.ts +19 -0
- package/dist/mcp/pipelineAuthorSupport.js +10 -0
- package/dist/mcp/pipelineAuthorTools.d.ts +8 -0
- package/dist/mcp/pipelineAuthorTools.js +109 -0
- package/dist/mcp/server.js +5 -0
- package/dist/mcp/tools.js +4 -0
- package/dist/mcp/triggerTools.js +32 -0
- package/dist/memory/access.d.ts +36 -0
- package/dist/memory/access.js +116 -0
- package/dist/memory/agentIssues.d.ts +3 -0
- package/dist/memory/agentIssues.js +23 -0
- package/dist/memory/bashDeny.d.ts +6 -0
- package/dist/memory/bashDeny.js +35 -0
- package/dist/memory/host.d.ts +51 -0
- package/dist/memory/host.js +394 -0
- package/dist/memory/ids.d.ts +15 -0
- package/dist/memory/ids.js +31 -0
- package/dist/memory/worker.d.ts +1 -0
- package/dist/memory/worker.js +84 -0
- package/dist/models/catalog.d.ts +113 -0
- package/dist/models/catalog.js +238 -0
- package/dist/models/response.d.ts +29 -0
- package/dist/models/response.js +25 -0
- package/dist/operatorAgent/agentAuthorContext.d.ts +17 -0
- package/dist/operatorAgent/agentAuthorContext.js +32 -0
- package/dist/operatorAgent/fakeHost.js +2 -0
- package/dist/operatorAgent/host.js +6 -3
- package/dist/operatorAgent/index.d.ts +4 -0
- package/dist/operatorAgent/index.js +4 -0
- package/dist/operatorAgent/piModel.js +2 -0
- package/dist/operatorAgent/profiles/agentAuthor.d.ts +11 -0
- package/dist/operatorAgent/profiles/agentAuthor.js +192 -0
- package/dist/operatorAgent/profiles/agentAuthorPlaybook.d.ts +1 -0
- package/dist/operatorAgent/profiles/agentAuthorPlaybook.js +29 -0
- package/dist/operatorAgent/profiles/workshopAuthor.js +2 -2
- package/dist/operatorAgent/profiles/workshopAuthorTools.js +3 -3
- package/dist/operatorAgent/proposals.d.ts +1 -0
- package/dist/operatorAgent/proposals.js +23 -2
- package/dist/package-meta.d.ts +1 -1
- package/dist/package-meta.js +1 -1
- package/dist/preflight/pipelinePreflight.js +1 -0
- package/dist/runstore/createStore.d.ts +2 -0
- package/dist/runstore/createStore.js +8 -0
- package/dist/runstore/overviewSummary.d.ts +56 -0
- package/dist/runstore/overviewSummary.js +134 -0
- package/dist/runstore/port.d.ts +3 -0
- package/dist/runstore/sqlite/SqliteRunStore.d.ts +2 -0
- package/dist/runstore/sqlite/SqliteRunStore.js +18 -0
- package/dist/runstore/sqlite/migrations/013-agents.d.ts +12 -0
- package/dist/runstore/sqlite/migrations/013-agents.js +156 -0
- package/dist/runstore/sqlite/migrations/014-agent-deliveries.d.ts +11 -0
- package/dist/runstore/sqlite/migrations/014-agent-deliveries.js +32 -0
- package/dist/runstore/sqlite/migrations/index.js +4 -0
- package/dist/runtime/stageAttemptBootstrap.js +15 -1
- package/dist/runtime/stageEnvironment.d.ts +2 -0
- package/dist/runtime/stageEnvironment.js +6 -1
- package/dist/runtime/stageProcessLauncher.js +23 -0
- package/dist/runtime/stageSecrets.d.ts +6 -1
- package/dist/runtime/stageSecrets.js +34 -1
- package/dist/server/agentAuthorRoutes.d.ts +5 -0
- package/dist/server/agentAuthorRoutes.js +64 -0
- package/dist/server/agentRoutes.d.ts +5 -0
- package/dist/server/agentRoutes.js +323 -0
- package/dist/server/bootstrap.d.ts +8 -0
- package/dist/server/bootstrap.js +36 -10
- package/dist/server/controlToken.js +8 -0
- package/dist/server/http.d.ts +8 -1
- package/dist/server/http.js +172 -20
- package/dist/server/mcpHost.js +1 -0
- package/dist/server/memoryRoutes.d.ts +5 -0
- package/dist/server/memoryRoutes.js +146 -0
- package/dist/server/projectMcpRoutes.d.ts +1 -0
- package/dist/server/projectMcpRoutes.js +241 -3
- package/dist/toolkit/guard.d.ts +27 -0
- package/dist/toolkit/guard.js +98 -0
- package/dist/toolkit/index.d.ts +48 -0
- package/dist/toolkit/index.js +82 -0
- package/dist/toolkit/meta.d.ts +36 -0
- package/dist/toolkit/meta.js +117 -0
- package/dist/toolkit/profiles.d.ts +13 -0
- package/dist/toolkit/profiles.js +25 -0
- package/dist/toolkit/record.d.ts +15 -0
- package/dist/toolkit/record.js +17 -0
- package/dist/tools/memoryTools.d.ts +95 -0
- package/dist/tools/memoryTools.js +179 -0
- package/dist/tools/readEmail.d.ts +3 -3
- package/dist/types/pipeline.d.ts +3 -0
- package/dist/types/stage.d.ts +2 -0
- package/dist/ui/assets/AssistantRuntimeProvider-CsiPFhCT.js +9 -0
- package/dist/ui/assets/MarkdownText-D7tYaIFO.js +29 -0
- package/dist/ui/assets/ReadonlyStore-BfXqHykm.js +6 -0
- package/dist/ui/assets/Tooltip-DrpoRRNi.js +1 -0
- package/dist/ui/assets/chat-BbLukq9W.js +5 -0
- package/dist/ui/assets/chat-zIZU_bP1.css +2 -0
- package/dist/ui/assets/jsx-runtime-iVoFAvQ_.js +1 -0
- package/dist/ui/assets/{index-Clv4sJpU.css → main-BFo79krC.css} +1 -1
- package/dist/ui/assets/main-CijYMeM0.js +259 -0
- package/dist/ui/chat.html +25 -0
- package/dist/ui/index.html +6 -2
- package/dist/workshop/modelSettings.d.ts +2 -2
- package/dist/workshop/modelSettings.js +5 -11
- package/examples/README.md +1 -0
- package/examples/channels-smoke/README.md +12 -1
- package/examples/github-release/README.md +1 -1
- package/examples/memory/README.md +99 -0
- package/examples/memory/desk-outage.task.yaml +15 -0
- package/examples/memory/desk-refund.task.yaml +15 -0
- package/examples/memory/desk-revisit-outage.task.yaml +11 -0
- package/examples/memory/desk-revisit.pipeline.yaml +123 -0
- package/examples/memory/desk.pipeline.yaml +192 -0
- package/examples/memory/file-card.pipeline.yaml +39 -0
- package/examples/memory/file-ledger.task.yaml +22 -0
- package/examples/memory/file-release.task.yaml +24 -0
- package/examples/memory/file-rich.pipeline.yaml +40 -0
- package/examples/memory/file-ticket.task.yaml +7 -0
- package/examples/memory/file-timeline.task.yaml +28 -0
- package/examples/memory/find-card.pipeline.yaml +46 -0
- package/examples/memory/find-ticket.task.yaml +7 -0
- package/examples/memory/missing.pipeline.yaml +16 -0
- package/examples/memory/recall.pipeline.yaml +24 -0
- package/examples/memory/recall.task.yaml +5 -0
- package/examples/memory/remember-ada.task.yaml +6 -0
- package/examples/memory/remember-grace.task.yaml +6 -0
- package/examples/memory/remember.pipeline.yaml +58 -0
- package/examples/memory/stageflow.yaml +12 -0
- package/examples/memory/update-card.pipeline.yaml +43 -0
- package/examples/memory/update-ledger.task.yaml +23 -0
- package/examples/memory/update-rich.pipeline.yaml +44 -0
- package/examples/memory/update-ticket.task.yaml +7 -0
- package/examples/stage-mcp/README.md +1 -1
- package/package.json +2 -1
- package/dist/ui/assets/index-BOhqnPNg.js +0 -260
|
@@ -10,10 +10,13 @@ import { RunManager } from "../runtime/runManager.js";
|
|
|
10
10
|
import { emailHostFor } from "../email/host.js";
|
|
11
11
|
import { EmailTriggers } from "../email/triggers.js";
|
|
12
12
|
import { type ChannelService } from "../channels/service.js";
|
|
13
|
+
import { type MemoryHost } from "../memory/host.js";
|
|
13
14
|
import { type RunChangeBus } from "../runtime/runChangeBus.js";
|
|
14
15
|
import { type McpHttpHandler } from "../mcp/server.js";
|
|
15
16
|
import { type HostConfig } from "../config/hostConfig.js";
|
|
16
17
|
import { bootProviderConfig } from "../agent/bootProviderConfig.js";
|
|
18
|
+
import { type AgentHostContext } from "../agents/hostContext.js";
|
|
19
|
+
import type { ConversationBackend } from "../agents/sessions.js";
|
|
17
20
|
export declare const DEFAULT_GC_INTERVAL_MS: number;
|
|
18
21
|
export type StageflowHostOptions = {
|
|
19
22
|
agent: StagePort;
|
|
@@ -33,6 +36,7 @@ export type StageflowHostOptions = {
|
|
|
33
36
|
/** Browser support for the runs this Host executes; defaults to the local host with the live view relay. */
|
|
34
37
|
browser?: StageBrowserSupport;
|
|
35
38
|
emailTriggerQueue?: ConstructorParameters<typeof EmailTriggers>[0]["queue"];
|
|
39
|
+
agentConversationBackend?: ConversationBackend;
|
|
36
40
|
};
|
|
37
41
|
export type StageflowHostBootstrap = {
|
|
38
42
|
a2a?: A2aHost;
|
|
@@ -62,6 +66,10 @@ export type StageflowHostBootstrap = {
|
|
|
62
66
|
stopEmailSource: () => void;
|
|
63
67
|
/** Connections page read model and background health checks. */
|
|
64
68
|
channels?: ChannelService;
|
|
69
|
+
/** Named markdown pages under `$STAGEFLOW_HOME/memories`. */
|
|
70
|
+
memories?: MemoryHost;
|
|
71
|
+
/** Shared agent store, chat controller, and conversation backend for HTTP/MCP. */
|
|
72
|
+
agentHost?: AgentHostContext;
|
|
65
73
|
email?: ReturnType<typeof emailHostFor> & {
|
|
66
74
|
triggers: EmailTriggers;
|
|
67
75
|
stop: () => Promise<void>;
|
package/dist/server/bootstrap.js
CHANGED
|
@@ -2,7 +2,7 @@ import path from "node:path";
|
|
|
2
2
|
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
3
3
|
import { createA2aHost } from "../a2a/server.js";
|
|
4
4
|
import { A2aStore } from "../a2a/store.js";
|
|
5
|
-
import { createRunStoreWithConnection } from "../runstore/createStore.js";
|
|
5
|
+
import { createRunStoreWithConnection, sqliteConnectionFromStore } from "../runstore/createStore.js";
|
|
6
6
|
import { resolveA2aConfigPath } from "../a2a/configDiscovery.js";
|
|
7
7
|
import { ensureProjectRootsFromEnv } from "../project/ensureProjectRootsFromEnv.js";
|
|
8
8
|
import { ensureGlobalHome, globalStageflowHome } from "../project/globalHome.js";
|
|
@@ -21,6 +21,7 @@ import { EmailSource } from "../runtime/emailSource.js";
|
|
|
21
21
|
import { emailHostFor, releaseEmailHost } from "../email/host.js";
|
|
22
22
|
import { EmailTriggers } from "../email/triggers.js";
|
|
23
23
|
import { startChannelService } from "../channels/service.js";
|
|
24
|
+
import { memoryHostFor } from "../memory/host.js";
|
|
24
25
|
import { fireTrigger } from "../runtime/triggerRunner.js";
|
|
25
26
|
import { createRunChangeBus, getRunChangeBusFromWrappedStore, isRunStoreWrapped, wrapRunStoreWithChangeBus, } from "../runtime/runChangeBus.js";
|
|
26
27
|
import { createMcpHttpHandler, resolveMcpStateless, } from "../mcp/server.js";
|
|
@@ -30,16 +31,9 @@ import { readCursorApiKey } from "../agent/cursorProvider.js";
|
|
|
30
31
|
import { applyPendingRestoreAtBoot, } from "../runstore/restore.js";
|
|
31
32
|
import { assertTmpdirUsable } from "../runstore/assertTmpdir.js";
|
|
32
33
|
import { logger as rootLogger } from "../logging/logger.js";
|
|
34
|
+
import { getModelCatalog } from "../models/catalog.js";
|
|
35
|
+
import { createAgentHostContext } from "../agents/hostContext.js";
|
|
33
36
|
export const DEFAULT_GC_INTERVAL_MS = 60 * 60 * 1000;
|
|
34
|
-
function sqliteConnectionFromStore(store) {
|
|
35
|
-
const connection = store.connection;
|
|
36
|
-
if (connection !== undefined &&
|
|
37
|
-
connection !== null &&
|
|
38
|
-
typeof connection.prepare === "function") {
|
|
39
|
-
return connection;
|
|
40
|
-
}
|
|
41
|
-
return undefined;
|
|
42
|
-
}
|
|
43
37
|
/** Parse `STAGEFLOW_GC_INTERVAL_MS`; default 1h; `0` disables. */
|
|
44
38
|
export function gcIntervalMsFromEnv(env = process.env) {
|
|
45
39
|
const raw = env.STAGEFLOW_GC_INTERVAL_MS;
|
|
@@ -334,10 +328,33 @@ export async function bootstrapStageflowHost(options) {
|
|
|
334
328
|
}
|
|
335
329
|
const channels = startChannelService(cwd);
|
|
336
330
|
const stopEmailSource = () => {
|
|
331
|
+
void agentHost?.stop();
|
|
337
332
|
void emailSource.stop();
|
|
338
333
|
void stopAccountEmail();
|
|
339
334
|
void channels.stop();
|
|
340
335
|
};
|
|
336
|
+
// Resolve the connected-provider list and the default model once at boot so sync callers see the catalog's answer.
|
|
337
|
+
void getModelCatalog(cwd)
|
|
338
|
+
.defaultModel()
|
|
339
|
+
.catch((error) => bootLog.warn("models.catalog_warm_failed", String(error)));
|
|
340
|
+
const agentHost = sqliteConnection !== undefined
|
|
341
|
+
? createAgentHostContext({
|
|
342
|
+
cwd,
|
|
343
|
+
agentDir,
|
|
344
|
+
connection: sqliteConnection,
|
|
345
|
+
channels,
|
|
346
|
+
...(options.agentConversationBackend ? { backend: options.agentConversationBackend } : {}),
|
|
347
|
+
listenOnChannels: process.env.STAGEFLOW_AGENT_CHANNEL_LISTENERS !== "0",
|
|
348
|
+
toolkitDeps: {
|
|
349
|
+
manager,
|
|
350
|
+
store,
|
|
351
|
+
agentDir,
|
|
352
|
+
projectRoot: rootDir,
|
|
353
|
+
...(options.providerAuthContext ? { providerAuthContext: options.providerAuthContext } : {}),
|
|
354
|
+
...(providerBoot !== undefined ? { providerBoot } : {}),
|
|
355
|
+
},
|
|
356
|
+
})
|
|
357
|
+
: undefined;
|
|
341
358
|
const mcpStateless = resolveMcpStateless({
|
|
342
359
|
mcpStateless: options.mcpStateless,
|
|
343
360
|
});
|
|
@@ -349,6 +366,13 @@ export async function bootstrapStageflowHost(options) {
|
|
|
349
366
|
runChangeBus,
|
|
350
367
|
providerAuthContext: options.providerAuthContext,
|
|
351
368
|
projectRoot: rootDir,
|
|
369
|
+
...(agentHost
|
|
370
|
+
? {
|
|
371
|
+
agentService: agentHost.service,
|
|
372
|
+
agentChat: agentHost.chat,
|
|
373
|
+
agentConversationBackend: agentHost.backend,
|
|
374
|
+
}
|
|
375
|
+
: {}),
|
|
352
376
|
...(providerBoot !== undefined ? { providerBoot } : {}),
|
|
353
377
|
}, { mcpStateless });
|
|
354
378
|
return {
|
|
@@ -378,7 +402,9 @@ export async function bootstrapStageflowHost(options) {
|
|
|
378
402
|
stopEmailSource,
|
|
379
403
|
email: { ...emailHost, triggers: emailTriggers, stop: stopAccountEmail },
|
|
380
404
|
channels,
|
|
405
|
+
memories: memoryHostFor(),
|
|
381
406
|
storeFilesystem,
|
|
382
407
|
workspaceCatalog,
|
|
408
|
+
...(agentHost ? { agentHost } : {}),
|
|
383
409
|
};
|
|
384
410
|
}
|
|
@@ -175,6 +175,10 @@ export class BindRefusedError extends Error {
|
|
|
175
175
|
function isTriggerWebhookRoute(method, pathname) {
|
|
176
176
|
return method === "POST" && /^\/api\/triggers\/[^/]+\/webhook$/.test(pathname);
|
|
177
177
|
}
|
|
178
|
+
/** Agent webhooks authenticate by the provider's own signature against the connection's inbound secret. */
|
|
179
|
+
function isAgentInboundRoute(method, pathname) {
|
|
180
|
+
return method === "POST" && /^\/api\/agent-inbound\/[^/]+$/.test(pathname);
|
|
181
|
+
}
|
|
178
182
|
/**
|
|
179
183
|
* Live view routes authenticate themselves: the ticket route checks the bearer
|
|
180
184
|
* scope for the requested mode, and the browser's event stream and input posts
|
|
@@ -190,6 +194,10 @@ export function requiredScopeFor(method, pathname) {
|
|
|
190
194
|
return null;
|
|
191
195
|
if (isTriggerWebhookRoute(method, pathname))
|
|
192
196
|
return null;
|
|
197
|
+
if (isAgentInboundRoute(method, pathname))
|
|
198
|
+
return null;
|
|
199
|
+
if (pathname.startsWith("/api/public/chat/"))
|
|
200
|
+
return null;
|
|
193
201
|
if (isLiveViewRoute(pathname))
|
|
194
202
|
return null;
|
|
195
203
|
if (pathname === "/api/backup" || pathname.startsWith("/api/backup/")) {
|
package/dist/server/http.d.ts
CHANGED
|
@@ -3,11 +3,13 @@ import { type ProviderAuthContext } from "../agent/providerAuth.js";
|
|
|
3
3
|
import { type LiveViewRoutesOptions } from "./liveViewRoutes.js";
|
|
4
4
|
import { WorkshopChatSessionRegistry } from "../workshop/chatTurn.js";
|
|
5
5
|
import { type AgentPort } from "../operatorAgent/index.js";
|
|
6
|
-
import type
|
|
6
|
+
import { type RunStoreKind } from "../runstore/createStore.js";
|
|
7
7
|
import type { RunStore } from "../runstore/port.js";
|
|
8
8
|
import type { RunManager } from "../runtime/runManager.js";
|
|
9
9
|
import type { RunChangeBus } from "../runtime/runChangeBus.js";
|
|
10
10
|
import type { StageBrowserSupport } from "../browser/browserHost.js";
|
|
11
|
+
import { type AgentHostContext } from "../agents/hostContext.js";
|
|
12
|
+
import type { ConversationBackend } from "../agents/sessions.js";
|
|
11
13
|
import { EmailTriggers } from "../email/triggers.js";
|
|
12
14
|
import { type AllowedHosts } from "./allowedHosts.js";
|
|
13
15
|
import { type ControlTokens } from "./controlToken.js";
|
|
@@ -33,6 +35,7 @@ export type UiServerOptions = {
|
|
|
33
35
|
browser?: StageBrowserSupport;
|
|
34
36
|
/** Optional AgentPort. Workshop is the caller today; tests inject a fake. */
|
|
35
37
|
workshopOperatorHost?: AgentPort;
|
|
38
|
+
agentConversationBackend?: ConversationBackend;
|
|
36
39
|
};
|
|
37
40
|
export declare function defaultUiDistDir(): string;
|
|
38
41
|
export type OperatorRouteDeps = {
|
|
@@ -61,6 +64,10 @@ export type OperatorRouteDeps = {
|
|
|
61
64
|
* Defaults to a registry owned by this route surface.
|
|
62
65
|
*/
|
|
63
66
|
workshopChatRegistry?: WorkshopChatSessionRegistry;
|
|
67
|
+
/** Injectable agent chat backend (tests). Defaults to Pi conversation host. */
|
|
68
|
+
agentConversationBackend?: ConversationBackend;
|
|
69
|
+
/** Shared with MCP when set by bootstrap. */
|
|
70
|
+
agentHost?: AgentHostContext;
|
|
64
71
|
};
|
|
65
72
|
/**
|
|
66
73
|
* The full operator-console route surface: REST API (runs, stages,
|
package/dist/server/http.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { OVERVIEW_RANGES, buildOverview, isOverviewRange, overviewWindowMs, } from "../runstore/overviewSummary.js";
|
|
1
2
|
import { readFile, access } from "node:fs/promises";
|
|
2
3
|
import { createReadStream, statSync } from "node:fs";
|
|
3
4
|
import path from "node:path";
|
|
@@ -25,7 +26,7 @@ import { createWorkshopBuild, focusUnboundWorkshopBuild, getWorkshopBuild, listW
|
|
|
25
26
|
import { resolveAgentPort } from "../operatorAgent/index.js";
|
|
26
27
|
import { listPipelinesForContext } from "../config/browseCatalog.js";
|
|
27
28
|
import { catalogContextFromStageflow } from "../config/resolveCatalogContext.js";
|
|
28
|
-
import {
|
|
29
|
+
import { listPipelinesMultiProject, listTasksMultiProject, } from "../config/multiProjectCatalog.js";
|
|
29
30
|
import { catalogPathErrorBody, CatalogPathError, resolveCatalogRelativePath, resolveCatalogStartInput, resolveDefaultWriteRoot, resolveWritableCatalogRoot, } from "../config/catalogRelativePath.js";
|
|
30
31
|
import { listExtensions } from "../config/listExtensions.js";
|
|
31
32
|
import { listSkills } from "../config/listSkills.js";
|
|
@@ -36,6 +37,7 @@ import { validateCatalog } from "../config/validateCatalog.js";
|
|
|
36
37
|
import { artifactMediaType, classifyArtifactContent, readRunArtifact, readRunArtifactBytes, } from "../mcp/readArtifact.js";
|
|
37
38
|
import { getRunDiff, listCheckoutChanges, readCheckoutFileBytes, } from "../mcp/checkoutTools.js";
|
|
38
39
|
import { readStageVerificationHistory } from "../runstore/verificationHistory.js";
|
|
40
|
+
import { sqliteConnectionFromStore } from "../runstore/createStore.js";
|
|
39
41
|
import { BackupError, createBackup, resolveBackupDownloadPath, } from "../runstore/backup.js";
|
|
40
42
|
import { RestoreError, resolveBackupNameForRestore, stageRestoreForBoot, } from "../runstore/restore.js";
|
|
41
43
|
import { iterateExportNdjson } from "../cli/exportAllCommand.js";
|
|
@@ -51,7 +53,15 @@ import { isTaskFile } from "../runtime/taskInput.js";
|
|
|
51
53
|
import { findTokenShapedField, tokenRejectedPayload, } from "../runtime/startPayload.js";
|
|
52
54
|
import { parseAskOperatorAnswer } from "../tools/askOperator.js";
|
|
53
55
|
import { handleEmailRoutes } from "./emailRoutes.js";
|
|
56
|
+
import { defaultAgentProjectRoot } from "../agents/projectRoot.js";
|
|
57
|
+
import { PublicWebChat } from "../agents/webChat/publicChat.js";
|
|
58
|
+
import { buildModelsResponse } from "../models/response.js";
|
|
59
|
+
import { createAgentHostContext } from "../agents/hostContext.js";
|
|
60
|
+
import { AgentScheduleSource, agentScheduleTickIntervalMsFromEnv, } from "../agents/scheduleSource.js";
|
|
61
|
+
import { handleAgentAuthorRoutes } from "./agentAuthorRoutes.js";
|
|
62
|
+
import { handleAgentRoutes } from "./agentRoutes.js";
|
|
54
63
|
import { handleChannelRoutes } from "./channelRoutes.js";
|
|
64
|
+
import { handleMemoryRoutes } from "./memoryRoutes.js";
|
|
55
65
|
import { bootstrapStageflowHost, } from "./bootstrap.js";
|
|
56
66
|
import { assertAllowedHttpAccess, isTrustedLocalHttpRequest, resolveAllowedHosts, } from "./allowedHosts.js";
|
|
57
67
|
import { enforceBearerAuth, loadControlTokens, requiredScopeFor, } from "./controlToken.js";
|
|
@@ -173,13 +183,38 @@ export function defaultUiDistDir() {
|
|
|
173
183
|
return path.resolve(here, "../ui");
|
|
174
184
|
}
|
|
175
185
|
export function createOperatorRoutes(deps) {
|
|
176
|
-
const { manager, store, cwd, agentDir, rootDir, providerAuthContext, uiDistDir, workshopOperatorHost, } = deps;
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
|
|
186
|
+
const { manager, store, cwd, agentDir, rootDir, providerAuthContext, uiDistDir, workshopOperatorHost, agentConversationBackend, agentHost: injectedHost, } = deps;
|
|
187
|
+
const agentConnection = injectedHost ? undefined : sqliteConnectionFromStore(store);
|
|
188
|
+
const agentHost = injectedHost ??
|
|
189
|
+
(agentConnection
|
|
190
|
+
? createAgentHostContext({
|
|
180
191
|
cwd,
|
|
181
|
-
|
|
182
|
-
|
|
192
|
+
agentDir,
|
|
193
|
+
connection: agentConnection,
|
|
194
|
+
...(agentConversationBackend ? { backend: agentConversationBackend } : {}),
|
|
195
|
+
})
|
|
196
|
+
: undefined);
|
|
197
|
+
const agentScheduleSource = agentHost
|
|
198
|
+
? new AgentScheduleSource({
|
|
199
|
+
service: agentHost.service,
|
|
200
|
+
projectRoot: defaultAgentProjectRoot(cwd),
|
|
201
|
+
intervalMs: agentScheduleTickIntervalMsFromEnv(),
|
|
202
|
+
})
|
|
203
|
+
: undefined;
|
|
204
|
+
void agentScheduleSource?.start(async (event) => {
|
|
205
|
+
await agentHost.chat.send({
|
|
206
|
+
projectRoot: defaultAgentProjectRoot(cwd),
|
|
207
|
+
agentId: event.agentId,
|
|
208
|
+
text: event.message,
|
|
209
|
+
source: "schedule",
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
const operatorHost = workshopOperatorHost ??
|
|
213
|
+
resolveAgentPort({
|
|
214
|
+
cwd,
|
|
215
|
+
projectRoot: cwd,
|
|
216
|
+
});
|
|
217
|
+
const workshopChatRegistry = deps.workshopChatRegistry ?? new WorkshopChatSessionRegistry(operatorHost);
|
|
183
218
|
const workshopSessionStoreRoot = () => resolveWorkshopSessionStoreRoot();
|
|
184
219
|
const workshopBuildStoreRoot = () => resolveWorkshopBuildStoreRoot();
|
|
185
220
|
const allowedHosts = deps.allowedHosts ?? resolveAllowedHosts();
|
|
@@ -215,6 +250,98 @@ export function createOperatorRoutes(deps) {
|
|
|
215
250
|
try {
|
|
216
251
|
if (await liveView.handle(req, res))
|
|
217
252
|
return true;
|
|
253
|
+
if (pathname === "/api/agent-author/chat" || pathname === "/api/agent-author/undo") {
|
|
254
|
+
if (await handleAgentAuthorRoutes(req, method, pathname, cwd, operatorHost, res, json))
|
|
255
|
+
return true;
|
|
256
|
+
}
|
|
257
|
+
if (PublicWebChat.owns(pathname)) {
|
|
258
|
+
if (!agentHost) {
|
|
259
|
+
json(res, 503, { code: "web_chat.unavailable" });
|
|
260
|
+
return true;
|
|
261
|
+
}
|
|
262
|
+
return await agentHost.publicChat.handle(req, res, method, pathname);
|
|
263
|
+
}
|
|
264
|
+
if (method === "GET" && uiDistDir !== undefined && /^\/chat\/[^/]+$/.test(pathname)) {
|
|
265
|
+
const page = path.join(uiDistDir, "chat.html");
|
|
266
|
+
try {
|
|
267
|
+
const html = await readFile(page);
|
|
268
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "no-store", "Referrer-Policy": "no-referrer", "X-Robots-Tag": "noindex" });
|
|
269
|
+
res.end(html);
|
|
270
|
+
return true;
|
|
271
|
+
}
|
|
272
|
+
catch {
|
|
273
|
+
json(res, 404, { error: "Chat page not built. Run npm run ui:build." });
|
|
274
|
+
return true;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
const agentInbound = method === "POST" ? pathname.match(/^\/api\/agent-inbound\/([^/]+)$/) : null;
|
|
278
|
+
if (agentInbound) {
|
|
279
|
+
if (!agentHost) {
|
|
280
|
+
json(res, 503, { code: "agent.store_unavailable" });
|
|
281
|
+
return true;
|
|
282
|
+
}
|
|
283
|
+
const declared = Number(req.headers["content-length"] ?? 0);
|
|
284
|
+
if (declared > 1024 * 1024) {
|
|
285
|
+
json(res, 413, {});
|
|
286
|
+
return true;
|
|
287
|
+
}
|
|
288
|
+
const chunks = [];
|
|
289
|
+
let received = 0;
|
|
290
|
+
for await (const chunk of req) {
|
|
291
|
+
const buffer = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
|
|
292
|
+
received += buffer.length;
|
|
293
|
+
if (received > 1024 * 1024) {
|
|
294
|
+
json(res, 413, {});
|
|
295
|
+
req.destroy();
|
|
296
|
+
return true;
|
|
297
|
+
}
|
|
298
|
+
chunks.push(buffer);
|
|
299
|
+
}
|
|
300
|
+
const raw = Buffer.concat(chunks);
|
|
301
|
+
const headers = {};
|
|
302
|
+
for (const [name, value] of Object.entries(req.headers))
|
|
303
|
+
headers[name] = Array.isArray(value) ? value[0] : value;
|
|
304
|
+
const outcome = await agentHost.inbound.handle(decodeURIComponent(agentInbound[1]), headers, raw.toString("utf8"));
|
|
305
|
+
json(res, outcome.status, outcome.body ?? {});
|
|
306
|
+
return true;
|
|
307
|
+
}
|
|
308
|
+
if (pathname === "/api/agents" || pathname.startsWith("/api/agents/")) {
|
|
309
|
+
if (!agentHost) {
|
|
310
|
+
json(res, 503, { code: "agent.store_unavailable" });
|
|
311
|
+
return true;
|
|
312
|
+
}
|
|
313
|
+
// Default: the Host's own project. An explicit project_root must be a known catalog root
|
|
314
|
+
// (writable for writes); its filesystem path, not its wire id, is where agents/ lives.
|
|
315
|
+
let agentProjectRoot = defaultAgentProjectRoot(cwd);
|
|
316
|
+
const requestedRoot = url.searchParams.get("project_root");
|
|
317
|
+
if (requestedRoot !== null && path.resolve(requestedRoot) !== path.resolve(agentProjectRoot)) {
|
|
318
|
+
try {
|
|
319
|
+
const resolved = method === "GET"
|
|
320
|
+
? await resolveCatalogStartInput({ store, bootCwd: cwd }, requestedRoot)
|
|
321
|
+
: await resolveWritableCatalogRoot({ store, bootCwd: cwd }, requestedRoot);
|
|
322
|
+
agentProjectRoot = resolved.wireRoot.path;
|
|
323
|
+
}
|
|
324
|
+
catch (err) {
|
|
325
|
+
if (err instanceof CatalogPathError) {
|
|
326
|
+
json(res, 400, catalogPathErrorBody(err));
|
|
327
|
+
return true;
|
|
328
|
+
}
|
|
329
|
+
throw err;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
if (await handleAgentRoutes(req, method, pathname, agentHost, agentProjectRoot, res, json))
|
|
333
|
+
return true;
|
|
334
|
+
}
|
|
335
|
+
if (pathname === "/api/memories" || pathname.startsWith("/api/memories/")) {
|
|
336
|
+
if (!assertAllowedHttpAccess({ entries: [] }, req, res, { requireOrigin: method !== "GET" }))
|
|
337
|
+
return true;
|
|
338
|
+
if (!boot.memories) {
|
|
339
|
+
json(res, 503, { code: "memory.unavailable" });
|
|
340
|
+
return true;
|
|
341
|
+
}
|
|
342
|
+
if (await handleMemoryRoutes(req, method, pathname, boot.memories, res, json))
|
|
343
|
+
return true;
|
|
344
|
+
}
|
|
218
345
|
if (pathname === "/api/connections" || pathname.startsWith("/api/connections/")) {
|
|
219
346
|
if (!assertAllowedHttpAccess({ entries: [] }, req, res, { requireOrigin: method !== "GET" }))
|
|
220
347
|
return true;
|
|
@@ -2730,27 +2857,26 @@ export function createOperatorRoutes(deps) {
|
|
|
2730
2857
|
});
|
|
2731
2858
|
return true;
|
|
2732
2859
|
}
|
|
2733
|
-
if (method === "GET" && pathname === "/api/models") {
|
|
2860
|
+
if ((method === "GET" && pathname === "/api/models") || (method === "POST" && pathname === "/api/models/refresh")) {
|
|
2861
|
+
if (method === "POST" && !assertAllowedHttpAccess({ entries: [] }, req, res, { requireOrigin: true }))
|
|
2862
|
+
return true;
|
|
2734
2863
|
const filter = url.searchParams.get("project_root") ?? undefined;
|
|
2735
|
-
const
|
|
2864
|
+
const { response, used } = await buildModelsResponse({
|
|
2736
2865
|
store,
|
|
2737
|
-
|
|
2866
|
+
cwd,
|
|
2738
2867
|
projectRootFilter: filter,
|
|
2868
|
+
refresh: method === "POST",
|
|
2739
2869
|
});
|
|
2740
|
-
if (
|
|
2741
|
-
|
|
2870
|
+
if (used.root_errors.some((e) => e.code === "unknown_project_root") &&
|
|
2871
|
+
used.items.length === 0) {
|
|
2742
2872
|
json(res, 400, {
|
|
2743
|
-
error:
|
|
2873
|
+
error: used.root_errors[0].message,
|
|
2744
2874
|
code: "unknown_project_root",
|
|
2745
|
-
root_errors:
|
|
2875
|
+
root_errors: used.root_errors,
|
|
2746
2876
|
});
|
|
2747
2877
|
return true;
|
|
2748
2878
|
}
|
|
2749
|
-
json(res, 200,
|
|
2750
|
-
models: result.models,
|
|
2751
|
-
entries: result.items,
|
|
2752
|
-
root_errors: result.root_errors,
|
|
2753
|
-
});
|
|
2879
|
+
json(res, 200, response);
|
|
2754
2880
|
return true;
|
|
2755
2881
|
}
|
|
2756
2882
|
if (await handleProviderRoutes(req, res, {
|
|
@@ -2763,6 +2889,7 @@ export function createOperatorRoutes(deps) {
|
|
|
2763
2889
|
}
|
|
2764
2890
|
if (await handleProjectMcpRoutes(req, res, {
|
|
2765
2891
|
projectRoot: rootDir,
|
|
2892
|
+
readJsonBody,
|
|
2766
2893
|
json,
|
|
2767
2894
|
})) {
|
|
2768
2895
|
return true;
|
|
@@ -2876,6 +3003,20 @@ export function createOperatorRoutes(deps) {
|
|
|
2876
3003
|
await handleApiHealth(req, res, boot);
|
|
2877
3004
|
return true;
|
|
2878
3005
|
}
|
|
3006
|
+
if (method === "GET" && pathname === "/api/overview") {
|
|
3007
|
+
const rangeParam = url.searchParams.get("range") ?? "7d";
|
|
3008
|
+
if (!isOverviewRange(rangeParam)) {
|
|
3009
|
+
json(res, 400, {
|
|
3010
|
+
error: `range must be one of ${OVERVIEW_RANGES.join(", ")}`,
|
|
3011
|
+
code: "invalid_range",
|
|
3012
|
+
});
|
|
3013
|
+
return true;
|
|
3014
|
+
}
|
|
3015
|
+
const now = new Date();
|
|
3016
|
+
const since = new Date(now.getTime() - 2 * overviewWindowMs(rangeParam)).toISOString();
|
|
3017
|
+
json(res, 200, buildOverview(await store.listRunOverviewRows(since), rangeParam, now));
|
|
3018
|
+
return true;
|
|
3019
|
+
}
|
|
2879
3020
|
if (method === "GET" && pathname === "/api/settings") {
|
|
2880
3021
|
const health = manager.getHealth();
|
|
2881
3022
|
const credential = getCredentialSourceSettings(cwd);
|
|
@@ -2972,7 +3113,14 @@ export function createOperatorRoutes(deps) {
|
|
|
2972
3113
|
}
|
|
2973
3114
|
}
|
|
2974
3115
|
};
|
|
2975
|
-
return Object.assign(handler, {
|
|
3116
|
+
return Object.assign(handler, {
|
|
3117
|
+
dispose: async () => {
|
|
3118
|
+
await agentScheduleSource?.stop();
|
|
3119
|
+
if (!injectedHost)
|
|
3120
|
+
await agentHost?.stop();
|
|
3121
|
+
await liveView.dispose();
|
|
3122
|
+
},
|
|
3123
|
+
});
|
|
2976
3124
|
}
|
|
2977
3125
|
export async function startUiServer(options) {
|
|
2978
3126
|
const host = options.host ?? "127.0.0.1";
|
|
@@ -3002,6 +3150,10 @@ export async function startUiServer(options) {
|
|
|
3002
3150
|
...(options.workshopOperatorHost
|
|
3003
3151
|
? { workshopOperatorHost: options.workshopOperatorHost }
|
|
3004
3152
|
: {}),
|
|
3153
|
+
...(boot.agentHost ? { agentHost: boot.agentHost } : {}),
|
|
3154
|
+
...(options.agentConversationBackend
|
|
3155
|
+
? { agentConversationBackend: options.agentConversationBackend }
|
|
3156
|
+
: {}),
|
|
3005
3157
|
});
|
|
3006
3158
|
const envelope = await createHttpHost({
|
|
3007
3159
|
boot,
|
package/dist/server/mcpHost.js
CHANGED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
import { type MemoryHost } from "../memory/host.js";
|
|
3
|
+
type Json = (res: ServerResponse, status: number, body: unknown) => void;
|
|
4
|
+
export declare function handleMemoryRoutes(req: IncomingMessage, method: string, pathname: string, host: MemoryHost, res: ServerResponse, json: Json): Promise<boolean>;
|
|
5
|
+
export {};
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { MemoryError } from "../memory/host.js";
|
|
2
|
+
const MAX_BODY_BYTES = 256 * 1024;
|
|
3
|
+
const STATUS = {
|
|
4
|
+
MEMORY_INVALID_ID: 400,
|
|
5
|
+
MEMORY_INVALID_DESCRIPTION: 400,
|
|
6
|
+
MEMORY_INVALID_KEY: 400,
|
|
7
|
+
MEMORY_TOO_LARGE: 400,
|
|
8
|
+
MEMORY_TOO_MANY_CARDS: 400,
|
|
9
|
+
MEMORY_NOT_ATTACHED: 403,
|
|
10
|
+
MEMORY_NOT_FOUND: 404,
|
|
11
|
+
MEMORY_NO_PREVIOUS: 404,
|
|
12
|
+
MEMORY_CARD_NOT_FOUND: 404,
|
|
13
|
+
MEMORY_EXISTS: 409,
|
|
14
|
+
MEMORY_CARD_EXISTS: 409,
|
|
15
|
+
MEMORY_MISMATCH: 422,
|
|
16
|
+
MEMORY_UNREADABLE: 500,
|
|
17
|
+
};
|
|
18
|
+
async function readBody(req) {
|
|
19
|
+
const chunks = [];
|
|
20
|
+
let size = 0;
|
|
21
|
+
for await (const chunk of req) {
|
|
22
|
+
const buffer = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
|
|
23
|
+
size += buffer.length;
|
|
24
|
+
if (size > MAX_BODY_BYTES)
|
|
25
|
+
throw new MemoryError("MEMORY_TOO_LARGE");
|
|
26
|
+
chunks.push(buffer);
|
|
27
|
+
}
|
|
28
|
+
if (chunks.length === 0)
|
|
29
|
+
return {};
|
|
30
|
+
try {
|
|
31
|
+
return JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
throw new MemoryError("MEMORY_INVALID_ID");
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function memoryId(raw) {
|
|
38
|
+
try {
|
|
39
|
+
return decodeURIComponent(raw);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
throw new MemoryError("MEMORY_INVALID_ID");
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function queryKey(req) {
|
|
46
|
+
const raw = req.url ?? "";
|
|
47
|
+
const mark = raw.indexOf("?");
|
|
48
|
+
if (mark < 0)
|
|
49
|
+
return null;
|
|
50
|
+
const params = new URLSearchParams(raw.slice(mark + 1));
|
|
51
|
+
if (!params.has("key"))
|
|
52
|
+
return null;
|
|
53
|
+
return params.get("key") ?? "";
|
|
54
|
+
}
|
|
55
|
+
function textField(value, code) {
|
|
56
|
+
if (typeof value !== "string")
|
|
57
|
+
throw new MemoryError(code);
|
|
58
|
+
return value;
|
|
59
|
+
}
|
|
60
|
+
export async function handleMemoryRoutes(req, method, pathname, host, res, json) {
|
|
61
|
+
const previousCard = pathname.match(/^\/api\/memories\/([^/]+)\/cards\/previous$/);
|
|
62
|
+
const cards = pathname.match(/^\/api\/memories\/([^/]+)\/cards$/);
|
|
63
|
+
const previousCover = pathname.match(/^\/api\/memories\/([^/]+)\/previous$/);
|
|
64
|
+
const detail = pathname.match(/^\/api\/memories\/([^/]+)$/);
|
|
65
|
+
const collection = pathname === "/api/memories";
|
|
66
|
+
if (!collection && !detail && !cards && !previousCard && !previousCover)
|
|
67
|
+
return false;
|
|
68
|
+
if (cards && method === "POST")
|
|
69
|
+
return false;
|
|
70
|
+
try {
|
|
71
|
+
if (collection && method === "GET") {
|
|
72
|
+
json(res, 200, { memories: await host.list() });
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
if (collection && method === "POST") {
|
|
76
|
+
const body = await readBody(req);
|
|
77
|
+
if (typeof body.id !== "string" || typeof body.description !== "string") {
|
|
78
|
+
throw new MemoryError("MEMORY_INVALID_ID");
|
|
79
|
+
}
|
|
80
|
+
const created = await host.create({ id: body.id, description: body.description });
|
|
81
|
+
json(res, 201, created);
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
if (detail && method === "GET") {
|
|
85
|
+
const id = memoryId(detail[1]);
|
|
86
|
+
const meta = await host.readMeta(id);
|
|
87
|
+
const page = await host.read(id);
|
|
88
|
+
json(res, 200, { ...meta, body: page });
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
if (detail && method === "PATCH") {
|
|
92
|
+
const id = memoryId(detail[1]);
|
|
93
|
+
const payload = await readBody(req);
|
|
94
|
+
const body = textField(payload.body, "MEMORY_TOO_LARGE");
|
|
95
|
+
await host.replace(id, body);
|
|
96
|
+
const meta = await host.readMeta(id);
|
|
97
|
+
json(res, 200, { ...meta, body });
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
if (previousCover && method === "GET") {
|
|
101
|
+
const id = memoryId(previousCover[1]);
|
|
102
|
+
json(res, 200, { body: await host.readPreviousCover(id) });
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
if (cards && method === "GET") {
|
|
106
|
+
const id = memoryId(cards[1]);
|
|
107
|
+
const key = queryKey(req);
|
|
108
|
+
if (key === null) {
|
|
109
|
+
json(res, 200, { keys: await host.listCards(id) });
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
json(res, 200, { body: await host.readCard(id, key) });
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
if (cards && method === "PATCH") {
|
|
116
|
+
const id = memoryId(cards[1]);
|
|
117
|
+
const payload = await readBody(req);
|
|
118
|
+
const key = textField(payload.key, "MEMORY_INVALID_KEY");
|
|
119
|
+
const body = textField(payload.body, "MEMORY_TOO_LARGE");
|
|
120
|
+
await host.updateCard(id, key, body);
|
|
121
|
+
json(res, 200, { body });
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
if (cards && method === "DELETE") {
|
|
125
|
+
const id = memoryId(cards[1]);
|
|
126
|
+
const payload = await readBody(req);
|
|
127
|
+
await host.deleteCard(id, textField(payload.key, "MEMORY_INVALID_KEY"));
|
|
128
|
+
json(res, 200, { removed: true });
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
if (previousCard && method === "GET") {
|
|
132
|
+
const id = memoryId(previousCard[1]);
|
|
133
|
+
const key = queryKey(req);
|
|
134
|
+
if (key === null)
|
|
135
|
+
throw new MemoryError("MEMORY_INVALID_KEY");
|
|
136
|
+
json(res, 200, { body: await host.readPreviousCard(id, key) });
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
const code = error instanceof MemoryError ? error.code : "MEMORY_UNREADABLE";
|
|
142
|
+
json(res, STATUS[code], { code });
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
2
|
export type ProjectMcpRoutesCtx = {
|
|
3
3
|
projectRoot: string;
|
|
4
|
+
readJsonBody: (req: IncomingMessage) => Promise<unknown>;
|
|
4
5
|
json: (res: ServerResponse, status: number, body: unknown) => void;
|
|
5
6
|
};
|
|
6
7
|
export declare function handleProjectMcpRoutes(req: IncomingMessage, res: ServerResponse, ctx: ProjectMcpRoutesCtx): Promise<boolean>;
|