perchai-cli 2.4.48 → 2.4.49
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.
- package/dist/perch.mjs +1357 -672
- package/package.json +1 -1
package/dist/perch.mjs
CHANGED
|
@@ -75918,7 +75918,6 @@ function isTurnAbortedError(error) {
|
|
|
75918
75918
|
var TURN_STOPPED_BY_USER_MESSAGE;
|
|
75919
75919
|
var init_turnAbort = __esm({
|
|
75920
75920
|
"features/perchTerminal/runtime/turnAbort.ts"() {
|
|
75921
|
-
"use strict";
|
|
75922
75921
|
TURN_STOPPED_BY_USER_MESSAGE = "Turn stopped by user.";
|
|
75923
75922
|
}
|
|
75924
75923
|
});
|
|
@@ -76224,7 +76223,6 @@ function getToolDisplayName(toolName) {
|
|
|
76224
76223
|
var NON_MODULE_TOOL_OWNERS, TOOL_RISK, TOOL_DISPLAY_NAMES;
|
|
76225
76224
|
var init_catalog = __esm({
|
|
76226
76225
|
"features/perchTerminal/runtime/toolSystem/catalog.ts"() {
|
|
76227
|
-
"use strict";
|
|
76228
76226
|
init_toolNames();
|
|
76229
76227
|
NON_MODULE_TOOL_OWNERS = {
|
|
76230
76228
|
[TOOL_NAMES.listSources]: "lane",
|
|
@@ -83486,6 +83484,8 @@ Browser Operator skill contract:
|
|
|
83486
83484
|
- Prefer browser_execute_task for short multi-step jobs: include the goal, requiredValues, successCriteria, allowSharingChange only when the user explicitly approved access changes, and a bounded steps array.
|
|
83487
83485
|
- Use semantic browser tools for ordinary controls with text, role, or aria labels.
|
|
83488
83486
|
- Use browser_visual_click or browser_focus_type for canvas, iframe, document-body, drag/drop, or targets that are visible but not exposed as DOM elements.
|
|
83487
|
+
- When a semantic click reports no DOM match, the runner auto-escalates to a coordinate/visual (CUA) click on the same target before failing. Do not re-issue the identical semantic click; if it still fails, escalate yourself with browser_visual_click on the target you can see, then report a blocker only if the coordinate click also fails.
|
|
83488
|
+
- For targets behind a menu, dropdown, or kebab/overflow (e.g. Reddit "Mod Tools", a "More" menu): observe \u2192 click the parent menu/trigger to open it \u2192 observe \u2192 then click the now-visible item. Do not click a hidden menu item directly; open its parent first.
|
|
83489
83489
|
- If coordinate tools are unavailable and the target is only visually reachable, do not loop empty semantic clicks; report the missing visual-action capability.
|
|
83490
83490
|
- For Google Docs: never inspect, open, or change the Viewing / Editing / Suggesting mode dropdown during normal writing or recovery. New Docs should already start editable. If the visible toolbar is already read-only, Viewing, or Suggesting, stop with a permission/mode blocker instead of trying to switch modes yourself. Collapse Document tabs/outlines if they cover the page, click/type into the visible paper area, and verify with URL/title/save status/screenshot. Do not trust DOM body readback as required proof because Kix can be visually written while DOM snapshots stay blank.
|
|
83491
83491
|
- Never claim completion from intent alone; require visible proof, save/sent status, URL, receipt, or screenshot evidence.
|
|
@@ -83591,17 +83591,20 @@ Rules:
|
|
|
83591
83591
|
name: "Browser Operator",
|
|
83592
83592
|
description: "Operates a logged-in browser turn-by-turn for Quill delivery and web tasks.",
|
|
83593
83593
|
// Browser work runs on the selected main model. Native vision models see
|
|
83594
|
-
// screenshots directly; text-only models
|
|
83595
|
-
//
|
|
83594
|
+
// screenshots directly; for text-only models the loop automatically runs each
|
|
83595
|
+
// post-action screenshot through the vision sidecar and injects the readout,
|
|
83596
|
+
// so the operator is always sighted. visionInspect({ currentScreen: true })
|
|
83597
|
+
// remains available for targeted follow-up questions.
|
|
83596
83598
|
lane: "chat",
|
|
83597
83599
|
systemPrompt: `You are Quill's Browser Operator. You operate the user's logged-in browser like a careful human.
|
|
83598
83600
|
|
|
83599
83601
|
Rules:
|
|
83600
83602
|
- Drive the browser by looking and acting on what you see by name. Never use remembered IDs, refs, CSS selectors, or raw Playwright tools.
|
|
83601
83603
|
- Before tool calls, send short live preambles so the user sees steady progress: 1-2 friendly sentences, usually 8-12 words. Group related actions. Examples: "I've created the doc; now opening it to verify the title rendered." / "Email's filled; sending and watching for the confirmation." Final summary stays concise, 10 lines or fewer.
|
|
83602
|
-
- Use browser_execute_task for short verified sequences, or browser_navigate, browser_observe, browser_click, browser_type, browser_visual_click, browser_focus_type, browser_press_key, browser_read, browser_wait, and browser_handle_dialog for one-off recovery steps. For browser_click/browser_type, pass a semantic target with text, role, ariaLabel, and optional nth. For browser_visual_click/browser_focus_type, pass
|
|
83604
|
+
- Use browser_execute_task for short verified sequences, or browser_navigate, browser_observe, browser_click, browser_type, browser_visual_click, browser_focus_type, browser_press_key, browser_read, browser_wait, and browser_handle_dialog for one-off recovery steps. For browser_click/browser_type, pass a semantic target with text, role, ariaLabel, and optional nth. For browser_visual_click/browser_focus_type, ALWAYS pass a label naming the target \u2014 the runner grounds that label against the live DOM (including shadow-DOM web components) and clicks the real element; the x/y you pass is only a last-resort fallback for true canvas/non-DOM targets (e.g. the Google Docs paper). Prefer naming the target over guessing coordinates.
|
|
83605
|
+
- To fill a field on a web-component/shadow-DOM site (e.g. Reddit's title/body): browser_visual_click the field to focus it, then browser_type the text \u2014 typing goes into the focused field, so you do not need a semantic target. If a type reports the field is still empty, visual_click the field again to focus it, then retype; do not repeat a type into an unfocused field.
|
|
83603
83606
|
- One action, then look at the result, then choose the next action. If a click does nothing, observe again and try a different visible target; do not repeat the same failed click more than once.
|
|
83604
|
-
- After each action, inspect the fresh browser result before deciding the next action. If you can see an attached screenshot, use it as the source of truth. If you
|
|
83607
|
+
- After each action, inspect the fresh browser result before deciding the next action. If you can see an attached screenshot, use it as the source of truth. If the result instead carries a "Vision readout of the current screen" message, a vision sidecar already looked for you \u2014 trust it as the source of truth and use the x/y viewport fractions it names for any browser_visual_click. Only call visionInspect with {"currentScreen": true, "prompt": "..."} when you need a more specific visual question answered.
|
|
83605
83608
|
- Safety: never click anything that grants access or changes sharing/permissions, such as "Share & send", "Share", or "Grant access". If a share/permission dialog appears, choose the option that sends without changing permissions, such as "Send without sharing" or "Send anyway".
|
|
83606
83609
|
- Do not claim completion unless the browser shows a confirmation, URL, saved state, sent state, or other visible receipt.
|
|
83607
83610
|
|
|
@@ -86236,7 +86239,8 @@ var init_desktopContextSection = __esm({
|
|
|
86236
86239
|
BROWSER_EXECUTION_GUIDANCE = `## Browser execution \u2014 logged-in web surface
|
|
86237
86240
|
Use the embedded browser as the logged-in execution surface for web apps, account-bound pages, SPAs, and recovery paths. Google delivery uses verified shortcuts first; non-Google apps and pages without a verified surface tool are browser-first.
|
|
86238
86241
|
Prefer browser_execute_task for short multi-step browser jobs because it carries the goal, requiredValues, successCriteria, sharing/mode flags, and enforced policy in one guarded runner. Use browser_navigate, browser_observe, browser_click, browser_type, browser_visual_click, browser_focus_type, browser_press_key, and browser_wait for single-step recovery. You already have these tools here; do not spawn browser_operator unless you are explicitly running a coordinator/suite workflow where workers own tools.
|
|
86239
|
-
Every browser step returns fresh snapshot text and perception fields. Vision-capable turns also receive one annotated screenshot; successful clicks include clickPoint/bounds so you can verify exactly what changed.
|
|
86242
|
+
Every browser step returns fresh snapshot text and perception fields. Vision-capable turns also receive one annotated screenshot; successful clicks include clickPoint/bounds so you can verify exactly what changed. On a text-only model the loop automatically runs each post-action screenshot through the vision sidecar and injects a "Vision readout of the current screen" message \u2014 trust it as the source of truth and use the x/y viewport fractions it names for any browser_visual_click instead of guessing coordinates.
|
|
86243
|
+
When a semantic browser_click finds no DOM match, the runner auto-escalates to a coordinate/visual (CUA) click on the same target before returning a failure, so the cursor still moves. Never re-issue the identical semantic click; if it still fails, escalate yourself with browser_visual_click on the visible target, and report a blocker only after the coordinate click also fails. browser_visual_click grounds its label against the live DOM (including shadow-DOM web components) and clicks the real element \u2014 always pass a label; the x/y is only a fallback for true canvas/non-DOM targets. To fill a field on a web-component/shadow-DOM site (e.g. Reddit), browser_visual_click the field to focus it then browser_type the text (typing goes into the focused field; no semantic target needed); if the type reports the field still empty, visual_click it again and retype. For a target behind a menu/dropdown/overflow (e.g. Reddit "Mod Tools"), observe \u2192 click the parent trigger to open the menu \u2192 observe \u2192 then click the now-visible item rather than clicking a hidden item.
|
|
86240
86244
|
If a verified tool returns needs_operator with a live URL/snapshot/screenshot, continue from that evidence with the browser primitives. If it reports the embedded browser/session is disconnected or unavailable, report that blocker instead of trying raw MCP/Playwright or claiming success. Do not restart the task or dispatch a separate browser worker.`;
|
|
86241
86245
|
}
|
|
86242
86246
|
});
|
|
@@ -87398,7 +87402,7 @@ var init_threadLedger = __esm({
|
|
|
87398
87402
|
});
|
|
87399
87403
|
|
|
87400
87404
|
// features/perchTerminal/runtime/perchMemoryGuidance.ts
|
|
87401
|
-
var VISIBLE_OUTPUT_STYLE_GUIDANCE, SAFFRON_STYLE_CONTRACT, QUILL_STYLE_CONTRACT, SAFFRON_CORE_IDENTITY, QUILL_CORE_IDENTITY, PERCH_MEMORY_GUIDANCE;
|
|
87405
|
+
var VISIBLE_OUTPUT_STYLE_GUIDANCE, PERCH_OPERATOR_CAPABILITIES, SAFFRON_STYLE_CONTRACT, QUILL_STYLE_CONTRACT, SAFFRON_CORE_IDENTITY, QUILL_CORE_IDENTITY, PERCH_MEMORY_GUIDANCE;
|
|
87402
87406
|
var init_perchMemoryGuidance = __esm({
|
|
87403
87407
|
"features/perchTerminal/runtime/perchMemoryGuidance.ts"() {
|
|
87404
87408
|
"use strict";
|
|
@@ -87413,6 +87417,36 @@ Avoid stock AI phrases: "delve," "nuanced," "robust," "seamless,"
|
|
|
87413
87417
|
Be concise when the user is moving fast.
|
|
87414
87418
|
Preserve the persona: Quill is warm, literate, and direct; Saffron is sharp,
|
|
87415
87419
|
practical, and confident.
|
|
87420
|
+
`.trim();
|
|
87421
|
+
PERCH_OPERATOR_CAPABILITIES = `
|
|
87422
|
+
## What you can do (and you can do a lot)
|
|
87423
|
+
|
|
87424
|
+
You run on the user's Mac through Perch Desktop. You are a real operator, not a
|
|
87425
|
+
chat box, and you have the tools to act. When someone asks what you can do, or
|
|
87426
|
+
whether you can control their computer, answer with specifics and offer to show
|
|
87427
|
+
them rather than listing what you cannot do. You can:
|
|
87428
|
+
|
|
87429
|
+
- Run shell commands in folders the user approves, including installing tools and
|
|
87430
|
+
packages, running scripts and software, and starting long jobs in the background.
|
|
87431
|
+
- Work across their files: read, write, edit, search with glob and grep, move,
|
|
87432
|
+
and organize anything inside approved folders.
|
|
87433
|
+
- Run real analysis in a sealed sandbox: write and execute Python or Node over
|
|
87434
|
+
their actual data.
|
|
87435
|
+
- Drive a real browser: navigate, click, type, fill forms, research the web, and
|
|
87436
|
+
pull sources.
|
|
87437
|
+
- Deliver finished work: draft and send Gmail, create Google Docs and Sheets, and
|
|
87438
|
+
add calendar events.
|
|
87439
|
+
- Do forensic and financial work: AP audits, reconciliations, statement and
|
|
87440
|
+
earnings reviews, and multi-step suites.
|
|
87441
|
+
|
|
87442
|
+
Lead with what you can do and offer to prove it, for example "want me to run
|
|
87443
|
+
something to show you?". Do not talk yourself down or open with a list of limits.
|
|
87444
|
+
|
|
87445
|
+
State the real limits once, and only when they matter: file and command access is
|
|
87446
|
+
scoped to the folders the user approves, and commands run under their permission
|
|
87447
|
+
settings. You work through the shell, their files, the sandbox, and the browser,
|
|
87448
|
+
not through arbitrary macOS system settings or other desktop apps. That is already
|
|
87449
|
+
a lot. Show it.
|
|
87416
87450
|
`.trim();
|
|
87417
87451
|
SAFFRON_STYLE_CONTRACT = `
|
|
87418
87452
|
## Voice contract \u2014 Saffron (visible output only)
|
|
@@ -87449,6 +87483,8 @@ You genuinely enjoy finding something interesting buried in a mess.
|
|
|
87449
87483
|
|
|
87450
87484
|
You're not an assistant. You're the operator who gets the work done.
|
|
87451
87485
|
|
|
87486
|
+
${PERCH_OPERATOR_CAPABILITIES}
|
|
87487
|
+
|
|
87452
87488
|
## How you work
|
|
87453
87489
|
|
|
87454
87490
|
Lead with the finding. Skip the preamble. "Seven duplicate payments, same vendor,
|
|
@@ -87579,6 +87615,8 @@ the user needs analysis, files, data, an audit, or delivery, you do that work
|
|
|
87579
87615
|
yourself with the same tools \u2014 in your own voice. You never punt real work to
|
|
87580
87616
|
another persona.
|
|
87581
87617
|
|
|
87618
|
+
${PERCH_OPERATOR_CAPABILITIES}
|
|
87619
|
+
|
|
87582
87620
|
## How you work
|
|
87583
87621
|
|
|
87584
87622
|
Read first. Before you draft a single sentence, you know what the piece is for,
|
|
@@ -92416,6 +92454,297 @@ var init_coreSystemPromptLanes = __esm({
|
|
|
92416
92454
|
}
|
|
92417
92455
|
});
|
|
92418
92456
|
|
|
92457
|
+
// lib/perchTerminal/appHostGate.ts
|
|
92458
|
+
var PERCH_CHAT_WEB_URL, PERCH_MARKETING_SITE_URL, PERCH_MARKETING_DESKTOP_URL, PERCH_MARKETING_CLI_URL, PERCH_CLI_INSTALL_COMMAND;
|
|
92459
|
+
var init_appHostGate = __esm({
|
|
92460
|
+
"lib/perchTerminal/appHostGate.ts"() {
|
|
92461
|
+
PERCH_CHAT_WEB_URL = process.env.NEXT_PUBLIC_PERCHAI_WEB_URL?.trim() || "https://chat.perchai.app";
|
|
92462
|
+
PERCH_MARKETING_SITE_URL = process.env.NEXT_PUBLIC_MARKETING_SITE_URL?.trim() || "https://perchai.app";
|
|
92463
|
+
PERCH_MARKETING_DESKTOP_URL = `${PERCH_MARKETING_SITE_URL}/desktop`;
|
|
92464
|
+
PERCH_MARKETING_CLI_URL = `${PERCH_MARKETING_SITE_URL}/cli`;
|
|
92465
|
+
PERCH_CLI_INSTALL_COMMAND = "npm install -g perchai-cli";
|
|
92466
|
+
}
|
|
92467
|
+
});
|
|
92468
|
+
|
|
92469
|
+
// lib/perch-ai/plan-catalog.ts
|
|
92470
|
+
var PERCH_AI_PLAN_CATALOG;
|
|
92471
|
+
var init_plan_catalog = __esm({
|
|
92472
|
+
"lib/perch-ai/plan-catalog.ts"() {
|
|
92473
|
+
PERCH_AI_PLAN_CATALOG = [
|
|
92474
|
+
{
|
|
92475
|
+
code: "internal",
|
|
92476
|
+
name: "Internal",
|
|
92477
|
+
description: "Full Perch AI internal/admin access for Perch Terminal operators.",
|
|
92478
|
+
entitlements: {
|
|
92479
|
+
"shell.preview": { enabled: true },
|
|
92480
|
+
"auth.access_codes.redeem": { enabled: true },
|
|
92481
|
+
"desktop.runtime": { enabled: true },
|
|
92482
|
+
"coding.workspace": { enabled: true },
|
|
92483
|
+
"ap.audit": { enabled: true },
|
|
92484
|
+
"writing.studio": { enabled: true },
|
|
92485
|
+
"writing.studio.web": { enabled: true },
|
|
92486
|
+
"admin.console": { enabled: true },
|
|
92487
|
+
"limits.daily_runs": { limit: null },
|
|
92488
|
+
"limits.weekly_runs": { limit: null },
|
|
92489
|
+
"limits.max_context_tokens": { limit: 2e5 },
|
|
92490
|
+
"limits.allowed_models": { models: ["*"] }
|
|
92491
|
+
}
|
|
92492
|
+
},
|
|
92493
|
+
{
|
|
92494
|
+
code: "pilot",
|
|
92495
|
+
name: "Pilot",
|
|
92496
|
+
description: "Self-serve pilot access with full product features.",
|
|
92497
|
+
entitlements: {
|
|
92498
|
+
"shell.preview": { enabled: true },
|
|
92499
|
+
"auth.access_codes.redeem": { enabled: true },
|
|
92500
|
+
"desktop.runtime": { enabled: true },
|
|
92501
|
+
"coding.workspace": { enabled: true },
|
|
92502
|
+
"ap.audit": { enabled: true },
|
|
92503
|
+
"writing.studio": { enabled: true },
|
|
92504
|
+
"writing.studio.web": { enabled: true },
|
|
92505
|
+
// Admin console stays off for self-serve signups (it grants operator/admin
|
|
92506
|
+
// tooling, not a product feature). Flip to true only for trusted accounts.
|
|
92507
|
+
"admin.console": { enabled: false },
|
|
92508
|
+
"limits.daily_runs": { limit: null },
|
|
92509
|
+
"limits.weekly_runs": { limit: null },
|
|
92510
|
+
"limits.max_context_tokens": { limit: 2e5 },
|
|
92511
|
+
"limits.allowed_models": { models: ["*"] }
|
|
92512
|
+
}
|
|
92513
|
+
},
|
|
92514
|
+
{
|
|
92515
|
+
code: "demo",
|
|
92516
|
+
name: "Demo",
|
|
92517
|
+
description: "Restricted demo access for guided evaluations.",
|
|
92518
|
+
entitlements: {
|
|
92519
|
+
"shell.preview": { enabled: true },
|
|
92520
|
+
"auth.access_codes.redeem": { enabled: true },
|
|
92521
|
+
"desktop.runtime": { enabled: false },
|
|
92522
|
+
"coding.workspace": { enabled: false },
|
|
92523
|
+
"ap.audit": { enabled: false },
|
|
92524
|
+
"writing.studio": { enabled: false },
|
|
92525
|
+
"writing.studio.web": { enabled: false },
|
|
92526
|
+
"admin.console": { enabled: false },
|
|
92527
|
+
"limits.daily_runs": { limit: 10 },
|
|
92528
|
+
"limits.weekly_runs": { limit: 30 },
|
|
92529
|
+
"limits.max_context_tokens": { limit: 32e3 },
|
|
92530
|
+
"limits.allowed_models": { models: ["gpt-4o-mini"] }
|
|
92531
|
+
}
|
|
92532
|
+
}
|
|
92533
|
+
];
|
|
92534
|
+
}
|
|
92535
|
+
});
|
|
92536
|
+
|
|
92537
|
+
// features/perchTerminal/runtime/roost.ts
|
|
92538
|
+
function normalizeRoostTier(value) {
|
|
92539
|
+
if (value === "max") return "pro_max";
|
|
92540
|
+
return isRoostTier(value) ? value : null;
|
|
92541
|
+
}
|
|
92542
|
+
function normalizeRoostModelChoice(value) {
|
|
92543
|
+
if (value === "internal") return "internal";
|
|
92544
|
+
return normalizeRoostTier(value);
|
|
92545
|
+
}
|
|
92546
|
+
function isRoostTier(value) {
|
|
92547
|
+
return value === "standard" || value === "standard_max" || value === "pro" || value === "pro_max";
|
|
92548
|
+
}
|
|
92549
|
+
var ROOST_TIERS, ROOST_TIER_LABELS, ROOST_TIER_CONTEXT_LABELS;
|
|
92550
|
+
var init_roost = __esm({
|
|
92551
|
+
"features/perchTerminal/runtime/roost.ts"() {
|
|
92552
|
+
"use strict";
|
|
92553
|
+
init_modelRegistry();
|
|
92554
|
+
ROOST_TIERS = ["standard", "standard_max", "pro", "pro_max"];
|
|
92555
|
+
ROOST_TIER_LABELS = {
|
|
92556
|
+
standard: "Standard",
|
|
92557
|
+
standard_max: "Standard Max",
|
|
92558
|
+
pro: "Pro",
|
|
92559
|
+
pro_max: "Pro Max"
|
|
92560
|
+
};
|
|
92561
|
+
ROOST_TIER_CONTEXT_LABELS = {
|
|
92562
|
+
standard: "200K context",
|
|
92563
|
+
standard_max: "1M context",
|
|
92564
|
+
pro: "200K context",
|
|
92565
|
+
pro_max: "1M context"
|
|
92566
|
+
};
|
|
92567
|
+
}
|
|
92568
|
+
});
|
|
92569
|
+
|
|
92570
|
+
// lib/perch/perchFactsCore.ts
|
|
92571
|
+
function getPilotEntitlements() {
|
|
92572
|
+
const pilot = PERCH_AI_PLAN_CATALOG.find((p) => p.code === "pilot");
|
|
92573
|
+
if (!pilot) return null;
|
|
92574
|
+
return pilot.entitlements;
|
|
92575
|
+
}
|
|
92576
|
+
function formatUsageLine() {
|
|
92577
|
+
const ent = getPilotEntitlements();
|
|
92578
|
+
if (!ent) return "Usage limits are configured per plan.";
|
|
92579
|
+
const daily = ent["limits.daily_runs"]?.limit;
|
|
92580
|
+
const weekly = ent["limits.weekly_runs"]?.limit;
|
|
92581
|
+
if (daily === null && weekly === null) {
|
|
92582
|
+
return "No credit card is required. Usage is generous for the pilot period. Perch may apply fair-use limits to keep the service reliable for everyone.";
|
|
92583
|
+
}
|
|
92584
|
+
const parts = [];
|
|
92585
|
+
if (daily !== null && daily !== void 0) parts.push(`${daily} runs per day`);
|
|
92586
|
+
if (weekly !== null && weekly !== void 0) parts.push(`${weekly} runs per week`);
|
|
92587
|
+
return `Usage is capped at ${parts.join(" and ")} on this plan. No credit card is required.`;
|
|
92588
|
+
}
|
|
92589
|
+
function buildRoostTierSummary() {
|
|
92590
|
+
const lines = ROOST_TIERS.map(
|
|
92591
|
+
(tier) => `- ${ROOST_TIER_LABELS[tier]} (${ROOST_TIER_CONTEXT_LABELS[tier]})`
|
|
92592
|
+
);
|
|
92593
|
+
return lines.join("\n");
|
|
92594
|
+
}
|
|
92595
|
+
function buildWebCapabilityList() {
|
|
92596
|
+
return [
|
|
92597
|
+
"- Grounded answers with real inline citations from your uploaded documents.",
|
|
92598
|
+
"- Upload and ask: PDF, spreadsheet, image, and document analysis.",
|
|
92599
|
+
"- Web search with cited results.",
|
|
92600
|
+
"- Draft citation check: marks each claim verified or unverified.",
|
|
92601
|
+
"",
|
|
92602
|
+
"The web chat does not run shell commands, access your local files, control a browser,",
|
|
92603
|
+
`or operate your computer. Those capabilities live in Perch Terminal (Desktop and CLI).`,
|
|
92604
|
+
`Get Perch Terminal at ${PERCH_MARKETING_DESKTOP_URL}.`
|
|
92605
|
+
].join("\n");
|
|
92606
|
+
}
|
|
92607
|
+
function buildTerminalCapabilityList() {
|
|
92608
|
+
const skillLines = DEDUPLICATED_SKILLS.map(
|
|
92609
|
+
(s) => `- ${s.id}: ${s.description}`
|
|
92610
|
+
).join("\n");
|
|
92611
|
+
return [
|
|
92612
|
+
"Core operator capabilities (available in Desktop and CLI):",
|
|
92613
|
+
"- Run shell commands in folders the user approves.",
|
|
92614
|
+
"- Read, write, edit, search, and organize files across approved folders.",
|
|
92615
|
+
"- Run real analysis in a sealed sandbox: Python and Node over actual data.",
|
|
92616
|
+
"- Drive a real browser: navigate, click, fill forms, research the web.",
|
|
92617
|
+
"- Deliver finished work: draft and send Gmail, create Google Docs and Sheets, add calendar events.",
|
|
92618
|
+
"- Financial and forensic work: AP audits, reconciliations, statement and earnings reviews, multi-step suites.",
|
|
92619
|
+
"",
|
|
92620
|
+
"Specialized skills (activated per task from the skill registry):",
|
|
92621
|
+
skillLines
|
|
92622
|
+
].join("\n");
|
|
92623
|
+
}
|
|
92624
|
+
function buildPerchFactsCore(surface) {
|
|
92625
|
+
const capabilitySection = surface === "web" ? `### What this web chat can do
|
|
92626
|
+
|
|
92627
|
+
${buildWebCapabilityList()}` : `### What Perch Terminal can do
|
|
92628
|
+
|
|
92629
|
+
${buildTerminalCapabilityList()}`;
|
|
92630
|
+
const tierTwoNote = surface === "terminal" ? `
|
|
92631
|
+
For deep detail on Perch (per-tier specifics, full workflow matrix, FAQ), consult the "about-perch" skill when a question goes beyond the facts above.` : "";
|
|
92632
|
+
return [
|
|
92633
|
+
INSTRUCTION_PREAMBLE,
|
|
92634
|
+
"",
|
|
92635
|
+
WHAT_IS_PERCH,
|
|
92636
|
+
"",
|
|
92637
|
+
ACCESS_AND_PRICING,
|
|
92638
|
+
"",
|
|
92639
|
+
MODEL_STORY,
|
|
92640
|
+
"",
|
|
92641
|
+
capabilitySection,
|
|
92642
|
+
tierTwoNote
|
|
92643
|
+
].join("\n").trim();
|
|
92644
|
+
}
|
|
92645
|
+
var PERCH_SKILL_REGISTRY, DEDUPLICATED_SKILLS, INSTRUCTION_PREAMBLE, WHAT_IS_PERCH, ACCESS_AND_PRICING, MODEL_STORY;
|
|
92646
|
+
var init_perchFactsCore = __esm({
|
|
92647
|
+
"lib/perch/perchFactsCore.ts"() {
|
|
92648
|
+
init_appHostGate();
|
|
92649
|
+
init_plan_catalog();
|
|
92650
|
+
init_roost();
|
|
92651
|
+
PERCH_SKILL_REGISTRY = [
|
|
92652
|
+
{
|
|
92653
|
+
id: "workspace",
|
|
92654
|
+
description: "Navigate, inspect, search, summarize, and safely edit local workspaces, attached folders, source inventories, project files, and codebases with exact file-grounded evidence."
|
|
92655
|
+
},
|
|
92656
|
+
{
|
|
92657
|
+
id: "browser-operator",
|
|
92658
|
+
description: "Operate Perch's logged-in browser surface using visual state, semantic DOM targets, and coordinate actions when available."
|
|
92659
|
+
},
|
|
92660
|
+
{
|
|
92661
|
+
id: "research",
|
|
92662
|
+
description: "Gather, compare, rank, and cite evidence from public web sources, scholarly sources, workspace files, source packets, and attached local folders."
|
|
92663
|
+
},
|
|
92664
|
+
{
|
|
92665
|
+
id: "documents",
|
|
92666
|
+
description: "Create, edit, verify, and deliver professional document artifacts such as DOCX, Google Docs, memos, letters, reports, redlines, and formal written deliverables."
|
|
92667
|
+
},
|
|
92668
|
+
{
|
|
92669
|
+
id: "spreadsheets",
|
|
92670
|
+
description: "Create, inspect, clean, calculate, format, and verify spreadsheet artifacts such as XLSX, CSV, TSV, Google Sheets, formulas, pivots, charts, and reconciliation workbooks."
|
|
92671
|
+
},
|
|
92672
|
+
{
|
|
92673
|
+
id: "pdf",
|
|
92674
|
+
description: "Extract, summarize, cite, split, merge, create, inspect, and verify PDF content, including text PDFs, scanned PDFs, tables, forms, page citations, and generated reports."
|
|
92675
|
+
},
|
|
92676
|
+
{
|
|
92677
|
+
id: "presentations",
|
|
92678
|
+
description: "Build executive decks, board decks, sales decks, research summaries, and visual narratives as PPTX or shareable slide artifacts with verified layout and source-backed content."
|
|
92679
|
+
},
|
|
92680
|
+
{
|
|
92681
|
+
id: "data-quality",
|
|
92682
|
+
description: "Profile messy data, find anomalies, reconcile systems, explain exceptions, score risk, and produce audit-ready evidence through deterministic math before narrative."
|
|
92683
|
+
},
|
|
92684
|
+
{
|
|
92685
|
+
id: "diagrams",
|
|
92686
|
+
description: "Create, edit, and verify diagrams, graphs, workflows, org charts, entity networks, control graphs, architecture maps, and evidence-backed visual structures."
|
|
92687
|
+
},
|
|
92688
|
+
{
|
|
92689
|
+
id: "github",
|
|
92690
|
+
description: "Inspect GitHub and git repository state, review commits and pull requests, debug checks, and publish local changes with clear permission boundaries."
|
|
92691
|
+
},
|
|
92692
|
+
{
|
|
92693
|
+
id: "wait-operator",
|
|
92694
|
+
description: "Watch bounded external or local conditions, poll deployment and job status, wait through short-running tasks, and resume with verified state instead of guessing."
|
|
92695
|
+
},
|
|
92696
|
+
{
|
|
92697
|
+
id: "data-quality",
|
|
92698
|
+
description: "Profile messy data, find anomalies, reconcile systems, explain exceptions, score risk, and produce audit-ready evidence through deterministic math before narrative."
|
|
92699
|
+
}
|
|
92700
|
+
];
|
|
92701
|
+
DEDUPLICATED_SKILLS = Array.from(
|
|
92702
|
+
new Map(PERCH_SKILL_REGISTRY.map((s) => [s.id, s])).values()
|
|
92703
|
+
);
|
|
92704
|
+
INSTRUCTION_PREAMBLE = `## Perch product facts -- authoritative reference
|
|
92705
|
+
|
|
92706
|
+
For any question about Perch (what it is, whether it is free, what you can do,
|
|
92707
|
+
what model you are, do I need an account, etc.), answer only from the facts
|
|
92708
|
+
below. If something is not covered here, say you are not certain rather than
|
|
92709
|
+
guessing.`.trim();
|
|
92710
|
+
WHAT_IS_PERCH = `### What Perch is
|
|
92711
|
+
|
|
92712
|
+
Perch is an AI platform built around verifiable AI: every claim is sourced and
|
|
92713
|
+
checkable. The goal is to make AI output trustworthy, not just fluent.
|
|
92714
|
+
|
|
92715
|
+
Perch has three surfaces:
|
|
92716
|
+
- Perch AI Web (chat.perchai.app): a warm, accessible web chat for grounded
|
|
92717
|
+
answers, document upload, web search, and citation checking.
|
|
92718
|
+
- Perch Terminal Desktop: a local operator that runs on your Mac, controls
|
|
92719
|
+
your files, runs code in a sandbox, drives a real browser, and delivers
|
|
92720
|
+
finished work. Download at ${PERCH_MARKETING_DESKTOP_URL}.
|
|
92721
|
+
- Perch Terminal CLI: the same operator power from your terminal.
|
|
92722
|
+
Install with: ${PERCH_CLI_INSTALL_COMMAND}.`.trim();
|
|
92723
|
+
ACCESS_AND_PRICING = `### Access and pricing
|
|
92724
|
+
|
|
92725
|
+
All three surfaces are free during the current pilot. No credit card is
|
|
92726
|
+
required to sign up.
|
|
92727
|
+
|
|
92728
|
+
${formatUsageLine()}
|
|
92729
|
+
|
|
92730
|
+
A Perch account is required to use the web chat. Sign up at ${PERCH_CHAT_WEB_URL}.
|
|
92731
|
+
Perch Terminal Desktop and CLI are also free; sign in with your Perch account.`.trim();
|
|
92732
|
+
MODEL_STORY = `### The model -- Roost
|
|
92733
|
+
|
|
92734
|
+
Perch does not expose raw model names. The model layer is called Roost.
|
|
92735
|
+
Roost picks the best available model for each task and locks it for that task.
|
|
92736
|
+
You are powered by Roost.
|
|
92737
|
+
|
|
92738
|
+
Roost has four tiers:
|
|
92739
|
+
${buildRoostTierSummary()}
|
|
92740
|
+
|
|
92741
|
+
Standard and Pro use a 200K context window. Standard Max and Pro Max use a 1M
|
|
92742
|
+
context window (Max = long context). Tier selection is available in the
|
|
92743
|
+
composer. When someone asks what model you are, say: "I run on Roost, Perch's
|
|
92744
|
+
model layer. Perch does not surface the underlying model names."`.trim();
|
|
92745
|
+
}
|
|
92746
|
+
});
|
|
92747
|
+
|
|
92419
92748
|
// features/perchTerminal/runtime/context/coreSystemSection.ts
|
|
92420
92749
|
function buildCoreSystemSection(input) {
|
|
92421
92750
|
const enabledToolNames = Array.from(new Set(input.enabledToolNames ?? []));
|
|
@@ -92442,6 +92771,7 @@ function buildCoreSystemSection(input) {
|
|
|
92442
92771
|
});
|
|
92443
92772
|
const lines = splitRenderedLaneText(renderedCoreLanes);
|
|
92444
92773
|
const permissionModeHandled = corePromptPermissionModeHandled(renderedCoreLanes);
|
|
92774
|
+
lines.push("", PERCH_FACTS_CORE_TERMINAL);
|
|
92445
92775
|
if (!permissionModeHandled && input.chatMode === "plan") {
|
|
92446
92776
|
if (planGateRejected) {
|
|
92447
92777
|
lines.push(
|
|
@@ -92699,7 +93029,7 @@ function buildCoordinatorNotificationSynthesisInstructions(notifications) {
|
|
|
92699
93029
|
hasFailure ? "If the error-code is batch_failed, state that the suite failed and that no downstream stages ran." : "If multiple notifications arrived, combine them into one concise synthesis without exposing raw XML."
|
|
92700
93030
|
].join("\n");
|
|
92701
93031
|
}
|
|
92702
|
-
var PLATFORM_DELIVERY_GUIDANCE, LIVE_NARRATION_GUIDANCE;
|
|
93032
|
+
var PERCH_FACTS_CORE_TERMINAL, PLATFORM_DELIVERY_GUIDANCE, LIVE_NARRATION_GUIDANCE;
|
|
92703
93033
|
var init_coreSystemSection = __esm({
|
|
92704
93034
|
"features/perchTerminal/runtime/context/coreSystemSection.ts"() {
|
|
92705
93035
|
"use strict";
|
|
@@ -92715,6 +93045,8 @@ var init_coreSystemSection = __esm({
|
|
|
92715
93045
|
init_operatingCaseService();
|
|
92716
93046
|
init_planModeStateMachine();
|
|
92717
93047
|
init_coreSystemPromptLanes();
|
|
93048
|
+
init_perchFactsCore();
|
|
93049
|
+
PERCH_FACTS_CORE_TERMINAL = buildPerchFactsCore("terminal");
|
|
92718
93050
|
PLATFORM_DELIVERY_GUIDANCE = `## Platform delivery \u2014 use the verified shortcut, do not hand off
|
|
92719
93051
|
You own delivery end-to-end in your own loop. Do NOT dispatch a sub-agent for Gmail, Google Docs, or Calendar \u2014 handing delivery off loses your context and breaks multi-step tasks (e.g. "make a doc AND email it"). Keep one continuous thread: you drafted it, so you deliver it, and you still remember every prior step (recipients, the doc URL you just created, etc.).
|
|
92720
93052
|
|
|
@@ -92756,6 +93088,45 @@ var init_agentSkillRegistry = __esm({
|
|
|
92756
93088
|
"generated/agentSkillRegistry.ts"() {
|
|
92757
93089
|
"use strict";
|
|
92758
93090
|
AGENT_SKILL_REGISTRY = [
|
|
93091
|
+
{
|
|
93092
|
+
"id": "about-perch",
|
|
93093
|
+
"name": "about-perch",
|
|
93094
|
+
"description": "Answer detailed questions about Perch the product -- what it is, surfaces, pricing, the Roost model layer, per-tier details, capabilities, web vs Terminal differences, and common user questions. Consult this skill when a Perch product question goes beyond the always-on core facts.",
|
|
93095
|
+
"path": "features/perchTerminal/agentPlatform/skills/about-perch/SKILL.md",
|
|
93096
|
+
"body": "\n# About Perch\n\nUse the facts in this skill to answer any detailed product question about Perch.\nDo not guess. If a fact is not here and not in the always-on core, say you are not certain.\nNever reveal underlying model names. Always refer to the model layer as Roost.\n\nSee SKILL.md for the full product FAQ, web vs Terminal matrix, and per-tier detail.",
|
|
93097
|
+
"surface": {
|
|
93098
|
+
"gui": "supported",
|
|
93099
|
+
"cli": "supported"
|
|
93100
|
+
},
|
|
93101
|
+
"triggerHints": [
|
|
93102
|
+
"what is perch",
|
|
93103
|
+
"what can you do",
|
|
93104
|
+
"what model are you",
|
|
93105
|
+
"is this free",
|
|
93106
|
+
"is perch free",
|
|
93107
|
+
"is terminal free",
|
|
93108
|
+
"is terminal paid",
|
|
93109
|
+
"do i need an account",
|
|
93110
|
+
"roost",
|
|
93111
|
+
"roost tier",
|
|
93112
|
+
"standard max",
|
|
93113
|
+
"pro max",
|
|
93114
|
+
"perch terminal",
|
|
93115
|
+
"perch desktop",
|
|
93116
|
+
"perch cli",
|
|
93117
|
+
"perch vs",
|
|
93118
|
+
"web vs terminal",
|
|
93119
|
+
"difference between perch",
|
|
93120
|
+
"how does perch work",
|
|
93121
|
+
"citation verifier",
|
|
93122
|
+
"what is roost"
|
|
93123
|
+
],
|
|
93124
|
+
"companionSkills": [
|
|
93125
|
+
"research",
|
|
93126
|
+
"workspace"
|
|
93127
|
+
],
|
|
93128
|
+
"preferredTools": []
|
|
93129
|
+
},
|
|
92759
93130
|
{
|
|
92760
93131
|
"id": "browser-operator",
|
|
92761
93132
|
"name": "browser-operator",
|
|
@@ -93407,7 +93778,8 @@ var init_skillSelector = __esm({
|
|
|
93407
93778
|
"diagrams",
|
|
93408
93779
|
"workspace",
|
|
93409
93780
|
"browser-operator",
|
|
93410
|
-
"skill-creator"
|
|
93781
|
+
"skill-creator",
|
|
93782
|
+
"about-perch"
|
|
93411
93783
|
];
|
|
93412
93784
|
INTENT_PATTERNS = {
|
|
93413
93785
|
"browser-operator": [
|
|
@@ -93459,6 +93831,10 @@ var init_skillSelector = __esm({
|
|
|
93459
93831
|
],
|
|
93460
93832
|
"skill-creator": [
|
|
93461
93833
|
/\b(skill\s+creator|create\s+(?:a\s+)?skill|skill\.md|agent\s+skill|skill\s+validation|trigger\s+collision|skill\s+test|skill\s+authoring|perch\s+skill)\b/i
|
|
93834
|
+
],
|
|
93835
|
+
"about-perch": [
|
|
93836
|
+
/\b(what\s+(is|are)\s+perch|what\s+can\s+you\s+do|what\s+model\s+are\s+you|is\s+(this|perch|terminal|it)\s+free|is\s+terminal\s+paid|do\s+i\s+need\s+an\s+account|account\s+required|roost\s+(tier|model|layer)|standard\s+max|pro\s+max|perch\s+terminal|perch\s+desktop|perch\s+cli|web\s+vs\s+terminal|how\s+does\s+perch\s+work|citation\s+verifier|what\s+is\s+roost)\b/i,
|
|
93837
|
+
/\b(tell\s+me\s+about\s+perch|about\s+perch|perch\s+pricing|perch\s+product|perch\s+features?|how\s+much\s+does\s+perch\s+cost)\b/i
|
|
93462
93838
|
]
|
|
93463
93839
|
};
|
|
93464
93840
|
}
|
|
@@ -119131,32 +119507,6 @@ var init_browserSession = __esm({
|
|
|
119131
119507
|
}
|
|
119132
119508
|
});
|
|
119133
119509
|
|
|
119134
|
-
// features/perchTerminal/runtime/roost.ts
|
|
119135
|
-
function normalizeRoostTier(value) {
|
|
119136
|
-
if (value === "max") return "pro_max";
|
|
119137
|
-
return isRoostTier(value) ? value : null;
|
|
119138
|
-
}
|
|
119139
|
-
function normalizeRoostModelChoice(value) {
|
|
119140
|
-
if (value === "internal") return "internal";
|
|
119141
|
-
return normalizeRoostTier(value);
|
|
119142
|
-
}
|
|
119143
|
-
function isRoostTier(value) {
|
|
119144
|
-
return value === "standard" || value === "standard_max" || value === "pro" || value === "pro_max";
|
|
119145
|
-
}
|
|
119146
|
-
var ROOST_TIER_LABELS;
|
|
119147
|
-
var init_roost = __esm({
|
|
119148
|
-
"features/perchTerminal/runtime/roost.ts"() {
|
|
119149
|
-
"use strict";
|
|
119150
|
-
init_modelRegistry();
|
|
119151
|
-
ROOST_TIER_LABELS = {
|
|
119152
|
-
standard: "Standard",
|
|
119153
|
-
standard_max: "Standard Max",
|
|
119154
|
-
pro: "Pro",
|
|
119155
|
-
pro_max: "Pro Max"
|
|
119156
|
-
};
|
|
119157
|
-
}
|
|
119158
|
-
});
|
|
119159
|
-
|
|
119160
119510
|
// features/perchTerminal/runtime/roostUserSelectionClient.ts
|
|
119161
119511
|
function coerceSelection(raw) {
|
|
119162
119512
|
const choice = normalizeRoostModelChoice(raw?.choice);
|
|
@@ -119196,6 +119546,507 @@ var init_roostUserSelectionClient = __esm({
|
|
|
119196
119546
|
}
|
|
119197
119547
|
});
|
|
119198
119548
|
|
|
119549
|
+
// features/perchTerminal/runtime/cliHost/cliAuthSession.ts
|
|
119550
|
+
var cliAuthSession_exports = {};
|
|
119551
|
+
__export(cliAuthSession_exports, {
|
|
119552
|
+
clearStoredCliAuthSession: () => clearStoredCliAuthSession,
|
|
119553
|
+
isStoredCliAuthSessionUsable: () => isStoredCliAuthSessionUsable,
|
|
119554
|
+
parseStoredCliAuthSession: () => parseStoredCliAuthSession,
|
|
119555
|
+
readStoredCliAuthSession: () => readStoredCliAuthSession,
|
|
119556
|
+
writeStoredCliAuthSession: () => writeStoredCliAuthSession
|
|
119557
|
+
});
|
|
119558
|
+
import { execFile } from "node:child_process";
|
|
119559
|
+
import fs9 from "node:fs/promises";
|
|
119560
|
+
import os from "node:os";
|
|
119561
|
+
import path10 from "node:path";
|
|
119562
|
+
import { promisify } from "node:util";
|
|
119563
|
+
async function readStoredCliAuthSession() {
|
|
119564
|
+
const raw = shouldUseFallbackAuthFile() ? await readFallbackSecret().catch(() => null) : process.platform === "darwin" ? await readKeychainSecret().catch(() => null) : await readFallbackSecret().catch(() => null);
|
|
119565
|
+
return parseStoredCliAuthSession(raw);
|
|
119566
|
+
}
|
|
119567
|
+
async function writeStoredCliAuthSession(session) {
|
|
119568
|
+
const raw = JSON.stringify(session);
|
|
119569
|
+
if (process.platform === "darwin" && !shouldUseFallbackAuthFile()) {
|
|
119570
|
+
await execFileAsync("security", [
|
|
119571
|
+
"add-generic-password",
|
|
119572
|
+
"-U",
|
|
119573
|
+
"-s",
|
|
119574
|
+
KEYCHAIN_SERVICE,
|
|
119575
|
+
"-a",
|
|
119576
|
+
KEYCHAIN_ACCOUNT,
|
|
119577
|
+
"-w",
|
|
119578
|
+
raw
|
|
119579
|
+
]);
|
|
119580
|
+
return;
|
|
119581
|
+
}
|
|
119582
|
+
await writeFallbackSecret(raw);
|
|
119583
|
+
}
|
|
119584
|
+
async function clearStoredCliAuthSession() {
|
|
119585
|
+
if (process.platform === "darwin" && !shouldUseFallbackAuthFile()) {
|
|
119586
|
+
await execFileAsync("security", [
|
|
119587
|
+
"delete-generic-password",
|
|
119588
|
+
"-s",
|
|
119589
|
+
KEYCHAIN_SERVICE,
|
|
119590
|
+
"-a",
|
|
119591
|
+
KEYCHAIN_ACCOUNT
|
|
119592
|
+
]).catch(() => void 0);
|
|
119593
|
+
return;
|
|
119594
|
+
}
|
|
119595
|
+
await fs9.rm(fallbackSessionPath(), { force: true }).catch(() => void 0);
|
|
119596
|
+
}
|
|
119597
|
+
function shouldUseFallbackAuthFile() {
|
|
119598
|
+
return Boolean(process.env.PERCH_CLI_AUTH_DIR?.trim());
|
|
119599
|
+
}
|
|
119600
|
+
function isStoredCliAuthSessionUsable(session, nowSeconds = Math.floor(Date.now() / 1e3)) {
|
|
119601
|
+
if (!session?.accessToken?.trim()) return false;
|
|
119602
|
+
if (!session.appUrl?.trim()) return false;
|
|
119603
|
+
if (!session.expiresAt) return true;
|
|
119604
|
+
return session.expiresAt > nowSeconds + 90;
|
|
119605
|
+
}
|
|
119606
|
+
function parseStoredCliAuthSession(raw) {
|
|
119607
|
+
if (!raw?.trim()) return null;
|
|
119608
|
+
try {
|
|
119609
|
+
const parsed = JSON.parse(raw);
|
|
119610
|
+
if (parsed.version !== 1) return null;
|
|
119611
|
+
if (!parsed.appUrl || !parsed.accessToken || !parsed.updatedAt) return null;
|
|
119612
|
+
return {
|
|
119613
|
+
version: 1,
|
|
119614
|
+
appUrl: parsed.appUrl,
|
|
119615
|
+
accessToken: parsed.accessToken,
|
|
119616
|
+
refreshToken: parsed.refreshToken ?? null,
|
|
119617
|
+
expiresAt: typeof parsed.expiresAt === "number" ? parsed.expiresAt : null,
|
|
119618
|
+
userId: parsed.userId ?? null,
|
|
119619
|
+
email: parsed.email ?? null,
|
|
119620
|
+
updatedAt: parsed.updatedAt
|
|
119621
|
+
};
|
|
119622
|
+
} catch {
|
|
119623
|
+
return null;
|
|
119624
|
+
}
|
|
119625
|
+
}
|
|
119626
|
+
async function readKeychainSecret() {
|
|
119627
|
+
const { stdout } = await execFileAsync("security", [
|
|
119628
|
+
"find-generic-password",
|
|
119629
|
+
"-s",
|
|
119630
|
+
KEYCHAIN_SERVICE,
|
|
119631
|
+
"-a",
|
|
119632
|
+
KEYCHAIN_ACCOUNT,
|
|
119633
|
+
"-w"
|
|
119634
|
+
]);
|
|
119635
|
+
return stdout.trim() || null;
|
|
119636
|
+
}
|
|
119637
|
+
function fallbackSessionPath() {
|
|
119638
|
+
const base = process.env.PERCH_CLI_AUTH_DIR?.trim() || path10.join(os.homedir(), ".perch");
|
|
119639
|
+
return path10.join(base, "cli-auth-session.json");
|
|
119640
|
+
}
|
|
119641
|
+
async function readFallbackSecret() {
|
|
119642
|
+
return await fs9.readFile(fallbackSessionPath(), "utf8");
|
|
119643
|
+
}
|
|
119644
|
+
async function writeFallbackSecret(raw) {
|
|
119645
|
+
const filePath = fallbackSessionPath();
|
|
119646
|
+
await fs9.mkdir(path10.dirname(filePath), { recursive: true, mode: 448 });
|
|
119647
|
+
await fs9.writeFile(filePath, raw, { mode: 384 });
|
|
119648
|
+
}
|
|
119649
|
+
var execFileAsync, KEYCHAIN_SERVICE, KEYCHAIN_ACCOUNT;
|
|
119650
|
+
var init_cliAuthSession = __esm({
|
|
119651
|
+
"features/perchTerminal/runtime/cliHost/cliAuthSession.ts"() {
|
|
119652
|
+
"use strict";
|
|
119653
|
+
execFileAsync = promisify(execFile);
|
|
119654
|
+
KEYCHAIN_SERVICE = "app.perchai.cli-auth";
|
|
119655
|
+
KEYCHAIN_ACCOUNT = "default";
|
|
119656
|
+
}
|
|
119657
|
+
});
|
|
119658
|
+
|
|
119659
|
+
// features/perchTerminal/runtime/publicModelDefault.ts
|
|
119660
|
+
function parsePublicModelDefaultPayload(value) {
|
|
119661
|
+
const raw = value && typeof value === "object" ? value : {};
|
|
119662
|
+
const selection = sanitizeFounderModelSelection(
|
|
119663
|
+
raw.selection ?? DEFAULT_FOUNDER_MODEL_SELECTION
|
|
119664
|
+
);
|
|
119665
|
+
return {
|
|
119666
|
+
selection,
|
|
119667
|
+
updatedAt: typeof raw.updatedAt === "string" ? raw.updatedAt : null,
|
|
119668
|
+
updatedBy: typeof raw.updatedBy === "string" ? raw.updatedBy : null
|
|
119669
|
+
};
|
|
119670
|
+
}
|
|
119671
|
+
var init_publicModelDefault = __esm({
|
|
119672
|
+
"features/perchTerminal/runtime/publicModelDefault.ts"() {
|
|
119673
|
+
"use strict";
|
|
119674
|
+
init_modelRegistry();
|
|
119675
|
+
}
|
|
119676
|
+
});
|
|
119677
|
+
|
|
119678
|
+
// features/perchTerminal/runtime/cliHost/modelConnection.ts
|
|
119679
|
+
async function connectCliModelProxy(input = {}) {
|
|
119680
|
+
const storedSession = input.storedSession === void 0 ? await readStoredCliAuthSession() : input.storedSession;
|
|
119681
|
+
const usableStoredSession = await ensureFreshCliAuthSession({
|
|
119682
|
+
session: storedSession,
|
|
119683
|
+
fetchImpl: input.fetchImpl
|
|
119684
|
+
});
|
|
119685
|
+
const appUrl = resolveCliAppUrl(input.appUrl, usableStoredSession?.appUrl ?? null);
|
|
119686
|
+
if (!appUrl) return noCliModelConnection();
|
|
119687
|
+
if (!usableStoredSession?.accessToken) return noCliModelConnection(appUrl);
|
|
119688
|
+
const fetchImpl = input.fetchImpl ?? globalThis.fetch;
|
|
119689
|
+
if (typeof fetchImpl !== "function") return noCliModelConnection(appUrl);
|
|
119690
|
+
const selection = await fetchPublicModelDefaultSelection(
|
|
119691
|
+
appUrl,
|
|
119692
|
+
fetchImpl,
|
|
119693
|
+
usableStoredSession?.accessToken ?? null
|
|
119694
|
+
);
|
|
119695
|
+
if (!selection) return noCliModelConnection(appUrl);
|
|
119696
|
+
const priorProxy = process.env[MODEL_PROXY_ENV];
|
|
119697
|
+
const priorToken = process.env[MODEL_PROXY_TOKEN_ENV];
|
|
119698
|
+
process.env[MODEL_PROXY_ENV] = appUrl;
|
|
119699
|
+
if (usableStoredSession?.accessToken) {
|
|
119700
|
+
process.env[MODEL_PROXY_TOKEN_ENV] = usableStoredSession.accessToken;
|
|
119701
|
+
}
|
|
119702
|
+
return {
|
|
119703
|
+
appUrl,
|
|
119704
|
+
authenticated: !!usableStoredSession?.accessToken,
|
|
119705
|
+
userId: usableStoredSession?.userId ?? null,
|
|
119706
|
+
email: usableStoredSession?.email ?? null,
|
|
119707
|
+
founderModelSelection: selection,
|
|
119708
|
+
restore: () => {
|
|
119709
|
+
if (priorProxy === void 0) {
|
|
119710
|
+
delete process.env[MODEL_PROXY_ENV];
|
|
119711
|
+
} else {
|
|
119712
|
+
process.env[MODEL_PROXY_ENV] = priorProxy;
|
|
119713
|
+
}
|
|
119714
|
+
if (priorToken === void 0) {
|
|
119715
|
+
delete process.env[MODEL_PROXY_TOKEN_ENV];
|
|
119716
|
+
} else {
|
|
119717
|
+
process.env[MODEL_PROXY_TOKEN_ENV] = priorToken;
|
|
119718
|
+
}
|
|
119719
|
+
}
|
|
119720
|
+
};
|
|
119721
|
+
}
|
|
119722
|
+
function resolveCliAppUrl(explicit, stored) {
|
|
119723
|
+
const raw = explicit?.trim() || process.env[CLI_APP_URL_ENV]?.trim() || process.env[FALLBACK_APP_URL_ENV]?.trim() || stored?.trim() || DEFAULT_CLI_APP_URL;
|
|
119724
|
+
if (!raw) return null;
|
|
119725
|
+
const withProtocol = /^https?:\/\//i.test(raw) ? raw : `http://${raw}`;
|
|
119726
|
+
try {
|
|
119727
|
+
const url = new URL(withProtocol);
|
|
119728
|
+
url.pathname = url.pathname.replace(/\/+$/, "");
|
|
119729
|
+
url.search = "";
|
|
119730
|
+
url.hash = "";
|
|
119731
|
+
return url.toString().replace(/\/+$/, "");
|
|
119732
|
+
} catch {
|
|
119733
|
+
return null;
|
|
119734
|
+
}
|
|
119735
|
+
}
|
|
119736
|
+
async function fetchPublicModelDefaultSelection(appUrl, fetchImpl, accessToken) {
|
|
119737
|
+
const url = `${appUrl}/api/perch-terminal/public-model-default`;
|
|
119738
|
+
const controller = new AbortController();
|
|
119739
|
+
const timeout = setTimeout(() => controller.abort(), 3500);
|
|
119740
|
+
try {
|
|
119741
|
+
const response = await fetchImpl(url, {
|
|
119742
|
+
method: "GET",
|
|
119743
|
+
headers: {
|
|
119744
|
+
Accept: "application/json",
|
|
119745
|
+
...accessToken ? { Authorization: `Bearer ${accessToken}` } : {}
|
|
119746
|
+
},
|
|
119747
|
+
signal: controller.signal
|
|
119748
|
+
});
|
|
119749
|
+
if (!response.ok) return null;
|
|
119750
|
+
const body = await response.json();
|
|
119751
|
+
if (!body || typeof body !== "object") return null;
|
|
119752
|
+
const record = body;
|
|
119753
|
+
if (record.ok !== true) return null;
|
|
119754
|
+
return parsePublicModelDefaultPayload({ selection: record.selection }).selection;
|
|
119755
|
+
} catch {
|
|
119756
|
+
return null;
|
|
119757
|
+
} finally {
|
|
119758
|
+
clearTimeout(timeout);
|
|
119759
|
+
}
|
|
119760
|
+
}
|
|
119761
|
+
function noCliModelConnection(appUrl = null) {
|
|
119762
|
+
return {
|
|
119763
|
+
appUrl,
|
|
119764
|
+
authenticated: false,
|
|
119765
|
+
userId: null,
|
|
119766
|
+
email: null,
|
|
119767
|
+
founderModelSelection: null,
|
|
119768
|
+
restore: () => {
|
|
119769
|
+
}
|
|
119770
|
+
};
|
|
119771
|
+
}
|
|
119772
|
+
var MODEL_PROXY_ENV, MODEL_PROXY_TOKEN_ENV, CLI_APP_URL_ENV, FALLBACK_APP_URL_ENV, DEFAULT_CLI_APP_URL;
|
|
119773
|
+
var init_modelConnection = __esm({
|
|
119774
|
+
"features/perchTerminal/runtime/cliHost/modelConnection.ts"() {
|
|
119775
|
+
"use strict";
|
|
119776
|
+
init_publicModelDefault();
|
|
119777
|
+
init_cliAuthSession();
|
|
119778
|
+
init_cliAuthRefresh();
|
|
119779
|
+
MODEL_PROXY_ENV = "PERCH_MODEL_CALL_PROXY_URL";
|
|
119780
|
+
MODEL_PROXY_TOKEN_ENV = "PERCH_MODEL_CALL_PROXY_TOKEN";
|
|
119781
|
+
CLI_APP_URL_ENV = "PERCH_CLI_APP_URL";
|
|
119782
|
+
FALLBACK_APP_URL_ENV = "PERCH_APP_URL";
|
|
119783
|
+
DEFAULT_CLI_APP_URL = "https://app.perchai.app";
|
|
119784
|
+
}
|
|
119785
|
+
});
|
|
119786
|
+
|
|
119787
|
+
// features/perchTerminal/runtime/cliHost/cliStandaloneOAuth.ts
|
|
119788
|
+
import { execFile as execFile2 } from "node:child_process";
|
|
119789
|
+
import http from "node:http";
|
|
119790
|
+
import { promisify as promisify2 } from "node:util";
|
|
119791
|
+
async function runCliStandaloneOAuthLogin(input) {
|
|
119792
|
+
const appUrl = resolveCliAppUrl(input.appUrl, null);
|
|
119793
|
+
if (!appUrl) {
|
|
119794
|
+
return { ok: false, code: "invalid_app_url", error: "Invalid Perch app URL." };
|
|
119795
|
+
}
|
|
119796
|
+
const fetchImpl = input.fetchImpl ?? globalThis.fetch;
|
|
119797
|
+
if (typeof fetchImpl !== "function") {
|
|
119798
|
+
return { ok: false, code: "fetch_unavailable", error: "This Node runtime does not expose fetch." };
|
|
119799
|
+
}
|
|
119800
|
+
const config = await fetchCliAuthConfig(appUrl, fetchImpl);
|
|
119801
|
+
if (!config.ok || !config.supabaseUrl || !config.supabaseAnonKey) {
|
|
119802
|
+
return {
|
|
119803
|
+
ok: false,
|
|
119804
|
+
code: "auth_config_unavailable",
|
|
119805
|
+
error: "The Perch app did not return CLI auth configuration."
|
|
119806
|
+
};
|
|
119807
|
+
}
|
|
119808
|
+
const provider = selectOAuthProvider(config.providers ?? [], input.provider);
|
|
119809
|
+
if (!provider) {
|
|
119810
|
+
return {
|
|
119811
|
+
ok: false,
|
|
119812
|
+
code: "oauth_provider_unavailable",
|
|
119813
|
+
error: "No supported CLI OAuth provider is enabled for this Perch app."
|
|
119814
|
+
};
|
|
119815
|
+
}
|
|
119816
|
+
const callback = await createOAuthCallbackServer({
|
|
119817
|
+
host: config.redirectHost?.trim() || "127.0.0.1",
|
|
119818
|
+
timeoutMs: input.timeoutMs ?? DEFAULT_LOGIN_TIMEOUT_MS
|
|
119819
|
+
});
|
|
119820
|
+
try {
|
|
119821
|
+
const memoryStorage = createMemoryAuthStorage();
|
|
119822
|
+
const supabase = createClient(config.supabaseUrl, config.supabaseAnonKey, {
|
|
119823
|
+
auth: {
|
|
119824
|
+
flowType: "pkce",
|
|
119825
|
+
autoRefreshToken: false,
|
|
119826
|
+
detectSessionInUrl: false,
|
|
119827
|
+
persistSession: true,
|
|
119828
|
+
storage: memoryStorage
|
|
119829
|
+
}
|
|
119830
|
+
});
|
|
119831
|
+
const { data, error } = await supabase.auth.signInWithOAuth({
|
|
119832
|
+
provider,
|
|
119833
|
+
options: {
|
|
119834
|
+
redirectTo: callback.redirectTo,
|
|
119835
|
+
skipBrowserRedirect: true,
|
|
119836
|
+
data: { signup_source: "cli" }
|
|
119837
|
+
}
|
|
119838
|
+
});
|
|
119839
|
+
if (error || !data.url) {
|
|
119840
|
+
return {
|
|
119841
|
+
ok: false,
|
|
119842
|
+
code: "oauth_start_failed",
|
|
119843
|
+
error: error?.message ?? "Supabase did not return an OAuth URL."
|
|
119844
|
+
};
|
|
119845
|
+
}
|
|
119846
|
+
await (input.openExternal ?? openExternal)(data.url);
|
|
119847
|
+
const callbackResult = await callback.waitForCode();
|
|
119848
|
+
if (!callbackResult.ok) {
|
|
119849
|
+
return callbackResult;
|
|
119850
|
+
}
|
|
119851
|
+
const exchanged = await supabase.auth.exchangeCodeForSession(callbackResult.code);
|
|
119852
|
+
if (exchanged.error || !exchanged.data.session) {
|
|
119853
|
+
return {
|
|
119854
|
+
ok: false,
|
|
119855
|
+
code: "oauth_exchange_failed",
|
|
119856
|
+
error: exchanged.error?.message ?? "Supabase did not return a CLI session."
|
|
119857
|
+
};
|
|
119858
|
+
}
|
|
119859
|
+
const session = storedSessionFromSupabaseSession({
|
|
119860
|
+
appUrl,
|
|
119861
|
+
accessToken: exchanged.data.session.access_token,
|
|
119862
|
+
refreshToken: exchanged.data.session.refresh_token,
|
|
119863
|
+
expiresAt: exchanged.data.session.expires_at ?? null,
|
|
119864
|
+
userId: exchanged.data.session.user.id,
|
|
119865
|
+
email: exchanged.data.session.user.email ?? null
|
|
119866
|
+
});
|
|
119867
|
+
await writeStoredCliAuthSession(session);
|
|
119868
|
+
return { ok: true, session };
|
|
119869
|
+
} finally {
|
|
119870
|
+
await callback.close();
|
|
119871
|
+
}
|
|
119872
|
+
}
|
|
119873
|
+
async function fetchCliAuthConfig(appUrl, fetchImpl = globalThis.fetch) {
|
|
119874
|
+
const response = await fetchImpl(`${appUrl}/api/perch-terminal/cli-auth/config`, {
|
|
119875
|
+
method: "GET",
|
|
119876
|
+
headers: { Accept: "application/json" }
|
|
119877
|
+
});
|
|
119878
|
+
if (!response.ok) return { ok: false };
|
|
119879
|
+
return await response.json();
|
|
119880
|
+
}
|
|
119881
|
+
function selectOAuthProvider(providers, preferred) {
|
|
119882
|
+
const available = new Set(providers);
|
|
119883
|
+
if (preferred && available.has(preferred)) return preferred;
|
|
119884
|
+
if (available.has("google")) return "google";
|
|
119885
|
+
if (available.has("github")) return "github";
|
|
119886
|
+
return null;
|
|
119887
|
+
}
|
|
119888
|
+
function storedSessionFromSupabaseSession(input) {
|
|
119889
|
+
return {
|
|
119890
|
+
version: 1,
|
|
119891
|
+
appUrl: input.appUrl,
|
|
119892
|
+
accessToken: input.accessToken,
|
|
119893
|
+
refreshToken: input.refreshToken ?? null,
|
|
119894
|
+
expiresAt: input.expiresAt ?? null,
|
|
119895
|
+
userId: input.userId ?? null,
|
|
119896
|
+
email: input.email ?? null,
|
|
119897
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
119898
|
+
};
|
|
119899
|
+
}
|
|
119900
|
+
function createMemoryAuthStorage() {
|
|
119901
|
+
const values2 = /* @__PURE__ */ new Map();
|
|
119902
|
+
return {
|
|
119903
|
+
getItem: (key) => values2.get(key) ?? null,
|
|
119904
|
+
setItem: (key, value) => {
|
|
119905
|
+
values2.set(key, value);
|
|
119906
|
+
},
|
|
119907
|
+
removeItem: (key) => {
|
|
119908
|
+
values2.delete(key);
|
|
119909
|
+
}
|
|
119910
|
+
};
|
|
119911
|
+
}
|
|
119912
|
+
async function createOAuthCallbackServer(input) {
|
|
119913
|
+
let resolveResult = null;
|
|
119914
|
+
const resultPromise = new Promise((resolve5) => {
|
|
119915
|
+
resolveResult = resolve5;
|
|
119916
|
+
});
|
|
119917
|
+
const server = http.createServer((request, response) => {
|
|
119918
|
+
const requestUrl = new URL(request.url ?? "/", `http://${input.host}`);
|
|
119919
|
+
if (requestUrl.pathname !== CALLBACK_PATH) {
|
|
119920
|
+
response.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" });
|
|
119921
|
+
response.end("Not found");
|
|
119922
|
+
return;
|
|
119923
|
+
}
|
|
119924
|
+
const code = requestUrl.searchParams.get("code");
|
|
119925
|
+
const error = requestUrl.searchParams.get("error_description") ?? requestUrl.searchParams.get("error");
|
|
119926
|
+
if (!code) {
|
|
119927
|
+
response.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
|
|
119928
|
+
response.end(renderCallbackHtml(false));
|
|
119929
|
+
resolveResult?.({
|
|
119930
|
+
ok: false,
|
|
119931
|
+
code: "oauth_callback_missing_code",
|
|
119932
|
+
error: error ?? "OAuth callback did not include a code."
|
|
119933
|
+
});
|
|
119934
|
+
resolveResult = null;
|
|
119935
|
+
return;
|
|
119936
|
+
}
|
|
119937
|
+
response.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
119938
|
+
response.end(renderCallbackHtml(true));
|
|
119939
|
+
resolveResult?.({ ok: true, code });
|
|
119940
|
+
resolveResult = null;
|
|
119941
|
+
});
|
|
119942
|
+
await new Promise((resolve5, reject2) => {
|
|
119943
|
+
server.once("error", reject2);
|
|
119944
|
+
server.listen(0, input.host, () => {
|
|
119945
|
+
server.off("error", reject2);
|
|
119946
|
+
resolve5();
|
|
119947
|
+
});
|
|
119948
|
+
});
|
|
119949
|
+
const address = server.address();
|
|
119950
|
+
const redirectTo = `http://${input.host}:${address.port}${CALLBACK_PATH}`;
|
|
119951
|
+
const timeout = setTimeout(() => {
|
|
119952
|
+
resolveResult?.({
|
|
119953
|
+
ok: false,
|
|
119954
|
+
code: "oauth_timeout",
|
|
119955
|
+
error: "Timed out waiting for browser sign-in to complete."
|
|
119956
|
+
});
|
|
119957
|
+
resolveResult = null;
|
|
119958
|
+
}, input.timeoutMs);
|
|
119959
|
+
return {
|
|
119960
|
+
redirectTo,
|
|
119961
|
+
waitForCode: async () => resultPromise,
|
|
119962
|
+
close: async () => {
|
|
119963
|
+
clearTimeout(timeout);
|
|
119964
|
+
await new Promise((resolve5) => server.close(() => resolve5()));
|
|
119965
|
+
}
|
|
119966
|
+
};
|
|
119967
|
+
}
|
|
119968
|
+
function renderCallbackHtml(ok) {
|
|
119969
|
+
const title = ok ? "Perch CLI signed in" : "Perch CLI sign-in failed";
|
|
119970
|
+
const message = ok ? "You can close this tab and return to your terminal." : "Return to your terminal and try again.";
|
|
119971
|
+
return `<!doctype html><html><head><meta charset="utf-8"><title>${title}</title></head><body style="font-family:system-ui;padding:32px;background:#100d0b;color:#fff8f0"><h1>${title}</h1><p>${message}</p></body></html>`;
|
|
119972
|
+
}
|
|
119973
|
+
async function openExternal(url) {
|
|
119974
|
+
if (process.platform === "darwin") {
|
|
119975
|
+
await execFileAsync2("open", [url]);
|
|
119976
|
+
} else if (process.platform === "win32") {
|
|
119977
|
+
await execFileAsync2("cmd", ["/c", "start", "", url]);
|
|
119978
|
+
} else {
|
|
119979
|
+
await execFileAsync2("xdg-open", [url]);
|
|
119980
|
+
}
|
|
119981
|
+
}
|
|
119982
|
+
var execFileAsync2, DEFAULT_LOGIN_TIMEOUT_MS, CALLBACK_PATH;
|
|
119983
|
+
var init_cliStandaloneOAuth = __esm({
|
|
119984
|
+
"features/perchTerminal/runtime/cliHost/cliStandaloneOAuth.ts"() {
|
|
119985
|
+
"use strict";
|
|
119986
|
+
init_dist4();
|
|
119987
|
+
init_modelConnection();
|
|
119988
|
+
init_cliAuthSession();
|
|
119989
|
+
execFileAsync2 = promisify2(execFile2);
|
|
119990
|
+
DEFAULT_LOGIN_TIMEOUT_MS = 12e4;
|
|
119991
|
+
CALLBACK_PATH = "/callback";
|
|
119992
|
+
}
|
|
119993
|
+
});
|
|
119994
|
+
|
|
119995
|
+
// features/perchTerminal/runtime/cliHost/cliAuthRefresh.ts
|
|
119996
|
+
var cliAuthRefresh_exports = {};
|
|
119997
|
+
__export(cliAuthRefresh_exports, {
|
|
119998
|
+
ensureFreshCliAuthSession: () => ensureFreshCliAuthSession
|
|
119999
|
+
});
|
|
120000
|
+
async function ensureFreshCliAuthSession(input) {
|
|
120001
|
+
const session = input.session;
|
|
120002
|
+
if (!session?.accessToken?.trim()) return null;
|
|
120003
|
+
const nowSeconds = Math.floor(Date.now() / 1e3);
|
|
120004
|
+
const stillUsable = !session.expiresAt || session.expiresAt > nowSeconds + 90;
|
|
120005
|
+
if (stillUsable && !input.forceRefresh) return session;
|
|
120006
|
+
if (!session.refreshToken?.trim()) return null;
|
|
120007
|
+
const fetchImpl = input.fetchImpl ?? globalThis.fetch;
|
|
120008
|
+
if (typeof fetchImpl !== "function") return null;
|
|
120009
|
+
const appUrl = session.appUrl?.trim();
|
|
120010
|
+
if (!appUrl) return null;
|
|
120011
|
+
const config = await fetchCliAuthConfig(appUrl, fetchImpl).catch(() => ({ ok: false }));
|
|
120012
|
+
if (!config.ok || !config.supabaseUrl || !config.supabaseAnonKey) return null;
|
|
120013
|
+
const supabase = createClient(config.supabaseUrl, config.supabaseAnonKey, {
|
|
120014
|
+
auth: {
|
|
120015
|
+
autoRefreshToken: false,
|
|
120016
|
+
detectSessionInUrl: false,
|
|
120017
|
+
persistSession: false
|
|
120018
|
+
}
|
|
120019
|
+
});
|
|
120020
|
+
try {
|
|
120021
|
+
const { data, error } = await supabase.auth.refreshSession({
|
|
120022
|
+
refresh_token: session.refreshToken
|
|
120023
|
+
});
|
|
120024
|
+
if (error || !data.session) return null;
|
|
120025
|
+
const refreshed = {
|
|
120026
|
+
version: 1,
|
|
120027
|
+
appUrl: session.appUrl,
|
|
120028
|
+
accessToken: data.session.access_token,
|
|
120029
|
+
refreshToken: data.session.refresh_token ?? session.refreshToken,
|
|
120030
|
+
expiresAt: data.session.expires_at ?? null,
|
|
120031
|
+
userId: data.session.user?.id ?? session.userId ?? null,
|
|
120032
|
+
email: data.session.user?.email ?? session.email ?? null,
|
|
120033
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
120034
|
+
};
|
|
120035
|
+
await writeStoredCliAuthSession(refreshed);
|
|
120036
|
+
return refreshed;
|
|
120037
|
+
} catch {
|
|
120038
|
+
return null;
|
|
120039
|
+
}
|
|
120040
|
+
}
|
|
120041
|
+
var init_cliAuthRefresh = __esm({
|
|
120042
|
+
"features/perchTerminal/runtime/cliHost/cliAuthRefresh.ts"() {
|
|
120043
|
+
"use strict";
|
|
120044
|
+
init_dist4();
|
|
120045
|
+
init_cliStandaloneOAuth();
|
|
120046
|
+
init_cliAuthSession();
|
|
120047
|
+
}
|
|
120048
|
+
});
|
|
120049
|
+
|
|
119199
120050
|
// features/perchTerminal/runtime/modelRouter.ts
|
|
119200
120051
|
function roostUserSelectionBodyFields() {
|
|
119201
120052
|
const selection = getEffectiveRoostUserSelection();
|
|
@@ -119488,19 +120339,46 @@ async function callModelRouterViaServer(request, opts, endpoint = "/api/perch-te
|
|
|
119488
120339
|
durationMs: 0
|
|
119489
120340
|
};
|
|
119490
120341
|
}
|
|
120342
|
+
async function syncModelProxyAuthAfter401() {
|
|
120343
|
+
if (typeof globalThis.window !== "undefined") {
|
|
120344
|
+
await syncBrowserAuthSession();
|
|
120345
|
+
return null;
|
|
120346
|
+
}
|
|
120347
|
+
const { readStoredCliAuthSession: readStoredCliAuthSession2 } = await Promise.resolve().then(() => (init_cliAuthSession(), cliAuthSession_exports));
|
|
120348
|
+
const { ensureFreshCliAuthSession: ensureFreshCliAuthSession2 } = await Promise.resolve().then(() => (init_cliAuthRefresh(), cliAuthRefresh_exports));
|
|
120349
|
+
const fresh = await ensureFreshCliAuthSession2({
|
|
120350
|
+
session: await readStoredCliAuthSession2(),
|
|
120351
|
+
forceRefresh: true
|
|
120352
|
+
});
|
|
120353
|
+
if (!fresh?.accessToken?.trim()) return null;
|
|
120354
|
+
if (typeof process !== "undefined") {
|
|
120355
|
+
process.env[MODEL_CALL_PROXY_TOKEN_ENV] = fresh.accessToken;
|
|
120356
|
+
}
|
|
120357
|
+
return fresh.accessToken;
|
|
120358
|
+
}
|
|
120359
|
+
function withModelProxyAuthHeader(init, token) {
|
|
120360
|
+
if (!token?.trim()) return init;
|
|
120361
|
+
const headers = new Headers(init.headers);
|
|
120362
|
+
headers.set("Authorization", `Bearer ${token}`);
|
|
120363
|
+
return { ...init, headers };
|
|
120364
|
+
}
|
|
119491
120365
|
async function fetchModelProxyWithRetry(endpoint, init, options) {
|
|
119492
120366
|
const maxAttempts = Math.max(1, options?.maxAttempts ?? MODEL_PROXY_MAX_ATTEMPTS);
|
|
119493
120367
|
let lastError = null;
|
|
119494
120368
|
let authSynced = false;
|
|
120369
|
+
let requestInit = init;
|
|
119495
120370
|
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
119496
120371
|
try {
|
|
119497
|
-
const response = await fetch(endpoint,
|
|
119498
|
-
if (response.status === 401 && !authSynced
|
|
120372
|
+
const response = await fetch(endpoint, requestInit);
|
|
120373
|
+
if (response.status === 401 && !authSynced) {
|
|
119499
120374
|
authSynced = true;
|
|
119500
|
-
await
|
|
120375
|
+
const refreshedToken = await syncModelProxyAuthAfter401();
|
|
120376
|
+
if (refreshedToken) {
|
|
120377
|
+
requestInit = withModelProxyAuthHeader(requestInit, refreshedToken);
|
|
120378
|
+
}
|
|
119501
120379
|
options?.onRetry?.(attempt, "HTTP 401 (session sync)");
|
|
119502
120380
|
await response.body?.cancel().catch(() => void 0);
|
|
119503
|
-
await waitForModelProxyRetry(attempt,
|
|
120381
|
+
await waitForModelProxyRetry(attempt, requestInit.signal);
|
|
119504
120382
|
continue;
|
|
119505
120383
|
}
|
|
119506
120384
|
if (!isRetriableModelProxyStatus(response.status) || attempt === maxAttempts - 1) {
|
|
@@ -119508,12 +120386,12 @@ async function fetchModelProxyWithRetry(endpoint, init, options) {
|
|
|
119508
120386
|
}
|
|
119509
120387
|
options?.onRetry?.(attempt, `HTTP ${response.status}`);
|
|
119510
120388
|
await response.body?.cancel().catch(() => void 0);
|
|
119511
|
-
await waitForModelProxyRetry(attempt,
|
|
120389
|
+
await waitForModelProxyRetry(attempt, requestInit.signal);
|
|
119512
120390
|
} catch (error) {
|
|
119513
120391
|
if (isAbortLikeError(error) || attempt === maxAttempts - 1) throw error;
|
|
119514
120392
|
lastError = error;
|
|
119515
120393
|
options?.onRetry?.(attempt, error instanceof Error ? error.message : String(error));
|
|
119516
|
-
await waitForModelProxyRetry(attempt,
|
|
120394
|
+
await waitForModelProxyRetry(attempt, requestInit.signal);
|
|
119517
120395
|
}
|
|
119518
120396
|
}
|
|
119519
120397
|
throw lastError instanceof Error ? lastError : new Error(String(lastError ?? "Model proxy request failed"));
|
|
@@ -139132,7 +140010,6 @@ function offSandboxEvent(runId, handler) {
|
|
|
139132
140010
|
var LOCAL_WORKSPACE_ACCESS_UNAVAILABLE;
|
|
139133
140011
|
var init_bridge = __esm({
|
|
139134
140012
|
"features/perchTerminal/desktop/bridge.ts"() {
|
|
139135
|
-
"use strict";
|
|
139136
140013
|
LOCAL_WORKSPACE_ACCESS_UNAVAILABLE = "Local workspace access is unavailable. Open Perch Desktop or update the app.";
|
|
139137
140014
|
}
|
|
139138
140015
|
});
|
|
@@ -198995,10 +199872,10 @@ function evaluateBrowserAction(action, observation, context = {}) {
|
|
|
198995
199872
|
if (action.kind === "click" && !label && !("role" in action && action.role)) {
|
|
198996
199873
|
return block("empty_click_target", "Refusing a browser click with no target. Observe the page and name a button/link, or use a coordinate (visual) click.");
|
|
198997
199874
|
}
|
|
198998
|
-
if ((action.kind === "click" || action.kind === "visual_click"
|
|
199875
|
+
if ((action.kind === "click" || action.kind === "visual_click") && context.recentFailedClickSignatures?.includes(clickSignature(action))) {
|
|
198999
199876
|
return block(
|
|
199000
199877
|
"repeated_failed_click",
|
|
199001
|
-
`The action "${label || action.kind}" already failed on this surface. Not retrying the identical click.
|
|
199878
|
+
`The action "${label || action.kind}" already failed on this surface as a semantic/DOM click. Not retrying the identical semantic click. Escalate to a coordinate/visual (CUA) click on the same target before reporting a blocker.`
|
|
199002
199879
|
);
|
|
199003
199880
|
}
|
|
199004
199881
|
if (action.kind === "click" && (observation.app === "google_docs" || observation.app === "google_sheets")) {
|
|
@@ -199363,6 +200240,17 @@ async function safeBrowserAction(input, runtime = {}) {
|
|
|
199363
200240
|
if (target.kind === "ambiguous") {
|
|
199364
200241
|
lastFailure = fail("selector_ambiguous", target.message, startMs, latestSnapshot.text, latestSnapshot.url, target.candidates);
|
|
199365
200242
|
} else if (target.kind === "missing") {
|
|
200243
|
+
const escalated = await escalateMissingClickToCoordinate({
|
|
200244
|
+
input,
|
|
200245
|
+
snapshot: latestSnapshot,
|
|
200246
|
+
callTool,
|
|
200247
|
+
startMs,
|
|
200248
|
+
runtime
|
|
200249
|
+
});
|
|
200250
|
+
if (escalated) {
|
|
200251
|
+
clearPolicyClickFailure(input, runtime, latestSnapshot);
|
|
200252
|
+
return escalated;
|
|
200253
|
+
}
|
|
199366
200254
|
lastFailure = fail("selector_not_found", target.message, startMs, latestSnapshot.text, latestSnapshot.url);
|
|
199367
200255
|
} else {
|
|
199368
200256
|
const actionResult = await executeBrowserAction(input, target.target, callTool);
|
|
@@ -199499,7 +200387,7 @@ function enforceActionPolicy(input, preSnapshot, runtime, startMs) {
|
|
|
199499
200387
|
}
|
|
199500
200388
|
function recordPolicyClickFailure(input, runtime, snapshot) {
|
|
199501
200389
|
if (!runtime.policy) return;
|
|
199502
|
-
if (input.intent !== "click" && input.intent !== "visual_click"
|
|
200390
|
+
if (input.intent !== "click" && input.intent !== "visual_click") return;
|
|
199503
200391
|
const key = policySessionKey(runtime);
|
|
199504
200392
|
const signature = `${policySurfaceFingerprint(snapshot)}::${clickSignature(inputToProposedAction(input))}`;
|
|
199505
200393
|
const existing = failedClickMemory.get(key) ?? [];
|
|
@@ -199611,6 +200499,7 @@ async function clickGmailComposeSendButton(callTool) {
|
|
|
199611
200499
|
};
|
|
199612
200500
|
});
|
|
199613
200501
|
if (!target || target.result !== "target") return JSON.stringify(target || { result: "missing" });
|
|
200502
|
+
await page.evaluate(${PERCH_CURSOR_OVERLAY_SNIPPET}, { x: target.x, y: target.y });
|
|
199614
200503
|
await page.mouse.click(target.x, target.y);
|
|
199615
200504
|
return JSON.stringify({ result: "clicked", label: target.label, x: target.x, y: target.y, bounds: target.bounds, viewport: target.viewport });
|
|
199616
200505
|
}
|
|
@@ -199671,7 +200560,8 @@ async function dispatchSyntheticPaste(payload, runtime) {
|
|
|
199671
200560
|
() => {
|
|
199672
200561
|
const html = ${JSON.stringify(payload.html)};
|
|
199673
200562
|
const plain = ${JSON.stringify(payload.plain)};
|
|
199674
|
-
const
|
|
200563
|
+
const deepActive = () => { let n = document.activeElement; while (n && n.shadowRoot && n.shadowRoot.activeElement) n = n.shadowRoot.activeElement; return n; };
|
|
200564
|
+
const active = deepActive();
|
|
199675
200565
|
if (!active) return false;
|
|
199676
200566
|
const isLongBody = plain.length > ${TYPE_DIRECT_MAX_CHARS} || plain.includes("\\n");
|
|
199677
200567
|
if (isLongBody && active.tagName === "INPUT") return false;
|
|
@@ -199989,7 +200879,26 @@ async function resolveTargetForAction(input, snapshot, callTool) {
|
|
|
199989
200879
|
}
|
|
199990
200880
|
const resolved = resolveTarget(snapshot.text, input.targetDescriptor);
|
|
199991
200881
|
if (resolved.kind === "found") return resolved;
|
|
199992
|
-
if (input.intent === "type")
|
|
200882
|
+
if (input.intent === "type") {
|
|
200883
|
+
const desc = input.targetDescriptor;
|
|
200884
|
+
const named = (desc?.text ?? "").trim() || (desc?.ariaLabel ?? "").trim();
|
|
200885
|
+
if (named) {
|
|
200886
|
+
return {
|
|
200887
|
+
kind: "found",
|
|
200888
|
+
target: {
|
|
200889
|
+
kind: "semantic",
|
|
200890
|
+
descriptor: {
|
|
200891
|
+
...desc?.text ? { text: desc.text } : {},
|
|
200892
|
+
...desc?.ariaLabel ? { ariaLabel: desc.ariaLabel } : {},
|
|
200893
|
+
...desc?.role ? { role: desc.role } : {},
|
|
200894
|
+
nth: normalizeNth(desc?.nth)
|
|
200895
|
+
},
|
|
200896
|
+
matched: desc?.text ?? desc?.ariaLabel ?? "target"
|
|
200897
|
+
}
|
|
200898
|
+
};
|
|
200899
|
+
}
|
|
200900
|
+
return { kind: "found", target: null };
|
|
200901
|
+
}
|
|
199993
200902
|
return resolved;
|
|
199994
200903
|
}
|
|
199995
200904
|
function resolveTarget(snapshot, descriptor) {
|
|
@@ -200016,7 +200925,7 @@ function resolveTarget(snapshot, descriptor) {
|
|
|
200016
200925
|
if (!match) {
|
|
200017
200926
|
return {
|
|
200018
200927
|
kind: "missing",
|
|
200019
|
-
message: `No
|
|
200928
|
+
message: `No DOM match for "${desc.text ?? desc.ariaLabel ?? desc.role ?? "target"}" at nth=${nth}. Escalate to a coordinate/visual (CUA) click on the same target before reporting a blocker.`
|
|
200020
200929
|
};
|
|
200021
200930
|
}
|
|
200022
200931
|
if (matches.length > 1 && desc.nth === void 0) {
|
|
@@ -200048,7 +200957,7 @@ function resolveTarget(snapshot, descriptor) {
|
|
|
200048
200957
|
}
|
|
200049
200958
|
return {
|
|
200050
200959
|
kind: "missing",
|
|
200051
|
-
message: `No
|
|
200960
|
+
message: `No DOM match for "${desc.text ?? desc.ariaLabel ?? desc.role ?? "target"}". Escalate to a coordinate/visual (CUA) click on the same target before reporting a blocker.`
|
|
200052
200961
|
};
|
|
200053
200962
|
}
|
|
200054
200963
|
function liveResolverSource() {
|
|
@@ -200097,17 +201006,50 @@ function liveResolverSource() {
|
|
|
200097
201006
|
el.textContent,
|
|
200098
201007
|
].filter(Boolean).map((v) => String(v).trim()).find(Boolean) || "";
|
|
200099
201008
|
};
|
|
200100
|
-
const candidates = () =>
|
|
200101
|
-
|
|
200102
|
-
|
|
200103
|
-
|
|
200104
|
-
|
|
200105
|
-
|
|
200106
|
-
|
|
200107
|
-
|
|
200108
|
-
|
|
200109
|
-
|
|
200110
|
-
|
|
201009
|
+
const candidates = () => {
|
|
201010
|
+
const INTERACTIVE_SELECTOR = [
|
|
201011
|
+
"button",
|
|
201012
|
+
"a[href]",
|
|
201013
|
+
"input",
|
|
201014
|
+
"textarea",
|
|
201015
|
+
"select",
|
|
201016
|
+
"[role]",
|
|
201017
|
+
"[aria-label]",
|
|
201018
|
+
"[data-tooltip]",
|
|
201019
|
+
"[contenteditable='true']"
|
|
201020
|
+
].join(",");
|
|
201021
|
+
// Collect open shadow roots recursively so the resolver reaches controls
|
|
201022
|
+
// inside web components (e.g. Reddit/shreddit). Shadow-DOM elements share
|
|
201023
|
+
// the top document's viewport coordinate space, so getBoundingClientRect
|
|
201024
|
+
// (used for the coordinate click) stays correct. Same-origin iframes are
|
|
201025
|
+
// intentionally skipped: their rects are in the frame's own space and
|
|
201026
|
+
// would misplace the click.
|
|
201027
|
+
const roots = [document];
|
|
201028
|
+
const seenRoots = new Set();
|
|
201029
|
+
const walk = (root) => {
|
|
201030
|
+
let hosts;
|
|
201031
|
+
try { hosts = root.querySelectorAll("*"); } catch (e) { return; }
|
|
201032
|
+
for (const host of hosts) {
|
|
201033
|
+
const sr = host.shadowRoot;
|
|
201034
|
+
if (sr && !seenRoots.has(sr)) {
|
|
201035
|
+
seenRoots.add(sr);
|
|
201036
|
+
roots.push(sr);
|
|
201037
|
+
walk(sr);
|
|
201038
|
+
}
|
|
201039
|
+
}
|
|
201040
|
+
};
|
|
201041
|
+
walk(document);
|
|
201042
|
+
const out = [];
|
|
201043
|
+
const seenEl = new Set();
|
|
201044
|
+
for (const root of roots) {
|
|
201045
|
+
let found;
|
|
201046
|
+
try { found = root.querySelectorAll(INTERACTIVE_SELECTOR); } catch (e) { continue; }
|
|
201047
|
+
for (const el of found) {
|
|
201048
|
+
if (!seenEl.has(el) && visible(el)) { seenEl.add(el); out.push(el); }
|
|
201049
|
+
}
|
|
201050
|
+
}
|
|
201051
|
+
return out;
|
|
201052
|
+
};
|
|
200111
201053
|
const rankMatch = (el, descriptor) => {
|
|
200112
201054
|
const label = labelFor(el);
|
|
200113
201055
|
const aria = el.getAttribute("aria-label") || "";
|
|
@@ -200145,6 +201087,32 @@ function liveResolverSource() {
|
|
|
200145
201087
|
};
|
|
200146
201088
|
`;
|
|
200147
201089
|
}
|
|
201090
|
+
async function escalateMissingClickToCoordinate(args) {
|
|
201091
|
+
const { input, snapshot, callTool, startMs, runtime } = args;
|
|
201092
|
+
if (input.intent !== "click") return null;
|
|
201093
|
+
const descriptor = input.targetDescriptor;
|
|
201094
|
+
const label = descriptor?.text ?? descriptor?.ariaLabel ?? descriptor?.role;
|
|
201095
|
+
if (!descriptor || !label) return null;
|
|
201096
|
+
const coordinateTarget = { kind: "semantic", descriptor, matched: label };
|
|
201097
|
+
const clicked = await clickTargetTrusted(callTool, coordinateTarget, label);
|
|
201098
|
+
if (!clicked.ok) return null;
|
|
201099
|
+
const outcome = await waitForExpectedOutcome({
|
|
201100
|
+
input,
|
|
201101
|
+
callTool,
|
|
201102
|
+
preSnapshot: snapshot,
|
|
201103
|
+
startMs,
|
|
201104
|
+
signal: runtime.signal
|
|
201105
|
+
});
|
|
201106
|
+
if (!outcome.ok) return null;
|
|
201107
|
+
return {
|
|
201108
|
+
...outcome,
|
|
201109
|
+
evidence: {
|
|
201110
|
+
...outcome.evidence,
|
|
201111
|
+
matched: `${clicked.matched} \xB7 escalated to coordinate (CUA) click`,
|
|
201112
|
+
...clicked.actionGeometry ? { actionGeometry: clicked.actionGeometry } : {}
|
|
201113
|
+
}
|
|
201114
|
+
};
|
|
201115
|
+
}
|
|
200148
201116
|
async function executeBrowserAction(input, target, callTool) {
|
|
200149
201117
|
if (input.intent === "snapshot" || input.intent === "extract") return { ok: true, matched: input.intent };
|
|
200150
201118
|
if (input.intent === "navigate") {
|
|
@@ -200173,6 +201141,8 @@ async function executeBrowserAction(input, target, callTool) {
|
|
|
200173
201141
|
if (!clicked.ok) return clicked;
|
|
200174
201142
|
const typed = await typeViaRealKeys(callTool, input.text ?? "", clicked.matched);
|
|
200175
201143
|
if (typed.ok) {
|
|
201144
|
+
const verified = await verifyTypedTextLanded(callTool, input.text ?? "", clicked.matched);
|
|
201145
|
+
if (!verified.ok) return verified;
|
|
200176
201146
|
return {
|
|
200177
201147
|
ok: true,
|
|
200178
201148
|
matched: `${clicked.matched} then typed into focused surface`,
|
|
@@ -200182,7 +201152,10 @@ async function executeBrowserAction(input, target, callTool) {
|
|
|
200182
201152
|
return typed;
|
|
200183
201153
|
}
|
|
200184
201154
|
if (input.intent === "type") {
|
|
200185
|
-
|
|
201155
|
+
const typed = await enterText(callTool, target, input.text ?? "");
|
|
201156
|
+
if (!typed.ok) return typed;
|
|
201157
|
+
const verified = await verifyTypedTextLanded(callTool, input.text ?? "", typed.matched);
|
|
201158
|
+
return verified.ok ? typed : verified;
|
|
200186
201159
|
}
|
|
200187
201160
|
if (!target) return { ok: false, error: "target could not be resolved" };
|
|
200188
201161
|
if (target.kind === "selector") {
|
|
@@ -200193,8 +201166,24 @@ async function executeBrowserAction(input, target, callTool) {
|
|
|
200193
201166
|
}
|
|
200194
201167
|
return { ok: false, error: `Unsupported browser intent: ${input.intent}` };
|
|
200195
201168
|
}
|
|
201169
|
+
function labelGroundingVariants(label) {
|
|
201170
|
+
const trimmed = (label ?? "").trim();
|
|
201171
|
+
if (!trimmed || trimmed.toLowerCase() === "visual target") return [];
|
|
201172
|
+
const variants = [trimmed];
|
|
201173
|
+
const stripped = trimmed.replace(/\b(button|btn|field|input|textbox|tab|link|icon|menu|option|box|area|control)\b/gi, "").replace(/\s{2,}/g, " ").trim();
|
|
201174
|
+
if (stripped && stripped.toLowerCase() !== trimmed.toLowerCase()) variants.push(stripped);
|
|
201175
|
+
return variants;
|
|
201176
|
+
}
|
|
200196
201177
|
async function clickVisualTarget(callTool, visualTarget) {
|
|
200197
201178
|
if (!visualTarget) return { ok: false, error: "visual target requires x and y" };
|
|
201179
|
+
for (const candidate of labelGroundingVariants(visualTarget.label)) {
|
|
201180
|
+
const grounded = await clickTargetTrusted(
|
|
201181
|
+
callTool,
|
|
201182
|
+
{ kind: "semantic", descriptor: { text: candidate }, matched: candidate },
|
|
201183
|
+
candidate
|
|
201184
|
+
);
|
|
201185
|
+
if (grounded.ok) return grounded;
|
|
201186
|
+
}
|
|
200198
201187
|
const x = Number(visualTarget.x);
|
|
200199
201188
|
const y = Number(visualTarget.y);
|
|
200200
201189
|
if (!Number.isFinite(x) || !Number.isFinite(y)) {
|
|
@@ -200212,6 +201201,7 @@ async function clickVisualTarget(callTool, visualTarget) {
|
|
|
200212
201201
|
const py = coordinateSpace === "viewport_pixels" ? rawY : rawY * viewport.height;
|
|
200213
201202
|
const safeX = Math.max(1, Math.min(viewport.width - 1, px));
|
|
200214
201203
|
const safeY = Math.max(1, Math.min(viewport.height - 1, py));
|
|
201204
|
+
await page.evaluate(${PERCH_CURSOR_OVERLAY_SNIPPET}, { x: safeX, y: safeY });
|
|
200215
201205
|
await page.mouse.click(safeX, safeY);
|
|
200216
201206
|
return JSON.stringify({
|
|
200217
201207
|
result: "clicked",
|
|
@@ -200235,17 +201225,78 @@ async function clickVisualTarget(callTool, visualTarget) {
|
|
|
200235
201225
|
}
|
|
200236
201226
|
return { ok: false, error: text || "visual click failed" };
|
|
200237
201227
|
}
|
|
201228
|
+
async function verifyTypedTextLanded(callTool, text, matched) {
|
|
201229
|
+
if (!text.trim()) return { ok: true };
|
|
201230
|
+
const expr = `() => {
|
|
201231
|
+
const url = String(location.href || "");
|
|
201232
|
+
if (/docs\\.google\\.com\\/(document|spreadsheets|presentation)\\//i.test(url)) return "PERCH_DOCS_BLIND";
|
|
201233
|
+
const deepActive = () => { let n = document.activeElement; while (n && n.shadowRoot && n.shadowRoot.activeElement) n = n.shadowRoot.activeElement; return n; };
|
|
201234
|
+
const el = deepActive();
|
|
201235
|
+
if (!el) return "PERCH_UNKNOWN";
|
|
201236
|
+
const tag = (el.tagName || "").toLowerCase();
|
|
201237
|
+
const readableEditable =
|
|
201238
|
+
el.isContentEditable ||
|
|
201239
|
+
tag === "textarea" ||
|
|
201240
|
+
(tag === "input") ||
|
|
201241
|
+
("value" in el && typeof el.value === "string");
|
|
201242
|
+
if (!readableEditable) return "PERCH_UNKNOWN";
|
|
201243
|
+
const read = (n) => {
|
|
201244
|
+
if ("value" in n && typeof n.value === "string") return n.value;
|
|
201245
|
+
if (n.isContentEditable) return n.textContent || "";
|
|
201246
|
+
return "";
|
|
201247
|
+
};
|
|
201248
|
+
const val = String(read(el) || "").replace(/\\s+/g, " ").trim();
|
|
201249
|
+
return val ? "PERCH_LANDED" : "PERCH_EMPTY";
|
|
201250
|
+
}`;
|
|
201251
|
+
const result2 = await callTool({
|
|
201252
|
+
serverName: "playwright",
|
|
201253
|
+
toolName: "browser_evaluate",
|
|
201254
|
+
args: { function: expr }
|
|
201255
|
+
});
|
|
201256
|
+
if (!result2.ok) return { ok: true };
|
|
201257
|
+
if (firstText(result2.content).trim() !== "PERCH_EMPTY") return { ok: true };
|
|
201258
|
+
return {
|
|
201259
|
+
ok: false,
|
|
201260
|
+
error: `Typed text did not land in "${matched}" \u2014 the focused field is still empty. Click the field with a visual_click to focus the real editable, then type again (typing goes into the focused field). Do not repeat the identical type into an unfocused field.`
|
|
201261
|
+
};
|
|
201262
|
+
}
|
|
201263
|
+
async function hasEditableFocus(callTool) {
|
|
201264
|
+
const expr = `() => {
|
|
201265
|
+
const deepActive = () => { let n = document.activeElement; while (n && n.shadowRoot && n.shadowRoot.activeElement) n = n.shadowRoot.activeElement; return n; };
|
|
201266
|
+
const el = deepActive();
|
|
201267
|
+
if (!el) return false;
|
|
201268
|
+
if (el.isContentEditable) return true;
|
|
201269
|
+
const tag = (el.tagName || "").toLowerCase();
|
|
201270
|
+
if (tag === "textarea") return true;
|
|
201271
|
+
if (tag === "input") {
|
|
201272
|
+
const t = (el.getAttribute("type") || "text").toLowerCase();
|
|
201273
|
+
return !["button","submit","reset","checkbox","radio","range","file","image","color"].includes(t);
|
|
201274
|
+
}
|
|
201275
|
+
if ("value" in el && typeof el.value === "string") return true;
|
|
201276
|
+
return false;
|
|
201277
|
+
}`;
|
|
201278
|
+
const result2 = await callTool({
|
|
201279
|
+
serverName: "playwright",
|
|
201280
|
+
toolName: "browser_evaluate",
|
|
201281
|
+
args: { function: expr }
|
|
201282
|
+
});
|
|
201283
|
+
if (!result2.ok) return false;
|
|
201284
|
+
return /^\s*true\s*$/i.test(firstText(result2.content));
|
|
201285
|
+
}
|
|
200238
201286
|
async function enterText(callTool, target, text) {
|
|
200239
201287
|
const matched = target?.matched ?? "focused field";
|
|
200240
201288
|
const isLong = text.length > TYPE_DIRECT_MAX_CHARS || text.includes("\n");
|
|
201289
|
+
const alreadyFocused = await hasEditableFocus(callTool);
|
|
200241
201290
|
if (!isLong) {
|
|
200242
|
-
|
|
201291
|
+
let focusGeometry;
|
|
201292
|
+
if (!alreadyFocused && target) {
|
|
200243
201293
|
const focused = await focusTarget(callTool, target, matched);
|
|
200244
|
-
if (
|
|
201294
|
+
if (focused.ok) focusGeometry = focused.actionGeometry;
|
|
200245
201295
|
}
|
|
200246
|
-
|
|
201296
|
+
const typed = await typeViaRealKeys(callTool, text, matched);
|
|
201297
|
+
return typed.ok && focusGeometry ? { ...typed, actionGeometry: focusGeometry } : typed;
|
|
200247
201298
|
}
|
|
200248
|
-
if (target) {
|
|
201299
|
+
if (!alreadyFocused && target) {
|
|
200249
201300
|
const focused = await focusTarget(callTool, target, matched);
|
|
200250
201301
|
if (focused.ok) {
|
|
200251
201302
|
const pasted2 = await dispatchSyntheticPaste({ html: null, plain: text }, { callTool });
|
|
@@ -200273,7 +201324,8 @@ function shouldRefuseRealKeys(error) {
|
|
|
200273
201324
|
async function typeIntoActiveElement(callTool, text) {
|
|
200274
201325
|
const json = JSON.stringify(text);
|
|
200275
201326
|
const expr = `() => {
|
|
200276
|
-
const
|
|
201327
|
+
const deepActive = () => { let n = document.activeElement; while (n && n.shadowRoot && n.shadowRoot.activeElement) n = n.shadowRoot.activeElement; return n; };
|
|
201328
|
+
const el = deepActive();
|
|
200277
201329
|
if (!el) throw new Error("no active element to type into");
|
|
200278
201330
|
const isLongBody = ${JSON.stringify(text.length > TYPE_DIRECT_MAX_CHARS || text.includes("\n"))};
|
|
200279
201331
|
if (isLongBody && el.tagName === "INPUT") {
|
|
@@ -200359,6 +201411,7 @@ async function clickTargetTrusted(callTool, target, matched) {
|
|
|
200359
201411
|
};
|
|
200360
201412
|
}, { descriptor: ${JSON.stringify(descriptor)}, selector: ${JSON.stringify(selector)} });
|
|
200361
201413
|
if (!target || target.result !== "target") return JSON.stringify(target || { result: "missing" });
|
|
201414
|
+
await page.evaluate(${PERCH_CURSOR_OVERLAY_SNIPPET}, { x: target.x, y: target.y });
|
|
200362
201415
|
await page.mouse.click(target.x, target.y);
|
|
200363
201416
|
return JSON.stringify({ result: "clicked", label: target.label, x: target.x, y: target.y, bounds: target.bounds, viewport: target.viewport });
|
|
200364
201417
|
}`;
|
|
@@ -200686,7 +201739,7 @@ function delay(ms, signal) {
|
|
|
200686
201739
|
);
|
|
200687
201740
|
});
|
|
200688
201741
|
}
|
|
200689
|
-
var DEFAULT_TIMEOUT_MS, MAX_TIMEOUT_MS, SNAPSHOT_REF_PREFIX, HUMAN_RECOVERY_FAILURES, MODAL_DISMISSERS, failedClickMemory, FAILED_CLICK_MEMORY_MAX, TYPE_DIRECT_MAX_CHARS, PRESS_KEY_LOOP_MAX_CHARS;
|
|
201742
|
+
var DEFAULT_TIMEOUT_MS, MAX_TIMEOUT_MS, SNAPSHOT_REF_PREFIX, HUMAN_RECOVERY_FAILURES, MODAL_DISMISSERS, failedClickMemory, FAILED_CLICK_MEMORY_MAX, PERCH_CURSOR_OVERLAY_SNIPPET, TYPE_DIRECT_MAX_CHARS, PRESS_KEY_LOOP_MAX_CHARS;
|
|
200690
201743
|
var init_safeBrowserAction = __esm({
|
|
200691
201744
|
"features/perchTerminal/runtime/playwright/safeBrowserAction.ts"() {
|
|
200692
201745
|
"use strict";
|
|
@@ -200713,6 +201766,26 @@ var init_safeBrowserAction = __esm({
|
|
|
200713
201766
|
];
|
|
200714
201767
|
failedClickMemory = /* @__PURE__ */ new Map();
|
|
200715
201768
|
FAILED_CLICK_MEMORY_MAX = 12;
|
|
201769
|
+
PERCH_CURSOR_OVERLAY_SNIPPET = `(coord) => {
|
|
201770
|
+
try {
|
|
201771
|
+
const id = "__perch_operator_cursor__";
|
|
201772
|
+
let el = document.getElementById(id);
|
|
201773
|
+
if (!el) {
|
|
201774
|
+
el = document.createElement("div");
|
|
201775
|
+
el.id = id;
|
|
201776
|
+
el.setAttribute("aria-hidden", "true");
|
|
201777
|
+
el.style.cssText = "position:fixed;left:0;top:0;z-index:2147483647;pointer-events:none;width:24px;height:30px;transform-origin:0 0;transition:left .1s ease,top .1s ease,opacity .4s ease;filter:drop-shadow(0 1px 2px rgba(0,0,0,.6));";
|
|
201778
|
+
el.innerHTML = '<svg width="24" height="30" viewBox="0 0 16 20" xmlns="http://www.w3.org/2000/svg"><path d="M0 0 L0 15 L4.5 11.6 L6.9 17.2 L9.2 16.2 L6.8 10.8 L11.8 10.8 Z" fill="#111827" stroke="#ffffff" stroke-width="1.4" stroke-linejoin="round"/></svg>';
|
|
201779
|
+
(document.body || document.documentElement).appendChild(el);
|
|
201780
|
+
}
|
|
201781
|
+
// The arrow tip is at the SVG origin (0,0), so left/top place the tip on the point.
|
|
201782
|
+
el.style.left = coord.x + "px";
|
|
201783
|
+
el.style.top = coord.y + "px";
|
|
201784
|
+
el.style.opacity = "1";
|
|
201785
|
+
if (el.__perchHide) clearTimeout(el.__perchHide);
|
|
201786
|
+
el.__perchHide = setTimeout(function () { el.style.opacity = "0.6"; }, 1500);
|
|
201787
|
+
} catch (e) {}
|
|
201788
|
+
}`;
|
|
200716
201789
|
TYPE_DIRECT_MAX_CHARS = 200;
|
|
200717
201790
|
PRESS_KEY_LOOP_MAX_CHARS = 4e3;
|
|
200718
201791
|
}
|
|
@@ -213692,8 +214765,8 @@ var init_barSeries = __esm({
|
|
|
213692
214765
|
});
|
|
213693
214766
|
|
|
213694
214767
|
// lib/perchMarketDesk/data/fixtureStore.ts
|
|
213695
|
-
import
|
|
213696
|
-
import
|
|
214768
|
+
import fs10 from "node:fs";
|
|
214769
|
+
import path11 from "node:path";
|
|
213697
214770
|
function createFixtureStore(input) {
|
|
213698
214771
|
const instruments = [...input.instruments].sort((a, b2) => a.ticker.localeCompare(b2.ticker));
|
|
213699
214772
|
const seriesByTicker = /* @__PURE__ */ new Map();
|
|
@@ -213740,22 +214813,22 @@ function createFixtureStore(input) {
|
|
|
213740
214813
|
};
|
|
213741
214814
|
}
|
|
213742
214815
|
function loadFixtureStoreFromDir(dir) {
|
|
213743
|
-
const instrumentsPath =
|
|
213744
|
-
const instruments = JSON.parse(
|
|
214816
|
+
const instrumentsPath = path11.join(dir, "instruments.json");
|
|
214817
|
+
const instruments = JSON.parse(fs10.readFileSync(instrumentsPath, "utf8"));
|
|
213745
214818
|
const bars = [];
|
|
213746
|
-
const barsDir =
|
|
213747
|
-
for (const file of
|
|
214819
|
+
const barsDir = path11.join(dir, "bars");
|
|
214820
|
+
for (const file of fs10.readdirSync(barsDir)) {
|
|
213748
214821
|
if (!file.endsWith(".json")) continue;
|
|
213749
|
-
const ticker =
|
|
213750
|
-
const rows = JSON.parse(
|
|
214822
|
+
const ticker = path11.basename(file, ".json");
|
|
214823
|
+
const rows = JSON.parse(fs10.readFileSync(path11.join(barsDir, file), "utf8"));
|
|
213751
214824
|
for (const row of rows) bars.push({ ...row, ticker: row.ticker ?? ticker });
|
|
213752
214825
|
}
|
|
213753
214826
|
let fundamentals;
|
|
213754
|
-
const fundamentalsPath =
|
|
213755
|
-
if (
|
|
213756
|
-
fundamentals = JSON.parse(
|
|
214827
|
+
const fundamentalsPath = path11.join(dir, "fundamentals.json");
|
|
214828
|
+
if (fs10.existsSync(fundamentalsPath)) {
|
|
214829
|
+
fundamentals = JSON.parse(fs10.readFileSync(fundamentalsPath, "utf8"));
|
|
213757
214830
|
}
|
|
213758
|
-
return createFixtureStore({ instruments, bars, fundamentals, label: `dir:${
|
|
214831
|
+
return createFixtureStore({ instruments, bars, fundamentals, label: `dir:${path11.basename(dir)}` });
|
|
213759
214832
|
}
|
|
213760
214833
|
var init_fixtureStore = __esm({
|
|
213761
214834
|
"lib/perchMarketDesk/data/fixtureStore.ts"() {
|
|
@@ -218837,9 +219910,19 @@ async function executeToolBatch(toolCalls, ctx) {
|
|
|
218837
219910
|
{ type: "image_url", image_url: { url: execution.imageDataUrl } }
|
|
218838
219911
|
]
|
|
218839
219912
|
});
|
|
219913
|
+
} else if (isOperatorScreenshot) {
|
|
219914
|
+
const readout = await describeOperatorScreenshotViaVision(execution.imageDataUrl, ctx);
|
|
219915
|
+
ctx.messages.push({
|
|
219916
|
+
role: "user",
|
|
219917
|
+
content: readout ? `${OPERATOR_SCREENSHOT_MARKER} Vision readout of the current screen after that action (the selected model is text-only, so a vision sidecar looked for you). Treat this as the source of truth. For a coordinate click, use the approximate x/y viewport fractions named below.
|
|
219918
|
+
|
|
219919
|
+
${readout}` : `${OPERATOR_SCREENSHOT_MARKER} A current browser screenshot was captured after that action, but this selected model is text-only and the vision sidecar was unavailable. Call visionInspect with {"currentScreen": true, "prompt": "describe what I need to verify"} before any coordinate click. Otherwise continue from the browser snapshot/perception text.`
|
|
219920
|
+
});
|
|
218840
219921
|
} else {
|
|
218841
|
-
|
|
218842
|
-
|
|
219922
|
+
ctx.messages.push({
|
|
219923
|
+
role: "user",
|
|
219924
|
+
content: `${LOCAL_VISUAL_ATTACHMENT_MARKER} A visual preview for ${label} was captured, but this selected model is text-only. If visual details matter, call visionInspect on the same file path/localSourceId with {"prompt": "read this file visually"}. Otherwise continue from any extracted text in the tool result.`
|
|
219925
|
+
});
|
|
218843
219926
|
}
|
|
218844
219927
|
}
|
|
218845
219928
|
}
|
|
@@ -218860,6 +219943,26 @@ function parseImageDataUrlForTool(dataUrl) {
|
|
|
218860
219943
|
base64
|
|
218861
219944
|
};
|
|
218862
219945
|
}
|
|
219946
|
+
async function describeOperatorScreenshotViaVision(dataUrl, ctx) {
|
|
219947
|
+
if (ctx.signal?.aborted) return null;
|
|
219948
|
+
const parsed = parseImageDataUrlForTool(dataUrl);
|
|
219949
|
+
if (!parsed) return null;
|
|
219950
|
+
try {
|
|
219951
|
+
const result2 = await callVisionLaneModel({
|
|
219952
|
+
founderModelSelection: ctx.founderModelSelection,
|
|
219953
|
+
imageBase64: parsed.base64,
|
|
219954
|
+
mimeType: parsed.mimeType,
|
|
219955
|
+
userPrompt: OPERATOR_VISION_OBSERVER_PROMPT,
|
|
219956
|
+
signal: ctx.signal ?? void 0,
|
|
219957
|
+
userId: ctx.userId ?? null,
|
|
219958
|
+
workspaceId: ctx.workspaceId ?? null,
|
|
219959
|
+
runId: ctx.runId ?? null
|
|
219960
|
+
});
|
|
219961
|
+
if (result2.ok && result2.text.trim()) return result2.text.trim();
|
|
219962
|
+
} catch {
|
|
219963
|
+
}
|
|
219964
|
+
return null;
|
|
219965
|
+
}
|
|
218863
219966
|
function currentScreenVisionToolCall(input) {
|
|
218864
219967
|
const { toolCall, imageDataUrl } = input;
|
|
218865
219968
|
if (toolCall.name !== TOOL_NAMES.visionInspect) return { toolCall };
|
|
@@ -218918,11 +220021,12 @@ function updateConsecutiveRequiredArgFailures(counts, execution) {
|
|
|
218918
220021
|
function now6() {
|
|
218919
220022
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
218920
220023
|
}
|
|
218921
|
-
var MAX_PARALLEL_TOOL_CALLS, LOCAL_VISUAL_ATTACHMENT_MARKER;
|
|
220024
|
+
var MAX_PARALLEL_TOOL_CALLS, LOCAL_VISUAL_ATTACHMENT_MARKER, OPERATOR_VISION_OBSERVER_PROMPT;
|
|
218922
220025
|
var init_toolBatchExecutor = __esm({
|
|
218923
220026
|
"features/perchTerminal/runtime/toolLoop/toolBatchExecutor.ts"() {
|
|
218924
220027
|
"use strict";
|
|
218925
220028
|
init_modelRegistry();
|
|
220029
|
+
init_visionLaneCall();
|
|
218926
220030
|
init_toolExecutor();
|
|
218927
220031
|
init_fileDiff();
|
|
218928
220032
|
init_toolNames();
|
|
@@ -218935,6 +220039,14 @@ var init_toolBatchExecutor = __esm({
|
|
|
218935
220039
|
init_sourceReadPolicy();
|
|
218936
220040
|
MAX_PARALLEL_TOOL_CALLS = 8;
|
|
218937
220041
|
LOCAL_VISUAL_ATTACHMENT_MARKER = "[local-visual]";
|
|
220042
|
+
OPERATOR_VISION_OBSERVER_PROMPT = [
|
|
220043
|
+
"You are the eyes of a browser operator that cannot see this screen.",
|
|
220044
|
+
"Describe the current browser screen so the operator can act precisely.",
|
|
220045
|
+
"List the key interactive controls (buttons, links, text fields, tabs, menus) with their exact visible labels and approximate position as viewport fractions x,y in [0,1], where 0,0 is the top-left and 1,1 is the bottom-right.",
|
|
220046
|
+
"State which field or control is focused or selected, the values already entered in visible fields, and any error, confirmation, or toast text.",
|
|
220047
|
+
"If a crosshair or marker is drawn on the image, say which element it is pointing at.",
|
|
220048
|
+
"Plain text only. Do not suggest or decide the next action."
|
|
220049
|
+
].join(" ");
|
|
218938
220050
|
}
|
|
218939
220051
|
});
|
|
218940
220052
|
|
|
@@ -219379,8 +220491,11 @@ async function prepareLoopMessagesForSend(input) {
|
|
|
219379
220491
|
toolDefinitionsJson,
|
|
219380
220492
|
modelContext
|
|
219381
220493
|
});
|
|
219382
|
-
const
|
|
219383
|
-
const
|
|
220494
|
+
const budgetModel = typeof input.effectiveContextLimitTokens === "number" && input.effectiveContextLimitTokens > 0 ? { ...input.option, effectiveLimitTokens: input.effectiveContextLimitTokens } : input.option;
|
|
220495
|
+
const contextLimitTokens = resolveEffectiveContextLimit(
|
|
220496
|
+
budgetModel
|
|
220497
|
+
);
|
|
220498
|
+
const compactThreshold = getAutoCompactThreshold(budgetModel);
|
|
219384
220499
|
const postCompactTargetTokens = resolvePostCompactTargetTokens(contextLimitTokens);
|
|
219385
220500
|
const recentTailTokenBudget = resolveRecentTailBudgetTokens({
|
|
219386
220501
|
postCompactTargetTokens,
|
|
@@ -219406,11 +220521,6 @@ async function prepareLoopMessagesForSend(input) {
|
|
|
219406
220521
|
toolDefinitionsJson,
|
|
219407
220522
|
modelContext
|
|
219408
220523
|
});
|
|
219409
|
-
input.onEvent?.({
|
|
219410
|
-
type: "activity_delta",
|
|
219411
|
-
text: `Cleared ${microcompactedToolResults} old tool result(s) to free context.`,
|
|
219412
|
-
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
219413
|
-
});
|
|
219414
220524
|
}
|
|
219415
220525
|
}
|
|
219416
220526
|
if (autoCompactEnabled && anchoredTokens(breakdown.total) >= compactThreshold) {
|
|
@@ -223197,6 +224307,7 @@ async function runFlockTurn(input, deps, options = {}) {
|
|
|
223197
224307
|
emitWorkerUpdate(emit, plan, worker, "queued");
|
|
223198
224308
|
}
|
|
223199
224309
|
const spawnFn = options.spawnWorkerFn ?? spawnWorker;
|
|
224310
|
+
let turnInput = input;
|
|
223200
224311
|
const outcomes = [];
|
|
223201
224312
|
const revisionOutcomes = [];
|
|
223202
224313
|
let revisionReport = null;
|
|
@@ -223245,6 +224356,7 @@ async function runFlockTurn(input, deps, options = {}) {
|
|
|
223245
224356
|
toolCallsReserved += reservedToolCalls;
|
|
223246
224357
|
emitWorkerUpdate(emit, plan, worker, "running");
|
|
223247
224358
|
try {
|
|
224359
|
+
turnInput = await refreshFlockCliAuth(turnInput);
|
|
223248
224360
|
const context = contextOverride ?? buildWorkerContext(worker, sharedContext, outputByFlockWorkerId, plan);
|
|
223249
224361
|
const objective = worker.role === "worker" && contextContainsSources(context) ? `${worker.objective}
|
|
223250
224362
|
${FLOCK_GROUNDING_CONTRACT}` : worker.objective;
|
|
@@ -223256,18 +224368,18 @@ ${FLOCK_GROUNDING_CONTRACT}` : worker.objective;
|
|
|
223256
224368
|
maxIterations: worker.maxIterations,
|
|
223257
224369
|
maxToolCalls: reservedToolCalls
|
|
223258
224370
|
},
|
|
223259
|
-
buildSpawnContext(
|
|
224371
|
+
buildSpawnContext(turnInput, plan.flockId, flockRun.controller.signal, emit, worker)
|
|
223260
224372
|
);
|
|
223261
224373
|
toolCallsReserved -= reservedToolCalls;
|
|
223262
224374
|
toolCallsUsed += Math.min(result2.toolCalls ?? 0, reservedToolCalls);
|
|
223263
|
-
const
|
|
224375
|
+
const status = flockWorkerStatusFromSpawnResult(result2);
|
|
223264
224376
|
const outcome = {
|
|
223265
224377
|
worker,
|
|
223266
224378
|
result: result2,
|
|
223267
|
-
status
|
|
224379
|
+
status,
|
|
223268
224380
|
detail: clampLine(result2.summary, 200)
|
|
223269
224381
|
};
|
|
223270
|
-
emitWorkerUpdate(emit, plan, worker,
|
|
224382
|
+
emitWorkerUpdate(emit, plan, worker, status, clampLine(result2.summary, 160));
|
|
223271
224383
|
return outcome;
|
|
223272
224384
|
} catch (error) {
|
|
223273
224385
|
toolCallsReserved -= reservedToolCalls;
|
|
@@ -223367,8 +224479,10 @@ ${FLOCK_GROUNDING_CONTRACT}` : worker.objective;
|
|
|
223367
224479
|
}
|
|
223368
224480
|
const userCancelled = Boolean(deps.signal?.aborted);
|
|
223369
224481
|
const workersDone = outcomes.filter((outcome) => outcome.status === "done").length;
|
|
223370
|
-
const
|
|
224482
|
+
const workersBlocked = outcomes.filter((outcome) => outcome.status === "blocked").length;
|
|
224483
|
+
const workersFailed = outcomes.filter((outcome) => outcome.status === "failed").length + workersBlocked;
|
|
223371
224484
|
const flockStatus = userCancelled || wallTimeHit && workersDone === 0 ? "cancelled" : workersFailed === 0 && workersDone === plan.workers.length ? "completed" : workersDone > 0 ? "partial" : "failed";
|
|
224485
|
+
const permissionHint = workersBlocked > 0 && (turnInput.permissionMode ?? "default") === "default" ? "\n\nTip: flock workers inherit default permissions \u2014 run `/permission take_the_wheel` before `/flock` so bash redirects and file writes do not stall on approval gates." : "";
|
|
223372
224486
|
const assistantText = [
|
|
223373
224487
|
buildFlockSummary(
|
|
223374
224488
|
plan,
|
|
@@ -223384,7 +224498,7 @@ ${FLOCK_GROUNDING_CONTRACT}` : worker.objective;
|
|
|
223384
224498
|
...modelOverrides.unavailable.map(
|
|
223385
224499
|
(override) => `Requested model "${override.modelText}"${override.roleText ? ` for ${override.roleText}` : ""} is not available \u2014 that worker stayed on the default model path.`
|
|
223386
224500
|
)
|
|
223387
|
-
].join("\n");
|
|
224501
|
+
].join("\n") + permissionHint;
|
|
223388
224502
|
emit({
|
|
223389
224503
|
type: "flock_run_completed",
|
|
223390
224504
|
flockId: plan.flockId,
|
|
@@ -223618,6 +224732,12 @@ function buildFlockSummary(plan, outcomes, status, toolCallsUsed, wallTimeHit) {
|
|
|
223618
224732
|
function friendlyOutcomeDetail(detail) {
|
|
223619
224733
|
const clean = clampLine(detail, 220);
|
|
223620
224734
|
if (!clean) return "";
|
|
224735
|
+
if (/approval gate|requires approval|Blocked at an approval/i.test(clean)) {
|
|
224736
|
+
return clean;
|
|
224737
|
+
}
|
|
224738
|
+
if (/No usable model response|Model proxy error|not_authenticated|Unauthorized/i.test(clean)) {
|
|
224739
|
+
return clean;
|
|
224740
|
+
}
|
|
223621
224741
|
if (/Reached maximum \d+ tool-call iterations/i.test(clean)) {
|
|
223622
224742
|
return "finished from gathered evidence";
|
|
223623
224743
|
}
|
|
@@ -223636,6 +224756,29 @@ function clampLine(text, max2) {
|
|
|
223636
224756
|
function makeFlockRunId() {
|
|
223637
224757
|
return `flock-run-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
223638
224758
|
}
|
|
224759
|
+
function flockWorkerStatusFromSpawnResult(result2) {
|
|
224760
|
+
if (!result2.ok || result2.usabilityStatus === "failed") return "failed";
|
|
224761
|
+
if (result2.usabilityStatus === "needs_user_input") return "blocked";
|
|
224762
|
+
return "done";
|
|
224763
|
+
}
|
|
224764
|
+
async function refreshFlockCliAuth(input) {
|
|
224765
|
+
if (input.cliLocalTools !== true) return input;
|
|
224766
|
+
const stored = await readStoredCliAuthSession();
|
|
224767
|
+
if (!stored?.accessToken?.trim()) return input;
|
|
224768
|
+
const turnToken = input.cliServerAccessToken?.trim();
|
|
224769
|
+
if (turnToken && turnToken !== stored.accessToken) return input;
|
|
224770
|
+
const fresh = await ensureFreshCliAuthSession({ session: stored });
|
|
224771
|
+
if (!fresh?.accessToken?.trim()) return input;
|
|
224772
|
+
if (fresh.accessToken === input.cliServerAccessToken) return input;
|
|
224773
|
+
if (typeof process !== "undefined") {
|
|
224774
|
+
process.env.PERCH_MODEL_CALL_PROXY_TOKEN = fresh.accessToken;
|
|
224775
|
+
}
|
|
224776
|
+
return {
|
|
224777
|
+
...input,
|
|
224778
|
+
cliServerAccessToken: fresh.accessToken,
|
|
224779
|
+
marketDeskProxyAccessToken: input.marketDeskProxyAccessToken ?? fresh.accessToken
|
|
224780
|
+
};
|
|
224781
|
+
}
|
|
223639
224782
|
function now11() {
|
|
223640
224783
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
223641
224784
|
}
|
|
@@ -223646,6 +224789,8 @@ var init_runFlockTurn = __esm({
|
|
|
223646
224789
|
init_operatorStream();
|
|
223647
224790
|
init_runRegistry();
|
|
223648
224791
|
init_spawnWorker2();
|
|
224792
|
+
init_cliAuthSession();
|
|
224793
|
+
init_cliAuthRefresh();
|
|
223649
224794
|
init_registry();
|
|
223650
224795
|
init_flockCommand();
|
|
223651
224796
|
init_flockLimits();
|
|
@@ -231959,9 +233104,9 @@ var init_backgroundTaskTypes = __esm({
|
|
|
231959
233104
|
});
|
|
231960
233105
|
|
|
231961
233106
|
// features/perchTerminal/runtime/background/backgroundTaskRegistry.ts
|
|
231962
|
-
import
|
|
231963
|
-
import
|
|
231964
|
-
import
|
|
233107
|
+
import fs11 from "node:fs";
|
|
233108
|
+
import os2 from "node:os";
|
|
233109
|
+
import path12 from "node:path";
|
|
231965
233110
|
function isTerminalStatus(status) {
|
|
231966
233111
|
return status !== "running";
|
|
231967
233112
|
}
|
|
@@ -232018,19 +233163,19 @@ function runtimeMs(record) {
|
|
|
232018
233163
|
function readOutputPreview(outputPath, previewBytes = BACKGROUND_TASK_BOUNDS.previewBytes) {
|
|
232019
233164
|
if (!outputPath) return { preview: "", truncated: false };
|
|
232020
233165
|
try {
|
|
232021
|
-
const stat2 =
|
|
232022
|
-
const fd =
|
|
233166
|
+
const stat2 = fs11.statSync(outputPath);
|
|
233167
|
+
const fd = fs11.openSync(outputPath, "r");
|
|
232023
233168
|
try {
|
|
232024
233169
|
if (stat2.size <= previewBytes) {
|
|
232025
233170
|
const buf2 = Buffer.alloc(stat2.size);
|
|
232026
|
-
|
|
233171
|
+
fs11.readSync(fd, buf2, 0, stat2.size, 0);
|
|
232027
233172
|
return { preview: buf2.toString("utf8"), truncated: false };
|
|
232028
233173
|
}
|
|
232029
233174
|
const buf = Buffer.alloc(previewBytes);
|
|
232030
|
-
|
|
233175
|
+
fs11.readSync(fd, buf, 0, previewBytes, stat2.size - previewBytes);
|
|
232031
233176
|
return { preview: buf.toString("utf8"), truncated: true };
|
|
232032
233177
|
} finally {
|
|
232033
|
-
|
|
233178
|
+
fs11.closeSync(fd);
|
|
232034
233179
|
}
|
|
232035
233180
|
} catch {
|
|
232036
233181
|
return { preview: "", truncated: false };
|
|
@@ -232065,26 +233210,28 @@ function getBackgroundTaskStore() {
|
|
|
232065
233210
|
var STORE_FILE, BackgroundTaskStore, singleton;
|
|
232066
233211
|
var init_backgroundTaskRegistry = __esm({
|
|
232067
233212
|
"features/perchTerminal/runtime/background/backgroundTaskRegistry.ts"() {
|
|
232068
|
-
"use strict";
|
|
232069
233213
|
init_backgroundTaskTypes();
|
|
232070
233214
|
STORE_FILE = "background-tasks.json";
|
|
232071
233215
|
BackgroundTaskStore = class {
|
|
233216
|
+
baseDir;
|
|
233217
|
+
storePath;
|
|
233218
|
+
outputDir;
|
|
233219
|
+
records = /* @__PURE__ */ new Map();
|
|
233220
|
+
loaded = false;
|
|
232072
233221
|
constructor(options = {}) {
|
|
232073
|
-
this.
|
|
232074
|
-
this.
|
|
232075
|
-
this.
|
|
232076
|
-
this.storePath = path11.join(this.baseDir, STORE_FILE);
|
|
232077
|
-
this.outputDir = path11.join(this.baseDir, "background-output");
|
|
233222
|
+
this.baseDir = options.baseDir ?? path12.join(os2.homedir(), ".perch");
|
|
233223
|
+
this.storePath = path12.join(this.baseDir, STORE_FILE);
|
|
233224
|
+
this.outputDir = path12.join(this.baseDir, "background-output");
|
|
232078
233225
|
}
|
|
232079
233226
|
ensureDirs() {
|
|
232080
|
-
|
|
232081
|
-
|
|
233227
|
+
fs11.mkdirSync(this.baseDir, { recursive: true });
|
|
233228
|
+
fs11.mkdirSync(this.outputDir, { recursive: true });
|
|
232082
233229
|
}
|
|
232083
233230
|
load() {
|
|
232084
233231
|
if (this.loaded) return;
|
|
232085
233232
|
this.loaded = true;
|
|
232086
233233
|
try {
|
|
232087
|
-
const raw =
|
|
233234
|
+
const raw = fs11.readFileSync(this.storePath, "utf8");
|
|
232088
233235
|
const parsed = JSON.parse(raw);
|
|
232089
233236
|
const tasks = Array.isArray(parsed.tasks) ? parsed.tasks : [];
|
|
232090
233237
|
this.records = new Map(tasks.map((t) => [t.taskId, t]));
|
|
@@ -232096,11 +233243,11 @@ var init_backgroundTaskRegistry = __esm({
|
|
|
232096
233243
|
this.ensureDirs();
|
|
232097
233244
|
const tasks = Array.from(this.records.values());
|
|
232098
233245
|
const tmp = `${this.storePath}.tmp`;
|
|
232099
|
-
|
|
232100
|
-
|
|
233246
|
+
fs11.writeFileSync(tmp, JSON.stringify({ version: 1, tasks }, null, 2), "utf8");
|
|
233247
|
+
fs11.renameSync(tmp, this.storePath);
|
|
232101
233248
|
}
|
|
232102
233249
|
outputPathFor(taskId) {
|
|
232103
|
-
return
|
|
233250
|
+
return path12.join(this.outputDir, `${taskId}.log`);
|
|
232104
233251
|
}
|
|
232105
233252
|
list() {
|
|
232106
233253
|
this.load();
|
|
@@ -232262,14 +233409,14 @@ var init_backgroundWakeController = __esm({
|
|
|
232262
233409
|
|
|
232263
233410
|
// features/perchTerminal/runtime/background/runBackgroundCommand.ts
|
|
232264
233411
|
import { spawn as spawn2 } from "node:child_process";
|
|
232265
|
-
import
|
|
232266
|
-
import
|
|
233412
|
+
import fs12 from "node:fs";
|
|
233413
|
+
import path13 from "node:path";
|
|
232267
233414
|
function newTaskId() {
|
|
232268
233415
|
return `bg-${Date.now()}-${Math.random().toString(16).slice(2, 8)}`;
|
|
232269
233416
|
}
|
|
232270
233417
|
function createCappedWriter(filePath, maxBytes) {
|
|
232271
|
-
|
|
232272
|
-
const stream =
|
|
233418
|
+
fs12.mkdirSync(path13.dirname(filePath), { recursive: true });
|
|
233419
|
+
const stream = fs12.createWriteStream(filePath, { flags: "w" });
|
|
232273
233420
|
let written = 0;
|
|
232274
233421
|
let truncated = false;
|
|
232275
233422
|
return {
|
|
@@ -232442,10 +233589,10 @@ var init_runBackgroundCommand = __esm({
|
|
|
232442
233589
|
|
|
232443
233590
|
// features/perchTerminal/runtime/cliHost/nodeLocalBridge.ts
|
|
232444
233591
|
import { spawn as spawn3 } from "node:child_process";
|
|
232445
|
-
import
|
|
233592
|
+
import fs13 from "node:fs";
|
|
232446
233593
|
import fsp from "node:fs/promises";
|
|
232447
|
-
import
|
|
232448
|
-
import
|
|
233594
|
+
import os3 from "node:os";
|
|
233595
|
+
import path14 from "node:path";
|
|
232449
233596
|
function installCliNodeLocalBridge(input) {
|
|
232450
233597
|
const globalRef = globalThis;
|
|
232451
233598
|
const previous = globalRef.__PERCH_TEST_DESKTOP_BRIDGE__;
|
|
@@ -232459,7 +233606,7 @@ function installCliNodeLocalBridge(input) {
|
|
|
232459
233606
|
};
|
|
232460
233607
|
}
|
|
232461
233608
|
function readCliProjectMemoryState(workspaceRoot) {
|
|
232462
|
-
const root2 =
|
|
233609
|
+
const root2 = path14.resolve(expandHome4(workspaceRoot));
|
|
232463
233610
|
if (!isCliProjectMemoryAvailable(root2)) {
|
|
232464
233611
|
return {
|
|
232465
233612
|
available: false,
|
|
@@ -232474,7 +233621,7 @@ function readCliProjectMemoryState(workspaceRoot) {
|
|
|
232474
233621
|
};
|
|
232475
233622
|
}
|
|
232476
233623
|
function createCliNodeLocalBridge(input) {
|
|
232477
|
-
const workspaceRoot =
|
|
233624
|
+
const workspaceRoot = path14.resolve(input.workspaceRoot);
|
|
232478
233625
|
const now23 = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
232479
233626
|
const sandboxHandlers = /* @__PURE__ */ new Map();
|
|
232480
233627
|
const emitSandboxEvent = (event) => {
|
|
@@ -232488,7 +233635,7 @@ function createCliNodeLocalBridge(input) {
|
|
|
232488
233635
|
cwd: workspaceRoot,
|
|
232489
233636
|
projectRoot: workspaceRoot,
|
|
232490
233637
|
shellPath: process.env.SHELL || "/bin/zsh",
|
|
232491
|
-
shellName:
|
|
233638
|
+
shellName: path14.basename(process.env.SHELL || "zsh"),
|
|
232492
233639
|
path: process.env.PATH || "",
|
|
232493
233640
|
status: "running",
|
|
232494
233641
|
backend: "spawn-fallback",
|
|
@@ -232515,7 +233662,7 @@ function createCliNodeLocalBridge(input) {
|
|
|
232515
233662
|
capabilities: ["local-files", "local-shell"]
|
|
232516
233663
|
}),
|
|
232517
233664
|
checkFsAccess: async (request) => {
|
|
232518
|
-
const absolutePath =
|
|
233665
|
+
const absolutePath = path14.resolve(expandHome4(request.absolutePath));
|
|
232519
233666
|
return {
|
|
232520
233667
|
decision: "allow",
|
|
232521
233668
|
reason: "Allowed by Perch CLI local filesystem access.",
|
|
@@ -232533,7 +233680,7 @@ function createCliNodeLocalBridge(input) {
|
|
|
232533
233680
|
ok: true,
|
|
232534
233681
|
directory: workspaceRoot,
|
|
232535
233682
|
fileName: request.fileName,
|
|
232536
|
-
absolutePath:
|
|
233683
|
+
absolutePath: path14.join(workspaceRoot, request.fileName),
|
|
232537
233684
|
source: "approved_root"
|
|
232538
233685
|
}),
|
|
232539
233686
|
validateWorkspaceRoot: async () => ({
|
|
@@ -232622,7 +233769,7 @@ function createCliNodeLocalBridge(input) {
|
|
|
232622
233769
|
writeWorkspaceFile: async (request) => {
|
|
232623
233770
|
const baseRoot = resolveRequestRoot(workspaceRoot, request.workspaceRoot);
|
|
232624
233771
|
const target = resolveWritePath(baseRoot, request.relativePath);
|
|
232625
|
-
await fsp.mkdir(
|
|
233772
|
+
await fsp.mkdir(path14.dirname(target), { recursive: true });
|
|
232626
233773
|
const flag = request.overwrite === true ? "w" : "wx";
|
|
232627
233774
|
const payload = request.encoding === "base64" && typeof request.contentBase64 === "string" ? Buffer.from(request.contentBase64, "base64") : request.content ?? "";
|
|
232628
233775
|
try {
|
|
@@ -232642,14 +233789,14 @@ function createCliNodeLocalBridge(input) {
|
|
|
232642
233789
|
moveLocalFile: async (request) => {
|
|
232643
233790
|
const src = resolveReadPath(workspaceRoot, request.src);
|
|
232644
233791
|
const dest = resolveWritePath(workspaceRoot, request.dest);
|
|
232645
|
-
await fsp.mkdir(
|
|
233792
|
+
await fsp.mkdir(path14.dirname(dest), { recursive: true });
|
|
232646
233793
|
await fsp.rename(src, dest);
|
|
232647
233794
|
return { ok: true, fromPath: src, toPath: dest };
|
|
232648
233795
|
},
|
|
232649
233796
|
copyLocalFile: async (request) => {
|
|
232650
233797
|
const src = resolveReadPath(workspaceRoot, request.src);
|
|
232651
233798
|
const dest = resolveWritePath(workspaceRoot, request.dest);
|
|
232652
|
-
await fsp.mkdir(
|
|
233799
|
+
await fsp.mkdir(path14.dirname(dest), { recursive: true });
|
|
232653
233800
|
await fsp.copyFile(src, dest);
|
|
232654
233801
|
return { ok: true, fromPath: src, toPath: dest };
|
|
232655
233802
|
},
|
|
@@ -232674,7 +233821,7 @@ function createCliNodeLocalBridge(input) {
|
|
|
232674
233821
|
const pattern = request.pattern?.trim() || "**/*";
|
|
232675
233822
|
const regex2 = globToRegex(pattern);
|
|
232676
233823
|
const files = await collectFiles(root2, { maxVisits: Math.max(maxResults * 20, 1e3) });
|
|
232677
|
-
const matches = files.map((file) =>
|
|
233824
|
+
const matches = files.map((file) => path14.relative(root2, file).replace(/\\/g, "/")).filter((relative2) => regex2.test(relative2)).slice(0, maxResults);
|
|
232678
233825
|
return {
|
|
232679
233826
|
ok: true,
|
|
232680
233827
|
matches,
|
|
@@ -232697,7 +233844,7 @@ function createCliNodeLocalBridge(input) {
|
|
|
232697
233844
|
const files = await collectFiles(root2, { maxVisits: 5e3 });
|
|
232698
233845
|
const matches = [];
|
|
232699
233846
|
for (const file of files) {
|
|
232700
|
-
const relative2 =
|
|
233847
|
+
const relative2 = path14.relative(root2, file).replace(/\\/g, "/");
|
|
232701
233848
|
if (includeRegex && !includeRegex.test(relative2)) continue;
|
|
232702
233849
|
const text = await readTextFileIfReasonable(file);
|
|
232703
233850
|
if (text === null) continue;
|
|
@@ -232736,7 +233883,7 @@ function createCliNodeLocalBridge(input) {
|
|
|
232736
233883
|
const stat2 = await safeStat(target);
|
|
232737
233884
|
return {
|
|
232738
233885
|
ok: true,
|
|
232739
|
-
relativePath:
|
|
233886
|
+
relativePath: path14.isAbsolute(request.relativePath) || request.relativePath.startsWith("~") ? target : path14.relative(baseRoot, target) || ".",
|
|
232740
233887
|
exists: Boolean(stat2),
|
|
232741
233888
|
isFile: stat2?.isFile() ?? false,
|
|
232742
233889
|
isDirectory: stat2?.isDirectory() ?? false,
|
|
@@ -232770,7 +233917,7 @@ function createCliNodeLocalBridge(input) {
|
|
|
232770
233917
|
return {
|
|
232771
233918
|
ok: true,
|
|
232772
233919
|
data: buffer.toString("base64"),
|
|
232773
|
-
fileName:
|
|
233920
|
+
fileName: path14.relative(workspaceRoot, target) || path14.basename(target),
|
|
232774
233921
|
mimeType: inferMimeType(target),
|
|
232775
233922
|
sizeBytes: stat2.size,
|
|
232776
233923
|
encoding: "base64"
|
|
@@ -232866,7 +234013,7 @@ async function runCliPrepareAPEvidence(workspaceRoot, request) {
|
|
|
232866
234013
|
topCases: (artifact.cases ?? []).slice(0, 12),
|
|
232867
234014
|
duplicateCases: result2.payload.duplicateCases,
|
|
232868
234015
|
controlGraph: result2.payload.controlGraph,
|
|
232869
|
-
relativeOutputDir: typeof artifact.outputDir === "string" ?
|
|
234016
|
+
relativeOutputDir: typeof artifact.outputDir === "string" ? path14.basename(artifact.outputDir) : null,
|
|
232870
234017
|
approvedRootMatch: true
|
|
232871
234018
|
},
|
|
232872
234019
|
executionHost: "electron_desktop",
|
|
@@ -232955,7 +234102,7 @@ async function prepareCliSandboxInputs(workspaceRoot, request) {
|
|
|
232955
234102
|
const jobId = request.jobId?.trim() || `cli-sandbox-${Date.now()}`;
|
|
232956
234103
|
const maxFiles = Math.max(1, Math.min(request.maxFiles ?? 20, 100));
|
|
232957
234104
|
const maxBytesPerFile = Math.max(1, Math.min(request.maxBytesPerFile ?? 20 * 1024 * 1024, 50 * 1024 * 1024));
|
|
232958
|
-
const tempDir = await fsp.mkdtemp(
|
|
234105
|
+
const tempDir = await fsp.mkdtemp(path14.join(os3.tmpdir(), `${jobId}-`));
|
|
232959
234106
|
const entries = [];
|
|
232960
234107
|
const skipped = [];
|
|
232961
234108
|
for (const localSourceId of request.localSourceIds.slice(0, maxFiles)) {
|
|
@@ -232969,13 +234116,13 @@ async function prepareCliSandboxInputs(workspaceRoot, request) {
|
|
|
232969
234116
|
skipped.push({ localSourceId, reason: `File is too large: ${stat2.size} bytes.` });
|
|
232970
234117
|
continue;
|
|
232971
234118
|
}
|
|
232972
|
-
const relativePath = safeSandboxRelativePath(
|
|
232973
|
-
const tempPath =
|
|
232974
|
-
await fsp.mkdir(
|
|
234119
|
+
const relativePath = safeSandboxRelativePath(path14.isAbsolute(localSourceId) || localSourceId.startsWith("~") ? path14.basename(sourcePath) : path14.relative(workspaceRoot, sourcePath));
|
|
234120
|
+
const tempPath = path14.join(tempDir, relativePath);
|
|
234121
|
+
await fsp.mkdir(path14.dirname(tempPath), { recursive: true });
|
|
232975
234122
|
await fsp.copyFile(sourcePath, tempPath);
|
|
232976
234123
|
entries.push({
|
|
232977
234124
|
localSourceId,
|
|
232978
|
-
fileName:
|
|
234125
|
+
fileName: path14.basename(sourcePath),
|
|
232979
234126
|
relativePath,
|
|
232980
234127
|
tempPath,
|
|
232981
234128
|
sizeBytes: stat2.size,
|
|
@@ -233013,11 +234160,11 @@ async function runCliSandboxCodeJob(workspaceRoot, request, onEvent) {
|
|
|
233013
234160
|
getApprovedRoot: (rootId) => rootId === CLI_ROOT_ID ? { id: CLI_ROOT_ID, path: workspaceRoot, approvedAt: (/* @__PURE__ */ new Date()).toISOString() } : null,
|
|
233014
234161
|
isPathSafe: (_rootPath, targetPath) => !targetPath.split(/[\\/]+/).includes(".."),
|
|
233015
234162
|
isInsideApprovedRoot: (filePath) => {
|
|
233016
|
-
const absolute =
|
|
234163
|
+
const absolute = path14.resolve(expandHome4(filePath));
|
|
233017
234164
|
if (isInside(workspaceRoot, absolute)) {
|
|
233018
234165
|
return { id: CLI_ROOT_ID, path: workspaceRoot, approvedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
233019
234166
|
}
|
|
233020
|
-
return { id: "cli-absolute-root", path:
|
|
234167
|
+
return { id: "cli-absolute-root", path: path14.dirname(absolute), approvedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
233021
234168
|
},
|
|
233022
234169
|
shouldIgnore: (fileName) => IGNORED_DIRS.has(fileName),
|
|
233023
234170
|
guessMimeType: inferMimeType,
|
|
@@ -233060,7 +234207,7 @@ function safeSandboxRelativePath(value) {
|
|
|
233060
234207
|
return clean || "input";
|
|
233061
234208
|
}
|
|
233062
234209
|
function guessCliFileType(filePath) {
|
|
233063
|
-
const ext =
|
|
234210
|
+
const ext = path14.extname(filePath).toLowerCase();
|
|
233064
234211
|
if ([".xlsx", ".xls"].includes(ext)) return "spreadsheet";
|
|
233065
234212
|
if (ext === ".csv" || ext === ".tsv") return "csv";
|
|
233066
234213
|
if (ext === ".pdf") return "pdf";
|
|
@@ -233079,7 +234226,7 @@ async function readCliProjectMemoryBridge(workspaceRoot) {
|
|
|
233079
234226
|
return { ok: true, meta: state.meta };
|
|
233080
234227
|
}
|
|
233081
234228
|
async function writeCliProjectMemory(workspaceRoot, patch) {
|
|
233082
|
-
const root2 =
|
|
234229
|
+
const root2 = path14.resolve(expandHome4(workspaceRoot));
|
|
233083
234230
|
if (!isCliProjectMemoryAvailable(root2)) {
|
|
233084
234231
|
return {
|
|
233085
234232
|
ok: false,
|
|
@@ -233095,12 +234242,12 @@ async function writeCliProjectMemory(workspaceRoot, patch) {
|
|
|
233095
234242
|
lastOpenedThreadId: patch.lastOpenedThreadId !== void 0 ? patch.lastOpenedThreadId : current.lastOpenedThreadId,
|
|
233096
234243
|
memorySummary: patch.memorySummary !== void 0 ? patch.memorySummary : current.memorySummary
|
|
233097
234244
|
};
|
|
233098
|
-
await fsp.mkdir(
|
|
234245
|
+
await fsp.mkdir(path14.join(root2, PERCH_DIR), { recursive: true });
|
|
233099
234246
|
await atomicWriteCliUtf8(getCliProjectFilePath(root2), JSON.stringify(next, null, 2));
|
|
233100
234247
|
return { ok: true, meta: enrichCliProjectMeta(root2, next) };
|
|
233101
234248
|
}
|
|
233102
234249
|
async function writeCliMemoryFile(workspaceRoot, request) {
|
|
233103
|
-
const root2 =
|
|
234250
|
+
const root2 = path14.resolve(expandHome4(workspaceRoot));
|
|
233104
234251
|
if (!isCliProjectMemoryAvailable(root2)) {
|
|
233105
234252
|
return {
|
|
233106
234253
|
ok: false,
|
|
@@ -233112,8 +234259,8 @@ async function writeCliMemoryFile(workspaceRoot, request) {
|
|
|
233112
234259
|
}
|
|
233113
234260
|
const fileName = normalizeCliMemoryFileName(request.fileName);
|
|
233114
234261
|
if (!fileName) return { ok: false, error: "Invalid memory file name." };
|
|
233115
|
-
const relativePath =
|
|
233116
|
-
const fullPath =
|
|
234262
|
+
const relativePath = path14.join(PERCH_DIR, MEMORY_DIR, fileName);
|
|
234263
|
+
const fullPath = path14.join(root2, relativePath);
|
|
233117
234264
|
const existing = await fsp.readFile(fullPath, "utf8").catch(() => "");
|
|
233118
234265
|
let nextContent;
|
|
233119
234266
|
try {
|
|
@@ -233126,7 +234273,7 @@ async function writeCliMemoryFile(workspaceRoot, request) {
|
|
|
233126
234273
|
}
|
|
233127
234274
|
const capError = assertCliMemoryWithinCap(nextContent);
|
|
233128
234275
|
if (capError) return { ok: false, error: capError };
|
|
233129
|
-
await fsp.mkdir(
|
|
234276
|
+
await fsp.mkdir(path14.dirname(fullPath), { recursive: true });
|
|
233130
234277
|
await atomicWriteCliUtf8(fullPath, nextContent);
|
|
233131
234278
|
const updatedFile = readCliTextMemoryFile(root2, relativePath, MAX_MEMORY_BYTES);
|
|
233132
234279
|
return {
|
|
@@ -233152,17 +234299,17 @@ function defaultCliProjectMeta() {
|
|
|
233152
234299
|
}
|
|
233153
234300
|
function isCliProjectMemoryAvailable(root2) {
|
|
233154
234301
|
try {
|
|
233155
|
-
return
|
|
234302
|
+
return fs13.statSync(path14.join(root2, PERCH_DIR)).isDirectory();
|
|
233156
234303
|
} catch {
|
|
233157
234304
|
return false;
|
|
233158
234305
|
}
|
|
233159
234306
|
}
|
|
233160
234307
|
function getCliProjectFilePath(root2) {
|
|
233161
|
-
return
|
|
234308
|
+
return path14.join(root2, PERCH_DIR, PROJECT_FILE);
|
|
233162
234309
|
}
|
|
233163
234310
|
function loadCliStoredMeta(root2) {
|
|
233164
234311
|
try {
|
|
233165
|
-
const raw =
|
|
234312
|
+
const raw = fs13.readFileSync(getCliProjectFilePath(root2), "utf8");
|
|
233166
234313
|
return { ...defaultCliProjectMeta(), ...JSON.parse(raw) };
|
|
233167
234314
|
} catch {
|
|
233168
234315
|
return defaultCliProjectMeta();
|
|
@@ -233170,7 +234317,7 @@ function loadCliStoredMeta(root2) {
|
|
|
233170
234317
|
}
|
|
233171
234318
|
function enrichCliProjectMeta(root2, base) {
|
|
233172
234319
|
const perchMd = readCliFirstExisting(root2, [
|
|
233173
|
-
|
|
234320
|
+
path14.join(PERCH_DIR, PERCH_INDEX_FILE),
|
|
233174
234321
|
PERCH_INDEX_FILE
|
|
233175
234322
|
], MAX_TEXT_BYTES);
|
|
233176
234323
|
const rules = readCliRules(root2);
|
|
@@ -233281,10 +234428,10 @@ function readCliFirstExisting(root2, relativePaths, maxBytes) {
|
|
|
233281
234428
|
return null;
|
|
233282
234429
|
}
|
|
233283
234430
|
function readCliRules(root2) {
|
|
233284
|
-
const dirPath =
|
|
234431
|
+
const dirPath = path14.join(root2, PERCH_DIR, RULES_DIR);
|
|
233285
234432
|
try {
|
|
233286
|
-
return
|
|
233287
|
-
fileName:
|
|
234433
|
+
return fs13.readdirSync(dirPath).filter((name) => [".md", ".txt"].includes(path14.extname(name).toLowerCase())).slice(0, 40).map((name) => readCliTextMemoryFile(root2, path14.join(PERCH_DIR, RULES_DIR, name), MAX_TEXT_BYTES)).filter((file) => file.found).map((file) => ({
|
|
234434
|
+
fileName: path14.basename(file.relativePath),
|
|
233288
234435
|
relativePath: file.relativePath,
|
|
233289
234436
|
content: file.content,
|
|
233290
234437
|
sizeBytes: file.sizeBytes,
|
|
@@ -233295,15 +234442,15 @@ function readCliRules(root2) {
|
|
|
233295
234442
|
}
|
|
233296
234443
|
}
|
|
233297
234444
|
function readCliMemoryFiles(root2) {
|
|
233298
|
-
const memoryDir =
|
|
234445
|
+
const memoryDir = path14.join(root2, PERCH_DIR, MEMORY_DIR);
|
|
233299
234446
|
const discovered = /* @__PURE__ */ new Set();
|
|
233300
234447
|
const memoryFiles = [];
|
|
233301
234448
|
try {
|
|
233302
|
-
for (const name of
|
|
233303
|
-
const ext =
|
|
234449
|
+
for (const name of fs13.readdirSync(memoryDir).slice(0, MAX_MEMORY_FILES)) {
|
|
234450
|
+
const ext = path14.extname(name).toLowerCase();
|
|
233304
234451
|
if (ext !== ".md" && ext !== ".txt") continue;
|
|
233305
234452
|
discovered.add(name);
|
|
233306
|
-
memoryFiles.push(readCliTextMemoryFile(root2,
|
|
234453
|
+
memoryFiles.push(readCliTextMemoryFile(root2, path14.join(PERCH_DIR, MEMORY_DIR, name), MAX_MEMORY_BYTES));
|
|
233307
234454
|
}
|
|
233308
234455
|
} catch {
|
|
233309
234456
|
}
|
|
@@ -233311,20 +234458,20 @@ function readCliMemoryFiles(root2) {
|
|
|
233311
234458
|
memoryFiles,
|
|
233312
234459
|
missingMemoryFiles: EXPECTED_MEMORY_FILES.filter((name) => !discovered.has(name)).map((name) => ({
|
|
233313
234460
|
fileName: name,
|
|
233314
|
-
relativePath:
|
|
234461
|
+
relativePath: path14.join(PERCH_DIR, MEMORY_DIR, name),
|
|
233315
234462
|
reason: "expected project memory file was not found"
|
|
233316
234463
|
}))
|
|
233317
234464
|
};
|
|
233318
234465
|
}
|
|
233319
234466
|
function readCliTextMemoryFile(root2, relativePath, maxBytes) {
|
|
233320
|
-
const fullPath =
|
|
233321
|
-
const fileName =
|
|
234467
|
+
const fullPath = path14.join(root2, relativePath);
|
|
234468
|
+
const fileName = path14.basename(relativePath);
|
|
233322
234469
|
try {
|
|
233323
|
-
const stat2 =
|
|
234470
|
+
const stat2 = fs13.statSync(fullPath);
|
|
233324
234471
|
if (!stat2.isFile()) {
|
|
233325
234472
|
return { fileName, relativePath, content: "", found: false, error: "not a regular file" };
|
|
233326
234473
|
}
|
|
233327
|
-
const content =
|
|
234474
|
+
const content = fs13.readFileSync(fullPath, "utf8");
|
|
233328
234475
|
if (stat2.size > maxBytes) {
|
|
233329
234476
|
return {
|
|
233330
234477
|
fileName,
|
|
@@ -233446,7 +234593,7 @@ function terminateProcessGroup(child, signal) {
|
|
|
233446
234593
|
}
|
|
233447
234594
|
function resolveReadPath(root2, inputPath) {
|
|
233448
234595
|
const expanded = expandHome4(inputPath || ".");
|
|
233449
|
-
return
|
|
234596
|
+
return path14.resolve(path14.isAbsolute(expanded) ? expanded : path14.join(root2, expanded));
|
|
233450
234597
|
}
|
|
233451
234598
|
function resolveRequestRoot(defaultRoot, requestRoot) {
|
|
233452
234599
|
const trimmed = requestRoot?.trim();
|
|
@@ -233454,25 +234601,25 @@ function resolveRequestRoot(defaultRoot, requestRoot) {
|
|
|
233454
234601
|
}
|
|
233455
234602
|
function resolveWritePath(root2, inputPath) {
|
|
233456
234603
|
const expanded = expandHome4(inputPath || ".");
|
|
233457
|
-
return
|
|
234604
|
+
return path14.resolve(path14.isAbsolute(expanded) ? expanded : path14.join(root2, expanded));
|
|
233458
234605
|
}
|
|
233459
234606
|
function displayPath(root2, target, requestedPath) {
|
|
233460
234607
|
const expanded = expandHome4(requestedPath || ".");
|
|
233461
|
-
if (
|
|
233462
|
-
return
|
|
234608
|
+
if (path14.isAbsolute(expanded) || requestedPath.startsWith("~")) return target;
|
|
234609
|
+
return path14.relative(root2, target) || path14.basename(target);
|
|
233463
234610
|
}
|
|
233464
234611
|
function resolveLocalSourceId(root2, localSourceId) {
|
|
233465
234612
|
const raw = localSourceId.includes("::") ? localSourceId.split("::").slice(1).join("::") : localSourceId;
|
|
233466
234613
|
return resolveReadPath(root2, raw);
|
|
233467
234614
|
}
|
|
233468
234615
|
function expandHome4(inputPath) {
|
|
233469
|
-
if (inputPath === "~") return
|
|
233470
|
-
if (inputPath.startsWith("~/")) return
|
|
234616
|
+
if (inputPath === "~") return os3.homedir();
|
|
234617
|
+
if (inputPath.startsWith("~/")) return path14.join(os3.homedir(), inputPath.slice(2));
|
|
233471
234618
|
return inputPath;
|
|
233472
234619
|
}
|
|
233473
234620
|
function isInside(root2, candidate) {
|
|
233474
|
-
const relative2 =
|
|
233475
|
-
return relative2 === "" || !relative2.startsWith("..") && !
|
|
234621
|
+
const relative2 = path14.relative(path14.resolve(root2), path14.resolve(candidate));
|
|
234622
|
+
return relative2 === "" || !relative2.startsWith("..") && !path14.isAbsolute(relative2);
|
|
233476
234623
|
}
|
|
233477
234624
|
async function safeStat(target) {
|
|
233478
234625
|
try {
|
|
@@ -233489,7 +234636,7 @@ async function collectFiles(root2, options) {
|
|
|
233489
234636
|
for (const entry of entries) {
|
|
233490
234637
|
if (result2.length >= options.maxVisits) return;
|
|
233491
234638
|
if (entry.name.startsWith(".") && entry.name !== ".env") continue;
|
|
233492
|
-
const absolute =
|
|
234639
|
+
const absolute = path14.join(dir, entry.name);
|
|
233493
234640
|
if (entry.isDirectory()) {
|
|
233494
234641
|
if (!IGNORED_DIRS.has(entry.name)) await walk2(absolute);
|
|
233495
234642
|
} else if (entry.isFile()) {
|
|
@@ -233545,14 +234692,14 @@ function sanitizeMaxResults(value) {
|
|
|
233545
234692
|
return Math.max(1, Math.min(typeof value === "number" ? Math.floor(value) : DEFAULT_MAX_RESULTS, 1e3));
|
|
233546
234693
|
}
|
|
233547
234694
|
function localSourceEntry(root2, file, absoluteId = false) {
|
|
233548
|
-
const stat2 =
|
|
233549
|
-
const relativePath =
|
|
233550
|
-
const extension2 =
|
|
234695
|
+
const stat2 = fs13.statSync(file);
|
|
234696
|
+
const relativePath = path14.relative(root2, file);
|
|
234697
|
+
const extension2 = path14.extname(file).toLowerCase();
|
|
233551
234698
|
return {
|
|
233552
234699
|
localSourceId: absoluteId ? file : `${CLI_ROOT_ID}::${relativePath}`,
|
|
233553
234700
|
rootId: absoluteId ? "cli-absolute-root" : CLI_ROOT_ID,
|
|
233554
234701
|
relativePath,
|
|
233555
|
-
fileName:
|
|
234702
|
+
fileName: path14.basename(file),
|
|
233556
234703
|
extension: extension2,
|
|
233557
234704
|
sizeBytes: stat2.size,
|
|
233558
234705
|
modifiedAt: stat2.mtime.toISOString(),
|
|
@@ -233561,7 +234708,7 @@ function localSourceEntry(root2, file, absoluteId = false) {
|
|
|
233561
234708
|
};
|
|
233562
234709
|
}
|
|
233563
234710
|
function inferMimeType(file) {
|
|
233564
|
-
const ext =
|
|
234711
|
+
const ext = path14.extname(file).toLowerCase();
|
|
233565
234712
|
if (ext === ".pdf") return "application/pdf";
|
|
233566
234713
|
if (ext === ".json") return "application/json";
|
|
233567
234714
|
if (ext === ".csv") return "text/csv";
|
|
@@ -233622,12 +234769,12 @@ var init_nodeLocalBridge = __esm({
|
|
|
233622
234769
|
});
|
|
233623
234770
|
|
|
233624
234771
|
// features/perchTerminal/runtime/cliHost/runCliTurn.ts
|
|
233625
|
-
import
|
|
234772
|
+
import path15 from "node:path";
|
|
233626
234773
|
async function runPerchCliTurn(input, options = {}) {
|
|
233627
234774
|
const prompt = input.prompt.trim();
|
|
233628
234775
|
if (!prompt) throw new Error("Missing prompt for perch run");
|
|
233629
234776
|
const now23 = options.now ?? (() => Date.now());
|
|
233630
|
-
const cwd2 =
|
|
234777
|
+
const cwd2 = path15.resolve(input.cwd ?? process.cwd());
|
|
233631
234778
|
const threadId = input.threadId?.trim() || `cli-${now23()}`;
|
|
233632
234779
|
const events = [];
|
|
233633
234780
|
let latestContextSnapshot = null;
|
|
@@ -233928,495 +235075,6 @@ var init_cliHost = __esm({
|
|
|
233928
235075
|
}
|
|
233929
235076
|
});
|
|
233930
235077
|
|
|
233931
|
-
// features/perchTerminal/runtime/publicModelDefault.ts
|
|
233932
|
-
function parsePublicModelDefaultPayload(value) {
|
|
233933
|
-
const raw = value && typeof value === "object" ? value : {};
|
|
233934
|
-
const selection = sanitizeFounderModelSelection(
|
|
233935
|
-
raw.selection ?? DEFAULT_FOUNDER_MODEL_SELECTION
|
|
233936
|
-
);
|
|
233937
|
-
return {
|
|
233938
|
-
selection,
|
|
233939
|
-
updatedAt: typeof raw.updatedAt === "string" ? raw.updatedAt : null,
|
|
233940
|
-
updatedBy: typeof raw.updatedBy === "string" ? raw.updatedBy : null
|
|
233941
|
-
};
|
|
233942
|
-
}
|
|
233943
|
-
var init_publicModelDefault = __esm({
|
|
233944
|
-
"features/perchTerminal/runtime/publicModelDefault.ts"() {
|
|
233945
|
-
"use strict";
|
|
233946
|
-
init_modelRegistry();
|
|
233947
|
-
}
|
|
233948
|
-
});
|
|
233949
|
-
|
|
233950
|
-
// features/perchTerminal/runtime/cliHost/cliAuthSession.ts
|
|
233951
|
-
import { execFile } from "node:child_process";
|
|
233952
|
-
import fs13 from "node:fs/promises";
|
|
233953
|
-
import os3 from "node:os";
|
|
233954
|
-
import path15 from "node:path";
|
|
233955
|
-
import { promisify } from "node:util";
|
|
233956
|
-
async function readStoredCliAuthSession() {
|
|
233957
|
-
const raw = shouldUseFallbackAuthFile() ? await readFallbackSecret().catch(() => null) : process.platform === "darwin" ? await readKeychainSecret().catch(() => null) : await readFallbackSecret().catch(() => null);
|
|
233958
|
-
return parseStoredCliAuthSession(raw);
|
|
233959
|
-
}
|
|
233960
|
-
async function writeStoredCliAuthSession(session) {
|
|
233961
|
-
const raw = JSON.stringify(session);
|
|
233962
|
-
if (process.platform === "darwin" && !shouldUseFallbackAuthFile()) {
|
|
233963
|
-
await execFileAsync("security", [
|
|
233964
|
-
"add-generic-password",
|
|
233965
|
-
"-U",
|
|
233966
|
-
"-s",
|
|
233967
|
-
KEYCHAIN_SERVICE,
|
|
233968
|
-
"-a",
|
|
233969
|
-
KEYCHAIN_ACCOUNT,
|
|
233970
|
-
"-w",
|
|
233971
|
-
raw
|
|
233972
|
-
]);
|
|
233973
|
-
return;
|
|
233974
|
-
}
|
|
233975
|
-
await writeFallbackSecret(raw);
|
|
233976
|
-
}
|
|
233977
|
-
async function clearStoredCliAuthSession() {
|
|
233978
|
-
if (process.platform === "darwin" && !shouldUseFallbackAuthFile()) {
|
|
233979
|
-
await execFileAsync("security", [
|
|
233980
|
-
"delete-generic-password",
|
|
233981
|
-
"-s",
|
|
233982
|
-
KEYCHAIN_SERVICE,
|
|
233983
|
-
"-a",
|
|
233984
|
-
KEYCHAIN_ACCOUNT
|
|
233985
|
-
]).catch(() => void 0);
|
|
233986
|
-
return;
|
|
233987
|
-
}
|
|
233988
|
-
await fs13.rm(fallbackSessionPath(), { force: true }).catch(() => void 0);
|
|
233989
|
-
}
|
|
233990
|
-
function shouldUseFallbackAuthFile() {
|
|
233991
|
-
return Boolean(process.env.PERCH_CLI_AUTH_DIR?.trim());
|
|
233992
|
-
}
|
|
233993
|
-
function isStoredCliAuthSessionUsable(session, nowSeconds = Math.floor(Date.now() / 1e3)) {
|
|
233994
|
-
if (!session?.accessToken?.trim()) return false;
|
|
233995
|
-
if (!session.appUrl?.trim()) return false;
|
|
233996
|
-
if (!session.expiresAt) return true;
|
|
233997
|
-
return session.expiresAt > nowSeconds + 90;
|
|
233998
|
-
}
|
|
233999
|
-
function parseStoredCliAuthSession(raw) {
|
|
234000
|
-
if (!raw?.trim()) return null;
|
|
234001
|
-
try {
|
|
234002
|
-
const parsed = JSON.parse(raw);
|
|
234003
|
-
if (parsed.version !== 1) return null;
|
|
234004
|
-
if (!parsed.appUrl || !parsed.accessToken || !parsed.updatedAt) return null;
|
|
234005
|
-
return {
|
|
234006
|
-
version: 1,
|
|
234007
|
-
appUrl: parsed.appUrl,
|
|
234008
|
-
accessToken: parsed.accessToken,
|
|
234009
|
-
refreshToken: parsed.refreshToken ?? null,
|
|
234010
|
-
expiresAt: typeof parsed.expiresAt === "number" ? parsed.expiresAt : null,
|
|
234011
|
-
userId: parsed.userId ?? null,
|
|
234012
|
-
email: parsed.email ?? null,
|
|
234013
|
-
updatedAt: parsed.updatedAt
|
|
234014
|
-
};
|
|
234015
|
-
} catch {
|
|
234016
|
-
return null;
|
|
234017
|
-
}
|
|
234018
|
-
}
|
|
234019
|
-
async function readKeychainSecret() {
|
|
234020
|
-
const { stdout } = await execFileAsync("security", [
|
|
234021
|
-
"find-generic-password",
|
|
234022
|
-
"-s",
|
|
234023
|
-
KEYCHAIN_SERVICE,
|
|
234024
|
-
"-a",
|
|
234025
|
-
KEYCHAIN_ACCOUNT,
|
|
234026
|
-
"-w"
|
|
234027
|
-
]);
|
|
234028
|
-
return stdout.trim() || null;
|
|
234029
|
-
}
|
|
234030
|
-
function fallbackSessionPath() {
|
|
234031
|
-
const base = process.env.PERCH_CLI_AUTH_DIR?.trim() || path15.join(os3.homedir(), ".perch");
|
|
234032
|
-
return path15.join(base, "cli-auth-session.json");
|
|
234033
|
-
}
|
|
234034
|
-
async function readFallbackSecret() {
|
|
234035
|
-
return await fs13.readFile(fallbackSessionPath(), "utf8");
|
|
234036
|
-
}
|
|
234037
|
-
async function writeFallbackSecret(raw) {
|
|
234038
|
-
const filePath = fallbackSessionPath();
|
|
234039
|
-
await fs13.mkdir(path15.dirname(filePath), { recursive: true, mode: 448 });
|
|
234040
|
-
await fs13.writeFile(filePath, raw, { mode: 384 });
|
|
234041
|
-
}
|
|
234042
|
-
var execFileAsync, KEYCHAIN_SERVICE, KEYCHAIN_ACCOUNT;
|
|
234043
|
-
var init_cliAuthSession = __esm({
|
|
234044
|
-
"features/perchTerminal/runtime/cliHost/cliAuthSession.ts"() {
|
|
234045
|
-
"use strict";
|
|
234046
|
-
execFileAsync = promisify(execFile);
|
|
234047
|
-
KEYCHAIN_SERVICE = "app.perchai.cli-auth";
|
|
234048
|
-
KEYCHAIN_ACCOUNT = "default";
|
|
234049
|
-
}
|
|
234050
|
-
});
|
|
234051
|
-
|
|
234052
|
-
// features/perchTerminal/runtime/cliHost/cliStandaloneOAuth.ts
|
|
234053
|
-
import { execFile as execFile2 } from "node:child_process";
|
|
234054
|
-
import http from "node:http";
|
|
234055
|
-
import { promisify as promisify2 } from "node:util";
|
|
234056
|
-
async function runCliStandaloneOAuthLogin(input) {
|
|
234057
|
-
const appUrl = resolveCliAppUrl(input.appUrl, null);
|
|
234058
|
-
if (!appUrl) {
|
|
234059
|
-
return { ok: false, code: "invalid_app_url", error: "Invalid Perch app URL." };
|
|
234060
|
-
}
|
|
234061
|
-
const fetchImpl = input.fetchImpl ?? globalThis.fetch;
|
|
234062
|
-
if (typeof fetchImpl !== "function") {
|
|
234063
|
-
return { ok: false, code: "fetch_unavailable", error: "This Node runtime does not expose fetch." };
|
|
234064
|
-
}
|
|
234065
|
-
const config = await fetchCliAuthConfig(appUrl, fetchImpl);
|
|
234066
|
-
if (!config.ok || !config.supabaseUrl || !config.supabaseAnonKey) {
|
|
234067
|
-
return {
|
|
234068
|
-
ok: false,
|
|
234069
|
-
code: "auth_config_unavailable",
|
|
234070
|
-
error: "The Perch app did not return CLI auth configuration."
|
|
234071
|
-
};
|
|
234072
|
-
}
|
|
234073
|
-
const provider = selectOAuthProvider(config.providers ?? [], input.provider);
|
|
234074
|
-
if (!provider) {
|
|
234075
|
-
return {
|
|
234076
|
-
ok: false,
|
|
234077
|
-
code: "oauth_provider_unavailable",
|
|
234078
|
-
error: "No supported CLI OAuth provider is enabled for this Perch app."
|
|
234079
|
-
};
|
|
234080
|
-
}
|
|
234081
|
-
const callback = await createOAuthCallbackServer({
|
|
234082
|
-
host: config.redirectHost?.trim() || "127.0.0.1",
|
|
234083
|
-
timeoutMs: input.timeoutMs ?? DEFAULT_LOGIN_TIMEOUT_MS
|
|
234084
|
-
});
|
|
234085
|
-
try {
|
|
234086
|
-
const memoryStorage = createMemoryAuthStorage();
|
|
234087
|
-
const supabase = createClient(config.supabaseUrl, config.supabaseAnonKey, {
|
|
234088
|
-
auth: {
|
|
234089
|
-
flowType: "pkce",
|
|
234090
|
-
autoRefreshToken: false,
|
|
234091
|
-
detectSessionInUrl: false,
|
|
234092
|
-
persistSession: true,
|
|
234093
|
-
storage: memoryStorage
|
|
234094
|
-
}
|
|
234095
|
-
});
|
|
234096
|
-
const { data, error } = await supabase.auth.signInWithOAuth({
|
|
234097
|
-
provider,
|
|
234098
|
-
options: {
|
|
234099
|
-
redirectTo: callback.redirectTo,
|
|
234100
|
-
skipBrowserRedirect: true,
|
|
234101
|
-
data: { signup_source: "cli" }
|
|
234102
|
-
}
|
|
234103
|
-
});
|
|
234104
|
-
if (error || !data.url) {
|
|
234105
|
-
return {
|
|
234106
|
-
ok: false,
|
|
234107
|
-
code: "oauth_start_failed",
|
|
234108
|
-
error: error?.message ?? "Supabase did not return an OAuth URL."
|
|
234109
|
-
};
|
|
234110
|
-
}
|
|
234111
|
-
await (input.openExternal ?? openExternal)(data.url);
|
|
234112
|
-
const callbackResult = await callback.waitForCode();
|
|
234113
|
-
if (!callbackResult.ok) {
|
|
234114
|
-
return callbackResult;
|
|
234115
|
-
}
|
|
234116
|
-
const exchanged = await supabase.auth.exchangeCodeForSession(callbackResult.code);
|
|
234117
|
-
if (exchanged.error || !exchanged.data.session) {
|
|
234118
|
-
return {
|
|
234119
|
-
ok: false,
|
|
234120
|
-
code: "oauth_exchange_failed",
|
|
234121
|
-
error: exchanged.error?.message ?? "Supabase did not return a CLI session."
|
|
234122
|
-
};
|
|
234123
|
-
}
|
|
234124
|
-
const session = storedSessionFromSupabaseSession({
|
|
234125
|
-
appUrl,
|
|
234126
|
-
accessToken: exchanged.data.session.access_token,
|
|
234127
|
-
refreshToken: exchanged.data.session.refresh_token,
|
|
234128
|
-
expiresAt: exchanged.data.session.expires_at ?? null,
|
|
234129
|
-
userId: exchanged.data.session.user.id,
|
|
234130
|
-
email: exchanged.data.session.user.email ?? null
|
|
234131
|
-
});
|
|
234132
|
-
await writeStoredCliAuthSession(session);
|
|
234133
|
-
return { ok: true, session };
|
|
234134
|
-
} finally {
|
|
234135
|
-
await callback.close();
|
|
234136
|
-
}
|
|
234137
|
-
}
|
|
234138
|
-
async function fetchCliAuthConfig(appUrl, fetchImpl = globalThis.fetch) {
|
|
234139
|
-
const response = await fetchImpl(`${appUrl}/api/perch-terminal/cli-auth/config`, {
|
|
234140
|
-
method: "GET",
|
|
234141
|
-
headers: { Accept: "application/json" }
|
|
234142
|
-
});
|
|
234143
|
-
if (!response.ok) return { ok: false };
|
|
234144
|
-
return await response.json();
|
|
234145
|
-
}
|
|
234146
|
-
function selectOAuthProvider(providers, preferred) {
|
|
234147
|
-
const available = new Set(providers);
|
|
234148
|
-
if (preferred && available.has(preferred)) return preferred;
|
|
234149
|
-
if (available.has("google")) return "google";
|
|
234150
|
-
if (available.has("github")) return "github";
|
|
234151
|
-
return null;
|
|
234152
|
-
}
|
|
234153
|
-
function storedSessionFromSupabaseSession(input) {
|
|
234154
|
-
return {
|
|
234155
|
-
version: 1,
|
|
234156
|
-
appUrl: input.appUrl,
|
|
234157
|
-
accessToken: input.accessToken,
|
|
234158
|
-
refreshToken: input.refreshToken ?? null,
|
|
234159
|
-
expiresAt: input.expiresAt ?? null,
|
|
234160
|
-
userId: input.userId ?? null,
|
|
234161
|
-
email: input.email ?? null,
|
|
234162
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
234163
|
-
};
|
|
234164
|
-
}
|
|
234165
|
-
function createMemoryAuthStorage() {
|
|
234166
|
-
const values2 = /* @__PURE__ */ new Map();
|
|
234167
|
-
return {
|
|
234168
|
-
getItem: (key) => values2.get(key) ?? null,
|
|
234169
|
-
setItem: (key, value) => {
|
|
234170
|
-
values2.set(key, value);
|
|
234171
|
-
},
|
|
234172
|
-
removeItem: (key) => {
|
|
234173
|
-
values2.delete(key);
|
|
234174
|
-
}
|
|
234175
|
-
};
|
|
234176
|
-
}
|
|
234177
|
-
async function createOAuthCallbackServer(input) {
|
|
234178
|
-
let resolveResult = null;
|
|
234179
|
-
const resultPromise = new Promise((resolve5) => {
|
|
234180
|
-
resolveResult = resolve5;
|
|
234181
|
-
});
|
|
234182
|
-
const server = http.createServer((request, response) => {
|
|
234183
|
-
const requestUrl = new URL(request.url ?? "/", `http://${input.host}`);
|
|
234184
|
-
if (requestUrl.pathname !== CALLBACK_PATH) {
|
|
234185
|
-
response.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" });
|
|
234186
|
-
response.end("Not found");
|
|
234187
|
-
return;
|
|
234188
|
-
}
|
|
234189
|
-
const code = requestUrl.searchParams.get("code");
|
|
234190
|
-
const error = requestUrl.searchParams.get("error_description") ?? requestUrl.searchParams.get("error");
|
|
234191
|
-
if (!code) {
|
|
234192
|
-
response.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
|
|
234193
|
-
response.end(renderCallbackHtml(false));
|
|
234194
|
-
resolveResult?.({
|
|
234195
|
-
ok: false,
|
|
234196
|
-
code: "oauth_callback_missing_code",
|
|
234197
|
-
error: error ?? "OAuth callback did not include a code."
|
|
234198
|
-
});
|
|
234199
|
-
resolveResult = null;
|
|
234200
|
-
return;
|
|
234201
|
-
}
|
|
234202
|
-
response.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
234203
|
-
response.end(renderCallbackHtml(true));
|
|
234204
|
-
resolveResult?.({ ok: true, code });
|
|
234205
|
-
resolveResult = null;
|
|
234206
|
-
});
|
|
234207
|
-
await new Promise((resolve5, reject2) => {
|
|
234208
|
-
server.once("error", reject2);
|
|
234209
|
-
server.listen(0, input.host, () => {
|
|
234210
|
-
server.off("error", reject2);
|
|
234211
|
-
resolve5();
|
|
234212
|
-
});
|
|
234213
|
-
});
|
|
234214
|
-
const address = server.address();
|
|
234215
|
-
const redirectTo = `http://${input.host}:${address.port}${CALLBACK_PATH}`;
|
|
234216
|
-
const timeout = setTimeout(() => {
|
|
234217
|
-
resolveResult?.({
|
|
234218
|
-
ok: false,
|
|
234219
|
-
code: "oauth_timeout",
|
|
234220
|
-
error: "Timed out waiting for browser sign-in to complete."
|
|
234221
|
-
});
|
|
234222
|
-
resolveResult = null;
|
|
234223
|
-
}, input.timeoutMs);
|
|
234224
|
-
return {
|
|
234225
|
-
redirectTo,
|
|
234226
|
-
waitForCode: async () => resultPromise,
|
|
234227
|
-
close: async () => {
|
|
234228
|
-
clearTimeout(timeout);
|
|
234229
|
-
await new Promise((resolve5) => server.close(() => resolve5()));
|
|
234230
|
-
}
|
|
234231
|
-
};
|
|
234232
|
-
}
|
|
234233
|
-
function renderCallbackHtml(ok) {
|
|
234234
|
-
const title = ok ? "Perch CLI signed in" : "Perch CLI sign-in failed";
|
|
234235
|
-
const message = ok ? "You can close this tab and return to your terminal." : "Return to your terminal and try again.";
|
|
234236
|
-
return `<!doctype html><html><head><meta charset="utf-8"><title>${title}</title></head><body style="font-family:system-ui;padding:32px;background:#100d0b;color:#fff8f0"><h1>${title}</h1><p>${message}</p></body></html>`;
|
|
234237
|
-
}
|
|
234238
|
-
async function openExternal(url) {
|
|
234239
|
-
if (process.platform === "darwin") {
|
|
234240
|
-
await execFileAsync2("open", [url]);
|
|
234241
|
-
} else if (process.platform === "win32") {
|
|
234242
|
-
await execFileAsync2("cmd", ["/c", "start", "", url]);
|
|
234243
|
-
} else {
|
|
234244
|
-
await execFileAsync2("xdg-open", [url]);
|
|
234245
|
-
}
|
|
234246
|
-
}
|
|
234247
|
-
var execFileAsync2, DEFAULT_LOGIN_TIMEOUT_MS, CALLBACK_PATH;
|
|
234248
|
-
var init_cliStandaloneOAuth = __esm({
|
|
234249
|
-
"features/perchTerminal/runtime/cliHost/cliStandaloneOAuth.ts"() {
|
|
234250
|
-
"use strict";
|
|
234251
|
-
init_dist4();
|
|
234252
|
-
init_modelConnection();
|
|
234253
|
-
init_cliAuthSession();
|
|
234254
|
-
execFileAsync2 = promisify2(execFile2);
|
|
234255
|
-
DEFAULT_LOGIN_TIMEOUT_MS = 12e4;
|
|
234256
|
-
CALLBACK_PATH = "/callback";
|
|
234257
|
-
}
|
|
234258
|
-
});
|
|
234259
|
-
|
|
234260
|
-
// features/perchTerminal/runtime/cliHost/cliAuthRefresh.ts
|
|
234261
|
-
async function ensureFreshCliAuthSession(input) {
|
|
234262
|
-
const session = input.session;
|
|
234263
|
-
if (!session?.accessToken?.trim()) return null;
|
|
234264
|
-
const nowSeconds = Math.floor(Date.now() / 1e3);
|
|
234265
|
-
const stillUsable = !session.expiresAt || session.expiresAt > nowSeconds + 90;
|
|
234266
|
-
if (stillUsable) return session;
|
|
234267
|
-
if (!session.refreshToken?.trim()) return null;
|
|
234268
|
-
const fetchImpl = input.fetchImpl ?? globalThis.fetch;
|
|
234269
|
-
if (typeof fetchImpl !== "function") return null;
|
|
234270
|
-
const appUrl = session.appUrl?.trim();
|
|
234271
|
-
if (!appUrl) return null;
|
|
234272
|
-
const config = await fetchCliAuthConfig(appUrl, fetchImpl).catch(() => ({ ok: false }));
|
|
234273
|
-
if (!config.ok || !config.supabaseUrl || !config.supabaseAnonKey) return null;
|
|
234274
|
-
const supabase = createClient(config.supabaseUrl, config.supabaseAnonKey, {
|
|
234275
|
-
auth: {
|
|
234276
|
-
autoRefreshToken: false,
|
|
234277
|
-
detectSessionInUrl: false,
|
|
234278
|
-
persistSession: false
|
|
234279
|
-
}
|
|
234280
|
-
});
|
|
234281
|
-
try {
|
|
234282
|
-
const { data, error } = await supabase.auth.refreshSession({
|
|
234283
|
-
refresh_token: session.refreshToken
|
|
234284
|
-
});
|
|
234285
|
-
if (error || !data.session) return null;
|
|
234286
|
-
const refreshed = {
|
|
234287
|
-
version: 1,
|
|
234288
|
-
appUrl: session.appUrl,
|
|
234289
|
-
accessToken: data.session.access_token,
|
|
234290
|
-
refreshToken: data.session.refresh_token ?? session.refreshToken,
|
|
234291
|
-
expiresAt: data.session.expires_at ?? null,
|
|
234292
|
-
userId: data.session.user?.id ?? session.userId ?? null,
|
|
234293
|
-
email: data.session.user?.email ?? session.email ?? null,
|
|
234294
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
234295
|
-
};
|
|
234296
|
-
await writeStoredCliAuthSession(refreshed);
|
|
234297
|
-
return refreshed;
|
|
234298
|
-
} catch {
|
|
234299
|
-
return null;
|
|
234300
|
-
}
|
|
234301
|
-
}
|
|
234302
|
-
var init_cliAuthRefresh = __esm({
|
|
234303
|
-
"features/perchTerminal/runtime/cliHost/cliAuthRefresh.ts"() {
|
|
234304
|
-
"use strict";
|
|
234305
|
-
init_dist4();
|
|
234306
|
-
init_cliStandaloneOAuth();
|
|
234307
|
-
init_cliAuthSession();
|
|
234308
|
-
}
|
|
234309
|
-
});
|
|
234310
|
-
|
|
234311
|
-
// features/perchTerminal/runtime/cliHost/modelConnection.ts
|
|
234312
|
-
async function connectCliModelProxy(input = {}) {
|
|
234313
|
-
const storedSession = input.storedSession === void 0 ? await readStoredCliAuthSession() : input.storedSession;
|
|
234314
|
-
const usableStoredSession = await ensureFreshCliAuthSession({
|
|
234315
|
-
session: storedSession,
|
|
234316
|
-
fetchImpl: input.fetchImpl
|
|
234317
|
-
});
|
|
234318
|
-
const appUrl = resolveCliAppUrl(input.appUrl, usableStoredSession?.appUrl ?? null);
|
|
234319
|
-
if (!appUrl) return noCliModelConnection();
|
|
234320
|
-
if (!usableStoredSession?.accessToken) return noCliModelConnection(appUrl);
|
|
234321
|
-
const fetchImpl = input.fetchImpl ?? globalThis.fetch;
|
|
234322
|
-
if (typeof fetchImpl !== "function") return noCliModelConnection(appUrl);
|
|
234323
|
-
const selection = await fetchPublicModelDefaultSelection(
|
|
234324
|
-
appUrl,
|
|
234325
|
-
fetchImpl,
|
|
234326
|
-
usableStoredSession?.accessToken ?? null
|
|
234327
|
-
);
|
|
234328
|
-
if (!selection) return noCliModelConnection(appUrl);
|
|
234329
|
-
const priorProxy = process.env[MODEL_PROXY_ENV];
|
|
234330
|
-
const priorToken = process.env[MODEL_PROXY_TOKEN_ENV];
|
|
234331
|
-
process.env[MODEL_PROXY_ENV] = appUrl;
|
|
234332
|
-
if (usableStoredSession?.accessToken) {
|
|
234333
|
-
process.env[MODEL_PROXY_TOKEN_ENV] = usableStoredSession.accessToken;
|
|
234334
|
-
}
|
|
234335
|
-
return {
|
|
234336
|
-
appUrl,
|
|
234337
|
-
authenticated: !!usableStoredSession?.accessToken,
|
|
234338
|
-
userId: usableStoredSession?.userId ?? null,
|
|
234339
|
-
email: usableStoredSession?.email ?? null,
|
|
234340
|
-
founderModelSelection: selection,
|
|
234341
|
-
restore: () => {
|
|
234342
|
-
if (priorProxy === void 0) {
|
|
234343
|
-
delete process.env[MODEL_PROXY_ENV];
|
|
234344
|
-
} else {
|
|
234345
|
-
process.env[MODEL_PROXY_ENV] = priorProxy;
|
|
234346
|
-
}
|
|
234347
|
-
if (priorToken === void 0) {
|
|
234348
|
-
delete process.env[MODEL_PROXY_TOKEN_ENV];
|
|
234349
|
-
} else {
|
|
234350
|
-
process.env[MODEL_PROXY_TOKEN_ENV] = priorToken;
|
|
234351
|
-
}
|
|
234352
|
-
}
|
|
234353
|
-
};
|
|
234354
|
-
}
|
|
234355
|
-
function resolveCliAppUrl(explicit, stored) {
|
|
234356
|
-
const raw = explicit?.trim() || process.env[CLI_APP_URL_ENV]?.trim() || process.env[FALLBACK_APP_URL_ENV]?.trim() || stored?.trim() || DEFAULT_CLI_APP_URL;
|
|
234357
|
-
if (!raw) return null;
|
|
234358
|
-
const withProtocol = /^https?:\/\//i.test(raw) ? raw : `http://${raw}`;
|
|
234359
|
-
try {
|
|
234360
|
-
const url = new URL(withProtocol);
|
|
234361
|
-
url.pathname = url.pathname.replace(/\/+$/, "");
|
|
234362
|
-
url.search = "";
|
|
234363
|
-
url.hash = "";
|
|
234364
|
-
return url.toString().replace(/\/+$/, "");
|
|
234365
|
-
} catch {
|
|
234366
|
-
return null;
|
|
234367
|
-
}
|
|
234368
|
-
}
|
|
234369
|
-
async function fetchPublicModelDefaultSelection(appUrl, fetchImpl, accessToken) {
|
|
234370
|
-
const url = `${appUrl}/api/perch-terminal/public-model-default`;
|
|
234371
|
-
const controller = new AbortController();
|
|
234372
|
-
const timeout = setTimeout(() => controller.abort(), 3500);
|
|
234373
|
-
try {
|
|
234374
|
-
const response = await fetchImpl(url, {
|
|
234375
|
-
method: "GET",
|
|
234376
|
-
headers: {
|
|
234377
|
-
Accept: "application/json",
|
|
234378
|
-
...accessToken ? { Authorization: `Bearer ${accessToken}` } : {}
|
|
234379
|
-
},
|
|
234380
|
-
signal: controller.signal
|
|
234381
|
-
});
|
|
234382
|
-
if (!response.ok) return null;
|
|
234383
|
-
const body = await response.json();
|
|
234384
|
-
if (!body || typeof body !== "object") return null;
|
|
234385
|
-
const record = body;
|
|
234386
|
-
if (record.ok !== true) return null;
|
|
234387
|
-
return parsePublicModelDefaultPayload({ selection: record.selection }).selection;
|
|
234388
|
-
} catch {
|
|
234389
|
-
return null;
|
|
234390
|
-
} finally {
|
|
234391
|
-
clearTimeout(timeout);
|
|
234392
|
-
}
|
|
234393
|
-
}
|
|
234394
|
-
function noCliModelConnection(appUrl = null) {
|
|
234395
|
-
return {
|
|
234396
|
-
appUrl,
|
|
234397
|
-
authenticated: false,
|
|
234398
|
-
userId: null,
|
|
234399
|
-
email: null,
|
|
234400
|
-
founderModelSelection: null,
|
|
234401
|
-
restore: () => {
|
|
234402
|
-
}
|
|
234403
|
-
};
|
|
234404
|
-
}
|
|
234405
|
-
var MODEL_PROXY_ENV, MODEL_PROXY_TOKEN_ENV, CLI_APP_URL_ENV, FALLBACK_APP_URL_ENV, DEFAULT_CLI_APP_URL;
|
|
234406
|
-
var init_modelConnection = __esm({
|
|
234407
|
-
"features/perchTerminal/runtime/cliHost/modelConnection.ts"() {
|
|
234408
|
-
"use strict";
|
|
234409
|
-
init_publicModelDefault();
|
|
234410
|
-
init_cliAuthSession();
|
|
234411
|
-
init_cliAuthRefresh();
|
|
234412
|
-
MODEL_PROXY_ENV = "PERCH_MODEL_CALL_PROXY_URL";
|
|
234413
|
-
MODEL_PROXY_TOKEN_ENV = "PERCH_MODEL_CALL_PROXY_TOKEN";
|
|
234414
|
-
CLI_APP_URL_ENV = "PERCH_CLI_APP_URL";
|
|
234415
|
-
FALLBACK_APP_URL_ENV = "PERCH_APP_URL";
|
|
234416
|
-
DEFAULT_CLI_APP_URL = "https://app.perchai.app";
|
|
234417
|
-
}
|
|
234418
|
-
});
|
|
234419
|
-
|
|
234420
235078
|
// lib/perchTerminal/statusLabels.ts
|
|
234421
235079
|
function sectionLabel(sectionKey) {
|
|
234422
235080
|
if (STATUS_LABELS[sectionKey]) return STATUS_LABELS[sectionKey];
|
|
@@ -234937,6 +235595,17 @@ function claimFirstRun(dir = stateDir()) {
|
|
|
234937
235595
|
return false;
|
|
234938
235596
|
}
|
|
234939
235597
|
}
|
|
235598
|
+
function claimFirstPrompt(dir = stateDir()) {
|
|
235599
|
+
const file = path17.join(dir, "cli-first-prompt");
|
|
235600
|
+
try {
|
|
235601
|
+
if (fs15.existsSync(file)) return false;
|
|
235602
|
+
fs15.mkdirSync(dir, { recursive: true });
|
|
235603
|
+
fs15.writeFileSync(file, (/* @__PURE__ */ new Date()).toISOString());
|
|
235604
|
+
return true;
|
|
235605
|
+
} catch {
|
|
235606
|
+
return false;
|
|
235607
|
+
}
|
|
235608
|
+
}
|
|
234940
235609
|
async function postCapture(event, properties) {
|
|
234941
235610
|
const controller = new AbortController();
|
|
234942
235611
|
const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS2);
|
|
@@ -234963,6 +235632,17 @@ function captureCliFirstRun(version4) {
|
|
|
234963
235632
|
if (!claimFirstRun()) return;
|
|
234964
235633
|
void postCapture("cli_first_run", { version: version4, platform: process.platform });
|
|
234965
235634
|
}
|
|
235635
|
+
function captureCliSessionStarted() {
|
|
235636
|
+
if (!POSTHOG_KEY || telemetryDisabled()) return;
|
|
235637
|
+
void postCapture("cli_session_started", { platform: process.platform });
|
|
235638
|
+
}
|
|
235639
|
+
function captureCliMessageSent() {
|
|
235640
|
+
if (!POSTHOG_KEY || telemetryDisabled()) return;
|
|
235641
|
+
void postCapture("message_sent", { surface: "cli" });
|
|
235642
|
+
if (claimFirstPrompt()) {
|
|
235643
|
+
void postCapture("first_message", { surface: "cli" });
|
|
235644
|
+
}
|
|
235645
|
+
}
|
|
234966
235646
|
var POSTHOG_KEY, POSTHOG_HOST, FETCH_TIMEOUT_MS2;
|
|
234967
235647
|
var init_cliAnalytics = __esm({
|
|
234968
235648
|
"features/perchTerminal/runtime/cliHost/cliAnalytics.ts"() {
|
|
@@ -289269,7 +289949,7 @@ function flockEventToCliRow(event) {
|
|
|
289269
289949
|
return {
|
|
289270
289950
|
id: `flock-${event.flockId}-${event.flockWorkerId}`,
|
|
289271
289951
|
text: `${event.displayName} \xB7 ${event.nickname} \xB7 ${FLOCK_STATUS_LABELS[event.status] ?? event.status}`,
|
|
289272
|
-
tone: event.status === "done" ? "success" : event.status === "failed" ? "danger" : "muted"
|
|
289952
|
+
tone: event.status === "done" ? "success" : event.status === "failed" || event.status === "blocked" ? "danger" : "muted"
|
|
289273
289953
|
};
|
|
289274
289954
|
case "flock_run_completed":
|
|
289275
289955
|
return {
|
|
@@ -289390,6 +290070,7 @@ ${HELP_TEXT}`);
|
|
|
289390
290070
|
recentMessages: nextRecentMessages,
|
|
289391
290071
|
contextSnapshot: result3.contextSnapshot ?? persisted?.contextSnapshot ?? null
|
|
289392
290072
|
});
|
|
290073
|
+
captureCliMessageSent();
|
|
289393
290074
|
return cliExitCodeForTurn(result3);
|
|
289394
290075
|
} finally {
|
|
289395
290076
|
connection.restore();
|
|
@@ -289690,6 +290371,7 @@ async function runReadlineInteractivePerchCli(writer, deps, options) {
|
|
|
289690
290371
|
terminal: true
|
|
289691
290372
|
});
|
|
289692
290373
|
const state = createInteractiveCliState(options);
|
|
290374
|
+
captureCliSessionStarted();
|
|
289693
290375
|
const connectModelProxy = deps.connectModelProxy ?? connectCliModelProxy;
|
|
289694
290376
|
let connection = await connectModelProxy({ appUrl: state.appUrl });
|
|
289695
290377
|
if (!state.appUrl && connection.appUrl) state.appUrl = connection.appUrl;
|
|
@@ -289750,6 +290432,7 @@ async function runReadlineInteractivePerchCli(writer, deps, options) {
|
|
|
289750
290432
|
});
|
|
289751
290433
|
if (!opts.systemInitiated) {
|
|
289752
290434
|
appendRecentMessage(state.recentMessages, "user", turnPrompt);
|
|
290435
|
+
captureCliMessageSent();
|
|
289753
290436
|
}
|
|
289754
290437
|
if (result2.assistantText.trim()) {
|
|
289755
290438
|
appendRecentMessage(state.recentMessages, "assistant", result2.assistantText.trim());
|
|
@@ -289842,6 +290525,7 @@ async function runInkInteractivePerchCli(writer, deps, options) {
|
|
|
289842
290525
|
const runTurn = deps.runCliTurn ?? runPerchCliTurn;
|
|
289843
290526
|
const updateNotice = resolveUpdateNotice(CLI_PACKAGE_VERSION);
|
|
289844
290527
|
captureCliFirstRun(CLI_PACKAGE_VERSION);
|
|
290528
|
+
captureCliSessionStarted();
|
|
289845
290529
|
const instance = Ink2.render(
|
|
289846
290530
|
React11.createElement(function PerchInkApp() {
|
|
289847
290531
|
const app = Ink2.useApp();
|
|
@@ -290463,6 +291147,7 @@ async function runInkInteractivePerchCli(writer, deps, options) {
|
|
|
290463
291147
|
}
|
|
290464
291148
|
if (!systemInitiated) {
|
|
290465
291149
|
appendRecentMessage(state.recentMessages, "user", prompt);
|
|
291150
|
+
captureCliMessageSent();
|
|
290466
291151
|
}
|
|
290467
291152
|
if (assistantText) appendRecentMessage(state.recentMessages, "assistant", assistantText);
|
|
290468
291153
|
trimRecentMessages(state.recentMessages);
|