@promptbook/cli 0.114.0-24 → 0.114.0-26

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 (41) hide show
  1. package/README.md +2 -2
  2. package/apps/agents-server/src/utils/agentProjects/agentProjectRuntimePm2.ts +56 -10
  3. package/apps/agents-server/src/utils/userChat/createImmediateUserChatAnswerModelRequirements.ts +1 -0
  4. package/esm/index.es.js +256 -59
  5. package/esm/index.es.js.map +1 -1
  6. package/esm/scripts/run-codex-prompts/runners/common/estimateUsageFromTextLength.d.ts +33 -0
  7. package/esm/scripts/run-codex-prompts/runners/common/modelPricing.d.ts +41 -0
  8. package/esm/scripts/run-codex-prompts/runners/gemini/gemini-pricing.d.ts +5 -18
  9. package/esm/scripts/run-codex-prompts/runners/qwen-code/QwenCodeRunner.d.ts +23 -0
  10. package/esm/scripts/run-codex-prompts/runners/qwen-code/buildQwenCodeScript.d.ts +8 -0
  11. package/esm/scripts/run-codex-prompts/runners/qwen-code/parseQwenCodeUsageFromOutput.d.ts +9 -0
  12. package/esm/scripts/run-codex-prompts/runners/qwen-code/qwen-code-pricing.d.ts +30 -0
  13. package/esm/src/book-3.0/cliAgentEnv.d.ts +3 -3
  14. package/esm/src/cli/cli-commands/common/promptRunnerCliOptions.d.ts +8 -2
  15. package/esm/src/commitments/WRITING_RULES/WRITING_RULES.d.ts +14 -7
  16. package/esm/src/commitments/index.d.ts +1 -1
  17. package/esm/src/version.d.ts +1 -1
  18. package/package.json +1 -1
  19. package/src/book-2.0/book-language-documentation/createStandaloneBookLanguageMarkdown.ts +1 -0
  20. package/src/book-3.0/cliAgentEnv.ts +3 -2
  21. package/src/cli/cli-commands/common/harness/HarnessDefinition.ts +6 -0
  22. package/src/cli/cli-commands/common/promptRunnerCliOptions.ts +14 -8
  23. package/src/commitments/WRITING_RULES/WRITING_RULES.ts +16 -8
  24. package/src/commitments/index.ts +2 -1
  25. package/src/other/templates/getTemplatesPipelineCollection.ts +859 -739
  26. package/src/version.ts +2 -2
  27. package/src/versions.txt +2 -0
  28. package/umd/index.umd.js +256 -59
  29. package/umd/index.umd.js.map +1 -1
  30. package/umd/scripts/run-codex-prompts/runners/common/estimateUsageFromTextLength.d.ts +33 -0
  31. package/umd/scripts/run-codex-prompts/runners/common/modelPricing.d.ts +41 -0
  32. package/umd/scripts/run-codex-prompts/runners/gemini/gemini-pricing.d.ts +5 -18
  33. package/umd/scripts/run-codex-prompts/runners/qwen-code/QwenCodeRunner.d.ts +23 -0
  34. package/umd/scripts/run-codex-prompts/runners/qwen-code/buildQwenCodeScript.d.ts +8 -0
  35. package/umd/scripts/run-codex-prompts/runners/qwen-code/parseQwenCodeUsageFromOutput.d.ts +9 -0
  36. package/umd/scripts/run-codex-prompts/runners/qwen-code/qwen-code-pricing.d.ts +30 -0
  37. package/umd/src/book-3.0/cliAgentEnv.d.ts +3 -3
  38. package/umd/src/cli/cli-commands/common/promptRunnerCliOptions.d.ts +8 -2
  39. package/umd/src/commitments/WRITING_RULES/WRITING_RULES.d.ts +14 -7
  40. package/umd/src/commitments/index.d.ts +1 -1
  41. package/umd/src/version.d.ts +1 -1
package/README.md CHANGED
@@ -549,7 +549,7 @@ Prompts marked with `[-]` are not ready yet, prompts containing `@@@` are treate
549
549
 
550
550
  #### Features
551
551
 
552
- - **Multi-runner execution:** `openai-codex`, `github-copilot`, `cline`, `claude-code`, `opencode`, `gemini`
552
+ - **Multi-runner execution:** `openai-codex`, `github-copilot`, `cline`, `claude-code`, `opencode`, `gemini`, `qwen-code`
553
553
  - **Context injection:** `--agent agents/coding/developer.book --context AGENTS.md` or inline extra instructions
554
554
  - **Reasoning control:** `--thinking-level low|medium|high|xhigh` for supported runners
555
555
  - **Unattended or interactive runs:** default auto mode, or `--no-auto` to wait for user confirmation before each prompt
@@ -626,7 +626,7 @@ ptbk coder verify
626
626
  | Flag | Purpose |
627
627
  | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
628
628
  | `--harness <name>` | Selects the coding harness. |
629
- | `--model <model>` | Chooses the runner model; required for `openai-codex` and `gemini`, optional for `github-copilot`. |
629
+ | `--model <model>` | Chooses the runner model; required for `openai-codex`, `gemini` and `qwen-code`, optional for `github-copilot`. |
630
630
  | `--context <text-or-file>` | Appends extra instructions inline or from a file like `AGENTS.md`. |
631
631
  | `--test <command>` | Runs a verification command after each prompt attempt and feeds failing output back for retries. |
632
632
  | `--test-before <mode>` | Runs tests before coding: `no` (default), `yes-and-fail` (stop with results), or `yes-and-fix` (create one repair prompt first); defaults to `npm test` when enabled without `--test`. |
@@ -1,6 +1,8 @@
1
1
  import { execFile } from 'child_process';
2
2
  import { createHash } from 'crypto';
3
3
  import { promisify } from 'util';
4
+ import { UnexpectedError } from '../../../../../src/errors/UnexpectedError';
5
+ import { spaceTrim } from '../../../../../src/utils/organization/spaceTrim';
4
6
  import { AGENT_PROJECT_RUNTIME_HOST } from './agentProjectRuntimeConstants';
5
7
  import type { AgentProjectRuntimeMode } from './AgentProjectRuntimeInfo';
6
8
  import { resolveAgentProjectRuntimeEnvironmentFlag } from './resolveAgentProjectRuntimeEnvironmentFlag';
@@ -44,8 +46,18 @@ const AGENT_PROJECT_PM2_PROCESS_LABEL_LENGTH = 36;
44
46
 
45
47
  /**
46
48
  * Max output retained from pm2 commands.
49
+ *
50
+ * Note: `pm2 jlist` embeds a full environment snapshot into every listed process, so the list grows by tens of
51
+ * kilobytes with each started project. A budget of one megabyte was exhausted by roughly a dozen projects,
52
+ * after which `execFile` killed every `pm2 jlist` call with `ERR_CHILD_PROCESS_STDIO_MAXBUFFER` and pm2
53
+ * appeared to know no process at all.
54
+ */
55
+ const PM2_COMMAND_MAX_BUFFER_BYTES = 64 * 1024 * 1024;
56
+
57
+ /**
58
+ * pm2 error reported when no process of the requested name is registered.
47
59
  */
48
- const PM2_COMMAND_MAX_BUFFER_BYTES = 1024 * 1024;
60
+ const PM2_MISSING_PROCESS_ERROR_PATTERN = /Process or Namespace .* not found/u;
49
61
 
50
62
  /**
51
63
  * Status snapshot for one pm2 process.
@@ -189,25 +201,46 @@ export async function startAgentProjectRuntimePm2Process(
189
201
  }
190
202
 
191
203
  /**
192
- * Stops one pm2 project process when it exists.
204
+ * Deletes every pm2 process registered under one name.
205
+ *
206
+ * Note: The delete is never skipped because pm2 could not be asked for its current process list. `pm2 start`
207
+ * appends another process instead of replacing the existing one when a process of the same name is already
208
+ * registered, so a delete which silently does nothing turns every later start of the same project into one
209
+ * more duplicate. Only pm2 itself reporting an unknown name counts as "already gone", every other failure
210
+ * is reported to the caller, which then must not start the project again.
193
211
  *
194
212
  * @param processName - pm2 process name.
195
213
  */
196
214
  export async function stopAgentProjectRuntimePm2Process(processName: string | null | undefined): Promise<void> {
197
- if (!processName) {
215
+ if (!processName || !isAgentProjectRuntimePm2Enabled()) {
198
216
  return;
199
217
  }
200
218
 
201
- const status = await resolveAgentProjectRuntimePm2Status(processName);
219
+ try {
220
+ await execFileAsync(PM2_COMMAND, ['delete', processName], {
221
+ maxBuffer: PM2_COMMAND_MAX_BUFFER_BYTES,
222
+ windowsHide: true,
223
+ });
224
+ } catch (error) {
225
+ if (isPm2MissingProcessError(error)) {
226
+ return;
227
+ }
202
228
 
203
- if (!status.isKnown) {
204
- return;
229
+ throw new UnexpectedError(
230
+ spaceTrim(
231
+ (block) => `
232
+ Failed to delete the pm2 process \`${processName}\` of an agent project.
233
+
234
+ Starting the project again would register one more pm2 process of the same name instead of
235
+ replacing the existing one, so the project was left alone.
236
+
237
+ **Cause:**
238
+ ${block(error instanceof Error ? error.message : String(error))}
239
+ `,
240
+ ),
241
+ );
205
242
  }
206
243
 
207
- await execFileAsync(PM2_COMMAND, ['delete', processName], {
208
- maxBuffer: PM2_COMMAND_MAX_BUFFER_BYTES,
209
- windowsHide: true,
210
- });
211
244
  await savePm2ProcessList();
212
245
  }
213
246
 
@@ -366,6 +399,19 @@ async function savePm2ProcessList(): Promise<void> {
366
399
  });
367
400
  }
368
401
 
402
+ /**
403
+ * Returns whether one failed pm2 command failed only because pm2 knows no process of the requested name.
404
+ */
405
+ function isPm2MissingProcessError(error: unknown): boolean {
406
+ if (!error || typeof error !== 'object') {
407
+ return false;
408
+ }
409
+
410
+ const { stderr } = error as { readonly stderr?: unknown };
411
+
412
+ return typeof stderr === 'string' && PM2_MISSING_PROCESS_ERROR_PATTERN.test(stderr);
413
+ }
414
+
369
415
  /**
370
416
  * Creates the fallback status for missing or unavailable pm2 processes.
371
417
  */
@@ -19,6 +19,7 @@ const IMMEDIATE_USER_CHAT_ANSWER_INSTRUCTION_COMMITMENTS = new Set<string>([
19
19
  'STYLE',
20
20
  'STYLES',
21
21
  'WRITING RULES',
22
+ 'WRITING RULE',
22
23
  'FORMAT',
23
24
  'FORMATS',
24
25
  'SCENARIO',
package/esm/index.es.js CHANGED
@@ -48,7 +48,7 @@ const BOOK_LANGUAGE_VERSION = '2.0.0';
48
48
  * @generated
49
49
  * @see https://github.com/webgptorg/promptbook
50
50
  */
51
- const PROMPTBOOK_ENGINE_VERSION = '0.114.0-24';
51
+ const PROMPTBOOK_ENGINE_VERSION = '0.114.0-26';
52
52
  /**
53
53
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
54
54
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -1721,6 +1721,7 @@ const CLI_AGENT_HARNESS_NAMES = [
1721
1721
  'claude-code',
1722
1722
  'opencode',
1723
1723
  'gemini',
1724
+ 'qwen-code',
1724
1725
  ];
1725
1726
  /**
1726
1727
  * All supported thinking-level values for CLI coding-agent runners.
@@ -1731,8 +1732,8 @@ const CLI_AGENT_THINKING_LEVEL_VALUES = ['low', 'medium', 'high', 'xhigh', 'max'
1731
1732
  /**
1732
1733
  * Environment variable used as the default runner identifier when `--harness` is omitted or not set in `CliAgent`.
1733
1734
  *
1734
- * Set this to one of the harness names (`openai-codex`, `github-copilot`, `cline`, `claude-code`, `opencode`, `gemini`)
1735
- * so that `CliAgent` and `ptbk agent exec` can run without an explicit `harness` option.
1735
+ * Set this to one of the harness names (`openai-codex`, `github-copilot`, `cline`, `claude-code`, `opencode`, `gemini`,
1736
+ * `qwen-code`) so that `CliAgent` and `ptbk agent exec` can run without an explicit `harness` option.
1736
1737
  *
1737
1738
  * @public exported from `@promptbook/node`
1738
1739
  */
@@ -1817,23 +1818,31 @@ const PROMPT_RUNNER_DESCRIPTION = spaceTrim$1(`
1817
1818
  - claude-code: Claude Code integration
1818
1819
  - opencode: Opencode integration
1819
1820
  - gemini: Google Gemini CLI integration (requires --model)
1821
+ - qwen-code: Qwen Code CLI integration (requires --model)
1820
1822
  `);
1821
1823
  /**
1822
1824
  * Commander description for the `--harness` option.
1823
1825
  *
1824
1826
  * @private internal utility of `promptbookCli`
1825
1827
  */
1826
- const PROMPT_RUNNER_HARNESS_OPTION_DESCRIPTION = 'Select runner: openai-codex, github-copilot, cline, claude-code, opencode, gemini (required for non-dry-run)';
1828
+ const PROMPT_RUNNER_HARNESS_OPTION_DESCRIPTION = `Select runner: ${PROMPT_RUNNER_HARNESS_NAMES.join(', ')} (required for non-dry-run)`;
1829
+ /**
1830
+ * Runner harness names listed as alternatives of the `--harness` option in error messages.
1831
+ *
1832
+ * @private internal utility of `promptbookCli`
1833
+ */
1834
+ const PROMPT_RUNNER_HARNESS_OPTION_HINT = `--harness <${PROMPT_RUNNER_HARNESS_NAMES.join('|')}>`;
1827
1835
  /**
1828
1836
  * Commander description for the `--model` option.
1829
1837
  *
1830
1838
  * @private internal utility of `promptbookCli`
1831
1839
  */
1832
1840
  const PROMPT_RUNNER_MODEL_OPTION_DESCRIPTION = spaceTrim$1(`
1833
- Model to use (required for openai-codex and gemini)
1841
+ Model to use (required for openai-codex, gemini and qwen-code)
1834
1842
 
1835
1843
  OpenAI examples: gpt-5.2-codex, default
1836
1844
  Gemini examples: gemini-3-flash-preview, default
1845
+ Qwen examples: qwen3.8-max, default
1837
1846
  `);
1838
1847
  /**
1839
1848
  * Commander description for the `--git-changes` option.
@@ -1921,7 +1930,7 @@ function resolvePromptRunnerHarnessName(harness, options) {
1921
1930
  if (!options.isAgentRequired) {
1922
1931
  return undefined;
1923
1932
  }
1924
- throw new Error(colors.red('You must choose a harness using --harness <openai-codex|github-copilot|cline|claude-code|opencode|gemini>'));
1933
+ throw new Error(colors.red(`You must choose a harness using ${PROMPT_RUNNER_HARNESS_OPTION_HINT}`));
1925
1934
  }
1926
1935
  if (isPromptRunnerHarnessName(harness)) {
1927
1936
  return harness;
@@ -20054,29 +20063,36 @@ class UseUserLocationCommitmentDefinition extends BaseCommitmentDefinition {
20054
20063
  /**
20055
20064
  * `WRITING RULES` commitment definition.
20056
20065
  *
20057
- * It adds constraints that apply strictly to writing style and presentation
20058
- * rather than task-solving behavior.
20066
+ * The `WRITING RULES`/`WRITING RULE` commitment adds constraints that apply strictly to writing style
20067
+ * and presentation rather than task-solving behavior.
20068
+ *
20069
+ * Example usage in agent source:
20070
+ *
20071
+ * ```book
20072
+ * WRITING RULES Keep every response under 120 words
20073
+ * WRITING RULE Always end the message with an emoji
20074
+ * ```
20059
20075
  *
20060
20076
  * @private [🪔] Maybe export the commitments through some package
20061
20077
  */
20062
20078
  class WritingRulesCommitmentDefinition extends BaseCommitmentDefinition {
20063
- constructor() {
20064
- super('WRITING RULES');
20079
+ constructor(type = 'WRITING RULES') {
20080
+ super(type);
20065
20081
  }
20066
20082
  /**
20067
- * Short one-line description of `WRITING RULES`.
20083
+ * Short one-line description of `WRITING RULES`/`WRITING RULE`.
20068
20084
  */
20069
20085
  get description() {
20070
20086
  return 'Add writing-only constraints such as tone, length, formatting, or emoji usage.';
20071
20087
  }
20072
20088
  /**
20073
- * Icon for `WRITING RULES`.
20089
+ * Icon for `WRITING RULES`/`WRITING RULE`.
20074
20090
  */
20075
20091
  get icon() {
20076
20092
  return '📝';
20077
20093
  }
20078
20094
  /**
20079
- * Markdown documentation for `WRITING RULES`.
20095
+ * Markdown documentation for `WRITING RULES`/`WRITING RULE` commitment.
20080
20096
  */
20081
20097
  get documentation() {
20082
20098
  return spaceTrim$1(`
@@ -20086,6 +20102,7 @@ class WritingRulesCommitmentDefinition extends BaseCommitmentDefinition {
20086
20102
 
20087
20103
  ## Key aspects
20088
20104
 
20105
+ - All writing rules are treated equally regardless of singular/plural form.
20089
20106
  - Use it for writing-only constraints such as tone, formatting, length, emoji usage, punctuation, or reading level.
20090
20107
  - Do **not** use it for problem-solving behavior, policy, tool usage, or decision-making logic. Use \`RULE\` for that.
20091
20108
  - Newer writing-rules blocks override conflicting earlier writing-rules blocks.
@@ -20284,7 +20301,8 @@ const COMMITMENT_REGISTRY = [
20284
20301
  new LanguageCommitmentDefinition('LANGUAGE'),
20285
20302
  new LanguageCommitmentDefinition('LANGUAGES'),
20286
20303
  new WritingSampleCommitmentDefinition(),
20287
- new WritingRulesCommitmentDefinition(),
20304
+ new WritingRulesCommitmentDefinition('WRITING RULES'),
20305
+ new WritingRulesCommitmentDefinition('WRITING RULE'),
20288
20306
  new SampleCommitmentDefinition('SAMPLE'),
20289
20307
  new SampleCommitmentDefinition('EXAMPLE'),
20290
20308
  new FormatCommitmentDefinition('FORMAT'),
@@ -24133,6 +24151,12 @@ const HARNESS_DEFINITIONS = {
24133
24151
  commandName: 'gemini',
24134
24152
  npmPackageName: '@google/gemini-cli',
24135
24153
  },
24154
+ 'qwen-code': {
24155
+ harnessName: 'qwen-code',
24156
+ label: 'Qwen Code',
24157
+ commandName: 'qwen',
24158
+ npmPackageName: '@qwen-code/qwen-code',
24159
+ },
24136
24160
  };
24137
24161
  /**
24138
24162
  * Looks up the definition of one supported CLI coding harness.
@@ -26258,6 +26282,71 @@ function uncertainNumber(value, isUncertain) {
26258
26282
  return { value };
26259
26283
  }
26260
26284
 
26285
+ /**
26286
+ * An estimation of how many characters are in one token.
26287
+ */
26288
+ const CHARS_PER_TOKEN = 4;
26289
+ /**
26290
+ * Estimates the usage of one harness run from the length of its prompt and its output.
26291
+ *
26292
+ * Harnesses which do not report their own token counts are billed per token anyway, so the
26293
+ * character counts are the only signal available for a price estimate.
26294
+ */
26295
+ function estimateUsageFromTextLength(options) {
26296
+ const { harnessLabel, prompt, output, resolvePricing } = options;
26297
+ try {
26298
+ const pricing = resolvePricing();
26299
+ const inputTokens = Math.ceil(prompt.length / CHARS_PER_TOKEN);
26300
+ const outputTokens = Math.ceil(output.length / CHARS_PER_TOKEN);
26301
+ const price = uncertainNumber((inputTokens / 1000000) * pricing.input + (outputTokens / 1000000) * pricing.output);
26302
+ return {
26303
+ ...UNCERTAIN_USAGE,
26304
+ price,
26305
+ input: {
26306
+ ...UNCERTAIN_USAGE.input,
26307
+ tokensCount: uncertainNumber(inputTokens),
26308
+ },
26309
+ output: {
26310
+ ...UNCERTAIN_USAGE.output,
26311
+ tokensCount: uncertainNumber(outputTokens),
26312
+ },
26313
+ };
26314
+ }
26315
+ catch (error) {
26316
+ console.error(colors.bgRed(`Error parsing ${harnessLabel} usage output:`), error);
26317
+ return UNCERTAIN_USAGE;
26318
+ }
26319
+ }
26320
+
26321
+ /**
26322
+ * Resolves the pricing of one model of one harness.
26323
+ *
26324
+ * It first tries exact match, then prefix match, then falls back to the pricing of a stable
26325
+ * default model, because every harness names new models faster than the pricing tables grow.
26326
+ */
26327
+ function resolveModelPricing(options) {
26328
+ var _a;
26329
+ const { pricingTable, modelNameForEstimation, modelName } = options;
26330
+ const fallbackPricing = pricingTable[modelNameForEstimation];
26331
+ if (fallbackPricing === undefined) {
26332
+ throw new UnexpectedError(spaceTrim(`
26333
+ Missing pricing of the model \`${modelNameForEstimation}\` used for price estimation.
26334
+
26335
+ **The model used for estimation must always be listed in its own pricing table.**
26336
+ `));
26337
+ }
26338
+ if (!modelName) {
26339
+ return fallbackPricing;
26340
+ }
26341
+ const exactMatch = pricingTable[modelName];
26342
+ if (exactMatch) {
26343
+ return exactMatch;
26344
+ }
26345
+ const prefixMatch = (_a = Object.entries(pricingTable).find(([knownModelName]) => modelName.startsWith(knownModelName))) === null || _a === void 0 ? void 0 : _a[1];
26346
+ return prefixMatch !== null && prefixMatch !== void 0 ? prefixMatch : fallbackPricing;
26347
+ }
26348
+ // Note: [💞] Ignore a discrepancy between file name and entity name
26349
+
26261
26350
  /**
26262
26351
  * The pricing for Gemini models per 1 million tokens in USD.
26263
26352
  *
@@ -26277,27 +26366,14 @@ const GEMINI_PRICING = {
26277
26366
  const GEMINI_MODEL_FOR_ESTIMATION = 'gemini-1.5-flash';
26278
26367
  /**
26279
26368
  * Resolves Gemini pricing for the requested model.
26280
- *
26281
- * It first tries exact match, then prefix match, then falls back to a stable
26282
- * default pricing model.
26283
26369
  */
26284
26370
  function resolveGeminiPricing(modelName) {
26285
- var _a;
26286
- const fallbackPricing = GEMINI_PRICING[GEMINI_MODEL_FOR_ESTIMATION];
26287
- if (!modelName) {
26288
- return fallbackPricing;
26289
- }
26290
- const exactMatch = GEMINI_PRICING[modelName];
26291
- if (exactMatch) {
26292
- return exactMatch;
26293
- }
26294
- const prefixMatch = (_a = Object.entries(GEMINI_PRICING).find(([knownModel]) => modelName.startsWith(knownModel))) === null || _a === void 0 ? void 0 : _a[1];
26295
- return prefixMatch !== null && prefixMatch !== void 0 ? prefixMatch : fallbackPricing;
26371
+ return resolveModelPricing({
26372
+ pricingTable: GEMINI_PRICING,
26373
+ modelNameForEstimation: GEMINI_MODEL_FOR_ESTIMATION,
26374
+ modelName,
26375
+ });
26296
26376
  }
26297
- /**
26298
- * A an estimation of how many characters are in one token.
26299
- */
26300
- const CHARS_PER_TOKEN = 4;
26301
26377
 
26302
26378
  /**
26303
26379
  * Parses Gemini CLI output and extracts usage information.
@@ -26307,28 +26383,12 @@ const CHARS_PER_TOKEN = 4;
26307
26383
  * @param modelName The Gemini model used for this run.
26308
26384
  */
26309
26385
  function parseGeminiUsageFromOutput(output, prompt, modelName) {
26310
- try {
26311
- const pricing = resolveGeminiPricing(modelName);
26312
- const inputTokens = Math.ceil(prompt.length / CHARS_PER_TOKEN);
26313
- const outputTokens = Math.ceil(output.length / CHARS_PER_TOKEN);
26314
- const price = uncertainNumber((inputTokens / 1000000) * pricing.input + (outputTokens / 1000000) * pricing.output);
26315
- return {
26316
- ...UNCERTAIN_USAGE,
26317
- price,
26318
- input: {
26319
- ...UNCERTAIN_USAGE.input,
26320
- tokensCount: uncertainNumber(inputTokens),
26321
- },
26322
- output: {
26323
- ...UNCERTAIN_USAGE.output,
26324
- tokensCount: uncertainNumber(outputTokens),
26325
- },
26326
- };
26327
- }
26328
- catch (error) {
26329
- console.error(colors.bgRed('Error parsing Gemini usage output:'), error);
26330
- return UNCERTAIN_USAGE;
26331
- }
26386
+ return estimateUsageFromTextLength({
26387
+ harnessLabel: 'Gemini',
26388
+ prompt,
26389
+ output,
26390
+ resolvePricing: () => resolveGeminiPricing(modelName),
26391
+ });
26332
26392
  }
26333
26393
 
26334
26394
  /**
@@ -27716,6 +27776,114 @@ class OpencodeRunner {
27716
27776
  }
27717
27777
  }
27718
27778
 
27779
+ /**
27780
+ * Delimiter used for passing large prompts to the Qwen Code CLI.
27781
+ */
27782
+ const QWEN_CODE_PROMPT_DELIMITER = 'QWEN_CODE_PROMPT';
27783
+ /**
27784
+ * Builds the shell script that runs Qwen Code with the prompt and coding context.
27785
+ *
27786
+ * Note: `-y` runs the harness unattended, because `ptbk coder` approves the whole prompt
27787
+ * up front and there is nobody at the keyboard to confirm the single tool calls.
27788
+ */
27789
+ function buildQwenCodeScript(options) {
27790
+ return spaceTrim((block) => `
27791
+ qwen -y -m ${options.model} -p "$(cat <<'${QWEN_CODE_PROMPT_DELIMITER}'
27792
+
27793
+ ${block(options.prompt)}
27794
+
27795
+ ${QWEN_CODE_PROMPT_DELIMITER}
27796
+ )"
27797
+ `);
27798
+ }
27799
+
27800
+ /**
27801
+ * The pricing for Qwen Code models per 1 million tokens in USD.
27802
+ *
27803
+ * Note: This is an estimation.
27804
+ *
27805
+ * @see https://www.alibabacloud.com/help/en/model-studio/models
27806
+ */
27807
+ const QWEN_CODE_PRICING = {
27808
+ 'qwen3-coder-plus': {
27809
+ input: 1,
27810
+ output: 5,
27811
+ },
27812
+ 'qwen3-coder-flash': {
27813
+ input: 0.3,
27814
+ output: 1.5,
27815
+ },
27816
+ 'qwen3-max': {
27817
+ input: 1.2,
27818
+ output: 6,
27819
+ },
27820
+ };
27821
+ /**
27822
+ * The model to use for price estimation.
27823
+ */
27824
+ const QWEN_CODE_MODEL_FOR_ESTIMATION = 'qwen3-coder-plus';
27825
+ /**
27826
+ * Resolves Qwen Code pricing for the requested model.
27827
+ */
27828
+ function resolveQwenCodePricing(modelName) {
27829
+ return resolveModelPricing({
27830
+ pricingTable: QWEN_CODE_PRICING,
27831
+ modelNameForEstimation: QWEN_CODE_MODEL_FOR_ESTIMATION,
27832
+ modelName,
27833
+ });
27834
+ }
27835
+
27836
+ /**
27837
+ * Parses Qwen Code CLI output and extracts usage information.
27838
+ *
27839
+ * @param output The output from the Qwen Code CLI.
27840
+ * @param prompt The prompt that was sent to the Qwen Code CLI.
27841
+ * @param modelName The Qwen Code model used for this run.
27842
+ */
27843
+ function parseQwenCodeUsageFromOutput(output, prompt, modelName) {
27844
+ return estimateUsageFromTextLength({
27845
+ harnessLabel: 'Qwen Code',
27846
+ prompt,
27847
+ output,
27848
+ resolvePricing: () => resolveQwenCodePricing(modelName),
27849
+ });
27850
+ }
27851
+
27852
+ /**
27853
+ * Default Qwen Code model used by the coding runner.
27854
+ */
27855
+ const DEFAULT_QWEN_CODE_MODEL = 'qwen3.8-max';
27856
+ /**
27857
+ * Runs prompts via the Qwen Code CLI.
27858
+ */
27859
+ class QwenCodeRunner {
27860
+ /**
27861
+ * Creates a new Qwen Code runner.
27862
+ */
27863
+ constructor(options) {
27864
+ this.options = options;
27865
+ this.name = 'qwen-code';
27866
+ }
27867
+ /**
27868
+ * Runs the prompt using Qwen Code and parses usage output.
27869
+ */
27870
+ async runPrompt(options) {
27871
+ const scriptContent = buildQwenCodeScript({
27872
+ prompt: options.prompt,
27873
+ model: this.options.model,
27874
+ });
27875
+ const output = await $runGoScriptWithOutput({
27876
+ scriptPath: options.scriptPath,
27877
+ scriptContent,
27878
+ logPath: options.logPath,
27879
+ shouldPrintLiveOutput: options.shouldPrintLiveOutput,
27880
+ preserveArtifactsOnSuccess: options.preserveArtifactsOnSuccess,
27881
+ });
27882
+ const usage = parseQwenCodeUsageFromOutput(output, options.prompt, this.options.model);
27883
+ return { usage };
27884
+ }
27885
+ }
27886
+
27719
27887
  /**
27720
27888
  * Constant for default codex model.
27721
27889
  */
@@ -27724,6 +27892,11 @@ const DEFAULT_CODEX_MODEL = 'gpt-5.2-codex';
27724
27892
  * Constant for cline model.
27725
27893
  */
27726
27894
  const CLINE_MODEL = 'gemini:gemini-3-flash-preview';
27895
+ /**
27896
+ * Harnesses which refuse to run without an explicit `--model`, because they expose many models
27897
+ * of very different capability and price and never pick a sensible one on their own.
27898
+ */
27899
+ const MODEL_REQUIRING_HARNESS_NAMES = ['openai-codex', 'gemini', 'qwen-code'];
27727
27900
  /**
27728
27901
  * Resolves the configured prompt runner together with status-line metadata.
27729
27902
  *
@@ -27761,6 +27934,9 @@ function resolvePromptRunner(options) {
27761
27934
  if (agentName === 'gemini') {
27762
27935
  return createGeminiRunnerResolution(options);
27763
27936
  }
27937
+ if (agentName === 'qwen-code') {
27938
+ return createQwenCodeRunnerResolution(options);
27939
+ }
27764
27940
  throw new Error(`Unknown harness: ${agentName}`);
27765
27941
  }
27766
27942
  /**
@@ -27802,6 +27978,23 @@ function createGeminiRunnerResolution(options) {
27802
27978
  model: actualRunnerModel,
27803
27979
  }), actualRunnerModel);
27804
27980
  }
27981
+ /**
27982
+ * Builds the Qwen Code CLI runner resolution, including required-model validation.
27983
+ */
27984
+ function createQwenCodeRunnerResolution(options) {
27985
+ const actualRunnerModel = resolveRequiredModel({
27986
+ agentName: 'qwen-code',
27987
+ providedModel: options.model,
27988
+ defaultModel: DEFAULT_QWEN_CODE_MODEL,
27989
+ exampleUsages: [
27990
+ `--harness qwen-code --model ${DEFAULT_QWEN_CODE_MODEL}`,
27991
+ '--harness qwen-code --model default',
27992
+ ],
27993
+ });
27994
+ return createRunnerResolution(options, new QwenCodeRunner({
27995
+ model: actualRunnerModel,
27996
+ }), actualRunnerModel);
27997
+ }
27805
27998
  /**
27806
27999
  * Combines the instantiated runner with prompt status metadata.
27807
28000
  */
@@ -27817,9 +28010,7 @@ function createRunnerResolution(options, runner, actualRunnerModel) {
27817
28010
  */
27818
28011
  function getRunnerMetadata(options, actualRunnerModel) {
27819
28012
  const runnerName = options.agentName ? getHarnessDefinition(options.agentName).label : 'unknown';
27820
- if (options.agentName === 'openai-codex' ||
27821
- options.agentName === 'github-copilot' ||
27822
- options.agentName === 'gemini') {
28013
+ if (options.agentName === 'github-copilot' || isModelRequiringHarnessName(options.agentName)) {
27823
28014
  return { runnerName, modelName: actualRunnerModel };
27824
28015
  }
27825
28016
  if (options.agentName === 'cline') {
@@ -27830,6 +28021,12 @@ function getRunnerMetadata(options, actualRunnerModel) {
27830
28021
  }
27831
28022
  return { runnerName };
27832
28023
  }
28024
+ /**
28025
+ * Checks whether one harness refuses to run without an explicit `--model`.
28026
+ */
28027
+ function isModelRequiringHarnessName(agentName) {
28028
+ return MODEL_REQUIRING_HARNESS_NAMES.includes(agentName);
28029
+ }
27833
28030
  /**
27834
28031
  * Resolves a runner model, allowing `default` but otherwise requiring an explicit value.
27835
28032
  */
@@ -39981,7 +40178,7 @@ function updateAgentRunUiForPulling(uiHandle, queueSnapshot, statusMessage) {
39981
40178
  */
39982
40179
  function validateAgentRunOptions(options) {
39983
40180
  if (!options.agentName) {
39984
- throw new NotAllowed('You must choose a harness using --harness <openai-codex|github-copilot|cline|claude-code|opencode|gemini>.');
40181
+ throw new NotAllowed(`You must choose a harness using ${PROMPT_RUNNER_HARNESS_OPTION_HINT}.`);
39985
40182
  }
39986
40183
  if (options.autoPull && options.noCommit) {
39987
40184
  throw new NotAllowed(spaceTrim$1(`
@@ -74653,7 +74850,7 @@ function parseRunOptions(args) {
74653
74850
  exitWithUsageError(error instanceof Error ? error.message : String(error));
74654
74851
  }
74655
74852
  if (!agentName && !dryRun) {
74656
- exitWithUsageError('You must choose a harness using --harness <openai-codex|github-copilot|cline|claude-code|opencode|gemini>');
74853
+ exitWithUsageError(`You must choose a harness using ${PROMPT_RUNNER_HARNESS_OPTION_HINT}`);
74657
74854
  }
74658
74855
  return {
74659
74856
  dryRun,