@buda-ai/connector 0.1.35 → 0.1.36

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.
@@ -64,19 +64,18 @@ If a user hits a plan limit (e.g., they ask for a cron schedule or an interval l
64
64
  1. Parse the user's request to determine:
65
65
  - **What** the task should do → becomes `taskTemplate.prompt` (the `name` is also used as `taskTemplate.title` automatically)
66
66
  - **When** it should run → becomes `scheduleType` + schedule config
67
- - **How to deliver result**:
68
- - Default to current automation session (`delivery.mode: "session"`)
69
- - Do **not** use `delivery.mode: "notify"` just because the task is scheduled, recurring, or uses cron
70
- - Use `delivery.mode: "notify"` only when the user explicitly asks to notify/push/send the result to a connected channel
71
- - Set `delivery.sessionId` to target the exact connected chat/group/DM whenever it is available
72
- - `delivery.channelId` is a legacy fallback only; do not prefer it over `delivery.sessionId`
67
+ - **Where the result belongs**:
68
+ - When created from a conversation, the tool pins execution to that exact current session.
69
+ - Without a current session, each run creates a new session.
70
+ - Return normal assistant text in the execution session; a bound external conversation receives that reply automatically.
71
+ - Do not configure `taskTemplate.delivery`, use a channel's most recent chat, or call `deliver_to_channel` for the automation's normal result.
72
+ - To change the execution conversation later, use the automation settings in the Buda UI to choose a fixed conversation or a new conversation per run.
73
73
  2. Choose the right schedule type (see [Schedule types](#schedule-types) below)
74
74
  3. Call the `create_automation` tool. Example for an interval schedule:
75
75
  - `name`: "Daily News Briefing"
76
76
  - `scheduleType`: "interval"
77
77
  - `intervalMinutes`: 1440
78
78
  - `taskTemplate.prompt`: "Search today's financial news and generate a summary"
79
- - `taskTemplate.delivery.mode`: "session"
80
79
 
81
80
  Example for a cron schedule:
82
81
  - `name`: "Morning News"
@@ -84,16 +83,6 @@ If a user hits a plan limit (e.g., they ask for a cron schedule or an interval l
84
83
  - `cronExpression`: "0 9 * * *"
85
84
  - `timezone`: "Asia/Shanghai"
86
85
  - `taskTemplate.prompt`: "Search today's financial news and generate a summary"
87
- - `taskTemplate.delivery.mode`: "session"
88
-
89
- Example only when the user explicitly asks to push results to a connected chat/group/DM:
90
- - `name`: "Morning News to Team Group"
91
- - `scheduleType`: "cron"
92
- - `cronExpression`: "0 9 * * *"
93
- - `timezone`: "Asia/Shanghai"
94
- - `taskTemplate.prompt`: "Search today's financial news and generate a summary"
95
- - `taskTemplate.delivery.mode`: "notify"
96
- - `taskTemplate.delivery.sessionId`: "tsk_xxxxxxxxxxxxxxxx"
97
86
 
98
87
  4. Report the result to the user, including the automation name and next run time.
99
88
 
package/dist/cli.js CHANGED
@@ -454,7 +454,7 @@ function parseLaunchConfig(raw) {
454
454
  bunnyDaemonPort
455
455
  };
456
456
  }
457
- const CONNECTOR_RUNTIME_VERSION = "0.1.35";
457
+ const CONNECTOR_RUNTIME_VERSION = "0.1.36";
458
458
  const DEFAULT_CONTROL_REQUEST_TIMEOUT_MS = 3e3;
459
459
  const getDaemonControlMetadataPath = (config, configRoot) => `${getConnectorDaemonLockTarget(config.serverUrl, config.connectorId, configRoot)}.control.json`;
460
460
  const writeMetadata = async (metadataPath, metadata) => {
@@ -3455,7 +3455,7 @@ async function stopEmbeddedBunnyDaemon(daemon) {
3455
3455
  const MIGRATION_VERSION = 1;
3456
3456
  const MIGRATION_DIRECTORY = path.join(".buda", "migrations", "legacy-workspace-v1");
3457
3457
  const JOURNAL_FILENAME = "journal.json";
3458
- const IMMUTABLE_ID_PATTERN$1 = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/;
3458
+ const IMMUTABLE_ID_PATTERN$1 = /^[A-Za-z0-9_-]{1,128}$/;
3459
3459
  const LEGACY_TEMPLATE_FILES = [
3460
3460
  "AGENTS.md",
3461
3461
  "HEARTBEAT.md",
@@ -4534,6 +4534,7 @@ function startConnectorRelay(config, bunnyDaemon, relayProtocol, workspaceRegist
4534
4534
  let activeTerminalTunnels = 0;
4535
4535
  const replayGuard = new ConnectorExecutionReplayGuard();
4536
4536
  const htmlPreviews = new HtmlPreviewServerRegistry();
4537
+ const workspaceHandlers = new WorkspaceDaemonHandlerStore();
4537
4538
  const clearTimers = () => {
4538
4539
  if (pingTimer) {
4539
4540
  clearInterval(pingTimer);
@@ -4640,7 +4641,7 @@ function startConnectorRelay(config, bunnyDaemon, relayProtocol, workspaceRegist
4640
4641
  onTerminalClose: () => {
4641
4642
  activeTerminalTunnels = Math.max(0, activeTerminalTunnels - 1);
4642
4643
  }
4643
- }, htmlPreviews);
4644
+ }, htmlPreviews, workspaceHandlers);
4644
4645
  });
4645
4646
  ws.on("close", (code) => {
4646
4647
  detach();
@@ -4649,6 +4650,7 @@ function startConnectorRelay(config, bunnyDaemon, relayProtocol, workspaceRegist
4649
4650
  htmlPreviews.closeAll();
4650
4651
  if (code === CONNECTOR_SOCKET_REPLACED_CLOSE_CODE) {
4651
4652
  stopped = true;
4653
+ workspaceHandlers.clear();
4652
4654
  writeLog("warn", "relay replaced by another process for this connector");
4653
4655
  } else {
4654
4656
  suspendedForAccessRevocation = true;
@@ -4703,12 +4705,14 @@ function startConnectorRelay(config, bunnyDaemon, relayProtocol, workspaceRegist
4703
4705
  reconnectTimer = null;
4704
4706
  }
4705
4707
  clearTimers();
4708
+ for (const controller of currentInflight.values()) controller.abort();
4709
+ workspaceHandlers.clear();
4706
4710
  socket?.close();
4707
4711
  htmlPreviews.closeAll();
4708
4712
  }
4709
4713
  };
4710
4714
  }
4711
- async function handleRelayMessage(socket, bunnyDaemon, raw, inflight, expectedConnectorId, relayProtocol, replayGuard, workspaceRegistry, serverUrl, localDaemonBaseUrl, activity, htmlPreviews) {
4715
+ async function handleRelayMessage(socket, bunnyDaemon, raw, inflight, expectedConnectorId, relayProtocol, replayGuard, workspaceRegistry, serverUrl, localDaemonBaseUrl, activity, htmlPreviews, workspaceHandlers) {
4712
4716
  if (!socket || socket.readyState !== WebSocket.OPEN) return;
4713
4717
  let message;
4714
4718
  try {
@@ -4824,7 +4828,7 @@ async function handleRelayMessage(socket, bunnyDaemon, raw, inflight, expectedCo
4824
4828
  workdirRoot: mounts?.mountRoot
4825
4829
  });
4826
4830
  } else if (isHtmlPreviewRequest && mounts && htmlPreviews) response = await handleHtmlPreviewRequest(message.request, mounts, htmlPreviews);
4827
- else if (descriptor && mounts) response = await handleWorkspaceDaemonRequest(message.request, mounts, controller.signal);
4831
+ else if (descriptor && mounts) response = await handleWorkspaceDaemonRequest(message.request, mounts, controller.signal, workspaceHandlers);
4828
4832
  else {
4829
4833
  const requestUrl = new URL(requestPath, bunnyDaemon.baseUrl);
4830
4834
  if (requestUrl.origin !== new URL(bunnyDaemon.baseUrl).origin) throw new Error("Relay request must target the embedded bunny daemon.");
@@ -4912,9 +4916,25 @@ async function handleRelayMessage(socket, bunnyDaemon, raw, inflight, expectedCo
4912
4916
  inflight.delete(message.requestId);
4913
4917
  }
4914
4918
  }
4915
- const handleWorkspaceDaemonRequest = async (request, mounts, signal) => {
4919
+ /** One owner per Relay, retained across socket reconnects and released on stop. */
4920
+ var WorkspaceDaemonHandlerStore = class {
4921
+ handlers = /* @__PURE__ */ new Map();
4922
+ get(mountRoot) {
4923
+ const root = path.resolve(mountRoot);
4924
+ let handler = this.handlers.get(root);
4925
+ if (!handler) {
4926
+ handler = createNextHandler({ root });
4927
+ this.handlers.set(root, handler);
4928
+ }
4929
+ return handler;
4930
+ }
4931
+ clear() {
4932
+ this.handlers.clear();
4933
+ }
4934
+ };
4935
+ const handleWorkspaceDaemonRequest = async (request, mounts, signal, handlers) => {
4916
4936
  const normalized = normalizeWorkspaceRequest(request, mounts);
4917
- const handler = createNextHandler({ root: mounts.mountRoot });
4937
+ const handler = handlers?.get(mounts.mountRoot) ?? createNextHandler({ root: mounts.mountRoot });
4918
4938
  if (getHeaderValue(normalized.headers, "x-buda-relay-body-encoding") === "base64") {
4919
4939
  if (new URL(normalized.path, "http://connector.workspace").pathname !== "/api/fs/write-stream") throw new Error("Base64 relay bodies are only supported for filesystem write streams.");
4920
4940
  if (typeof normalized.body !== "string") throw new Error("Invalid base64 filesystem write body.");
@@ -5059,7 +5079,7 @@ async function ensureSymlink(target, linkPath) {
5059
5079
  const REGISTRY_VERSION = 1;
5060
5080
  const REGISTRY_DIRECTORY = ".buda";
5061
5081
  const REGISTRY_FILENAME = "workspace-registry.json";
5062
- const IMMUTABLE_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/;
5082
+ const IMMUTABLE_ID_PATTERN = /^[A-Za-z0-9_-]{1,128}$/;
5063
5083
  const UNSAFE_FILESYSTEM_TYPES = /* @__PURE__ */ new Set([
5064
5084
  26985,
5065
5085
  20859,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@buda-ai/connector",
3
- "version": "0.1.35",
3
+ "version": "0.1.36",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -27,7 +27,7 @@
27
27
  "lint:err": "biome format . --write && biome check . --write --diagnostic-level=error"
28
28
  },
29
29
  "dependencies": {
30
- "@bunny-agent/daemon": "0.9.61-release.1",
30
+ "@bunny-agent/daemon": "0.10.2",
31
31
  "@clack/prompts": "^1.0.0",
32
32
  "playwright-core": "^1.52.0",
33
33
  "ws": "^8.21.0"
@@ -36,7 +36,7 @@
36
36
  "node-pty": "^1.1.0"
37
37
  },
38
38
  "devDependencies": {
39
- "@types/node": "^24.13.2",
39
+ "@types/node": "^26.3.0",
40
40
  "@types/ws": "^8.18.1",
41
41
  "relaylib": "workspace:*",
42
42
  "tsdown": "^0.22.14",