shortcutxl 0.3.89 → 0.3.91

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 (97) hide show
  1. package/BINARY-INVENTORY.json +13 -13
  2. package/CHANGELOG.md +7 -1
  3. package/dist/ai/types.d.ts +2 -0
  4. package/dist/app/connectors/secret-store.d.ts +8 -0
  5. package/dist/app/connectors/secret-store.js +73 -1
  6. package/dist/app/local/tools/execute-code/approval-payload.d.ts +5 -1
  7. package/dist/app/local/tools/execute-code/executor.d.ts +4 -15
  8. package/dist/app/local/tools/execute-code/executor.js +56 -146
  9. package/dist/app/local/tools/execute-code/observability.js +0 -20
  10. package/dist/app/local/tools/execute-code/tool.js +0 -17
  11. package/dist/app/local/tools/execute-code/write-analyzer.js +2 -0
  12. package/dist/app/local/tools/take-screenshot/com-screenshot.js +8 -1
  13. package/dist/app/local/tools/take-screenshot/mog-screenshot.d.ts +0 -1
  14. package/dist/app/local/tools/take-screenshot/mog-screenshot.js +10 -17
  15. package/dist/app/mcp/auth-status.d.ts +2 -0
  16. package/dist/app/mcp/auth-status.js +5 -1
  17. package/dist/app/mcp/connection.d.ts +32 -2
  18. package/dist/app/mcp/connection.js +179 -83
  19. package/dist/app/mcp/index.js +44 -9
  20. package/dist/app/mcp/types.d.ts +3 -1
  21. package/dist/app/modes/ask/prompt.js +1 -1
  22. package/dist/app/modes/manage/prompt.js +1 -1
  23. package/dist/app/providers/model-registry.d.ts +1 -0
  24. package/dist/app/providers/model-registry.js +1 -0
  25. package/dist/app/providers/model-resolver.js +1 -0
  26. package/dist/app/session/session-models.js +2 -1
  27. package/dist/app/tools/take-screenshot.d.ts +3 -0
  28. package/dist/app/tools/take-screenshot.js +5 -2
  29. package/dist/cli.js +965 -509
  30. package/dist/contracts/agent-api.d.ts +4 -4
  31. package/dist/contracts/agent-api.js +4 -4
  32. package/dist/contracts/agent-snapshot.d.ts +14 -0
  33. package/dist/contracts/agent-snapshot.js +6 -0
  34. package/dist/core/agent-snapshot-builder.js +6 -2
  35. package/dist/core/prompts/agent-guidelines.d.ts +1 -1
  36. package/dist/core/prompts/agent-guidelines.js +1 -1
  37. package/dist/core/prompts/take-screenshot.d.ts +1 -1
  38. package/dist/core/prompts/take-screenshot.js +1 -0
  39. package/dist/core/session/compaction/compaction.d.ts +1 -1
  40. package/dist/core/session/compaction/compaction.js +2 -2
  41. package/dist/core/session/compaction/constants.d.ts +4 -3
  42. package/dist/core/session/compaction/constants.js +5 -4
  43. package/dist/core/session/session-compaction.js +6 -4
  44. package/dist/embedded-agent/agent-event-funnel.d.ts +0 -2
  45. package/dist/embedded-agent/agent-event-funnel.js +0 -3
  46. package/dist/embedded-agent/compaction-wiring.d.ts +2 -2
  47. package/dist/embedded-agent/compaction-wiring.js +44 -22
  48. package/dist/embedded-agent/compose.js +6 -5
  49. package/dist/embedded-agent/host-tools/mcp/contract.d.ts +12 -0
  50. package/dist/embedded-agent/host-tools/mcp/contract.js +20 -1
  51. package/dist/embedded-agent/host-tools/mcp/index.d.ts +1 -1
  52. package/dist/embedded-agent/host-tools/mcp/index.js +1 -1
  53. package/dist/embedded-agent/host-tools/take-screenshot/contract.d.ts +1 -1
  54. package/dist/embedded-agent/model-registry.d.ts +2 -0
  55. package/dist/embedded-agent/model-registry.js +2 -0
  56. package/dist/embedded-agent/prompt/build.d.ts +0 -1
  57. package/dist/embedded-agent/prompt/build.js +0 -1
  58. package/dist/embedded-agent/provider-fast-mode.js +7 -4
  59. package/dist/embedded-agent/recovery-wiring.d.ts +2 -0
  60. package/dist/embedded-agent/recovery-wiring.js +1 -13
  61. package/dist/main.js +34 -3
  62. package/dist/model-display.js +1 -1
  63. package/dist/model-ids.d.ts +7 -1
  64. package/dist/model-ids.js +14 -2
  65. package/dist/rpc/rpc-host.d.ts +3 -1
  66. package/dist/rpc/rpc-host.js +4 -1
  67. package/dist/rpc/rpc-mode.d.ts +1 -1
  68. package/dist/rpc/rpc-types.d.ts +11 -0
  69. package/dist/shortcut-model-catalog.d.ts +2 -1
  70. package/dist/shortcut-model-catalog.js +49 -10
  71. package/dist/subagent-model-policy.js +1 -0
  72. package/dist/utils/api-reference.d.ts +0 -2
  73. package/dist/utils/api-reference.js +21 -5
  74. package/dist/utils/mog-headless-api-reference.js +0 -1
  75. package/native-app/main/index.js +2 -2
  76. package/native-app/package.json +1 -1
  77. package/native-app/renderer/assets/{index-SuRESuDS.js → index-B4oy3UFe.js} +90 -24
  78. package/native-app/renderer/index.html +1 -1
  79. package/package.json +6 -1
  80. package/skills/mcp/SKILL.md +2 -2
  81. package/user-docs/dist/index.html +14 -26
  82. package/user-docs/security.md +4 -9
  83. package/xll/modules/shortcut_xl/_exec_entry.py +19 -4
  84. package/xll/modules/shortcut_xl/_exec_sandbox.py +2 -2
  85. package/xll/modules/shortcut_xl/_log.py +9 -2
  86. package/xll/modules/shortcut_xl/_threading.py +1 -1
  87. package/xll/python/Lib/site-packages/httpx-0.28.1.dist-info/RECORD +1 -1
  88. package/xll/python/Lib/site-packages/idna-3.18.dist-info/RECORD +1 -1
  89. package/xll/python/Lib/site-packages/pip-26.2.dist-info/RECORD +3 -3
  90. package/xll/python/Lib/site-packages/pywin32-311.dist-info/RECORD +2 -2
  91. package/xll/python/Scripts/httpx.exe +0 -0
  92. package/xll/python/Scripts/idna.exe +0 -0
  93. package/xll/python/Scripts/pip.exe +0 -0
  94. package/xll/python/Scripts/pip3.13.exe +0 -0
  95. package/xll/python/Scripts/pip3.exe +0 -0
  96. package/xll/python/Scripts/pywin32_postinstall.exe +0 -0
  97. package/xll/python/Scripts/pywin32_testall.exe +0 -0
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "generatedAt": "2026-09-02T01:14:28.575Z",
3
+ "generatedAt": "2026-09-10T05:20:31.240Z",
4
4
  "package": "shortcutxl",
5
5
  "build": {
6
6
  "builder": "github-actions",
7
7
  "repository": "lyfegame/shortcut",
8
- "sourceRef": "refs/heads/codex/cli-daily-credit-usage",
9
- "sourceCommit": "79a4adfc21dce36c575744cbbf07df842928b610",
10
- "workflowRef": "lyfegame/shortcut/.github/workflows/release-shortcut-cli-npm-package.yml@refs/heads/codex/cli-daily-credit-usage",
11
- "runId": "33578448535",
8
+ "sourceRef": "refs/heads/mcp-startup-budget",
9
+ "sourceCommit": "0905764a93d06c695b77bf413eaa2003d6bbd83f",
10
+ "workflowRef": "lyfegame/shortcut/.github/workflows/release-shortcut-cli-npm-package.yml@refs/heads/mcp-startup-budget",
11
+ "runId": "34440542822",
12
12
  "runnerOs": "Windows",
13
13
  "runnerImage": "win25-vs2026",
14
- "runnerImageVersion": "20260824.214.3"
14
+ "runnerImageVersion": "20260907.229.1"
15
15
  },
16
16
  "binaryExtensions": [
17
17
  ".dll",
@@ -550,7 +550,7 @@
550
550
  },
551
551
  {
552
552
  "path": "xll/python/Scripts/httpx.exe",
553
- "sha256": "56ecd26f514b1582761c3403f4938d112adc5f033a63d4bc945181079ca82ead",
553
+ "sha256": "a022dc159b79a3312afeed6f1733da09a8e85523163566f0df9da427319a0040",
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": "ffda0108e10dc93a09f57419aac0a52d85871bb787308bf946ac851cb11f703a",
561
+ "sha256": "8cd6a6078a64ae8e4c826fde0c179ee719401389df7d73832baf0dd3c582ab38",
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": "ce0f86f7cf5cc2882a4821943d3af21f7c41218ff43e7a1f62b18f185aefde97",
569
+ "sha256": "4a130513690433dd5e1e502566a7fd2084fc629eb85d08f66b0aad591a012c23",
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": "ce0f86f7cf5cc2882a4821943d3af21f7c41218ff43e7a1f62b18f185aefde97",
577
+ "sha256": "4a130513690433dd5e1e502566a7fd2084fc629eb85d08f66b0aad591a012c23",
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": "ce0f86f7cf5cc2882a4821943d3af21f7c41218ff43e7a1f62b18f185aefde97",
585
+ "sha256": "4a130513690433dd5e1e502566a7fd2084fc629eb85d08f66b0aad591a012c23",
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": "ff04c09d3e15a07d9a4006748c92e511f7e5c5abb2c78b68d7233e8f3ff19630",
593
+ "sha256": "1e121893a273808b93c23160b171d2055aa4ec72ddb72c92a3d7ebb274115f1d",
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": "c0641e3d6816baddcabb155f66ffb5de7c378b8257b95ad16f0a45e26184b879",
601
+ "sha256": "d92feb62593764e35c01fa841192e2cbab59bd51b4d33f88f14197527c48ffa2",
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,8 +1,14 @@
1
1
  # Changelog
2
2
 
3
- ## [0.3.89]
3
+ ## [0.3.91]
4
+
5
+ - **GPT-6 Astra** - GPT-6 Astra is now available in the model selector.
6
+ - **Faster, more reliable MCP startup** - Shortcut now starts MCP integrations faster, retries temporary connection failures, and clearly reports servers that take too long to start.
7
+
8
+ ## [0.3.90]
4
9
 
5
10
  - **Skill Forge** - A recursive self-improvement loop for skills. Create or improve personal skills by giving Skill Forge tasks and a grading bar to train, evaluate, and refine against.
11
+ - **Safer, more reliable Excel changes** - Shortcut now asks for approval before making workbook changes and allows longer operations to finish without timing out.
6
12
  - **Credit usage at a glance** - See today's personal credit usage for non-teams users.
7
13
 
8
14
  ## [0.3.88]
@@ -296,6 +296,8 @@ export interface Model<TApi extends Api> {
296
296
  cacheWrite: number;
297
297
  };
298
298
  contextWindow: number;
299
+ /** Optional auto-compaction threshold. Defaults to the runtime-wide threshold policy. */
300
+ compactionThresholdTokens?: number;
299
301
  maxTokens: number;
300
302
  capabilities?: ModelCapabilities;
301
303
  headers?: Record<string, string>;
@@ -12,12 +12,20 @@
12
12
  export declare function setSecret(name: string, payload: Record<string, string>): void;
13
13
  /** Retrieve a secret payload for a connection. Returns undefined if not found. */
14
14
  export declare function getSecret(name: string): Record<string, string> | undefined;
15
+ /**
16
+ * Retrieve several connection secrets without serially blocking the Node event loop.
17
+ * Windows uses one PowerShell process for the whole batch so the P/Invoke helper is
18
+ * compiled once. Other platforms perform their native lookups concurrently.
19
+ */
20
+ export declare function getSecrets(names: readonly string[], timeoutMs?: number): Promise<Map<string, Record<string, string> | undefined>>;
15
21
  /** Remove a secret for a connection. Returns true if it existed. */
16
22
  export declare function removeSecret(name: string): boolean;
17
23
  /** Store a JSON payload in the OS keychain under a custom service namespace. */
18
24
  export declare function setNamespacedSecret(service: string, name: string, payload: object): void;
19
25
  /** Retrieve a JSON payload from the OS keychain for a custom service namespace. */
20
26
  export declare function getNamespacedSecret<T extends object>(service: string, name: string): T | undefined;
27
+ /** Retrieve several secrets through a single asynchronous batch operation where possible. */
28
+ export declare function getNamespacedSecrets<T extends object>(service: string, names: readonly string[], timeoutMs?: number): Promise<Map<string, T | undefined>>;
21
29
  /** Remove a JSON payload from the OS keychain for a custom service namespace. */
22
30
  export declare function removeNamespacedSecret(service: string, name: string): boolean;
23
31
  /**
@@ -8,7 +8,7 @@
8
8
  *
9
9
  * Imports: none from internal layers. Pure shell-out utility.
10
10
  */
11
- import { execFileSync } from 'child_process';
11
+ import { execFile, execFileSync } from 'child_process';
12
12
  const CONNECTION_SERVICE = 'shortcut.connection';
13
13
  // =============================================================================
14
14
  // Public API
@@ -21,6 +21,14 @@ export function setSecret(name, payload) {
21
21
  export function getSecret(name) {
22
22
  return getNamespacedSecret(CONNECTION_SERVICE, name);
23
23
  }
24
+ /**
25
+ * Retrieve several connection secrets without serially blocking the Node event loop.
26
+ * Windows uses one PowerShell process for the whole batch so the P/Invoke helper is
27
+ * compiled once. Other platforms perform their native lookups concurrently.
28
+ */
29
+ export function getSecrets(names, timeoutMs = 10_000) {
30
+ return getNamespacedSecrets(CONNECTION_SERVICE, names, timeoutMs);
31
+ }
24
32
  /** Remove a secret for a connection. Returns true if it existed. */
25
33
  export function removeSecret(name) {
26
34
  return removeNamespacedSecret(CONNECTION_SERVICE, name);
@@ -61,6 +69,45 @@ export function getNamespacedSecret(service, name) {
61
69
  }
62
70
  return undefined;
63
71
  }
72
+ /** Retrieve several secrets through a single asynchronous batch operation where possible. */
73
+ export async function getNamespacedSecrets(service, names, timeoutMs = 10_000) {
74
+ const uniqueNames = [...new Set(names)];
75
+ const result = new Map();
76
+ if (uniqueNames.length === 0)
77
+ return result;
78
+ if (process.platform === 'win32') {
79
+ const targets = uniqueNames.map((name) => `${service}/${name}`);
80
+ const psTargets = targets.map((target) => `'${psEsc(target)}'`).join(', ');
81
+ const ps = `${WIN_CRED_PREAMBLE}
82
+ $result = @{}
83
+ foreach ($target in @(${psTargets})) {
84
+ $value = [CredManager]::Read($target)
85
+ if ($null -ne $value) { $result[$target] = $value }
86
+ }
87
+ $result | ConvertTo-Json -Compress`;
88
+ const raw = await execFileUtf8('powershell', ['-NoProfile', '-EncodedCommand', psEncode(ps)], timeoutMs);
89
+ const values = raw.trim() ? JSON.parse(raw) : {};
90
+ for (const [index, name] of uniqueNames.entries()) {
91
+ result.set(name, parseSecretJson(values[targets[index]]));
92
+ }
93
+ return result;
94
+ }
95
+ if (process.platform === 'darwin') {
96
+ await Promise.all(uniqueNames.map(async (name) => {
97
+ try {
98
+ const raw = await execFileUtf8('security', ['find-generic-password', '-a', name, '-s', service, '-w'], timeoutMs);
99
+ result.set(name, parseSecretJson(raw));
100
+ }
101
+ catch {
102
+ result.set(name, undefined);
103
+ }
104
+ }));
105
+ return result;
106
+ }
107
+ for (const name of uniqueNames)
108
+ result.set(name, undefined);
109
+ return result;
110
+ }
64
111
  /** Remove a JSON payload from the OS keychain for a custom service namespace. */
65
112
  export function removeNamespacedSecret(service, name) {
66
113
  try {
@@ -115,7 +162,11 @@ export function buildConnectionEnv(connectionNames) {
115
162
  * PowerShell script that uses P/Invoke to call CredRead/CredWrite/CredDelete
116
163
  * from advapi32.dll. Works on every Windows version. No external modules needed.
117
164
  */
165
+ // Secrets are read back as .NET strings but serialized to stdout with the
166
+ // console's OEM codepage, which varies per machine and locale. Force BOM-less
167
+ // UTF-8 so Node decodes non-ASCII values correctly on every host.
118
168
  const WIN_CRED_PREAMBLE = `
169
+ [Console]::OutputEncoding = New-Object System.Text.UTF8Encoding $false
119
170
  Add-Type -TypeDefinition @"
120
171
  using System;
121
172
  using System.Runtime.InteropServices;
@@ -161,6 +212,27 @@ public class CredManager {
161
212
  function psEncode(script) {
162
213
  return Buffer.from(script, 'utf16le').toString('base64');
163
214
  }
215
+ function execFileUtf8(command, args, timeout) {
216
+ return new Promise((resolve, reject) => {
217
+ execFile(command, args, { encoding: 'utf-8', timeout, windowsHide: true }, (error, stdout) => {
218
+ if (error) {
219
+ reject(error);
220
+ return;
221
+ }
222
+ resolve(stdout);
223
+ });
224
+ });
225
+ }
226
+ function parseSecretJson(raw) {
227
+ if (!raw)
228
+ return undefined;
229
+ try {
230
+ return JSON.parse(raw.trim());
231
+ }
232
+ catch {
233
+ return undefined;
234
+ }
235
+ }
164
236
  function winCredWrite(target, secret) {
165
237
  const ps = `${WIN_CRED_PREAMBLE}\n[CredManager]::Write('${psEsc(target)}', '${psEsc(secret)}')`;
166
238
  execFileSync('powershell', ['-NoProfile', '-EncodedCommand', psEncode(ps)], {
@@ -6,6 +6,10 @@ export interface ExcelDiffApprovalPayload {
6
6
  kind: 'diff';
7
7
  description: string;
8
8
  }
9
+ export interface ExcelWriteApprovalPayload {
10
+ kind: 'write';
11
+ description: string;
12
+ }
9
13
  export interface OfficeDangerousApprovalPayload {
10
14
  kind: 'dangerous';
11
15
  description: string;
@@ -13,5 +17,5 @@ export interface OfficeDangerousApprovalPayload {
13
17
  summary: string;
14
18
  operations: OfficeApprovalOperation[];
15
19
  }
16
- export type OfficeApprovalPayload = ExcelDiffApprovalPayload | OfficeDangerousApprovalPayload;
20
+ export type OfficeApprovalPayload = ExcelDiffApprovalPayload | ExcelWriteApprovalPayload | OfficeDangerousApprovalPayload;
17
21
  //# sourceMappingURL=approval-payload.d.ts.map
@@ -2,9 +2,9 @@
2
2
  * COM execute_code executor — local XLL-backed execution lifecycle.
3
3
  *
4
4
  * Owns the COM-specific execution flow:
5
- * 1. Send code to the local XLL via xllFetch
6
- * 2. Parse and validate the response
7
- * 3. Run the approval / revert / cleanup flow
5
+ * 1. Classify code and request write approval before execution
6
+ * 2. Send approved code to the local XLL via xllFetch
7
+ * 3. Parse and validate the response
8
8
  *
9
9
  * Used by:
10
10
  * - `createExcelExecTool()` (COM ToolDefinition wrapper)
@@ -23,7 +23,7 @@ export interface ComExecResult {
23
23
  cfTxId?: string;
24
24
  editMode?: boolean;
25
25
  }
26
- export type ApprovalOutcome = 'accepted' | 'approval-unavailable' | 'failed' | 'reverted' | 'revert-failed' | 'rejected';
26
+ export type ApprovalOutcome = 'accepted' | 'approval-unavailable' | 'failed' | 'rejected';
27
27
  export interface LocalExecResult {
28
28
  result: ComExecResult;
29
29
  outcome: ApprovalOutcome;
@@ -33,19 +33,9 @@ export interface ExcelExecApprovalObservability {
33
33
  onRequested?: (request: ApprovalRequest<OfficeApprovalPayload>) => void;
34
34
  onAccepted?: (request: ApprovalRequest<OfficeApprovalPayload>, choice: ApprovalChoice) => void;
35
35
  onRejected?: (request: ApprovalRequest<OfficeApprovalPayload>) => void;
36
- onReverted?: (request: ApprovalRequest<OfficeApprovalPayload>) => void;
37
- onRevertFailed?: (request: ApprovalRequest<OfficeApprovalPayload>) => void;
38
- }
39
- interface ApprovalResolution {
40
- outcome: ApprovalOutcome;
41
- approvalSummaries?: string[];
42
36
  }
43
37
  /** Validate that a parsed JSON value has the COM execute result shape. */
44
38
  export declare function isComExecResult(v: unknown): v is ComExecResult;
45
- /**
46
- * Resolve approval for cell changes: show UI if needed, then revert or cleanup.
47
- */
48
- export declare function resolveApproval(result: ComExecResult, approval: ApprovalGate<OfficeApprovalPayload> | undefined, postControl: (body: object) => Promise<Response>, ctx: ExtensionContext, description?: string, observability?: ExcelExecApprovalObservability, priorApprovalSummaries?: string[]): Promise<ApprovalResolution>;
49
39
  /**
50
40
  * Execute code against the local XLL and run approval.
51
41
  *
@@ -60,5 +50,4 @@ export declare function executeComLocally(excelTarget: ExcelHostEndpoint, code:
60
50
  observability?: ExcelExecApprovalObservability;
61
51
  editModeTiming?: Partial<EditModeRetryTiming>;
62
52
  }): Promise<LocalExecResult>;
63
- export {};
64
53
  //# sourceMappingURL=executor.d.ts.map
@@ -2,14 +2,13 @@
2
2
  * COM execute_code executor — local XLL-backed execution lifecycle.
3
3
  *
4
4
  * Owns the COM-specific execution flow:
5
- * 1. Send code to the local XLL via xllFetch
6
- * 2. Parse and validate the response
7
- * 3. Run the approval / revert / cleanup flow
5
+ * 1. Classify code and request write approval before execution
6
+ * 2. Send approved code to the local XLL via xllFetch
7
+ * 3. Parse and validate the response
8
8
  *
9
9
  * Used by:
10
10
  * - `createExcelExecTool()` (COM ToolDefinition wrapper)
11
11
  */
12
- import { log } from '../../../../shared/logging/agent-log.js';
13
12
  import { INTERACTIVE_APPROVAL_UNAVAILABLE_MESSAGE } from '../../../approvals/approval-errors.js';
14
13
  import { APPROVAL } from '../../../approvals/types.js';
15
14
  import { getApprovalSurface, resolveApprovalExecutionDecision } from '../../../permissions/approval-surface.js';
@@ -31,13 +30,6 @@ export function isComExecResult(v) {
31
30
  (r.error === undefined || typeof r.error === 'string') &&
32
31
  (r.editMode === undefined || typeof r.editMode === 'boolean'));
33
32
  }
34
- // ---------------------------------------------------------------------------
35
- // Control constants
36
- // ---------------------------------------------------------------------------
37
- const CONTROL = {
38
- REVERT: 'revert',
39
- CLEANUP: 'cleanup'
40
- };
41
33
  function describeExcelApprovalChoice(choice) {
42
34
  switch (choice) {
43
35
  case APPROVAL.ACCEPT:
@@ -97,95 +89,41 @@ async function requestAlwaysConfirmApproval(approval, ctx, description, dangerou
97
89
  return { choice, unavailable: false };
98
90
  }
99
91
  // ---------------------------------------------------------------------------
100
- // Approval resolution
92
+ // Pre-execution write approval
101
93
  // ---------------------------------------------------------------------------
102
- /**
103
- * Resolve approval for cell changes: show UI if needed, then revert or cleanup.
104
- */
105
- export async function resolveApproval(result, approval, postControl, ctx, description, observability, priorApprovalSummaries) {
106
- const { diff, cfTxId } = result;
107
- let approvalSummaries = priorApprovalSummaries;
108
- if (diff) {
109
- const request = {
110
- kind: 'execute_code',
111
- title: 'Review Excel changes',
112
- choices: [APPROVAL.ACCEPT, APPROVAL.ACCEPT_ALL, APPROVAL.REJECT],
113
- payload: {
114
- kind: 'diff',
115
- description: description?.trim() || 'Updating Excel'
116
- }
117
- };
118
- const decision = resolveApprovalExecutionDecision({
119
- surface: getApprovalSurface(ctx),
120
- gateAvailable: approval !== undefined,
121
- isSafe: false,
122
- isAutoApproved: approval?.isAutoApproved() ?? false,
123
- alwaysRequireExplicitApproval: false,
124
- allowHeadlessApproval: ctx.permissionPolicy?.office.allowHeadlessApproval ?? false
125
- });
126
- if (decision === 'prompt') {
127
- if (!approval) {
128
- observability?.onRejected?.(request);
129
- try {
130
- await postControl({ control: CONTROL.REVERT, cfTxId });
131
- observability?.onReverted?.(request);
132
- return { outcome: 'reverted' };
133
- }
134
- catch {
135
- if (cfTxId != null) {
136
- await postControl({ control: CONTROL.CLEANUP, cfTxId }).catch(() => { });
137
- }
138
- observability?.onRevertFailed?.(request);
139
- return { outcome: 'revert-failed' };
140
- }
141
- }
142
- observability?.onRequested?.(request);
143
- const choice = await approval.requestApproval(request, ctx);
144
- if (choice === APPROVAL.REJECT || choice === undefined) {
145
- observability?.onRejected?.(request);
146
- try {
147
- await postControl({ control: CONTROL.REVERT, cfTxId });
148
- observability?.onReverted?.(request);
149
- return { outcome: 'rejected' };
150
- }
151
- catch {
152
- if (cfTxId != null) {
153
- await postControl({ control: CONTROL.CLEANUP, cfTxId }).catch(() => { });
154
- }
155
- observability?.onRevertFailed?.(request);
156
- return { outcome: 'revert-failed' };
157
- }
158
- }
159
- observability?.onAccepted?.(request, choice);
160
- if (choice === APPROVAL.ACCEPT_ALL) {
161
- approval.onAcceptAll();
162
- }
163
- approvalSummaries = appendApprovalSummary(approvalSummaries, choice);
164
- }
165
- else if (decision === 'deny') {
166
- observability?.onRejected?.(request);
167
- try {
168
- await postControl({ control: CONTROL.REVERT, cfTxId });
169
- observability?.onReverted?.(request);
170
- return { outcome: 'reverted' };
171
- }
172
- catch {
173
- if (cfTxId != null) {
174
- await postControl({ control: CONTROL.CLEANUP, cfTxId }).catch(() => { });
175
- }
176
- observability?.onRevertFailed?.(request);
177
- return { outcome: 'revert-failed' };
178
- }
94
+ async function requestWriteApproval(approval, ctx, description, observability) {
95
+ const request = {
96
+ kind: 'execute_code',
97
+ title: 'Review Excel changes',
98
+ choices: [APPROVAL.ACCEPT, APPROVAL.ACCEPT_ALL, APPROVAL.REJECT],
99
+ payload: {
100
+ kind: 'write',
101
+ description: description?.trim() || 'Updating Excel'
179
102
  }
103
+ };
104
+ const decision = resolveApprovalExecutionDecision({
105
+ surface: getApprovalSurface(ctx),
106
+ gateAvailable: approval !== undefined,
107
+ isSafe: false,
108
+ isAutoApproved: approval?.isAutoApproved() ?? false,
109
+ alwaysRequireExplicitApproval: false,
110
+ allowHeadlessApproval: ctx?.permissionPolicy?.office.allowHeadlessApproval ?? false
111
+ });
112
+ if (decision === 'allow') {
113
+ return { choice: APPROVAL.ACCEPT, unavailable: false, prompted: false };
180
114
  }
181
- // Accepted (explicitly or auto) — clean up CF highlights.
182
- if (cfTxId != null) {
183
- await postControl({ control: CONTROL.CLEANUP, cfTxId }).catch((e) => {
184
- const msg = e instanceof Error ? e.message : String(e);
185
- log.error('excel-exec', `CF cleanup failed for tx ${cfTxId}: ${msg}`);
186
- });
115
+ if (decision === 'deny' || !approval || !ctx) {
116
+ observability?.onRejected?.(request);
117
+ return { choice: APPROVAL.REJECT, unavailable: true, prompted: false };
118
+ }
119
+ observability?.onRequested?.(request);
120
+ const choice = await approval.requestApproval(request, ctx);
121
+ if (choice === APPROVAL.REJECT || choice === undefined) {
122
+ observability?.onRejected?.(request);
123
+ return { choice, unavailable: false, prompted: true };
187
124
  }
188
- return { outcome: 'accepted', approvalSummaries };
125
+ observability?.onAccepted?.(request, choice);
126
+ return { choice, unavailable: false, prompted: true };
189
127
  }
190
128
  // ---------------------------------------------------------------------------
191
129
  // Core executor
@@ -200,7 +138,7 @@ export async function executeComLocally(excelTarget, code, options) {
200
138
  const resolvedExcelHttpUrl = excelTarget.url;
201
139
  const { approval, ctx, description, signal, observability, editModeTiming } = options ?? {};
202
140
  let approvalSummaries;
203
- const postControl = (body, sig) => xllFetch(`${resolvedExcelHttpUrl}/exec`, {
141
+ const postExec = (body, sig) => xllFetch(`${resolvedExcelHttpUrl}/exec`, {
204
142
  method: 'POST',
205
143
  headers: { 'Content-Type': 'application/json' },
206
144
  body: JSON.stringify(body),
@@ -224,7 +162,7 @@ export async function executeComLocally(excelTarget, code, options) {
224
162
  const sendExecRequest = async () => {
225
163
  let response;
226
164
  try {
227
- response = await postControl({ code, autoApprove }, signal);
165
+ response = await postExec({ code, autoApprove: true }, signal);
228
166
  }
229
167
  catch (e) {
230
168
  if (signal?.aborted || isEditModeAbortError(e)) {
@@ -237,7 +175,6 @@ export async function executeComLocally(excelTarget, code, options) {
237
175
  };
238
176
  // Static analysis: skip approval for code with no detectable writes.
239
177
  const analysis = analyzeComWriteCode(code);
240
- const dangerousLifecycleBypassed = analysis.alwaysRequireApproval && isDangerousExcelLifecycleBypassed(ctx);
241
178
  if (analysis.alwaysRequireApproval) {
242
179
  const approvalResult = await requestAlwaysConfirmApproval(approval, ctx, description, analysis.dangerousOperations, observability);
243
180
  const { choice } = approvalResult;
@@ -257,25 +194,26 @@ export async function executeComLocally(excelTarget, code, options) {
257
194
  }
258
195
  approvalSummaries = appendApprovalSummary(approvalSummaries, choice);
259
196
  }
260
- const autoApprove = analysis.isReadOnly || analysis.alwaysRequireApproval || (approval?.isAutoApproved() ?? false);
261
- const executionDecision = dangerousLifecycleBypassed
262
- ? 'allow'
263
- : resolveApprovalExecutionDecision({
264
- surface: getApprovalSurface(ctx),
265
- gateAvailable: approval !== undefined,
266
- isSafe: analysis.isReadOnly,
267
- isAutoApproved: approval?.isAutoApproved() ?? false,
268
- alwaysRequireExplicitApproval: false,
269
- allowHeadlessApproval: ctx?.permissionPolicy?.office.allowHeadlessApproval ?? false
270
- });
271
- if (executionDecision === 'deny') {
272
- return {
273
- result: {
274
- ok: true,
275
- output: INTERACTIVE_APPROVAL_UNAVAILABLE_MESSAGE
276
- },
277
- outcome: 'approval-unavailable'
278
- };
197
+ else if (!analysis.isReadOnly) {
198
+ const approvalResult = await requestWriteApproval(approval, ctx, description, observability);
199
+ const { choice } = approvalResult;
200
+ if (choice !== APPROVAL.ACCEPT && choice !== APPROVAL.ACCEPT_ALL) {
201
+ return {
202
+ result: {
203
+ ok: true,
204
+ output: approvalResult.unavailable
205
+ ? INTERACTIVE_APPROVAL_UNAVAILABLE_MESSAGE
206
+ : 'Excel operation rejected by user.'
207
+ },
208
+ outcome: approvalResult.unavailable ? 'approval-unavailable' : 'rejected'
209
+ };
210
+ }
211
+ if (approvalResult.prompted && choice === APPROVAL.ACCEPT_ALL) {
212
+ approval?.onAcceptAll();
213
+ }
214
+ if (approvalResult.prompted) {
215
+ approvalSummaries = appendApprovalSummary(approvalSummaries, choice);
216
+ }
279
217
  }
280
218
  let result = await sendExecRequest();
281
219
  if (result.editMode) {
@@ -289,34 +227,6 @@ export async function executeComLocally(excelTarget, code, options) {
289
227
  if (!result.ok) {
290
228
  return { result, outcome: 'failed' };
291
229
  }
292
- if (ctx) {
293
- const { outcome, approvalSummaries: resolvedApprovalSummaries } = await resolveApproval(result, approval, (b) => postControl(b), ctx, description, observability, approvalSummaries);
294
- return { result, outcome, approvalSummaries: resolvedApprovalSummaries };
295
- }
296
- if (result.diff) {
297
- try {
298
- await postControl({ control: CONTROL.REVERT, cfTxId: result.cfTxId });
299
- return {
300
- result: { ...result, output: 'Excel operation rejected by user.' },
301
- outcome: 'reverted'
302
- };
303
- }
304
- catch {
305
- if (result.cfTxId != null) {
306
- await postControl({ control: CONTROL.CLEANUP, cfTxId: result.cfTxId }).catch(() => { });
307
- }
308
- return {
309
- result: { ...result, output: 'Excel operation rejected by user.' },
310
- outcome: 'revert-failed'
311
- };
312
- }
313
- }
314
- if (result.cfTxId != null) {
315
- await postControl({ control: CONTROL.CLEANUP, cfTxId: result.cfTxId }).catch((e) => {
316
- const msg = e instanceof Error ? e.message : String(e);
317
- log.error('excel-exec', `CF cleanup failed for tx ${result.cfTxId}: ${msg}`);
318
- });
319
- }
320
230
  return { result, outcome: 'accepted', approvalSummaries };
321
231
  }
322
232
  //# sourceMappingURL=executor.js.map
@@ -31,26 +31,6 @@ export function createExecuteCodeApprovalObservability(clientLogger) {
31
31
  approval_kind: request.payload.kind
32
32
  }
33
33
  });
34
- },
35
- onReverted: (request) => {
36
- clientLogger.info({
37
- module: 'tool',
38
- event: 'tool_approval_reverted',
39
- data: {
40
- tool_name: TOOL_NAME,
41
- approval_kind: request.payload.kind
42
- }
43
- });
44
- },
45
- onRevertFailed: (request) => {
46
- clientLogger.error({
47
- module: 'tool',
48
- event: 'tool_approval_revert_failed',
49
- data: {
50
- tool_name: TOOL_NAME,
51
- approval_kind: request.payload.kind
52
- }
53
- });
54
34
  }
55
35
  };
56
36
  }
@@ -81,23 +81,6 @@ export function createExcelExecTool(resolveTarget, approval, observability) {
81
81
  ctx: ctx,
82
82
  signal: signal ?? undefined
83
83
  });
84
- if (outcome === 'revert-failed') {
85
- return {
86
- content: [
87
- {
88
- type: 'text',
89
- text: 'Revert failed — changes are still applied in Excel. You may need to undo manually (Ctrl+Z).'
90
- }
91
- ],
92
- details: result
93
- };
94
- }
95
- if (outcome === 'reverted') {
96
- return {
97
- content: [{ type: 'text', text: 'Changes reverted by user.' }],
98
- details: result
99
- };
100
- }
101
84
  if (outcome === 'rejected') {
102
85
  return {
103
86
  content: [{ type: 'text', text: USER_REJECTED_APPROVAL_MESSAGE }],
@@ -47,8 +47,10 @@ const RAW_COM_WRITES = [
47
47
  /\.ClearComments\s*\(/,
48
48
  /\.ClearNotes\s*\(/,
49
49
  /\.Copy\s*\(/,
50
+ /\.Cut\s*\(/,
50
51
  /\.PasteSpecial\s*\(/,
51
52
  /\.AutoFill\s*\(/,
53
+ /\.Resize\s*\(/,
52
54
  /\.Merge\s*\(/,
53
55
  /\.UnMerge\s*\(/,
54
56
  /\.Move\s*\(/,