perchai-cli 2.4.48 → 2.4.50
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 +1222 -502
- package/package.json +1 -1
package/dist/perch.mjs
CHANGED
|
@@ -75566,7 +75566,6 @@ var init_payroll = __esm({
|
|
|
75566
75566
|
// lib/perchBusinessTools/index.ts
|
|
75567
75567
|
var init_perchBusinessTools = __esm({
|
|
75568
75568
|
"lib/perchBusinessTools/index.ts"() {
|
|
75569
|
-
"use strict";
|
|
75570
75569
|
init_generateAPAuditPacket();
|
|
75571
75570
|
init_inventoryFolder();
|
|
75572
75571
|
init_loadBusinessTables();
|
|
@@ -83486,6 +83485,8 @@ Browser Operator skill contract:
|
|
|
83486
83485
|
- 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
83486
|
- Use semantic browser tools for ordinary controls with text, role, or aria labels.
|
|
83488
83487
|
- 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.
|
|
83488
|
+
- 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.
|
|
83489
|
+
- 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
83490
|
- 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
83491
|
- 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
83492
|
- Never claim completion from intent alone; require visible proof, save/sent status, URL, receipt, or screenshot evidence.
|
|
@@ -83591,17 +83592,20 @@ Rules:
|
|
|
83591
83592
|
name: "Browser Operator",
|
|
83592
83593
|
description: "Operates a logged-in browser turn-by-turn for Quill delivery and web tasks.",
|
|
83593
83594
|
// Browser work runs on the selected main model. Native vision models see
|
|
83594
|
-
// screenshots directly; text-only models
|
|
83595
|
-
//
|
|
83595
|
+
// screenshots directly; for text-only models the loop automatically runs each
|
|
83596
|
+
// post-action screenshot through the vision sidecar and injects the readout,
|
|
83597
|
+
// so the operator is always sighted. visionInspect({ currentScreen: true })
|
|
83598
|
+
// remains available for targeted follow-up questions.
|
|
83596
83599
|
lane: "chat",
|
|
83597
83600
|
systemPrompt: `You are Quill's Browser Operator. You operate the user's logged-in browser like a careful human.
|
|
83598
83601
|
|
|
83599
83602
|
Rules:
|
|
83600
83603
|
- 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
83604
|
- 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
|
|
83605
|
+
- 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.
|
|
83606
|
+
- 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
83607
|
- 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
|
|
83608
|
+
- 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
83609
|
- 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
83610
|
- Do not claim completion unless the browser shows a confirmation, URL, saved state, sent state, or other visible receipt.
|
|
83607
83611
|
|
|
@@ -86236,7 +86240,8 @@ var init_desktopContextSection = __esm({
|
|
|
86236
86240
|
BROWSER_EXECUTION_GUIDANCE = `## Browser execution \u2014 logged-in web surface
|
|
86237
86241
|
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
86242
|
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.
|
|
86243
|
+
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.
|
|
86244
|
+
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
86245
|
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
86246
|
}
|
|
86242
86247
|
});
|
|
@@ -87398,7 +87403,7 @@ var init_threadLedger = __esm({
|
|
|
87398
87403
|
});
|
|
87399
87404
|
|
|
87400
87405
|
// 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;
|
|
87406
|
+
var VISIBLE_OUTPUT_STYLE_GUIDANCE, PERCH_OPERATOR_CAPABILITIES, SAFFRON_STYLE_CONTRACT, QUILL_STYLE_CONTRACT, SAFFRON_CORE_IDENTITY, QUILL_CORE_IDENTITY, PERCH_MEMORY_GUIDANCE;
|
|
87402
87407
|
var init_perchMemoryGuidance = __esm({
|
|
87403
87408
|
"features/perchTerminal/runtime/perchMemoryGuidance.ts"() {
|
|
87404
87409
|
"use strict";
|
|
@@ -87413,6 +87418,36 @@ Avoid stock AI phrases: "delve," "nuanced," "robust," "seamless,"
|
|
|
87413
87418
|
Be concise when the user is moving fast.
|
|
87414
87419
|
Preserve the persona: Quill is warm, literate, and direct; Saffron is sharp,
|
|
87415
87420
|
practical, and confident.
|
|
87421
|
+
`.trim();
|
|
87422
|
+
PERCH_OPERATOR_CAPABILITIES = `
|
|
87423
|
+
## What you can do (and you can do a lot)
|
|
87424
|
+
|
|
87425
|
+
You run on the user's Mac through Perch Desktop. You are a real operator, not a
|
|
87426
|
+
chat box, and you have the tools to act. When someone asks what you can do, or
|
|
87427
|
+
whether you can control their computer, answer with specifics and offer to show
|
|
87428
|
+
them rather than listing what you cannot do. You can:
|
|
87429
|
+
|
|
87430
|
+
- Run shell commands in folders the user approves, including installing tools and
|
|
87431
|
+
packages, running scripts and software, and starting long jobs in the background.
|
|
87432
|
+
- Work across their files: read, write, edit, search with glob and grep, move,
|
|
87433
|
+
and organize anything inside approved folders.
|
|
87434
|
+
- Run real analysis in a sealed sandbox: write and execute Python or Node over
|
|
87435
|
+
their actual data.
|
|
87436
|
+
- Drive a real browser: navigate, click, type, fill forms, research the web, and
|
|
87437
|
+
pull sources.
|
|
87438
|
+
- Deliver finished work: draft and send Gmail, create Google Docs and Sheets, and
|
|
87439
|
+
add calendar events.
|
|
87440
|
+
- Do forensic and financial work: AP audits, reconciliations, statement and
|
|
87441
|
+
earnings reviews, and multi-step suites.
|
|
87442
|
+
|
|
87443
|
+
Lead with what you can do and offer to prove it, for example "want me to run
|
|
87444
|
+
something to show you?". Do not talk yourself down or open with a list of limits.
|
|
87445
|
+
|
|
87446
|
+
State the real limits once, and only when they matter: file and command access is
|
|
87447
|
+
scoped to the folders the user approves, and commands run under their permission
|
|
87448
|
+
settings. You work through the shell, their files, the sandbox, and the browser,
|
|
87449
|
+
not through arbitrary macOS system settings or other desktop apps. That is already
|
|
87450
|
+
a lot. Show it.
|
|
87416
87451
|
`.trim();
|
|
87417
87452
|
SAFFRON_STYLE_CONTRACT = `
|
|
87418
87453
|
## Voice contract \u2014 Saffron (visible output only)
|
|
@@ -87449,6 +87484,8 @@ You genuinely enjoy finding something interesting buried in a mess.
|
|
|
87449
87484
|
|
|
87450
87485
|
You're not an assistant. You're the operator who gets the work done.
|
|
87451
87486
|
|
|
87487
|
+
${PERCH_OPERATOR_CAPABILITIES}
|
|
87488
|
+
|
|
87452
87489
|
## How you work
|
|
87453
87490
|
|
|
87454
87491
|
Lead with the finding. Skip the preamble. "Seven duplicate payments, same vendor,
|
|
@@ -87579,6 +87616,8 @@ the user needs analysis, files, data, an audit, or delivery, you do that work
|
|
|
87579
87616
|
yourself with the same tools \u2014 in your own voice. You never punt real work to
|
|
87580
87617
|
another persona.
|
|
87581
87618
|
|
|
87619
|
+
${PERCH_OPERATOR_CAPABILITIES}
|
|
87620
|
+
|
|
87582
87621
|
## How you work
|
|
87583
87622
|
|
|
87584
87623
|
Read first. Before you draft a single sentence, you know what the piece is for,
|
|
@@ -91110,6 +91149,7 @@ Final answers lead with findings, name artifacts or delivery status, and give on
|
|
|
91110
91149
|
var MARKET_DESK_TOOL_NAMES;
|
|
91111
91150
|
var init_marketDeskAccess = __esm({
|
|
91112
91151
|
"features/perchTerminal/runtime/marketDesk/marketDeskAccess.ts"() {
|
|
91152
|
+
"use strict";
|
|
91113
91153
|
init_toolNames();
|
|
91114
91154
|
MARKET_DESK_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
91115
91155
|
TOOL_NAMES.getMarketSignal,
|
|
@@ -91789,6 +91829,7 @@ function listFinancialPlaybooks() {
|
|
|
91789
91829
|
var AP_AUDIT_PACKET_DEF, PLAYBOOKS;
|
|
91790
91830
|
var init_registry2 = __esm({
|
|
91791
91831
|
"features/perchTerminal/runtime/financialPlaybooks/registry.ts"() {
|
|
91832
|
+
"use strict";
|
|
91792
91833
|
init_managedWorkflowRegistry2();
|
|
91793
91834
|
init_toolNames();
|
|
91794
91835
|
AP_AUDIT_PACKET_DEF = {
|
|
@@ -92416,6 +92457,297 @@ var init_coreSystemPromptLanes = __esm({
|
|
|
92416
92457
|
}
|
|
92417
92458
|
});
|
|
92418
92459
|
|
|
92460
|
+
// lib/perchTerminal/appHostGate.ts
|
|
92461
|
+
var PERCH_CHAT_WEB_URL, PERCH_MARKETING_SITE_URL, PERCH_MARKETING_DESKTOP_URL, PERCH_MARKETING_CLI_URL, PERCH_CLI_INSTALL_COMMAND;
|
|
92462
|
+
var init_appHostGate = __esm({
|
|
92463
|
+
"lib/perchTerminal/appHostGate.ts"() {
|
|
92464
|
+
PERCH_CHAT_WEB_URL = process.env.NEXT_PUBLIC_PERCHAI_WEB_URL?.trim() || "https://chat.perchai.app";
|
|
92465
|
+
PERCH_MARKETING_SITE_URL = process.env.NEXT_PUBLIC_MARKETING_SITE_URL?.trim() || "https://perchai.app";
|
|
92466
|
+
PERCH_MARKETING_DESKTOP_URL = `${PERCH_MARKETING_SITE_URL}/desktop`;
|
|
92467
|
+
PERCH_MARKETING_CLI_URL = `${PERCH_MARKETING_SITE_URL}/cli`;
|
|
92468
|
+
PERCH_CLI_INSTALL_COMMAND = "npm install -g perchai-cli";
|
|
92469
|
+
}
|
|
92470
|
+
});
|
|
92471
|
+
|
|
92472
|
+
// lib/perch-ai/plan-catalog.ts
|
|
92473
|
+
var PERCH_AI_PLAN_CATALOG;
|
|
92474
|
+
var init_plan_catalog = __esm({
|
|
92475
|
+
"lib/perch-ai/plan-catalog.ts"() {
|
|
92476
|
+
PERCH_AI_PLAN_CATALOG = [
|
|
92477
|
+
{
|
|
92478
|
+
code: "internal",
|
|
92479
|
+
name: "Internal",
|
|
92480
|
+
description: "Full Perch AI internal/admin access for Perch Terminal operators.",
|
|
92481
|
+
entitlements: {
|
|
92482
|
+
"shell.preview": { enabled: true },
|
|
92483
|
+
"auth.access_codes.redeem": { enabled: true },
|
|
92484
|
+
"desktop.runtime": { enabled: true },
|
|
92485
|
+
"coding.workspace": { enabled: true },
|
|
92486
|
+
"ap.audit": { enabled: true },
|
|
92487
|
+
"writing.studio": { enabled: true },
|
|
92488
|
+
"writing.studio.web": { enabled: true },
|
|
92489
|
+
"admin.console": { enabled: true },
|
|
92490
|
+
"limits.daily_runs": { limit: null },
|
|
92491
|
+
"limits.weekly_runs": { limit: null },
|
|
92492
|
+
"limits.max_context_tokens": { limit: 2e5 },
|
|
92493
|
+
"limits.allowed_models": { models: ["*"] }
|
|
92494
|
+
}
|
|
92495
|
+
},
|
|
92496
|
+
{
|
|
92497
|
+
code: "pilot",
|
|
92498
|
+
name: "Pilot",
|
|
92499
|
+
description: "Self-serve pilot access with full product features.",
|
|
92500
|
+
entitlements: {
|
|
92501
|
+
"shell.preview": { enabled: true },
|
|
92502
|
+
"auth.access_codes.redeem": { enabled: true },
|
|
92503
|
+
"desktop.runtime": { enabled: true },
|
|
92504
|
+
"coding.workspace": { enabled: true },
|
|
92505
|
+
"ap.audit": { enabled: true },
|
|
92506
|
+
"writing.studio": { enabled: true },
|
|
92507
|
+
"writing.studio.web": { enabled: true },
|
|
92508
|
+
// Admin console stays off for self-serve signups (it grants operator/admin
|
|
92509
|
+
// tooling, not a product feature). Flip to true only for trusted accounts.
|
|
92510
|
+
"admin.console": { enabled: false },
|
|
92511
|
+
"limits.daily_runs": { limit: null },
|
|
92512
|
+
"limits.weekly_runs": { limit: null },
|
|
92513
|
+
"limits.max_context_tokens": { limit: 2e5 },
|
|
92514
|
+
"limits.allowed_models": { models: ["*"] }
|
|
92515
|
+
}
|
|
92516
|
+
},
|
|
92517
|
+
{
|
|
92518
|
+
code: "demo",
|
|
92519
|
+
name: "Demo",
|
|
92520
|
+
description: "Restricted demo access for guided evaluations.",
|
|
92521
|
+
entitlements: {
|
|
92522
|
+
"shell.preview": { enabled: true },
|
|
92523
|
+
"auth.access_codes.redeem": { enabled: true },
|
|
92524
|
+
"desktop.runtime": { enabled: false },
|
|
92525
|
+
"coding.workspace": { enabled: false },
|
|
92526
|
+
"ap.audit": { enabled: false },
|
|
92527
|
+
"writing.studio": { enabled: false },
|
|
92528
|
+
"writing.studio.web": { enabled: false },
|
|
92529
|
+
"admin.console": { enabled: false },
|
|
92530
|
+
"limits.daily_runs": { limit: 10 },
|
|
92531
|
+
"limits.weekly_runs": { limit: 30 },
|
|
92532
|
+
"limits.max_context_tokens": { limit: 32e3 },
|
|
92533
|
+
"limits.allowed_models": { models: ["gpt-4o-mini"] }
|
|
92534
|
+
}
|
|
92535
|
+
}
|
|
92536
|
+
];
|
|
92537
|
+
}
|
|
92538
|
+
});
|
|
92539
|
+
|
|
92540
|
+
// features/perchTerminal/runtime/roost.ts
|
|
92541
|
+
function normalizeRoostTier(value) {
|
|
92542
|
+
if (value === "max") return "pro_max";
|
|
92543
|
+
return isRoostTier(value) ? value : null;
|
|
92544
|
+
}
|
|
92545
|
+
function normalizeRoostModelChoice(value) {
|
|
92546
|
+
if (value === "internal") return "internal";
|
|
92547
|
+
return normalizeRoostTier(value);
|
|
92548
|
+
}
|
|
92549
|
+
function isRoostTier(value) {
|
|
92550
|
+
return value === "standard" || value === "standard_max" || value === "pro" || value === "pro_max";
|
|
92551
|
+
}
|
|
92552
|
+
var ROOST_TIERS, ROOST_TIER_LABELS, ROOST_TIER_CONTEXT_LABELS;
|
|
92553
|
+
var init_roost = __esm({
|
|
92554
|
+
"features/perchTerminal/runtime/roost.ts"() {
|
|
92555
|
+
"use strict";
|
|
92556
|
+
init_modelRegistry();
|
|
92557
|
+
ROOST_TIERS = ["standard", "standard_max", "pro", "pro_max"];
|
|
92558
|
+
ROOST_TIER_LABELS = {
|
|
92559
|
+
standard: "Standard",
|
|
92560
|
+
standard_max: "Standard Max",
|
|
92561
|
+
pro: "Pro",
|
|
92562
|
+
pro_max: "Pro Max"
|
|
92563
|
+
};
|
|
92564
|
+
ROOST_TIER_CONTEXT_LABELS = {
|
|
92565
|
+
standard: "200K context",
|
|
92566
|
+
standard_max: "1M context",
|
|
92567
|
+
pro: "200K context",
|
|
92568
|
+
pro_max: "1M context"
|
|
92569
|
+
};
|
|
92570
|
+
}
|
|
92571
|
+
});
|
|
92572
|
+
|
|
92573
|
+
// lib/perch/perchFactsCore.ts
|
|
92574
|
+
function getPilotEntitlements() {
|
|
92575
|
+
const pilot = PERCH_AI_PLAN_CATALOG.find((p) => p.code === "pilot");
|
|
92576
|
+
if (!pilot) return null;
|
|
92577
|
+
return pilot.entitlements;
|
|
92578
|
+
}
|
|
92579
|
+
function formatUsageLine() {
|
|
92580
|
+
const ent = getPilotEntitlements();
|
|
92581
|
+
if (!ent) return "Usage limits are configured per plan.";
|
|
92582
|
+
const daily = ent["limits.daily_runs"]?.limit;
|
|
92583
|
+
const weekly = ent["limits.weekly_runs"]?.limit;
|
|
92584
|
+
if (daily === null && weekly === null) {
|
|
92585
|
+
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.";
|
|
92586
|
+
}
|
|
92587
|
+
const parts = [];
|
|
92588
|
+
if (daily !== null && daily !== void 0) parts.push(`${daily} runs per day`);
|
|
92589
|
+
if (weekly !== null && weekly !== void 0) parts.push(`${weekly} runs per week`);
|
|
92590
|
+
return `Usage is capped at ${parts.join(" and ")} on this plan. No credit card is required.`;
|
|
92591
|
+
}
|
|
92592
|
+
function buildRoostTierSummary() {
|
|
92593
|
+
const lines = ROOST_TIERS.map(
|
|
92594
|
+
(tier) => `- ${ROOST_TIER_LABELS[tier]} (${ROOST_TIER_CONTEXT_LABELS[tier]})`
|
|
92595
|
+
);
|
|
92596
|
+
return lines.join("\n");
|
|
92597
|
+
}
|
|
92598
|
+
function buildWebCapabilityList() {
|
|
92599
|
+
return [
|
|
92600
|
+
"- Grounded answers with real inline citations from your uploaded documents.",
|
|
92601
|
+
"- Upload and ask: PDF, spreadsheet, image, and document analysis.",
|
|
92602
|
+
"- Web search with cited results.",
|
|
92603
|
+
"- Draft citation check: marks each claim verified or unverified.",
|
|
92604
|
+
"",
|
|
92605
|
+
"The web chat does not run shell commands, access your local files, control a browser,",
|
|
92606
|
+
`or operate your computer. Those capabilities live in Perch Terminal (Desktop and CLI).`,
|
|
92607
|
+
`Get Perch Terminal at ${PERCH_MARKETING_DESKTOP_URL}.`
|
|
92608
|
+
].join("\n");
|
|
92609
|
+
}
|
|
92610
|
+
function buildTerminalCapabilityList() {
|
|
92611
|
+
const skillLines = DEDUPLICATED_SKILLS.map(
|
|
92612
|
+
(s) => `- ${s.id}: ${s.description}`
|
|
92613
|
+
).join("\n");
|
|
92614
|
+
return [
|
|
92615
|
+
"Core operator capabilities (available in Desktop and CLI):",
|
|
92616
|
+
"- Run shell commands in folders the user approves.",
|
|
92617
|
+
"- Read, write, edit, search, and organize files across approved folders.",
|
|
92618
|
+
"- Run real analysis in a sealed sandbox: Python and Node over actual data.",
|
|
92619
|
+
"- Drive a real browser: navigate, click, fill forms, research the web.",
|
|
92620
|
+
"- Deliver finished work: draft and send Gmail, create Google Docs and Sheets, add calendar events.",
|
|
92621
|
+
"- Financial and forensic work: AP audits, reconciliations, statement and earnings reviews, multi-step suites.",
|
|
92622
|
+
"",
|
|
92623
|
+
"Specialized skills (activated per task from the skill registry):",
|
|
92624
|
+
skillLines
|
|
92625
|
+
].join("\n");
|
|
92626
|
+
}
|
|
92627
|
+
function buildPerchFactsCore(surface) {
|
|
92628
|
+
const capabilitySection = surface === "web" ? `### What this web chat can do
|
|
92629
|
+
|
|
92630
|
+
${buildWebCapabilityList()}` : `### What Perch Terminal can do
|
|
92631
|
+
|
|
92632
|
+
${buildTerminalCapabilityList()}`;
|
|
92633
|
+
const tierTwoNote = surface === "terminal" ? `
|
|
92634
|
+
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.` : "";
|
|
92635
|
+
return [
|
|
92636
|
+
INSTRUCTION_PREAMBLE,
|
|
92637
|
+
"",
|
|
92638
|
+
WHAT_IS_PERCH,
|
|
92639
|
+
"",
|
|
92640
|
+
ACCESS_AND_PRICING,
|
|
92641
|
+
"",
|
|
92642
|
+
MODEL_STORY,
|
|
92643
|
+
"",
|
|
92644
|
+
capabilitySection,
|
|
92645
|
+
tierTwoNote
|
|
92646
|
+
].join("\n").trim();
|
|
92647
|
+
}
|
|
92648
|
+
var PERCH_SKILL_REGISTRY, DEDUPLICATED_SKILLS, INSTRUCTION_PREAMBLE, WHAT_IS_PERCH, ACCESS_AND_PRICING, MODEL_STORY;
|
|
92649
|
+
var init_perchFactsCore = __esm({
|
|
92650
|
+
"lib/perch/perchFactsCore.ts"() {
|
|
92651
|
+
init_appHostGate();
|
|
92652
|
+
init_plan_catalog();
|
|
92653
|
+
init_roost();
|
|
92654
|
+
PERCH_SKILL_REGISTRY = [
|
|
92655
|
+
{
|
|
92656
|
+
id: "workspace",
|
|
92657
|
+
description: "Navigate, inspect, search, summarize, and safely edit local workspaces, attached folders, source inventories, project files, and codebases with exact file-grounded evidence."
|
|
92658
|
+
},
|
|
92659
|
+
{
|
|
92660
|
+
id: "browser-operator",
|
|
92661
|
+
description: "Operate Perch's logged-in browser surface using visual state, semantic DOM targets, and coordinate actions when available."
|
|
92662
|
+
},
|
|
92663
|
+
{
|
|
92664
|
+
id: "research",
|
|
92665
|
+
description: "Gather, compare, rank, and cite evidence from public web sources, scholarly sources, workspace files, source packets, and attached local folders."
|
|
92666
|
+
},
|
|
92667
|
+
{
|
|
92668
|
+
id: "documents",
|
|
92669
|
+
description: "Create, edit, verify, and deliver professional document artifacts such as DOCX, Google Docs, memos, letters, reports, redlines, and formal written deliverables."
|
|
92670
|
+
},
|
|
92671
|
+
{
|
|
92672
|
+
id: "spreadsheets",
|
|
92673
|
+
description: "Create, inspect, clean, calculate, format, and verify spreadsheet artifacts such as XLSX, CSV, TSV, Google Sheets, formulas, pivots, charts, and reconciliation workbooks."
|
|
92674
|
+
},
|
|
92675
|
+
{
|
|
92676
|
+
id: "pdf",
|
|
92677
|
+
description: "Extract, summarize, cite, split, merge, create, inspect, and verify PDF content, including text PDFs, scanned PDFs, tables, forms, page citations, and generated reports."
|
|
92678
|
+
},
|
|
92679
|
+
{
|
|
92680
|
+
id: "presentations",
|
|
92681
|
+
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."
|
|
92682
|
+
},
|
|
92683
|
+
{
|
|
92684
|
+
id: "data-quality",
|
|
92685
|
+
description: "Profile messy data, find anomalies, reconcile systems, explain exceptions, score risk, and produce audit-ready evidence through deterministic math before narrative."
|
|
92686
|
+
},
|
|
92687
|
+
{
|
|
92688
|
+
id: "diagrams",
|
|
92689
|
+
description: "Create, edit, and verify diagrams, graphs, workflows, org charts, entity networks, control graphs, architecture maps, and evidence-backed visual structures."
|
|
92690
|
+
},
|
|
92691
|
+
{
|
|
92692
|
+
id: "github",
|
|
92693
|
+
description: "Inspect GitHub and git repository state, review commits and pull requests, debug checks, and publish local changes with clear permission boundaries."
|
|
92694
|
+
},
|
|
92695
|
+
{
|
|
92696
|
+
id: "wait-operator",
|
|
92697
|
+
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."
|
|
92698
|
+
},
|
|
92699
|
+
{
|
|
92700
|
+
id: "data-quality",
|
|
92701
|
+
description: "Profile messy data, find anomalies, reconcile systems, explain exceptions, score risk, and produce audit-ready evidence through deterministic math before narrative."
|
|
92702
|
+
}
|
|
92703
|
+
];
|
|
92704
|
+
DEDUPLICATED_SKILLS = Array.from(
|
|
92705
|
+
new Map(PERCH_SKILL_REGISTRY.map((s) => [s.id, s])).values()
|
|
92706
|
+
);
|
|
92707
|
+
INSTRUCTION_PREAMBLE = `## Perch product facts -- authoritative reference
|
|
92708
|
+
|
|
92709
|
+
For any question about Perch (what it is, whether it is free, what you can do,
|
|
92710
|
+
what model you are, do I need an account, etc.), answer only from the facts
|
|
92711
|
+
below. If something is not covered here, say you are not certain rather than
|
|
92712
|
+
guessing.`.trim();
|
|
92713
|
+
WHAT_IS_PERCH = `### What Perch is
|
|
92714
|
+
|
|
92715
|
+
Perch is an AI platform built around verifiable AI: every claim is sourced and
|
|
92716
|
+
checkable. The goal is to make AI output trustworthy, not just fluent.
|
|
92717
|
+
|
|
92718
|
+
Perch has three surfaces:
|
|
92719
|
+
- Perch AI Web (chat.perchai.app): a warm, accessible web chat for grounded
|
|
92720
|
+
answers, document upload, web search, and citation checking.
|
|
92721
|
+
- Perch Terminal Desktop: a local operator that runs on your Mac, controls
|
|
92722
|
+
your files, runs code in a sandbox, drives a real browser, and delivers
|
|
92723
|
+
finished work. Download at ${PERCH_MARKETING_DESKTOP_URL}.
|
|
92724
|
+
- Perch Terminal CLI: the same operator power from your terminal.
|
|
92725
|
+
Install with: ${PERCH_CLI_INSTALL_COMMAND}.`.trim();
|
|
92726
|
+
ACCESS_AND_PRICING = `### Access and pricing
|
|
92727
|
+
|
|
92728
|
+
All three surfaces are free during the current pilot. No credit card is
|
|
92729
|
+
required to sign up.
|
|
92730
|
+
|
|
92731
|
+
${formatUsageLine()}
|
|
92732
|
+
|
|
92733
|
+
A Perch account is required to use the web chat. Sign up at ${PERCH_CHAT_WEB_URL}.
|
|
92734
|
+
Perch Terminal Desktop and CLI are also free; sign in with your Perch account.`.trim();
|
|
92735
|
+
MODEL_STORY = `### The model -- Roost
|
|
92736
|
+
|
|
92737
|
+
Perch does not expose raw model names. The model layer is called Roost.
|
|
92738
|
+
Roost picks the best available model for each task and locks it for that task.
|
|
92739
|
+
You are powered by Roost.
|
|
92740
|
+
|
|
92741
|
+
Roost has four tiers:
|
|
92742
|
+
${buildRoostTierSummary()}
|
|
92743
|
+
|
|
92744
|
+
Standard and Pro use a 200K context window. Standard Max and Pro Max use a 1M
|
|
92745
|
+
context window (Max = long context). Tier selection is available in the
|
|
92746
|
+
composer. When someone asks what model you are, say: "I run on Roost, Perch's
|
|
92747
|
+
model layer. Perch does not surface the underlying model names."`.trim();
|
|
92748
|
+
}
|
|
92749
|
+
});
|
|
92750
|
+
|
|
92419
92751
|
// features/perchTerminal/runtime/context/coreSystemSection.ts
|
|
92420
92752
|
function buildCoreSystemSection(input) {
|
|
92421
92753
|
const enabledToolNames = Array.from(new Set(input.enabledToolNames ?? []));
|
|
@@ -92442,6 +92774,7 @@ function buildCoreSystemSection(input) {
|
|
|
92442
92774
|
});
|
|
92443
92775
|
const lines = splitRenderedLaneText(renderedCoreLanes);
|
|
92444
92776
|
const permissionModeHandled = corePromptPermissionModeHandled(renderedCoreLanes);
|
|
92777
|
+
lines.push("", PERCH_FACTS_CORE_TERMINAL);
|
|
92445
92778
|
if (!permissionModeHandled && input.chatMode === "plan") {
|
|
92446
92779
|
if (planGateRejected) {
|
|
92447
92780
|
lines.push(
|
|
@@ -92699,7 +93032,7 @@ function buildCoordinatorNotificationSynthesisInstructions(notifications) {
|
|
|
92699
93032
|
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
93033
|
].join("\n");
|
|
92701
93034
|
}
|
|
92702
|
-
var PLATFORM_DELIVERY_GUIDANCE, LIVE_NARRATION_GUIDANCE;
|
|
93035
|
+
var PERCH_FACTS_CORE_TERMINAL, PLATFORM_DELIVERY_GUIDANCE, LIVE_NARRATION_GUIDANCE;
|
|
92703
93036
|
var init_coreSystemSection = __esm({
|
|
92704
93037
|
"features/perchTerminal/runtime/context/coreSystemSection.ts"() {
|
|
92705
93038
|
"use strict";
|
|
@@ -92715,6 +93048,8 @@ var init_coreSystemSection = __esm({
|
|
|
92715
93048
|
init_operatingCaseService();
|
|
92716
93049
|
init_planModeStateMachine();
|
|
92717
93050
|
init_coreSystemPromptLanes();
|
|
93051
|
+
init_perchFactsCore();
|
|
93052
|
+
PERCH_FACTS_CORE_TERMINAL = buildPerchFactsCore("terminal");
|
|
92718
93053
|
PLATFORM_DELIVERY_GUIDANCE = `## Platform delivery \u2014 use the verified shortcut, do not hand off
|
|
92719
93054
|
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
93055
|
|
|
@@ -92756,6 +93091,45 @@ var init_agentSkillRegistry = __esm({
|
|
|
92756
93091
|
"generated/agentSkillRegistry.ts"() {
|
|
92757
93092
|
"use strict";
|
|
92758
93093
|
AGENT_SKILL_REGISTRY = [
|
|
93094
|
+
{
|
|
93095
|
+
"id": "about-perch",
|
|
93096
|
+
"name": "about-perch",
|
|
93097
|
+
"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.",
|
|
93098
|
+
"path": "features/perchTerminal/agentPlatform/skills/about-perch/SKILL.md",
|
|
93099
|
+
"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.",
|
|
93100
|
+
"surface": {
|
|
93101
|
+
"gui": "supported",
|
|
93102
|
+
"cli": "supported"
|
|
93103
|
+
},
|
|
93104
|
+
"triggerHints": [
|
|
93105
|
+
"what is perch",
|
|
93106
|
+
"what can you do",
|
|
93107
|
+
"what model are you",
|
|
93108
|
+
"is this free",
|
|
93109
|
+
"is perch free",
|
|
93110
|
+
"is terminal free",
|
|
93111
|
+
"is terminal paid",
|
|
93112
|
+
"do i need an account",
|
|
93113
|
+
"roost",
|
|
93114
|
+
"roost tier",
|
|
93115
|
+
"standard max",
|
|
93116
|
+
"pro max",
|
|
93117
|
+
"perch terminal",
|
|
93118
|
+
"perch desktop",
|
|
93119
|
+
"perch cli",
|
|
93120
|
+
"perch vs",
|
|
93121
|
+
"web vs terminal",
|
|
93122
|
+
"difference between perch",
|
|
93123
|
+
"how does perch work",
|
|
93124
|
+
"citation verifier",
|
|
93125
|
+
"what is roost"
|
|
93126
|
+
],
|
|
93127
|
+
"companionSkills": [
|
|
93128
|
+
"research",
|
|
93129
|
+
"workspace"
|
|
93130
|
+
],
|
|
93131
|
+
"preferredTools": []
|
|
93132
|
+
},
|
|
92759
93133
|
{
|
|
92760
93134
|
"id": "browser-operator",
|
|
92761
93135
|
"name": "browser-operator",
|
|
@@ -93407,7 +93781,8 @@ var init_skillSelector = __esm({
|
|
|
93407
93781
|
"diagrams",
|
|
93408
93782
|
"workspace",
|
|
93409
93783
|
"browser-operator",
|
|
93410
|
-
"skill-creator"
|
|
93784
|
+
"skill-creator",
|
|
93785
|
+
"about-perch"
|
|
93411
93786
|
];
|
|
93412
93787
|
INTENT_PATTERNS = {
|
|
93413
93788
|
"browser-operator": [
|
|
@@ -93459,6 +93834,10 @@ var init_skillSelector = __esm({
|
|
|
93459
93834
|
],
|
|
93460
93835
|
"skill-creator": [
|
|
93461
93836
|
/\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
|
|
93837
|
+
],
|
|
93838
|
+
"about-perch": [
|
|
93839
|
+
/\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,
|
|
93840
|
+
/\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
93841
|
]
|
|
93463
93842
|
};
|
|
93464
93843
|
}
|
|
@@ -119131,29 +119510,18 @@ var init_browserSession = __esm({
|
|
|
119131
119510
|
}
|
|
119132
119511
|
});
|
|
119133
119512
|
|
|
119134
|
-
// features/perchTerminal/runtime/
|
|
119135
|
-
function
|
|
119136
|
-
|
|
119137
|
-
return isRoostTier(value) ? value : null;
|
|
119138
|
-
}
|
|
119139
|
-
function normalizeRoostModelChoice(value) {
|
|
119140
|
-
if (value === "internal") return "internal";
|
|
119141
|
-
return normalizeRoostTier(value);
|
|
119513
|
+
// features/perchTerminal/runtime/cliAuthRefreshRegistry.ts
|
|
119514
|
+
function setCliModelProxyAuthRefresher(fn) {
|
|
119515
|
+
refresher = fn;
|
|
119142
119516
|
}
|
|
119143
|
-
function
|
|
119144
|
-
return
|
|
119517
|
+
function refreshCliModelProxyAuth() {
|
|
119518
|
+
return refresher ? refresher() : Promise.resolve(null);
|
|
119145
119519
|
}
|
|
119146
|
-
var
|
|
119147
|
-
var
|
|
119148
|
-
"features/perchTerminal/runtime/
|
|
119520
|
+
var refresher;
|
|
119521
|
+
var init_cliAuthRefreshRegistry = __esm({
|
|
119522
|
+
"features/perchTerminal/runtime/cliAuthRefreshRegistry.ts"() {
|
|
119149
119523
|
"use strict";
|
|
119150
|
-
|
|
119151
|
-
ROOST_TIER_LABELS = {
|
|
119152
|
-
standard: "Standard",
|
|
119153
|
-
standard_max: "Standard Max",
|
|
119154
|
-
pro: "Pro",
|
|
119155
|
-
pro_max: "Pro Max"
|
|
119156
|
-
};
|
|
119524
|
+
refresher = null;
|
|
119157
119525
|
}
|
|
119158
119526
|
});
|
|
119159
119527
|
|
|
@@ -119488,19 +119856,36 @@ async function callModelRouterViaServer(request, opts, endpoint = "/api/perch-te
|
|
|
119488
119856
|
durationMs: 0
|
|
119489
119857
|
};
|
|
119490
119858
|
}
|
|
119859
|
+
async function syncModelProxyAuthAfter401() {
|
|
119860
|
+
if (typeof globalThis.window !== "undefined") {
|
|
119861
|
+
await syncBrowserAuthSession();
|
|
119862
|
+
return null;
|
|
119863
|
+
}
|
|
119864
|
+
return refreshCliModelProxyAuth();
|
|
119865
|
+
}
|
|
119866
|
+
function withModelProxyAuthHeader(init, token) {
|
|
119867
|
+
if (!token?.trim()) return init;
|
|
119868
|
+
const headers = new Headers(init.headers);
|
|
119869
|
+
headers.set("Authorization", `Bearer ${token}`);
|
|
119870
|
+
return { ...init, headers };
|
|
119871
|
+
}
|
|
119491
119872
|
async function fetchModelProxyWithRetry(endpoint, init, options) {
|
|
119492
119873
|
const maxAttempts = Math.max(1, options?.maxAttempts ?? MODEL_PROXY_MAX_ATTEMPTS);
|
|
119493
119874
|
let lastError = null;
|
|
119494
119875
|
let authSynced = false;
|
|
119876
|
+
let requestInit = init;
|
|
119495
119877
|
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
119496
119878
|
try {
|
|
119497
|
-
const response = await fetch(endpoint,
|
|
119498
|
-
if (response.status === 401 && !authSynced
|
|
119879
|
+
const response = await fetch(endpoint, requestInit);
|
|
119880
|
+
if (response.status === 401 && !authSynced) {
|
|
119499
119881
|
authSynced = true;
|
|
119500
|
-
await
|
|
119882
|
+
const refreshedToken = await syncModelProxyAuthAfter401();
|
|
119883
|
+
if (refreshedToken) {
|
|
119884
|
+
requestInit = withModelProxyAuthHeader(requestInit, refreshedToken);
|
|
119885
|
+
}
|
|
119501
119886
|
options?.onRetry?.(attempt, "HTTP 401 (session sync)");
|
|
119502
119887
|
await response.body?.cancel().catch(() => void 0);
|
|
119503
|
-
await waitForModelProxyRetry(attempt,
|
|
119888
|
+
await waitForModelProxyRetry(attempt, requestInit.signal);
|
|
119504
119889
|
continue;
|
|
119505
119890
|
}
|
|
119506
119891
|
if (!isRetriableModelProxyStatus(response.status) || attempt === maxAttempts - 1) {
|
|
@@ -119508,12 +119893,12 @@ async function fetchModelProxyWithRetry(endpoint, init, options) {
|
|
|
119508
119893
|
}
|
|
119509
119894
|
options?.onRetry?.(attempt, `HTTP ${response.status}`);
|
|
119510
119895
|
await response.body?.cancel().catch(() => void 0);
|
|
119511
|
-
await waitForModelProxyRetry(attempt,
|
|
119896
|
+
await waitForModelProxyRetry(attempt, requestInit.signal);
|
|
119512
119897
|
} catch (error) {
|
|
119513
119898
|
if (isAbortLikeError(error) || attempt === maxAttempts - 1) throw error;
|
|
119514
119899
|
lastError = error;
|
|
119515
119900
|
options?.onRetry?.(attempt, error instanceof Error ? error.message : String(error));
|
|
119516
|
-
await waitForModelProxyRetry(attempt,
|
|
119901
|
+
await waitForModelProxyRetry(attempt, requestInit.signal);
|
|
119517
119902
|
}
|
|
119518
119903
|
}
|
|
119519
119904
|
throw lastError instanceof Error ? lastError : new Error(String(lastError ?? "Model proxy request failed"));
|
|
@@ -119637,6 +120022,7 @@ var init_modelRouter = __esm({
|
|
|
119637
120022
|
init_contextMeter();
|
|
119638
120023
|
init_inferenceUsageRecorder();
|
|
119639
120024
|
init_browserSession();
|
|
120025
|
+
init_cliAuthRefreshRegistry();
|
|
119640
120026
|
init_roostUserSelectionClient();
|
|
119641
120027
|
init_providerModelStep();
|
|
119642
120028
|
init_toolCallNormalizer();
|
|
@@ -197350,7 +197736,6 @@ function recordDispatchBatchResult(tasks, result2, ctx, opts = {}) {
|
|
|
197350
197736
|
var lifecycleByRun, MAX_LIFECYCLE_RUNS;
|
|
197351
197737
|
var init_workerLifecycle = __esm({
|
|
197352
197738
|
"features/perchTerminal/runtime/workers/workerLifecycle.ts"() {
|
|
197353
|
-
"use strict";
|
|
197354
197739
|
init_workerManifest();
|
|
197355
197740
|
lifecycleByRun = /* @__PURE__ */ new Map();
|
|
197356
197741
|
MAX_LIFECYCLE_RUNS = 200;
|
|
@@ -198995,10 +199380,10 @@ function evaluateBrowserAction(action, observation, context = {}) {
|
|
|
198995
199380
|
if (action.kind === "click" && !label && !("role" in action && action.role)) {
|
|
198996
199381
|
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
199382
|
}
|
|
198998
|
-
if ((action.kind === "click" || action.kind === "visual_click"
|
|
199383
|
+
if ((action.kind === "click" || action.kind === "visual_click") && context.recentFailedClickSignatures?.includes(clickSignature(action))) {
|
|
198999
199384
|
return block(
|
|
199000
199385
|
"repeated_failed_click",
|
|
199001
|
-
`The action "${label || action.kind}" already failed on this surface. Not retrying the identical click.
|
|
199386
|
+
`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
199387
|
);
|
|
199003
199388
|
}
|
|
199004
199389
|
if (action.kind === "click" && (observation.app === "google_docs" || observation.app === "google_sheets")) {
|
|
@@ -199363,6 +199748,17 @@ async function safeBrowserAction(input, runtime = {}) {
|
|
|
199363
199748
|
if (target.kind === "ambiguous") {
|
|
199364
199749
|
lastFailure = fail("selector_ambiguous", target.message, startMs, latestSnapshot.text, latestSnapshot.url, target.candidates);
|
|
199365
199750
|
} else if (target.kind === "missing") {
|
|
199751
|
+
const escalated = await escalateMissingClickToCoordinate({
|
|
199752
|
+
input,
|
|
199753
|
+
snapshot: latestSnapshot,
|
|
199754
|
+
callTool,
|
|
199755
|
+
startMs,
|
|
199756
|
+
runtime
|
|
199757
|
+
});
|
|
199758
|
+
if (escalated) {
|
|
199759
|
+
clearPolicyClickFailure(input, runtime, latestSnapshot);
|
|
199760
|
+
return escalated;
|
|
199761
|
+
}
|
|
199366
199762
|
lastFailure = fail("selector_not_found", target.message, startMs, latestSnapshot.text, latestSnapshot.url);
|
|
199367
199763
|
} else {
|
|
199368
199764
|
const actionResult = await executeBrowserAction(input, target.target, callTool);
|
|
@@ -199499,7 +199895,7 @@ function enforceActionPolicy(input, preSnapshot, runtime, startMs) {
|
|
|
199499
199895
|
}
|
|
199500
199896
|
function recordPolicyClickFailure(input, runtime, snapshot) {
|
|
199501
199897
|
if (!runtime.policy) return;
|
|
199502
|
-
if (input.intent !== "click" && input.intent !== "visual_click"
|
|
199898
|
+
if (input.intent !== "click" && input.intent !== "visual_click") return;
|
|
199503
199899
|
const key = policySessionKey(runtime);
|
|
199504
199900
|
const signature = `${policySurfaceFingerprint(snapshot)}::${clickSignature(inputToProposedAction(input))}`;
|
|
199505
199901
|
const existing = failedClickMemory.get(key) ?? [];
|
|
@@ -199611,6 +200007,7 @@ async function clickGmailComposeSendButton(callTool) {
|
|
|
199611
200007
|
};
|
|
199612
200008
|
});
|
|
199613
200009
|
if (!target || target.result !== "target") return JSON.stringify(target || { result: "missing" });
|
|
200010
|
+
await page.evaluate(${PERCH_CURSOR_OVERLAY_SNIPPET}, { x: target.x, y: target.y });
|
|
199614
200011
|
await page.mouse.click(target.x, target.y);
|
|
199615
200012
|
return JSON.stringify({ result: "clicked", label: target.label, x: target.x, y: target.y, bounds: target.bounds, viewport: target.viewport });
|
|
199616
200013
|
}
|
|
@@ -199671,7 +200068,8 @@ async function dispatchSyntheticPaste(payload, runtime) {
|
|
|
199671
200068
|
() => {
|
|
199672
200069
|
const html = ${JSON.stringify(payload.html)};
|
|
199673
200070
|
const plain = ${JSON.stringify(payload.plain)};
|
|
199674
|
-
const
|
|
200071
|
+
const deepActive = () => { let n = document.activeElement; while (n && n.shadowRoot && n.shadowRoot.activeElement) n = n.shadowRoot.activeElement; return n; };
|
|
200072
|
+
const active = deepActive();
|
|
199675
200073
|
if (!active) return false;
|
|
199676
200074
|
const isLongBody = plain.length > ${TYPE_DIRECT_MAX_CHARS} || plain.includes("\\n");
|
|
199677
200075
|
if (isLongBody && active.tagName === "INPUT") return false;
|
|
@@ -199989,7 +200387,26 @@ async function resolveTargetForAction(input, snapshot, callTool) {
|
|
|
199989
200387
|
}
|
|
199990
200388
|
const resolved = resolveTarget(snapshot.text, input.targetDescriptor);
|
|
199991
200389
|
if (resolved.kind === "found") return resolved;
|
|
199992
|
-
if (input.intent === "type")
|
|
200390
|
+
if (input.intent === "type") {
|
|
200391
|
+
const desc = input.targetDescriptor;
|
|
200392
|
+
const named = (desc?.text ?? "").trim() || (desc?.ariaLabel ?? "").trim();
|
|
200393
|
+
if (named) {
|
|
200394
|
+
return {
|
|
200395
|
+
kind: "found",
|
|
200396
|
+
target: {
|
|
200397
|
+
kind: "semantic",
|
|
200398
|
+
descriptor: {
|
|
200399
|
+
...desc?.text ? { text: desc.text } : {},
|
|
200400
|
+
...desc?.ariaLabel ? { ariaLabel: desc.ariaLabel } : {},
|
|
200401
|
+
...desc?.role ? { role: desc.role } : {},
|
|
200402
|
+
nth: normalizeNth(desc?.nth)
|
|
200403
|
+
},
|
|
200404
|
+
matched: desc?.text ?? desc?.ariaLabel ?? "target"
|
|
200405
|
+
}
|
|
200406
|
+
};
|
|
200407
|
+
}
|
|
200408
|
+
return { kind: "found", target: null };
|
|
200409
|
+
}
|
|
199993
200410
|
return resolved;
|
|
199994
200411
|
}
|
|
199995
200412
|
function resolveTarget(snapshot, descriptor) {
|
|
@@ -200016,7 +200433,7 @@ function resolveTarget(snapshot, descriptor) {
|
|
|
200016
200433
|
if (!match) {
|
|
200017
200434
|
return {
|
|
200018
200435
|
kind: "missing",
|
|
200019
|
-
message: `No
|
|
200436
|
+
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
200437
|
};
|
|
200021
200438
|
}
|
|
200022
200439
|
if (matches.length > 1 && desc.nth === void 0) {
|
|
@@ -200048,7 +200465,7 @@ function resolveTarget(snapshot, descriptor) {
|
|
|
200048
200465
|
}
|
|
200049
200466
|
return {
|
|
200050
200467
|
kind: "missing",
|
|
200051
|
-
message: `No
|
|
200468
|
+
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
200469
|
};
|
|
200053
200470
|
}
|
|
200054
200471
|
function liveResolverSource() {
|
|
@@ -200097,17 +200514,50 @@ function liveResolverSource() {
|
|
|
200097
200514
|
el.textContent,
|
|
200098
200515
|
].filter(Boolean).map((v) => String(v).trim()).find(Boolean) || "";
|
|
200099
200516
|
};
|
|
200100
|
-
const candidates = () =>
|
|
200101
|
-
|
|
200102
|
-
|
|
200103
|
-
|
|
200104
|
-
|
|
200105
|
-
|
|
200106
|
-
|
|
200107
|
-
|
|
200108
|
-
|
|
200109
|
-
|
|
200110
|
-
|
|
200517
|
+
const candidates = () => {
|
|
200518
|
+
const INTERACTIVE_SELECTOR = [
|
|
200519
|
+
"button",
|
|
200520
|
+
"a[href]",
|
|
200521
|
+
"input",
|
|
200522
|
+
"textarea",
|
|
200523
|
+
"select",
|
|
200524
|
+
"[role]",
|
|
200525
|
+
"[aria-label]",
|
|
200526
|
+
"[data-tooltip]",
|
|
200527
|
+
"[contenteditable='true']"
|
|
200528
|
+
].join(",");
|
|
200529
|
+
// Collect open shadow roots recursively so the resolver reaches controls
|
|
200530
|
+
// inside web components (e.g. Reddit/shreddit). Shadow-DOM elements share
|
|
200531
|
+
// the top document's viewport coordinate space, so getBoundingClientRect
|
|
200532
|
+
// (used for the coordinate click) stays correct. Same-origin iframes are
|
|
200533
|
+
// intentionally skipped: their rects are in the frame's own space and
|
|
200534
|
+
// would misplace the click.
|
|
200535
|
+
const roots = [document];
|
|
200536
|
+
const seenRoots = new Set();
|
|
200537
|
+
const walk = (root) => {
|
|
200538
|
+
let hosts;
|
|
200539
|
+
try { hosts = root.querySelectorAll("*"); } catch (e) { return; }
|
|
200540
|
+
for (const host of hosts) {
|
|
200541
|
+
const sr = host.shadowRoot;
|
|
200542
|
+
if (sr && !seenRoots.has(sr)) {
|
|
200543
|
+
seenRoots.add(sr);
|
|
200544
|
+
roots.push(sr);
|
|
200545
|
+
walk(sr);
|
|
200546
|
+
}
|
|
200547
|
+
}
|
|
200548
|
+
};
|
|
200549
|
+
walk(document);
|
|
200550
|
+
const out = [];
|
|
200551
|
+
const seenEl = new Set();
|
|
200552
|
+
for (const root of roots) {
|
|
200553
|
+
let found;
|
|
200554
|
+
try { found = root.querySelectorAll(INTERACTIVE_SELECTOR); } catch (e) { continue; }
|
|
200555
|
+
for (const el of found) {
|
|
200556
|
+
if (!seenEl.has(el) && visible(el)) { seenEl.add(el); out.push(el); }
|
|
200557
|
+
}
|
|
200558
|
+
}
|
|
200559
|
+
return out;
|
|
200560
|
+
};
|
|
200111
200561
|
const rankMatch = (el, descriptor) => {
|
|
200112
200562
|
const label = labelFor(el);
|
|
200113
200563
|
const aria = el.getAttribute("aria-label") || "";
|
|
@@ -200145,6 +200595,32 @@ function liveResolverSource() {
|
|
|
200145
200595
|
};
|
|
200146
200596
|
`;
|
|
200147
200597
|
}
|
|
200598
|
+
async function escalateMissingClickToCoordinate(args) {
|
|
200599
|
+
const { input, snapshot, callTool, startMs, runtime } = args;
|
|
200600
|
+
if (input.intent !== "click") return null;
|
|
200601
|
+
const descriptor = input.targetDescriptor;
|
|
200602
|
+
const label = descriptor?.text ?? descriptor?.ariaLabel ?? descriptor?.role;
|
|
200603
|
+
if (!descriptor || !label) return null;
|
|
200604
|
+
const coordinateTarget = { kind: "semantic", descriptor, matched: label };
|
|
200605
|
+
const clicked = await clickTargetTrusted(callTool, coordinateTarget, label);
|
|
200606
|
+
if (!clicked.ok) return null;
|
|
200607
|
+
const outcome = await waitForExpectedOutcome({
|
|
200608
|
+
input,
|
|
200609
|
+
callTool,
|
|
200610
|
+
preSnapshot: snapshot,
|
|
200611
|
+
startMs,
|
|
200612
|
+
signal: runtime.signal
|
|
200613
|
+
});
|
|
200614
|
+
if (!outcome.ok) return null;
|
|
200615
|
+
return {
|
|
200616
|
+
...outcome,
|
|
200617
|
+
evidence: {
|
|
200618
|
+
...outcome.evidence,
|
|
200619
|
+
matched: `${clicked.matched} \xB7 escalated to coordinate (CUA) click`,
|
|
200620
|
+
...clicked.actionGeometry ? { actionGeometry: clicked.actionGeometry } : {}
|
|
200621
|
+
}
|
|
200622
|
+
};
|
|
200623
|
+
}
|
|
200148
200624
|
async function executeBrowserAction(input, target, callTool) {
|
|
200149
200625
|
if (input.intent === "snapshot" || input.intent === "extract") return { ok: true, matched: input.intent };
|
|
200150
200626
|
if (input.intent === "navigate") {
|
|
@@ -200173,6 +200649,8 @@ async function executeBrowserAction(input, target, callTool) {
|
|
|
200173
200649
|
if (!clicked.ok) return clicked;
|
|
200174
200650
|
const typed = await typeViaRealKeys(callTool, input.text ?? "", clicked.matched);
|
|
200175
200651
|
if (typed.ok) {
|
|
200652
|
+
const verified = await verifyTypedTextLanded(callTool, input.text ?? "", clicked.matched);
|
|
200653
|
+
if (!verified.ok) return verified;
|
|
200176
200654
|
return {
|
|
200177
200655
|
ok: true,
|
|
200178
200656
|
matched: `${clicked.matched} then typed into focused surface`,
|
|
@@ -200182,7 +200660,10 @@ async function executeBrowserAction(input, target, callTool) {
|
|
|
200182
200660
|
return typed;
|
|
200183
200661
|
}
|
|
200184
200662
|
if (input.intent === "type") {
|
|
200185
|
-
|
|
200663
|
+
const typed = await enterText(callTool, target, input.text ?? "");
|
|
200664
|
+
if (!typed.ok) return typed;
|
|
200665
|
+
const verified = await verifyTypedTextLanded(callTool, input.text ?? "", typed.matched);
|
|
200666
|
+
return verified.ok ? typed : verified;
|
|
200186
200667
|
}
|
|
200187
200668
|
if (!target) return { ok: false, error: "target could not be resolved" };
|
|
200188
200669
|
if (target.kind === "selector") {
|
|
@@ -200193,8 +200674,24 @@ async function executeBrowserAction(input, target, callTool) {
|
|
|
200193
200674
|
}
|
|
200194
200675
|
return { ok: false, error: `Unsupported browser intent: ${input.intent}` };
|
|
200195
200676
|
}
|
|
200677
|
+
function labelGroundingVariants(label) {
|
|
200678
|
+
const trimmed = (label ?? "").trim();
|
|
200679
|
+
if (!trimmed || trimmed.toLowerCase() === "visual target") return [];
|
|
200680
|
+
const variants = [trimmed];
|
|
200681
|
+
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();
|
|
200682
|
+
if (stripped && stripped.toLowerCase() !== trimmed.toLowerCase()) variants.push(stripped);
|
|
200683
|
+
return variants;
|
|
200684
|
+
}
|
|
200196
200685
|
async function clickVisualTarget(callTool, visualTarget) {
|
|
200197
200686
|
if (!visualTarget) return { ok: false, error: "visual target requires x and y" };
|
|
200687
|
+
for (const candidate of labelGroundingVariants(visualTarget.label)) {
|
|
200688
|
+
const grounded = await clickTargetTrusted(
|
|
200689
|
+
callTool,
|
|
200690
|
+
{ kind: "semantic", descriptor: { text: candidate }, matched: candidate },
|
|
200691
|
+
candidate
|
|
200692
|
+
);
|
|
200693
|
+
if (grounded.ok) return grounded;
|
|
200694
|
+
}
|
|
200198
200695
|
const x = Number(visualTarget.x);
|
|
200199
200696
|
const y = Number(visualTarget.y);
|
|
200200
200697
|
if (!Number.isFinite(x) || !Number.isFinite(y)) {
|
|
@@ -200212,6 +200709,7 @@ async function clickVisualTarget(callTool, visualTarget) {
|
|
|
200212
200709
|
const py = coordinateSpace === "viewport_pixels" ? rawY : rawY * viewport.height;
|
|
200213
200710
|
const safeX = Math.max(1, Math.min(viewport.width - 1, px));
|
|
200214
200711
|
const safeY = Math.max(1, Math.min(viewport.height - 1, py));
|
|
200712
|
+
await page.evaluate(${PERCH_CURSOR_OVERLAY_SNIPPET}, { x: safeX, y: safeY });
|
|
200215
200713
|
await page.mouse.click(safeX, safeY);
|
|
200216
200714
|
return JSON.stringify({
|
|
200217
200715
|
result: "clicked",
|
|
@@ -200235,17 +200733,78 @@ async function clickVisualTarget(callTool, visualTarget) {
|
|
|
200235
200733
|
}
|
|
200236
200734
|
return { ok: false, error: text || "visual click failed" };
|
|
200237
200735
|
}
|
|
200736
|
+
async function verifyTypedTextLanded(callTool, text, matched) {
|
|
200737
|
+
if (!text.trim()) return { ok: true };
|
|
200738
|
+
const expr = `() => {
|
|
200739
|
+
const url = String(location.href || "");
|
|
200740
|
+
if (/docs\\.google\\.com\\/(document|spreadsheets|presentation)\\//i.test(url)) return "PERCH_DOCS_BLIND";
|
|
200741
|
+
const deepActive = () => { let n = document.activeElement; while (n && n.shadowRoot && n.shadowRoot.activeElement) n = n.shadowRoot.activeElement; return n; };
|
|
200742
|
+
const el = deepActive();
|
|
200743
|
+
if (!el) return "PERCH_UNKNOWN";
|
|
200744
|
+
const tag = (el.tagName || "").toLowerCase();
|
|
200745
|
+
const readableEditable =
|
|
200746
|
+
el.isContentEditable ||
|
|
200747
|
+
tag === "textarea" ||
|
|
200748
|
+
(tag === "input") ||
|
|
200749
|
+
("value" in el && typeof el.value === "string");
|
|
200750
|
+
if (!readableEditable) return "PERCH_UNKNOWN";
|
|
200751
|
+
const read = (n) => {
|
|
200752
|
+
if ("value" in n && typeof n.value === "string") return n.value;
|
|
200753
|
+
if (n.isContentEditable) return n.textContent || "";
|
|
200754
|
+
return "";
|
|
200755
|
+
};
|
|
200756
|
+
const val = String(read(el) || "").replace(/\\s+/g, " ").trim();
|
|
200757
|
+
return val ? "PERCH_LANDED" : "PERCH_EMPTY";
|
|
200758
|
+
}`;
|
|
200759
|
+
const result2 = await callTool({
|
|
200760
|
+
serverName: "playwright",
|
|
200761
|
+
toolName: "browser_evaluate",
|
|
200762
|
+
args: { function: expr }
|
|
200763
|
+
});
|
|
200764
|
+
if (!result2.ok) return { ok: true };
|
|
200765
|
+
if (firstText(result2.content).trim() !== "PERCH_EMPTY") return { ok: true };
|
|
200766
|
+
return {
|
|
200767
|
+
ok: false,
|
|
200768
|
+
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.`
|
|
200769
|
+
};
|
|
200770
|
+
}
|
|
200771
|
+
async function hasEditableFocus(callTool) {
|
|
200772
|
+
const expr = `() => {
|
|
200773
|
+
const deepActive = () => { let n = document.activeElement; while (n && n.shadowRoot && n.shadowRoot.activeElement) n = n.shadowRoot.activeElement; return n; };
|
|
200774
|
+
const el = deepActive();
|
|
200775
|
+
if (!el) return false;
|
|
200776
|
+
if (el.isContentEditable) return true;
|
|
200777
|
+
const tag = (el.tagName || "").toLowerCase();
|
|
200778
|
+
if (tag === "textarea") return true;
|
|
200779
|
+
if (tag === "input") {
|
|
200780
|
+
const t = (el.getAttribute("type") || "text").toLowerCase();
|
|
200781
|
+
return !["button","submit","reset","checkbox","radio","range","file","image","color"].includes(t);
|
|
200782
|
+
}
|
|
200783
|
+
if ("value" in el && typeof el.value === "string") return true;
|
|
200784
|
+
return false;
|
|
200785
|
+
}`;
|
|
200786
|
+
const result2 = await callTool({
|
|
200787
|
+
serverName: "playwright",
|
|
200788
|
+
toolName: "browser_evaluate",
|
|
200789
|
+
args: { function: expr }
|
|
200790
|
+
});
|
|
200791
|
+
if (!result2.ok) return false;
|
|
200792
|
+
return /^\s*true\s*$/i.test(firstText(result2.content));
|
|
200793
|
+
}
|
|
200238
200794
|
async function enterText(callTool, target, text) {
|
|
200239
200795
|
const matched = target?.matched ?? "focused field";
|
|
200240
200796
|
const isLong = text.length > TYPE_DIRECT_MAX_CHARS || text.includes("\n");
|
|
200797
|
+
const alreadyFocused = await hasEditableFocus(callTool);
|
|
200241
200798
|
if (!isLong) {
|
|
200242
|
-
|
|
200799
|
+
let focusGeometry;
|
|
200800
|
+
if (!alreadyFocused && target) {
|
|
200243
200801
|
const focused = await focusTarget(callTool, target, matched);
|
|
200244
|
-
if (
|
|
200802
|
+
if (focused.ok) focusGeometry = focused.actionGeometry;
|
|
200245
200803
|
}
|
|
200246
|
-
|
|
200804
|
+
const typed = await typeViaRealKeys(callTool, text, matched);
|
|
200805
|
+
return typed.ok && focusGeometry ? { ...typed, actionGeometry: focusGeometry } : typed;
|
|
200247
200806
|
}
|
|
200248
|
-
if (target) {
|
|
200807
|
+
if (!alreadyFocused && target) {
|
|
200249
200808
|
const focused = await focusTarget(callTool, target, matched);
|
|
200250
200809
|
if (focused.ok) {
|
|
200251
200810
|
const pasted2 = await dispatchSyntheticPaste({ html: null, plain: text }, { callTool });
|
|
@@ -200273,7 +200832,8 @@ function shouldRefuseRealKeys(error) {
|
|
|
200273
200832
|
async function typeIntoActiveElement(callTool, text) {
|
|
200274
200833
|
const json = JSON.stringify(text);
|
|
200275
200834
|
const expr = `() => {
|
|
200276
|
-
const
|
|
200835
|
+
const deepActive = () => { let n = document.activeElement; while (n && n.shadowRoot && n.shadowRoot.activeElement) n = n.shadowRoot.activeElement; return n; };
|
|
200836
|
+
const el = deepActive();
|
|
200277
200837
|
if (!el) throw new Error("no active element to type into");
|
|
200278
200838
|
const isLongBody = ${JSON.stringify(text.length > TYPE_DIRECT_MAX_CHARS || text.includes("\n"))};
|
|
200279
200839
|
if (isLongBody && el.tagName === "INPUT") {
|
|
@@ -200359,6 +200919,7 @@ async function clickTargetTrusted(callTool, target, matched) {
|
|
|
200359
200919
|
};
|
|
200360
200920
|
}, { descriptor: ${JSON.stringify(descriptor)}, selector: ${JSON.stringify(selector)} });
|
|
200361
200921
|
if (!target || target.result !== "target") return JSON.stringify(target || { result: "missing" });
|
|
200922
|
+
await page.evaluate(${PERCH_CURSOR_OVERLAY_SNIPPET}, { x: target.x, y: target.y });
|
|
200362
200923
|
await page.mouse.click(target.x, target.y);
|
|
200363
200924
|
return JSON.stringify({ result: "clicked", label: target.label, x: target.x, y: target.y, bounds: target.bounds, viewport: target.viewport });
|
|
200364
200925
|
}`;
|
|
@@ -200686,7 +201247,7 @@ function delay(ms, signal) {
|
|
|
200686
201247
|
);
|
|
200687
201248
|
});
|
|
200688
201249
|
}
|
|
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;
|
|
201250
|
+
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
201251
|
var init_safeBrowserAction = __esm({
|
|
200691
201252
|
"features/perchTerminal/runtime/playwright/safeBrowserAction.ts"() {
|
|
200692
201253
|
"use strict";
|
|
@@ -200713,6 +201274,26 @@ var init_safeBrowserAction = __esm({
|
|
|
200713
201274
|
];
|
|
200714
201275
|
failedClickMemory = /* @__PURE__ */ new Map();
|
|
200715
201276
|
FAILED_CLICK_MEMORY_MAX = 12;
|
|
201277
|
+
PERCH_CURSOR_OVERLAY_SNIPPET = `(coord) => {
|
|
201278
|
+
try {
|
|
201279
|
+
const id = "__perch_operator_cursor__";
|
|
201280
|
+
let el = document.getElementById(id);
|
|
201281
|
+
if (!el) {
|
|
201282
|
+
el = document.createElement("div");
|
|
201283
|
+
el.id = id;
|
|
201284
|
+
el.setAttribute("aria-hidden", "true");
|
|
201285
|
+
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));";
|
|
201286
|
+
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>';
|
|
201287
|
+
(document.body || document.documentElement).appendChild(el);
|
|
201288
|
+
}
|
|
201289
|
+
// The arrow tip is at the SVG origin (0,0), so left/top place the tip on the point.
|
|
201290
|
+
el.style.left = coord.x + "px";
|
|
201291
|
+
el.style.top = coord.y + "px";
|
|
201292
|
+
el.style.opacity = "1";
|
|
201293
|
+
if (el.__perchHide) clearTimeout(el.__perchHide);
|
|
201294
|
+
el.__perchHide = setTimeout(function () { el.style.opacity = "0.6"; }, 1500);
|
|
201295
|
+
} catch (e) {}
|
|
201296
|
+
}`;
|
|
200716
201297
|
TYPE_DIRECT_MAX_CHARS = 200;
|
|
200717
201298
|
PRESS_KEY_LOOP_MAX_CHARS = 4e3;
|
|
200718
201299
|
}
|
|
@@ -218837,9 +219418,19 @@ async function executeToolBatch(toolCalls, ctx) {
|
|
|
218837
219418
|
{ type: "image_url", image_url: { url: execution.imageDataUrl } }
|
|
218838
219419
|
]
|
|
218839
219420
|
});
|
|
219421
|
+
} else if (isOperatorScreenshot) {
|
|
219422
|
+
const readout = await describeOperatorScreenshotViaVision(execution.imageDataUrl, ctx);
|
|
219423
|
+
ctx.messages.push({
|
|
219424
|
+
role: "user",
|
|
219425
|
+
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.
|
|
219426
|
+
|
|
219427
|
+
${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.`
|
|
219428
|
+
});
|
|
218840
219429
|
} else {
|
|
218841
|
-
|
|
218842
|
-
|
|
219430
|
+
ctx.messages.push({
|
|
219431
|
+
role: "user",
|
|
219432
|
+
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.`
|
|
219433
|
+
});
|
|
218843
219434
|
}
|
|
218844
219435
|
}
|
|
218845
219436
|
}
|
|
@@ -218860,6 +219451,26 @@ function parseImageDataUrlForTool(dataUrl) {
|
|
|
218860
219451
|
base64
|
|
218861
219452
|
};
|
|
218862
219453
|
}
|
|
219454
|
+
async function describeOperatorScreenshotViaVision(dataUrl, ctx) {
|
|
219455
|
+
if (ctx.signal?.aborted) return null;
|
|
219456
|
+
const parsed = parseImageDataUrlForTool(dataUrl);
|
|
219457
|
+
if (!parsed) return null;
|
|
219458
|
+
try {
|
|
219459
|
+
const result2 = await callVisionLaneModel({
|
|
219460
|
+
founderModelSelection: ctx.founderModelSelection,
|
|
219461
|
+
imageBase64: parsed.base64,
|
|
219462
|
+
mimeType: parsed.mimeType,
|
|
219463
|
+
userPrompt: OPERATOR_VISION_OBSERVER_PROMPT,
|
|
219464
|
+
signal: ctx.signal ?? void 0,
|
|
219465
|
+
userId: ctx.userId ?? null,
|
|
219466
|
+
workspaceId: ctx.workspaceId ?? null,
|
|
219467
|
+
runId: ctx.runId ?? null
|
|
219468
|
+
});
|
|
219469
|
+
if (result2.ok && result2.text.trim()) return result2.text.trim();
|
|
219470
|
+
} catch {
|
|
219471
|
+
}
|
|
219472
|
+
return null;
|
|
219473
|
+
}
|
|
218863
219474
|
function currentScreenVisionToolCall(input) {
|
|
218864
219475
|
const { toolCall, imageDataUrl } = input;
|
|
218865
219476
|
if (toolCall.name !== TOOL_NAMES.visionInspect) return { toolCall };
|
|
@@ -218918,11 +219529,12 @@ function updateConsecutiveRequiredArgFailures(counts, execution) {
|
|
|
218918
219529
|
function now6() {
|
|
218919
219530
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
218920
219531
|
}
|
|
218921
|
-
var MAX_PARALLEL_TOOL_CALLS, LOCAL_VISUAL_ATTACHMENT_MARKER;
|
|
219532
|
+
var MAX_PARALLEL_TOOL_CALLS, LOCAL_VISUAL_ATTACHMENT_MARKER, OPERATOR_VISION_OBSERVER_PROMPT;
|
|
218922
219533
|
var init_toolBatchExecutor = __esm({
|
|
218923
219534
|
"features/perchTerminal/runtime/toolLoop/toolBatchExecutor.ts"() {
|
|
218924
219535
|
"use strict";
|
|
218925
219536
|
init_modelRegistry();
|
|
219537
|
+
init_visionLaneCall();
|
|
218926
219538
|
init_toolExecutor();
|
|
218927
219539
|
init_fileDiff();
|
|
218928
219540
|
init_toolNames();
|
|
@@ -218935,6 +219547,14 @@ var init_toolBatchExecutor = __esm({
|
|
|
218935
219547
|
init_sourceReadPolicy();
|
|
218936
219548
|
MAX_PARALLEL_TOOL_CALLS = 8;
|
|
218937
219549
|
LOCAL_VISUAL_ATTACHMENT_MARKER = "[local-visual]";
|
|
219550
|
+
OPERATOR_VISION_OBSERVER_PROMPT = [
|
|
219551
|
+
"You are the eyes of a browser operator that cannot see this screen.",
|
|
219552
|
+
"Describe the current browser screen so the operator can act precisely.",
|
|
219553
|
+
"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.",
|
|
219554
|
+
"State which field or control is focused or selected, the values already entered in visible fields, and any error, confirmation, or toast text.",
|
|
219555
|
+
"If a crosshair or marker is drawn on the image, say which element it is pointing at.",
|
|
219556
|
+
"Plain text only. Do not suggest or decide the next action."
|
|
219557
|
+
].join(" ");
|
|
218938
219558
|
}
|
|
218939
219559
|
});
|
|
218940
219560
|
|
|
@@ -219379,8 +219999,11 @@ async function prepareLoopMessagesForSend(input) {
|
|
|
219379
219999
|
toolDefinitionsJson,
|
|
219380
220000
|
modelContext
|
|
219381
220001
|
});
|
|
219382
|
-
const
|
|
219383
|
-
const
|
|
220002
|
+
const budgetModel = typeof input.effectiveContextLimitTokens === "number" && input.effectiveContextLimitTokens > 0 ? { ...input.option, effectiveLimitTokens: input.effectiveContextLimitTokens } : input.option;
|
|
220003
|
+
const contextLimitTokens = resolveEffectiveContextLimit(
|
|
220004
|
+
budgetModel
|
|
220005
|
+
);
|
|
220006
|
+
const compactThreshold = getAutoCompactThreshold(budgetModel);
|
|
219384
220007
|
const postCompactTargetTokens = resolvePostCompactTargetTokens(contextLimitTokens);
|
|
219385
220008
|
const recentTailTokenBudget = resolveRecentTailBudgetTokens({
|
|
219386
220009
|
postCompactTargetTokens,
|
|
@@ -219406,11 +220029,6 @@ async function prepareLoopMessagesForSend(input) {
|
|
|
219406
220029
|
toolDefinitionsJson,
|
|
219407
220030
|
modelContext
|
|
219408
220031
|
});
|
|
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
220032
|
}
|
|
219415
220033
|
}
|
|
219416
220034
|
if (autoCompactEnabled && anchoredTokens(breakdown.total) >= compactThreshold) {
|
|
@@ -223197,6 +223815,7 @@ async function runFlockTurn(input, deps, options = {}) {
|
|
|
223197
223815
|
emitWorkerUpdate(emit, plan, worker, "queued");
|
|
223198
223816
|
}
|
|
223199
223817
|
const spawnFn = options.spawnWorkerFn ?? spawnWorker;
|
|
223818
|
+
let turnInput = input;
|
|
223200
223819
|
const outcomes = [];
|
|
223201
223820
|
const revisionOutcomes = [];
|
|
223202
223821
|
let revisionReport = null;
|
|
@@ -223245,6 +223864,7 @@ async function runFlockTurn(input, deps, options = {}) {
|
|
|
223245
223864
|
toolCallsReserved += reservedToolCalls;
|
|
223246
223865
|
emitWorkerUpdate(emit, plan, worker, "running");
|
|
223247
223866
|
try {
|
|
223867
|
+
turnInput = deps.refreshTurnInput ? await deps.refreshTurnInput(turnInput) : turnInput;
|
|
223248
223868
|
const context = contextOverride ?? buildWorkerContext(worker, sharedContext, outputByFlockWorkerId, plan);
|
|
223249
223869
|
const objective = worker.role === "worker" && contextContainsSources(context) ? `${worker.objective}
|
|
223250
223870
|
${FLOCK_GROUNDING_CONTRACT}` : worker.objective;
|
|
@@ -223256,18 +223876,18 @@ ${FLOCK_GROUNDING_CONTRACT}` : worker.objective;
|
|
|
223256
223876
|
maxIterations: worker.maxIterations,
|
|
223257
223877
|
maxToolCalls: reservedToolCalls
|
|
223258
223878
|
},
|
|
223259
|
-
buildSpawnContext(
|
|
223879
|
+
buildSpawnContext(turnInput, plan.flockId, flockRun.controller.signal, emit, worker)
|
|
223260
223880
|
);
|
|
223261
223881
|
toolCallsReserved -= reservedToolCalls;
|
|
223262
223882
|
toolCallsUsed += Math.min(result2.toolCalls ?? 0, reservedToolCalls);
|
|
223263
|
-
const
|
|
223883
|
+
const status = flockWorkerStatusFromSpawnResult(result2);
|
|
223264
223884
|
const outcome = {
|
|
223265
223885
|
worker,
|
|
223266
223886
|
result: result2,
|
|
223267
|
-
status
|
|
223887
|
+
status,
|
|
223268
223888
|
detail: clampLine(result2.summary, 200)
|
|
223269
223889
|
};
|
|
223270
|
-
emitWorkerUpdate(emit, plan, worker,
|
|
223890
|
+
emitWorkerUpdate(emit, plan, worker, status, clampLine(result2.summary, 160));
|
|
223271
223891
|
return outcome;
|
|
223272
223892
|
} catch (error) {
|
|
223273
223893
|
toolCallsReserved -= reservedToolCalls;
|
|
@@ -223367,8 +223987,10 @@ ${FLOCK_GROUNDING_CONTRACT}` : worker.objective;
|
|
|
223367
223987
|
}
|
|
223368
223988
|
const userCancelled = Boolean(deps.signal?.aborted);
|
|
223369
223989
|
const workersDone = outcomes.filter((outcome) => outcome.status === "done").length;
|
|
223370
|
-
const
|
|
223990
|
+
const workersBlocked = outcomes.filter((outcome) => outcome.status === "blocked").length;
|
|
223991
|
+
const workersFailed = outcomes.filter((outcome) => outcome.status === "failed").length + workersBlocked;
|
|
223371
223992
|
const flockStatus = userCancelled || wallTimeHit && workersDone === 0 ? "cancelled" : workersFailed === 0 && workersDone === plan.workers.length ? "completed" : workersDone > 0 ? "partial" : "failed";
|
|
223993
|
+
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
223994
|
const assistantText = [
|
|
223373
223995
|
buildFlockSummary(
|
|
223374
223996
|
plan,
|
|
@@ -223384,7 +224006,7 @@ ${FLOCK_GROUNDING_CONTRACT}` : worker.objective;
|
|
|
223384
224006
|
...modelOverrides.unavailable.map(
|
|
223385
224007
|
(override) => `Requested model "${override.modelText}"${override.roleText ? ` for ${override.roleText}` : ""} is not available \u2014 that worker stayed on the default model path.`
|
|
223386
224008
|
)
|
|
223387
|
-
].join("\n");
|
|
224009
|
+
].join("\n") + permissionHint;
|
|
223388
224010
|
emit({
|
|
223389
224011
|
type: "flock_run_completed",
|
|
223390
224012
|
flockId: plan.flockId,
|
|
@@ -223618,6 +224240,12 @@ function buildFlockSummary(plan, outcomes, status, toolCallsUsed, wallTimeHit) {
|
|
|
223618
224240
|
function friendlyOutcomeDetail(detail) {
|
|
223619
224241
|
const clean = clampLine(detail, 220);
|
|
223620
224242
|
if (!clean) return "";
|
|
224243
|
+
if (/approval gate|requires approval|Blocked at an approval/i.test(clean)) {
|
|
224244
|
+
return clean;
|
|
224245
|
+
}
|
|
224246
|
+
if (/No usable model response|Model proxy error|not_authenticated|Unauthorized/i.test(clean)) {
|
|
224247
|
+
return clean;
|
|
224248
|
+
}
|
|
223621
224249
|
if (/Reached maximum \d+ tool-call iterations/i.test(clean)) {
|
|
223622
224250
|
return "finished from gathered evidence";
|
|
223623
224251
|
}
|
|
@@ -223636,6 +224264,11 @@ function clampLine(text, max2) {
|
|
|
223636
224264
|
function makeFlockRunId() {
|
|
223637
224265
|
return `flock-run-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
223638
224266
|
}
|
|
224267
|
+
function flockWorkerStatusFromSpawnResult(result2) {
|
|
224268
|
+
if (!result2.ok || result2.usabilityStatus === "failed") return "failed";
|
|
224269
|
+
if (result2.usabilityStatus === "needs_user_input") return "blocked";
|
|
224270
|
+
return "done";
|
|
224271
|
+
}
|
|
223639
224272
|
function now11() {
|
|
223640
224273
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
223641
224274
|
}
|
|
@@ -232065,13 +232698,15 @@ function getBackgroundTaskStore() {
|
|
|
232065
232698
|
var STORE_FILE, BackgroundTaskStore, singleton;
|
|
232066
232699
|
var init_backgroundTaskRegistry = __esm({
|
|
232067
232700
|
"features/perchTerminal/runtime/background/backgroundTaskRegistry.ts"() {
|
|
232068
|
-
"use strict";
|
|
232069
232701
|
init_backgroundTaskTypes();
|
|
232070
232702
|
STORE_FILE = "background-tasks.json";
|
|
232071
232703
|
BackgroundTaskStore = class {
|
|
232704
|
+
baseDir;
|
|
232705
|
+
storePath;
|
|
232706
|
+
outputDir;
|
|
232707
|
+
records = /* @__PURE__ */ new Map();
|
|
232708
|
+
loaded = false;
|
|
232072
232709
|
constructor(options = {}) {
|
|
232073
|
-
this.records = /* @__PURE__ */ new Map();
|
|
232074
|
-
this.loaded = false;
|
|
232075
232710
|
this.baseDir = options.baseDir ?? path11.join(os.homedir(), ".perch");
|
|
232076
232711
|
this.storePath = path11.join(this.baseDir, STORE_FILE);
|
|
232077
232712
|
this.outputDir = path11.join(this.baseDir, "background-output");
|
|
@@ -233621,337 +234256,11 @@ var init_nodeLocalBridge = __esm({
|
|
|
233621
234256
|
}
|
|
233622
234257
|
});
|
|
233623
234258
|
|
|
233624
|
-
// features/perchTerminal/runtime/cliHost/runCliTurn.ts
|
|
233625
|
-
import path14 from "node:path";
|
|
233626
|
-
async function runPerchCliTurn(input, options = {}) {
|
|
233627
|
-
const prompt = input.prompt.trim();
|
|
233628
|
-
if (!prompt) throw new Error("Missing prompt for perch run");
|
|
233629
|
-
const now23 = options.now ?? (() => Date.now());
|
|
233630
|
-
const cwd2 = path14.resolve(input.cwd ?? process.cwd());
|
|
233631
|
-
const threadId = input.threadId?.trim() || `cli-${now23()}`;
|
|
233632
|
-
const events = [];
|
|
233633
|
-
let latestContextSnapshot = null;
|
|
233634
|
-
const turnRunner = resolveTurnRunnerForPrompt(prompt) ?? options.turnRunner ?? runOperatorTurn;
|
|
233635
|
-
const turnInput = buildCliTurnInput(input, {
|
|
233636
|
-
prompt,
|
|
233637
|
-
cwd: cwd2,
|
|
233638
|
-
threadId
|
|
233639
|
-
});
|
|
233640
|
-
const deps = buildCliTurnDeps({
|
|
233641
|
-
signal: input.signal ?? null,
|
|
233642
|
-
events,
|
|
233643
|
-
onEvent: input.onEvent,
|
|
233644
|
-
onContextSnapshot: (snapshot) => {
|
|
233645
|
-
latestContextSnapshot = snapshot;
|
|
233646
|
-
}
|
|
233647
|
-
});
|
|
233648
|
-
const restoreLocalBridge = input.cliLocalTools === false ? null : installCliNodeLocalBridge({ workspaceRoot: cwd2 });
|
|
233649
|
-
try {
|
|
233650
|
-
const result2 = await turnRunner(turnInput, deps);
|
|
233651
|
-
return buildCliTurnResult({
|
|
233652
|
-
prompt,
|
|
233653
|
-
cwd: cwd2,
|
|
233654
|
-
threadId,
|
|
233655
|
-
events,
|
|
233656
|
-
contextSnapshot: latestContextSnapshot,
|
|
233657
|
-
result: result2,
|
|
233658
|
-
error: null
|
|
233659
|
-
});
|
|
233660
|
-
} catch (error) {
|
|
233661
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
233662
|
-
const runId = findRunId(events) ?? threadId;
|
|
233663
|
-
return buildCliTurnResult({
|
|
233664
|
-
prompt,
|
|
233665
|
-
cwd: cwd2,
|
|
233666
|
-
threadId,
|
|
233667
|
-
events,
|
|
233668
|
-
contextSnapshot: latestContextSnapshot,
|
|
233669
|
-
result: {
|
|
233670
|
-
status: "failed",
|
|
233671
|
-
error: message,
|
|
233672
|
-
runId
|
|
233673
|
-
},
|
|
233674
|
-
error: message
|
|
233675
|
-
});
|
|
233676
|
-
} finally {
|
|
233677
|
-
restoreLocalBridge?.();
|
|
233678
|
-
}
|
|
233679
|
-
}
|
|
233680
|
-
function buildCliTurnInput(input, resolved) {
|
|
233681
|
-
const workspaceId = input.workspaceId ?? null;
|
|
233682
|
-
const userId = input.userId ?? null;
|
|
233683
|
-
return {
|
|
233684
|
-
trimmedInput: resolved.prompt,
|
|
233685
|
-
clientRunId: input.clientRunId ?? null,
|
|
233686
|
-
chatMode: input.chatMode ?? "agents",
|
|
233687
|
-
threadId: resolved.threadId,
|
|
233688
|
-
personaId: input.personaId ?? DEFAULT_PERSONA_ID,
|
|
233689
|
-
workspaceId,
|
|
233690
|
-
userId,
|
|
233691
|
-
selectedSourceId: input.selectedSourceId ?? null,
|
|
233692
|
-
recentMessages: input.recentMessages ?? [],
|
|
233693
|
-
usageRunIds: input.usageRunIds ?? [],
|
|
233694
|
-
sessionContext: emptyPerchTerminalSessionContext({
|
|
233695
|
-
userId,
|
|
233696
|
-
workspaceId
|
|
233697
|
-
}),
|
|
233698
|
-
desktopConnected: input.desktopConnected ?? false,
|
|
233699
|
-
cliLocalTools: input.cliLocalTools !== false,
|
|
233700
|
-
activeRootPath: input.activeRootPath ?? resolved.cwd,
|
|
233701
|
-
localSources: [],
|
|
233702
|
-
localSourcesMeta: null,
|
|
233703
|
-
projectMeta: readCliProjectMemoryState(resolved.cwd).meta,
|
|
233704
|
-
permanentMemories: input.permanentMemories ?? [],
|
|
233705
|
-
userMemories: input.userMemories ?? [],
|
|
233706
|
-
supabase: null,
|
|
233707
|
-
supabaseConfigured: false,
|
|
233708
|
-
cliServerAppUrl: input.cliServerAppUrl ?? input.marketDeskProxyAppUrl ?? input.appUrl ?? null,
|
|
233709
|
-
cliServerAccessToken: input.cliServerAccessToken ?? input.marketDeskProxyAccessToken ?? null,
|
|
233710
|
-
marketDeskProxyAppUrl: input.marketDeskProxyAppUrl ?? input.appUrl ?? null,
|
|
233711
|
-
marketDeskProxyAccessToken: input.marketDeskProxyAccessToken ?? null,
|
|
233712
|
-
permissionMode: normalizePermissionMode(input.permissionMode ?? "default"),
|
|
233713
|
-
founderModelSelection: input.founderModelSelection ?? null,
|
|
233714
|
-
threadEvidence: [],
|
|
233715
|
-
workspaceFileResolution: null,
|
|
233716
|
-
folderIndexSummary: null,
|
|
233717
|
-
mcpTools: [],
|
|
233718
|
-
coordinatorMode: false,
|
|
233719
|
-
systemNotifications: [],
|
|
233720
|
-
workflowState: null,
|
|
233721
|
-
skipUserMessagePersistence: false,
|
|
233722
|
-
systemInitiated: input.systemInitiated === true
|
|
233723
|
-
};
|
|
233724
|
-
}
|
|
233725
|
-
function buildCliTurnDeps(input) {
|
|
233726
|
-
return {
|
|
233727
|
-
signal: input.signal,
|
|
233728
|
-
onEvent: (event) => {
|
|
233729
|
-
input.events.push(event);
|
|
233730
|
-
input.onEvent?.(event);
|
|
233731
|
-
},
|
|
233732
|
-
onContextAssembled: input.onContextSnapshot,
|
|
233733
|
-
persistUserMessage: async () => null,
|
|
233734
|
-
persistAssistantMessage: async () => null,
|
|
233735
|
-
createWorkflowRun: async () => null,
|
|
233736
|
-
finishWorkflowRun: async () => {
|
|
233737
|
-
},
|
|
233738
|
-
persistToolCall: async () => {
|
|
233739
|
-
},
|
|
233740
|
-
persistSandboxExecution: async () => null,
|
|
233741
|
-
persistContextSnapshot: async () => null,
|
|
233742
|
-
createManagedArtifact: async () => null,
|
|
233743
|
-
createManagedReport: async () => null,
|
|
233744
|
-
logProductAuditEvent: async () => {
|
|
233745
|
-
}
|
|
233746
|
-
};
|
|
233747
|
-
}
|
|
233748
|
-
function buildCliTurnResult(input) {
|
|
233749
|
-
const segments = buildTranscriptSegments({
|
|
233750
|
-
events: input.events,
|
|
233751
|
-
complete: true,
|
|
233752
|
-
error: input.error
|
|
233753
|
-
});
|
|
233754
|
-
const status = input.result.status;
|
|
233755
|
-
const assistantText = "assistantText" in input.result ? input.result.assistantText : "";
|
|
233756
|
-
const durationMs = "durationMs" in input.result ? input.result.durationMs : null;
|
|
233757
|
-
const error = input.error ?? ("error" in input.result ? input.result.error : void 0) ?? ("reason" in input.result ? input.result.reason : void 0);
|
|
233758
|
-
return {
|
|
233759
|
-
ok: status === "completed",
|
|
233760
|
-
status,
|
|
233761
|
-
prompt: input.prompt,
|
|
233762
|
-
cwd: input.cwd,
|
|
233763
|
-
threadId: input.threadId,
|
|
233764
|
-
runId: input.result.runId,
|
|
233765
|
-
durationMs,
|
|
233766
|
-
assistantText,
|
|
233767
|
-
events: input.events,
|
|
233768
|
-
segments,
|
|
233769
|
-
contextSnapshot: input.contextSnapshot,
|
|
233770
|
-
...error ? { error } : {}
|
|
233771
|
-
};
|
|
233772
|
-
}
|
|
233773
|
-
function findRunId(events) {
|
|
233774
|
-
const started = events.find((event) => event.type === "turn_started");
|
|
233775
|
-
return started?.type === "turn_started" ? started.runId : null;
|
|
233776
|
-
}
|
|
233777
|
-
var init_runCliTurn = __esm({
|
|
233778
|
-
"features/perchTerminal/runtime/cliHost/runCliTurn.ts"() {
|
|
233779
|
-
"use strict";
|
|
233780
|
-
init_session();
|
|
233781
|
-
init_sharedSlashCommands();
|
|
233782
|
-
init_operatorStream();
|
|
233783
|
-
init_permissionModes();
|
|
233784
|
-
init_personaRegistry();
|
|
233785
|
-
init_runOperatorTurn();
|
|
233786
|
-
init_nodeLocalBridge();
|
|
233787
|
-
init_nodeLocalBridge();
|
|
233788
|
-
}
|
|
233789
|
-
});
|
|
233790
|
-
|
|
233791
|
-
// features/perchTerminal/runtime/cliHost/renderCliTurn.ts
|
|
233792
|
-
function renderCliTurnText(result2, options = {}) {
|
|
233793
|
-
const lines = [];
|
|
233794
|
-
const seen = /* @__PURE__ */ new Set();
|
|
233795
|
-
for (const event of result2.events) {
|
|
233796
|
-
const line = renderEventLine(event);
|
|
233797
|
-
if (!line) continue;
|
|
233798
|
-
const lineKey = `${line.label}:${line.text}`;
|
|
233799
|
-
if (seen.has(lineKey)) continue;
|
|
233800
|
-
seen.add(lineKey);
|
|
233801
|
-
lines.push(renderStreamLine(line.label, line.text, options));
|
|
233802
|
-
}
|
|
233803
|
-
if (result2.assistantText.trim()) {
|
|
233804
|
-
if (lines.length) lines.push("");
|
|
233805
|
-
lines.push(
|
|
233806
|
-
renderStreamLine(
|
|
233807
|
-
personaLabel(options.personaId),
|
|
233808
|
-
result2.assistantText.trim(),
|
|
233809
|
-
options
|
|
233810
|
-
)
|
|
233811
|
-
);
|
|
233812
|
-
}
|
|
233813
|
-
if (!result2.ok) {
|
|
233814
|
-
if (lines.length) lines.push("");
|
|
233815
|
-
lines.push(
|
|
233816
|
-
renderStreamLine(
|
|
233817
|
-
"stop",
|
|
233818
|
-
`Perch stopped: ${result2.error ?? result2.status}`,
|
|
233819
|
-
{ ...options, color: options.color }
|
|
233820
|
-
)
|
|
233821
|
-
);
|
|
233822
|
-
}
|
|
233823
|
-
return `${lines.join("\n").trim()}
|
|
233824
|
-
`;
|
|
233825
|
-
}
|
|
233826
|
-
function cliExitCodeForTurn(result2) {
|
|
233827
|
-
if (result2.status === "completed") return 0;
|
|
233828
|
-
if (result2.status === "approval_required" || result2.status === "blocked" || result2.status === "incomplete") {
|
|
233829
|
-
return 2;
|
|
233830
|
-
}
|
|
233831
|
-
return 1;
|
|
233832
|
-
}
|
|
233833
|
-
function renderEventLine(event) {
|
|
233834
|
-
switch (event.type) {
|
|
233835
|
-
case "assistant_preamble":
|
|
233836
|
-
case "activity_delta":
|
|
233837
|
-
case "workflow_narration":
|
|
233838
|
-
return event.text.trim() ? { label: "perch", text: event.text.trim() } : null;
|
|
233839
|
-
case "model_call_started":
|
|
233840
|
-
return { label: "work", text: event.lane ? `Working (${event.lane})` : "Working" };
|
|
233841
|
-
case "tool_call_started":
|
|
233842
|
-
return { label: "tool", text: `Running ${humanizeToolName(event.toolName)}` };
|
|
233843
|
-
case "tool_call_failed":
|
|
233844
|
-
return {
|
|
233845
|
-
label: "need",
|
|
233846
|
-
text: `Needs attention: ${humanizeToolName(event.toolName)}${event.error ? ` \u2014 ${event.error}` : ""}`
|
|
233847
|
-
};
|
|
233848
|
-
case "live_card_start":
|
|
233849
|
-
return { label: "tool", text: event.subtitle ? `${event.title} \xB7 ${event.subtitle}` : event.title };
|
|
233850
|
-
case "live_step":
|
|
233851
|
-
return { label: "tool", text: `${event.label} ${event.status}` };
|
|
233852
|
-
case "live_card_end":
|
|
233853
|
-
return event.receipt?.summary || event.error ? { label: event.error ? "need" : "tool", text: event.receipt?.summary ?? event.error ?? "" } : null;
|
|
233854
|
-
case "workflow_card_start":
|
|
233855
|
-
return { label: "tool", text: `${event.personaName}: ${event.workflowLabel}` };
|
|
233856
|
-
case "workflow_step":
|
|
233857
|
-
return { label: "tool", text: `${event.label} ${event.status}` };
|
|
233858
|
-
case "managed_workflow_started":
|
|
233859
|
-
return { label: "tool", text: event.title };
|
|
233860
|
-
case "managed_workflow_completed":
|
|
233861
|
-
return { label: "tool", text: event.summary };
|
|
233862
|
-
case "capability_required":
|
|
233863
|
-
return { label: "need", text: `Action needed: ${event.message}` };
|
|
233864
|
-
case "executor_waiting_for_user":
|
|
233865
|
-
return { label: "wait", text: `Waiting: ${event.prompt}` };
|
|
233866
|
-
case "executor_result_summary":
|
|
233867
|
-
return { label: "tool", text: event.summary };
|
|
233868
|
-
default:
|
|
233869
|
-
return null;
|
|
233870
|
-
}
|
|
233871
|
-
}
|
|
233872
|
-
function humanizeToolName(name) {
|
|
233873
|
-
return name.replace(/^mcp__/, "").replace(/_/g, " ");
|
|
233874
|
-
}
|
|
233875
|
-
function renderStreamLine(label, text, options) {
|
|
233876
|
-
if (!options.streamLabels) return text;
|
|
233877
|
-
const rawLabel = label.toLowerCase().slice(0, 7);
|
|
233878
|
-
const padded = rawLabel.padEnd(7, " ");
|
|
233879
|
-
const labelText = colorize(padded, colorForLabel(rawLabel), options.color);
|
|
233880
|
-
const body = colorizeMultiline(text, colorForBody(rawLabel), options.color, " ".repeat(7));
|
|
233881
|
-
return `${labelText} ${body}`;
|
|
233882
|
-
}
|
|
233883
|
-
function colorize(text, color, enabled) {
|
|
233884
|
-
return enabled ? `${ANSI[color]}${text}${ANSI.reset}` : text;
|
|
233885
|
-
}
|
|
233886
|
-
function colorizeMultiline(text, color, enabled, continuationIndent) {
|
|
233887
|
-
const painted = colorize(text, color, enabled);
|
|
233888
|
-
return painted.replace(/\n/g, `
|
|
233889
|
-
${continuationIndent} `);
|
|
233890
|
-
}
|
|
233891
|
-
function colorForLabel(label) {
|
|
233892
|
-
if (label === "need" || label === "stop") return "red";
|
|
233893
|
-
if (label === "tool" || label === "work" || label === "wait") return "muted";
|
|
233894
|
-
if (label === "you") return "bronze";
|
|
233895
|
-
return "accent";
|
|
233896
|
-
}
|
|
233897
|
-
function colorForBody(label) {
|
|
233898
|
-
if (label === "need" || label === "stop") return "red";
|
|
233899
|
-
if (label === "tool" || label === "work" || label === "wait") return "muted";
|
|
233900
|
-
return "cream";
|
|
233901
|
-
}
|
|
233902
|
-
function personaLabel(personaId) {
|
|
233903
|
-
return personaId === "quill" ? "quill" : personaId === "saffron" ? "saffron" : "perch";
|
|
233904
|
-
}
|
|
233905
|
-
var ANSI;
|
|
233906
|
-
var init_renderCliTurn = __esm({
|
|
233907
|
-
"features/perchTerminal/runtime/cliHost/renderCliTurn.ts"() {
|
|
233908
|
-
"use strict";
|
|
233909
|
-
ANSI = {
|
|
233910
|
-
reset: "\x1B[0m",
|
|
233911
|
-
accent: "\x1B[38;2;47;94;67m",
|
|
233912
|
-
bronze: "\x1B[38;2;154;82;40m",
|
|
233913
|
-
cream: "\x1B[38;2;255;248;240m",
|
|
233914
|
-
muted: "\x1B[38;2;142;133;125m",
|
|
233915
|
-
green: "\x1B[38;2;110;191;130m",
|
|
233916
|
-
red: "\x1B[38;2;208;107;85m"
|
|
233917
|
-
};
|
|
233918
|
-
}
|
|
233919
|
-
});
|
|
233920
|
-
|
|
233921
|
-
// features/perchTerminal/runtime/cliHost/index.ts
|
|
233922
|
-
var init_cliHost = __esm({
|
|
233923
|
-
"features/perchTerminal/runtime/cliHost/index.ts"() {
|
|
233924
|
-
"use strict";
|
|
233925
|
-
init_runCliTurn();
|
|
233926
|
-
init_renderCliTurn();
|
|
233927
|
-
init_nodeLocalBridge();
|
|
233928
|
-
}
|
|
233929
|
-
});
|
|
233930
|
-
|
|
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
234259
|
// features/perchTerminal/runtime/cliHost/cliAuthSession.ts
|
|
233951
234260
|
import { execFile } from "node:child_process";
|
|
233952
234261
|
import fs13 from "node:fs/promises";
|
|
233953
234262
|
import os3 from "node:os";
|
|
233954
|
-
import
|
|
234263
|
+
import path14 from "node:path";
|
|
233955
234264
|
import { promisify } from "node:util";
|
|
233956
234265
|
async function readStoredCliAuthSession() {
|
|
233957
234266
|
const raw = shouldUseFallbackAuthFile() ? await readFallbackSecret().catch(() => null) : process.platform === "darwin" ? await readKeychainSecret().catch(() => null) : await readFallbackSecret().catch(() => null);
|
|
@@ -234028,15 +234337,15 @@ async function readKeychainSecret() {
|
|
|
234028
234337
|
return stdout.trim() || null;
|
|
234029
234338
|
}
|
|
234030
234339
|
function fallbackSessionPath() {
|
|
234031
|
-
const base = process.env.PERCH_CLI_AUTH_DIR?.trim() ||
|
|
234032
|
-
return
|
|
234340
|
+
const base = process.env.PERCH_CLI_AUTH_DIR?.trim() || path14.join(os3.homedir(), ".perch");
|
|
234341
|
+
return path14.join(base, "cli-auth-session.json");
|
|
234033
234342
|
}
|
|
234034
234343
|
async function readFallbackSecret() {
|
|
234035
234344
|
return await fs13.readFile(fallbackSessionPath(), "utf8");
|
|
234036
234345
|
}
|
|
234037
234346
|
async function writeFallbackSecret(raw) {
|
|
234038
234347
|
const filePath = fallbackSessionPath();
|
|
234039
|
-
await fs13.mkdir(
|
|
234348
|
+
await fs13.mkdir(path14.dirname(filePath), { recursive: true, mode: 448 });
|
|
234040
234349
|
await fs13.writeFile(filePath, raw, { mode: 384 });
|
|
234041
234350
|
}
|
|
234042
234351
|
var execFileAsync, KEYCHAIN_SERVICE, KEYCHAIN_ACCOUNT;
|
|
@@ -234049,6 +234358,162 @@ var init_cliAuthSession = __esm({
|
|
|
234049
234358
|
}
|
|
234050
234359
|
});
|
|
234051
234360
|
|
|
234361
|
+
// features/perchTerminal/runtime/publicModelDefault.ts
|
|
234362
|
+
function parsePublicModelDefaultPayload(value) {
|
|
234363
|
+
const raw = value && typeof value === "object" ? value : {};
|
|
234364
|
+
const selection = sanitizeFounderModelSelection(
|
|
234365
|
+
raw.selection ?? DEFAULT_FOUNDER_MODEL_SELECTION
|
|
234366
|
+
);
|
|
234367
|
+
return {
|
|
234368
|
+
selection,
|
|
234369
|
+
updatedAt: typeof raw.updatedAt === "string" ? raw.updatedAt : null,
|
|
234370
|
+
updatedBy: typeof raw.updatedBy === "string" ? raw.updatedBy : null
|
|
234371
|
+
};
|
|
234372
|
+
}
|
|
234373
|
+
var init_publicModelDefault = __esm({
|
|
234374
|
+
"features/perchTerminal/runtime/publicModelDefault.ts"() {
|
|
234375
|
+
"use strict";
|
|
234376
|
+
init_modelRegistry();
|
|
234377
|
+
}
|
|
234378
|
+
});
|
|
234379
|
+
|
|
234380
|
+
// features/perchTerminal/runtime/cliHost/installCliModelProxyAuthRefresh.ts
|
|
234381
|
+
function installCliModelProxyAuthRefresh() {
|
|
234382
|
+
setCliModelProxyAuthRefresher(async () => {
|
|
234383
|
+
const fresh = await ensureFreshCliAuthSession({
|
|
234384
|
+
session: await readStoredCliAuthSession(),
|
|
234385
|
+
forceRefresh: true
|
|
234386
|
+
});
|
|
234387
|
+
if (!fresh?.accessToken?.trim()) return null;
|
|
234388
|
+
if (typeof process !== "undefined") {
|
|
234389
|
+
process.env[MODEL_CALL_PROXY_TOKEN_ENV2] = fresh.accessToken;
|
|
234390
|
+
}
|
|
234391
|
+
return fresh.accessToken;
|
|
234392
|
+
});
|
|
234393
|
+
return () => setCliModelProxyAuthRefresher(null);
|
|
234394
|
+
}
|
|
234395
|
+
var MODEL_CALL_PROXY_TOKEN_ENV2;
|
|
234396
|
+
var init_installCliModelProxyAuthRefresh = __esm({
|
|
234397
|
+
"features/perchTerminal/runtime/cliHost/installCliModelProxyAuthRefresh.ts"() {
|
|
234398
|
+
"use strict";
|
|
234399
|
+
init_cliAuthRefreshRegistry();
|
|
234400
|
+
init_cliAuthRefresh();
|
|
234401
|
+
init_cliAuthSession();
|
|
234402
|
+
MODEL_CALL_PROXY_TOKEN_ENV2 = "PERCH_MODEL_CALL_PROXY_TOKEN";
|
|
234403
|
+
}
|
|
234404
|
+
});
|
|
234405
|
+
|
|
234406
|
+
// features/perchTerminal/runtime/cliHost/modelConnection.ts
|
|
234407
|
+
async function connectCliModelProxy(input = {}) {
|
|
234408
|
+
installCliModelProxyAuthRefresh();
|
|
234409
|
+
const storedSession = input.storedSession === void 0 ? await readStoredCliAuthSession() : input.storedSession;
|
|
234410
|
+
const usableStoredSession = await ensureFreshCliAuthSession({
|
|
234411
|
+
session: storedSession,
|
|
234412
|
+
fetchImpl: input.fetchImpl
|
|
234413
|
+
});
|
|
234414
|
+
const appUrl = resolveCliAppUrl(input.appUrl, usableStoredSession?.appUrl ?? null);
|
|
234415
|
+
if (!appUrl) return noCliModelConnection();
|
|
234416
|
+
if (!usableStoredSession?.accessToken) return noCliModelConnection(appUrl);
|
|
234417
|
+
const fetchImpl = input.fetchImpl ?? globalThis.fetch;
|
|
234418
|
+
if (typeof fetchImpl !== "function") return noCliModelConnection(appUrl);
|
|
234419
|
+
const selection = await fetchPublicModelDefaultSelection(
|
|
234420
|
+
appUrl,
|
|
234421
|
+
fetchImpl,
|
|
234422
|
+
usableStoredSession?.accessToken ?? null
|
|
234423
|
+
);
|
|
234424
|
+
if (!selection) return noCliModelConnection(appUrl);
|
|
234425
|
+
const priorProxy = process.env[MODEL_PROXY_ENV];
|
|
234426
|
+
const priorToken = process.env[MODEL_PROXY_TOKEN_ENV];
|
|
234427
|
+
process.env[MODEL_PROXY_ENV] = appUrl;
|
|
234428
|
+
if (usableStoredSession?.accessToken) {
|
|
234429
|
+
process.env[MODEL_PROXY_TOKEN_ENV] = usableStoredSession.accessToken;
|
|
234430
|
+
}
|
|
234431
|
+
return {
|
|
234432
|
+
appUrl,
|
|
234433
|
+
authenticated: !!usableStoredSession?.accessToken,
|
|
234434
|
+
userId: usableStoredSession?.userId ?? null,
|
|
234435
|
+
email: usableStoredSession?.email ?? null,
|
|
234436
|
+
founderModelSelection: selection,
|
|
234437
|
+
restore: () => {
|
|
234438
|
+
if (priorProxy === void 0) {
|
|
234439
|
+
delete process.env[MODEL_PROXY_ENV];
|
|
234440
|
+
} else {
|
|
234441
|
+
process.env[MODEL_PROXY_ENV] = priorProxy;
|
|
234442
|
+
}
|
|
234443
|
+
if (priorToken === void 0) {
|
|
234444
|
+
delete process.env[MODEL_PROXY_TOKEN_ENV];
|
|
234445
|
+
} else {
|
|
234446
|
+
process.env[MODEL_PROXY_TOKEN_ENV] = priorToken;
|
|
234447
|
+
}
|
|
234448
|
+
}
|
|
234449
|
+
};
|
|
234450
|
+
}
|
|
234451
|
+
function resolveCliAppUrl(explicit, stored) {
|
|
234452
|
+
const raw = explicit?.trim() || process.env[CLI_APP_URL_ENV]?.trim() || process.env[FALLBACK_APP_URL_ENV]?.trim() || stored?.trim() || DEFAULT_CLI_APP_URL;
|
|
234453
|
+
if (!raw) return null;
|
|
234454
|
+
const withProtocol = /^https?:\/\//i.test(raw) ? raw : `http://${raw}`;
|
|
234455
|
+
try {
|
|
234456
|
+
const url = new URL(withProtocol);
|
|
234457
|
+
url.pathname = url.pathname.replace(/\/+$/, "");
|
|
234458
|
+
url.search = "";
|
|
234459
|
+
url.hash = "";
|
|
234460
|
+
return url.toString().replace(/\/+$/, "");
|
|
234461
|
+
} catch {
|
|
234462
|
+
return null;
|
|
234463
|
+
}
|
|
234464
|
+
}
|
|
234465
|
+
async function fetchPublicModelDefaultSelection(appUrl, fetchImpl, accessToken) {
|
|
234466
|
+
const url = `${appUrl}/api/perch-terminal/public-model-default`;
|
|
234467
|
+
const controller = new AbortController();
|
|
234468
|
+
const timeout = setTimeout(() => controller.abort(), 3500);
|
|
234469
|
+
try {
|
|
234470
|
+
const response = await fetchImpl(url, {
|
|
234471
|
+
method: "GET",
|
|
234472
|
+
headers: {
|
|
234473
|
+
Accept: "application/json",
|
|
234474
|
+
...accessToken ? { Authorization: `Bearer ${accessToken}` } : {}
|
|
234475
|
+
},
|
|
234476
|
+
signal: controller.signal
|
|
234477
|
+
});
|
|
234478
|
+
if (!response.ok) return null;
|
|
234479
|
+
const body = await response.json();
|
|
234480
|
+
if (!body || typeof body !== "object") return null;
|
|
234481
|
+
const record = body;
|
|
234482
|
+
if (record.ok !== true) return null;
|
|
234483
|
+
return parsePublicModelDefaultPayload({ selection: record.selection }).selection;
|
|
234484
|
+
} catch {
|
|
234485
|
+
return null;
|
|
234486
|
+
} finally {
|
|
234487
|
+
clearTimeout(timeout);
|
|
234488
|
+
}
|
|
234489
|
+
}
|
|
234490
|
+
function noCliModelConnection(appUrl = null) {
|
|
234491
|
+
return {
|
|
234492
|
+
appUrl,
|
|
234493
|
+
authenticated: false,
|
|
234494
|
+
userId: null,
|
|
234495
|
+
email: null,
|
|
234496
|
+
founderModelSelection: null,
|
|
234497
|
+
restore: () => {
|
|
234498
|
+
}
|
|
234499
|
+
};
|
|
234500
|
+
}
|
|
234501
|
+
var MODEL_PROXY_ENV, MODEL_PROXY_TOKEN_ENV, CLI_APP_URL_ENV, FALLBACK_APP_URL_ENV, DEFAULT_CLI_APP_URL;
|
|
234502
|
+
var init_modelConnection = __esm({
|
|
234503
|
+
"features/perchTerminal/runtime/cliHost/modelConnection.ts"() {
|
|
234504
|
+
"use strict";
|
|
234505
|
+
init_publicModelDefault();
|
|
234506
|
+
init_cliAuthSession();
|
|
234507
|
+
init_cliAuthRefresh();
|
|
234508
|
+
init_installCliModelProxyAuthRefresh();
|
|
234509
|
+
MODEL_PROXY_ENV = "PERCH_MODEL_CALL_PROXY_URL";
|
|
234510
|
+
MODEL_PROXY_TOKEN_ENV = "PERCH_MODEL_CALL_PROXY_TOKEN";
|
|
234511
|
+
CLI_APP_URL_ENV = "PERCH_CLI_APP_URL";
|
|
234512
|
+
FALLBACK_APP_URL_ENV = "PERCH_APP_URL";
|
|
234513
|
+
DEFAULT_CLI_APP_URL = "https://app.perchai.app";
|
|
234514
|
+
}
|
|
234515
|
+
});
|
|
234516
|
+
|
|
234052
234517
|
// features/perchTerminal/runtime/cliHost/cliStandaloneOAuth.ts
|
|
234053
234518
|
import { execFile as execFile2 } from "node:child_process";
|
|
234054
234519
|
import http from "node:http";
|
|
@@ -234263,7 +234728,7 @@ async function ensureFreshCliAuthSession(input) {
|
|
|
234263
234728
|
if (!session?.accessToken?.trim()) return null;
|
|
234264
234729
|
const nowSeconds = Math.floor(Date.now() / 1e3);
|
|
234265
234730
|
const stillUsable = !session.expiresAt || session.expiresAt > nowSeconds + 90;
|
|
234266
|
-
if (stillUsable) return session;
|
|
234731
|
+
if (stillUsable && !input.forceRefresh) return session;
|
|
234267
234732
|
if (!session.refreshToken?.trim()) return null;
|
|
234268
234733
|
const fetchImpl = input.fetchImpl ?? globalThis.fetch;
|
|
234269
234734
|
if (typeof fetchImpl !== "function") return null;
|
|
@@ -234308,112 +234773,340 @@ var init_cliAuthRefresh = __esm({
|
|
|
234308
234773
|
}
|
|
234309
234774
|
});
|
|
234310
234775
|
|
|
234311
|
-
// features/perchTerminal/runtime/cliHost/
|
|
234312
|
-
async function
|
|
234313
|
-
|
|
234314
|
-
const
|
|
234315
|
-
|
|
234316
|
-
|
|
234317
|
-
|
|
234318
|
-
const
|
|
234319
|
-
if (!
|
|
234320
|
-
if (
|
|
234321
|
-
|
|
234322
|
-
|
|
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;
|
|
234776
|
+
// features/perchTerminal/runtime/cliHost/refreshCliTurnAuth.ts
|
|
234777
|
+
async function refreshCliTurnAuth(input) {
|
|
234778
|
+
if (input.cliLocalTools !== true) return input;
|
|
234779
|
+
const stored = await readStoredCliAuthSession();
|
|
234780
|
+
if (!stored?.accessToken?.trim()) return input;
|
|
234781
|
+
const turnToken = input.cliServerAccessToken?.trim();
|
|
234782
|
+
if (turnToken && turnToken !== stored.accessToken) return input;
|
|
234783
|
+
const fresh = await ensureFreshCliAuthSession({ session: stored });
|
|
234784
|
+
if (!fresh?.accessToken?.trim()) return input;
|
|
234785
|
+
if (fresh.accessToken === input.cliServerAccessToken) return input;
|
|
234786
|
+
if (typeof process !== "undefined") {
|
|
234787
|
+
process.env.PERCH_MODEL_CALL_PROXY_TOKEN = fresh.accessToken;
|
|
234334
234788
|
}
|
|
234335
234789
|
return {
|
|
234336
|
-
|
|
234337
|
-
|
|
234338
|
-
|
|
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
|
-
}
|
|
234790
|
+
...input,
|
|
234791
|
+
cliServerAccessToken: fresh.accessToken,
|
|
234792
|
+
marketDeskProxyAccessToken: input.marketDeskProxyAccessToken ?? fresh.accessToken
|
|
234353
234793
|
};
|
|
234354
234794
|
}
|
|
234355
|
-
|
|
234356
|
-
|
|
234357
|
-
|
|
234358
|
-
|
|
234359
|
-
|
|
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;
|
|
234795
|
+
var init_refreshCliTurnAuth = __esm({
|
|
234796
|
+
"features/perchTerminal/runtime/cliHost/refreshCliTurnAuth.ts"() {
|
|
234797
|
+
"use strict";
|
|
234798
|
+
init_cliAuthSession();
|
|
234799
|
+
init_cliAuthRefresh();
|
|
234367
234800
|
}
|
|
234368
|
-
}
|
|
234369
|
-
|
|
234370
|
-
|
|
234371
|
-
|
|
234372
|
-
|
|
234801
|
+
});
|
|
234802
|
+
|
|
234803
|
+
// features/perchTerminal/runtime/cliHost/runCliTurn.ts
|
|
234804
|
+
import path15 from "node:path";
|
|
234805
|
+
async function runPerchCliTurn(input, options = {}) {
|
|
234806
|
+
const prompt = input.prompt.trim();
|
|
234807
|
+
if (!prompt) throw new Error("Missing prompt for perch run");
|
|
234808
|
+
const now23 = options.now ?? (() => Date.now());
|
|
234809
|
+
const cwd2 = path15.resolve(input.cwd ?? process.cwd());
|
|
234810
|
+
const threadId = input.threadId?.trim() || `cli-${now23()}`;
|
|
234811
|
+
const events = [];
|
|
234812
|
+
let latestContextSnapshot = null;
|
|
234813
|
+
const turnRunner = resolveTurnRunnerForPrompt(prompt) ?? options.turnRunner ?? runOperatorTurn;
|
|
234814
|
+
const turnInput = buildCliTurnInput(input, {
|
|
234815
|
+
prompt,
|
|
234816
|
+
cwd: cwd2,
|
|
234817
|
+
threadId
|
|
234818
|
+
});
|
|
234819
|
+
const deps = buildCliTurnDeps({
|
|
234820
|
+
signal: input.signal ?? null,
|
|
234821
|
+
events,
|
|
234822
|
+
onEvent: input.onEvent,
|
|
234823
|
+
onContextSnapshot: (snapshot) => {
|
|
234824
|
+
latestContextSnapshot = snapshot;
|
|
234825
|
+
},
|
|
234826
|
+
refreshTurnInput: input.cliLocalTools !== false ? refreshCliTurnAuth : void 0
|
|
234827
|
+
});
|
|
234828
|
+
const restoreLocalBridge = input.cliLocalTools === false ? null : installCliNodeLocalBridge({ workspaceRoot: cwd2 });
|
|
234373
234829
|
try {
|
|
234374
|
-
const
|
|
234375
|
-
|
|
234376
|
-
|
|
234377
|
-
|
|
234378
|
-
|
|
234830
|
+
const result2 = await turnRunner(turnInput, deps);
|
|
234831
|
+
return buildCliTurnResult({
|
|
234832
|
+
prompt,
|
|
234833
|
+
cwd: cwd2,
|
|
234834
|
+
threadId,
|
|
234835
|
+
events,
|
|
234836
|
+
contextSnapshot: latestContextSnapshot,
|
|
234837
|
+
result: result2,
|
|
234838
|
+
error: null
|
|
234839
|
+
});
|
|
234840
|
+
} catch (error) {
|
|
234841
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
234842
|
+
const runId = findRunId(events) ?? threadId;
|
|
234843
|
+
return buildCliTurnResult({
|
|
234844
|
+
prompt,
|
|
234845
|
+
cwd: cwd2,
|
|
234846
|
+
threadId,
|
|
234847
|
+
events,
|
|
234848
|
+
contextSnapshot: latestContextSnapshot,
|
|
234849
|
+
result: {
|
|
234850
|
+
status: "failed",
|
|
234851
|
+
error: message,
|
|
234852
|
+
runId
|
|
234379
234853
|
},
|
|
234380
|
-
|
|
234854
|
+
error: message
|
|
234381
234855
|
});
|
|
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
234856
|
} finally {
|
|
234391
|
-
|
|
234857
|
+
restoreLocalBridge?.();
|
|
234392
234858
|
}
|
|
234393
234859
|
}
|
|
234394
|
-
function
|
|
234860
|
+
function buildCliTurnInput(input, resolved) {
|
|
234861
|
+
const workspaceId = input.workspaceId ?? null;
|
|
234862
|
+
const userId = input.userId ?? null;
|
|
234395
234863
|
return {
|
|
234396
|
-
|
|
234397
|
-
|
|
234398
|
-
|
|
234399
|
-
|
|
234400
|
-
|
|
234401
|
-
|
|
234864
|
+
trimmedInput: resolved.prompt,
|
|
234865
|
+
clientRunId: input.clientRunId ?? null,
|
|
234866
|
+
chatMode: input.chatMode ?? "agents",
|
|
234867
|
+
threadId: resolved.threadId,
|
|
234868
|
+
personaId: input.personaId ?? DEFAULT_PERSONA_ID,
|
|
234869
|
+
workspaceId,
|
|
234870
|
+
userId,
|
|
234871
|
+
selectedSourceId: input.selectedSourceId ?? null,
|
|
234872
|
+
recentMessages: input.recentMessages ?? [],
|
|
234873
|
+
usageRunIds: input.usageRunIds ?? [],
|
|
234874
|
+
sessionContext: emptyPerchTerminalSessionContext({
|
|
234875
|
+
userId,
|
|
234876
|
+
workspaceId
|
|
234877
|
+
}),
|
|
234878
|
+
desktopConnected: input.desktopConnected ?? false,
|
|
234879
|
+
cliLocalTools: input.cliLocalTools !== false,
|
|
234880
|
+
activeRootPath: input.activeRootPath ?? resolved.cwd,
|
|
234881
|
+
localSources: [],
|
|
234882
|
+
localSourcesMeta: null,
|
|
234883
|
+
projectMeta: readCliProjectMemoryState(resolved.cwd).meta,
|
|
234884
|
+
permanentMemories: input.permanentMemories ?? [],
|
|
234885
|
+
userMemories: input.userMemories ?? [],
|
|
234886
|
+
supabase: null,
|
|
234887
|
+
supabaseConfigured: false,
|
|
234888
|
+
cliServerAppUrl: input.cliServerAppUrl ?? input.marketDeskProxyAppUrl ?? input.appUrl ?? null,
|
|
234889
|
+
cliServerAccessToken: input.cliServerAccessToken ?? input.marketDeskProxyAccessToken ?? null,
|
|
234890
|
+
marketDeskProxyAppUrl: input.marketDeskProxyAppUrl ?? input.appUrl ?? null,
|
|
234891
|
+
marketDeskProxyAccessToken: input.marketDeskProxyAccessToken ?? null,
|
|
234892
|
+
permissionMode: normalizePermissionMode(input.permissionMode ?? "default"),
|
|
234893
|
+
founderModelSelection: input.founderModelSelection ?? null,
|
|
234894
|
+
threadEvidence: [],
|
|
234895
|
+
workspaceFileResolution: null,
|
|
234896
|
+
folderIndexSummary: null,
|
|
234897
|
+
mcpTools: [],
|
|
234898
|
+
coordinatorMode: false,
|
|
234899
|
+
systemNotifications: [],
|
|
234900
|
+
workflowState: null,
|
|
234901
|
+
skipUserMessagePersistence: false,
|
|
234902
|
+
systemInitiated: input.systemInitiated === true
|
|
234903
|
+
};
|
|
234904
|
+
}
|
|
234905
|
+
function buildCliTurnDeps(input) {
|
|
234906
|
+
return {
|
|
234907
|
+
signal: input.signal,
|
|
234908
|
+
onEvent: (event) => {
|
|
234909
|
+
input.events.push(event);
|
|
234910
|
+
input.onEvent?.(event);
|
|
234911
|
+
},
|
|
234912
|
+
onContextAssembled: input.onContextSnapshot,
|
|
234913
|
+
...input.refreshTurnInput ? { refreshTurnInput: input.refreshTurnInput } : {},
|
|
234914
|
+
persistUserMessage: async () => null,
|
|
234915
|
+
persistAssistantMessage: async () => null,
|
|
234916
|
+
createWorkflowRun: async () => null,
|
|
234917
|
+
finishWorkflowRun: async () => {
|
|
234918
|
+
},
|
|
234919
|
+
persistToolCall: async () => {
|
|
234920
|
+
},
|
|
234921
|
+
persistSandboxExecution: async () => null,
|
|
234922
|
+
persistContextSnapshot: async () => null,
|
|
234923
|
+
createManagedArtifact: async () => null,
|
|
234924
|
+
createManagedReport: async () => null,
|
|
234925
|
+
logProductAuditEvent: async () => {
|
|
234402
234926
|
}
|
|
234403
234927
|
};
|
|
234404
234928
|
}
|
|
234405
|
-
|
|
234406
|
-
|
|
234407
|
-
|
|
234929
|
+
function buildCliTurnResult(input) {
|
|
234930
|
+
const segments = buildTranscriptSegments({
|
|
234931
|
+
events: input.events,
|
|
234932
|
+
complete: true,
|
|
234933
|
+
error: input.error
|
|
234934
|
+
});
|
|
234935
|
+
const status = input.result.status;
|
|
234936
|
+
const assistantText = "assistantText" in input.result ? input.result.assistantText : "";
|
|
234937
|
+
const durationMs = "durationMs" in input.result ? input.result.durationMs : null;
|
|
234938
|
+
const error = input.error ?? ("error" in input.result ? input.result.error : void 0) ?? ("reason" in input.result ? input.result.reason : void 0);
|
|
234939
|
+
return {
|
|
234940
|
+
ok: status === "completed",
|
|
234941
|
+
status,
|
|
234942
|
+
prompt: input.prompt,
|
|
234943
|
+
cwd: input.cwd,
|
|
234944
|
+
threadId: input.threadId,
|
|
234945
|
+
runId: input.result.runId,
|
|
234946
|
+
durationMs,
|
|
234947
|
+
assistantText,
|
|
234948
|
+
events: input.events,
|
|
234949
|
+
segments,
|
|
234950
|
+
contextSnapshot: input.contextSnapshot,
|
|
234951
|
+
...error ? { error } : {}
|
|
234952
|
+
};
|
|
234953
|
+
}
|
|
234954
|
+
function findRunId(events) {
|
|
234955
|
+
const started = events.find((event) => event.type === "turn_started");
|
|
234956
|
+
return started?.type === "turn_started" ? started.runId : null;
|
|
234957
|
+
}
|
|
234958
|
+
var init_runCliTurn = __esm({
|
|
234959
|
+
"features/perchTerminal/runtime/cliHost/runCliTurn.ts"() {
|
|
234408
234960
|
"use strict";
|
|
234409
|
-
|
|
234410
|
-
|
|
234411
|
-
|
|
234412
|
-
|
|
234413
|
-
|
|
234414
|
-
|
|
234415
|
-
|
|
234416
|
-
|
|
234961
|
+
init_session();
|
|
234962
|
+
init_sharedSlashCommands();
|
|
234963
|
+
init_operatorStream();
|
|
234964
|
+
init_permissionModes();
|
|
234965
|
+
init_personaRegistry();
|
|
234966
|
+
init_runOperatorTurn();
|
|
234967
|
+
init_nodeLocalBridge();
|
|
234968
|
+
init_nodeLocalBridge();
|
|
234969
|
+
init_refreshCliTurnAuth();
|
|
234970
|
+
}
|
|
234971
|
+
});
|
|
234972
|
+
|
|
234973
|
+
// features/perchTerminal/runtime/cliHost/renderCliTurn.ts
|
|
234974
|
+
function renderCliTurnText(result2, options = {}) {
|
|
234975
|
+
const lines = [];
|
|
234976
|
+
const seen = /* @__PURE__ */ new Set();
|
|
234977
|
+
for (const event of result2.events) {
|
|
234978
|
+
const line = renderEventLine(event);
|
|
234979
|
+
if (!line) continue;
|
|
234980
|
+
const lineKey = `${line.label}:${line.text}`;
|
|
234981
|
+
if (seen.has(lineKey)) continue;
|
|
234982
|
+
seen.add(lineKey);
|
|
234983
|
+
lines.push(renderStreamLine(line.label, line.text, options));
|
|
234984
|
+
}
|
|
234985
|
+
if (result2.assistantText.trim()) {
|
|
234986
|
+
if (lines.length) lines.push("");
|
|
234987
|
+
lines.push(
|
|
234988
|
+
renderStreamLine(
|
|
234989
|
+
personaLabel(options.personaId),
|
|
234990
|
+
result2.assistantText.trim(),
|
|
234991
|
+
options
|
|
234992
|
+
)
|
|
234993
|
+
);
|
|
234994
|
+
}
|
|
234995
|
+
if (!result2.ok) {
|
|
234996
|
+
if (lines.length) lines.push("");
|
|
234997
|
+
lines.push(
|
|
234998
|
+
renderStreamLine(
|
|
234999
|
+
"stop",
|
|
235000
|
+
`Perch stopped: ${result2.error ?? result2.status}`,
|
|
235001
|
+
{ ...options, color: options.color }
|
|
235002
|
+
)
|
|
235003
|
+
);
|
|
235004
|
+
}
|
|
235005
|
+
return `${lines.join("\n").trim()}
|
|
235006
|
+
`;
|
|
235007
|
+
}
|
|
235008
|
+
function cliExitCodeForTurn(result2) {
|
|
235009
|
+
if (result2.status === "completed") return 0;
|
|
235010
|
+
if (result2.status === "approval_required" || result2.status === "blocked" || result2.status === "incomplete") {
|
|
235011
|
+
return 2;
|
|
235012
|
+
}
|
|
235013
|
+
return 1;
|
|
235014
|
+
}
|
|
235015
|
+
function renderEventLine(event) {
|
|
235016
|
+
switch (event.type) {
|
|
235017
|
+
case "assistant_preamble":
|
|
235018
|
+
case "activity_delta":
|
|
235019
|
+
case "workflow_narration":
|
|
235020
|
+
return event.text.trim() ? { label: "perch", text: event.text.trim() } : null;
|
|
235021
|
+
case "model_call_started":
|
|
235022
|
+
return { label: "work", text: event.lane ? `Working (${event.lane})` : "Working" };
|
|
235023
|
+
case "tool_call_started":
|
|
235024
|
+
return { label: "tool", text: `Running ${humanizeToolName(event.toolName)}` };
|
|
235025
|
+
case "tool_call_failed":
|
|
235026
|
+
return {
|
|
235027
|
+
label: "need",
|
|
235028
|
+
text: `Needs attention: ${humanizeToolName(event.toolName)}${event.error ? ` \u2014 ${event.error}` : ""}`
|
|
235029
|
+
};
|
|
235030
|
+
case "live_card_start":
|
|
235031
|
+
return { label: "tool", text: event.subtitle ? `${event.title} \xB7 ${event.subtitle}` : event.title };
|
|
235032
|
+
case "live_step":
|
|
235033
|
+
return { label: "tool", text: `${event.label} ${event.status}` };
|
|
235034
|
+
case "live_card_end":
|
|
235035
|
+
return event.receipt?.summary || event.error ? { label: event.error ? "need" : "tool", text: event.receipt?.summary ?? event.error ?? "" } : null;
|
|
235036
|
+
case "workflow_card_start":
|
|
235037
|
+
return { label: "tool", text: `${event.personaName}: ${event.workflowLabel}` };
|
|
235038
|
+
case "workflow_step":
|
|
235039
|
+
return { label: "tool", text: `${event.label} ${event.status}` };
|
|
235040
|
+
case "managed_workflow_started":
|
|
235041
|
+
return { label: "tool", text: event.title };
|
|
235042
|
+
case "managed_workflow_completed":
|
|
235043
|
+
return { label: "tool", text: event.summary };
|
|
235044
|
+
case "capability_required":
|
|
235045
|
+
return { label: "need", text: `Action needed: ${event.message}` };
|
|
235046
|
+
case "executor_waiting_for_user":
|
|
235047
|
+
return { label: "wait", text: `Waiting: ${event.prompt}` };
|
|
235048
|
+
case "executor_result_summary":
|
|
235049
|
+
return { label: "tool", text: event.summary };
|
|
235050
|
+
default:
|
|
235051
|
+
return null;
|
|
235052
|
+
}
|
|
235053
|
+
}
|
|
235054
|
+
function humanizeToolName(name) {
|
|
235055
|
+
return name.replace(/^mcp__/, "").replace(/_/g, " ");
|
|
235056
|
+
}
|
|
235057
|
+
function renderStreamLine(label, text, options) {
|
|
235058
|
+
if (!options.streamLabels) return text;
|
|
235059
|
+
const rawLabel = label.toLowerCase().slice(0, 7);
|
|
235060
|
+
const padded = rawLabel.padEnd(7, " ");
|
|
235061
|
+
const labelText = colorize(padded, colorForLabel(rawLabel), options.color);
|
|
235062
|
+
const body = colorizeMultiline(text, colorForBody(rawLabel), options.color, " ".repeat(7));
|
|
235063
|
+
return `${labelText} ${body}`;
|
|
235064
|
+
}
|
|
235065
|
+
function colorize(text, color, enabled) {
|
|
235066
|
+
return enabled ? `${ANSI[color]}${text}${ANSI.reset}` : text;
|
|
235067
|
+
}
|
|
235068
|
+
function colorizeMultiline(text, color, enabled, continuationIndent) {
|
|
235069
|
+
const painted = colorize(text, color, enabled);
|
|
235070
|
+
return painted.replace(/\n/g, `
|
|
235071
|
+
${continuationIndent} `);
|
|
235072
|
+
}
|
|
235073
|
+
function colorForLabel(label) {
|
|
235074
|
+
if (label === "need" || label === "stop") return "red";
|
|
235075
|
+
if (label === "tool" || label === "work" || label === "wait") return "muted";
|
|
235076
|
+
if (label === "you") return "bronze";
|
|
235077
|
+
return "accent";
|
|
235078
|
+
}
|
|
235079
|
+
function colorForBody(label) {
|
|
235080
|
+
if (label === "need" || label === "stop") return "red";
|
|
235081
|
+
if (label === "tool" || label === "work" || label === "wait") return "muted";
|
|
235082
|
+
return "cream";
|
|
235083
|
+
}
|
|
235084
|
+
function personaLabel(personaId) {
|
|
235085
|
+
return personaId === "quill" ? "quill" : personaId === "saffron" ? "saffron" : "perch";
|
|
235086
|
+
}
|
|
235087
|
+
var ANSI;
|
|
235088
|
+
var init_renderCliTurn = __esm({
|
|
235089
|
+
"features/perchTerminal/runtime/cliHost/renderCliTurn.ts"() {
|
|
235090
|
+
"use strict";
|
|
235091
|
+
ANSI = {
|
|
235092
|
+
reset: "\x1B[0m",
|
|
235093
|
+
accent: "\x1B[38;2;47;94;67m",
|
|
235094
|
+
bronze: "\x1B[38;2;154;82;40m",
|
|
235095
|
+
cream: "\x1B[38;2;255;248;240m",
|
|
235096
|
+
muted: "\x1B[38;2;142;133;125m",
|
|
235097
|
+
green: "\x1B[38;2;110;191;130m",
|
|
235098
|
+
red: "\x1B[38;2;208;107;85m"
|
|
235099
|
+
};
|
|
235100
|
+
}
|
|
235101
|
+
});
|
|
235102
|
+
|
|
235103
|
+
// features/perchTerminal/runtime/cliHost/index.ts
|
|
235104
|
+
var init_cliHost = __esm({
|
|
235105
|
+
"features/perchTerminal/runtime/cliHost/index.ts"() {
|
|
235106
|
+
"use strict";
|
|
235107
|
+
init_runCliTurn();
|
|
235108
|
+
init_renderCliTurn();
|
|
235109
|
+
init_nodeLocalBridge();
|
|
234417
235110
|
}
|
|
234418
235111
|
});
|
|
234419
235112
|
|
|
@@ -234937,6 +235630,17 @@ function claimFirstRun(dir = stateDir()) {
|
|
|
234937
235630
|
return false;
|
|
234938
235631
|
}
|
|
234939
235632
|
}
|
|
235633
|
+
function claimFirstPrompt(dir = stateDir()) {
|
|
235634
|
+
const file = path17.join(dir, "cli-first-prompt");
|
|
235635
|
+
try {
|
|
235636
|
+
if (fs15.existsSync(file)) return false;
|
|
235637
|
+
fs15.mkdirSync(dir, { recursive: true });
|
|
235638
|
+
fs15.writeFileSync(file, (/* @__PURE__ */ new Date()).toISOString());
|
|
235639
|
+
return true;
|
|
235640
|
+
} catch {
|
|
235641
|
+
return false;
|
|
235642
|
+
}
|
|
235643
|
+
}
|
|
234940
235644
|
async function postCapture(event, properties) {
|
|
234941
235645
|
const controller = new AbortController();
|
|
234942
235646
|
const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS2);
|
|
@@ -234963,6 +235667,17 @@ function captureCliFirstRun(version4) {
|
|
|
234963
235667
|
if (!claimFirstRun()) return;
|
|
234964
235668
|
void postCapture("cli_first_run", { version: version4, platform: process.platform });
|
|
234965
235669
|
}
|
|
235670
|
+
function captureCliSessionStarted() {
|
|
235671
|
+
if (!POSTHOG_KEY || telemetryDisabled()) return;
|
|
235672
|
+
void postCapture("cli_session_started", { platform: process.platform });
|
|
235673
|
+
}
|
|
235674
|
+
function captureCliMessageSent() {
|
|
235675
|
+
if (!POSTHOG_KEY || telemetryDisabled()) return;
|
|
235676
|
+
void postCapture("message_sent", { surface: "cli" });
|
|
235677
|
+
if (claimFirstPrompt()) {
|
|
235678
|
+
void postCapture("first_message", { surface: "cli" });
|
|
235679
|
+
}
|
|
235680
|
+
}
|
|
234966
235681
|
var POSTHOG_KEY, POSTHOG_HOST, FETCH_TIMEOUT_MS2;
|
|
234967
235682
|
var init_cliAnalytics = __esm({
|
|
234968
235683
|
"features/perchTerminal/runtime/cliHost/cliAnalytics.ts"() {
|
|
@@ -289269,7 +289984,7 @@ function flockEventToCliRow(event) {
|
|
|
289269
289984
|
return {
|
|
289270
289985
|
id: `flock-${event.flockId}-${event.flockWorkerId}`,
|
|
289271
289986
|
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"
|
|
289987
|
+
tone: event.status === "done" ? "success" : event.status === "failed" || event.status === "blocked" ? "danger" : "muted"
|
|
289273
289988
|
};
|
|
289274
289989
|
case "flock_run_completed":
|
|
289275
289990
|
return {
|
|
@@ -289390,6 +290105,7 @@ ${HELP_TEXT}`);
|
|
|
289390
290105
|
recentMessages: nextRecentMessages,
|
|
289391
290106
|
contextSnapshot: result3.contextSnapshot ?? persisted?.contextSnapshot ?? null
|
|
289392
290107
|
});
|
|
290108
|
+
captureCliMessageSent();
|
|
289393
290109
|
return cliExitCodeForTurn(result3);
|
|
289394
290110
|
} finally {
|
|
289395
290111
|
connection.restore();
|
|
@@ -289690,6 +290406,7 @@ async function runReadlineInteractivePerchCli(writer, deps, options) {
|
|
|
289690
290406
|
terminal: true
|
|
289691
290407
|
});
|
|
289692
290408
|
const state = createInteractiveCliState(options);
|
|
290409
|
+
captureCliSessionStarted();
|
|
289693
290410
|
const connectModelProxy = deps.connectModelProxy ?? connectCliModelProxy;
|
|
289694
290411
|
let connection = await connectModelProxy({ appUrl: state.appUrl });
|
|
289695
290412
|
if (!state.appUrl && connection.appUrl) state.appUrl = connection.appUrl;
|
|
@@ -289750,6 +290467,7 @@ async function runReadlineInteractivePerchCli(writer, deps, options) {
|
|
|
289750
290467
|
});
|
|
289751
290468
|
if (!opts.systemInitiated) {
|
|
289752
290469
|
appendRecentMessage(state.recentMessages, "user", turnPrompt);
|
|
290470
|
+
captureCliMessageSent();
|
|
289753
290471
|
}
|
|
289754
290472
|
if (result2.assistantText.trim()) {
|
|
289755
290473
|
appendRecentMessage(state.recentMessages, "assistant", result2.assistantText.trim());
|
|
@@ -289842,6 +290560,7 @@ async function runInkInteractivePerchCli(writer, deps, options) {
|
|
|
289842
290560
|
const runTurn = deps.runCliTurn ?? runPerchCliTurn;
|
|
289843
290561
|
const updateNotice = resolveUpdateNotice(CLI_PACKAGE_VERSION);
|
|
289844
290562
|
captureCliFirstRun(CLI_PACKAGE_VERSION);
|
|
290563
|
+
captureCliSessionStarted();
|
|
289845
290564
|
const instance = Ink2.render(
|
|
289846
290565
|
React11.createElement(function PerchInkApp() {
|
|
289847
290566
|
const app = Ink2.useApp();
|
|
@@ -290463,6 +291182,7 @@ async function runInkInteractivePerchCli(writer, deps, options) {
|
|
|
290463
291182
|
}
|
|
290464
291183
|
if (!systemInitiated) {
|
|
290465
291184
|
appendRecentMessage(state.recentMessages, "user", prompt);
|
|
291185
|
+
captureCliMessageSent();
|
|
290466
291186
|
}
|
|
290467
291187
|
if (assistantText) appendRecentMessage(state.recentMessages, "assistant", assistantText);
|
|
290468
291188
|
trimRecentMessages(state.recentMessages);
|