@promptbook/node 0.114.0-10 → 0.114.0-12

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 (43) hide show
  1. package/esm/index.es.js +306 -84
  2. package/esm/index.es.js.map +1 -1
  3. package/esm/scripts/run-codex-prompts/common/runGoScript/$spawnLoggedBashScript.d.ts +25 -0
  4. package/esm/scripts/run-codex-prompts/common/runGoScript/$terminateLoggedBashProcessTree.d.ts +11 -0
  5. package/esm/scripts/run-codex-prompts/common/runGoScript/scriptExecutionLog.d.ts +4 -0
  6. package/esm/src/book-2.0/agent-source/AgentSourceParseResult.d.ts +5 -0
  7. package/esm/src/book-2.0/agent-source/ParsedUnknownCommitment.d.ts +26 -0
  8. package/esm/src/book-2.0/agent-source/createAgentModelRequirementsWithCommitments.unknown.test.d.ts +1 -0
  9. package/esm/src/book-2.0/agent-source/getBookCommitmentLineType.d.ts +19 -0
  10. package/esm/src/book-2.0/agent-source/parseAgentSourceWithCommitments.unknown.test.d.ts +1 -0
  11. package/esm/src/book-components/BookEditor/createUnknownCommitmentDiagnostics.d.ts +28 -0
  12. package/esm/src/book-components/BookEditor/createUnknownCommitmentDiagnostics.test.d.ts +1 -0
  13. package/esm/src/cli/cli-commands/common/harness/$checkHarnessInstallation.d.ts +3 -3
  14. package/esm/src/cli/cli-commands/common/harness/$checkHarnessInstallation.test.d.ts +1 -0
  15. package/esm/src/cli/cli-commands/common/harness/$ensureHarnessInstallations.d.ts +3 -3
  16. package/esm/src/cli/cli-commands/common/harness/HarnessInstallationStatus.d.ts +3 -2
  17. package/esm/src/cli/cli-commands/common/harnessUpdateCliOptions.d.ts +29 -0
  18. package/esm/src/cli/cli-commands/common/harnessUpdateCliOptions.test.d.ts +1 -0
  19. package/esm/src/executables/platforms/locateAppOnWindows.d.ts +1 -1
  20. package/esm/src/executables/platforms/locateAppOnWindows.test.d.ts +1 -0
  21. package/esm/src/version.d.ts +1 -1
  22. package/package.json +2 -2
  23. package/umd/index.umd.js +304 -82
  24. package/umd/index.umd.js.map +1 -1
  25. package/umd/scripts/run-codex-prompts/common/runGoScript/$spawnLoggedBashScript.d.ts +25 -0
  26. package/umd/scripts/run-codex-prompts/common/runGoScript/$terminateLoggedBashProcessTree.d.ts +11 -0
  27. package/umd/scripts/run-codex-prompts/common/runGoScript/scriptExecutionLog.d.ts +4 -0
  28. package/umd/src/book-2.0/agent-source/AgentSourceParseResult.d.ts +5 -0
  29. package/umd/src/book-2.0/agent-source/ParsedUnknownCommitment.d.ts +26 -0
  30. package/umd/src/book-2.0/agent-source/createAgentModelRequirementsWithCommitments.unknown.test.d.ts +1 -0
  31. package/umd/src/book-2.0/agent-source/getBookCommitmentLineType.d.ts +19 -0
  32. package/umd/src/book-2.0/agent-source/parseAgentSourceWithCommitments.unknown.test.d.ts +1 -0
  33. package/umd/src/book-components/BookEditor/createUnknownCommitmentDiagnostics.d.ts +28 -0
  34. package/umd/src/book-components/BookEditor/createUnknownCommitmentDiagnostics.test.d.ts +1 -0
  35. package/umd/src/cli/cli-commands/common/harness/$checkHarnessInstallation.d.ts +3 -3
  36. package/umd/src/cli/cli-commands/common/harness/$checkHarnessInstallation.test.d.ts +1 -0
  37. package/umd/src/cli/cli-commands/common/harness/$ensureHarnessInstallations.d.ts +3 -3
  38. package/umd/src/cli/cli-commands/common/harness/HarnessInstallationStatus.d.ts +3 -2
  39. package/umd/src/cli/cli-commands/common/harnessUpdateCliOptions.d.ts +29 -0
  40. package/umd/src/cli/cli-commands/common/harnessUpdateCliOptions.test.d.ts +1 -0
  41. package/umd/src/executables/platforms/locateAppOnWindows.d.ts +1 -1
  42. package/umd/src/executables/platforms/locateAppOnWindows.test.d.ts +1 -0
  43. package/umd/src/version.d.ts +1 -1
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-10';
50
+ const PROMPTBOOK_ENGINE_VERSION = '0.114.0-12';
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);
@@ -27345,6 +27490,30 @@
27345
27490
  }
27346
27491
  }
27347
27492
 
27493
+ /**
27494
+ * Pattern shared by Book parsing and editor highlighting for a commitment-like line.
27495
+ *
27496
+ * A Book commitment keyword consists of one or more all-uppercase words with at least
27497
+ * two characters each. This deliberately mirrors the Book editor syntax rule so an
27498
+ * unknown keyword starts a new source block everywhere it is interpreted.
27499
+ *
27500
+ * @private internal constant of `getBookCommitmentLineType`
27501
+ */
27502
+ const BOOK_COMMITMENT_LINE_TYPE_REGEX = /^\s*(?<type>[A-Z][A-Z0-9]+(?:\s+[A-Z][A-Z0-9]+)*)(?=\s|$)/;
27503
+ /**
27504
+ * Extracts the uppercase commitment type from a Book line, including types not registered by Promptbook.
27505
+ *
27506
+ * @param line - One raw Book source line.
27507
+ * @returns Matched commitment type or `null` when the line does not begin with commitment syntax.
27508
+ *
27509
+ * @private internal utility of Book parsing and `<BookEditor/>`
27510
+ */
27511
+ function getBookCommitmentLineType(line) {
27512
+ var _a;
27513
+ const match = BOOK_COMMITMENT_LINE_TYPE_REGEX.exec(line);
27514
+ return ((_a = match === null || match === void 0 ? void 0 : match.groups) === null || _a === void 0 ? void 0 : _a.type) || null;
27515
+ }
27516
+
27348
27517
  /**
27349
27518
  * Consumes the agent-name prelude from raw source.
27350
27519
  *
@@ -27402,13 +27571,15 @@
27402
27571
  return {
27403
27572
  agentName: null,
27404
27573
  commitments: [],
27574
+ unknownCommitments: [],
27405
27575
  nonCommitmentLines: [],
27406
27576
  };
27407
27577
  }
27408
27578
  const { lines, agentName, agentNameLineIndex, agentNameLineNumber } = parseAgentSourcePrelude(agentSource);
27409
27579
  const commitments = [];
27580
+ const unknownCommitments = [];
27410
27581
  const nonCommitmentLines = agentNameLineIndex >= 0 ? [lines[agentNameLineIndex]] : [];
27411
- // Parse commitments with multiline support
27582
+ // Parse commitments with multiline support.
27412
27583
  let currentCommitment = null;
27413
27584
  // Process lines starting from after the agent name line
27414
27585
  const startIndex = agentNameLineIndex >= 0 ? agentNameLineIndex + 1 : 0;
@@ -27422,25 +27593,11 @@
27422
27593
  // Check if this line starts or ends a code block
27423
27594
  if (trimmedLine.startsWith('```')) {
27424
27595
  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
- }
27596
+ appendLineToCurrentCommitmentOrNonCommitmentLines(currentCommitment, line, nonCommitmentLines);
27433
27597
  continue;
27434
27598
  }
27435
27599
  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
- }
27600
+ appendLineToCurrentCommitmentOrNonCommitmentLines(currentCommitment, line, nonCommitmentLines);
27444
27601
  continue;
27445
27602
  }
27446
27603
  // Check if this line starts a new commitment
@@ -27449,22 +27606,14 @@
27449
27606
  const typeRegex = definition.createTypeRegex();
27450
27607
  const match = typeRegex.exec(line.trim());
27451
27608
  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
- }
27609
+ appendCurrentCommitment(currentCommitment, commitments, unknownCommitments);
27462
27610
  // Extract the initial content from the commitment line
27463
27611
  const fullRegex = definition.createRegex();
27464
27612
  const fullMatch = fullRegex.exec(line.trim());
27465
27613
  const initialContent = ((_b = fullMatch === null || fullMatch === void 0 ? void 0 : fullMatch.groups) === null || _b === void 0 ? void 0 : _b.contents) || '';
27466
27614
  // Start a new commitment
27467
27615
  currentCommitment = {
27616
+ kind: 'known',
27468
27617
  type: definition.type,
27469
27618
  startLineNumber: i + 1,
27470
27619
  originalStartLine: line,
@@ -27474,52 +27623,90 @@
27474
27623
  break;
27475
27624
  }
27476
27625
  }
27626
+ if (foundNewCommitment) {
27627
+ continue;
27628
+ }
27629
+ const unknownCommitmentType = getBookCommitmentLineType(line);
27630
+ if (unknownCommitmentType) {
27631
+ appendCurrentCommitment(currentCommitment, commitments, unknownCommitments);
27632
+ currentCommitment = {
27633
+ kind: 'unknown',
27634
+ type: unknownCommitmentType,
27635
+ startLineNumber: i + 1,
27636
+ originalStartLine: line,
27637
+ sourceLines: [line],
27638
+ };
27639
+ continue;
27640
+ }
27477
27641
  // Check if this is a horizontal line (ends any current commitment)
27478
27642
  const isHorizontalLine = HORIZONTAL_LINE_PATTERN$1.test(line);
27479
27643
  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
- }
27644
+ appendCurrentCommitment(currentCommitment, commitments, unknownCommitments);
27645
+ currentCommitment = null;
27491
27646
  // Add horizontal line to non-commitment lines
27492
27647
  nonCommitmentLines.push(line);
27493
27648
  continue;
27494
27649
  }
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
- });
27650
+ appendLineToCurrentCommitmentOrNonCommitmentLines(currentCommitment, line, nonCommitmentLines);
27515
27651
  }
27652
+ appendCurrentCommitment(currentCommitment, commitments, unknownCommitments);
27516
27653
  return {
27517
27654
  agentName,
27518
27655
  agentNameLineNumber,
27519
27656
  commitments,
27657
+ unknownCommitments,
27520
27658
  nonCommitmentLines,
27521
27659
  };
27522
27660
  }
27661
+ /**
27662
+ * Stores the current known or unknown commitment in its appropriate parsed collection.
27663
+ *
27664
+ * @param currentCommitment - Commitment-like block collected so far.
27665
+ * @param commitments - Parsed registered commitments to append to.
27666
+ * @param unknownCommitments - Parsed unregistered commitments to append to.
27667
+ *
27668
+ * @private internal utility of `parseAgentSourceWithCommitments`
27669
+ */
27670
+ function appendCurrentCommitment(currentCommitment, commitments, unknownCommitments) {
27671
+ if (!currentCommitment) {
27672
+ return;
27673
+ }
27674
+ if (currentCommitment.kind === 'unknown') {
27675
+ unknownCommitments.push({
27676
+ type: currentCommitment.type,
27677
+ originalLine: currentCommitment.originalStartLine,
27678
+ lineNumber: currentCommitment.startLineNumber,
27679
+ source: _spaceTrim.spaceTrim(currentCommitment.sourceLines.join('\n')),
27680
+ });
27681
+ return;
27682
+ }
27683
+ commitments.push({
27684
+ type: currentCommitment.type,
27685
+ content: _spaceTrim.spaceTrim(currentCommitment.contentLines.join('\n')),
27686
+ originalLine: currentCommitment.originalStartLine,
27687
+ lineNumber: currentCommitment.startLineNumber,
27688
+ });
27689
+ }
27690
+ /**
27691
+ * Adds one source line to the active commitment-like block, or to plain source content when none is active.
27692
+ *
27693
+ * @param currentCommitment - Commitment-like block currently being collected.
27694
+ * @param line - Source line to append.
27695
+ * @param nonCommitmentLines - Plain source lines to append to when no block is active.
27696
+ *
27697
+ * @private internal utility of `parseAgentSourceWithCommitments`
27698
+ */
27699
+ function appendLineToCurrentCommitmentOrNonCommitmentLines(currentCommitment, line, nonCommitmentLines) {
27700
+ if (!currentCommitment) {
27701
+ nonCommitmentLines.push(line);
27702
+ return;
27703
+ }
27704
+ if (currentCommitment.kind === 'unknown') {
27705
+ currentCommitment.sourceLines.push(line);
27706
+ return;
27707
+ }
27708
+ currentCommitment.contentLines.push(line);
27709
+ }
27523
27710
 
27524
27711
  /**
27525
27712
  * Plugin for importing agent books *(`.book` files)*
@@ -27657,7 +27844,8 @@
27657
27844
  requirements = await importReferencedFiles(requirements);
27658
27845
  requirements = appendMcpServers(requirements, agentSource);
27659
27846
  requirements = appendNonCommitmentContent(requirements, parseResult);
27660
- return appendExampleInteractions(requirements, parseResult);
27847
+ requirements = appendExampleInteractions(requirements, parseResult);
27848
+ return appendUnknownCommitmentContent(requirements, parseResult);
27661
27849
  }
27662
27850
  /**
27663
27851
  * Imports text files referenced by IMPORT commitments and appends their transformed content to the system message.
@@ -27779,6 +27967,36 @@
27779
27967
  }
27780
27968
  return appendSystemMessageSection(requirements, nonCommitmentContent);
27781
27969
  }
27970
+ /**
27971
+ * Appends unknown commitment blocks as final extra context without applying commitment side effects.
27972
+ *
27973
+ * @param requirements - Current requirements snapshot.
27974
+ * @param parseResult - Parsed source including unknown commitment blocks.
27975
+ * @returns Requirements with unknown commitment context appended at the end of the system message.
27976
+ *
27977
+ * @private internal utility of `augmentAgentModelRequirementsFromSource`
27978
+ */
27979
+ function appendUnknownCommitmentContent(requirements, parseResult) {
27980
+ const unknownCommitmentContent = getUnknownCommitmentContent(parseResult);
27981
+ if (!unknownCommitmentContent) {
27982
+ return requirements;
27983
+ }
27984
+ return appendSystemMessageSection(requirements, unknownCommitmentContent);
27985
+ }
27986
+ /**
27987
+ * Joins unknown commitment blocks into their final extra-context representation.
27988
+ *
27989
+ * @param parseResult - Parsed source including unknown commitment blocks.
27990
+ * @returns Unknown commitment source blocks in their original order.
27991
+ *
27992
+ * @private internal utility of `appendUnknownCommitmentContent`
27993
+ */
27994
+ function getUnknownCommitmentContent(parseResult) {
27995
+ return parseResult.unknownCommitments
27996
+ .map((commitment) => commitment.source)
27997
+ .filter(Boolean)
27998
+ .join('\n\n');
27999
+ }
27782
28000
  /**
27783
28001
  * Collects plain-text lines that were not parsed as commitments and should still become part of the final system message.
27784
28002
  *
@@ -39753,23 +39971,27 @@
39753
39971
  // TODO: [🧠][♿] Maybe export through `@promptbook/node`
39754
39972
 
39755
39973
  /**
39756
- * Attempts to locate the specified application on a Windows system by searching common installation directories.
39974
+ * Attempts to locate the specified application on a Windows system by searching PATH and common installation directories.
39757
39975
  * Returns the path to the executable if found, or null otherwise.
39758
39976
  *
39759
39977
  * @private within the repository
39760
39978
  */
39761
39979
  async function locateAppOnWindows({ appName, windowsSuffix, }) {
39762
39980
  try {
39763
- const prefixes = [
39981
+ const EXECUTABLE_FILENAME = windowsSuffix.replace(/^.*[\\/]/, '');
39982
+ const PATH_EXECUTABLE_PATHS = (process.env.PATH || '')
39983
+ .split(path.delimiter)
39984
+ .filter(Boolean)
39985
+ .map((pathPrefix) => path.join(pathPrefix, EXECUTABLE_FILENAME));
39986
+ const INSTALLATION_EXECUTABLE_PATHS = [
39764
39987
  process.env.LOCALAPPDATA,
39765
39988
  path.join(process.env.LOCALAPPDATA || '', 'Programs'),
39766
39989
  process.env.PROGRAMFILES,
39767
39990
  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;
39991
+ ].map((pathPrefix) => pathPrefix + windowsSuffix);
39992
+ for (const executablePath of [...PATH_EXECUTABLE_PATHS, ...INSTALLATION_EXECUTABLE_PATHS]) {
39993
+ if (await isExecutable(executablePath, $provideFilesystemForNode())) {
39994
+ return executablePath;
39773
39995
  }
39774
39996
  }
39775
39997
  throw new Error(`Can not locate app ${appName} on Windows.`);