@code-yeongyu/senpi 2026.9.16-2 → 2026.9.16-3

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 (87) hide show
  1. package/CHANGELOG.md +18 -2
  2. package/dist/cli/help-extension-flags.d.ts +22 -0
  3. package/dist/cli/help-extension-flags.d.ts.map +1 -0
  4. package/dist/cli/help-extension-flags.js +30 -0
  5. package/dist/cli/help-extension-flags.js.map +1 -0
  6. package/dist/cli/help-fast-path.d.ts +19 -0
  7. package/dist/cli/help-fast-path.d.ts.map +1 -0
  8. package/dist/cli/help-fast-path.js +64 -0
  9. package/dist/cli/help-fast-path.js.map +1 -0
  10. package/dist/cli/help-flags-cache.d.ts +23 -0
  11. package/dist/cli/help-flags-cache.d.ts.map +1 -0
  12. package/dist/cli/help-flags-cache.js +118 -0
  13. package/dist/cli/help-flags-cache.js.map +1 -0
  14. package/dist/cli/startup-loading-indicator.d.ts.map +1 -1
  15. package/dist/cli/startup-loading-indicator.js +10 -4
  16. package/dist/cli/startup-loading-indicator.js.map +1 -1
  17. package/dist/cli.js +9 -0
  18. package/dist/cli.js.map +1 -1
  19. package/dist/core/agent-session.d.ts +0 -11
  20. package/dist/core/agent-session.d.ts.map +1 -1
  21. package/dist/core/agent-session.js +6 -50
  22. package/dist/core/agent-session.js.map +1 -1
  23. package/dist/core/extensions/builtin/ttsr/detectors/collapse-near-duplicates.d.ts +38 -0
  24. package/dist/core/extensions/builtin/ttsr/detectors/collapse-near-duplicates.d.ts.map +1 -0
  25. package/dist/core/extensions/builtin/ttsr/detectors/collapse-near-duplicates.js +155 -0
  26. package/dist/core/extensions/builtin/ttsr/detectors/collapse-near-duplicates.js.map +1 -0
  27. package/dist/core/extensions/builtin/ttsr/detectors/collapse.d.ts +2 -0
  28. package/dist/core/extensions/builtin/ttsr/detectors/collapse.d.ts.map +1 -1
  29. package/dist/core/extensions/builtin/ttsr/detectors/collapse.js +4 -1
  30. package/dist/core/extensions/builtin/ttsr/detectors/collapse.js.map +1 -1
  31. package/dist/core/retry-fallback/settings.d.ts +0 -3
  32. package/dist/core/retry-fallback/settings.d.ts.map +1 -1
  33. package/dist/core/retry-fallback/settings.js.map +1 -1
  34. package/dist/core/sdk.d.ts.map +1 -1
  35. package/dist/core/sdk.js +0 -1
  36. package/dist/core/sdk.js.map +1 -1
  37. package/dist/core/settings-manager.d.ts +1 -12
  38. package/dist/core/settings-manager.d.ts.map +1 -1
  39. package/dist/core/settings-manager.js +0 -21
  40. package/dist/core/settings-manager.js.map +1 -1
  41. package/dist/main.d.ts.map +1 -1
  42. package/dist/main.js +31 -3
  43. package/dist/main.js.map +1 -1
  44. package/dist/modes/interactive/interactive-mode.d.ts +1 -12
  45. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  46. package/dist/modes/interactive/interactive-mode.js +1 -34
  47. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  48. package/dist/modes/interactive/working-status.d.ts +2 -3
  49. package/dist/modes/interactive/working-status.d.ts.map +1 -1
  50. package/dist/modes/interactive/working-status.js +4 -17
  51. package/dist/modes/interactive/working-status.js.map +1 -1
  52. package/dist/senpi +9 -0
  53. package/docs/settings.md +1 -19
  54. package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +12 -0
  55. package/node_modules/@code-yeongyu/senpi-codemode/package.json +4 -4
  56. package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
  57. package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.js +3 -30
  58. package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.js.map +1 -1
  59. package/node_modules/@earendil-works/pi-agent-core/dist/agent.d.ts +0 -5
  60. package/node_modules/@earendil-works/pi-agent-core/dist/agent.d.ts.map +1 -1
  61. package/node_modules/@earendil-works/pi-agent-core/dist/agent.js +0 -2
  62. package/node_modules/@earendil-works/pi-agent-core/dist/agent.js.map +1 -1
  63. package/node_modules/@earendil-works/pi-agent-core/dist/index.d.ts +0 -2
  64. package/node_modules/@earendil-works/pi-agent-core/dist/index.d.ts.map +1 -1
  65. package/node_modules/@earendil-works/pi-agent-core/dist/index.js +0 -1
  66. package/node_modules/@earendil-works/pi-agent-core/dist/index.js.map +1 -1
  67. package/node_modules/@earendil-works/pi-agent-core/dist/types.d.ts +0 -9
  68. package/node_modules/@earendil-works/pi-agent-core/dist/types.d.ts.map +1 -1
  69. package/node_modules/@earendil-works/pi-agent-core/dist/types.js.map +1 -1
  70. package/node_modules/@earendil-works/pi-agent-core/package.json +3 -3
  71. package/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.json +1 -1
  72. package/node_modules/@earendil-works/pi-ai/dist/providers/data/fireworks.json +1 -1
  73. package/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
  74. package/node_modules/@earendil-works/pi-ai/dist/providers/data/venice.json +1 -1
  75. package/node_modules/@earendil-works/pi-ai/dist/utils/retry.d.ts +0 -1
  76. package/node_modules/@earendil-works/pi-ai/dist/utils/retry.d.ts.map +1 -1
  77. package/node_modules/@earendil-works/pi-ai/dist/utils/retry.js +0 -18
  78. package/node_modules/@earendil-works/pi-ai/dist/utils/retry.js.map +1 -1
  79. package/node_modules/@earendil-works/pi-ai/package.json +2 -2
  80. package/node_modules/@earendil-works/pi-pty/package.json +1 -1
  81. package/node_modules/@earendil-works/pi-telemetry/package.json +1 -1
  82. package/node_modules/@earendil-works/pi-tui/package.json +1 -1
  83. package/package.json +7 -7
  84. package/node_modules/@earendil-works/pi-agent-core/dist/stream-throughput-watchdog.d.ts +0 -101
  85. package/node_modules/@earendil-works/pi-agent-core/dist/stream-throughput-watchdog.d.ts.map +0 -1
  86. package/node_modules/@earendil-works/pi-agent-core/dist/stream-throughput-watchdog.js +0 -178
  87. package/node_modules/@earendil-works/pi-agent-core/dist/stream-throughput-watchdog.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -12,6 +12,24 @@
12
12
 
13
13
  ### Removed
14
14
 
15
+ ## [2026.9.16-3] - 2026-09-16
16
+
17
+ ### Breaking Changes
18
+
19
+ ### Added
20
+
21
+ ### Changed
22
+
23
+ - The stream guard now interrupts a message that keeps restating itself in different words, not only one that repeats a paragraph verbatim. A paragraph counts as an echo when its normalized word set overlaps an earlier paragraph of the same message by half, and the guard acts only once at least 8 of the last 12 paragraphs are echoes, so a repeated sentence, a callback, or a closing summary is left alone while a narration loop is cut within a few thousand characters. Paragraphs inside fenced code blocks are ignored, and the earlier text is kept - only the repeated run is dropped before the retry. Measured against 12,499 real assistant messages, the rule matched nothing except two known runaway generations. ([#1330](https://github.com/code-yeongyu/senpi/issues/1330))
24
+
25
+ - `--help` no longer boots the engine to print a help screen. The usage text and the flags extensions register are answered from a cache of the last launch's flag set (`<agentDir>/cache/help-flags.json`, validated against the engine version and the mtime/size of every extension, settings and trust input, so an upgrade or an edited extension refreshes it); a cache miss loads extensions for their flags only and skips the model runtime, the session and every other resource class. Measured warm on an Apple M4 Pro: 790ms → 28ms on bun and 959ms → 59ms on node for `--help`; a help screen never prompts for project trust and never runs project-local extension code that is not already trusted. ([oh-my-openagent#8371](https://github.com/code-yeongyu/oh-my-openagent/issues/8371))
26
+
27
+ ### Fixed
28
+
29
+ - The startup spinner is drawn the moment interactive startup begins instead of after a 120ms grace timer. That timer could not fire while the synchronous extension imports it was meant to cover were running, so on a real terminal the first frame appeared only once the whole load was done (measured: first byte at 2.27s, one frame before the TUI took over) and the load ran on a blank screen. ([oh-my-openagent#8371](https://github.com/code-yeongyu/oh-my-openagent/issues/8371))
30
+
31
+ ### Removed
32
+
15
33
  ## [2026.9.16-2] - 2026-09-16
16
34
 
17
35
  ### Breaking Changes
@@ -33,8 +51,6 @@
33
51
 
34
52
  - A provider that accepts a request and never starts streaming no longer ends the turn with the watchdog's own message (`Provider stream start timed out after 180000ms ...`). The stall is still retried on the same model with the configured stream-start bound, and still hands the turn to the next model in a configured `retry.fallbackChains` entry whose answer becomes the turn result. What changed is what you read: the transcript (and `senpi -p`) describes the stall in plain language, and when nothing can take the turn over the final line names the stalled model, the attempts spent and the next step - `/fallback`, resending, or raising `retry.provider.streamStartTimeoutMs` (`0` disables). The wording on the assistant message is unchanged, so retry classification and fallback routing behave exactly as before ([#1740](https://github.com/code-yeongyu/senpi/issues/1740)).
35
53
 
36
- - A provider that keeps streaming at a uselessly low rate is now detected instead of looking healthy forever. Every previous guard on a live stream watched for silence (the stream-start bound stops applying at the first event; the idle bound is re-armed by every event), so a turn crawling at ~2 tok/s never failed, never retried and never walked a fallback chain. After the first stream event senpi now ignores `retry.provider.throughputGraceMs` (default 5000) of streaming and then measures streamed text and thinking units over a trailing `retry.provider.throughputWindowMs` (default 20000); a full window carrying at least 16 units whose sustained rate is below `retry.provider.minThroughputTokensPerSecond` (default 8, `0` disables) aborts the request with `Provider stream throughput degraded: <n> tok/s over <n>s (floor <n> tok/s)`. That failure is retryable but spends no same-model attempts - replaying the payload cannot make the upstream faster - so it goes straight to the configured fallback chain, and with no candidate the turn ends on that error with the usual "No fallback chain configured - set one with /fallback." guidance instead of continuing to crawl. Time the provider spends running local tools is excluded from the measurement, and the interactive working line now shows the live rate (`Working (1m 12s - 2.1 tok/s - esc to interrupt)`) so a degraded turn is visible while it runs ([#1739](https://github.com/code-yeongyu/senpi/issues/1739)).
37
-
38
54
  - A session no longer dead-ends when the compaction summarizer is killed by a provider or credential failure. Such a stream used to end required compaction with the raw internal `senpi:no-turn-retry:` marker, which also disabled auto-retry and model fallback; because compaction never applied, the context stayed above the threshold and every following prompt failed identically. Those failures now apply the deterministic compaction checkpoint (retained-suffix safety checks unchanged) and the turn continues, with one plain warning saying a provider summary could not be completed, that a checkpoint was applied and older detail was dropped, and that it is safe to continue. Provider refusals, missing credentials and user aborts still surface loudly instead of reducing context. One compaction is also bounded at 15 minutes total across every attempt and retry regardless of input size (an explicit `compaction.summarizationMaxDurationMs` above that still wins), and the summary stream's final settlement now happens inside the watchdog, so a provider whose stream ends without a terminal event can no longer park compaction with no timer armed ([#1741](https://github.com/code-yeongyu/senpi/issues/1741)).
39
55
 
40
56
  ### Removed
@@ -0,0 +1,22 @@
1
+ import type { ExtensionFlag, InlineExtension } from "../core/extensions/types.ts";
2
+ import type { SettingsManager } from "../core/settings-manager.ts";
3
+ export interface HelpExtensionFlagsResult {
4
+ readonly flags: ExtensionFlag[];
5
+ readonly extensionPaths: string[];
6
+ }
7
+ /**
8
+ * Load extensions for their CLI flags and nothing else.
9
+ *
10
+ * Help renders flag descriptors, so this deliberately skips every other resource class and the
11
+ * whole model/session stack that `createAgentSessionServices` would build: skills, prompt
12
+ * templates, themes and context files cannot register a flag.
13
+ */
14
+ export declare function resolveHelpExtensionFlags(options: {
15
+ readonly cwd: string;
16
+ readonly agentDir: string;
17
+ readonly settingsManager: SettingsManager;
18
+ readonly additionalExtensionPaths: readonly string[];
19
+ readonly noExtensions: boolean;
20
+ readonly extensionFactories?: readonly InlineExtension[];
21
+ }): Promise<HelpExtensionFlagsResult>;
22
+ //# sourceMappingURL=help-extension-flags.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"help-extension-flags.d.ts","sourceRoot":"","sources":["../../src/cli/help-extension-flags.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAElF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAEnE,MAAM,WAAW,wBAAwB;IACxC,QAAQ,CAAC,KAAK,EAAE,aAAa,EAAE,CAAC;IAChC,QAAQ,CAAC,cAAc,EAAE,MAAM,EAAE,CAAC;CAClC;AAED;;;;;;GAMG;AACH,wBAAsB,yBAAyB,CAAC,OAAO,EAAE;IACxD,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,eAAe,EAAE,eAAe,CAAC;IAC1C,QAAQ,CAAC,wBAAwB,EAAE,SAAS,MAAM,EAAE,CAAC;IACrD,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B,QAAQ,CAAC,kBAAkB,CAAC,EAAE,SAAS,eAAe,EAAE,CAAC;CACzD,GAAG,OAAO,CAAC,wBAAwB,CAAC,CAoBpC"}
@@ -0,0 +1,30 @@
1
+ import { DefaultResourceLoader } from "../core/resource-loader.js";
2
+ /**
3
+ * Load extensions for their CLI flags and nothing else.
4
+ *
5
+ * Help renders flag descriptors, so this deliberately skips every other resource class and the
6
+ * whole model/session stack that `createAgentSessionServices` would build: skills, prompt
7
+ * templates, themes and context files cannot register a flag.
8
+ */
9
+ export async function resolveHelpExtensionFlags(options) {
10
+ const resourceLoader = new DefaultResourceLoader({
11
+ cwd: options.cwd,
12
+ agentDir: options.agentDir,
13
+ settingsManager: options.settingsManager,
14
+ sharedHostEnabled: false,
15
+ additionalExtensionPaths: [...options.additionalExtensionPaths],
16
+ noExtensions: options.noExtensions,
17
+ noSkills: true,
18
+ noPromptTemplates: true,
19
+ noThemes: true,
20
+ noContextFiles: true,
21
+ ...(options.extensionFactories ? { extensionFactories: [...options.extensionFactories] } : {}),
22
+ });
23
+ await resourceLoader.reload();
24
+ const { extensions } = resourceLoader.getExtensions();
25
+ return {
26
+ flags: extensions.flatMap((extension) => [...extension.flags.values()]),
27
+ extensionPaths: extensions.map((extension) => extension.resolvedPath),
28
+ };
29
+ }
30
+ //# sourceMappingURL=help-extension-flags.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"help-extension-flags.js","sourceRoot":"","sources":["../../src/cli/help-extension-flags.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAQnE;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAAC,OAO/C;IACA,MAAM,cAAc,GAAG,IAAI,qBAAqB,CAAC;QAChD,GAAG,EAAE,OAAO,CAAC,GAAG;QAChB,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,eAAe,EAAE,OAAO,CAAC,eAAe;QACxC,iBAAiB,EAAE,KAAK;QACxB,wBAAwB,EAAE,CAAC,GAAG,OAAO,CAAC,wBAAwB,CAAC;QAC/D,YAAY,EAAE,OAAO,CAAC,YAAY;QAClC,QAAQ,EAAE,IAAI;QACd,iBAAiB,EAAE,IAAI;QACvB,QAAQ,EAAE,IAAI;QACd,cAAc,EAAE,IAAI;QACpB,GAAG,CAAC,OAAO,CAAC,kBAAkB,CAAC,CAAC,CAAC,EAAE,kBAAkB,EAAE,CAAC,GAAG,OAAO,CAAC,kBAAkB,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC9F,CAAC,CAAC;IACH,MAAM,cAAc,CAAC,MAAM,EAAE,CAAC;IAC9B,MAAM,EAAE,UAAU,EAAE,GAAG,cAAc,CAAC,aAAa,EAAE,CAAC;IACtD,OAAO;QACN,KAAK,EAAE,UAAU,CAAC,OAAO,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,GAAG,SAAS,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;QACvE,cAAc,EAAE,UAAU,CAAC,GAAG,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,YAAY,CAAC;KACrE,CAAC;AACH,CAAC","sourcesContent":["import type { ExtensionFlag, InlineExtension } from \"../core/extensions/types.ts\";\nimport { DefaultResourceLoader } from \"../core/resource-loader.ts\";\nimport type { SettingsManager } from \"../core/settings-manager.ts\";\n\nexport interface HelpExtensionFlagsResult {\n\treadonly flags: ExtensionFlag[];\n\treadonly extensionPaths: string[];\n}\n\n/**\n * Load extensions for their CLI flags and nothing else.\n *\n * Help renders flag descriptors, so this deliberately skips every other resource class and the\n * whole model/session stack that `createAgentSessionServices` would build: skills, prompt\n * templates, themes and context files cannot register a flag.\n */\nexport async function resolveHelpExtensionFlags(options: {\n\treadonly cwd: string;\n\treadonly agentDir: string;\n\treadonly settingsManager: SettingsManager;\n\treadonly additionalExtensionPaths: readonly string[];\n\treadonly noExtensions: boolean;\n\treadonly extensionFactories?: readonly InlineExtension[];\n}): Promise<HelpExtensionFlagsResult> {\n\tconst resourceLoader = new DefaultResourceLoader({\n\t\tcwd: options.cwd,\n\t\tagentDir: options.agentDir,\n\t\tsettingsManager: options.settingsManager,\n\t\tsharedHostEnabled: false,\n\t\tadditionalExtensionPaths: [...options.additionalExtensionPaths],\n\t\tnoExtensions: options.noExtensions,\n\t\tnoSkills: true,\n\t\tnoPromptTemplates: true,\n\t\tnoThemes: true,\n\t\tnoContextFiles: true,\n\t\t...(options.extensionFactories ? { extensionFactories: [...options.extensionFactories] } : {}),\n\t});\n\tawait resourceLoader.reload();\n\tconst { extensions } = resourceLoader.getExtensions();\n\treturn {\n\t\tflags: extensions.flatMap((extension) => [...extension.flags.values()]),\n\t\textensionPaths: extensions.map((extension) => extension.resolvedPath),\n\t};\n}\n"]}
@@ -0,0 +1,19 @@
1
+ import { type Args } from "./args.ts";
2
+ import { type HelpFlagsScope } from "./help-flags-cache.ts";
3
+ export declare function isPlainHelpRequest(parsed: Args): boolean;
4
+ /**
5
+ * A help screen never prompts for project trust and never runs project-local extension code
6
+ * the user has not already trusted: only a recorded decision, or an explicit override, lets
7
+ * project resources in.
8
+ */
9
+ export declare function resolveHelpProjectTrust(parsed: Args, cwd: string, agentDir: string): boolean;
10
+ export declare function helpFlagsScope(parsed: Args, cwd: string, agentDir: string, projectTrusted: boolean): HelpFlagsScope;
11
+ /**
12
+ * Answer `--help` before the engine module graph is imported.
13
+ *
14
+ * Returns false for every launch it cannot answer from what is already known - a non-plain help
15
+ * request, or a scope whose cached flags are missing or stale - and the normal startup path then
16
+ * resolves the flags and refills the cache.
17
+ */
18
+ export declare function tryPrintHelpWithoutEngine(argv: readonly string[]): boolean;
19
+ //# sourceMappingURL=help-fast-path.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"help-fast-path.d.ts","sourceRoot":"","sources":["../../src/cli/help-fast-path.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,IAAI,EAAwB,MAAM,WAAW,CAAC;AAC5D,OAAO,EAAE,KAAK,cAAc,EAAsB,MAAM,uBAAuB,CAAC;AAEhF,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,IAAI,GAAG,OAAO,CAExD;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAI5F;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,cAAc,EAAE,OAAO,GAAG,cAAc,CAQnH;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAuB1E"}
@@ -0,0 +1,64 @@
1
+ import { getAgentDir } from "../config.js";
2
+ import { hasTrustRequiringProjectResources, ProjectTrustStore } from "../core/trust-manager.js";
3
+ import { parseArgs, printHelp } from "./args.js";
4
+ import { readHelpFlagsCache } from "./help-flags-cache.js";
5
+ export function isPlainHelpRequest(parsed) {
6
+ return parsed.help === true && parsed.print !== true && parsed.mode === undefined;
7
+ }
8
+ /**
9
+ * A help screen never prompts for project trust and never runs project-local extension code
10
+ * the user has not already trusted: only a recorded decision, or an explicit override, lets
11
+ * project resources in.
12
+ */
13
+ export function resolveHelpProjectTrust(parsed, cwd, agentDir) {
14
+ if (parsed.projectTrustOverride !== undefined)
15
+ return parsed.projectTrustOverride;
16
+ if (!hasTrustRequiringProjectResources(cwd))
17
+ return true;
18
+ return new ProjectTrustStore(agentDir).get(cwd) === true;
19
+ }
20
+ export function helpFlagsScope(parsed, cwd, agentDir, projectTrusted) {
21
+ return {
22
+ cwd,
23
+ agentDir,
24
+ cliExtensionPaths: [...(parsed.extensions ?? [])],
25
+ noExtensions: parsed.noExtensions === true,
26
+ projectTrusted,
27
+ };
28
+ }
29
+ /**
30
+ * Answer `--help` before the engine module graph is imported.
31
+ *
32
+ * Returns false for every launch it cannot answer from what is already known - a non-plain help
33
+ * request, or a scope whose cached flags are missing or stale - and the normal startup path then
34
+ * resolves the flags and refills the cache.
35
+ */
36
+ export function tryPrintHelpWithoutEngine(argv) {
37
+ let parsed;
38
+ try {
39
+ parsed = parseArgs([...argv]);
40
+ }
41
+ catch {
42
+ return false;
43
+ }
44
+ if (!isPlainHelpRequest(parsed) || parsed.diagnostics.length > 0)
45
+ return false;
46
+ if (parsed.noExtensions === true) {
47
+ printHelp([]);
48
+ return true;
49
+ }
50
+ let flags;
51
+ try {
52
+ const cwd = process.cwd();
53
+ const agentDir = getAgentDir();
54
+ flags = readHelpFlagsCache(helpFlagsScope(parsed, cwd, agentDir, resolveHelpProjectTrust(parsed, cwd, agentDir)));
55
+ }
56
+ catch {
57
+ return false;
58
+ }
59
+ if (!flags)
60
+ return false;
61
+ printHelp(flags);
62
+ return true;
63
+ }
64
+ //# sourceMappingURL=help-fast-path.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"help-fast-path.js","sourceRoot":"","sources":["../../src/cli/help-fast-path.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAE3C,OAAO,EAAE,iCAAiC,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAChG,OAAO,EAAa,SAAS,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAC5D,OAAO,EAAuB,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAEhF,MAAM,UAAU,kBAAkB,CAAC,MAAY;IAC9C,OAAO,MAAM,CAAC,IAAI,KAAK,IAAI,IAAI,MAAM,CAAC,KAAK,KAAK,IAAI,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS,CAAC;AACnF,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,uBAAuB,CAAC,MAAY,EAAE,GAAW,EAAE,QAAgB;IAClF,IAAI,MAAM,CAAC,oBAAoB,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC,oBAAoB,CAAC;IAClF,IAAI,CAAC,iCAAiC,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IACzD,OAAO,IAAI,iBAAiB,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,IAAI,CAAC;AAC1D,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,MAAY,EAAE,GAAW,EAAE,QAAgB,EAAE,cAAuB;IAClG,OAAO;QACN,GAAG;QACH,QAAQ;QACR,iBAAiB,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC;QACjD,YAAY,EAAE,MAAM,CAAC,YAAY,KAAK,IAAI;QAC1C,cAAc;KACd,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,yBAAyB,CAAC,IAAuB;IAChE,IAAI,MAAY,CAAC;IACjB,IAAI,CAAC;QACJ,MAAM,GAAG,SAAS,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC;IAC/B,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,KAAK,CAAC;IACd,CAAC;IACD,IAAI,CAAC,kBAAkB,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IAC/E,IAAI,MAAM,CAAC,YAAY,KAAK,IAAI,EAAE,CAAC;QAClC,SAAS,CAAC,EAAE,CAAC,CAAC;QACd,OAAO,IAAI,CAAC;IACb,CAAC;IACD,IAAI,KAAkC,CAAC;IACvC,IAAI,CAAC;QACJ,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;QAC1B,MAAM,QAAQ,GAAG,WAAW,EAAE,CAAC;QAC/B,KAAK,GAAG,kBAAkB,CAAC,cAAc,CAAC,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,uBAAuB,CAAC,MAAM,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC;IACnH,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,KAAK,CAAC;IACd,CAAC;IACD,IAAI,CAAC,KAAK;QAAE,OAAO,KAAK,CAAC;IACzB,SAAS,CAAC,KAAK,CAAC,CAAC;IACjB,OAAO,IAAI,CAAC;AACb,CAAC","sourcesContent":["import { getAgentDir } from \"../config.ts\";\nimport type { ExtensionFlag } from \"../core/extensions/types.ts\";\nimport { hasTrustRequiringProjectResources, ProjectTrustStore } from \"../core/trust-manager.ts\";\nimport { type Args, parseArgs, printHelp } from \"./args.ts\";\nimport { type HelpFlagsScope, readHelpFlagsCache } from \"./help-flags-cache.ts\";\n\nexport function isPlainHelpRequest(parsed: Args): boolean {\n\treturn parsed.help === true && parsed.print !== true && parsed.mode === undefined;\n}\n\n/**\n * A help screen never prompts for project trust and never runs project-local extension code\n * the user has not already trusted: only a recorded decision, or an explicit override, lets\n * project resources in.\n */\nexport function resolveHelpProjectTrust(parsed: Args, cwd: string, agentDir: string): boolean {\n\tif (parsed.projectTrustOverride !== undefined) return parsed.projectTrustOverride;\n\tif (!hasTrustRequiringProjectResources(cwd)) return true;\n\treturn new ProjectTrustStore(agentDir).get(cwd) === true;\n}\n\nexport function helpFlagsScope(parsed: Args, cwd: string, agentDir: string, projectTrusted: boolean): HelpFlagsScope {\n\treturn {\n\t\tcwd,\n\t\tagentDir,\n\t\tcliExtensionPaths: [...(parsed.extensions ?? [])],\n\t\tnoExtensions: parsed.noExtensions === true,\n\t\tprojectTrusted,\n\t};\n}\n\n/**\n * Answer `--help` before the engine module graph is imported.\n *\n * Returns false for every launch it cannot answer from what is already known - a non-plain help\n * request, or a scope whose cached flags are missing or stale - and the normal startup path then\n * resolves the flags and refills the cache.\n */\nexport function tryPrintHelpWithoutEngine(argv: readonly string[]): boolean {\n\tlet parsed: Args;\n\ttry {\n\t\tparsed = parseArgs([...argv]);\n\t} catch {\n\t\treturn false;\n\t}\n\tif (!isPlainHelpRequest(parsed) || parsed.diagnostics.length > 0) return false;\n\tif (parsed.noExtensions === true) {\n\t\tprintHelp([]);\n\t\treturn true;\n\t}\n\tlet flags: ExtensionFlag[] | undefined;\n\ttry {\n\t\tconst cwd = process.cwd();\n\t\tconst agentDir = getAgentDir();\n\t\tflags = readHelpFlagsCache(helpFlagsScope(parsed, cwd, agentDir, resolveHelpProjectTrust(parsed, cwd, agentDir)));\n\t} catch {\n\t\treturn false;\n\t}\n\tif (!flags) return false;\n\tprintHelp(flags);\n\treturn true;\n}\n"]}
@@ -0,0 +1,23 @@
1
+ import type { ExtensionFlag } from "../core/extensions/types.ts";
2
+ export interface HelpFlagsScope {
3
+ readonly cwd: string;
4
+ readonly agentDir: string;
5
+ readonly cliExtensionPaths: readonly string[];
6
+ readonly noExtensions: boolean;
7
+ readonly projectTrusted: boolean;
8
+ }
9
+ /**
10
+ * Flags a previous run resolved for this exact scope, or `undefined` when anything that feeds
11
+ * extension discovery changed. Never throws: a help screen must not depend on its own cache.
12
+ */
13
+ export declare function readHelpFlagsCache(scope: HelpFlagsScope): ExtensionFlag[] | undefined;
14
+ /**
15
+ * Record the flags a full extension load produced. Failure is silent by contract: this runs on
16
+ * the startup path, where a cache write must never be the reason a launch fails.
17
+ */
18
+ export declare function writeHelpFlagsCache(options: {
19
+ readonly scope: HelpFlagsScope;
20
+ readonly flags: readonly ExtensionFlag[];
21
+ readonly extensionPaths: readonly string[];
22
+ }): void;
23
+ //# sourceMappingURL=help-flags-cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"help-flags-cache.d.ts","sourceRoot":"","sources":["../../src/cli/help-flags-cache.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAKjE,MAAM,WAAW,cAAc;IAC9B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,iBAAiB,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9C,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;CACjC;AAgFD;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,cAAc,GAAG,aAAa,EAAE,GAAG,SAAS,CAOrF;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE;IAC5C,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;IAC/B,QAAQ,CAAC,KAAK,EAAE,SAAS,aAAa,EAAE,CAAC;IACzC,QAAQ,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC;CAC3C,GAAG,IAAI,CA4BP"}
@@ -0,0 +1,118 @@
1
+ import { createHash } from "node:crypto";
2
+ import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
3
+ import { dirname, isAbsolute, join } from "node:path";
4
+ import { CONFIG_DIR_NAME, DISPLAY_VERSION } from "../config.js";
5
+ const CACHE_FILE_VERSION = 1;
6
+ const MAX_CACHED_SCOPES = 8;
7
+ function cachePath(agentDir) {
8
+ return join(agentDir, "cache", "help-flags.json");
9
+ }
10
+ function scopeKey(scope) {
11
+ const material = [
12
+ scope.cwd,
13
+ scope.noExtensions ? "no-extensions" : "extensions",
14
+ scope.projectTrusted ? "trusted" : "untrusted",
15
+ ...scope.cliExtensionPaths,
16
+ ].join("\u0000");
17
+ return createHash("sha256").update(material).digest("hex").slice(0, 32);
18
+ }
19
+ /**
20
+ * A path's identity for cache validation. Directories carry only their mtime, which is what
21
+ * changes when an extension file is added or removed inside them; files carry mtime and size,
22
+ * so an in-place upgrade of a bundled plugin invalidates the entry it produced.
23
+ */
24
+ function stamp(path) {
25
+ try {
26
+ const stats = statSync(path);
27
+ if (stats.isDirectory())
28
+ return `d:${stats.mtimeMs}`;
29
+ return `f:${stats.mtimeMs}:${stats.size}`;
30
+ }
31
+ catch {
32
+ return "absent";
33
+ }
34
+ }
35
+ function discoveryInputs(scope, extensionPaths) {
36
+ const paths = new Set([
37
+ join(scope.agentDir, "settings.json"),
38
+ join(scope.agentDir, "extensions"),
39
+ join(scope.agentDir, "trust.json"),
40
+ join(scope.cwd, CONFIG_DIR_NAME, "settings.json"),
41
+ join(scope.cwd, CONFIG_DIR_NAME, "extensions"),
42
+ ]);
43
+ for (const path of extensionPaths) {
44
+ if (!isAbsolute(path))
45
+ continue;
46
+ paths.add(path);
47
+ paths.add(dirname(path));
48
+ }
49
+ for (const path of scope.cliExtensionPaths) {
50
+ if (!isAbsolute(path))
51
+ continue;
52
+ paths.add(path);
53
+ }
54
+ return [...paths].sort();
55
+ }
56
+ function readCacheFile(agentDir) {
57
+ try {
58
+ const parsed = JSON.parse(readFileSync(cachePath(agentDir), "utf8"));
59
+ if (parsed.version !== CACHE_FILE_VERSION || typeof parsed.scopes !== "object" || parsed.scopes === null) {
60
+ return undefined;
61
+ }
62
+ return parsed;
63
+ }
64
+ catch {
65
+ return undefined;
66
+ }
67
+ }
68
+ /**
69
+ * Flags a previous run resolved for this exact scope, or `undefined` when anything that feeds
70
+ * extension discovery changed. Never throws: a help screen must not depend on its own cache.
71
+ */
72
+ export function readHelpFlagsCache(scope) {
73
+ const cached = readCacheFile(scope.agentDir)?.scopes[scopeKey(scope)];
74
+ if (!cached || cached.appVersion !== DISPLAY_VERSION || !Array.isArray(cached.inputs))
75
+ return undefined;
76
+ for (const input of cached.inputs) {
77
+ if (stamp(input.path) !== input.state)
78
+ return undefined;
79
+ }
80
+ return [...cached.flags];
81
+ }
82
+ /**
83
+ * Record the flags a full extension load produced. Failure is silent by contract: this runs on
84
+ * the startup path, where a cache write must never be the reason a launch fails.
85
+ */
86
+ export function writeHelpFlagsCache(options) {
87
+ const { scope, flags, extensionPaths } = options;
88
+ try {
89
+ const existing = readCacheFile(scope.agentDir);
90
+ const scopes = { ...(existing?.scopes ?? {}) };
91
+ scopes[scopeKey(scope)] = {
92
+ writtenAt: Date.now(),
93
+ appVersion: DISPLAY_VERSION,
94
+ inputs: discoveryInputs(scope, extensionPaths).map((path) => ({ path, state: stamp(path) })),
95
+ flags: [...flags],
96
+ };
97
+ const keptEntries = Object.entries(scopes)
98
+ .sort(([, left], [, right]) => right.writtenAt - left.writtenAt)
99
+ .slice(0, MAX_CACHED_SCOPES);
100
+ const file = { version: CACHE_FILE_VERSION, scopes: Object.fromEntries(keptEntries) };
101
+ const target = cachePath(scope.agentDir);
102
+ mkdirSync(dirname(target), { recursive: true });
103
+ const temporary = `${target}.${process.pid}.tmp`;
104
+ writeFileSync(temporary, JSON.stringify(file), { mode: 0o600 });
105
+ try {
106
+ renameSync(temporary, target);
107
+ }
108
+ catch (error) {
109
+ if (existsSync(temporary))
110
+ rmSync(temporary, { force: true });
111
+ throw error;
112
+ }
113
+ }
114
+ catch {
115
+ return;
116
+ }
117
+ }
118
+ //# sourceMappingURL=help-flags-cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"help-flags-cache.js","sourceRoot":"","sources":["../../src/cli/help-flags-cache.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from \"node:crypto\";\nimport { existsSync, mkdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from \"node:fs\";\nimport { dirname, isAbsolute, join } from \"node:path\";\nimport { CONFIG_DIR_NAME, DISPLAY_VERSION } from \"../config.ts\";\nimport type { ExtensionFlag } from \"../core/extensions/types.ts\";\n\nconst CACHE_FILE_VERSION = 1;\nconst MAX_CACHED_SCOPES = 8;\n\nexport interface HelpFlagsScope {\n\treadonly cwd: string;\n\treadonly agentDir: string;\n\treadonly cliExtensionPaths: readonly string[];\n\treadonly noExtensions: boolean;\n\treadonly projectTrusted: boolean;\n}\n\ninterface InputStamp {\n\treadonly path: string;\n\treadonly state: string;\n}\n\ninterface CachedScope {\n\treadonly writtenAt: number;\n\treadonly appVersion: string;\n\treadonly inputs: readonly InputStamp[];\n\treadonly flags: readonly ExtensionFlag[];\n}\n\ninterface CacheFile {\n\treadonly version: number;\n\treadonly scopes: Record<string, CachedScope>;\n}\n\nfunction cachePath(agentDir: string): string {\n\treturn join(agentDir, \"cache\", \"help-flags.json\");\n}\n\nfunction scopeKey(scope: HelpFlagsScope): string {\n\tconst material = [\n\t\tscope.cwd,\n\t\tscope.noExtensions ? \"no-extensions\" : \"extensions\",\n\t\tscope.projectTrusted ? \"trusted\" : \"untrusted\",\n\t\t...scope.cliExtensionPaths,\n\t].join(\"\\u0000\");\n\treturn createHash(\"sha256\").update(material).digest(\"hex\").slice(0, 32);\n}\n\n/**\n * A path's identity for cache validation. Directories carry only their mtime, which is what\n * changes when an extension file is added or removed inside them; files carry mtime and size,\n * so an in-place upgrade of a bundled plugin invalidates the entry it produced.\n */\nfunction stamp(path: string): string {\n\ttry {\n\t\tconst stats = statSync(path);\n\t\tif (stats.isDirectory()) return `d:${stats.mtimeMs}`;\n\t\treturn `f:${stats.mtimeMs}:${stats.size}`;\n\t} catch {\n\t\treturn \"absent\";\n\t}\n}\n\nfunction discoveryInputs(scope: HelpFlagsScope, extensionPaths: readonly string[]): string[] {\n\tconst paths = new Set<string>([\n\t\tjoin(scope.agentDir, \"settings.json\"),\n\t\tjoin(scope.agentDir, \"extensions\"),\n\t\tjoin(scope.agentDir, \"trust.json\"),\n\t\tjoin(scope.cwd, CONFIG_DIR_NAME, \"settings.json\"),\n\t\tjoin(scope.cwd, CONFIG_DIR_NAME, \"extensions\"),\n\t]);\n\tfor (const path of extensionPaths) {\n\t\tif (!isAbsolute(path)) continue;\n\t\tpaths.add(path);\n\t\tpaths.add(dirname(path));\n\t}\n\tfor (const path of scope.cliExtensionPaths) {\n\t\tif (!isAbsolute(path)) continue;\n\t\tpaths.add(path);\n\t}\n\treturn [...paths].sort();\n}\n\nfunction readCacheFile(agentDir: string): CacheFile | undefined {\n\ttry {\n\t\tconst parsed = JSON.parse(readFileSync(cachePath(agentDir), \"utf8\")) as CacheFile;\n\t\tif (parsed.version !== CACHE_FILE_VERSION || typeof parsed.scopes !== \"object\" || parsed.scopes === null) {\n\t\t\treturn undefined;\n\t\t}\n\t\treturn parsed;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\n/**\n * Flags a previous run resolved for this exact scope, or `undefined` when anything that feeds\n * extension discovery changed. Never throws: a help screen must not depend on its own cache.\n */\nexport function readHelpFlagsCache(scope: HelpFlagsScope): ExtensionFlag[] | undefined {\n\tconst cached = readCacheFile(scope.agentDir)?.scopes[scopeKey(scope)];\n\tif (!cached || cached.appVersion !== DISPLAY_VERSION || !Array.isArray(cached.inputs)) return undefined;\n\tfor (const input of cached.inputs) {\n\t\tif (stamp(input.path) !== input.state) return undefined;\n\t}\n\treturn [...cached.flags];\n}\n\n/**\n * Record the flags a full extension load produced. Failure is silent by contract: this runs on\n * the startup path, where a cache write must never be the reason a launch fails.\n */\nexport function writeHelpFlagsCache(options: {\n\treadonly scope: HelpFlagsScope;\n\treadonly flags: readonly ExtensionFlag[];\n\treadonly extensionPaths: readonly string[];\n}): void {\n\tconst { scope, flags, extensionPaths } = options;\n\ttry {\n\t\tconst existing = readCacheFile(scope.agentDir);\n\t\tconst scopes: Record<string, CachedScope> = { ...(existing?.scopes ?? {}) };\n\t\tscopes[scopeKey(scope)] = {\n\t\t\twrittenAt: Date.now(),\n\t\t\tappVersion: DISPLAY_VERSION,\n\t\t\tinputs: discoveryInputs(scope, extensionPaths).map((path) => ({ path, state: stamp(path) })),\n\t\t\tflags: [...flags],\n\t\t};\n\t\tconst keptEntries = Object.entries(scopes)\n\t\t\t.sort(([, left], [, right]) => right.writtenAt - left.writtenAt)\n\t\t\t.slice(0, MAX_CACHED_SCOPES);\n\t\tconst file: CacheFile = { version: CACHE_FILE_VERSION, scopes: Object.fromEntries(keptEntries) };\n\t\tconst target = cachePath(scope.agentDir);\n\t\tmkdirSync(dirname(target), { recursive: true });\n\t\tconst temporary = `${target}.${process.pid}.tmp`;\n\t\twriteFileSync(temporary, JSON.stringify(file), { mode: 0o600 });\n\t\ttry {\n\t\t\trenameSync(temporary, target);\n\t\t} catch (error) {\n\t\t\tif (existsSync(temporary)) rmSync(temporary, { force: true });\n\t\t\tthrow error;\n\t\t}\n\t} catch {\n\t\treturn;\n\t}\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"startup-loading-indicator.d.ts","sourceRoot":"","sources":["../../src/cli/startup-loading-indicator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AACvE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AAaxD,MAAM,WAAW,8BAA8B;IAC9C,QAAQ,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACpC;AAED,MAAM,WAAW,uBAAuB;IACvC,KAAK,IAAI,IAAI,CAAC;IACd,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAAC;IAC1C,KAAK,IAAI,IAAI,CAAC;IACd,MAAM,IAAI,IAAI,CAAC;IACf,IAAI,IAAI,IAAI,CAAC;IACb,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC1B;AA8HD,wBAAgB,6BAA6B,CAAC,OAAO,EAAE,8BAA8B,GAAG,uBAAuB,CAE9G;AAED,wBAAgB,iCAAiC,CAAC,KAAK,EAAE;IACxD,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;CAChC,GAAG,OAAO,CAEV;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,CAC1C,OAAO,EAAE,mBAAmB,EAC5B,SAAS,EAAE,IAAI,CAAC,uBAAuB,EAAE,OAAO,GAAG,QAAQ,CAAC,GAC1D,mBAAmB,CAsBrB"}
1
+ {"version":3,"file":"startup-loading-indicator.d.ts","sourceRoot":"","sources":["../../src/cli/startup-loading-indicator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AACvE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AAaxD,MAAM,WAAW,8BAA8B;IAC9C,QAAQ,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACpC;AAED,MAAM,WAAW,uBAAuB;IACvC,KAAK,IAAI,IAAI,CAAC;IACd,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAAC;IAC1C,KAAK,IAAI,IAAI,CAAC;IACd,MAAM,IAAI,IAAI,CAAC;IACf,IAAI,IAAI,IAAI,CAAC;IACb,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC1B;AAmID,wBAAgB,6BAA6B,CAAC,OAAO,EAAE,8BAA8B,GAAG,uBAAuB,CAE9G;AAED,wBAAgB,iCAAiC,CAAC,KAAK,EAAE;IACxD,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;CAChC,GAAG,OAAO,CAEV;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,CAC1C,OAAO,EAAE,mBAAmB,EAC5B,SAAS,EAAE,IAAI,CAAC,uBAAuB,EAAE,OAAO,GAAG,QAAQ,CAAC,GAC1D,mBAAmB,CAsBrB"}
@@ -10,9 +10,12 @@ const DEFAULT_INTERVAL_MS = 80;
10
10
  /**
11
11
  * Single-line ANSI loading indicator for the pre-TUI startup window, borrowed
12
12
  * from codex's UI-first startup design (codex-rs/tui keeps a dim placeholder
13
- * header until the session is configured). The grace delay keeps fast startups
14
- * flash-free; stop() must run before any other stdout writer (TUI, prompts,
15
- * help) takes over the terminal.
13
+ * header until the session is configured). The first frame is written in
14
+ * start() itself: the work it covers is synchronous module loading, which
15
+ * starves every timer until it is done, so a timer-driven first frame lands
16
+ * only after that work (measured: 2.27s to the first byte on a real pty). The
17
+ * grace delay now gates the animation only; stop() must run before any other
18
+ * stdout writer (TUI, prompts, help) takes over the terminal.
16
19
  */
17
20
  class AnsiStartupLoadingIndicator {
18
21
  constructor(options) {
@@ -45,6 +48,7 @@ class AnsiStartupLoadingIndicator {
45
48
  this.writer(CLEAR_LINE + SHOW_CURSOR);
46
49
  };
47
50
  process.on("exit", this.exitListener);
51
+ this.draw(true);
48
52
  this.startGraceTimer();
49
53
  }
50
54
  setPhase(phase) {
@@ -68,6 +72,7 @@ class AnsiStartupLoadingIndicator {
68
72
  this.beginAnimation();
69
73
  }
70
74
  else {
75
+ this.draw(true);
71
76
  this.startGraceTimer();
72
77
  }
73
78
  }
@@ -89,7 +94,8 @@ class AnsiStartupLoadingIndicator {
89
94
  }, this.graceMs);
90
95
  }
91
96
  beginAnimation() {
92
- this.draw(true);
97
+ if (!this.drawn)
98
+ this.draw(true);
93
99
  this.frameTimer = setInterval(() => {
94
100
  this.frameIndex = (this.frameIndex + 1) % this.frames.length;
95
101
  this.draw(false);
@@ -1 +1 @@
1
- {"version":3,"file":"startup-loading-indicator.js","sourceRoot":"","sources":["../../src/cli/startup-loading-indicator.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ProjectTrustContext } from \"../core/extensions/types.ts\";\nimport type { AppMode } from \"../core/project-trust.ts\";\n\nconst HIDE_CURSOR = \"\\u001b[?25l\";\nconst SHOW_CURSOR = \"\\u001b[?25h\";\nconst CLEAR_LINE = \"\\r\\u001b[2K\";\nconst DIM = \"\\u001b[2m\";\nconst RESET = \"\\u001b[0m\";\n\nconst DEFAULT_FRAMES = [\"⠋\", \"⠙\", \"⠹\", \"⠸\", \"⠼\", \"⠴\", \"⠦\", \"⠧\", \"⠇\", \"⠏\"] as const;\nconst DEFAULT_LABEL = \"Loading\";\nconst DEFAULT_GRACE_MS = 120;\nconst DEFAULT_INTERVAL_MS = 80;\n\nexport interface StartupLoadingIndicatorOptions {\n\treadonly writer: (chunk: string) => void;\n\treadonly isTTY: boolean;\n\treadonly label?: string;\n\treadonly graceMs?: number;\n\treadonly intervalMs?: number;\n\treadonly frames?: readonly string[];\n}\n\nexport interface StartupLoadingIndicator {\n\tstart(): void;\n\tsetPhase(phase: string | undefined): void;\n\tpause(): void;\n\tresume(): void;\n\tstop(): void;\n\treadonly running: boolean;\n}\n\n/**\n * Single-line ANSI loading indicator for the pre-TUI startup window, borrowed\n * from codex's UI-first startup design (codex-rs/tui keeps a dim placeholder\n * header until the session is configured). The grace delay keeps fast startups\n * flash-free; stop() must run before any other stdout writer (TUI, prompts,\n * help) takes over the terminal.\n */\nclass AnsiStartupLoadingIndicator implements StartupLoadingIndicator {\n\tprivate readonly writer: (chunk: string) => void;\n\tprivate readonly isTTY: boolean;\n\tprivate readonly label: string;\n\tprivate readonly graceMs: number;\n\tprivate readonly intervalMs: number;\n\tprivate readonly frames: readonly string[];\n\tprivate phase: string | undefined = undefined;\n\tprivate frameIndex = 0;\n\tprivate drawn = false;\n\tprivate graceElapsed = false;\n\tprivate started = false;\n\tprivate paused = false;\n\tprivate stopped = false;\n\tprivate graceTimer: NodeJS.Timeout | undefined = undefined;\n\tprivate frameTimer: NodeJS.Timeout | undefined = undefined;\n\tprivate exitListener: (() => void) | undefined = undefined;\n\n\tconstructor(options: StartupLoadingIndicatorOptions) {\n\t\tthis.writer = options.writer;\n\t\tthis.isTTY = options.isTTY;\n\t\tthis.label = options.label ?? DEFAULT_LABEL;\n\t\tthis.graceMs = options.graceMs ?? DEFAULT_GRACE_MS;\n\t\tthis.intervalMs = options.intervalMs ?? DEFAULT_INTERVAL_MS;\n\t\tthis.frames = options.frames && options.frames.length > 0 ? options.frames : DEFAULT_FRAMES;\n\t}\n\n\tget running(): boolean {\n\t\treturn this.started && !this.stopped;\n\t}\n\n\tstart(): void {\n\t\tif (!this.isTTY || this.started || this.stopped) return;\n\t\tthis.started = true;\n\t\tthis.exitListener = () => {\n\t\t\tif (this.drawn) this.writer(CLEAR_LINE + SHOW_CURSOR);\n\t\t};\n\t\tprocess.on(\"exit\", this.exitListener);\n\t\tthis.startGraceTimer();\n\t}\n\n\tsetPhase(phase: string | undefined): void {\n\t\tthis.phase = phase;\n\t\tif (this.drawn && !this.paused && !this.stopped) {\n\t\t\tthis.draw(false);\n\t\t}\n\t}\n\n\tpause(): void {\n\t\tif (!this.started || this.stopped || this.paused) return;\n\t\tthis.paused = true;\n\t\tthis.clearTimers();\n\t\tthis.eraseLine();\n\t}\n\n\tresume(): void {\n\t\tif (!this.started || this.stopped || !this.paused) return;\n\t\tthis.paused = false;\n\t\tif (this.graceElapsed) {\n\t\t\tthis.beginAnimation();\n\t\t} else {\n\t\t\tthis.startGraceTimer();\n\t\t}\n\t}\n\n\tstop(): void {\n\t\tif (this.stopped) return;\n\t\tthis.stopped = true;\n\t\tthis.clearTimers();\n\t\tthis.eraseLine();\n\t\tif (this.exitListener) {\n\t\t\tprocess.removeListener(\"exit\", this.exitListener);\n\t\t\tthis.exitListener = undefined;\n\t\t}\n\t}\n\n\tprivate startGraceTimer(): void {\n\t\tthis.graceTimer = setTimeout(() => {\n\t\t\tthis.graceElapsed = true;\n\t\t\tthis.beginAnimation();\n\t\t}, this.graceMs);\n\t}\n\n\tprivate beginAnimation(): void {\n\t\tthis.draw(true);\n\t\tthis.frameTimer = setInterval(() => {\n\t\t\tthis.frameIndex = (this.frameIndex + 1) % this.frames.length;\n\t\t\tthis.draw(false);\n\t\t}, this.intervalMs);\n\t}\n\n\tprivate draw(hideCursor: boolean): void {\n\t\tconst frame = this.frames[this.frameIndex] ?? \"\";\n\t\tconst phaseSuffix = this.phase ? ` ${this.phase}` : \"\";\n\t\tconst line = `${DIM}${frame} ${this.label}…${phaseSuffix}${RESET}`;\n\t\tthis.writer(`${hideCursor ? HIDE_CURSOR : \"\"}${CLEAR_LINE}${line}`);\n\t\tthis.drawn = true;\n\t}\n\n\tprivate eraseLine(): void {\n\t\tif (!this.drawn) return;\n\t\tthis.writer(CLEAR_LINE + SHOW_CURSOR);\n\t\tthis.drawn = false;\n\t}\n\n\tprivate clearTimers(): void {\n\t\tif (this.graceTimer) {\n\t\t\tclearTimeout(this.graceTimer);\n\t\t\tthis.graceTimer = undefined;\n\t\t}\n\t\tif (this.frameTimer) {\n\t\t\tclearInterval(this.frameTimer);\n\t\t\tthis.frameTimer = undefined;\n\t\t}\n\t}\n}\n\nexport function createStartupLoadingIndicator(options: StartupLoadingIndicatorOptions): StartupLoadingIndicator {\n\treturn new AnsiStartupLoadingIndicator(options);\n}\n\nexport function shouldShowStartupLoadingIndicator(input: {\n\treadonly appMode: AppMode;\n\treadonly stdoutIsTTY: boolean;\n\treadonly helpRequested: boolean;\n}): boolean {\n\treturn input.appMode === \"interactive\" && input.stdoutIsTTY && !input.helpRequested;\n}\n\n/**\n * Project-trust prompts open their own startup TUI mid-load; pausing the\n * indicator around each prompt keeps the two from fighting over the terminal.\n */\nexport function pauseIndicatorDuringPrompts(\n\tcontext: ProjectTrustContext,\n\tindicator: Pick<StartupLoadingIndicator, \"pause\" | \"resume\">,\n): ProjectTrustContext {\n\tconst wrapPrompt =\n\t\t<Args extends readonly unknown[], Result>(\n\t\t\tprompt: (...args: Args) => Promise<Result>,\n\t\t): ((...args: Args) => Promise<Result>) =>\n\t\tasync (...args: Args): Promise<Result> => {\n\t\t\tindicator.pause();\n\t\t\ttry {\n\t\t\t\treturn await prompt(...args);\n\t\t\t} finally {\n\t\t\t\tindicator.resume();\n\t\t\t}\n\t\t};\n\treturn {\n\t\t...context,\n\t\tui: {\n\t\t\tselect: wrapPrompt(context.ui.select),\n\t\t\tconfirm: wrapPrompt(context.ui.confirm),\n\t\t\tinput: wrapPrompt(context.ui.input),\n\t\t\tnotify: context.ui.notify,\n\t\t},\n\t};\n}\n"]}
1
+ {"version":3,"file":"startup-loading-indicator.js","sourceRoot":"","sources":["../../src/cli/startup-loading-indicator.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ProjectTrustContext } from \"../core/extensions/types.ts\";\nimport type { AppMode } from \"../core/project-trust.ts\";\n\nconst HIDE_CURSOR = \"\\u001b[?25l\";\nconst SHOW_CURSOR = \"\\u001b[?25h\";\nconst CLEAR_LINE = \"\\r\\u001b[2K\";\nconst DIM = \"\\u001b[2m\";\nconst RESET = \"\\u001b[0m\";\n\nconst DEFAULT_FRAMES = [\"⠋\", \"⠙\", \"⠹\", \"⠸\", \"⠼\", \"⠴\", \"⠦\", \"⠧\", \"⠇\", \"⠏\"] as const;\nconst DEFAULT_LABEL = \"Loading\";\nconst DEFAULT_GRACE_MS = 120;\nconst DEFAULT_INTERVAL_MS = 80;\n\nexport interface StartupLoadingIndicatorOptions {\n\treadonly writer: (chunk: string) => void;\n\treadonly isTTY: boolean;\n\treadonly label?: string;\n\treadonly graceMs?: number;\n\treadonly intervalMs?: number;\n\treadonly frames?: readonly string[];\n}\n\nexport interface StartupLoadingIndicator {\n\tstart(): void;\n\tsetPhase(phase: string | undefined): void;\n\tpause(): void;\n\tresume(): void;\n\tstop(): void;\n\treadonly running: boolean;\n}\n\n/**\n * Single-line ANSI loading indicator for the pre-TUI startup window, borrowed\n * from codex's UI-first startup design (codex-rs/tui keeps a dim placeholder\n * header until the session is configured). The first frame is written in\n * start() itself: the work it covers is synchronous module loading, which\n * starves every timer until it is done, so a timer-driven first frame lands\n * only after that work (measured: 2.27s to the first byte on a real pty). The\n * grace delay now gates the animation only; stop() must run before any other\n * stdout writer (TUI, prompts, help) takes over the terminal.\n */\nclass AnsiStartupLoadingIndicator implements StartupLoadingIndicator {\n\tprivate readonly writer: (chunk: string) => void;\n\tprivate readonly isTTY: boolean;\n\tprivate readonly label: string;\n\tprivate readonly graceMs: number;\n\tprivate readonly intervalMs: number;\n\tprivate readonly frames: readonly string[];\n\tprivate phase: string | undefined = undefined;\n\tprivate frameIndex = 0;\n\tprivate drawn = false;\n\tprivate graceElapsed = false;\n\tprivate started = false;\n\tprivate paused = false;\n\tprivate stopped = false;\n\tprivate graceTimer: NodeJS.Timeout | undefined = undefined;\n\tprivate frameTimer: NodeJS.Timeout | undefined = undefined;\n\tprivate exitListener: (() => void) | undefined = undefined;\n\n\tconstructor(options: StartupLoadingIndicatorOptions) {\n\t\tthis.writer = options.writer;\n\t\tthis.isTTY = options.isTTY;\n\t\tthis.label = options.label ?? DEFAULT_LABEL;\n\t\tthis.graceMs = options.graceMs ?? DEFAULT_GRACE_MS;\n\t\tthis.intervalMs = options.intervalMs ?? DEFAULT_INTERVAL_MS;\n\t\tthis.frames = options.frames && options.frames.length > 0 ? options.frames : DEFAULT_FRAMES;\n\t}\n\n\tget running(): boolean {\n\t\treturn this.started && !this.stopped;\n\t}\n\n\tstart(): void {\n\t\tif (!this.isTTY || this.started || this.stopped) return;\n\t\tthis.started = true;\n\t\tthis.exitListener = () => {\n\t\t\tif (this.drawn) this.writer(CLEAR_LINE + SHOW_CURSOR);\n\t\t};\n\t\tprocess.on(\"exit\", this.exitListener);\n\t\tthis.draw(true);\n\t\tthis.startGraceTimer();\n\t}\n\n\tsetPhase(phase: string | undefined): void {\n\t\tthis.phase = phase;\n\t\tif (this.drawn && !this.paused && !this.stopped) {\n\t\t\tthis.draw(false);\n\t\t}\n\t}\n\n\tpause(): void {\n\t\tif (!this.started || this.stopped || this.paused) return;\n\t\tthis.paused = true;\n\t\tthis.clearTimers();\n\t\tthis.eraseLine();\n\t}\n\n\tresume(): void {\n\t\tif (!this.started || this.stopped || !this.paused) return;\n\t\tthis.paused = false;\n\t\tif (this.graceElapsed) {\n\t\t\tthis.beginAnimation();\n\t\t} else {\n\t\t\tthis.draw(true);\n\t\t\tthis.startGraceTimer();\n\t\t}\n\t}\n\n\tstop(): void {\n\t\tif (this.stopped) return;\n\t\tthis.stopped = true;\n\t\tthis.clearTimers();\n\t\tthis.eraseLine();\n\t\tif (this.exitListener) {\n\t\t\tprocess.removeListener(\"exit\", this.exitListener);\n\t\t\tthis.exitListener = undefined;\n\t\t}\n\t}\n\n\tprivate startGraceTimer(): void {\n\t\tthis.graceTimer = setTimeout(() => {\n\t\t\tthis.graceElapsed = true;\n\t\t\tthis.beginAnimation();\n\t\t}, this.graceMs);\n\t}\n\n\tprivate beginAnimation(): void {\n\t\tif (!this.drawn) this.draw(true);\n\t\tthis.frameTimer = setInterval(() => {\n\t\t\tthis.frameIndex = (this.frameIndex + 1) % this.frames.length;\n\t\t\tthis.draw(false);\n\t\t}, this.intervalMs);\n\t}\n\n\tprivate draw(hideCursor: boolean): void {\n\t\tconst frame = this.frames[this.frameIndex] ?? \"\";\n\t\tconst phaseSuffix = this.phase ? ` ${this.phase}` : \"\";\n\t\tconst line = `${DIM}${frame} ${this.label}…${phaseSuffix}${RESET}`;\n\t\tthis.writer(`${hideCursor ? HIDE_CURSOR : \"\"}${CLEAR_LINE}${line}`);\n\t\tthis.drawn = true;\n\t}\n\n\tprivate eraseLine(): void {\n\t\tif (!this.drawn) return;\n\t\tthis.writer(CLEAR_LINE + SHOW_CURSOR);\n\t\tthis.drawn = false;\n\t}\n\n\tprivate clearTimers(): void {\n\t\tif (this.graceTimer) {\n\t\t\tclearTimeout(this.graceTimer);\n\t\t\tthis.graceTimer = undefined;\n\t\t}\n\t\tif (this.frameTimer) {\n\t\t\tclearInterval(this.frameTimer);\n\t\t\tthis.frameTimer = undefined;\n\t\t}\n\t}\n}\n\nexport function createStartupLoadingIndicator(options: StartupLoadingIndicatorOptions): StartupLoadingIndicator {\n\treturn new AnsiStartupLoadingIndicator(options);\n}\n\nexport function shouldShowStartupLoadingIndicator(input: {\n\treadonly appMode: AppMode;\n\treadonly stdoutIsTTY: boolean;\n\treadonly helpRequested: boolean;\n}): boolean {\n\treturn input.appMode === \"interactive\" && input.stdoutIsTTY && !input.helpRequested;\n}\n\n/**\n * Project-trust prompts open their own startup TUI mid-load; pausing the\n * indicator around each prompt keeps the two from fighting over the terminal.\n */\nexport function pauseIndicatorDuringPrompts(\n\tcontext: ProjectTrustContext,\n\tindicator: Pick<StartupLoadingIndicator, \"pause\" | \"resume\">,\n): ProjectTrustContext {\n\tconst wrapPrompt =\n\t\t<Args extends readonly unknown[], Result>(\n\t\t\tprompt: (...args: Args) => Promise<Result>,\n\t\t): ((...args: Args) => Promise<Result>) =>\n\t\tasync (...args: Args): Promise<Result> => {\n\t\t\tindicator.pause();\n\t\t\ttry {\n\t\t\t\treturn await prompt(...args);\n\t\t\t} finally {\n\t\t\t\tindicator.resume();\n\t\t\t}\n\t\t};\n\treturn {\n\t\t...context,\n\t\tui: {\n\t\t\tselect: wrapPrompt(context.ui.select),\n\t\t\tconfirm: wrapPrompt(context.ui.confirm),\n\t\t\tinput: wrapPrompt(context.ui.input),\n\t\t\tnotify: context.ui.notify,\n\t\t},\n\t};\n}\n"]}
package/dist/cli.js CHANGED
@@ -120,6 +120,15 @@ if (isRootCommand(args) && (args.includes("--version") || args.includes("-v")))
120
120
  console.log(DISPLAY_VERSION);
121
121
  process.exit();
122
122
  }
123
+ // Help is static text plus the flags extensions registered, so a launch that already knows those
124
+ // flags must not import the engine graph to print them. The import stays dynamic for the same
125
+ // reason `cli-main` is: a static one would evaluate that graph before this answer.
126
+ if (isRootCommand(args) && args.some((arg) => arg === "--help" || arg === "-h")) {
127
+ const { tryPrintHelpWithoutEngine } = await import("./cli/help-fast-path.js");
128
+ if (tryPrintHelpWithoutEngine(args)) {
129
+ process.exit();
130
+ }
131
+ }
123
132
  if (isMissingBundledWorkspaceDependencies(getPackageDir())) {
124
133
  if (await handleBootstrapSelfUpdate(args)) {
125
134
  process.exit();
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\nimport \"./valid-cwd.ts\";\nimport { spawn, spawnSync } from \"node:child_process\";\nimport { existsSync, realpathSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { processBunRuntimeOptions, resolveBunReexec } from \"./bun-runtime.ts\";\nimport { enableStartupCompileCache } from \"./compile-cache.ts\";\nimport { APP_NAME, DISPLAY_VERSION, getPackageDir } from \"./config.ts\";\nimport { hasInheritedInspectorOption, releaseInheritedInspectorForChild } from \"./inspector-policy.ts\";\nimport { handleBootstrapSelfUpdate } from \"./self-update-bootstrap.ts\";\n\n// Upstream's `cli/setup.ts` helper is deliberately not used here: this launcher only decides the\n// runtime and process structure, and `cli-main.ts` performs the equivalent process/title/env/http\n// setup for both entry paths (Node launcher and the Bun binary).\n\n/**\n * Hand a Bun-installed CLI to Bun before anything else runs.\n *\n * `bun install -g` links this script into `~/.bun/bin`, but the shebang still starts it on Node,\n * so a user who chose Bun silently gets the Node runtime. Re-exec through the Bun binary when the\n * script really lives in Bun's global tree (or `SENPI_RUNTIME=bun` asks for it). This runs before\n * `enableStartupCompileCache()` on purpose: a re-exec must not pay for Node's compile-cache setup.\n * Node `execArgv` is deliberately dropped — those flags belong to the Node process, not to Bun.\n */\nfunction reexecUnderBunIfNeeded(): boolean {\n\tconst options = processBunRuntimeOptions(existsSync, realpathSync);\n\tlet scriptRealPath = process.argv[1] ?? fileURLToPath(import.meta.url);\n\ttry {\n\t\t// `~/.bun/bin/<name>` is a symlink into the global tree, so the link target is what has\n\t\t// to be classified and re-executed. A path that cannot be resolved is simply used as-is;\n\t\t// runtime selection must never be the reason startup fails.\n\t\tscriptRealPath = realpathSync(scriptRealPath);\n\t} catch {}\n\tconst decision = resolveBunReexec({\n\t\tscriptRealPath,\n\t\tversions: process.versions,\n\t\thasInheritedInspectorOption: hasInheritedInspectorOption(),\n\t\toptions,\n\t});\n\tif (decision.action === \"stay\") {\n\t\treturn false;\n\t}\n\tconst result = spawnSync(decision.bunPath, [scriptRealPath, ...process.argv.slice(2)], {\n\t\tstdio: \"inherit\",\n\t\twindowsHide: true,\n\t});\n\tif (result.signal) {\n\t\tprocess.kill(process.pid, result.signal);\n\t\treturn true;\n\t}\n\tprocess.exitCode = result.status ?? 1;\n\treturn true;\n}\n\nif (reexecUnderBunIfNeeded()) {\n\tprocess.exit();\n}\n\n// Must run before cli-main is loaded, by either path: it caches the engine graph this process\n// imports on the fast path below, and it publishes NODE_COMPILE_CACHE so a spawned cli-main child\n// inherits this process's cache directory instead of resolving and re-filling its own.\nenableStartupCompileCache();\n\nprocess.title = APP_NAME;\nprocess.env.PI_CODING_AGENT = \"true\";\nprocess.env.AI_AGENT = APP_NAME;\nprocess.emitWarning = (() => {}) as typeof process.emitWarning;\n\nconst args = process.argv.slice(2);\nconst PACKAGE_COMMANDS = new Set([\"install\", \"remove\", \"uninstall\", \"update\", \"list\", \"config\"]);\n\nfunction isRootCommand(args: readonly string[]): boolean {\n\tconst firstArg = args[0];\n\treturn firstArg === undefined || !PACKAGE_COMMANDS.has(firstArg);\n}\n\nfunction isPackageManagerInstall(packageDir: string): boolean {\n\treturn packageDir.replace(/\\\\/g, \"/\").includes(\"/node_modules/@code-yeongyu/senpi\");\n}\n\nfunction isMissingBundledWorkspaceDependencies(packageDir: string): boolean {\n\tif (!isPackageManagerInstall(packageDir)) {\n\t\treturn false;\n\t}\n\tconst bundledPackages = [\"pi-agent-core\", \"pi-ai\", \"pi-tui\"];\n\treturn bundledPackages.some((name) => {\n\t\treturn !existsSync(join(packageDir, \"node_modules\", \"@earendil-works\", name, \"dist\", \"index.js\"));\n\t});\n}\n\n/**\n * Decide whether the agent needs its own process.\n *\n * Two things justify the extra Node process, and only two. An inherited Inspector option means a\n * debugger socket has to be released here and re-opened over there, which a same-process load\n * cannot do. Custom exec arguments (`--max-old-space-size`, a loader `--import`, ...) were chosen\n * for the process that runs the agent, and they are only applied at process start, so they must be\n * replayed onto a fresh one. Brand scrubbing does NOT justify it: `cli-main` calls\n * `scrubBrandFromEnvironment()` itself, so loading it here scrubs this process's environment before\n * anything the agent spawns can inherit it.\n */\nfunction requiresIsolatedProcess(): boolean {\n\treturn process.execArgv.length > 0 || hasInheritedInspectorOption();\n}\n\nasync function spawnFullCli(): Promise<number> {\n\tconst extension = import.meta.url.endsWith(\".ts\") ? \".ts\" : \".js\";\n\tconst fullCliPath = fileURLToPath(new URL(`./cli-main${extension}`, import.meta.url));\n\treleaseInheritedInspectorForChild();\n\treturn await new Promise<number>((resolve, reject) => {\n\t\tconst child = spawn(process.execPath, [...process.execArgv, fullCliPath, ...args], {\n\t\t\tenv: process.env,\n\t\t\tstdio: \"inherit\",\n\t\t});\n\t\tchild.on(\"error\", (error) => {\n\t\t\treject(error);\n\t\t});\n\t\tchild.on(\"close\", (code, signal) => {\n\t\t\tif (signal) {\n\t\t\t\tprocess.kill(process.pid, signal);\n\t\t\t\tresolve(1);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tresolve(code ?? 1);\n\t\t});\n\t});\n}\n\nif (isRootCommand(args) && (args.includes(\"--version\") || args.includes(\"-v\"))) {\n\tconsole.log(DISPLAY_VERSION);\n\tprocess.exit();\n}\n\nif (isMissingBundledWorkspaceDependencies(getPackageDir())) {\n\tif (await handleBootstrapSelfUpdate(args)) {\n\t\tprocess.exit();\n\t}\n}\n\nif (requiresIsolatedProcess()) {\n\tprocess.exitCode = await spawnFullCli();\n} else {\n\t// Entry-point process-structure seam: `cli-main` runs `main()` at module scope and owns\n\t// `process.exitCode` and any `process.exit()` of its own, so importing it here IS the run - there\n\t// is no result to forward. It has to be a dynamic import: a static one would evaluate the whole\n\t// engine graph before the `--version` and bootstrap-repair paths above, which answer without it.\n\tawait import(\"./cli-main.ts\");\n}\n"]}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\nimport \"./valid-cwd.ts\";\nimport { spawn, spawnSync } from \"node:child_process\";\nimport { existsSync, realpathSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { processBunRuntimeOptions, resolveBunReexec } from \"./bun-runtime.ts\";\nimport { enableStartupCompileCache } from \"./compile-cache.ts\";\nimport { APP_NAME, DISPLAY_VERSION, getPackageDir } from \"./config.ts\";\nimport { hasInheritedInspectorOption, releaseInheritedInspectorForChild } from \"./inspector-policy.ts\";\nimport { handleBootstrapSelfUpdate } from \"./self-update-bootstrap.ts\";\n\n// Upstream's `cli/setup.ts` helper is deliberately not used here: this launcher only decides the\n// runtime and process structure, and `cli-main.ts` performs the equivalent process/title/env/http\n// setup for both entry paths (Node launcher and the Bun binary).\n\n/**\n * Hand a Bun-installed CLI to Bun before anything else runs.\n *\n * `bun install -g` links this script into `~/.bun/bin`, but the shebang still starts it on Node,\n * so a user who chose Bun silently gets the Node runtime. Re-exec through the Bun binary when the\n * script really lives in Bun's global tree (or `SENPI_RUNTIME=bun` asks for it). This runs before\n * `enableStartupCompileCache()` on purpose: a re-exec must not pay for Node's compile-cache setup.\n * Node `execArgv` is deliberately dropped — those flags belong to the Node process, not to Bun.\n */\nfunction reexecUnderBunIfNeeded(): boolean {\n\tconst options = processBunRuntimeOptions(existsSync, realpathSync);\n\tlet scriptRealPath = process.argv[1] ?? fileURLToPath(import.meta.url);\n\ttry {\n\t\t// `~/.bun/bin/<name>` is a symlink into the global tree, so the link target is what has\n\t\t// to be classified and re-executed. A path that cannot be resolved is simply used as-is;\n\t\t// runtime selection must never be the reason startup fails.\n\t\tscriptRealPath = realpathSync(scriptRealPath);\n\t} catch {}\n\tconst decision = resolveBunReexec({\n\t\tscriptRealPath,\n\t\tversions: process.versions,\n\t\thasInheritedInspectorOption: hasInheritedInspectorOption(),\n\t\toptions,\n\t});\n\tif (decision.action === \"stay\") {\n\t\treturn false;\n\t}\n\tconst result = spawnSync(decision.bunPath, [scriptRealPath, ...process.argv.slice(2)], {\n\t\tstdio: \"inherit\",\n\t\twindowsHide: true,\n\t});\n\tif (result.signal) {\n\t\tprocess.kill(process.pid, result.signal);\n\t\treturn true;\n\t}\n\tprocess.exitCode = result.status ?? 1;\n\treturn true;\n}\n\nif (reexecUnderBunIfNeeded()) {\n\tprocess.exit();\n}\n\n// Must run before cli-main is loaded, by either path: it caches the engine graph this process\n// imports on the fast path below, and it publishes NODE_COMPILE_CACHE so a spawned cli-main child\n// inherits this process's cache directory instead of resolving and re-filling its own.\nenableStartupCompileCache();\n\nprocess.title = APP_NAME;\nprocess.env.PI_CODING_AGENT = \"true\";\nprocess.env.AI_AGENT = APP_NAME;\nprocess.emitWarning = (() => {}) as typeof process.emitWarning;\n\nconst args = process.argv.slice(2);\nconst PACKAGE_COMMANDS = new Set([\"install\", \"remove\", \"uninstall\", \"update\", \"list\", \"config\"]);\n\nfunction isRootCommand(args: readonly string[]): boolean {\n\tconst firstArg = args[0];\n\treturn firstArg === undefined || !PACKAGE_COMMANDS.has(firstArg);\n}\n\nfunction isPackageManagerInstall(packageDir: string): boolean {\n\treturn packageDir.replace(/\\\\/g, \"/\").includes(\"/node_modules/@code-yeongyu/senpi\");\n}\n\nfunction isMissingBundledWorkspaceDependencies(packageDir: string): boolean {\n\tif (!isPackageManagerInstall(packageDir)) {\n\t\treturn false;\n\t}\n\tconst bundledPackages = [\"pi-agent-core\", \"pi-ai\", \"pi-tui\"];\n\treturn bundledPackages.some((name) => {\n\t\treturn !existsSync(join(packageDir, \"node_modules\", \"@earendil-works\", name, \"dist\", \"index.js\"));\n\t});\n}\n\n/**\n * Decide whether the agent needs its own process.\n *\n * Two things justify the extra Node process, and only two. An inherited Inspector option means a\n * debugger socket has to be released here and re-opened over there, which a same-process load\n * cannot do. Custom exec arguments (`--max-old-space-size`, a loader `--import`, ...) were chosen\n * for the process that runs the agent, and they are only applied at process start, so they must be\n * replayed onto a fresh one. Brand scrubbing does NOT justify it: `cli-main` calls\n * `scrubBrandFromEnvironment()` itself, so loading it here scrubs this process's environment before\n * anything the agent spawns can inherit it.\n */\nfunction requiresIsolatedProcess(): boolean {\n\treturn process.execArgv.length > 0 || hasInheritedInspectorOption();\n}\n\nasync function spawnFullCli(): Promise<number> {\n\tconst extension = import.meta.url.endsWith(\".ts\") ? \".ts\" : \".js\";\n\tconst fullCliPath = fileURLToPath(new URL(`./cli-main${extension}`, import.meta.url));\n\treleaseInheritedInspectorForChild();\n\treturn await new Promise<number>((resolve, reject) => {\n\t\tconst child = spawn(process.execPath, [...process.execArgv, fullCliPath, ...args], {\n\t\t\tenv: process.env,\n\t\t\tstdio: \"inherit\",\n\t\t});\n\t\tchild.on(\"error\", (error) => {\n\t\t\treject(error);\n\t\t});\n\t\tchild.on(\"close\", (code, signal) => {\n\t\t\tif (signal) {\n\t\t\t\tprocess.kill(process.pid, signal);\n\t\t\t\tresolve(1);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tresolve(code ?? 1);\n\t\t});\n\t});\n}\n\nif (isRootCommand(args) && (args.includes(\"--version\") || args.includes(\"-v\"))) {\n\tconsole.log(DISPLAY_VERSION);\n\tprocess.exit();\n}\n\n// Help is static text plus the flags extensions registered, so a launch that already knows those\n// flags must not import the engine graph to print them. The import stays dynamic for the same\n// reason `cli-main` is: a static one would evaluate that graph before this answer.\nif (isRootCommand(args) && args.some((arg) => arg === \"--help\" || arg === \"-h\")) {\n\tconst { tryPrintHelpWithoutEngine } = await import(\"./cli/help-fast-path.ts\");\n\tif (tryPrintHelpWithoutEngine(args)) {\n\t\tprocess.exit();\n\t}\n}\n\nif (isMissingBundledWorkspaceDependencies(getPackageDir())) {\n\tif (await handleBootstrapSelfUpdate(args)) {\n\t\tprocess.exit();\n\t}\n}\n\nif (requiresIsolatedProcess()) {\n\tprocess.exitCode = await spawnFullCli();\n} else {\n\t// Entry-point process-structure seam: `cli-main` runs `main()` at module scope and owns\n\t// `process.exitCode` and any `process.exit()` of its own, so importing it here IS the run - there\n\t// is no result to forward. It has to be a dynamic import: a static one would evaluate the whole\n\t// engine graph before the `--version` and bootstrap-repair paths above, which answer without it.\n\tawait import(\"./cli-main.ts\");\n}\n"]}
@@ -232,17 +232,6 @@ export type AgentSessionEvent = Exclude<AgentEvent, {
232
232
  from: string;
233
233
  to: string;
234
234
  chainConfigured: boolean;
235
- }
236
- /**
237
- * A turn ended on the throughput watchdog's verdict without a fallback model
238
- * to take it over. `chainConfigured` mirrors `server_fallback_aborted`: the
239
- * no-chain case has no other signal to offer the UI.
240
- */
241
- | {
242
- type: "stream_throughput_degraded";
243
- model: string;
244
- errorMessage: string;
245
- chainConfigured: boolean;
246
235
  } | {
247
236
  type: "auth_login_url";
248
237
  provider: string;