@promptbook/node 0.114.0-11 → 0.114.0-13

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.
Files changed (69) hide show
  1. package/esm/index.es.js +459 -133
  2. package/esm/index.es.js.map +1 -1
  3. package/esm/scripts/run-agent-messages/messages/buildAgentGoalChatPromptSection.d.ts +22 -4
  4. package/esm/scripts/run-agent-messages/messages/buildAgentMessagePrompt.d.ts +3 -3
  5. package/esm/scripts/run-codex-prompts/common/runGoScript/$spawnLoggedBashScript.d.ts +25 -0
  6. package/esm/scripts/run-codex-prompts/common/runGoScript/$terminateLoggedBashProcessTree.d.ts +11 -0
  7. package/esm/scripts/run-codex-prompts/common/runGoScript/scriptExecutionLog.d.ts +4 -0
  8. package/esm/src/book-2.0/agent-source/AgentSourceParseResult.d.ts +5 -0
  9. package/esm/src/book-2.0/agent-source/ParsedUnknownCommitment.d.ts +26 -0
  10. package/esm/src/book-2.0/agent-source/createAgentModelRequirementsWithCommitments.unknown.test.d.ts +1 -0
  11. package/esm/src/book-2.0/agent-source/getBookCommitmentLineType.d.ts +19 -0
  12. package/esm/src/book-2.0/agent-source/parseAgentSourceWithCommitments.unknown.test.d.ts +1 -0
  13. package/esm/src/book-3.0/AgentMessageRunReport.d.ts +28 -0
  14. package/esm/src/book-3.0/AgentPlannedMessagesSidecar.d.ts +94 -0
  15. package/esm/src/book-3.0/AgentPlannedMessagesSidecar.test.d.ts +1 -0
  16. package/esm/src/book-3.0/createAgentMessageSidecarBaseName.d.ts +13 -0
  17. package/esm/src/book-components/BookEditor/createUnknownCommitmentDiagnostics.d.ts +28 -0
  18. package/esm/src/book-components/BookEditor/createUnknownCommitmentDiagnostics.test.d.ts +1 -0
  19. package/esm/src/book-components/Chat/utils/agentProjectToolCall.d.ts +46 -0
  20. package/esm/src/book-components/Chat/utils/timeoutToolCallPresentation.d.ts +24 -0
  21. package/esm/src/cli/cli-commands/common/harness/$checkHarnessInstallation.d.ts +3 -3
  22. package/esm/src/cli/cli-commands/common/harness/$checkHarnessInstallation.test.d.ts +1 -0
  23. package/esm/src/cli/cli-commands/common/harness/$ensureHarnessInstallations.d.ts +3 -3
  24. package/esm/src/cli/cli-commands/common/harness/HarnessInstallationStatus.d.ts +3 -2
  25. package/esm/src/cli/cli-commands/common/harnessUpdateCliOptions.d.ts +29 -0
  26. package/esm/src/cli/cli-commands/common/harnessUpdateCliOptions.test.d.ts +1 -0
  27. package/esm/src/executables/platforms/locateAppOnWindows.d.ts +1 -1
  28. package/esm/src/executables/platforms/locateAppOnWindows.test.d.ts +1 -0
  29. package/esm/src/utils/agent-message-runtime/parseAgentMessageRuntimeLogEvents.d.ts +59 -0
  30. package/esm/src/utils/agent-message-runtime/resolveAgentMessageTouchedProjectNames.d.ts +19 -0
  31. package/esm/src/utils/agent-message-runtime/resolveAgentMessageTouchedProjectNames.test.d.ts +1 -0
  32. package/esm/src/version.d.ts +1 -1
  33. package/package.json +2 -2
  34. package/umd/index.umd.js +457 -131
  35. package/umd/index.umd.js.map +1 -1
  36. package/umd/scripts/run-agent-messages/messages/buildAgentGoalChatPromptSection.d.ts +22 -4
  37. package/umd/scripts/run-agent-messages/messages/buildAgentMessagePrompt.d.ts +3 -3
  38. package/umd/scripts/run-codex-prompts/common/runGoScript/$spawnLoggedBashScript.d.ts +25 -0
  39. package/umd/scripts/run-codex-prompts/common/runGoScript/$terminateLoggedBashProcessTree.d.ts +11 -0
  40. package/umd/scripts/run-codex-prompts/common/runGoScript/scriptExecutionLog.d.ts +4 -0
  41. package/umd/src/book-2.0/agent-source/AgentSourceParseResult.d.ts +5 -0
  42. package/umd/src/book-2.0/agent-source/ParsedUnknownCommitment.d.ts +26 -0
  43. package/umd/src/book-2.0/agent-source/createAgentModelRequirementsWithCommitments.unknown.test.d.ts +1 -0
  44. package/umd/src/book-2.0/agent-source/getBookCommitmentLineType.d.ts +19 -0
  45. package/umd/src/book-2.0/agent-source/parseAgentSourceWithCommitments.unknown.test.d.ts +1 -0
  46. package/umd/src/book-3.0/AgentMessageRunReport.d.ts +28 -0
  47. package/umd/src/book-3.0/AgentPlannedMessagesSidecar.d.ts +94 -0
  48. package/umd/src/book-3.0/AgentPlannedMessagesSidecar.test.d.ts +1 -0
  49. package/umd/src/book-3.0/createAgentMessageSidecarBaseName.d.ts +13 -0
  50. package/umd/src/book-components/BookEditor/createUnknownCommitmentDiagnostics.d.ts +28 -0
  51. package/umd/src/book-components/BookEditor/createUnknownCommitmentDiagnostics.test.d.ts +1 -0
  52. package/umd/src/book-components/Chat/utils/agentProjectToolCall.d.ts +46 -0
  53. package/umd/src/book-components/Chat/utils/timeoutToolCallPresentation.d.ts +24 -0
  54. package/umd/src/cli/cli-commands/common/harness/$checkHarnessInstallation.d.ts +3 -3
  55. package/umd/src/cli/cli-commands/common/harness/$checkHarnessInstallation.test.d.ts +1 -0
  56. package/umd/src/cli/cli-commands/common/harness/$ensureHarnessInstallations.d.ts +3 -3
  57. package/umd/src/cli/cli-commands/common/harness/HarnessInstallationStatus.d.ts +3 -2
  58. package/umd/src/cli/cli-commands/common/harnessUpdateCliOptions.d.ts +29 -0
  59. package/umd/src/cli/cli-commands/common/harnessUpdateCliOptions.test.d.ts +1 -0
  60. package/umd/src/executables/platforms/locateAppOnWindows.d.ts +1 -1
  61. package/umd/src/executables/platforms/locateAppOnWindows.test.d.ts +1 -0
  62. package/umd/src/utils/agent-message-runtime/parseAgentMessageRuntimeLogEvents.d.ts +59 -0
  63. package/umd/src/utils/agent-message-runtime/resolveAgentMessageTouchedProjectNames.d.ts +19 -0
  64. package/umd/src/utils/agent-message-runtime/resolveAgentMessageTouchedProjectNames.test.d.ts +1 -0
  65. package/umd/src/version.d.ts +1 -1
  66. package/esm/apps/agents-server/src/utils/agentGoalChat/agentGoalChatConstants.d.ts +0 -12
  67. package/esm/apps/agents-server/src/utils/agentProjects/agentProjectRuntimeConstants.d.ts +0 -40
  68. package/umd/apps/agents-server/src/utils/agentGoalChat/agentGoalChatConstants.d.ts +0 -12
  69. package/umd/apps/agents-server/src/utils/agentProjects/agentProjectRuntimeConstants.d.ts +0 -40
package/umd/index.umd.js CHANGED
@@ -47,7 +47,7 @@
47
47
  * @generated
48
48
  * @see https://github.com/webgptorg/promptbook
49
49
  */
50
- const PROMPTBOOK_ENGINE_VERSION = '0.114.0-11';
50
+ const PROMPTBOOK_ENGINE_VERSION = '0.114.0-13';
51
51
  /**
52
52
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
53
53
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -2954,14 +2954,90 @@
2954
2954
  * without the generated script and the prompt it embeds.
2955
2955
  */
2956
2956
  const SCRIPT_EXECUTION_LOG_RAW_OUTPUT_MARKER = '--- raw output ---';
2957
+ /**
2958
+ * Command which terminates the Bash process tree rooted at the running harness.
2959
+ *
2960
+ * Bash's POSIX process IDs differ from Windows process IDs in Git Bash, so the Windows branch resolves the native PID
2961
+ * before delegating to `taskkill`. Unix harnesses run in a dedicated Bash job process group, which can be terminated
2962
+ * with its negative process ID.
2963
+ */
2964
+ const TERMINATE_BASH_PROCESS_TREE_COMMAND = process.platform === 'win32'
2965
+ ? _spaceTrim.spaceTrim(`
2966
+ HARNESS_WINDOWS_PROCESS_ID="$(ps -l -p "$HARNESS_PROCESS_ID" | awk 'NR == 2 { print $4 }')"
2967
+ if [ -n "$HARNESS_WINDOWS_PROCESS_ID" ]; then
2968
+ MSYS_NO_PATHCONV=1 taskkill.exe /PID "$HARNESS_WINDOWS_PROCESS_ID" /T /F > /dev/null 2>&1 || true
2969
+ fi
2970
+ `)
2971
+ : 'kill -TERM -- "-$HARNESS_PROCESS_ID" 2>/dev/null || true';
2972
+ /**
2973
+ * Shell condition that detects whether the Node process which owns the harness is still running.
2974
+ *
2975
+ * Git Bash translates process IDs and command switches, so querying the native Windows PID needs both `tasklist` and
2976
+ * disabled MSYS path conversion there. Unix can use the native `kill -0` process existence check.
2977
+ */
2978
+ const IS_PARENT_PROCESS_RUNNING_CONDITION = process.platform === 'win32'
2979
+ ? 'MSYS_NO_PATHCONV=1 tasklist.exe /FI "PID eq $PARENT_CODER_PROCESS_ID" /NH | awk -v processId="$PARENT_CODER_PROCESS_ID" \'$2 == processId { isFound = 1 } END { exit isFound ? 0 : 1 }\''
2980
+ : 'kill -0 "$PARENT_CODER_PROCESS_ID" 2>/dev/null';
2981
+ /**
2982
+ * Environment variable that identifies the Node process responsible for a temporary harness shell.
2983
+ */
2984
+ const PTBK_CODER_PARENT_PROCESS_ID_ENV_NAME = 'PTBK_CODER_PARENT_PROCESS_ID';
2957
2985
  /**
2958
2986
  * Small bash wrapper that preserves stdout/stderr streams while teeing both into the runtime log file.
2987
+ *
2988
+ * A watcher polls the owning Node process by its PID. If that process exits abruptly, the watcher stops the whole
2989
+ * Bash and harness process tree instead of letting it continue as an orphan.
2959
2990
  */
2960
2991
  const LOGGED_BASH_WRAPPER_COMMAND = _spaceTrim.spaceTrim(`
2992
+ PARENT_CODER_PROCESS_ID="\${${PTBK_CODER_PARENT_PROCESS_ID_ENV_NAME}}"
2993
+ unset ${PTBK_CODER_PARENT_PROCESS_ID_ENV_NAME}
2994
+
2995
+ terminate_harness_process_tree() {
2996
+ # Keep the EXIT cleanup intact so the wrapper can also stop its parent-process watcher.
2997
+ trap - HUP INT TERM
2998
+ ${TERMINATE_BASH_PROCESS_TREE_COMMAND}
2999
+ }
3000
+
3001
+ is_parent_process_running() {
3002
+ ${IS_PARENT_PROCESS_RUNNING_CONDITION}
3003
+ }
3004
+
2961
3005
  if [ -n "\${${PTBK_CODER_LOG_FILE_ENV_NAME}:-}" ]; then
2962
- exec > >(tee -a "\$${PTBK_CODER_LOG_FILE_ENV_NAME}") 2> >(tee -a "\$${PTBK_CODER_LOG_FILE_ENV_NAME}" >&2)
3006
+ exec > >(tee -a "$${PTBK_CODER_LOG_FILE_ENV_NAME}") 2> >(tee -a "$${PTBK_CODER_LOG_FILE_ENV_NAME}" >&2)
3007
+ fi
3008
+
3009
+ watch_parent_process() {
3010
+ trap 'exit 0' HUP INT TERM
3011
+
3012
+ while is_parent_process_running; do
3013
+ sleep 1
3014
+ done
3015
+
3016
+ terminate_harness_process_tree
3017
+ }
3018
+
3019
+ # Background harness jobs need their own process group on Unix so termination cannot reach the parent coder.
3020
+ set -m
3021
+ if ! is_parent_process_running; then
3022
+ exit 1
2963
3023
  fi
2964
- bash "$1"
3024
+ bash "$1" &
3025
+ HARNESS_PROCESS_ID=$!
3026
+
3027
+ cleanup_parent_process_watcher() {
3028
+ kill "$PARENT_PROCESS_WATCHER_PID" 2>/dev/null || true
3029
+ wait "$PARENT_PROCESS_WATCHER_PID" 2>/dev/null || true
3030
+ }
3031
+
3032
+ watch_parent_process &
3033
+ PARENT_PROCESS_WATCHER_PID=$!
3034
+
3035
+ trap cleanup_parent_process_watcher EXIT
3036
+ trap terminate_harness_process_tree HUP INT TERM
3037
+
3038
+ wait "$HARNESS_PROCESS_ID"
3039
+ SCRIPT_EXIT_CODE=$?
3040
+ exit "$SCRIPT_EXIT_CODE"
2965
3041
  `);
2966
3042
  /**
2967
3043
  * Shapes one bash invocation that optionally mirrors live script output into a temporary log file.
@@ -3017,6 +3093,38 @@
3017
3093
  return scriptPath.toLowerCase().endsWith('.test.sh') ? 'test shell' : 'runner shell';
3018
3094
  }
3019
3095
 
3096
+ /**
3097
+ * Standard streams used by every temporary Bash runner.
3098
+ */
3099
+ const BASH_PROCESS_STDIO = ['pipe', 'pipe', 'pipe'];
3100
+ /**
3101
+ * Whether the current Node process is running on Windows.
3102
+ */
3103
+ const IS_WINDOWS$1 = process.platform === 'win32';
3104
+ /**
3105
+ * Starts one temporary Bash script in a process tree owned by the current Node process.
3106
+ *
3107
+ * The wrapper watches the supplied owning process ID. When that process exits abruptly, the wrapper terminates every
3108
+ * nested shell and harness process instead of leaving it orphaned.
3109
+ *
3110
+ * @private internal utility of the coding prompt runner
3111
+ */
3112
+ function $spawnLoggedBashScript(options) {
3113
+ var _a;
3114
+ const bashExecution = buildLoggedBashExecution(options.scriptPath, options.logPath);
3115
+ const parentProcessId = (_a = options.parentProcessId) !== null && _a !== void 0 ? _a : process.pid;
3116
+ return child_process.spawn('bash', bashExecution.args, {
3117
+ detached: !IS_WINDOWS$1,
3118
+ env: {
3119
+ ...process.env,
3120
+ ...bashExecution.env,
3121
+ [PTBK_CODER_PARENT_PROCESS_ID_ENV_NAME]: parentProcessId.toString(),
3122
+ },
3123
+ stdio: BASH_PROCESS_STDIO,
3124
+ windowsHide: true,
3125
+ });
3126
+ }
3127
+
3020
3128
  /**
3021
3129
  * Prints one live shell output chunk to the terminal when console mirroring is enabled.
3022
3130
  */
@@ -3039,12 +3147,12 @@
3039
3147
  async function runBashScriptWithOutput(options) {
3040
3148
  var _a;
3041
3149
  await appendScriptExecutionLogStart(options);
3042
- const bashExecution = buildLoggedBashExecution(options.scriptPath, options.logPath);
3043
3150
  const scriptPathPosix = toPosixPath(options.scriptPath);
3044
3151
  const shouldPrintLiveOutput = (_a = options.shouldPrintLiveOutput) !== null && _a !== void 0 ? _a : true;
3045
3152
  return await new Promise((resolve, reject) => {
3046
- const commandProcess = child_process.spawn('bash', bashExecution.args, {
3047
- env: bashExecution.env ? { ...process.env, ...bashExecution.env } : process.env,
3153
+ const commandProcess = $spawnLoggedBashScript({
3154
+ scriptPath: options.scriptPath,
3155
+ logPath: options.logPath,
3048
3156
  });
3049
3157
  let output = '';
3050
3158
  let settled = false;
@@ -3103,8 +3211,9 @@
3103
3211
  const failure = new Error(_spaceTrim.spaceTrim(output) || `Command "bash ${scriptPathPosix}" exited with code ${code}`);
3104
3212
  settleWithLog(`failed with exit code ${code !== null && code !== void 0 ? code : 'unknown'}`, () => reject(failure), failure);
3105
3213
  };
3214
+ // Wait for `close`, not only `exit`, because the Bash wrapper can still be flushing its tee process
3215
+ // substitutions after the direct shell exits.
3106
3216
  commandProcess.on('close', handleExit);
3107
- commandProcess.on('exit', handleExit);
3108
3217
  commandProcess.on('disconnect', () => {
3109
3218
  const failure = new Error(`Command "bash ${scriptPathPosix}" disconnected`);
3110
3219
  settleWithLog('failed after disconnect', () => reject(failure), failure);
@@ -3971,6 +4080,35 @@
3971
4080
  error.message.includes('bin/node'));
3972
4081
  }
3973
4082
 
4083
+ /**
4084
+ * Whether the current coder process runs on Windows.
4085
+ */
4086
+ const IS_WINDOWS = process.platform === 'win32';
4087
+ /**
4088
+ * Stops one active temporary Bash shell together with the harness process tree it owns.
4089
+ *
4090
+ * Unix sends `SIGTERM` to the wrapper so its shell trap terminates the separately grouped harness job. Windows does
4091
+ * not reliably deliver that signal to Bash, so the native `taskkill` command terminates the direct shell tree instead.
4092
+ *
4093
+ * @private internal utility of the coding prompt runner
4094
+ */
4095
+ function $terminateLoggedBashProcessTree(commandProcess) {
4096
+ if (commandProcess.exitCode !== null || commandProcess.signalCode !== null) {
4097
+ return;
4098
+ }
4099
+ if (!IS_WINDOWS) {
4100
+ commandProcess.kill('SIGTERM');
4101
+ return;
4102
+ }
4103
+ if (!commandProcess.pid) {
4104
+ return;
4105
+ }
4106
+ child_process.spawnSync('taskkill.exe', ['/PID', commandProcess.pid.toString(), '/T', '/F'], {
4107
+ stdio: 'ignore',
4108
+ windowsHide: true,
4109
+ });
4110
+ }
4111
+
3974
4112
  /**
3975
4113
  * Maximum number of output characters included in thrown error messages.
3976
4114
  */
@@ -4013,15 +4151,16 @@
4013
4151
  const scriptPathPosix = toPosixPath(scriptPath);
4014
4152
  const shouldPrintLiveOutput = (_a = options.shouldPrintLiveOutput) !== null && _a !== void 0 ? _a : true;
4015
4153
  await appendScriptExecutionLogStart(options);
4016
- const bashExecution = buildLoggedBashExecution(scriptPath, options.logPath);
4017
4154
  return await new Promise((resolve, reject) => {
4018
- const commandProcess = child_process.spawn('bash', bashExecution.args, {
4019
- env: bashExecution.env ? { ...process.env, ...bashExecution.env } : process.env,
4155
+ const commandProcess = $spawnLoggedBashScript({
4156
+ scriptPath,
4157
+ logPath: options.logPath,
4020
4158
  });
4021
4159
  let stdoutBuffer = '';
4022
4160
  let stderrBuffer = '';
4023
4161
  let fullOutput = '';
4024
4162
  let markerSeen = false;
4163
+ let isCompletedAfterIdleTimeout = false;
4025
4164
  let idleTimer;
4026
4165
  let settled = false;
4027
4166
  let isSettling = false;
@@ -4063,15 +4202,16 @@
4063
4202
  });
4064
4203
  };
4065
4204
  /**
4066
- * Finishes a marker-completed run and then asks bash to stop.
4205
+ * Stops a marker-completed process tree after its trailing output becomes idle.
4067
4206
  */
4068
4207
  const finishAfterIdleTimeout = () => {
4069
- settleWithLog('completed after idle timeout', () => resolve(fullOutput));
4208
+ isCompletedAfterIdleTimeout = true;
4209
+ idleTimer = undefined;
4070
4210
  try {
4071
- commandProcess.kill();
4211
+ $terminateLoggedBashProcessTree(commandProcess);
4072
4212
  }
4073
4213
  catch (_a) {
4074
- // Windows can report EPERM when bash exits before the idle timer fires.
4214
+ // The close handler below still settles normally when Bash already exited before the idle timer fired.
4075
4215
  }
4076
4216
  };
4077
4217
  /**
@@ -4130,7 +4270,11 @@
4130
4270
  const failure = code === 0 || markerSeen
4131
4271
  ? undefined
4132
4272
  : new Error(buildCommandFailureMessage(scriptPathPosix, code, fullOutput));
4133
- const status = failure ? `failed with exit code ${code !== null && code !== void 0 ? code : 'unknown'}` : 'succeeded';
4273
+ const status = isCompletedAfterIdleTimeout
4274
+ ? 'completed after idle timeout'
4275
+ : failure
4276
+ ? `failed with exit code ${code !== null && code !== void 0 ? code : 'unknown'}`
4277
+ : 'succeeded';
4134
4278
  settleWithLog(status, () => {
4135
4279
  if (!failure) {
4136
4280
  resolve(fullOutput);
@@ -4139,8 +4283,9 @@
4139
4283
  reject(failure);
4140
4284
  });
4141
4285
  };
4286
+ // Wait for `close`, not only `exit`, because the Bash wrapper can still be flushing its tee process
4287
+ // substitutions after the direct shell exits.
4142
4288
  commandProcess.on('close', handleExit);
4143
- commandProcess.on('exit', handleExit);
4144
4289
  commandProcess.on('disconnect', () => {
4145
4290
  const failure = new Error(buildCommandFailureMessage(scriptPathPosix, null, fullOutput));
4146
4291
  settleWithLog('failed after disconnect', () => reject(failure), failure);
@@ -5093,7 +5238,7 @@
5093
5238
  - You have 100% control over every project folder: create, read, modify, and delete files there, run scripts and commands inside it, and keep any persistent data you need between conversations.
5094
5239
  - When the user asks you to build something (for example a website) or to do longer-term work, create a new project folder \`${AGENT_PROJECTS_DIRECTORY_PATH}/<project-name>/\` (use a short kebab-case name) and do the work inside it. Modify an existing project when the user refers to work you already did.
5095
5240
  - A project can be a git repository — you can run \`git init\` inside a project folder and commit your work there.
5096
- - Do not touch anything outside the \`${AGENT_PROJECTS_DIRECTORY_PATH}/\` directory except the queued message file you are answering.
5241
+ - Do not touch anything outside the \`${AGENT_PROJECTS_DIRECTORY_PATH}/\` directory beyond the file changes allowed at the top of this prompt.
5097
5242
 
5098
5243
  ${block(buildProjectReferenceInstructions(projectsUrlPath))}
5099
5244
  ${block(buildProjectRuntimeInstructions(options.projectRuntimeApi))}
@@ -5149,63 +5294,148 @@
5149
5294
  }
5150
5295
 
5151
5296
  /**
5152
- * Host used for local project runtimes.
5297
+ * Milliseconds in one second.
5298
+ *
5299
+ * @private internal timeout-chat constant
5153
5300
  */
5301
+ const SECOND_IN_MILLISECONDS = 1000;
5154
5302
  /**
5155
- * Header used by internal local-runner routes.
5303
+ * Seconds in one minute.
5304
+ *
5305
+ * @private internal timeout-chat constant
5156
5306
  */
5157
- const USER_CHAT_WORKER_TOKEN_HEADER = 'x-user-chat-worker-token';
5158
-
5307
+ const MINUTE_IN_SECONDS = 60;
5159
5308
  /**
5160
- * Title stored on every agent goal chat.
5309
+ * Seconds in one hour.
5310
+ *
5311
+ * @private internal timeout-chat constant
5312
+ */
5313
+ const HOUR_IN_SECONDS = 60 * MINUTE_IN_SECONDS;
5314
+ /**
5315
+ * Seconds in one day.
5316
+ *
5317
+ * @private internal timeout-chat constant
5318
+ */
5319
+ const HOURS_IN_DAY = 24;
5320
+ /**
5321
+ * Seconds in one day.
5322
+ *
5323
+ * @private internal timeout-chat constant
5161
5324
  */
5325
+ const DAY_IN_SECONDS = HOURS_IN_DAY * HOUR_IN_SECONDS;
5162
5326
  /**
5163
- * Internal endpoint used by managed coding agents to plan goal-chat messages.
5327
+ * Converts timeout duration into natural language sentence fragments.
5328
+ *
5329
+ * Shared by every timeout surface: the chat chips, the goal-chat planned messages of the Agents
5330
+ * Server, and the planned-message prompt section of the agent-folder runner.
5331
+ *
5332
+ * @param milliseconds - Duration or repeat interval in milliseconds.
5333
+ * @returns Label such as `5 minutes` or `1 hour 30 minutes`.
5334
+ *
5335
+ * @private internal utility of `<Chat/>` and the Agents Server planned messages
5164
5336
  */
5165
- const AGENT_GOAL_CHAT_PLANNED_MESSAGES_INTERNAL_API_PATH = '/api/internal/agent-goal-chat-planned-messages';
5337
+ function formatTimeoutDurationHuman(milliseconds) {
5338
+ const normalizedMilliseconds = Math.max(0, Math.floor(milliseconds));
5339
+ const totalSeconds = Math.max(1, Math.floor(normalizedMilliseconds / SECOND_IN_MILLISECONDS));
5340
+ if (totalSeconds < MINUTE_IN_SECONDS) {
5341
+ return formatTimeoutUnit(totalSeconds, 'second');
5342
+ }
5343
+ if (totalSeconds < HOUR_IN_SECONDS) {
5344
+ const roundedMinutes = Math.max(1, Math.round(totalSeconds / MINUTE_IN_SECONDS));
5345
+ return formatTimeoutUnit(roundedMinutes, 'minute');
5346
+ }
5347
+ if (totalSeconds < DAY_IN_SECONDS) {
5348
+ const hours = Math.floor(totalSeconds / HOUR_IN_SECONDS);
5349
+ const minutes = Math.floor((totalSeconds % HOUR_IN_SECONDS) / MINUTE_IN_SECONDS);
5350
+ if (minutes === 0) {
5351
+ return formatTimeoutUnit(hours, 'hour');
5352
+ }
5353
+ return `${formatTimeoutUnit(hours, 'hour')} ${formatTimeoutUnit(minutes, 'minute')}`;
5354
+ }
5355
+ const days = Math.floor(totalSeconds / DAY_IN_SECONDS);
5356
+ const hours = Math.floor((totalSeconds % DAY_IN_SECONDS) / HOUR_IN_SECONDS);
5357
+ if (hours === 0) {
5358
+ return formatTimeoutUnit(days, 'day');
5359
+ }
5360
+ return `${formatTimeoutUnit(days, 'day')} ${formatTimeoutUnit(hours, 'hour')}`;
5361
+ }
5362
+ /**
5363
+ * Formats one pluralized timeout unit.
5364
+ *
5365
+ * @private internal timeout-chat helper
5366
+ */
5367
+ function formatTimeoutUnit(value, unit) {
5368
+ return `${value} ${unit}${value === 1 ? '' : 's'}`;
5369
+ }
5370
+ // Note: [💞] Ignore a discrepancy between file name and entity name
5166
5371
 
5167
5372
  /**
5168
5373
  * Builds the planned-message instructions available to an Agents Server-managed coding agent.
5169
5374
  *
5170
- * Planned messages always target the agent's singleton goal chat, regardless of the chat in which
5171
- * the command is invoked.
5375
+ * Planned messages always target the agent's singleton goal chat, regardless of the chat in which the
5376
+ * wake-up is planned, and they repeat like `setInterval` until they are cancelled. The sidecar file is
5377
+ * the only channel that changes them, so an answer that merely claims a follow-up was planned changes
5378
+ * nothing — and an answer that changes nothing keeps the current plan running.
5379
+ *
5380
+ * @param sidecar - Sidecar prepared by the Agents Server for the answered message.
5381
+ * @returns Prompt section, or an empty string outside Agents Server-managed runs.
5172
5382
  */
5173
- function buildAgentGoalChatPromptSection(runtimeApi) {
5174
- if (!runtimeApi) {
5383
+ function buildAgentGoalChatPromptSection(sidecar) {
5384
+ if (!sidecar) {
5175
5385
  return '';
5176
5386
  }
5177
- return _spaceTrim.spaceTrim(`
5178
- ## Planned goal-chat messages
5179
-
5180
- You can plan a future message that wakes you in your singleton goal chat. These commands are available during every Agents Server-managed chat invocation, not only while answering inside the goal chat.
5387
+ return _spaceTrim.spaceTrim((block) => `
5388
+ ## Planned goal-chat messages
5181
5389
 
5182
- - Use \`list_timeouts\` before planning follow-up work so you do not create duplicates.
5183
- - Use \`set_timeout\` when useful work should resume later. The delay is a positive number of milliseconds and the message should say exactly what your future invocation needs to do.
5184
- - Use \`cancel_timeout\` with the returned \`timeoutId\` when a planned message is no longer useful.
5185
- - A fired message appears in the goal chat and invokes you there. If more future work remains, plan the next useful message during that invocation.
5390
+ You can plan a message that repeatedly wakes you in your singleton goal chat. A planned message works like \`setInterval\`, not like \`setTimeout\`: once planned, it keeps waking you at its interval until you cancel it. This works during every Agents Server-managed chat invocation, not only while answering inside the goal chat.
5186
5391
 
5187
- Run the corresponding authenticated command:
5392
+ ${block(buildCurrentPlannedMessagesLines(sidecar.currentPlannedMessages))}
5188
5393
 
5189
- \`\`\`bash
5190
- # list_timeouts
5191
- curl -sS -X POST "$${runtimeApi.serverUrlEnvironmentVariableName}${AGENT_GOAL_CHAT_PLANNED_MESSAGES_INTERNAL_API_PATH}" \\
5192
- -H "Content-Type: application/json" \\
5193
- -H "${USER_CHAT_WORKER_TOKEN_HEADER}: $${runtimeApi.tokenEnvironmentVariableName}" \\
5194
- -d '{"action":"list","agentPermanentId":"${runtimeApi.agentPermanentId}"}'
5394
+ Editing \`${sidecar.relativeSidecarPath}\` is the **only** way to change what wakes you. Writing in your answer that you scheduled something does not schedule anything.
5195
5395
 
5196
- # set_timeout
5197
- curl -sS -X POST "$${runtimeApi.serverUrlEnvironmentVariableName}${AGENT_GOAL_CHAT_PLANNED_MESSAGES_INTERNAL_API_PATH}" \\
5198
- -H "Content-Type: application/json" \\
5199
- -H "${USER_CHAT_WORKER_TOKEN_HEADER}: $${runtimeApi.tokenEnvironmentVariableName}" \\
5200
- -d '{"action":"set","agentPermanentId":"${runtimeApi.agentPermanentId}","milliseconds":3600000,"message":"Continue the current goal and decide the next concrete action."}'
5396
+ - **Keeping your planned messages as they are is the default.** When the list above already matches your goal, leave \`commands\` empty — every listed message keeps repeating on its own, and re-planning it would only duplicate it.
5397
+ - When your goal needs a repeating wake-up that is not listed above, append \`{"action":"set","milliseconds":<repeat interval>,"message":"<what each future invocation must do>"}\` to the \`commands\` array of that file.
5398
+ - When a listed message no longer matches your goal (for example your goal now says every 10 minutes while the message repeats every 5 minutes), append \`{"action":"cancel","timeoutId":"<timeout id>"}\` and one \`set\` command with the interval your goal really requires.
5399
+ - When your goal no longer needs a listed message at all, cancel it without planning a replacement.
5400
+ - Never edit \`version\`, \`agentPermanentId\`, or \`currentPlannedMessages\`.
5401
+ - The Agents Server applies every command once your answer is finished, and each repetition appears in the goal chat and invokes you there.
5402
+ - Only mention a planned follow-up in your answer when it is really planned: either you appended the matching \`set\` command, or you kept one of the messages listed above.
5403
+ `);
5404
+ }
5405
+ /**
5406
+ * Renders the already planned messages so the agent can compare them with its goal.
5407
+ *
5408
+ * @param currentPlannedMessages - Planned messages prepared by the Agents Server.
5409
+ * @returns Markdown list, or a sentence stating that nothing is planned yet.
5410
+ *
5411
+ * @private function of `buildAgentGoalChatPromptSection`
5412
+ */
5413
+ function buildCurrentPlannedMessagesLines(currentPlannedMessages) {
5414
+ if (currentPlannedMessages.length === 0) {
5415
+ return 'You currently have no planned messages waiting.';
5416
+ }
5417
+ const plannedMessageLines = currentPlannedMessages.map(createCurrentPlannedMessageLine);
5418
+ return _spaceTrim.spaceTrim((block) => `
5419
+ These planned messages are already waiting for you, so keep them unless they stopped matching your goal:
5201
5420
 
5202
- # cancel_timeout
5203
- curl -sS -X POST "$${runtimeApi.serverUrlEnvironmentVariableName}${AGENT_GOAL_CHAT_PLANNED_MESSAGES_INTERNAL_API_PATH}" \\
5204
- -H "Content-Type: application/json" \\
5205
- -H "${USER_CHAT_WORKER_TOKEN_HEADER}: $${runtimeApi.tokenEnvironmentVariableName}" \\
5206
- -d '{"action":"cancel","agentPermanentId":"${runtimeApi.agentPermanentId}","timeoutId":"<timeout-id>"}'
5207
- \`\`\`
5208
- `);
5421
+ ${block(plannedMessageLines.join('\n'))}
5422
+ `);
5423
+ }
5424
+ /**
5425
+ * Renders one already planned message with the schedule the agent has to compare with its goal.
5426
+ *
5427
+ * @param plannedMessage - One planned message prepared by the Agents Server.
5428
+ * @returns Markdown list item.
5429
+ *
5430
+ * @private function of `buildAgentGoalChatPromptSection`
5431
+ */
5432
+ function createCurrentPlannedMessageLine(plannedMessage) {
5433
+ var _a;
5434
+ const message = ((_a = plannedMessage.message) === null || _a === void 0 ? void 0 : _a.trim()) || 'Continue working towards the current goal.';
5435
+ const schedule = plannedMessage.intervalMs
5436
+ ? `repeats every ${formatTimeoutDurationHuman(plannedMessage.intervalMs)}`
5437
+ : `wakes you once at ${plannedMessage.dueAt}`;
5438
+ return `- \`${plannedMessage.timeoutId}\` ${schedule}: ${message}`;
5209
5439
  }
5210
5440
 
5211
5441
  // Note: [💞] This file defines the shared TEAM workspace convention rather than one standalone entity.
@@ -5236,8 +5466,8 @@
5236
5466
  return '';
5237
5467
  }
5238
5468
  const teammateSections = workspace.manifest.teammates.map((teammate) => {
5239
- const sourcePath = toPromptPath(path.join(workspace.relativeWorkspacePath, AGENT_TEAMMATE_SOURCES_DIRECTORY_NAME, teammate.sourceFileName));
5240
- const transcriptPath = toPromptPath(path.join(workspace.relativeWorkspacePath, `${teammate.permanentId}--01.book`));
5469
+ const sourcePath = toPromptPath$1(path.join(workspace.relativeWorkspacePath, AGENT_TEAMMATE_SOURCES_DIRECTORY_NAME, teammate.sourceFileName));
5470
+ const transcriptPath = toPromptPath$1(path.join(workspace.relativeWorkspacePath, `${teammate.permanentId}--01.book`));
5241
5471
  const instructions = teammate.instructions.trim() || 'No additional TEAM instructions were provided.';
5242
5472
  return _spaceTrim.spaceTrim(`
5243
5473
  - ${teammate.agentName}
@@ -5255,7 +5485,7 @@
5255
5485
 
5256
5486
  - The underlying coding harness is already executing this turn **exactly once**. Never start, queue, or invoke another coding harness, agent runner, or \`ptbk\` command to consult a teammate.
5257
5487
  - The primary agent remains responsible for the final user-facing \`MESSAGE @Agent\` answer.
5258
- - Teammate source files are read-only context. When a teammate consultation occurs, record the actual exchange in a new top-level \`.book\` file in \`${toPromptPath(workspace.relativeWorkspacePath)}\`.
5488
+ - Teammate source files are read-only context. When a teammate consultation occurs, record the actual exchange in a new top-level \`.book\` file in \`${toPromptPath$1(workspace.relativeWorkspacePath)}\`.
5259
5489
  - Name each transcript \`<teammate permanent id>--<sequence>.book\`, for example \`${workspace.manifest.teammates[0].permanentId}--01.book\`.
5260
5490
  - Every transcript must contain alternating \`MESSAGE @${workspace.manifest.primaryAgent.agentName}\` and \`MESSAGE @<teammate name>\` blocks. Do not create a transcript for a teammate that was not consulted.
5261
5491
  `);
@@ -5263,7 +5493,7 @@
5263
5493
  /**
5264
5494
  * Converts a filesystem-relative path into the portable path notation used in prompts.
5265
5495
  */
5266
- function toPromptPath(path) {
5496
+ function toPromptPath$1(path) {
5267
5497
  return path.replace(/\\/gu, '/');
5268
5498
  }
5269
5499
 
@@ -5276,7 +5506,7 @@
5276
5506
 
5277
5507
  - Read \`${messageRelativePath}\` and answer the most recent \`MESSAGE\` block. In a normal chat it is from \`@User\`; in your internal goal chat it can be from \`@Agent\`.
5278
5508
  - Only change the queued message file by appending one new \`MESSAGE @Agent\` block
5279
- ${block(buildAllowedFileChangesPromptLine(options.teamWorkspace))}
5509
+ ${block(buildAllowedFileChangesPromptLine(options))}
5280
5510
 
5281
5511
  ## Rules for the answering
5282
5512
 
@@ -5312,7 +5542,7 @@
5312
5542
 
5313
5543
  - You can use \`message\` for message that will be sent immediately after clicking the button, or \`messageDraft\` for message that will be prefilled in the input field for editing before sending.
5314
5544
 
5315
- ${block(buildAgentGoalChatPromptSection(options.goalChatRuntimeApi))}
5545
+ ${block(buildAgentGoalChatPromptSection(options.plannedMessagesSidecar))}
5316
5546
 
5317
5547
  ${block(buildAgentProjectsPromptSection(options))}
5318
5548
 
@@ -5326,11 +5556,30 @@
5326
5556
  /**
5327
5557
  * Explains the exact files that one coding harness may change for a user turn.
5328
5558
  */
5329
- function buildAllowedFileChangesPromptLine(teamWorkspace) {
5330
- if (!teamWorkspace) {
5331
- return `- Do not modify any other file in the repository, except files inside your own \`${AGENT_PROJECTS_DIRECTORY_PATH}/\` directory`;
5559
+ function buildAllowedFileChangesPromptLine(options) {
5560
+ const allowedFileChanges = [`files inside your own \`${AGENT_PROJECTS_DIRECTORY_PATH}/\` directory`];
5561
+ if (options.teamWorkspace) {
5562
+ allowedFileChanges.push(`new consultation transcripts inside \`${toPromptPath(options.teamWorkspace.relativeWorkspacePath)}\``);
5563
+ }
5564
+ if (options.plannedMessagesSidecar) {
5565
+ allowedFileChanges.push(`the \`commands\` array of \`${toPromptPath(options.plannedMessagesSidecar.relativeSidecarPath)}\``);
5332
5566
  }
5333
- return `- Do not modify any other file in the repository, except files inside your own \`${AGENT_PROJECTS_DIRECTORY_PATH}/\` directory and new consultation transcripts inside \`${teamWorkspace.relativeWorkspacePath.replace(/\\/gu, '/')}\``;
5567
+ return `- Do not modify any other file in the repository, except ${formatAllowedFileChanges(allowedFileChanges)}`;
5568
+ }
5569
+ /**
5570
+ * Joins the allowed file-change phrases into one readable enumeration.
5571
+ */
5572
+ function formatAllowedFileChanges(allowedFileChanges) {
5573
+ if (allowedFileChanges.length === 1) {
5574
+ return allowedFileChanges[0];
5575
+ }
5576
+ return `${allowedFileChanges.slice(0, -1).join(', ')} and ${allowedFileChanges[allowedFileChanges.length - 1]}`;
5577
+ }
5578
+ /**
5579
+ * Converts a filesystem-relative path into the portable path notation used in prompts.
5580
+ */
5581
+ function toPromptPath(path) {
5582
+ return path.replace(/\\/gu, '/');
5334
5583
  }
5335
5584
 
5336
5585
  /**
@@ -27345,6 +27594,30 @@
27345
27594
  }
27346
27595
  }
27347
27596
 
27597
+ /**
27598
+ * Pattern shared by Book parsing and editor highlighting for a commitment-like line.
27599
+ *
27600
+ * A Book commitment keyword consists of one or more all-uppercase words with at least
27601
+ * two characters each. This deliberately mirrors the Book editor syntax rule so an
27602
+ * unknown keyword starts a new source block everywhere it is interpreted.
27603
+ *
27604
+ * @private internal constant of `getBookCommitmentLineType`
27605
+ */
27606
+ const BOOK_COMMITMENT_LINE_TYPE_REGEX = /^\s*(?<type>[A-Z][A-Z0-9]+(?:\s+[A-Z][A-Z0-9]+)*)(?=\s|$)/;
27607
+ /**
27608
+ * Extracts the uppercase commitment type from a Book line, including types not registered by Promptbook.
27609
+ *
27610
+ * @param line - One raw Book source line.
27611
+ * @returns Matched commitment type or `null` when the line does not begin with commitment syntax.
27612
+ *
27613
+ * @private internal utility of Book parsing and `<BookEditor/>`
27614
+ */
27615
+ function getBookCommitmentLineType(line) {
27616
+ var _a;
27617
+ const match = BOOK_COMMITMENT_LINE_TYPE_REGEX.exec(line);
27618
+ return ((_a = match === null || match === void 0 ? void 0 : match.groups) === null || _a === void 0 ? void 0 : _a.type) || null;
27619
+ }
27620
+
27348
27621
  /**
27349
27622
  * Consumes the agent-name prelude from raw source.
27350
27623
  *
@@ -27402,13 +27675,15 @@
27402
27675
  return {
27403
27676
  agentName: null,
27404
27677
  commitments: [],
27678
+ unknownCommitments: [],
27405
27679
  nonCommitmentLines: [],
27406
27680
  };
27407
27681
  }
27408
27682
  const { lines, agentName, agentNameLineIndex, agentNameLineNumber } = parseAgentSourcePrelude(agentSource);
27409
27683
  const commitments = [];
27684
+ const unknownCommitments = [];
27410
27685
  const nonCommitmentLines = agentNameLineIndex >= 0 ? [lines[agentNameLineIndex]] : [];
27411
- // Parse commitments with multiline support
27686
+ // Parse commitments with multiline support.
27412
27687
  let currentCommitment = null;
27413
27688
  // Process lines starting from after the agent name line
27414
27689
  const startIndex = agentNameLineIndex >= 0 ? agentNameLineIndex + 1 : 0;
@@ -27422,25 +27697,11 @@
27422
27697
  // Check if this line starts or ends a code block
27423
27698
  if (trimmedLine.startsWith('```')) {
27424
27699
  isInsideCodeBlock = !isInsideCodeBlock;
27425
- if (currentCommitment) {
27426
- // If we are inside a commitment, the code block is part of it
27427
- currentCommitment.contentLines.push(line);
27428
- }
27429
- else {
27430
- // If we are not inside a commitment, the code block is non-commitment
27431
- nonCommitmentLines.push(line);
27432
- }
27700
+ appendLineToCurrentCommitmentOrNonCommitmentLines(currentCommitment, line, nonCommitmentLines);
27433
27701
  continue;
27434
27702
  }
27435
27703
  if (isInsideCodeBlock) {
27436
- if (currentCommitment) {
27437
- // If we are inside a commitment and a code block, the line is part of the commitment
27438
- currentCommitment.contentLines.push(line);
27439
- }
27440
- else {
27441
- // If we are inside a code block but not a commitment, the line is non-commitment
27442
- nonCommitmentLines.push(line);
27443
- }
27704
+ appendLineToCurrentCommitmentOrNonCommitmentLines(currentCommitment, line, nonCommitmentLines);
27444
27705
  continue;
27445
27706
  }
27446
27707
  // Check if this line starts a new commitment
@@ -27449,22 +27710,14 @@
27449
27710
  const typeRegex = definition.createTypeRegex();
27450
27711
  const match = typeRegex.exec(line.trim());
27451
27712
  if (match && ((_a = match.groups) === null || _a === void 0 ? void 0 : _a.type)) {
27452
- // Save the previous commitment if it exists
27453
- if (currentCommitment) {
27454
- const fullContent = currentCommitment.contentLines.join('\n');
27455
- commitments.push({
27456
- type: currentCommitment.type,
27457
- content: _spaceTrim.spaceTrim(fullContent),
27458
- originalLine: currentCommitment.originalStartLine,
27459
- lineNumber: currentCommitment.startLineNumber,
27460
- });
27461
- }
27713
+ appendCurrentCommitment(currentCommitment, commitments, unknownCommitments);
27462
27714
  // Extract the initial content from the commitment line
27463
27715
  const fullRegex = definition.createRegex();
27464
27716
  const fullMatch = fullRegex.exec(line.trim());
27465
27717
  const initialContent = ((_b = fullMatch === null || fullMatch === void 0 ? void 0 : fullMatch.groups) === null || _b === void 0 ? void 0 : _b.contents) || '';
27466
27718
  // Start a new commitment
27467
27719
  currentCommitment = {
27720
+ kind: 'known',
27468
27721
  type: definition.type,
27469
27722
  startLineNumber: i + 1,
27470
27723
  originalStartLine: line,
@@ -27474,52 +27727,90 @@
27474
27727
  break;
27475
27728
  }
27476
27729
  }
27730
+ if (foundNewCommitment) {
27731
+ continue;
27732
+ }
27733
+ const unknownCommitmentType = getBookCommitmentLineType(line);
27734
+ if (unknownCommitmentType) {
27735
+ appendCurrentCommitment(currentCommitment, commitments, unknownCommitments);
27736
+ currentCommitment = {
27737
+ kind: 'unknown',
27738
+ type: unknownCommitmentType,
27739
+ startLineNumber: i + 1,
27740
+ originalStartLine: line,
27741
+ sourceLines: [line],
27742
+ };
27743
+ continue;
27744
+ }
27477
27745
  // Check if this is a horizontal line (ends any current commitment)
27478
27746
  const isHorizontalLine = HORIZONTAL_LINE_PATTERN$1.test(line);
27479
27747
  if (isHorizontalLine) {
27480
- // Save the current commitment if it exists
27481
- if (currentCommitment) {
27482
- const fullContent = currentCommitment.contentLines.join('\n');
27483
- commitments.push({
27484
- type: currentCommitment.type,
27485
- content: _spaceTrim.spaceTrim(fullContent),
27486
- originalLine: currentCommitment.originalStartLine,
27487
- lineNumber: currentCommitment.startLineNumber,
27488
- });
27489
- currentCommitment = null;
27490
- }
27748
+ appendCurrentCommitment(currentCommitment, commitments, unknownCommitments);
27749
+ currentCommitment = null;
27491
27750
  // Add horizontal line to non-commitment lines
27492
27751
  nonCommitmentLines.push(line);
27493
27752
  continue;
27494
27753
  }
27495
- if (!foundNewCommitment) {
27496
- if (currentCommitment) {
27497
- // This line belongs to the current commitment
27498
- currentCommitment.contentLines.push(line);
27499
- }
27500
- else {
27501
- // This line is not part of any commitment
27502
- nonCommitmentLines.push(line);
27503
- }
27504
- }
27505
- }
27506
- // Don't forget to save the last commitment if it exists
27507
- if (currentCommitment) {
27508
- const fullContent = currentCommitment.contentLines.join('\n');
27509
- commitments.push({
27510
- type: currentCommitment.type,
27511
- content: _spaceTrim.spaceTrim(fullContent),
27512
- originalLine: currentCommitment.originalStartLine,
27513
- lineNumber: currentCommitment.startLineNumber,
27514
- });
27754
+ appendLineToCurrentCommitmentOrNonCommitmentLines(currentCommitment, line, nonCommitmentLines);
27515
27755
  }
27756
+ appendCurrentCommitment(currentCommitment, commitments, unknownCommitments);
27516
27757
  return {
27517
27758
  agentName,
27518
27759
  agentNameLineNumber,
27519
27760
  commitments,
27761
+ unknownCommitments,
27520
27762
  nonCommitmentLines,
27521
27763
  };
27522
27764
  }
27765
+ /**
27766
+ * Stores the current known or unknown commitment in its appropriate parsed collection.
27767
+ *
27768
+ * @param currentCommitment - Commitment-like block collected so far.
27769
+ * @param commitments - Parsed registered commitments to append to.
27770
+ * @param unknownCommitments - Parsed unregistered commitments to append to.
27771
+ *
27772
+ * @private internal utility of `parseAgentSourceWithCommitments`
27773
+ */
27774
+ function appendCurrentCommitment(currentCommitment, commitments, unknownCommitments) {
27775
+ if (!currentCommitment) {
27776
+ return;
27777
+ }
27778
+ if (currentCommitment.kind === 'unknown') {
27779
+ unknownCommitments.push({
27780
+ type: currentCommitment.type,
27781
+ originalLine: currentCommitment.originalStartLine,
27782
+ lineNumber: currentCommitment.startLineNumber,
27783
+ source: _spaceTrim.spaceTrim(currentCommitment.sourceLines.join('\n')),
27784
+ });
27785
+ return;
27786
+ }
27787
+ commitments.push({
27788
+ type: currentCommitment.type,
27789
+ content: _spaceTrim.spaceTrim(currentCommitment.contentLines.join('\n')),
27790
+ originalLine: currentCommitment.originalStartLine,
27791
+ lineNumber: currentCommitment.startLineNumber,
27792
+ });
27793
+ }
27794
+ /**
27795
+ * Adds one source line to the active commitment-like block, or to plain source content when none is active.
27796
+ *
27797
+ * @param currentCommitment - Commitment-like block currently being collected.
27798
+ * @param line - Source line to append.
27799
+ * @param nonCommitmentLines - Plain source lines to append to when no block is active.
27800
+ *
27801
+ * @private internal utility of `parseAgentSourceWithCommitments`
27802
+ */
27803
+ function appendLineToCurrentCommitmentOrNonCommitmentLines(currentCommitment, line, nonCommitmentLines) {
27804
+ if (!currentCommitment) {
27805
+ nonCommitmentLines.push(line);
27806
+ return;
27807
+ }
27808
+ if (currentCommitment.kind === 'unknown') {
27809
+ currentCommitment.sourceLines.push(line);
27810
+ return;
27811
+ }
27812
+ currentCommitment.contentLines.push(line);
27813
+ }
27523
27814
 
27524
27815
  /**
27525
27816
  * Plugin for importing agent books *(`.book` files)*
@@ -27657,7 +27948,8 @@
27657
27948
  requirements = await importReferencedFiles(requirements);
27658
27949
  requirements = appendMcpServers(requirements, agentSource);
27659
27950
  requirements = appendNonCommitmentContent(requirements, parseResult);
27660
- return appendExampleInteractions(requirements, parseResult);
27951
+ requirements = appendExampleInteractions(requirements, parseResult);
27952
+ return appendUnknownCommitmentContent(requirements, parseResult);
27661
27953
  }
27662
27954
  /**
27663
27955
  * Imports text files referenced by IMPORT commitments and appends their transformed content to the system message.
@@ -27779,6 +28071,36 @@
27779
28071
  }
27780
28072
  return appendSystemMessageSection(requirements, nonCommitmentContent);
27781
28073
  }
28074
+ /**
28075
+ * Appends unknown commitment blocks as final extra context without applying commitment side effects.
28076
+ *
28077
+ * @param requirements - Current requirements snapshot.
28078
+ * @param parseResult - Parsed source including unknown commitment blocks.
28079
+ * @returns Requirements with unknown commitment context appended at the end of the system message.
28080
+ *
28081
+ * @private internal utility of `augmentAgentModelRequirementsFromSource`
28082
+ */
28083
+ function appendUnknownCommitmentContent(requirements, parseResult) {
28084
+ const unknownCommitmentContent = getUnknownCommitmentContent(parseResult);
28085
+ if (!unknownCommitmentContent) {
28086
+ return requirements;
28087
+ }
28088
+ return appendSystemMessageSection(requirements, unknownCommitmentContent);
28089
+ }
28090
+ /**
28091
+ * Joins unknown commitment blocks into their final extra-context representation.
28092
+ *
28093
+ * @param parseResult - Parsed source including unknown commitment blocks.
28094
+ * @returns Unknown commitment source blocks in their original order.
28095
+ *
28096
+ * @private internal utility of `appendUnknownCommitmentContent`
28097
+ */
28098
+ function getUnknownCommitmentContent(parseResult) {
28099
+ return parseResult.unknownCommitments
28100
+ .map((commitment) => commitment.source)
28101
+ .filter(Boolean)
28102
+ .join('\n\n');
28103
+ }
27782
28104
  /**
27783
28105
  * Collects plain-text lines that were not parsed as commitments and should still become part of the final system message.
27784
28106
  *
@@ -39753,23 +40075,27 @@
39753
40075
  // TODO: [🧠][♿] Maybe export through `@promptbook/node`
39754
40076
 
39755
40077
  /**
39756
- * Attempts to locate the specified application on a Windows system by searching common installation directories.
40078
+ * Attempts to locate the specified application on a Windows system by searching PATH and common installation directories.
39757
40079
  * Returns the path to the executable if found, or null otherwise.
39758
40080
  *
39759
40081
  * @private within the repository
39760
40082
  */
39761
40083
  async function locateAppOnWindows({ appName, windowsSuffix, }) {
39762
40084
  try {
39763
- const prefixes = [
40085
+ const EXECUTABLE_FILENAME = windowsSuffix.replace(/^.*[\\/]/, '');
40086
+ const PATH_EXECUTABLE_PATHS = (process.env.PATH || '')
40087
+ .split(path.delimiter)
40088
+ .filter(Boolean)
40089
+ .map((pathPrefix) => path.join(pathPrefix, EXECUTABLE_FILENAME));
40090
+ const INSTALLATION_EXECUTABLE_PATHS = [
39764
40091
  process.env.LOCALAPPDATA,
39765
40092
  path.join(process.env.LOCALAPPDATA || '', 'Programs'),
39766
40093
  process.env.PROGRAMFILES,
39767
40094
  process.env['PROGRAMFILES(X86)'],
39768
- ];
39769
- for (const prefix of prefixes) {
39770
- const path = prefix + windowsSuffix;
39771
- if (await isExecutable(path, $provideFilesystemForNode())) {
39772
- return path;
40095
+ ].map((pathPrefix) => pathPrefix + windowsSuffix);
40096
+ for (const executablePath of [...PATH_EXECUTABLE_PATHS, ...INSTALLATION_EXECUTABLE_PATHS]) {
40097
+ if (await isExecutable(executablePath, $provideFilesystemForNode())) {
40098
+ return executablePath;
39773
40099
  }
39774
40100
  }
39775
40101
  throw new Error(`Can not locate app ${appName} on Windows.`);