shortcutxl 0.3.85 → 0.3.87

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 (126) hide show
  1. package/BINARY-INVENTORY.json +12 -12
  2. package/CHANGELOG.md +10 -3
  3. package/SECURITY.md +1 -1
  4. package/dist/app/agent-session-runtime-adapter.d.ts +6 -0
  5. package/dist/app/agent-session-runtime-adapter.js +20 -2
  6. package/dist/app/background-agents/durable-memory/coordinator.js +1 -12
  7. package/dist/app/background-agents/durable-memory/memory-tools.js +8 -43
  8. package/dist/app/background-agents/shorty/doctor/read-tools.d.ts +5 -0
  9. package/dist/app/background-agents/shorty/doctor/read-tools.js +6 -0
  10. package/dist/app/background-agents/shorty/doctor/records.d.ts +17 -0
  11. package/dist/app/background-agents/shorty/doctor/records.js +69 -0
  12. package/dist/app/background-agents/shorty/doctor/runtime.d.ts +12 -0
  13. package/dist/app/background-agents/shorty/doctor/runtime.js +162 -0
  14. package/dist/app/background-agents/shorty/execution.d.ts +16 -0
  15. package/dist/app/background-agents/shorty/execution.js +28 -0
  16. package/dist/app/background-agents/shorty/fix-prompts.d.ts +4 -0
  17. package/dist/app/background-agents/shorty/fix-prompts.js +15 -0
  18. package/dist/app/background-agents/shorty/inspector/runtime.d.ts +7 -0
  19. package/dist/app/background-agents/shorty/inspector/runtime.js +156 -0
  20. package/dist/app/background-agents/shorty/permission-policy.d.ts +22 -0
  21. package/dist/app/background-agents/shorty/permission-policy.js +22 -0
  22. package/dist/app/background-agents/shorty/runtime.d.ts +3 -0
  23. package/dist/app/background-agents/shorty/runtime.js +42 -0
  24. package/dist/app/background-agents/shorty/types.d.ts +26 -0
  25. package/dist/app/background-agents/shorty/types.js +2 -0
  26. package/dist/app/extensions/slash-commands.js +1 -0
  27. package/dist/app/local/tools/execute-code/read-only-mode.d.ts +3 -0
  28. package/dist/app/local/tools/execute-code/read-only-mode.js +12 -0
  29. package/dist/app/local/tools/execute-code/tool.js +10 -0
  30. package/dist/app/modes/installation/prompt-windows.js +4 -4
  31. package/dist/app/permissions/effective-view.d.ts +0 -1
  32. package/dist/app/permissions/effective-view.js +0 -12
  33. package/dist/app/providers/shortcut-api-v2-transport.js +1 -0
  34. package/dist/app/session/forked-agent.d.ts +3 -9
  35. package/dist/app/session/forked-agent.js +5 -6
  36. package/dist/app/tools/scoped-file-tools.d.ts +8 -0
  37. package/dist/app/tools/scoped-file-tools.js +43 -0
  38. package/dist/cli/platform-api-commands.d.ts +20 -0
  39. package/dist/cli/platform-api-commands.js +6 -5
  40. package/dist/cli.js +36867 -35564
  41. package/dist/config.js +0 -10
  42. package/dist/constants.d.ts +3 -2
  43. package/dist/constants.js +3 -4
  44. package/dist/contracts/agent-api.d.ts +8 -3
  45. package/dist/contracts/agent-api.js +14 -7
  46. package/dist/embedded-agent/compose.js +7 -5
  47. package/dist/embedded-agent/context-review/agent.d.ts +8 -0
  48. package/dist/embedded-agent/context-review/agent.js +17 -6
  49. package/dist/embedded-agent/host-runtime-options.js +12 -6
  50. package/dist/embedded-agent/host-tools/execute-code/contract.js +4 -3
  51. package/dist/embedded-agent/model-registry.d.ts +2 -0
  52. package/dist/embedded-agent/model-registry.js +2 -0
  53. package/dist/embedded-agent/provider-native-request.js +17 -4
  54. package/dist/embedded-agent/suggestions.d.ts +5 -2
  55. package/dist/embedded-agent/suggestions.js +46 -26
  56. package/dist/main-helpers.js +2 -2
  57. package/dist/main.js +55 -11
  58. package/dist/model-ids.d.ts +4 -0
  59. package/dist/model-ids.js +4 -0
  60. package/dist/rpc/index.d.ts +1 -1
  61. package/dist/rpc/rpc-host.d.ts +4 -0
  62. package/dist/rpc/rpc-host.js +35 -0
  63. package/dist/rpc/rpc-mode.d.ts +1 -1
  64. package/dist/rpc/rpc-types.d.ts +22 -0
  65. package/dist/setup.js +13 -7
  66. package/dist/shell/components/editors/custom-editor.d.ts +2 -0
  67. package/dist/shell/components/editors/custom-editor.js +7 -0
  68. package/dist/shell/components/shorty-issues.d.ts +37 -0
  69. package/dist/shell/components/shorty-issues.js +202 -0
  70. package/dist/shell/interactive/interactive-mode-options.d.ts +3 -0
  71. package/dist/shell/interactive/interactive-mode.d.ts +6 -0
  72. package/dist/shell/interactive/interactive-mode.js +144 -1
  73. package/dist/shell/sandbox/sandbox-command.js +5 -0
  74. package/dist/shortcut-model-catalog.js +53 -25
  75. package/dist/startup/output-workbook.d.ts +29 -0
  76. package/dist/startup/output-workbook.js +96 -0
  77. package/dist/startup/runtime-routing.js +6 -2
  78. package/dist/startup/sync-xll.js +4 -4
  79. package/dist/startup/terminal-update-flow.d.ts +0 -1
  80. package/dist/startup/terminal-update-flow.js +0 -3
  81. package/dist/startup/uninstall.d.ts +30 -1
  82. package/dist/startup/uninstall.js +91 -45
  83. package/dist/startup/update-execution.js +7 -19
  84. package/dist/startup/update-manager.d.ts +0 -4
  85. package/dist/startup/update-manager.js +6 -28
  86. package/dist/startup/windows-excel-runtime-upgrade.js +3 -3
  87. package/native-app/main/chunks/{linux-BbyBJPUK.js → linux-D5vrr6vD.js} +3 -5
  88. package/native-app/main/chunks/{mac-CEzb62LU.js → mac-C1gONwcL.js} +3 -5
  89. package/native-app/main/chunks/{setup-worker-protocol-Bk1EVQnX.js → setup-worker-protocol-P3bbcYzl.js} +0 -7
  90. package/native-app/main/chunks/{shared-Kic2CBJF.js → shared-DpP_I5SO.js} +24 -2
  91. package/native-app/main/index.js +165 -51
  92. package/native-app/main/setup-worker.js +17 -15
  93. package/native-app/package.json +1 -1
  94. package/native-app/preload/index.cjs +4 -0
  95. package/native-app/renderer/assets/{index-DttUIDtA.js → index-BRbc7n0z.js} +695 -9
  96. package/native-app/renderer/assets/{index-CjXWevtW.css → index-Ch24bXR6.css} +471 -1
  97. package/native-app/renderer/assets/mascot-CsVDHiJB.png +0 -0
  98. package/native-app/renderer/assets/shortcutagent-DxFsGSkw.png +0 -0
  99. package/native-app/renderer/index.html +2 -2
  100. package/package.json +2 -4
  101. package/xll/python/Lib/site-packages/httpx-0.28.1.dist-info/RECORD +1 -1
  102. package/xll/python/Lib/site-packages/idna-3.18.dist-info/RECORD +1 -1
  103. package/xll/python/Lib/site-packages/pip-26.2.dist-info/RECORD +3 -3
  104. package/xll/python/Lib/site-packages/pywin32-311.dist-info/RECORD +2 -2
  105. package/xll/python/Scripts/httpx.exe +0 -0
  106. package/xll/python/Scripts/idna.exe +0 -0
  107. package/xll/python/Scripts/pip.exe +0 -0
  108. package/xll/python/Scripts/pip3.13.exe +0 -0
  109. package/xll/python/Scripts/pip3.exe +0 -0
  110. package/xll/python/Scripts/pywin32_postinstall.exe +0 -0
  111. package/xll/python/Scripts/pywin32_testall.exe +0 -0
  112. package/dist/app/providers/shortcut-auth-recovery.d.ts +0 -10
  113. package/dist/app/providers/shortcut-auth-recovery.js +0 -14
  114. package/dist/sea/bootstrap.d.ts +0 -11
  115. package/dist/sea/bootstrap.js +0 -17
  116. package/dist/sea/runtime-assets.d.ts +0 -14
  117. package/dist/sea/runtime-assets.js +0 -110
  118. package/dist/sea/smoke.d.ts +0 -3
  119. package/dist/sea/smoke.js +0 -52
  120. package/dist/startup/sea-release.d.ts +0 -5
  121. package/dist/startup/sea-release.js +0 -17
  122. package/dist/startup/sea-update.d.ts +0 -19
  123. package/dist/startup/sea-update.js +0 -100
  124. package/native-app/main/chunks/constants-D1yo8u9C.js +0 -32
  125. package/native-app/renderer/assets/mascot-Y6byL4Yn.png +0 -3
  126. package/native-app/renderer/assets/shortcutagent-_zicTsV3.png +0 -3
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "generatedAt": "2026-08-20T19:59:33.921Z",
3
+ "generatedAt": "2026-08-24T23:36:20.445Z",
4
4
  "package": "shortcutxl",
5
5
  "build": {
6
6
  "builder": "github-actions",
7
7
  "repository": "lyfegame/shortcut",
8
- "sourceRef": "refs/heads/pwang724/fix-cli-app-approval-scrolling",
9
- "sourceCommit": "9cc676833842002c8742057b44e12b6635f5888f",
10
- "workflowRef": "lyfegame/shortcut/.github/workflows/release-shortcutxl.yml@refs/heads/pwang724/fix-cli-app-approval-scrolling",
11
- "runId": "32411468790",
8
+ "sourceRef": "refs/heads/shortcutxl-0.3.87-release",
9
+ "sourceCommit": "932b5d424becf9729dc82c1da5c0d7d4ff8be22c",
10
+ "workflowRef": "lyfegame/shortcut/.github/workflows/release-shortcut-cli-npm-package.yml@refs/heads/shortcutxl-0.3.87-release",
11
+ "runId": "32789945621",
12
12
  "runnerOs": "Windows",
13
13
  "runnerImage": "win25-vs2026",
14
14
  "runnerImageVersion": "20260818.207.1"
@@ -550,7 +550,7 @@
550
550
  },
551
551
  {
552
552
  "path": "xll/python/Scripts/httpx.exe",
553
- "sha256": "6f8a9f92f6a4069c9563897fac73999dd5b2ca8243f4777913ae4100fb51eae8",
553
+ "sha256": "0b542e5a9dc832d34f33d5c75e111e16f0d9d0100968e8405409018dbf329879",
554
554
  "source": "httpx console launcher installed into embedded Python",
555
555
  "version": "see packaged httpx distribution",
556
556
  "builtBy": "third-party",
@@ -558,7 +558,7 @@
558
558
  },
559
559
  {
560
560
  "path": "xll/python/Scripts/idna.exe",
561
- "sha256": "f2222ae2caf7c715f65ed4d7769e0eb047bfd8cdaa0e8bb1298ffd4f9c9f4914",
561
+ "sha256": "d985abffe9723dd6672037af85ec4cbaae6b4d9f452028dc794b4d6d61514e05",
562
562
  "source": "Python package console launcher installed into embedded Python",
563
563
  "version": "see owning Python package metadata in site-packages",
564
564
  "builtBy": "third-party",
@@ -566,7 +566,7 @@
566
566
  },
567
567
  {
568
568
  "path": "xll/python/Scripts/pip.exe",
569
- "sha256": "f60aaf7555f70c4076f52bf4bb58d6885273fe0dfd8dad13b5fbe6b46daa973b",
569
+ "sha256": "9c25f44908e9efe1afcad1e73d6de35927703f088e03fb35d764aff719bb6304",
570
570
  "source": "pip console launcher installed into embedded Python",
571
571
  "version": "see packaged pip distribution",
572
572
  "builtBy": "third-party",
@@ -574,7 +574,7 @@
574
574
  },
575
575
  {
576
576
  "path": "xll/python/Scripts/pip3.13.exe",
577
- "sha256": "f60aaf7555f70c4076f52bf4bb58d6885273fe0dfd8dad13b5fbe6b46daa973b",
577
+ "sha256": "9c25f44908e9efe1afcad1e73d6de35927703f088e03fb35d764aff719bb6304",
578
578
  "source": "pip console launcher installed into embedded Python",
579
579
  "version": "see packaged pip distribution",
580
580
  "builtBy": "third-party",
@@ -582,7 +582,7 @@
582
582
  },
583
583
  {
584
584
  "path": "xll/python/Scripts/pip3.exe",
585
- "sha256": "f60aaf7555f70c4076f52bf4bb58d6885273fe0dfd8dad13b5fbe6b46daa973b",
585
+ "sha256": "9c25f44908e9efe1afcad1e73d6de35927703f088e03fb35d764aff719bb6304",
586
586
  "source": "pip console launcher installed into embedded Python",
587
587
  "version": "see packaged pip distribution",
588
588
  "builtBy": "third-party",
@@ -590,7 +590,7 @@
590
590
  },
591
591
  {
592
592
  "path": "xll/python/Scripts/pywin32_postinstall.exe",
593
- "sha256": "537ca79bfb736241887ca9017db7583dd6d566255d24f9005b598949ae1059d1",
593
+ "sha256": "00e246620113fd62b23749e74fa31cfb0dd486327abbff4ebda1fb40ba634b28",
594
594
  "source": "Python package console launcher installed into embedded Python",
595
595
  "version": "see owning Python package metadata in site-packages",
596
596
  "builtBy": "third-party",
@@ -598,7 +598,7 @@
598
598
  },
599
599
  {
600
600
  "path": "xll/python/Scripts/pywin32_testall.exe",
601
- "sha256": "d0884c20bf24120ac7f100096f597452e5b01470bfdbc77c998fcc69ff58295a",
601
+ "sha256": "c6b2a8c33794368c0834b9dea8e97cc2c188a32c0d95c286c4cf258317af506a",
602
602
  "source": "Python package console launcher installed into embedded Python",
603
603
  "version": "see owning Python package metadata in site-packages",
604
604
  "builtBy": "third-party",
package/CHANGELOG.md CHANGED
@@ -1,13 +1,20 @@
1
1
  # Changelog
2
2
 
3
- ## [0.3.85]
3
+ ## [0.3.87]
4
4
 
5
+ - **Shorty reviewers** - Inspector Shorty checks recent workbook changes for issues, while Doctor Shorty reviews your saved memory and personal skills.
6
+ - **More reliable native app startup** - Fixed app bugs and resolved some app stability issues.
7
+
8
+ ## [0.3.86]
9
+
10
+ - **Signed Windows installer and updates** - Shortcut now ships as a signed Windows app installer, with versioned installer and updater assets published on [GitHub Releases](https://github.com/lyfegame/shortcutxl-releases/releases).
11
+ - **Clearer installation flow** - Windows setup guidance now describes Shortcut's bundled runtime, includes Office-wide recovery steps, and keeps Excel, Word, and PowerPoint integrations current during first run and upgrades.
5
12
  - **Scrollable approval dialogs** - Long approval requests in the native app now remain fully accessible.
6
13
 
7
14
  ## [0.3.84]
8
15
 
9
- - **UI improvements**
10
- - **Shared Office approvals** - Excel, Word, and PowerPoint now use one clearly labeled approval setting across the CLI and native app.
16
+ - **Work across Excel, Word, and PowerPoint** - On Windows, Shortcut can now discover and work with open Word documents and PowerPoint presentations alongside Excel.
17
+ - **Simpler skill setup and sync** - The native app now finds skills from Claude Code and Codex automatically, and adds a dedicated place to review and sync differences with Shortcut Cloud.
11
18
 
12
19
  ## [0.3.83]
13
20
 
package/SECURITY.md CHANGED
@@ -11,7 +11,7 @@ The release workflow retains the exact npm tarball, its SHA-256 checksum, and it
11
11
  ```bash
12
12
  cosign verify-blob \
13
13
  --bundle shortcutxl-<version>.tgz.sigstore.json \
14
- --certificate-identity "https://github.com/lyfegame/shortcut/.github/workflows/release-shortcutxl.yml@<source-ref>" \
14
+ --certificate-identity "https://github.com/lyfegame/shortcut/.github/workflows/release-shortcut-cli-npm-package.yml@<source-ref>" \
15
15
  --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
16
16
  shortcutxl-<version>.tgz
17
17
  ```
@@ -16,6 +16,7 @@ import type { ToolRegistry } from '../contracts/host-tool-registry.js';
16
16
  import { type ThinkingLevel, type ToolDefinition } from '../core/index.js';
17
17
  import type { RuntimePermissionPolicy } from '../core/permission-policy.js';
18
18
  import { AgentSession, type FastModeDisabledReason } from './agent-session.js';
19
+ import type { LocalShortyRuntime } from './background-agents/shorty/types.js';
19
20
  import type { ResourceLoader } from './resources/resource-loader.js';
20
21
  import type { AgentSessionSettingsStore } from './session/session-settings-store.js';
21
22
  import type { ProductSessionStore } from './session/session-store-types.js';
@@ -38,6 +39,10 @@ export interface CreateAgentSessionRuntimeAdapterOptions {
38
39
  approvalState?: AgentApprovalState;
39
40
  budgetLimits?: RuntimeBudgetLimits;
40
41
  enableToolSummaries?: boolean;
42
+ /** Enable local Doctor/Inspector background reviews for the interactive CLI. */
43
+ enableShortyReviews?: boolean;
44
+ /** Whether the current workbook backend supports unattended read-only inspection. */
45
+ isInspectorAvailable?: () => boolean;
41
46
  fastModeAllowed?: boolean;
42
47
  fastModeDisabledReason?: FastModeDisabledReason;
43
48
  modeName?: string;
@@ -58,6 +63,7 @@ export interface CreateAgentSessionRuntimeAdapterOptions {
58
63
  }
59
64
  export interface CreateAgentSessionRuntimeAdapterRuntimeResult {
60
65
  runtime: EmbeddedAgentRuntime;
66
+ shortyRuntime: LocalShortyRuntime;
61
67
  }
62
68
  export interface CreateAgentSessionRuntimeAdapterResult extends CreateAgentSessionRuntimeAdapterRuntimeResult {
63
69
  extensionsResult: ReturnType<ResourceLoader['getExtensions']>;
@@ -17,6 +17,7 @@ import { createPendingHostToolRequests } from '../core/pending-host-tool-request
17
17
  import { getHostToolTimeoutMs } from '../embedded-agent/host-tools/timeouts.js';
18
18
  import { AgentSession, LoginRequiredError } from './agent-session.js';
19
19
  import { SessionAutoApproveState } from './approvals/session-auto-approve.js';
20
+ import { createLocalShortyRuntime } from './background-agents/shorty/runtime.js';
20
21
  import { convertToLlm } from './messages.js';
21
22
  import { findInitialModel } from './providers/model-resolver.js';
22
23
  export async function resolveInitialSessionModel(options) {
@@ -50,7 +51,7 @@ export async function resolveInitialSessionModel(options) {
50
51
  }
51
52
  return { model: resolved.model, modelFallbackMessage };
52
53
  }
53
- function createAgentSessionCore(session, pendingHostToolRequests) {
54
+ function createAgentSessionCore(session, pendingHostToolRequests, shortyRuntime) {
54
55
  // AgentSession itself doesn't carry a "the last turn was cancelled" flag —
55
56
  // cancellation just leaves the message stream truncated. Track it locally
56
57
  // so the next snapshot reflects AgentStopReason.Cancelled. Cleared on
@@ -67,6 +68,12 @@ function createAgentSessionCore(session, pendingHostToolRequests) {
67
68
  session.replaceToolsByName(configuration.toolNames);
68
69
  session.agent.setSystemPrompt(configuration.systemPrompt);
69
70
  },
71
+ async configureReviewBuddy(enabled) {
72
+ shortyRuntime.configureInspector(enabled);
73
+ },
74
+ async runContextReview(input) {
75
+ await shortyRuntime.runDoctor(input.trigger);
76
+ },
70
77
  async controlTask(request) {
71
78
  return {
72
79
  ok: false,
@@ -79,6 +86,7 @@ function createAgentSessionCore(session, pendingHostToolRequests) {
79
86
  session.clearGoal();
80
87
  },
81
88
  dispose() {
89
+ shortyRuntime.dispose();
82
90
  session.dispose();
83
91
  },
84
92
  async getGoal() {
@@ -112,6 +120,9 @@ function createAgentSessionCore(session, pendingHostToolRequests) {
112
120
  },
113
121
  subscribeSnapshotSource(listener) {
114
122
  return session.subscribe(listener);
123
+ },
124
+ subscribeExtraEvents(listener) {
125
+ return shortyRuntime.subscribe(listener);
115
126
  }
116
127
  }, pendingHostToolRequests);
117
128
  }
@@ -254,9 +265,16 @@ export async function createAgentSessionRuntimeAdapter(options) {
254
265
  observability: options.observability,
255
266
  compactionComplete: completeSimple
256
267
  });
268
+ const shortyRuntime = createLocalShortyRuntime({
269
+ enabled: options.enableShortyReviews,
270
+ isInspectorAvailable: options.isInspectorAvailable,
271
+ modelRegistry,
272
+ session
273
+ });
257
274
  return {
258
275
  session,
259
- runtime: createAgentSessionCore(session, pendingHostToolRequests),
276
+ runtime: createAgentSessionCore(session, pendingHostToolRequests, shortyRuntime),
277
+ shortyRuntime,
260
278
  extensionsResult: resourceLoader.getExtensions(),
261
279
  modelFallbackMessage
262
280
  };
@@ -1,4 +1,3 @@
1
- import { SHORTCUT_LLM_PROXY_URL } from '../../../constants.js';
2
1
  import { getActiveUserMessageId } from '../../../core/user-message-id.js';
3
2
  import { DURABLE_MEMORY_AGENT_EFFORT, DURABLE_MEMORY_AGENT_MODEL, DURABLE_MEMORY_AGENT_TOOLS, DURABLE_MEMORY_TURN_END_CADENCE } from '../../extensions/durable-memory/constants.js';
4
3
  import { buildDurableMemoryPromptFragment } from '../../extensions/durable-memory/retrieval.js';
@@ -6,7 +5,6 @@ import { emitDurableMemoryStatus } from '../../extensions/durable-memory/status.
6
5
  import { validateWorkspaceState } from '../../extensions/durable-memory/validator.js';
7
6
  import { DurableMemoryWorkspace } from '../../extensions/durable-memory/workspace.js';
8
7
  import { deriveModePermissionPolicy } from '../../permissions/policy.js';
9
- import { createShortcutAuthRecovery } from '../../providers/shortcut-auth-recovery.js';
10
8
  import { resolveForkTraceDir } from '../../session/fork-trace-paths.js';
11
9
  import { runForkedAgent } from '../../session/forked-agent.js';
12
10
  import { durableMemoryAgent } from './memory-agent.js';
@@ -244,11 +242,6 @@ export class DurableMemoryCoordinator {
244
242
  const currentModel = ctx.model;
245
243
  if (!currentModel)
246
244
  return null;
247
- // `ExtensionContext.modelRegistry` is typed as the neutral
248
- // `AgentModelRegistry` interface, but every production path hands
249
- // in the concrete `ModelRegistry` (which exposes `authStorage`).
250
- // Cast here without a null check — tests mock a minimal registry
251
- // and `createShortcutAuthRecovery(undefined)` is well-defined.
252
245
  const messages = ctx.getMessages();
253
246
  const userMessageId = ctx.userMessageId ?? getActiveUserMessageId(messages);
254
247
  if (!userMessageId)
@@ -300,7 +293,6 @@ export class DurableMemoryCoordinator {
300
293
  if (signal.aborted)
301
294
  return;
302
295
  const memoryModel = run.modelRegistry.find?.(run.model.provider, DURABLE_MEMORY_AGENT_MODEL) ?? run.model;
303
- const baseUrl = memoryModel.baseUrl ?? run.model.baseUrl ?? SHORTCUT_LLM_PROXY_URL;
304
296
  const apiKey = await run.modelRegistry.getApiKey(memoryModel);
305
297
  if (!apiKey)
306
298
  return;
@@ -329,12 +321,9 @@ export class DurableMemoryCoordinator {
329
321
  messages: run.messages,
330
322
  tools,
331
323
  model: memoryModel,
332
- baseUrl,
333
- apiKey,
324
+ modelRegistry: run.modelRegistry,
334
325
  continuationRunId: run.continuationRunId,
335
- getExtraHeaders: () => ({ 'X-Session-Id': run.sessionId }),
336
326
  userMessageId: run.userMessageId,
337
- recoverAuth: createShortcutAuthRecovery(run.modelRegistry.authStorage),
338
327
  getToolContext: () => ({ permissionPolicy })
339
328
  }, {
340
329
  kind: DURABLE_MEMORY_FORK_KIND,
@@ -1,54 +1,19 @@
1
- import { isAbsolute, relative, resolve, sep } from 'node:path';
2
1
  import { createEditTool } from '../../tools/edit.js';
3
- import { createGrepTool } from '../../tools/grep.js';
4
- import { createReadTool } from '../../tools/read.js';
2
+ import { assertPathWithinAnyRoot, createScopedReadTools, wrapScopedTool } from '../../tools/scoped-file-tools.js';
5
3
  import { createWriteTool } from '../../tools/write.js';
6
- /** True if `targetPath` resolves to `rootDir` or one of its descendants. */
7
- function isWithinRoot(rootDir, targetPath) {
8
- const relativePath = relative(resolve(rootDir), resolve(targetPath));
9
- return (relativePath === '' ||
10
- (relativePath !== '..' && !relativePath.startsWith(`..${sep}`) && !isAbsolute(relativePath)));
11
- }
12
4
  /** Throw if `inputPath` is outside the durable-memory write root. */
13
- function assertPathWithinRoot(rootDir, inputPath) {
14
- const absolutePath = resolve(rootDir, inputPath);
15
- if (!isWithinRoot(rootDir, absolutePath)) {
16
- throw new Error(`Path is outside durable-memory root: ${inputPath}`);
17
- }
18
- }
19
- /** Throw if `inputPath` is outside every allowed durable-memory read root. */
20
- function assertPathWithinAnyRoot(roots, inputPath) {
21
- const absolutePath = resolve(roots[0], inputPath);
22
- if (!roots.some((root) => isWithinRoot(root, absolutePath))) {
23
- throw new Error(`Path is outside durable-memory read roots: ${inputPath}`);
24
- }
25
- }
26
- /** Attach durable-memory path-policy validation to an underlying file tool. */
27
- function wrapScopedTool(tool, validate) {
28
- return {
29
- ...tool,
30
- execute: async (toolCallId, rawParams, signal, onUpdate, ctx) => {
31
- validate(rawParams);
32
- return tool.execute(toolCallId, rawParams, signal, onUpdate, ctx);
33
- }
34
- };
5
+ async function assertPathWithinRoot(rootDir, inputPath) {
6
+ await assertPathWithinAnyRoot(rootDir, [rootDir], inputPath, 'durable-memory root', 'write');
35
7
  }
36
8
  /** Build the path-scoped read/edit/write/grep tool set for the durable-memory agent. */
37
9
  export function createDurableMemoryTools(rootDir, options = {}) {
38
10
  const readRoots = [rootDir, ...(options.readRoots ?? [])];
39
- const readTool = wrapScopedTool(createReadTool(rootDir), (params) => {
40
- assertPathWithinAnyRoot(readRoots, params.path);
41
- });
42
- const editTool = wrapScopedTool(createEditTool(rootDir), (params) => {
43
- assertPathWithinRoot(rootDir, params.path);
44
- });
45
- const writeTool = wrapScopedTool(createWriteTool(rootDir), (params) => {
46
- assertPathWithinRoot(rootDir, params.path);
11
+ const [readTool, grepTool] = createScopedReadTools(rootDir, readRoots, 'durable-memory read roots');
12
+ const editTool = wrapScopedTool(createEditTool(rootDir), async (params) => {
13
+ await assertPathWithinRoot(rootDir, params.path);
47
14
  });
48
- const grepTool = wrapScopedTool(createGrepTool(rootDir), (params) => {
49
- if (params.path) {
50
- assertPathWithinAnyRoot(readRoots, params.path);
51
- }
15
+ const writeTool = wrapScopedTool(createWriteTool(rootDir), async (params) => {
16
+ await assertPathWithinRoot(rootDir, params.path);
52
17
  });
53
18
  return [readTool, editTool, writeTool, grepTool];
54
19
  }
@@ -0,0 +1,5 @@
1
+ import type { ToolDefinition } from '../../../../core/core-types.js';
2
+ import type { LocalDoctorPaths } from './records.js';
3
+ /** Build Doctor's read-only tools with access limited to memories and personal skills. */
4
+ export declare function createDoctorReadTools(paths: LocalDoctorPaths): ToolDefinition[];
5
+ //# sourceMappingURL=read-tools.d.ts.map
@@ -0,0 +1,6 @@
1
+ import { createScopedReadTools } from '../../../tools/scoped-file-tools.js';
2
+ /** Build Doctor's read-only tools with access limited to memories and personal skills. */
3
+ export function createDoctorReadTools(paths) {
4
+ return createScopedReadTools(paths.agentDir, [paths.memoriesDir, paths.personalSkills], 'Doctor read roots');
5
+ }
6
+ //# sourceMappingURL=read-tools.js.map
@@ -0,0 +1,17 @@
1
+ import { type AgentContextReviewRecommendation, type AgentContextReviewUpdatedEvent } from '../../../../contracts/agent-api.js';
2
+ import type { LocalContextReviewDecision } from '../types.js';
3
+ export interface LocalDoctorPaths {
4
+ readonly agentDir: string;
5
+ readonly memoriesDir: string;
6
+ readonly memoryIndex: string;
7
+ readonly personalSkills: string;
8
+ readonly archivedSkills: string;
9
+ readonly reviewRecords: string;
10
+ }
11
+ export declare function resolveLocalDoctorPaths(agentDir?: string): LocalDoctorPaths;
12
+ export declare function createDoctorRecordStore(paths: LocalDoctorPaths, now: () => number): {
13
+ isAutomaticReviewDue(date: Date): boolean;
14
+ markAutomaticAttempt(date: Date): void;
15
+ recordDecision(event: AgentContextReviewUpdatedEvent, action: LocalContextReviewDecision, recommendations?: readonly AgentContextReviewRecommendation[]): void;
16
+ };
17
+ //# sourceMappingURL=records.d.ts.map
@@ -0,0 +1,69 @@
1
+ import { mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { getAgentDir, getMemoriesDir } from '../../../../config.js';
4
+ import { AgentContextReviewPhase } from '../../../../contracts/agent-api.js';
5
+ const DOCTOR_ATTEMPT_FILE = 'context-review-attempt.json';
6
+ function todayKey(now) {
7
+ const year = now.getFullYear();
8
+ const month = String(now.getMonth() + 1).padStart(2, '0');
9
+ const day = String(now.getDate()).padStart(2, '0');
10
+ return `${year}-${month}-${day}`;
11
+ }
12
+ export function resolveLocalDoctorPaths(agentDir) {
13
+ const resolvedAgentDir = agentDir ?? getAgentDir();
14
+ const memoriesDir = agentDir ? join(agentDir, 'memories') : getMemoriesDir();
15
+ const personalSkills = join(resolvedAgentDir, 'skills');
16
+ return {
17
+ agentDir: resolvedAgentDir,
18
+ memoriesDir,
19
+ memoryIndex: join(memoriesDir, 'MEMORY.md'),
20
+ personalSkills,
21
+ archivedSkills: join(personalSkills, '_archive'),
22
+ reviewRecords: join(memoriesDir, 'context-reviews')
23
+ };
24
+ }
25
+ export function createDoctorRecordStore(paths, now) {
26
+ const attemptPath = join(paths.agentDir, DOCTOR_ATTEMPT_FILE);
27
+ return {
28
+ isAutomaticReviewDue(date) {
29
+ try {
30
+ const stored = JSON.parse(readFileSync(attemptPath, 'utf8'));
31
+ return stored.date !== todayKey(date);
32
+ }
33
+ catch {
34
+ return true;
35
+ }
36
+ },
37
+ markAutomaticAttempt(date) {
38
+ mkdirSync(paths.agentDir, { recursive: true });
39
+ const tempPath = `${attemptPath}.tmp`;
40
+ writeFileSync(tempPath, `${JSON.stringify({ date: todayKey(date) })}\n`, 'utf8');
41
+ renameSync(tempPath, attemptPath);
42
+ },
43
+ recordDecision(event, action, recommendations = event.recommendations ?? []) {
44
+ if (event.phase !== AgentContextReviewPhase.Completed ||
45
+ !event.createdAt ||
46
+ !event.recordPath ||
47
+ !event.outcome) {
48
+ throw new Error('Only completed Doctor Shorty reviews can be recorded.');
49
+ }
50
+ const fileName = event.recordPath.split('/').at(-1);
51
+ if (!fileName || !fileName.endsWith('.json'))
52
+ throw new Error('Invalid review record path.');
53
+ mkdirSync(paths.reviewRecords, { recursive: true });
54
+ const record = {
55
+ id: event.reviewId,
56
+ created_at: event.createdAt,
57
+ trigger: event.trigger,
58
+ outcome: event.outcome,
59
+ recommendations,
60
+ decision: { action, recorded_at: new Date(now()).toISOString() }
61
+ };
62
+ const target = join(paths.reviewRecords, fileName);
63
+ const tempPath = `${target}.tmp`;
64
+ writeFileSync(tempPath, `${JSON.stringify(record, null, 2)}\n`, 'utf8');
65
+ renameSync(tempPath, target);
66
+ }
67
+ };
68
+ }
69
+ //# sourceMappingURL=records.js.map
@@ -0,0 +1,12 @@
1
+ import { type AgentContextReviewTrigger } from '../../../../contracts/agent-api.js';
2
+ import type { CreateLocalShortyRuntimeOptions, LocalShortyEvent } from '../types.js';
3
+ import { createDoctorRecordStore } from './records.js';
4
+ export interface LocalDoctorRuntime {
5
+ configure(enabled: boolean): void;
6
+ run(trigger: AgentContextReviewTrigger): Promise<void>;
7
+ runAutomaticIfDue(now?: Date): Promise<boolean>;
8
+ recordDecision: ReturnType<typeof createDoctorRecordStore>['recordDecision'];
9
+ dispose(): void;
10
+ }
11
+ export declare function createLocalDoctorRuntime(options: CreateLocalShortyRuntimeOptions, emit: (event: LocalShortyEvent) => void): LocalDoctorRuntime;
12
+ //# sourceMappingURL=runtime.d.ts.map
@@ -0,0 +1,162 @@
1
+ import { AgentContextReviewPhase, AgentEventKind } from '../../../../contracts/agent-api.js';
2
+ import { buildContextReviewQuery, buildContextReviewRecordPath, buildContextReviewSystemPrompt, contextReviewAgent } from '../../../../embedded-agent/context-review/agent.js';
3
+ import { parseContextReviewResult } from '../../../../embedded-agent/context-review/result.js';
4
+ import { readAgentReviewActivityDescription, upsertAgentReviewActivity } from '../../../../embedded-agent/review/activity.js';
5
+ import { GREP, READ } from '../../../../tool-names.js';
6
+ import { resolveAutomaticLocalShortyModel, runLocalShortyAgent } from '../execution.js';
7
+ import { buildShortyPermissionPolicy } from '../permission-policy.js';
8
+ import { createDoctorReadTools } from './read-tools.js';
9
+ import { createDoctorRecordStore, resolveLocalDoctorPaths } from './records.js';
10
+ const REVIEW_TIMEOUT_MS = contextReviewAgent.timeoutSeconds * 1_000;
11
+ export function createLocalDoctorRuntime(options, emit) {
12
+ const now = options.now ?? Date.now;
13
+ const paths = resolveLocalDoctorPaths(options.agentDir);
14
+ const records = createDoctorRecordStore(paths, now);
15
+ let enabled = options.enabled ?? false;
16
+ let disposed = false;
17
+ let active = null;
18
+ const run = async (trigger, model) => {
19
+ if (disposed || active || (!enabled && trigger === 'automatic'))
20
+ return;
21
+ const reviewId = `context-review-${globalThis.crypto.randomUUID()}`;
22
+ const createdAt = new Date(now()).toISOString();
23
+ const controller = new AbortController();
24
+ active = { reviewId, controller };
25
+ const startedAt = now();
26
+ let activities = [];
27
+ const emitRunningState = () => {
28
+ emit({
29
+ kind: AgentEventKind.ContextReviewUpdated,
30
+ reviewId,
31
+ trigger,
32
+ phase: AgentContextReviewPhase.Started,
33
+ activities
34
+ });
35
+ };
36
+ emitRunningState();
37
+ let timedOut = false;
38
+ const timeout = setTimeout(() => {
39
+ timedOut = true;
40
+ controller.abort();
41
+ }, REVIEW_TIMEOUT_MS);
42
+ try {
43
+ const tools = createDoctorReadTools(paths);
44
+ const permissionPolicy = buildShortyPermissionPolicy([READ, GREP], [paths.memoriesDir, paths.personalSkills]);
45
+ const result = await runLocalShortyAgent({
46
+ model,
47
+ modelRegistry: options.modelRegistry,
48
+ parent: {
49
+ systemPrompt: buildContextReviewSystemPrompt({
50
+ memoryIndex: paths.memoryIndex,
51
+ personalSkills: paths.personalSkills,
52
+ archivedSkills: paths.archivedSkills,
53
+ reviewRecords: paths.reviewRecords
54
+ }, 'read-grep'),
55
+ messages: [],
56
+ tools,
57
+ continuationRunId: reviewId,
58
+ getToolContext: () => ({
59
+ continuationRunId: reviewId,
60
+ permissionPolicy
61
+ })
62
+ },
63
+ controller: {
64
+ kind: 'doctor-shorty',
65
+ instruction: buildContextReviewQuery({ trigger }),
66
+ effortLevel: 'low',
67
+ signal: controller.signal,
68
+ onToolCall: (toolCall) => {
69
+ const description = readAgentReviewActivityDescription(toolCall.arguments ?? {}) ??
70
+ (toolCall.name === GREP
71
+ ? 'Searching memory and skills'
72
+ : toolCall.name === READ
73
+ ? 'Reading memory and skills'
74
+ : null);
75
+ if (!description || active?.reviewId !== reviewId)
76
+ return;
77
+ activities = upsertAgentReviewActivity(activities, {
78
+ id: toolCall.id,
79
+ description,
80
+ status: 'running'
81
+ });
82
+ emitRunningState();
83
+ }
84
+ }
85
+ });
86
+ if (active?.reviewId !== reviewId)
87
+ return;
88
+ const parsed = parseContextReviewResult(result.response);
89
+ const recordPath = buildContextReviewRecordPath(new Date(createdAt));
90
+ emit({
91
+ kind: AgentEventKind.ContextReviewUpdated,
92
+ reviewId,
93
+ trigger,
94
+ phase: AgentContextReviewPhase.Completed,
95
+ createdAt,
96
+ recordPath,
97
+ outcome: parsed.outcome,
98
+ recommendations: parsed.recommendations.map((recommendation) => ({
99
+ ...recommendation,
100
+ id: `${reviewId}:${recommendation.id}`
101
+ })),
102
+ durationMs: Math.max(0, now() - startedAt)
103
+ });
104
+ }
105
+ catch (error) {
106
+ if (active?.reviewId !== reviewId)
107
+ return;
108
+ emit({
109
+ kind: AgentEventKind.ContextReviewUpdated,
110
+ reviewId,
111
+ trigger,
112
+ phase: timedOut
113
+ ? AgentContextReviewPhase.Failed
114
+ : controller.signal.aborted
115
+ ? AgentContextReviewPhase.Cancelled
116
+ : AgentContextReviewPhase.Failed,
117
+ message: timedOut
118
+ ? 'Context check timed out.'
119
+ : error instanceof Error
120
+ ? error.message
121
+ : String(error),
122
+ durationMs: Math.max(0, now() - startedAt)
123
+ });
124
+ }
125
+ finally {
126
+ clearTimeout(timeout);
127
+ if (active?.reviewId === reviewId)
128
+ active = null;
129
+ }
130
+ };
131
+ return {
132
+ configure(nextEnabled) {
133
+ enabled = nextEnabled;
134
+ if (!enabled && active) {
135
+ active.controller.abort();
136
+ active = null;
137
+ }
138
+ },
139
+ run,
140
+ async runAutomaticIfDue(date = new Date()) {
141
+ if (disposed || !enabled || active)
142
+ return false;
143
+ if (!records.isAutomaticReviewDue(date))
144
+ return false;
145
+ const model = resolveAutomaticLocalShortyModel(options.modelRegistry);
146
+ if (!model)
147
+ return false;
148
+ records.markAutomaticAttempt(date);
149
+ await run('automatic', model);
150
+ return true;
151
+ },
152
+ recordDecision: records.recordDecision,
153
+ dispose() {
154
+ if (disposed)
155
+ return;
156
+ disposed = true;
157
+ active?.controller.abort();
158
+ active = null;
159
+ }
160
+ };
161
+ }
162
+ //# sourceMappingURL=runtime.js.map
@@ -0,0 +1,16 @@
1
+ import type { Model } from '../../../ai/types.js';
2
+ import type { AgentModelRegistry } from '../../../contracts/agent-model.js';
3
+ import { type ForkedAgentControllerOptions, type ForkedAgentParentContext, type ForkedAgentResult } from '../../session/forked-agent.js';
4
+ export interface RunLocalShortyAgentOptions {
5
+ readonly controller: ForkedAgentControllerOptions;
6
+ readonly model?: Model<any>;
7
+ readonly modelRegistry: AgentModelRegistry;
8
+ readonly parent: Omit<ForkedAgentParentContext, 'model' | 'modelRegistry'>;
9
+ }
10
+ /** Resolve the model used by every local Doctor and Inspector run. */
11
+ export declare function resolveLocalShortyModel(modelRegistry: AgentModelRegistry): Model<any> | undefined;
12
+ /** Return Sol only when an automatic review can authenticate without prompting. */
13
+ export declare function resolveAutomaticLocalShortyModel(modelRegistry: AgentModelRegistry): Model<any> | undefined;
14
+ /** Run a local reviewer without leaking credential plumbing into the reviewer. */
15
+ export declare function runLocalShortyAgent(options: RunLocalShortyAgentOptions): Promise<ForkedAgentResult>;
16
+ //# sourceMappingURL=execution.d.ts.map
@@ -0,0 +1,28 @@
1
+ import { SHORTCUT_MODEL_ID } from '../../../model-ids.js';
2
+ import { runForkedAgent } from '../../session/forked-agent.js';
3
+ /** Resolve the model used by every local Doctor and Inspector run. */
4
+ export function resolveLocalShortyModel(modelRegistry) {
5
+ return modelRegistry.find('shortcut', SHORTCUT_MODEL_ID.Gpt56Sol);
6
+ }
7
+ /** Return Sol only when an automatic review can authenticate without prompting. */
8
+ export function resolveAutomaticLocalShortyModel(modelRegistry) {
9
+ const model = resolveLocalShortyModel(modelRegistry);
10
+ if (!model)
11
+ return undefined;
12
+ const hasCredentials = modelRegistry
13
+ .getAvailable()
14
+ .some((available) => available.provider === model.provider && available.id === model.id);
15
+ return hasCredentials ? model : undefined;
16
+ }
17
+ /** Run a local reviewer without leaking credential plumbing into the reviewer. */
18
+ export async function runLocalShortyAgent(options) {
19
+ const model = options.model ?? resolveLocalShortyModel(options.modelRegistry);
20
+ if (!model)
21
+ throw new Error('The Shortcut Sol review model is not registered.');
22
+ return runForkedAgent({
23
+ ...options.parent,
24
+ model,
25
+ modelRegistry: options.modelRegistry
26
+ }, options.controller);
27
+ }
28
+ //# sourceMappingURL=execution.js.map
@@ -0,0 +1,4 @@
1
+ import type { AgentContextReviewRecommendation, AgentReviewBuddyFinding } from '../../../contracts/agent-api.js';
2
+ export declare function formatInspectorFixRequest(findings: readonly AgentReviewBuddyFinding[]): string;
3
+ export declare function formatDoctorFixRequest(recommendations: readonly AgentContextReviewRecommendation[]): string;
4
+ //# sourceMappingURL=fix-prompts.d.ts.map