@botiverse/raft-daemon 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3670,6 +3670,12 @@ function currentTimeMs() {
3670
3670
  function currentDate() {
3671
3671
  return new Date(currentTimeMs());
3672
3672
  }
3673
+ function setClockTimeout(fn, ms) {
3674
+ return setTimeout(fn, ms);
3675
+ }
3676
+ function clearClockTimeout(timeout) {
3677
+ clearTimeout(timeout);
3678
+ }
3673
3679
 
3674
3680
  // ../shared/src/agentInbox.ts
3675
3681
  function formatAgentInboxDelta(rows, options = {}) {
@@ -5167,8 +5173,31 @@ If a tool or error output contains credential-shaped strings, redact them to \`s
5167
5173
 
5168
5174
  **Profile credential resolution is strict.** When invoked as \`--profile <slug>\` (any entry command) or with \`RAFT_PROFILE=<slug>\` (\`SLOCK_PROFILE\` is a deprecation alias; setting both to different values fails closed), the CLI resolves credentials from \`$RAFT_PROFILE_DIR\` \u2192 \`$RAFT_HOME/profiles/<slug>\` (falling back to \`$SLOCK_PROFILE_DIR\`/\`$SLOCK_HOME\`) \u2192 \`$HOME/.slock/profiles/<slug>\` in that order. It does **not** fall back to a different profile's credential, to an ambient user-level token, or to environment-leaked secrets \u2014 if your designated profile credential is missing or unreadable, the CLI fails closed rather than authenticating as someone else.`;
5169
5175
  }
5170
- function buildSendingMessagesSection() {
5176
+ function buildSendingMessagesSection(shell) {
5171
5177
  const D = MESSAGE_HEREDOC_DELIMITER;
5178
+ if (shell === "powershell") {
5179
+ return `### Sending messages
5180
+
5181
+ - **Reply to a channel**: pipe a PowerShell here-string into \`raft message send --target "#channel-name"\`
5182
+ - **Reply to a DM**: pipe a PowerShell here-string into \`raft message send --target dm:@peer-name\`
5183
+ - **Reply in a thread**: pipe a PowerShell here-string into \`raft message send --target "#channel:shortid"\`
5184
+ - **Start a NEW DM**: pipe a PowerShell here-string into \`raft message send --target dm:@person-name\`
5185
+
5186
+ Message content is always read from stdin. On Windows PowerShell, use a single-quoted here-string so quotes, backticks, dollar variables, and newlines stay literal:
5187
+ \`\`\`powershell
5188
+ @'
5189
+ Long message with "quotes", $vars, \`backticks\`, and code blocks.
5190
+ '@ | raft message send --target "#channel-name"
5191
+ \`\`\`
5192
+
5193
+ The closing \`'@\` must start at the beginning of its line. Do not use Bash heredoc syntax (\`<<\`) in PowerShell.
5194
+
5195
+ If Slock says a message was not sent and was saved as a draft, choose one path:
5196
+ - To update the draft, pipe the revised here-string into a normal \`raft message send --target <target>\` command.
5197
+ - To send the current draft unchanged, use \`raft message send --send-draft --target <target>\` with no stdin. Do not use \`--send-draft\` when changing content.
5198
+
5199
+ **IMPORTANT**: To reply to any message, always reuse the exact \`target\` from the received message. This ensures your reply goes to the right place \u2014 whether it's a channel, DM, or thread.`;
5200
+ }
5172
5201
  return `### Sending messages
5173
5202
 
5174
5203
  - **Reply to a channel**: \`raft message send --target "#channel-name" <<'${D}'\` followed by the message body and \`${D}\`
@@ -5199,15 +5228,16 @@ When a reminder already exists, prefer \`raft reminder snooze\` to push it later
5199
5228
  Use \`raft reminder schedule\` rather than runtime-native wake or cron tools such as ScheduleWakeup or CronCreate for user-visible reminders, so reminders stay author-owned, persistent, observable, snoozable, updatable, and cancelable in Slock.
5200
5229
  Create agent reminders only after resolving the anchor message from the current conversation and passing its msgId explicitly; if no anchor can be resolved, consider posting a status update in the relevant thread so the intent is visible, then revisit when context is available.`;
5201
5230
  }
5202
- function buildThreadsSection() {
5231
+ function buildThreadsSection(shell) {
5203
5232
  const D = MESSAGE_HEREDOC_DELIMITER;
5233
+ const startThreadInstruction = shell === "powershell" ? 'pipe a PowerShell single-quoted here-string into `raft message send --target "#general:00000000"`' : `reply with \`raft message send --target "#general:00000000" <<'${D}'\` followed by the message body and \`${D}\``;
5204
5234
  return `### Threads
5205
5235
 
5206
5236
  Threads are sub-conversations attached to a specific message. They let you discuss a topic without cluttering the main channel.
5207
5237
 
5208
5238
  - **Thread targets** have a colon and short ID suffix: \`#general:00000000\` (thread in #general) or \`dm:@richard:11111111\` (thread in a DM).
5209
5239
  - When you receive a message from a thread (the target has a \`:shortid\` suffix), **always reply using that same target** to keep the conversation in the thread.
5210
- - **Start a new thread**: Use the \`msg=\` field from the header as the thread suffix. For example, if you see \`[target=#general msg=00000000 ...]\`, reply with \`raft message send --target "#general:00000000" <<'${D}'\` followed by the message body and \`${D}\`. The thread will be auto-created if it doesn't exist yet. Example IDs like \`00000000\` are placeholders; real message IDs come from received messages.
5240
+ - **Start a new thread**: Use the \`msg=\` field from the header as the thread suffix. For example, if you see \`[target=#general msg=00000000 ...]\`, ${startThreadInstruction}. The thread will be auto-created if it doesn't exist yet. Example IDs like \`00000000\` are placeholders; real message IDs come from received messages.
5211
5241
  - When you send a message, the response includes the message ID. You can use it to start a thread on your own message.
5212
5242
  - You can read thread history: \`raft message read --target "#general:00000000"\`
5213
5243
  - Unfollowing a thread removes its follow record and stops its ordinary delivery while the parent channel is unmuted: \`raft thread unfollow --target "#general:00000000"\`. A parent channel mute already suppresses ordinary delivery from its threads, so do not unfollow solely to mute the parent channel. Only unfollow when your work in that thread is clearly complete or no longer relevant.
@@ -5404,12 +5434,13 @@ Your context will be periodically compressed to stay within limits. When this ha
5404
5434
  - Keep MEMORY.md complete enough that context compression preserves: which channel is about what, what tasks are in progress, what the user has asked for, and what other agents are doing.`;
5405
5435
  }
5406
5436
  function buildSlockCliGuideSections(options) {
5437
+ const shell = options.shell ?? "posix";
5407
5438
  return {
5408
5439
  communication: buildCommunicationSection(options.audience),
5409
5440
  credentialHygiene: buildCredentialHygieneSection(),
5410
- sendingMessages: buildSendingMessagesSection(),
5441
+ sendingMessages: buildSendingMessagesSection(shell),
5411
5442
  reminders: buildRemindersSection(),
5412
- threads: buildThreadsSection(),
5443
+ threads: buildThreadsSection(shell),
5413
5444
  discovery: buildDiscoverySection(),
5414
5445
  channelAwareness: buildChannelAwarenessSection(),
5415
5446
  readingHistory: buildReadingHistorySection(),
@@ -5459,6 +5490,7 @@ function buildPrompt(config, opts) {
5459
5490
  handle: config.name,
5460
5491
  displayName: config.displayName || config.name
5461
5492
  },
5493
+ shell: opts.commandShell,
5462
5494
  taskClaimCmd
5463
5495
  });
5464
5496
  const checkCmd = "`raft message check`";
@@ -8213,6 +8245,7 @@ var WINDOWS_ENVIRONMENT_SCRIPT = [
8213
8245
  " $result | ConvertTo-Json -Compress -Depth 3",
8214
8246
  "}"
8215
8247
  ].join(" ");
8248
+ var WINDOWS_COMMAND_RESOLVE_TIMEOUT_MS = 1e3;
8216
8249
  function normalizeProcessEnv(value) {
8217
8250
  if (!value || typeof value !== "object" || Array.isArray(value)) return {};
8218
8251
  const env = {};
@@ -8321,7 +8354,8 @@ function resolveCommandOnWindows(command, env, execFileSyncFn, existsSyncFn) {
8321
8354
  command
8322
8355
  ], {
8323
8356
  stdio: ["ignore", "pipe", "ignore"],
8324
- env
8357
+ env,
8358
+ timeout: WINDOWS_COMMAND_RESOLVE_TIMEOUT_MS
8325
8359
  }));
8326
8360
  const resolved = output.trim().split(/\r?\n/)[0];
8327
8361
  if (!resolved) return null;
@@ -11932,7 +11966,6 @@ import { mkdirSync as mkdirSync3, readdirSync as readdirSync3 } from "fs";
11932
11966
  import path12 from "path";
11933
11967
  import {
11934
11968
  AuthStorage,
11935
- createBashTool,
11936
11969
  createAgentSessionFromServices,
11937
11970
  createAgentSessionServices,
11938
11971
  getAgentDir,
@@ -11940,6 +11973,126 @@ import {
11940
11973
  SettingsManager,
11941
11974
  VERSION as PI_SDK_VERSION
11942
11975
  } from "@earendil-works/pi-coding-agent";
11976
+
11977
+ // src/drivers/piCommandTool.ts
11978
+ import { spawn as spawn9 } from "child_process";
11979
+ import {
11980
+ createBashTool
11981
+ } from "@earendil-works/pi-coding-agent";
11982
+ var POWERSHELL_STDIN_LOADER = [
11983
+ "$raftReader = [System.IO.StreamReader]::new([Console]::OpenStandardInput(), [System.Text.Encoding]::ASCII, $false)",
11984
+ "try { $raftEncodedScript = $raftReader.ReadToEnd() } finally { $raftReader.Dispose() }",
11985
+ "$raftScript = [System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String($raftEncodedScript))",
11986
+ "& ([ScriptBlock]::Create($raftScript))"
11987
+ ].join("\r\n");
11988
+ var POWERSHELL_ARGS = [
11989
+ "-NoLogo",
11990
+ "-NoProfile",
11991
+ "-NonInteractive",
11992
+ "-ExecutionPolicy",
11993
+ "Bypass",
11994
+ "-Command",
11995
+ POWERSHELL_STDIN_LOADER
11996
+ ];
11997
+ var MAX_TIMEOUT_SECONDS = 2147483647 / 1e3;
11998
+ function killWindowsProcessTree(pid) {
11999
+ const killer = spawn9("taskkill.exe", ["/F", "/T", "/PID", String(pid)], {
12000
+ detached: true,
12001
+ stdio: "ignore",
12002
+ windowsHide: true
12003
+ });
12004
+ killer.unref();
12005
+ }
12006
+ function buildPiPowerShellScript(command) {
12007
+ return [
12008
+ "$ErrorActionPreference = 'Stop'",
12009
+ "$utf8NoBom = New-Object System.Text.UTF8Encoding($false)",
12010
+ "[Console]::OutputEncoding = $utf8NoBom",
12011
+ "$OutputEncoding = $utf8NoBom",
12012
+ "$global:LASTEXITCODE = 0",
12013
+ "& {",
12014
+ command,
12015
+ "}",
12016
+ "$raftCommandSucceeded = $?",
12017
+ "$raftNativeExitCode = $global:LASTEXITCODE",
12018
+ "if ($raftNativeExitCode -ne 0) { exit $raftNativeExitCode }",
12019
+ "if (-not $raftCommandSucceeded) { exit 1 }",
12020
+ "exit 0",
12021
+ ""
12022
+ ].join("\r\n");
12023
+ }
12024
+ function createPiPowerShellOperations(deps = {}) {
12025
+ const spawnProcess = deps.spawn ?? spawn9;
12026
+ const killProcessTree = deps.killProcessTree ?? killWindowsProcessTree;
12027
+ return {
12028
+ exec: async (command, cwd, { onData, signal, timeout, env }) => {
12029
+ if (signal?.aborted) throw new Error("aborted");
12030
+ if (timeout !== void 0 && (!Number.isFinite(timeout) || timeout <= 0 || timeout > MAX_TIMEOUT_SECONDS)) {
12031
+ throw new Error(
12032
+ `Invalid timeout: must be between 0 and ${MAX_TIMEOUT_SECONDS} seconds`
12033
+ );
12034
+ }
12035
+ const child = spawnProcess("powershell.exe", [...POWERSHELL_ARGS], {
12036
+ cwd,
12037
+ env,
12038
+ stdio: ["pipe", "pipe", "pipe"],
12039
+ windowsHide: true
12040
+ });
12041
+ child.stdout.on("data", onData);
12042
+ child.stderr.on("data", onData);
12043
+ child.stdin.on("error", () => void 0);
12044
+ child.stdin.end(
12045
+ Buffer.from(buildPiPowerShellScript(command), "utf16le").toString(
12046
+ "base64"
12047
+ )
12048
+ );
12049
+ let timedOut = false;
12050
+ const terminate = () => {
12051
+ if (child.pid) killProcessTree(child.pid);
12052
+ };
12053
+ const timeoutHandle = timeout === void 0 ? void 0 : setClockTimeout(() => {
12054
+ timedOut = true;
12055
+ terminate();
12056
+ }, timeout * 1e3);
12057
+ const onAbort = () => terminate();
12058
+ signal?.addEventListener("abort", onAbort, { once: true });
12059
+ if (signal?.aborted) terminate();
12060
+ try {
12061
+ const exitCode = await new Promise((resolve, reject) => {
12062
+ child.once("error", reject);
12063
+ child.once("close", resolve);
12064
+ });
12065
+ if (signal?.aborted) throw new Error("aborted");
12066
+ if (timedOut) throw new Error(`timeout:${timeout}`);
12067
+ return { exitCode };
12068
+ } finally {
12069
+ if (timeoutHandle) clearClockTimeout(timeoutHandle);
12070
+ signal?.removeEventListener("abort", onAbort);
12071
+ }
12072
+ }
12073
+ };
12074
+ }
12075
+ function createPiCommandTool(cwd, toolSpawnEnv, deps = {}) {
12076
+ const platform = deps.platform ?? process.platform;
12077
+ const tool = createBashTool(cwd, {
12078
+ ...platform === "win32" ? { operations: createPiPowerShellOperations(deps) } : {},
12079
+ spawnHook: (spawnContext) => ({
12080
+ ...spawnContext,
12081
+ env: {
12082
+ ...spawnContext.env,
12083
+ ...toolSpawnEnv
12084
+ }
12085
+ })
12086
+ });
12087
+ if (platform !== "win32") return tool;
12088
+ return {
12089
+ ...tool,
12090
+ label: "PowerShell",
12091
+ description: "Execute a Windows PowerShell 5.1 command in the current working directory. Use PowerShell syntax, not Bash syntax. Standard output and standard error are returned."
12092
+ };
12093
+ }
12094
+
12095
+ // src/drivers/pi.ts
11943
12096
  var PI_SESSION_DIR = ".pi-sessions";
11944
12097
  var BUILTIN_SESSION_DIR = ".builtin-sessions";
11945
12098
  var BUILTIN_AGENT_DIR = ".builtin-runtime";
@@ -12048,8 +12201,12 @@ async function withProcessEnvPatch(patch, fn, opts = {}) {
12048
12201
  }
12049
12202
  });
12050
12203
  }
12051
- function resolvePiModelFromRegistry(modelId, modelRegistry) {
12204
+ function resolvePiModelFromRegistry(modelId, modelRegistry, runtimeConfig) {
12052
12205
  if (!modelId || modelId === "default") return void 0;
12206
+ if (runtimeConfig.runtime === "builtin" && runtimeConfig.provider.kind === "gateway" && runtimeConfig.model.kind === "custom") {
12207
+ const provider2 = runtimeConfig.provider.providerId === "openai-compatible" ? "openai" : "anthropic";
12208
+ return modelRegistry.find(provider2, runtimeConfig.model.name);
12209
+ }
12053
12210
  const [provider, ...modelParts] = modelId.split("/");
12054
12211
  if (provider && modelParts.length > 0) {
12055
12212
  return modelRegistry.find(provider, modelParts.join("/"));
@@ -12381,7 +12538,7 @@ async function createPiAgentSessionForContext(ctx, sessionId, opts = {}) {
12381
12538
  agent_dir_source: opts.agentDirSource ?? (spawnEnv.PI_CODING_AGENT_DIR ? "spawn_env" : "default"),
12382
12539
  ...launchTraceEvidenceAttrs
12383
12540
  });
12384
- const model = resolvePiModelFromRegistry(launchRuntimeFields.model, services.modelRegistry);
12541
+ const model = resolvePiModelFromRegistry(launchRuntimeFields.model, services.modelRegistry, runtimeConfig);
12385
12542
  const resolvedModel = formatPiModelLogId(model);
12386
12543
  traceSpan?.addEvent(`${traceEventPrefix}.model_resolved`, {
12387
12544
  available_models_count: services.modelRegistry.getAvailable().length,
@@ -12437,15 +12594,7 @@ async function createPiAgentSessionForContext(ctx, sessionId, opts = {}) {
12437
12594
  model,
12438
12595
  thinkingLevel: launchRuntimeFields.reasoningEffort,
12439
12596
  customTools: [
12440
- createBashTool(ctx.workingDirectory, {
12441
- spawnHook: (spawnContext) => ({
12442
- ...spawnContext,
12443
- env: {
12444
- ...spawnContext.env,
12445
- ...toolSpawnEnv
12446
- }
12447
- })
12448
- })
12597
+ createPiCommandTool(ctx.workingDirectory, toolSpawnEnv)
12449
12598
  ]
12450
12599
  }), { removeFirst: providerEnvScope });
12451
12600
  traceSpan?.addEvent(`${traceEventPrefix}.started`, {
@@ -12756,13 +12905,15 @@ var PiDriver = class {
12756
12905
  return null;
12757
12906
  }
12758
12907
  buildSystemPrompt(config, _agentId) {
12908
+ const windowsPowerShell = process.platform === "win32";
12759
12909
  return buildCliTransportSystemPrompt(config, {
12760
- extraCriticalRules: [],
12910
+ extraCriticalRules: windowsPowerShell ? ["- Pi's `bash` tool is a compatibility name on Windows: it executes native Windows PowerShell 5.1. Use PowerShell syntax and never Bash heredocs or Unix-only commands."] : [],
12761
12911
  postStartupNotes: [
12762
12912
  "**Pi runtime note:** Slock keeps Pi running as a persistent SDK session. While you are working, Slock may send inbox-count notifications into the current turn; call `raft message check` at natural breakpoints."
12763
12913
  ],
12764
12914
  includeStdinNotificationSection: true,
12765
- messageNotificationStyle: "direct"
12915
+ messageNotificationStyle: "direct",
12916
+ commandShell: windowsPowerShell ? "powershell" : "posix"
12766
12917
  });
12767
12918
  }
12768
12919
  };
@@ -12798,13 +12949,15 @@ var BuiltInDriver = class extends PiDriver {
12798
12949
  }));
12799
12950
  }
12800
12951
  buildSystemPrompt(config, _agentId) {
12952
+ const windowsPowerShell = process.platform === "win32";
12801
12953
  return buildCliTransportSystemPrompt(config, {
12802
- extraCriticalRules: [],
12954
+ extraCriticalRules: windowsPowerShell ? ["- This runtime's `bash` tool is a compatibility name on Windows: it executes native Windows PowerShell 5.1. Use PowerShell syntax and never Bash heredocs or Unix-only commands."] : [],
12803
12955
  postStartupNotes: [
12804
12956
  "**Built-in runtime note:** Slock keeps this runtime in an isolated SDK session. While you are working, Slock may send inbox-count notifications into the current turn; call `raft message check` at natural breakpoints."
12805
12957
  ],
12806
12958
  includeStdinNotificationSection: true,
12807
- messageNotificationStyle: "direct"
12959
+ messageNotificationStyle: "direct",
12960
+ commandShell: windowsPowerShell ? "powershell" : "posix"
12808
12961
  });
12809
12962
  }
12810
12963
  };
@@ -26512,6 +26665,11 @@ var DaemonCore = class {
26512
26665
  });
26513
26666
  }
26514
26667
  handleConnect() {
26668
+ try {
26669
+ this.options.lifecycleHooks?.onConnect?.();
26670
+ } catch (err) {
26671
+ logger.warn(`[Daemon] Connection lifecycle hook failed: ${err instanceof Error ? err.message : String(err)}`);
26672
+ }
26515
26673
  if (!this.opencliWrappersRegenerated) {
26516
26674
  this.opencliWrappersRegenerated = true;
26517
26675
  try {
@@ -26604,7 +26762,6 @@ var DaemonCore = class {
26604
26762
  for (const agentId of agentsForSnapshot) {
26605
26763
  this.connection.send({ type: "reminder.snapshot.request", agentId });
26606
26764
  }
26607
- this.options.lifecycleHooks?.onConnect?.();
26608
26765
  }
26609
26766
  handleDisconnect() {
26610
26767
  logger.warn("[Daemon] Lost connection \u2014 agents continue running locally");
package/dist/core.js CHANGED
@@ -36,7 +36,7 @@ import {
36
36
  stageAgentMigrationObjectStoreBundle,
37
37
  subscribeDaemonLogs,
38
38
  verifyAgentMigrationAdoptPlan
39
- } from "./chunk-FGXAADHM.js";
39
+ } from "./chunk-BK46OPUN.js";
40
40
  export {
41
41
  AGENT_MIGRATION_BUNDLE_SCHEMA_VERSION,
42
42
  AGENT_MIGRATION_CONTROL_SEAM_ENV,
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  DAEMON_CLI_USAGE,
4
4
  DaemonCore,
5
5
  parseDaemonCliArgs
6
- } from "./chunk-FGXAADHM.js";
6
+ } from "./chunk-BK46OPUN.js";
7
7
 
8
8
  // src/index.ts
9
9
  var parsedArgs = parseDaemonCliArgs(process.argv.slice(2));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botiverse/raft-daemon",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "raft-daemon": "dist/raft-daemon.js",