@wjarka/cezarion 0.16.0-dev.1503 → 0.16.0-pr923.1504

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 (79) hide show
  1. package/README.md +15 -4
  2. package/dist/contract/index.js +3 -0
  3. package/dist/contract/runs.d.ts +30 -0
  4. package/dist/contract/skills.d.ts +4 -0
  5. package/dist/core/agent-runner.d.ts +9 -6
  6. package/dist/core/agent-runner.js +4 -2
  7. package/dist/core/agent-runner.js.map +1 -1
  8. package/dist/core/cursor-print-capabilities.d.ts +12 -0
  9. package/dist/core/cursor-print-capabilities.js +77 -0
  10. package/dist/core/cursor-print-capabilities.js.map +1 -0
  11. package/dist/core/cursor-print-process.d.ts +31 -0
  12. package/dist/core/cursor-print-process.js +129 -0
  13. package/dist/core/cursor-print-process.js.map +1 -0
  14. package/dist/core/cursor-print-runner.d.ts +26 -0
  15. package/dist/core/cursor-print-runner.js +512 -0
  16. package/dist/core/cursor-print-runner.js.map +1 -0
  17. package/dist/core/cursor-print-tool-catalog.d.ts +6 -0
  18. package/dist/core/cursor-print-tool-catalog.js +78 -0
  19. package/dist/core/cursor-print-tool-catalog.js.map +1 -0
  20. package/dist/core/cursor-print-ui-mapper.d.ts +47 -0
  21. package/dist/core/cursor-print-ui-mapper.js +573 -0
  22. package/dist/core/cursor-print-ui-mapper.js.map +1 -0
  23. package/dist/core/cursor-runner.d.ts +24 -0
  24. package/dist/core/cursor-runner.js +146 -0
  25. package/dist/core/cursor-runner.js.map +1 -0
  26. package/dist/core/process-usage.d.ts +5 -0
  27. package/dist/core/process-usage.js +24 -4
  28. package/dist/core/process-usage.js.map +1 -1
  29. package/dist/core/runner-factory.d.ts +4 -1
  30. package/dist/core/runner-factory.js +3 -3
  31. package/dist/core/runner-factory.js.map +1 -1
  32. package/dist/delegation/transport.d.ts +13 -13
  33. package/dist/runs/store.d.ts +9 -0
  34. package/dist/runs/store.js +1 -0
  35. package/dist/runs/store.js.map +1 -1
  36. package/dist/server/server.d.ts +118 -92
  37. package/dist/workflows/run.js +39 -8
  38. package/dist/workflows/run.js.map +1 -1
  39. package/package.json +3 -3
  40. package/scripts/mock-ci-tool.mjs +12 -4
  41. package/scripts/mock-cursor-acp.mjs +1 -0
  42. package/scripts/mock-cursor-print.mjs +196 -0
  43. package/scripts/probe-cursor-print.mjs +616 -0
  44. package/web/dist/assets/{agent-accounts-CFOBMVHc.js → agent-accounts-CDhVzwnd.js} +1 -1
  45. package/web/dist/assets/{alert-dialog-BrpNUwMt.js → alert-dialog-A0-kEfJK.js} +1 -1
  46. package/web/dist/assets/{button-C_rd7CPp.js → button-Jj1Itx83.js} +1 -1
  47. package/web/dist/assets/{compare-variants-D6WXhvXA.js → compare-variants-aor2j4bO.js} +1 -1
  48. package/web/dist/assets/{diff-DrEARXTV.js → diff-CDL5SDNO.js} +2 -2
  49. package/web/dist/assets/{diff-controls-B1-j7nFg.js → diff-controls-BWAkxipD.js} +1 -1
  50. package/web/dist/assets/{diff-view-DvGLjdTk.js → diff-view-DWh9FnNY.js} +1 -1
  51. package/web/dist/assets/{dropdown-menu-BegOZFrj.js → dropdown-menu-Ddqp8eUw.js} +1 -1
  52. package/web/dist/assets/{format-DpNe3S0A.js → format-DTNjugyE.js} +1 -1
  53. package/web/dist/assets/{git-toolbar-Cw4ljmhB.js → git-toolbar-B4rwm9Lr.js} +1 -1
  54. package/web/dist/assets/{github-CfV5sqL0.js → github-DgMD1v4U.js} +1 -1
  55. package/web/dist/assets/{image-preview-FOEfqpZP.js → image-preview-Dy5i96RC.js} +1 -1
  56. package/web/dist/assets/{index-DgWWlOLy.js → index-BfAearY6.js} +4 -4
  57. package/web/dist/assets/{nav-row-styles-vp2JbhuJ.js → nav-row-styles-Ci-Ke7wG.js} +1 -1
  58. package/web/dist/assets/{popover-BohP6WTx.js → popover-BjjDOSGv.js} +1 -1
  59. package/web/dist/assets/{preview-pane-connected-qTA5KNoJ.js → preview-pane-connected-Nv_nhV63.js} +1 -1
  60. package/web/dist/assets/{preview-state-vuMviq6q.js → preview-state-D8-YBJ6m.js} +1 -1
  61. package/web/dist/assets/{prompt-templates-Cubdut_C.js → prompt-templates-DFeXUVOZ.js} +1 -1
  62. package/web/dist/assets/{repo-git-Cf4t5PD0.js → repo-git-DcWh5s_b.js} +1 -1
  63. package/web/dist/assets/{run-diff-BpkUW37Z.js → run-diff-1ZzFrOx1.js} +1 -1
  64. package/web/dist/assets/{run-header-68WHf9wV.js → run-header-CPoYzbAy.js} +1 -1
  65. package/web/dist/assets/{skills-DiVIgKeU.js → skills-CnLazLgF.js} +1 -1
  66. package/web/dist/assets/{status-dot-CqTJHSP9.js → status-dot-DwFmt_Jg.js} +1 -1
  67. package/web/dist/assets/{tab-link-B07Hu4QX.js → tab-link-COpvArgR.js} +1 -1
  68. package/web/dist/assets/{task-changes--lQF6EYf.js → task-changes-CY-mi-mC.js} +1 -1
  69. package/web/dist/assets/{task-commits-Cx0qy4DN.js → task-commits-EvLi8T4a.js} +1 -1
  70. package/web/dist/assets/{task-files-D50s-gKW.js → task-files-BR3d8X2U.js} +1 -1
  71. package/web/dist/assets/{task-github-item-DaqmSwYT.js → task-github-item-BVsPg9RI.js} +1 -1
  72. package/web/dist/assets/{task-thread-BPQ64lui.js → task-thread-DQCsu_v8.js} +1 -1
  73. package/web/dist/assets/{textarea-D20O4Pw0.js → textarea-BbNV5Uo2.js} +1 -1
  74. package/web/dist/assets/{thread-state-DWcFMrvb.js → thread-state-DTkIvHsU.js} +1 -1
  75. package/web/dist/assets/{use-hand-to-agent-state-Cy9eyyfL.js → use-hand-to-agent-state-DT-QBsPC.js} +1 -1
  76. package/web/dist/assets/{utils-JsL3ZsJf.js → utils-POVs3UKu.js} +1 -1
  77. package/web/dist/assets/{workflows-jwG7uxqi.js → workflows-BTE-5RwX.js} +1 -1
  78. package/web/dist/assets/{zoomable-image-BJJ64KGc.js → zoomable-image-cL1VgYK4.js} +1 -1
  79. package/web/dist/index.html +13 -13
@@ -0,0 +1,616 @@
1
+ #!/usr/bin/env node
2
+ // Opt-in vendor probe. It saves only a small allowlisted summary, never a transcript.
3
+ import { spawn, spawnSync } from 'node:child_process';
4
+ import { createHash, randomBytes, randomUUID } from 'node:crypto';
5
+ import { existsSync, mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
6
+ import { tmpdir } from 'node:os';
7
+ import { join, resolve } from 'node:path';
8
+ import { parseArgs } from 'node:util';
9
+ import { deflateSync } from 'node:zlib';
10
+
11
+ const { values } = parseArgs({ options: {
12
+ model: { type: 'string' },
13
+ 'output-dir': { type: 'string' },
14
+ case: { type: 'string' },
15
+ } });
16
+ if (!values.model || !values['output-dir'] || !['native-question', 'delegation', 'mcp', 'portable-ask', 'plugins', 'resume-missing', 'fields', 'plugin-lifecycle', 'errors'].includes(values.case)) {
17
+ process.stderr.write('Usage: probe-cursor-print.mjs --model <discovered-id> --output-dir <path> --case native-question|delegation|mcp|portable-ask|plugins|resume-missing|fields|plugin-lifecycle|errors\n');
18
+ process.exit(1);
19
+ }
20
+
21
+ const bin = process.env.CEZ_CURSOR_BIN || 'agent';
22
+ const scratch = mkdtempSync(join(tmpdir(), 'cez-cursor-print-probe-'));
23
+ const checkout = join(scratch, 'checkout');
24
+ const outputDir = resolve(values['output-dir']);
25
+
26
+ function git(args) {
27
+ const result = spawnSync('git', args, { encoding: 'utf8', timeout: 15_000 });
28
+ if (result.status !== 0) throw new Error(`git ${args[0]} failed`);
29
+ return result.stdout.trim();
30
+ }
31
+
32
+ function runAgent(args, cwd, timeoutMs, env = process.env) {
33
+ return new Promise((resolveRun, rejectRun) => {
34
+ const child = spawn(bin, args, { cwd, env, detached: process.platform !== 'win32', stdio: ['ignore', 'pipe', 'pipe'] });
35
+ let stdout = '';
36
+ let stderr = '';
37
+ let truncated = false;
38
+ let timedOut = false;
39
+ const append = (current, chunk) => {
40
+ if (current.length + chunk.length > 1_048_576) { truncated = true; return current; }
41
+ return current + chunk;
42
+ };
43
+ child.stdout.setEncoding('utf8');
44
+ child.stderr.setEncoding('utf8');
45
+ child.stdout.on('data', chunk => { stdout = append(stdout, chunk); });
46
+ child.stderr.on('data', chunk => { stderr = append(stderr, chunk); });
47
+ const signalGroup = signal => {
48
+ try { process.kill(process.platform === 'win32' ? child.pid : -child.pid, signal); } catch { /* already gone */ }
49
+ };
50
+ const groupAlive = () => {
51
+ try { process.kill(process.platform === 'win32' ? child.pid : -child.pid, 0); return true; } catch { return false; }
52
+ };
53
+ const timer = setTimeout(() => { timedOut = true; signalGroup('SIGTERM'); }, timeoutMs);
54
+ const escalation = setTimeout(() => signalGroup('SIGKILL'), timeoutMs + 2_000);
55
+ child.on('error', error => { clearTimeout(timer); clearTimeout(escalation); rejectRun(error); });
56
+ child.on('close', async code => {
57
+ clearTimeout(timer);
58
+ clearTimeout(escalation);
59
+ signalGroup('SIGTERM');
60
+ await new Promise(done => setTimeout(done, 500));
61
+ if (groupAlive()) signalGroup('SIGKILL');
62
+ await new Promise(done => setTimeout(done, 100));
63
+ resolveRun({ code, stdout, stderr, truncated, timedOut, processGroupGone: !groupAlive() });
64
+ });
65
+ });
66
+ }
67
+
68
+ function framesOf(stdout) {
69
+ return stdout.split('\n').flatMap(line => {
70
+ try { return [JSON.parse(line)]; } catch { return []; }
71
+ });
72
+ }
73
+
74
+ function toolKindsOf(frames) {
75
+ return [...new Set(frames.flatMap(frame => frame.type === 'tool_call'
76
+ ? Object.keys(frame.tool_call ?? {}).filter(key => key.endsWith('ToolCall')) : []))].sort();
77
+ }
78
+
79
+ function resultOf(frames) {
80
+ return frames.findLast(frame => frame.type === 'result');
81
+ }
82
+
83
+ async function probeNativeQuestion(version) {
84
+ const run = await runAgent([
85
+ '-p', '--force', '--trust', '--output-format', 'stream-json',
86
+ '--model', values.model, '--allowed-tools', 'ask_question_tool_call',
87
+ 'Use the native AskQuestion tool now. Ask me to choose Alpha or Beta. Do not answer it yourself and do not use any other tool.',
88
+ ], checkout, 45_000);
89
+
90
+ const frames = framesOf(run.stdout);
91
+ const request = frames.find(frame => frame.type === 'interaction_query' && frame.subtype === 'request' && frame.query_type === 'askQuestionInteractionQuery');
92
+ const response = frames.find(frame => frame.type === 'interaction_query' && frame.subtype === 'response' && frame.query_type === 'askQuestionInteractionQuery');
93
+ const rejection = response?.response?.askQuestionInteractionResponse?.result?.rejected?.reason;
94
+ return {
95
+ schema: 1,
96
+ case: 'native-question',
97
+ cliVersion: version,
98
+ model: values.model,
99
+ invocation: ['-p', '--force', '--trust', '--output-format stream-json', '--allowed-tools ask_question_tool_call'],
100
+ exitCode: run.code,
101
+ timedOut: run.timedOut,
102
+ outputTruncated: run.truncated,
103
+ nativeQuestionRequested: Boolean(request),
104
+ nativeQuestionAnswered: Boolean(response),
105
+ responseKind: rejection ? 'rejected' : response ? 'other' : 'none',
106
+ rejectionKind: typeof rejection === 'string' && rejection.includes('skipped') ? 'skipped' : rejection ? 'other' : 'none',
107
+ sameSession: Boolean(request && response && request.session_id === response.session_id),
108
+ responseDelayMs: request && response ? response.timestamp_ms - request.timestamp_ms : null,
109
+ outcome: run.code === 0 && !run.timedOut && !run.truncated && request && response && request.session_id === response.session_id && rejection?.includes('skipped')
110
+ ? 'blocked' : request && response ? 'unqualified' : 'inconclusive',
111
+ };
112
+ }
113
+
114
+ const nativeDelegationEntries = [
115
+ 'task_tool_call', 'create_agent_tool_call', 'adopt_tool_call',
116
+ 'send_to_agent_tool_call', 'send_message_tool_call',
117
+ 'get_agent_status_tool_call', 'stop_agent_tool_call',
118
+ 'read_agent_transcript_tool_call', 'start_grind_execution_tool_call',
119
+ 'start_grind_planning_tool_call',
120
+ ];
121
+
122
+ async function discoverSafeAllowedTools() {
123
+ // Cursor validates this public proto-oneof list before inference. The name
124
+ // is deliberately invalid; no model or native worker can run in this step.
125
+ const catalogRun = await runAgent([
126
+ '-p', '--force', '--trust', '--output-format', 'stream-json',
127
+ '--model', values.model, '--allowed-tools', 'definitelyNotATool', 'Reply OK.',
128
+ ], checkout, 15_000);
129
+ const names = catalogRun.stderr.match(/Expected one of: ([a-z_, ]+)/)?.[1]
130
+ ?.split(',').map(value => value.trim()).filter(Boolean) ?? [];
131
+ const safeAllowedTools = names.filter(name => !nativeDelegationEntries.includes(name));
132
+ return { catalogRun, names, safeAllowedTools };
133
+ }
134
+
135
+ async function probeDelegation(version) {
136
+ const { catalogRun, names, safeAllowedTools } = await discoverSafeAllowedTools();
137
+
138
+ const firstCanary = randomBytes(16).toString('hex');
139
+ writeFileSync(join(checkout, 'probe-first.txt'), `${firstCanary}\n`, { mode: 0o600 });
140
+ const prompt = name => `Use the native Read tool to read ${name}, then reply with its exact contents. Do not use any other tool.`;
141
+ const first = await runAgent([
142
+ '-p', '--force', '--trust', '--output-format', 'stream-json',
143
+ '--model', values.model, '--allowed-tools', 'read_tool_call', prompt('probe-first.txt'),
144
+ ], checkout, 45_000);
145
+ const firstFrames = framesOf(first.stdout);
146
+ const firstId = resultOf(firstFrames)?.session_id;
147
+
148
+ const secondCanary = randomBytes(16).toString('hex');
149
+ writeFileSync(join(checkout, 'probe-second.txt'), `${secondCanary}\n`, { mode: 0o600 });
150
+ const second = firstId ? await runAgent([
151
+ '-p', '--force', '--trust', '--output-format', 'stream-json',
152
+ '--model', values.model, '--resume', firstId,
153
+ '--allowed-tools', 'read_todos_tool_call', prompt('probe-second.txt'),
154
+ ], checkout, 45_000) : undefined;
155
+ const secondFrames = second ? framesOf(second.stdout) : [];
156
+
157
+ const thirdCanary = randomBytes(16).toString('hex');
158
+ writeFileSync(join(checkout, 'probe-third.txt'), `${thirdCanary}\n`, { mode: 0o600 });
159
+ const third = firstId && second ? await runAgent([
160
+ '-p', '--force', '--trust', '--output-format', 'stream-json',
161
+ '--model', values.model, '--resume', firstId,
162
+ '--allowed-tools', safeAllowedTools.join(','), prompt('probe-third.txt'),
163
+ ], checkout, 45_000) : undefined;
164
+ const thirdFrames = third ? framesOf(third.stdout) : [];
165
+
166
+ const firstTools = toolKindsOf(firstFrames);
167
+ const secondTools = toolKindsOf(secondFrames);
168
+ const firstSawCanary = String(resultOf(firstFrames)?.result ?? '').includes(firstCanary);
169
+ const secondSawCanary = String(resultOf(secondFrames)?.result ?? '').includes(secondCanary);
170
+ const thirdTools = toolKindsOf(thirdFrames);
171
+ const thirdSawCanary = String(resultOf(thirdFrames)?.result ?? '').includes(thirdCanary);
172
+ const sameSession = Boolean(firstId && firstId === resultOf(secondFrames)?.session_id
173
+ && firstId === resultOf(thirdFrames)?.session_id);
174
+ const noDelegationEntriesInAllowlist = nativeDelegationEntries.every(name => !safeAllowedTools.includes(name));
175
+ const genericFilterVerified = catalogRun.code === 1 && !catalogRun.timedOut
176
+ && names.includes('task_tool_call') && names.includes('create_agent_tool_call')
177
+ && nativeDelegationEntries.every(name => names.includes(name)) && noDelegationEntriesInAllowlist
178
+ && first.code === 0 && !first.timedOut && !first.truncated
179
+ && firstTools.includes('readToolCall') && firstSawCanary
180
+ && second?.code === 0 && !second.timedOut && !second.truncated
181
+ && !secondTools.includes('readToolCall') && !secondSawCanary
182
+ && third?.code === 0 && !third.timedOut && !third.truncated
183
+ && thirdTools.includes('readToolCall') && thirdSawCanary && sameSession;
184
+ return {
185
+ schema: 1,
186
+ case: 'delegation',
187
+ cliVersion: version,
188
+ model: values.model,
189
+ testedMechanism: '--allowed-tools',
190
+ catalogValidatedBeforeInference: catalogRun.code === 1 && names.includes('task_tool_call'),
191
+ nativeDelegationEntries,
192
+ safeAllowedTools,
193
+ freshReadToolCalled: firstTools.includes('readToolCall'),
194
+ freshReadSucceeded: firstSawCanary,
195
+ resumedReadToolCalled: secondTools.includes('readToolCall'),
196
+ resumedReadSucceeded: secondSawCanary,
197
+ fullAllowlistReadToolCalled: thirdTools.includes('readToolCall'),
198
+ fullAllowlistReadSucceeded: thirdSawCanary,
199
+ noDelegationEntriesInAllowlist,
200
+ sameNativeSession: sameSession,
201
+ directTaskCallAttempted: false,
202
+ outcome: genericFilterVerified ? 'pass' : 'blocked',
203
+ };
204
+ }
205
+
206
+ async function probeMcp(version) {
207
+ const { catalogRun, names, safeAllowedTools } = await discoverSafeAllowedTools();
208
+ const pluginDir = join(checkout, 'cez-probe-plugin');
209
+ mkdirSync(pluginDir);
210
+ writeFileSync(join(pluginDir, 'plugin.json'), JSON.stringify({ name: 'cez-probe-plugin', version: '0.0.1', description: 'Inert local qualification plugin' }));
211
+ writeFileSync(join(pluginDir, 'mcp.json'), JSON.stringify({ mcpServers: { 'cez-probe': {
212
+ type: 'stdio', command: 'node', args: [join(pluginDir, 'server.mjs')],
213
+ env: { CEZ_PROBE_CANARY: '${CEZ_PROBE_CANARY}' },
214
+ } } }));
215
+ writeFileSync(join(pluginDir, 'server.mjs'), `
216
+ import { createHash } from 'node:crypto';
217
+ import { writeFileSync } from 'node:fs';
218
+ writeFileSync(new URL('./started', import.meta.url), '1');
219
+ let buffer = '';
220
+ process.stdin.setEncoding('utf8');
221
+ process.stdin.on('data', chunk => {
222
+ buffer += chunk;
223
+ while (buffer.includes('\\n')) {
224
+ const end = buffer.indexOf('\\n');
225
+ const line = buffer.slice(0, end);
226
+ buffer = buffer.slice(end + 1);
227
+ let request;
228
+ try { request = JSON.parse(line); } catch { continue; }
229
+ if (request.id === undefined) continue;
230
+ let result = {};
231
+ if (request.method === 'initialize') result = {
232
+ protocolVersion: request.params?.protocolVersion || '2024-11-05',
233
+ capabilities: { tools: {} }, serverInfo: { name: 'cez-probe', version: '0.0.1' },
234
+ };
235
+ if (request.method === 'tools/list') result = { tools: [{
236
+ name: 'check_env', description: 'Return a hash of an inherited synthetic environment value',
237
+ inputSchema: { type: 'object', properties: {}, additionalProperties: false },
238
+ }] };
239
+ if (request.method === 'tools/call') {
240
+ writeFileSync(new URL('./called', import.meta.url), '1');
241
+ result = { content: [{ type: 'text', text: 'env-hash:' + createHash('sha256').update(process.env.CEZ_PROBE_CANARY || '').digest('hex') }], isError: false };
242
+ }
243
+ process.stdout.write(JSON.stringify({ jsonrpc: '2.0', id: request.id, result }) + '\\n');
244
+ }
245
+ });
246
+ `);
247
+
248
+ const runTurn = async (id) => {
249
+ const canary = randomBytes(16).toString('hex');
250
+ const expectedHash = createHash('sha256').update(canary).digest('hex');
251
+ rmSync(join(pluginDir, 'called'), { force: true });
252
+ const run = await runAgent([
253
+ '-p', '--force', '--trust', '--output-format', 'stream-json', '--model', values.model,
254
+ ...(id ? ['--resume', id] : []), '--allowed-tools', safeAllowedTools.join(','),
255
+ '--plugin-dir', pluginDir,
256
+ id ? 'The synthetic environment value changed since the last turn. Call cez-probe check_env again now; the previous hash is stale. Report only the new hash. Do not use another tool.'
257
+ : 'Call the cez-probe MCP check_env tool once and report its hash. Do not use any other tool.',
258
+ ], checkout, 45_000, { ...process.env, CEZ_PROBE_CANARY: canary });
259
+ const frames = framesOf(run.stdout);
260
+ return {
261
+ id: resultOf(frames)?.session_id,
262
+ ok: run.code === 0 && !run.timedOut && !run.truncated && existsSync(join(pluginDir, 'called'))
263
+ && run.stdout.includes(expectedHash) && toolKindsOf(frames).includes('mcpToolCall'),
264
+ mcpToolCalled: toolKindsOf(frames).includes('mcpToolCall'),
265
+ serverCalled: existsSync(join(pluginDir, 'called')),
266
+ environmentHashSeen: run.stdout.includes(expectedHash),
267
+ stderrMentionsApproval: /approv/i.test(run.stderr),
268
+ };
269
+ };
270
+ const first = await runTurn();
271
+ const second = first.id ? await runTurn(first.id) : undefined;
272
+ const catalogSafe = catalogRun.code === 1 && names.includes('mcp_tool_call')
273
+ && nativeDelegationEntries.every(name => names.includes(name) && !safeAllowedTools.includes(name));
274
+ return {
275
+ schema: 1, case: 'mcp', cliVersion: version, model: values.model,
276
+ invocation: ['-p', '--force', '--trust', '--output-format stream-json', '--allowed-tools <non-agent catalog>', '--plugin-dir <disposable local plugin>', '--resume <exact native id>'],
277
+ environmentBinding: 'MCP env placeholder expanded from per-process environment',
278
+ globalCursorConfigWritten: false, blanketMcpApprovalUsed: false,
279
+ catalogSafe, freshToolCalled: first.mcpToolCalled, freshServerCalled: first.serverCalled,
280
+ freshEnvironmentHashSeen: first.environmentHashSeen,
281
+ resumedToolCalled: second?.mcpToolCalled ?? false, resumedServerCalled: second?.serverCalled ?? false,
282
+ resumedEnvironmentHashSeen: second?.environmentHashSeen ?? false,
283
+ sameNativeSession: Boolean(first.id && first.id === second?.id),
284
+ stderrMentionsApproval: first.stderrMentionsApproval || Boolean(second?.stderrMentionsApproval),
285
+ outcome: catalogSafe && first.ok && second?.ok && first.id === second.id
286
+ && !first.stderrMentionsApproval && !second.stderrMentionsApproval ? 'pass' : 'blocked',
287
+ };
288
+ }
289
+
290
+ async function probePortableAsk(version) {
291
+ const phrase = randomBytes(12).toString('hex');
292
+ const marker = 'CEZ:ASK {"questions":[{"header":"Library","question":"Which library?","multiSelect":false,"options":[{"label":"Vitest","description":"Use Vitest"},{"label":"Jest","description":"Use Jest"}]}]}';
293
+ const first = await runAgent([
294
+ '-p', '--force', '--trust', '--output-format', 'stream-json', '--model', values.model,
295
+ '--allowed-tools', 'read_todos_tool_call',
296
+ `Remember this blind phrase for the next turn: ${phrase}. Your entire answer to this turn must be exactly this one line, with no code fence or text before or after it:\n${marker}`,
297
+ ], checkout, 45_000);
298
+ const firstFrames = framesOf(first.stdout);
299
+ const firstResult = resultOf(firstFrames);
300
+ const firstText = String(firstResult?.result ?? '');
301
+ const askLine = firstText.split('\n').findLast(line => line.startsWith('CEZ:ASK '));
302
+ let markerValid = false;
303
+ try { markerValid = Boolean(JSON.parse(askLine?.slice('CEZ:ASK '.length) ?? '').questions?.length); } catch { /* invalid marker */ }
304
+ const second = firstResult?.session_id ? await runAgent([
305
+ '-p', '--force', '--trust', '--output-format', 'stream-json', '--model', values.model,
306
+ '--resume', firstResult.session_id, '--allowed-tools', 'read_todos_tool_call',
307
+ 'Library: Vitest. State the blind phrase from the previous turn, then say accepted.',
308
+ ], checkout, 45_000) : undefined;
309
+ const secondFrames = second ? framesOf(second.stdout) : [];
310
+ const secondResult = resultOf(secondFrames);
311
+ const sameNativeSession = Boolean(firstResult?.session_id && firstResult.session_id === secondResult?.session_id);
312
+ const phraseRecalled = String(secondResult?.result ?? '').includes(phrase);
313
+ return {
314
+ schema: 1, case: 'portable-ask', cliVersion: version, model: values.model,
315
+ invocation: ['-p', '--force', '--trust', '--output-format stream-json', '--allowed-tools read_todos_tool_call', '--resume <exact native id>'],
316
+ markerTokenSeen: firstText.includes('CEZ:ASK'), markerSeen: Boolean(askLine), markerValid,
317
+ firstResultTextPresent: firstText.length > 0, explicitReplyDelivered: Boolean(second),
318
+ phraseRecalled, sameNativeSession,
319
+ fullCezarAskRoundTrip: false,
320
+ outcome: first.code === 0 && !first.timedOut && !first.truncated && markerValid
321
+ && second?.code === 0 && !second.timedOut && !second.truncated
322
+ && phraseRecalled && sameNativeSession ? 'pass' : 'blocked',
323
+ };
324
+ }
325
+
326
+ async function probePlugins(version) {
327
+ // This project-local disable is a negative control. It never changes a
328
+ // user's Cursor installation, marketplace state or Claude settings.
329
+ const claudeSettings = join(checkout, '.claude');
330
+ mkdirSync(claudeSettings, { recursive: true });
331
+ writeFileSync(join(claudeSettings, 'settings.json'), JSON.stringify({ enabledPlugins: {
332
+ 'superpowers@apptension-dev': false,
333
+ 'superpowers@claude-plugins-official': false,
334
+ 'superpowers@apptension-toolkit-dev': false,
335
+ } }));
336
+ const pluginDir = join(checkout, 'cez-inert-plugin');
337
+ mkdirSync(pluginDir);
338
+ writeFileSync(join(pluginDir, 'plugin.json'), JSON.stringify({ name: 'cez-inert-plugin', version: '0.0.1', description: 'Inert qualification plugin' }));
339
+ const { catalogRun, names, safeAllowedTools } = await discoverSafeAllowedTools();
340
+ const run = await runAgent([
341
+ '-p', '--force', '--trust', '--output-format', 'stream-json', '--model', values.model,
342
+ '--allowed-tools', safeAllowedTools.join(','), '--plugin-dir', pluginDir,
343
+ 'From your loaded skills, read the Superpowers brainstorming SKILL.md and report its first heading. Do not search the filesystem for a disabled skill.',
344
+ ], checkout, 45_000);
345
+ const frames = framesOf(run.stdout);
346
+ const reads = frames.filter(frame => frame.type === 'tool_call' && frame.tool_call?.readToolCall)
347
+ .map(frame => JSON.stringify(frame.tool_call.readToolCall));
348
+ const marketplaceSkillRead = reads.some(read => /\.cursor\/plugins\/cache\/.*superpowers.*SKILL\.md/i.test(read));
349
+ const claudeSkillRead = reads.some(read => /\.claude\/plugins\/.*superpowers.*SKILL\.md/i.test(read));
350
+ return {
351
+ schema: 1, case: 'plugins', cliVersion: version, model: values.model,
352
+ invocation: ['-p', '--force', '--trust', '--output-format stream-json', '--allowed-tools <non-agent catalog>', '--plugin-dir <disposable inert plugin>'],
353
+ projectSettings: '.claude/settings.json enabledPlugins=false for known Superpowers IDs in disposable worktree',
354
+ globalConfigurationWritten: false,
355
+ catalogValidatedBeforeInference: catalogRun.code === 1 && names.includes('read_tool_call'),
356
+ readToolFrames: reads.length, marketplaceSkillRead, claudeSkillRead,
357
+ outcome: run.code === 0 && !run.timedOut && !run.truncated && marketplaceSkillRead
358
+ ? 'blocked' : 'inconclusive',
359
+ };
360
+ }
361
+
362
+ async function probeResumeMissing(version) {
363
+ const nonexistentId = randomUUID();
364
+ const run = await runAgent([
365
+ '-p', '--force', '--trust', '--output-format', 'stream-json', '--model', values.model,
366
+ '--resume', nonexistentId, '--allowed-tools', 'read_todos_tool_call',
367
+ 'What exact phrase did I ask you to remember in the previous turn of this chat? If this chat has no previous turn, answer NO_PRIOR_TURN.',
368
+ ], checkout, 45_000);
369
+ const frames = framesOf(run.stdout);
370
+ const result = resultOf(frames);
371
+ const successWithRequestedId = run.code === 0 && result?.subtype === 'success'
372
+ && result.session_id === nonexistentId;
373
+ const noPriorTurn = String(result?.result ?? '').includes('NO_PRIOR_TURN');
374
+ return {
375
+ schema: 1, case: 'resume-missing', cliVersion: version, model: values.model,
376
+ invocation: ['-p', '--force', '--trust', '--output-format stream-json', '--resume <random never-seen UUID>', '--allowed-tools read_todos_tool_call'],
377
+ exitCode: run.code, timedOut: run.timedOut, outputTruncated: run.truncated,
378
+ successWithRequestedId, noPriorTurn,
379
+ missingSessionError: /not found|missing|resume|session/i.test(run.stderr),
380
+ outcome: !run.timedOut && !run.truncated && successWithRequestedId && noPriorTurn
381
+ ? 'blocked' : run.code !== 0 && !result ? 'pass' : 'inconclusive',
382
+ };
383
+ }
384
+
385
+ function writeStripedPng(path) {
386
+ // A small randomized red/blue control, generated locally and never retained.
387
+ const colors = Math.random() < 0.5
388
+ ? [[255, 0, 0], [0, 0, 255]] : [[0, 0, 255], [255, 0, 0]];
389
+ const width = 40;
390
+ const height = 20;
391
+ const raw = Buffer.concat(Array.from({ length: height }, () => Buffer.from([
392
+ 0, ...Array.from({ length: width }, (_, x) => colors[x < width / 2 ? 0 : 1]).flat(),
393
+ ])));
394
+ const crc = bytes => {
395
+ let value = 0xffffffff;
396
+ for (const byte of bytes) {
397
+ value ^= byte;
398
+ for (let bit = 0; bit < 8; bit++) value = (value >>> 1) ^ (value & 1 ? 0xedb88320 : 0);
399
+ }
400
+ return (value ^ 0xffffffff) >>> 0;
401
+ };
402
+ const chunk = (type, data) => {
403
+ const label = Buffer.from(type);
404
+ const size = Buffer.alloc(4);
405
+ size.writeUInt32BE(data.length);
406
+ const checksum = Buffer.alloc(4);
407
+ checksum.writeUInt32BE(crc(Buffer.concat([label, data])));
408
+ return Buffer.concat([size, label, data, checksum]);
409
+ };
410
+ const header = Buffer.alloc(13);
411
+ header.writeUInt32BE(width, 0);
412
+ header.writeUInt32BE(height, 4);
413
+ header[8] = 8;
414
+ header[9] = 2;
415
+ writeFileSync(path, Buffer.concat([
416
+ Buffer.from('89504e470d0a1a0a', 'hex'), chunk('IHDR', header),
417
+ chunk('IDAT', deflateSync(raw)), chunk('IEND', Buffer.alloc(0)),
418
+ ]));
419
+ return colors[0][0] === 255 ? 'RED_BLUE' : 'BLUE_RED';
420
+ }
421
+
422
+ async function probeFields(version) {
423
+ const modelListing = await runAgent(['--list-models'], checkout, 15_000);
424
+ const advertisedModel = modelListing.code === 0
425
+ && modelListing.stdout.split('\n').some(line => line.startsWith(`${values.model} - `));
426
+ const lowEffortModel = values.model.replace(/-medium$/, '-low');
427
+ const lowEffortAdvertised = lowEffortModel !== values.model && modelListing.stdout.split('\n')
428
+ .some(line => line.startsWith(`${lowEffortModel} - `));
429
+ const lowEffort = lowEffortAdvertised ? await runAgent([
430
+ '-p', '--force', '--trust', '--output-format', 'stream-json',
431
+ '--model', lowEffortModel, '--allowed-tools', 'read_todos_tool_call', 'Reply OK.',
432
+ ], checkout, 45_000) : undefined;
433
+ const invalid = await runAgent([
434
+ '-p', '--force', '--trust', '--output-format', 'stream-json',
435
+ '--model', 'cez-nonexistent-model-pin', 'Reply OK.',
436
+ ], checkout, 15_000);
437
+ const invalidRejectedBeforeInference = invalid.code !== 0
438
+ && !framesOf(invalid.stdout).some(frame => frame.type === 'assistant' || frame.type === 'result');
439
+
440
+ const extraRoot = join(scratch, 'extra-root');
441
+ mkdirSync(extraRoot);
442
+ const canary = randomBytes(12).toString('hex');
443
+ writeFileSync(join(extraRoot, 'root-canary.txt'), canary, { mode: 0o600 });
444
+ const image = join(checkout, 'stripes.png');
445
+ const colorOrder = writeStripedPng(image);
446
+ const args = [
447
+ '-p', '--force', '--trust', '--output-format', 'stream-json',
448
+ '--model', values.model, '--allowed-tools', 'read_tool_call',
449
+ '--add-dir', extraRoot, '--image', image,
450
+ ];
451
+ const first = await runAgent([...args,
452
+ `Read ${join(extraRoot, 'root-canary.txt')} with the native Read tool. Then describe the image's colors from left to right as RED_BLUE or BLUE_RED. Reply with the file contents and that code only.`,
453
+ ], checkout, 45_000);
454
+ const firstFrames = framesOf(first.stdout);
455
+ const firstResult = resultOf(firstFrames);
456
+ const firstId = firstResult?.session_id;
457
+ const second = firstId ? await runAgent([
458
+ '-p', '--force', '--trust', '--output-format', 'stream-json', '--model', values.model,
459
+ '--resume', firstId, '--allowed-tools', 'read_todos_tool_call',
460
+ 'What was the image color-order code from the previous turn? Reply with just that code.',
461
+ ], checkout, 45_000) : undefined;
462
+ const secondResult = second ? resultOf(framesOf(second.stdout)) : undefined;
463
+ const rootRead = firstFrames.some(frame => frame.type === 'tool_call' && frame.tool_call?.readToolCall);
464
+ const readCall = firstFrames.find(frame => frame.type === 'tool_call' && frame.subtype === 'completed'
465
+ && frame.tool_call?.readToolCall)?.tool_call.readToolCall;
466
+ const text = String(firstResult?.result ?? '');
467
+ return {
468
+ schema: 1, case: 'fields', cliVersion: version, model: values.model,
469
+ invocation: ['-p', '--force', '--trust', '--output-format stream-json', '--model <advertised ID>', '--allowed-tools read_tool_call', '--add-dir <disposable root>', '--image <disposable PNG>', '--resume <exact native ID>'],
470
+ advertisedModel, invalidRejectedBeforeInference,
471
+ advertisedEffortVariant: lowEffortAdvertised,
472
+ advertisedEffortVariantAdmitted: lowEffort?.code === 0 && resultOf(framesOf(lowEffort.stdout))?.subtype === 'success',
473
+ extraRootToolRead: rootRead, extraRootCanarySeen: text.includes(canary),
474
+ readArgKeys: readCall?.args && typeof readCall.args === 'object' ? Object.keys(readCall.args).sort() : [],
475
+ readResultKeys: readCall?.result && typeof readCall.result === 'object' ? Object.keys(readCall.result).sort() : [],
476
+ imageOrderSeen: text.includes(colorOrder),
477
+ sameNativeSession: Boolean(firstId && secondResult?.session_id === firstId),
478
+ currentInstructionFollowedOnResume: String(secondResult?.result ?? '').trim() === colorOrder,
479
+ freshExitCode: first.code, resumedExitCode: second?.code ?? null,
480
+ timedOut: first.timedOut || Boolean(second?.timedOut),
481
+ outputTruncated: first.truncated || Boolean(second?.truncated),
482
+ outcome: advertisedModel && lowEffortAdvertised && lowEffort?.code === 0
483
+ && resultOf(framesOf(lowEffort.stdout))?.subtype === 'success'
484
+ && invalidRejectedBeforeInference && first.code === 0
485
+ && second?.code === 0 && rootRead && text.includes(canary)
486
+ && text.includes(colorOrder) && secondResult?.session_id === firstId
487
+ && String(secondResult?.result ?? '').trim() === colorOrder ? 'pass' : 'blocked',
488
+ };
489
+ }
490
+
491
+ async function probeErrors(version) {
492
+ const invalidKey = `cez-invalid-${randomBytes(16).toString('hex')}`;
493
+ const auth = await runAgent([
494
+ '-p', '--force', '--trust', '--output-format', 'stream-json',
495
+ '--model', values.model, '--allowed-tools', 'read_todos_tool_call', 'Reply OK.',
496
+ ], checkout, 20_000, { ...process.env, CURSOR_API_KEY: invalidKey });
497
+ const authFrames = framesOf(auth.stdout);
498
+ const authFailedWithoutSuccess = auth.code !== 0
499
+ && !authFrames.some(frame => frame.type === 'result' && frame.subtype === 'success');
500
+ const timeout = await runAgent([
501
+ '-p', '--force', '--trust', '--output-format', 'stream-json',
502
+ '--model', values.model, '--allowed-tools', 'read_todos_tool_call', 'Reply OK.',
503
+ ], checkout, 10);
504
+ return {
505
+ schema: 1, case: 'errors', cliVersion: version, model: values.model,
506
+ invocation: ['-p', '--force', '--trust', '--output-format stream-json', '--allowed-tools read_todos_tool_call', 'invalid synthetic CURSOR_API_KEY in process env', '10 ms timeout in disposable worktree'],
507
+ invalidKeyNotEchoed: !auth.stdout.includes(invalidKey) && !auth.stderr.includes(invalidKey),
508
+ invalidKeyRejected: authFailedWithoutSuccess,
509
+ noFallbackToLoggedInAccount: authFailedWithoutSuccess,
510
+ authProcessGroupGone: auth.processGroupGone,
511
+ timeoutTriggered: timeout.timedOut,
512
+ timeoutProcessGroupGone: timeout.processGroupGone,
513
+ outcome: authFailedWithoutSuccess && !auth.stdout.includes(invalidKey)
514
+ && !auth.stderr.includes(invalidKey) && auth.processGroupGone
515
+ && timeout.timedOut && timeout.processGroupGone ? 'pass' : 'blocked',
516
+ };
517
+ }
518
+
519
+ async function probePluginLifecycle(version) {
520
+ const { catalogRun, names, safeAllowedTools } = await discoverSafeAllowedTools();
521
+ const pluginDir = join(checkout, 'cez-print-qualification-plugin');
522
+ const manifestDir = join(pluginDir, '.cursor-plugin');
523
+ const hooksDir = join(pluginDir, 'hooks');
524
+ const skillDir = join(pluginDir, 'skills', 'print-qualification');
525
+ mkdirSync(manifestDir, { recursive: true });
526
+ mkdirSync(hooksDir);
527
+ mkdirSync(skillDir, { recursive: true });
528
+ const heading = `# Print qualification ${randomBytes(6).toString('hex')}`;
529
+ const hookPhrase = randomBytes(12).toString('hex');
530
+ writeFileSync(join(skillDir, 'SKILL.md'), `${heading}\nA local test skill.\n`);
531
+ writeFileSync(join(manifestDir, 'plugin.json'), JSON.stringify({
532
+ name: 'cez-print-qualification', version: '0.0.1',
533
+ description: 'Disposable local plugin for fork 590 qualification',
534
+ skills: './skills/', hooks: './hooks/hooks.json',
535
+ }));
536
+ const hookOutput = join(hooksDir, 'started');
537
+ writeFileSync(join(hooksDir, 'start.mjs'), `import { appendFileSync } from 'node:fs';\nappendFileSync(${JSON.stringify(hookOutput)}, '1\\n');\nprocess.stdout.write(JSON.stringify({ additional_context: ${JSON.stringify(`Remember this hook phrase for later: ${hookPhrase}`)} }));\n`);
538
+ writeFileSync(join(hooksDir, 'hooks.json'), JSON.stringify({
539
+ version: 1, hooks: { sessionStart: [{ command: `node ${join(hooksDir, 'start.mjs')}` }] },
540
+ }));
541
+ const hookCount = () => existsSync(hookOutput) ? readFileSync(hookOutput, 'utf8').split('\n').filter(Boolean).length : 0;
542
+ const base = ['-p', '--force', '--trust', '--output-format', 'stream-json',
543
+ '--model', values.model, '--allowed-tools', safeAllowedTools.join(',')];
544
+ const first = await runAgent([...base, '--plugin-dir', pluginDir,
545
+ 'Use your loaded print-qualification skill. Read its SKILL.md with the native Read tool and report the first heading exactly.',
546
+ ], checkout, 45_000);
547
+ const firstFrames = framesOf(first.stdout);
548
+ const firstId = resultOf(firstFrames)?.session_id;
549
+ const firstHookCount = hookCount();
550
+ const localSkillRead = firstFrames.some(frame => frame.type === 'tool_call'
551
+ && frame.tool_call?.readToolCall && JSON.stringify(frame.tool_call.readToolCall).includes('print-qualification/SKILL.md'));
552
+ const second = firstId ? await runAgent([...base, '--resume', firstId, '--plugin-dir', pluginDir,
553
+ 'From your enabled marketplace plugins, read the Superpowers brainstorming SKILL.md with the native Read tool. Then report its first heading and the hook phrase injected at the start of this session.',
554
+ ], checkout, 45_000) : undefined;
555
+ const secondFrames = second ? framesOf(second.stdout) : [];
556
+ const secondId = resultOf(secondFrames)?.session_id;
557
+ const secondHookCount = hookCount();
558
+ const marketplaceSkillRead = secondFrames.some(frame => frame.type === 'tool_call'
559
+ && frame.tool_call?.readToolCall
560
+ && /\.cursor\/plugins\/cache\/.*superpowers.*SKILL\.md/i.test(JSON.stringify(frame.tool_call.readToolCall)));
561
+ rmSync(pluginDir, { recursive: true, force: true });
562
+ const absent = await runAgent([...base,
563
+ 'If a loaded skill named print-qualification is available, read its SKILL.md. Otherwise reply ABSENT. Do not search the filesystem.',
564
+ ], checkout, 45_000);
565
+ const absentFrames = framesOf(absent.stdout);
566
+ const absentLocalRead = absentFrames.some(frame => frame.type === 'tool_call'
567
+ && frame.tool_call?.readToolCall && JSON.stringify(frame.tool_call.readToolCall).includes('print-qualification/SKILL.md'));
568
+ return {
569
+ schema: 1, case: 'plugin-lifecycle', cliVersion: version, model: values.model,
570
+ invocation: ['-p', '--force', '--trust', '--output-format stream-json', '--allowed-tools <non-agent catalog>', '--plugin-dir <disposable local plugin>', '--resume <exact native ID>'],
571
+ catalogValidatedBeforeInference: catalogRun.code === 1 && names.includes('read_tool_call'),
572
+ localSkillRead, localHeadingSeen: String(resultOf(firstFrames)?.result ?? '').includes(heading),
573
+ marketplaceSkillRead, sameNativeSession: Boolean(firstId && secondId === firstId),
574
+ freshHookRan: firstHookCount > 0, resumedHookRan: secondHookCount > firstHookCount,
575
+ hookContextRecalledOnResume: String(resultOf(secondFrames)?.result ?? '').includes(hookPhrase),
576
+ absentLocalSkillRead: absentLocalRead,
577
+ absentModelReportedMissing: String(resultOf(absentFrames)?.result ?? '').includes('ABSENT'),
578
+ globalConfigurationWritten: false,
579
+ outcome: catalogRun.code === 1 && first.code === 0 && second?.code === 0 && absent.code === 0
580
+ && localSkillRead && String(resultOf(firstFrames)?.result ?? '').includes(heading)
581
+ && marketplaceSkillRead && firstId === secondId && firstHookCount > 0
582
+ && String(resultOf(secondFrames)?.result ?? '').includes(hookPhrase)
583
+ && !absentLocalRead
584
+ && String(resultOf(absentFrames)?.result ?? '').includes('ABSENT') ? 'pass' : 'blocked',
585
+ };
586
+ }
587
+
588
+ let added = false;
589
+ let cleaned = true;
590
+ try {
591
+ const version = await runAgent(['--version'], process.cwd(), 10_000);
592
+ if (version.code !== 0 || version.timedOut) throw new Error('Cursor version check failed');
593
+ git(['worktree', 'add', '--quiet', '--detach', checkout, 'HEAD']);
594
+ added = true;
595
+
596
+ const probes = { 'native-question': probeNativeQuestion, delegation: probeDelegation, mcp: probeMcp, 'portable-ask': probePortableAsk, plugins: probePlugins, 'resume-missing': probeResumeMissing, fields: probeFields, 'plugin-lifecycle': probePluginLifecycle, errors: probeErrors };
597
+ const summary = await probes[values.case](version.stdout.trim());
598
+ mkdirSync(outputDir, { recursive: true });
599
+ const output = join(outputDir, `print-${values.case}.json`);
600
+ writeFileSync(output, `${JSON.stringify(summary, null, 2)}\n`, { mode: 0o600 });
601
+ process.stdout.write(`${output}\n`);
602
+ process.exitCode = summary.outcome === 'pass' ? 0 : summary.outcome === 'blocked' ? 2 : 1;
603
+ } catch (error) {
604
+ process.stderr.write(`Probe failed: ${error instanceof Error ? error.message : 'unknown error'}\n`);
605
+ process.exitCode = 1;
606
+ } finally {
607
+ if (added) {
608
+ const removed = spawnSync('git', ['worktree', 'remove', '--force', checkout], { encoding: 'utf8', timeout: 15_000 });
609
+ if (removed.status !== 0) {
610
+ process.stderr.write(`Probe cleanup failed; inspect ${checkout}\n`);
611
+ process.exitCode = 1;
612
+ cleaned = false;
613
+ }
614
+ }
615
+ if (cleaned) rmSync(scratch, { recursive: true, force: true });
616
+ }
@@ -1 +1 @@
1
- import{Ct as e,E as t}from"./alert-dialog-BrpNUwMt.js";function n(){let n=t(),r=e(),i=(n.data?.profiles??[]).map(e=>({provider:e.provider,id:e.id,label:e.label,configDir:e.configDir})),a=r.data?.info?.root;return{accounts:i,repoAccount:a?n.data?.selections?.[a]:void 0}}function r(e,t){return e.filter(e=>e.provider===t).length>1}export{n,r as t};
1
+ import{Ct as e,E as t}from"./alert-dialog-A0-kEfJK.js";function n(){let n=t(),r=e(),i=(n.data?.profiles??[]).map(e=>({provider:e.provider,id:e.id,label:e.label,configDir:e.configDir})),a=r.data?.info?.root;return{accounts:i,repoAccount:a?n.data?.selections?.[a]:void 0}}function r(e,t){return e.filter(e=>e.provider===t).length>1}export{n,r as t};