perchai-cli 2.4.22 → 2.4.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/perch.mjs +781 -23
  2. package/package.json +1 -1
package/dist/perch.mjs CHANGED
@@ -75566,6 +75566,7 @@ 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";
75569
75570
  init_generateAPAuditPacket();
75570
75571
  init_inventoryFolder();
75571
75572
  init_loadBusinessTables();
@@ -75899,7 +75900,6 @@ function isTurnAbortedError(error) {
75899
75900
  var TURN_STOPPED_BY_USER_MESSAGE;
75900
75901
  var init_turnAbort = __esm({
75901
75902
  "features/perchTerminal/runtime/turnAbort.ts"() {
75902
- "use strict";
75903
75903
  TURN_STOPPED_BY_USER_MESSAGE = "Turn stopped by user.";
75904
75904
  }
75905
75905
  });
@@ -85560,7 +85560,6 @@ Final answers lead with findings, name artifacts or delivery status, and give on
85560
85560
  var MARKET_DESK_TOOL_NAMES;
85561
85561
  var init_marketDeskAccess = __esm({
85562
85562
  "features/perchTerminal/runtime/marketDesk/marketDeskAccess.ts"() {
85563
- "use strict";
85564
85563
  init_toolNames();
85565
85564
  MARKET_DESK_TOOL_NAMES = /* @__PURE__ */ new Set([
85566
85565
  TOOL_NAMES.getMarketSignal,
@@ -86717,7 +86716,7 @@ var init_managedWorkflowRegistry = __esm({
86717
86716
  "name": "AP Suite Publisher",
86718
86717
  "description": "Publishes the final AP operator suite envelope from analyst output. Writes findings CSV and report to the output folder.",
86719
86718
  "lane": "writer",
86720
- "systemPrompt": '# AP Suite Publisher\n\nYou receive the analyst\'s structured output from the deterministic AP extraction. Your job is to write deliverable files to disk and return their paths \u2014 not to summarize in prose.\n\n## What to write\n\nUsing `writeLocalFile`, write two files to `{folderPath}/perch-output/`:\n\n**1. `findings.csv`** \u2014 one row per finding from `priorOutput.sandboxArtifact.findings` + analyst `thematicBlocks`:\n```\nid,severity,category,title,financial_exposure,evidence\nfinding-1,critical,duplicate_invoice,"INV-88421 matches INV-77209 same vendor same amount",12400,invoices/INV-88421.csv\n```\n\n**2. `report.md`** \u2014 a structured markdown report:\n```markdown\n# AP Audit Report \u2014 {folderPath leaf}\nGenerated: {ISO date}\n\n## Executive Summary\n{analyst executiveSummary \u2014 2\u20134 sentences, verbatim from analyst output}\n\n## Risk Assessment\n**Level:** {level} \u2014 {justification}\n\n## Findings ({N} total)\n{For each thematic block: ### {title}, severity badge, narrative, finding IDs}\n\n## Recommendations\n{Numbered list from analyst recommendations}\n\n## Data Coverage\n- Records processed: {metrics.recordsNormalized}\n- Tables loaded: {metrics.tablesLoaded}\n- Findings: {metrics.findingsCount}\n{warnings if any}\n```\n\n## Rules\n\n1. **Write the files first, return the paths.** Don\'t summarize in your response \u2014 the files are the deliverable.\n2. Use the folderPath from context. If it\'s not in priorOutput, use the folder name you can infer from evidence paths.\n3. Never fabricate findings not in `sandboxArtifact` or analyst output.\n4. If `sandboxArtifact` is null or analyst output is empty, write a report.md explaining what failed and why.\n\n## Producing Office Files\n\nThe markdown and CSV instructions above still apply. When an Excel workbook is requested or useful for review, produce it as an additional artifact:\n\n1. Use `writeLocalFile` to write a Python script under `/tmp/`.\n2. Run the script with Bash (`python3 /tmp/<script>.py`) using `bash` or `runBashTerminalCommand`.\n3. Save outputs under `{folderPath}/perch-output/`.\n4. Verify the files exist with `ls -l "{folderPath}/perch-output/"`.\n5. Return the verified path in `outputFiles`.\n\nIf the Python Office library is unavailable, fall back to a CSV artifact and return that path clearly.\n\n## Output Schema\n\n```json\n{\n "status": "completed|partial|failed",\n "outputFiles": {\n "findings": "{folderPath}/perch-output/findings.csv",\n "report": "{folderPath}/perch-output/report.md"\n },\n "findingsCount": 0,\n "topFinding": "string or null"\n}\n```\n\n---\n\n---\nname: xlsx-author\ndescription: Produce a real .xlsx workbook on disk from a publisher worker using Bash and openpyxl.\n---\n\n# xlsx-author\n\nUse this skill when the deliverable should be an Excel workbook artifact, not a markdown table or CSV-only substitute.\n\n## Output contract\n\n- Write the workbook to `{folderPath}/perch-output/<name>.xlsx`.\n- Return the workbook path in `outputFiles`.\n- Also keep the required markdown and CSV deliverables from the base prompt.\n\n## How to build the workbook\n\n1. Use `writeLocalFile` to write a short Python script to `/tmp/<workflow>-xlsx-author.py`.\n2. Run it with Bash using `python3 /tmp/<workflow>-xlsx-author.py`.\n3. The script should create `{folderPath}/perch-output/` if needed and save the workbook there.\n4. Verify the artifact with `ls -l "{folderPath}/perch-output/"` before returning.\n\nUse `openpyxl`:\n\n```python\nfrom openpyxl import Workbook\nfrom openpyxl.styles import Font, PatternFill\n\nwb = Workbook()\nws = wb.active\nws.title = "Inputs"\nws["B2"] = "Exposure"\nws["C2"] = 125000\nws["C2"].font = Font(color="0000FF") # blue = hardcoded input\n\ncalc = wb.create_sheet("Analysis")\ncalc["C5"] = "=Inputs!C2" # black = formula\n\nchecks = wb.create_sheet("Checks")\nchecks["A1"] = "Workbook checks"\nchecks["B2"] = "=Analysis!C5=Inputs!C2"\n\nwb.save(output_path)\n```\n\n## Conventions\n\n- Blue / black / green: blue = hardcoded input, black = formula, green = link to another sheet or file.\n- No hardcodes in calculation cells. Put source values on an Inputs tab; calculation tabs use formulas.\n- Include a Checks tab with TRUE/FALSE checks for totals, tie-outs, and row counts.\n- Use clear sheet names and freeze header rows where useful.\n- One model per file. Do not append to an existing workbook unless explicitly asked.\n\n## Fallback\n\nIf `openpyxl` is unavailable, write a CSV version of the workbook content to `{folderPath}/perch-output/<name>.csv`, verify it with `ls`, and clearly return the CSV path instead of pretending an XLSX was created.\n\n---\n\n---\nname: csv-findings\ndescription: Produce clean findings CSV files that spreadsheet tools can open reliably.\n---\n\n# csv-findings\n\nUse this skill for required findings, exceptions, reconciliation, or signal CSV deliverables.\n\n## Output contract\n\n- Write CSV files to `{folderPath}/perch-output/`.\n- Use deterministic column order from the base prompt.\n- Return the CSV path in `outputFiles`.\n\n## How to build the CSV\n\nPrefer writing a small Python script to `/tmp/<workflow>-csv-findings.py` and running it with Bash when quoting, commas, newlines, or currency values could be tricky. Use the standard library `csv` module, create `{folderPath}/perch-output/`, and verify with `ls -l "{folderPath}/perch-output/"`.\n\n```python\nimport csv\nfrom pathlib import Path\n\nout_dir = Path(folder_path) / "perch-output"\nout_dir.mkdir(parents=True, exist_ok=True)\nwith (out_dir / "findings.csv").open("w", newline="", encoding="utf-8") as f:\n writer = csv.DictWriter(f, fieldnames=["id", "severity", "category", "title", "financial_exposure", "evidence"])\n writer.writeheader()\n writer.writerows(rows)\n```\n\n## Conventions\n\n- Preserve exact IDs, names, amounts, accounts, dates, and source filenames from the analyst or sandbox output.\n- Quote fields through the CSV writer; do not hand-escape rows.\n- Use blank cells for missing values rather than invented values.\n- If there are no findings, still write the header row.',
86719
+ "systemPrompt": '# AP Suite Publisher\n\nYou receive the analyst\'s structured output from the deterministic AP extraction. Your job is to write deliverable files to disk and return their paths \u2014 not to summarize in prose.\n\n## What to write\n\nUsing `writeLocalFile`, write two files to `{folderPath}/perch-output/`:\n\n**1. `findings.csv`** \u2014 one row per finding from `priorOutput.sandboxArtifact.findings` + analyst `thematicBlocks`:\n```\nid,severity,category,title,financial_exposure,evidence\nfinding-1,critical,duplicate_invoice,"INV-88421 matches INV-77209 same vendor same amount",12400,invoices/INV-88421.csv\n```\n\n**2. `report.md`** \u2014 a structured markdown report:\n```markdown\n# AP Audit Report \u2014 {folderPath leaf}\nGenerated: {ISO date}\n\n## Executive Summary\n{analyst executiveSummary \u2014 2\u20134 sentences, verbatim from analyst output}\n\n## Risk Assessment\n**Level:** {level} \u2014 {justification}\n\n## Findings ({N} total)\n{For each thematic block: ### {title}, severity badge, narrative, finding IDs}\n\n## Recommendations\n{Numbered list from analyst recommendations}\n\n## Data Coverage\n- Records processed: {metrics.recordsNormalized}\n- Tables loaded: {metrics.tablesLoaded}\n- Findings: {metrics.findingsCount}\n{warnings if any}\n```\n\n## Rules\n\n1. **Write the files first, return the paths.** Don\'t summarize in your response \u2014 the files are the deliverable.\n2. Use the folderPath from context. If it\'s not in priorOutput, use the folder name you can infer from evidence paths.\n3. Never fabricate findings not in `sandboxArtifact` or analyst output.\n4. If `sandboxArtifact` is null or analyst output is empty, write a report.md explaining what failed and why.\n\n## Producing Office Files\n\nThe markdown and CSV instructions above still apply. When an Excel workbook is requested or useful for review, produce it as an additional artifact:\n\n1. Use `writeLocalFile` to write a Python script under `/tmp/`.\n2. Run the script with Bash (`python3 /tmp/<script>.py`) using `bash` or `runBashTerminalCommand`.\n3. Save outputs under `{folderPath}/perch-output/`.\n4. Verify the files exist with `ls -l "{folderPath}/perch-output/"`.\n5. Return the verified path in `outputFiles`.\n\nIf the Python Office library is unavailable, fall back to a CSV artifact and return that path clearly.\n\n## Output Schema\n\n```json\n{\n "status": "completed|partial|failed",\n "outputFiles": {\n "findings": "{folderPath}/perch-output/findings.csv",\n "report": "{folderPath}/perch-output/report.md"\n },\n "findingsCount": 0,\n "topFinding": "string or null"\n}\n```\n\n---\n\n# Core Perch skill: spreadsheets\n\n\n# spreadsheets\n\nUse this skill when the user asks for a spreadsheet artifact, spreadsheet inspection, import-ready CSV, formulas, reconciliation tabs, exception tabs, or workbook-quality output.\n\n## Operating Rules\n\n- Profile incoming sheets before changing them: row counts, columns, types, key IDs, totals, and empty fields.\n- Preserve identifiers as text when they are invoice numbers, account numbers, vendor IDs, employee IDs, tickers, or other non-math values.\n- Put source values on input tabs and formulas on analysis tabs when producing XLSX workbooks.\n- Add checks for totals, row counts, formulas, and exception counts when the workbook drives a decision.\n- Use deterministic code or workbook inspection for fragile calculations; do not hand-wave formula behavior.\n\n## Verification\n\nBefore delivery, verify that the file exists, formulas are present where expected, totals reconcile, and CSV exports keep exact values. See `references/workbook-quality.md`.\n\n---\n\n---\nname: csv-findings\ndescription: Produce clean findings CSV files that spreadsheet tools can open reliably.\n---\n\n# csv-findings\n\nUse this skill for required findings, exceptions, reconciliation, or signal CSV deliverables.\n\n## Output contract\n\n- Write CSV files to `{folderPath}/perch-output/`.\n- Use deterministic column order from the base prompt.\n- Return the CSV path in `outputFiles`.\n\n## How to build the CSV\n\nPrefer writing a small Python script to `/tmp/<workflow>-csv-findings.py` and running it with Bash when quoting, commas, newlines, or currency values could be tricky. Use the standard library `csv` module, create `{folderPath}/perch-output/`, and verify with `ls -l "{folderPath}/perch-output/"`.\n\n```python\nimport csv\nfrom pathlib import Path\n\nout_dir = Path(folder_path) / "perch-output"\nout_dir.mkdir(parents=True, exist_ok=True)\nwith (out_dir / "findings.csv").open("w", newline="", encoding="utf-8") as f:\n writer = csv.DictWriter(f, fieldnames=["id", "severity", "category", "title", "financial_exposure", "evidence"])\n writer.writeheader()\n writer.writerows(rows)\n```\n\n## Conventions\n\n- Preserve exact IDs, names, amounts, accounts, dates, and source filenames from the analyst or sandbox output.\n- Quote fields through the CSV writer; do not hand-escape rows.\n- Use blank cells for missing values rather than invented values.\n- If there are no findings, still write the header row.',
86721
86720
  "allowedTools": [
86722
86721
  "writeLocalFile",
86723
86722
  "bash",
@@ -87024,7 +87023,7 @@ var init_managedWorkflowRegistry = __esm({
87024
87023
  "name": "Earnings Publisher",
87025
87024
  "description": "Writes the earnings review findings and memo to the output folder.",
87026
87025
  "lane": "writer",
87027
- "systemPrompt": '# Earnings Publisher\n\nYou receive the analyst\'s structured earnings output. Your job is to write deliverable files to disk \u2014 not to summarize in prose.\n\n## What to write\n\nUsing `writeLocalFile`, write two files to `{folderPath}/perch-output/`:\n\n**1. `earnings-findings.csv`** \u2014 key metrics and conflicts, one row per signal:\n```\ntype,metric,value,source,conflict,detail\nrevenue,Q1_2026,$125M,earnings-release.html,no,"Revenue $125M in-line"\nconflict,revenue,$412M vs $125M,transcript.txt vs release.html,yes,"Two sources disagree \u2014 transcript shows $412M"\nguidance,FY_2026,raised,call-transcript.md,no,"Guidance raised to $520M"\n```\n\n**2. `earnings-memo.md`** \u2014 analyst memo format:\n```markdown\n# Earnings Review \u2014 {company / folder name}\nGenerated: {ISO date}\n\n## Headline\n{1-sentence: period, revenue, beat/miss, guidance direction}\n\n## Key Metrics\n| Metric | Value | vs Consensus | Source |\n|--------|-------|--------------|--------|\n{rows from analyst output \u2014 exact figures}\n\n## Conflicts & Risk Signals\n{Any cross-source discrepancies with both values and file names}\n\n## Guidance\n{Changes, raised/lowered/withdrawn, exact figures}\n\n## Coverage\n{Files reviewed, any gaps}\n```\n\nThe `folderPath` is in `priorOutput.folderPath`.\n\n## Rules\n\n1. **Write files first, return paths.** The memo is what the analyst team reads \u2014 not a chat message.\n2. Revenue, EPS, and margin figures must be exact from analyst output \u2014 never round or paraphrase.\n3. If two sources disagree, include both values and both file names in the conflict row.\n4. Never fabricate signals not in the analyst output.\n\n## Producing Office Files\n\nThe markdown and CSV instructions above still apply. When an Excel workbook is requested or useful for review, produce it as an additional artifact:\n\n1. Use `writeLocalFile` to write a Python script under `/tmp/`.\n2. Run the script with Bash (`python3 /tmp/<script>.py`) using `bash` or `runBashTerminalCommand`.\n3. Save outputs under `{folderPath}/perch-output/`.\n4. Verify the files exist with `ls -l "{folderPath}/perch-output/"`.\n5. Return the verified path in `outputFiles`.\n\nIf the Python Office library is unavailable, fall back to a CSV artifact and return that path clearly.\n\n## Output Schema\n\n```json\n{\n "status": "completed|partial|failed",\n "outputFiles": {\n "findings": "{folderPath}/perch-output/earnings-findings.csv",\n "memo": "{folderPath}/perch-output/earnings-memo.md"\n },\n "keyMetric": "Revenue $125M, beat 3%",\n "conflictsFound": 0\n}\n```\n\n---\n\n---\nname: xlsx-author\ndescription: Produce a real .xlsx workbook on disk from a publisher worker using Bash and openpyxl.\n---\n\n# xlsx-author\n\nUse this skill when the deliverable should be an Excel workbook artifact, not a markdown table or CSV-only substitute.\n\n## Output contract\n\n- Write the workbook to `{folderPath}/perch-output/<name>.xlsx`.\n- Return the workbook path in `outputFiles`.\n- Also keep the required markdown and CSV deliverables from the base prompt.\n\n## How to build the workbook\n\n1. Use `writeLocalFile` to write a short Python script to `/tmp/<workflow>-xlsx-author.py`.\n2. Run it with Bash using `python3 /tmp/<workflow>-xlsx-author.py`.\n3. The script should create `{folderPath}/perch-output/` if needed and save the workbook there.\n4. Verify the artifact with `ls -l "{folderPath}/perch-output/"` before returning.\n\nUse `openpyxl`:\n\n```python\nfrom openpyxl import Workbook\nfrom openpyxl.styles import Font, PatternFill\n\nwb = Workbook()\nws = wb.active\nws.title = "Inputs"\nws["B2"] = "Exposure"\nws["C2"] = 125000\nws["C2"].font = Font(color="0000FF") # blue = hardcoded input\n\ncalc = wb.create_sheet("Analysis")\ncalc["C5"] = "=Inputs!C2" # black = formula\n\nchecks = wb.create_sheet("Checks")\nchecks["A1"] = "Workbook checks"\nchecks["B2"] = "=Analysis!C5=Inputs!C2"\n\nwb.save(output_path)\n```\n\n## Conventions\n\n- Blue / black / green: blue = hardcoded input, black = formula, green = link to another sheet or file.\n- No hardcodes in calculation cells. Put source values on an Inputs tab; calculation tabs use formulas.\n- Include a Checks tab with TRUE/FALSE checks for totals, tie-outs, and row counts.\n- Use clear sheet names and freeze header rows where useful.\n- One model per file. Do not append to an existing workbook unless explicitly asked.\n\n## Fallback\n\nIf `openpyxl` is unavailable, write a CSV version of the workbook content to `{folderPath}/perch-output/<name>.csv`, verify it with `ls`, and clearly return the CSV path instead of pretending an XLSX was created.\n\n---\n\n---\nname: csv-findings\ndescription: Produce clean findings CSV files that spreadsheet tools can open reliably.\n---\n\n# csv-findings\n\nUse this skill for required findings, exceptions, reconciliation, or signal CSV deliverables.\n\n## Output contract\n\n- Write CSV files to `{folderPath}/perch-output/`.\n- Use deterministic column order from the base prompt.\n- Return the CSV path in `outputFiles`.\n\n## How to build the CSV\n\nPrefer writing a small Python script to `/tmp/<workflow>-csv-findings.py` and running it with Bash when quoting, commas, newlines, or currency values could be tricky. Use the standard library `csv` module, create `{folderPath}/perch-output/`, and verify with `ls -l "{folderPath}/perch-output/"`.\n\n```python\nimport csv\nfrom pathlib import Path\n\nout_dir = Path(folder_path) / "perch-output"\nout_dir.mkdir(parents=True, exist_ok=True)\nwith (out_dir / "findings.csv").open("w", newline="", encoding="utf-8") as f:\n writer = csv.DictWriter(f, fieldnames=["id", "severity", "category", "title", "financial_exposure", "evidence"])\n writer.writeheader()\n writer.writerows(rows)\n```\n\n## Conventions\n\n- Preserve exact IDs, names, amounts, accounts, dates, and source filenames from the analyst or sandbox output.\n- Quote fields through the CSV writer; do not hand-escape rows.\n- Use blank cells for missing values rather than invented values.\n- If there are no findings, still write the header row.',
87026
+ "systemPrompt": '# Earnings Publisher\n\nYou receive the analyst\'s structured earnings output. Your job is to write deliverable files to disk \u2014 not to summarize in prose.\n\n## What to write\n\nUsing `writeLocalFile`, write two files to `{folderPath}/perch-output/`:\n\n**1. `earnings-findings.csv`** \u2014 key metrics and conflicts, one row per signal:\n```\ntype,metric,value,source,conflict,detail\nrevenue,Q1_2026,$125M,earnings-release.html,no,"Revenue $125M in-line"\nconflict,revenue,$412M vs $125M,transcript.txt vs release.html,yes,"Two sources disagree \u2014 transcript shows $412M"\nguidance,FY_2026,raised,call-transcript.md,no,"Guidance raised to $520M"\n```\n\n**2. `earnings-memo.md`** \u2014 analyst memo format:\n```markdown\n# Earnings Review \u2014 {company / folder name}\nGenerated: {ISO date}\n\n## Headline\n{1-sentence: period, revenue, beat/miss, guidance direction}\n\n## Key Metrics\n| Metric | Value | vs Consensus | Source |\n|--------|-------|--------------|--------|\n{rows from analyst output \u2014 exact figures}\n\n## Conflicts & Risk Signals\n{Any cross-source discrepancies with both values and file names}\n\n## Guidance\n{Changes, raised/lowered/withdrawn, exact figures}\n\n## Coverage\n{Files reviewed, any gaps}\n```\n\nThe `folderPath` is in `priorOutput.folderPath`.\n\n## Rules\n\n1. **Write files first, return paths.** The memo is what the analyst team reads \u2014 not a chat message.\n2. Revenue, EPS, and margin figures must be exact from analyst output \u2014 never round or paraphrase.\n3. If two sources disagree, include both values and both file names in the conflict row.\n4. Never fabricate signals not in the analyst output.\n\n## Producing Office Files\n\nThe markdown and CSV instructions above still apply. When an Excel workbook is requested or useful for review, produce it as an additional artifact:\n\n1. Use `writeLocalFile` to write a Python script under `/tmp/`.\n2. Run the script with Bash (`python3 /tmp/<script>.py`) using `bash` or `runBashTerminalCommand`.\n3. Save outputs under `{folderPath}/perch-output/`.\n4. Verify the files exist with `ls -l "{folderPath}/perch-output/"`.\n5. Return the verified path in `outputFiles`.\n\nIf the Python Office library is unavailable, fall back to a CSV artifact and return that path clearly.\n\n## Output Schema\n\n```json\n{\n "status": "completed|partial|failed",\n "outputFiles": {\n "findings": "{folderPath}/perch-output/earnings-findings.csv",\n "memo": "{folderPath}/perch-output/earnings-memo.md"\n },\n "keyMetric": "Revenue $125M, beat 3%",\n "conflictsFound": 0\n}\n```\n\n---\n\n# Core Perch skill: spreadsheets\n\n\n# spreadsheets\n\nUse this skill when the user asks for a spreadsheet artifact, spreadsheet inspection, import-ready CSV, formulas, reconciliation tabs, exception tabs, or workbook-quality output.\n\n## Operating Rules\n\n- Profile incoming sheets before changing them: row counts, columns, types, key IDs, totals, and empty fields.\n- Preserve identifiers as text when they are invoice numbers, account numbers, vendor IDs, employee IDs, tickers, or other non-math values.\n- Put source values on input tabs and formulas on analysis tabs when producing XLSX workbooks.\n- Add checks for totals, row counts, formulas, and exception counts when the workbook drives a decision.\n- Use deterministic code or workbook inspection for fragile calculations; do not hand-wave formula behavior.\n\n## Verification\n\nBefore delivery, verify that the file exists, formulas are present where expected, totals reconcile, and CSV exports keep exact values. See `references/workbook-quality.md`.\n\n---\n\n---\nname: csv-findings\ndescription: Produce clean findings CSV files that spreadsheet tools can open reliably.\n---\n\n# csv-findings\n\nUse this skill for required findings, exceptions, reconciliation, or signal CSV deliverables.\n\n## Output contract\n\n- Write CSV files to `{folderPath}/perch-output/`.\n- Use deterministic column order from the base prompt.\n- Return the CSV path in `outputFiles`.\n\n## How to build the CSV\n\nPrefer writing a small Python script to `/tmp/<workflow>-csv-findings.py` and running it with Bash when quoting, commas, newlines, or currency values could be tricky. Use the standard library `csv` module, create `{folderPath}/perch-output/`, and verify with `ls -l "{folderPath}/perch-output/"`.\n\n```python\nimport csv\nfrom pathlib import Path\n\nout_dir = Path(folder_path) / "perch-output"\nout_dir.mkdir(parents=True, exist_ok=True)\nwith (out_dir / "findings.csv").open("w", newline="", encoding="utf-8") as f:\n writer = csv.DictWriter(f, fieldnames=["id", "severity", "category", "title", "financial_exposure", "evidence"])\n writer.writeheader()\n writer.writerows(rows)\n```\n\n## Conventions\n\n- Preserve exact IDs, names, amounts, accounts, dates, and source filenames from the analyst or sandbox output.\n- Quote fields through the CSV writer; do not hand-escape rows.\n- Use blank cells for missing values rather than invented values.\n- If there are no findings, still write the header row.',
87028
87027
  "allowedTools": [
87029
87028
  "writeLocalFile",
87030
87029
  "bash",
@@ -87183,7 +87182,7 @@ var init_managedWorkflowRegistry = __esm({
87183
87182
  "name": "GL Resolver",
87184
87183
  "description": "Receives the analyst output and writes the GL exception report to the output folder.",
87185
87184
  "lane": "writer",
87186
- "systemPrompt": '# GL Resolver\n\nYou receive the analyst\'s structured output from the deterministic GL reconciliation. Your job is to write deliverable files to disk \u2014 not to summarize in prose.\n\n## What to write\n\nUsing `writeLocalFile`, write two files to `{folderPath}/perch-output/`:\n\n**1. `gl-exceptions.csv`** \u2014 one row per finding:\n```\nid,severity,category,account,entry_id,financial_exposure,detail\ngl-suspense-1,high,suspense_aging,9999,,47500,"Account 9999 carries $47,500 balance"\ngl-unmatched-suspense-2,high,unmatched_suspense,9999,JE-2026-0315,47500,"JE-2026-0315 debits suspense with no offset"\n```\n\n**2. `gl-report.md`** \u2014 structured markdown:\n```markdown\n# GL Reconciliation Report \u2014 {folderPath leaf}\nGenerated: {ISO date}\n\n## Executive Summary\n{analyst executiveSummary}\n\n## Risk Assessment\n**Level:** {level} \u2014 {justification}\n\n## Exception Detail\n{For each thematic block: ### {title}, findings with account IDs and entry IDs}\n\n## Recommended Actions\n{Numbered list \u2014 e.g. "Clear suspense 9999 by allocating JE-2026-0315 to cost center X"}\n\n## Coverage\n- Trial balance rows: {metrics.trialBalanceRows}\n- Journal entries: {metrics.journalEntryCount}\n- Suspense accounts: {metrics.suspenseAccountCount}\n```\n\n## Rules\n\n1. **Write files first, return paths.** The CSV is the deliverable \u2014 accounting needs to sort and filter it, not read a chat message.\n2. Account IDs and entry IDs must appear verbatim from the findings \u2014 don\'t paraphrase.\n3. Never fabricate breaks not in `sandboxArtifact`.\n\n## Producing Office Files\n\nThe markdown and CSV instructions above still apply. When an Excel workbook is requested or useful for review, produce it as an additional artifact:\n\n1. Use `writeLocalFile` to write a Python script under `/tmp/`.\n2. Run the script with Bash (`python3 /tmp/<script>.py`) using `bash` or `runBashTerminalCommand`.\n3. Save outputs under `{folderPath}/perch-output/`.\n4. Verify the files exist with `ls -l "{folderPath}/perch-output/"`.\n5. Return the verified path in `outputFiles`.\n\nIf the Python Office library is unavailable, fall back to a CSV artifact and return that path clearly.\n\n## Output Schema\n\n```json\n{\n "status": "completed|partial|failed",\n "outputFiles": {\n "exceptions": "{folderPath}/perch-output/gl-exceptions.csv",\n "report": "{folderPath}/perch-output/gl-report.md"\n },\n "exceptionsCount": 0,\n "topException": "string or null"\n}\n```\n\n---\n\n---\nname: xlsx-author\ndescription: Produce a real .xlsx workbook on disk from a publisher worker using Bash and openpyxl.\n---\n\n# xlsx-author\n\nUse this skill when the deliverable should be an Excel workbook artifact, not a markdown table or CSV-only substitute.\n\n## Output contract\n\n- Write the workbook to `{folderPath}/perch-output/<name>.xlsx`.\n- Return the workbook path in `outputFiles`.\n- Also keep the required markdown and CSV deliverables from the base prompt.\n\n## How to build the workbook\n\n1. Use `writeLocalFile` to write a short Python script to `/tmp/<workflow>-xlsx-author.py`.\n2. Run it with Bash using `python3 /tmp/<workflow>-xlsx-author.py`.\n3. The script should create `{folderPath}/perch-output/` if needed and save the workbook there.\n4. Verify the artifact with `ls -l "{folderPath}/perch-output/"` before returning.\n\nUse `openpyxl`:\n\n```python\nfrom openpyxl import Workbook\nfrom openpyxl.styles import Font, PatternFill\n\nwb = Workbook()\nws = wb.active\nws.title = "Inputs"\nws["B2"] = "Exposure"\nws["C2"] = 125000\nws["C2"].font = Font(color="0000FF") # blue = hardcoded input\n\ncalc = wb.create_sheet("Analysis")\ncalc["C5"] = "=Inputs!C2" # black = formula\n\nchecks = wb.create_sheet("Checks")\nchecks["A1"] = "Workbook checks"\nchecks["B2"] = "=Analysis!C5=Inputs!C2"\n\nwb.save(output_path)\n```\n\n## Conventions\n\n- Blue / black / green: blue = hardcoded input, black = formula, green = link to another sheet or file.\n- No hardcodes in calculation cells. Put source values on an Inputs tab; calculation tabs use formulas.\n- Include a Checks tab with TRUE/FALSE checks for totals, tie-outs, and row counts.\n- Use clear sheet names and freeze header rows where useful.\n- One model per file. Do not append to an existing workbook unless explicitly asked.\n\n## Fallback\n\nIf `openpyxl` is unavailable, write a CSV version of the workbook content to `{folderPath}/perch-output/<name>.csv`, verify it with `ls`, and clearly return the CSV path instead of pretending an XLSX was created.\n\n---\n\n---\nname: csv-findings\ndescription: Produce clean findings CSV files that spreadsheet tools can open reliably.\n---\n\n# csv-findings\n\nUse this skill for required findings, exceptions, reconciliation, or signal CSV deliverables.\n\n## Output contract\n\n- Write CSV files to `{folderPath}/perch-output/`.\n- Use deterministic column order from the base prompt.\n- Return the CSV path in `outputFiles`.\n\n## How to build the CSV\n\nPrefer writing a small Python script to `/tmp/<workflow>-csv-findings.py` and running it with Bash when quoting, commas, newlines, or currency values could be tricky. Use the standard library `csv` module, create `{folderPath}/perch-output/`, and verify with `ls -l "{folderPath}/perch-output/"`.\n\n```python\nimport csv\nfrom pathlib import Path\n\nout_dir = Path(folder_path) / "perch-output"\nout_dir.mkdir(parents=True, exist_ok=True)\nwith (out_dir / "findings.csv").open("w", newline="", encoding="utf-8") as f:\n writer = csv.DictWriter(f, fieldnames=["id", "severity", "category", "title", "financial_exposure", "evidence"])\n writer.writeheader()\n writer.writerows(rows)\n```\n\n## Conventions\n\n- Preserve exact IDs, names, amounts, accounts, dates, and source filenames from the analyst or sandbox output.\n- Quote fields through the CSV writer; do not hand-escape rows.\n- Use blank cells for missing values rather than invented values.\n- If there are no findings, still write the header row.',
87185
+ "systemPrompt": '# GL Resolver\n\nYou receive the analyst\'s structured output from the deterministic GL reconciliation. Your job is to write deliverable files to disk \u2014 not to summarize in prose.\n\n## What to write\n\nUsing `writeLocalFile`, write two files to `{folderPath}/perch-output/`:\n\n**1. `gl-exceptions.csv`** \u2014 one row per finding:\n```\nid,severity,category,account,entry_id,financial_exposure,detail\ngl-suspense-1,high,suspense_aging,9999,,47500,"Account 9999 carries $47,500 balance"\ngl-unmatched-suspense-2,high,unmatched_suspense,9999,JE-2026-0315,47500,"JE-2026-0315 debits suspense with no offset"\n```\n\n**2. `gl-report.md`** \u2014 structured markdown:\n```markdown\n# GL Reconciliation Report \u2014 {folderPath leaf}\nGenerated: {ISO date}\n\n## Executive Summary\n{analyst executiveSummary}\n\n## Risk Assessment\n**Level:** {level} \u2014 {justification}\n\n## Exception Detail\n{For each thematic block: ### {title}, findings with account IDs and entry IDs}\n\n## Recommended Actions\n{Numbered list \u2014 e.g. "Clear suspense 9999 by allocating JE-2026-0315 to cost center X"}\n\n## Coverage\n- Trial balance rows: {metrics.trialBalanceRows}\n- Journal entries: {metrics.journalEntryCount}\n- Suspense accounts: {metrics.suspenseAccountCount}\n```\n\n## Rules\n\n1. **Write files first, return paths.** The CSV is the deliverable \u2014 accounting needs to sort and filter it, not read a chat message.\n2. Account IDs and entry IDs must appear verbatim from the findings \u2014 don\'t paraphrase.\n3. Never fabricate breaks not in `sandboxArtifact`.\n\n## Producing Office Files\n\nThe markdown and CSV instructions above still apply. When an Excel workbook is requested or useful for review, produce it as an additional artifact:\n\n1. Use `writeLocalFile` to write a Python script under `/tmp/`.\n2. Run the script with Bash (`python3 /tmp/<script>.py`) using `bash` or `runBashTerminalCommand`.\n3. Save outputs under `{folderPath}/perch-output/`.\n4. Verify the files exist with `ls -l "{folderPath}/perch-output/"`.\n5. Return the verified path in `outputFiles`.\n\nIf the Python Office library is unavailable, fall back to a CSV artifact and return that path clearly.\n\n## Output Schema\n\n```json\n{\n "status": "completed|partial|failed",\n "outputFiles": {\n "exceptions": "{folderPath}/perch-output/gl-exceptions.csv",\n "report": "{folderPath}/perch-output/gl-report.md"\n },\n "exceptionsCount": 0,\n "topException": "string or null"\n}\n```\n\n---\n\n# Core Perch skill: spreadsheets\n\n\n# spreadsheets\n\nUse this skill when the user asks for a spreadsheet artifact, spreadsheet inspection, import-ready CSV, formulas, reconciliation tabs, exception tabs, or workbook-quality output.\n\n## Operating Rules\n\n- Profile incoming sheets before changing them: row counts, columns, types, key IDs, totals, and empty fields.\n- Preserve identifiers as text when they are invoice numbers, account numbers, vendor IDs, employee IDs, tickers, or other non-math values.\n- Put source values on input tabs and formulas on analysis tabs when producing XLSX workbooks.\n- Add checks for totals, row counts, formulas, and exception counts when the workbook drives a decision.\n- Use deterministic code or workbook inspection for fragile calculations; do not hand-wave formula behavior.\n\n## Verification\n\nBefore delivery, verify that the file exists, formulas are present where expected, totals reconcile, and CSV exports keep exact values. See `references/workbook-quality.md`.\n\n---\n\n---\nname: csv-findings\ndescription: Produce clean findings CSV files that spreadsheet tools can open reliably.\n---\n\n# csv-findings\n\nUse this skill for required findings, exceptions, reconciliation, or signal CSV deliverables.\n\n## Output contract\n\n- Write CSV files to `{folderPath}/perch-output/`.\n- Use deterministic column order from the base prompt.\n- Return the CSV path in `outputFiles`.\n\n## How to build the CSV\n\nPrefer writing a small Python script to `/tmp/<workflow>-csv-findings.py` and running it with Bash when quoting, commas, newlines, or currency values could be tricky. Use the standard library `csv` module, create `{folderPath}/perch-output/`, and verify with `ls -l "{folderPath}/perch-output/"`.\n\n```python\nimport csv\nfrom pathlib import Path\n\nout_dir = Path(folder_path) / "perch-output"\nout_dir.mkdir(parents=True, exist_ok=True)\nwith (out_dir / "findings.csv").open("w", newline="", encoding="utf-8") as f:\n writer = csv.DictWriter(f, fieldnames=["id", "severity", "category", "title", "financial_exposure", "evidence"])\n writer.writeheader()\n writer.writerows(rows)\n```\n\n## Conventions\n\n- Preserve exact IDs, names, amounts, accounts, dates, and source filenames from the analyst or sandbox output.\n- Quote fields through the CSV writer; do not hand-escape rows.\n- Use blank cells for missing values rather than invented values.\n- If there are no findings, still write the header row.',
87187
87186
  "allowedTools": [
87188
87187
  "writeLocalFile",
87189
87188
  "bash",
@@ -89181,7 +89180,7 @@ var init_managedWorkflowRegistry = __esm({
89181
89180
  "name": "Payroll Publisher",
89182
89181
  "description": "Publishes a payroll approval memo from deterministic run artifacts without recalculating payroll.",
89183
89182
  "lane": "writer",
89184
- "systemPrompt": "# Payroll Publisher\n\nYou publish an approval memo for a completed payroll run.\n\nUse only the deterministic payroll artifact and the analyst output. You may write a short `payroll_output/approval_cover_memo.md` if asked by the coordinator, but you must not alter gross pay, tax cells, deductions, provider import files, or source timesheets.\n\nIf the run is blocked, the memo must say provider import files are not safe to upload until the listed exceptions are fixed and the suite is rerun.\n\nReturn valid JSON matching `schemas/payroll-publisher-output.json`.",
89183
+ "systemPrompt": "# Payroll Publisher\n\nYou publish an approval memo for a completed payroll run.\n\nUse only the deterministic payroll artifact and the analyst output. You may write a short `payroll_output/approval_cover_memo.md` if asked by the coordinator, but you must not alter gross pay, tax cells, deductions, provider import files, or source timesheets.\n\nIf the run is blocked, the memo must say provider import files are not safe to upload until the listed exceptions are fixed and the suite is rerun.\n\nReturn valid JSON matching `schemas/payroll-publisher-output.json`.\n\n---\n\n# Core Perch skill: documents\n\n\n# documents\n\nUse this skill when the user asks Perch to create, revise, review, redline, or deliver a document-shaped artifact.\n\n## Operating Rules\n\n- For long documents, outline the structure before writing the full body.\n- Keep the output as a real deliverable: DOCX, Markdown, Google Doc, or another explicitly requested document target.\n- Preserve source-grounded claims. Load `research`, `pdf`, or `workspace` when citations, page evidence, local files, or source packets drive the content.\n- For Google Docs, use a verified Google Docs shortcut when it can return proof, or hand off to `browser-operator` for live browser editing.\n- Do not claim a header, footer, comment, tracked edit, or page layout feature if the current surface cannot verify it.\n\n## Verification\n\nBefore delivery, check title, body, section order, missing placeholders, citation handoff, and saved file state. Use `references/verification.md` for the compact QA checklist.",
89185
89184
  "allowedTools": [
89186
89185
  "writeLocalFile",
89187
89186
  "readLocalSourceFile",
@@ -89858,7 +89857,7 @@ var init_managedWorkflowRegistry = __esm({
89858
89857
  "name": "Statement Publisher",
89859
89858
  "description": "Writes the statement audit findings and reconciliation to the output folder.",
89860
89859
  "lane": "writer",
89861
- "systemPrompt": '# Statement Publisher\n\nYou receive the analyst\'s structured output. Your job is to write deliverable files to disk \u2014 not to summarize in prose.\n\n## What to write\n\nUsing `writeLocalFile`, write two files to `{folderPath}/perch-output/`:\n\n**1. `nav-reconciliation.csv`** \u2014 one row per finding from `sandboxArtifact.findings`:\n```\nid,severity,investor,lp_nav,admin_nav,gap,detail\nstmt-nav-mismatch-1,critical,Alpha Capital,200000,115000,85000,"LP ending $200k vs fund admin $115k"\n```\n\n**2. `statement-report.md`** \u2014 structured markdown:\n```markdown\n# LP Statement Audit Report \u2014 {folder name}\nGenerated: {ISO date}\n\n## Executive Summary\n{analyst executiveSummary}\n\n## Risk Assessment\n**Level:** {level} \u2014 {justification}\n\n## NAV Reconciliation\n{For each finding: investor name, LP NAV, admin NAV, gap amount, source files}\n\n## Recommended Actions\n{Numbered list \u2014 e.g. "Reconcile Alpha Capital $85,000 gap with fund admin before LP distribution"}\n\n## Coverage\n- LP statement records: {metrics.lpStatementRecords}\n- Fund admin records: {metrics.fundAdminRecords}\n- Total findings: {metrics.findingsCount}\n```\n\nThe `folderPath` is in `priorOutput.folderPath`. Use it to build output paths.\n\n## Rules\n\n1. **Write files first, return paths.** The CSV is what compliance reviews.\n2. Investor names and NAV figures must be verbatim from findings \u2014 never paraphrase amounts.\n3. Never fabricate mismatches not in `sandboxArtifact`.\n4. If sandboxArtifact or analyst output is missing, write a report.md explaining what failed.\n\n## Producing Office Files\n\nThe markdown and CSV instructions above still apply. When an Excel workbook is requested or useful for review, produce it as an additional artifact:\n\n1. Use `writeLocalFile` to write a Python script under `/tmp/`.\n2. Run the script with Bash (`python3 /tmp/<script>.py`) using `bash` or `runBashTerminalCommand`.\n3. Save outputs under `{folderPath}/perch-output/`.\n4. Verify the files exist with `ls -l "{folderPath}/perch-output/"`.\n5. Return the verified path in `outputFiles`.\n\nIf the Python Office library is unavailable, fall back to a CSV artifact and return that path clearly.\n\n## Output Schema\n\n```json\n{\n "status": "completed|partial|failed",\n "outputFiles": {\n "reconciliation": "{folderPath}/perch-output/nav-reconciliation.csv",\n "report": "{folderPath}/perch-output/statement-report.md"\n },\n "findingsCount": 0,\n "topFinding": "string or null"\n}\n```\n\n---\n\n---\nname: xlsx-author\ndescription: Produce a real .xlsx workbook on disk from a publisher worker using Bash and openpyxl.\n---\n\n# xlsx-author\n\nUse this skill when the deliverable should be an Excel workbook artifact, not a markdown table or CSV-only substitute.\n\n## Output contract\n\n- Write the workbook to `{folderPath}/perch-output/<name>.xlsx`.\n- Return the workbook path in `outputFiles`.\n- Also keep the required markdown and CSV deliverables from the base prompt.\n\n## How to build the workbook\n\n1. Use `writeLocalFile` to write a short Python script to `/tmp/<workflow>-xlsx-author.py`.\n2. Run it with Bash using `python3 /tmp/<workflow>-xlsx-author.py`.\n3. The script should create `{folderPath}/perch-output/` if needed and save the workbook there.\n4. Verify the artifact with `ls -l "{folderPath}/perch-output/"` before returning.\n\nUse `openpyxl`:\n\n```python\nfrom openpyxl import Workbook\nfrom openpyxl.styles import Font, PatternFill\n\nwb = Workbook()\nws = wb.active\nws.title = "Inputs"\nws["B2"] = "Exposure"\nws["C2"] = 125000\nws["C2"].font = Font(color="0000FF") # blue = hardcoded input\n\ncalc = wb.create_sheet("Analysis")\ncalc["C5"] = "=Inputs!C2" # black = formula\n\nchecks = wb.create_sheet("Checks")\nchecks["A1"] = "Workbook checks"\nchecks["B2"] = "=Analysis!C5=Inputs!C2"\n\nwb.save(output_path)\n```\n\n## Conventions\n\n- Blue / black / green: blue = hardcoded input, black = formula, green = link to another sheet or file.\n- No hardcodes in calculation cells. Put source values on an Inputs tab; calculation tabs use formulas.\n- Include a Checks tab with TRUE/FALSE checks for totals, tie-outs, and row counts.\n- Use clear sheet names and freeze header rows where useful.\n- One model per file. Do not append to an existing workbook unless explicitly asked.\n\n## Fallback\n\nIf `openpyxl` is unavailable, write a CSV version of the workbook content to `{folderPath}/perch-output/<name>.csv`, verify it with `ls`, and clearly return the CSV path instead of pretending an XLSX was created.\n\n---\n\n---\nname: csv-findings\ndescription: Produce clean findings CSV files that spreadsheet tools can open reliably.\n---\n\n# csv-findings\n\nUse this skill for required findings, exceptions, reconciliation, or signal CSV deliverables.\n\n## Output contract\n\n- Write CSV files to `{folderPath}/perch-output/`.\n- Use deterministic column order from the base prompt.\n- Return the CSV path in `outputFiles`.\n\n## How to build the CSV\n\nPrefer writing a small Python script to `/tmp/<workflow>-csv-findings.py` and running it with Bash when quoting, commas, newlines, or currency values could be tricky. Use the standard library `csv` module, create `{folderPath}/perch-output/`, and verify with `ls -l "{folderPath}/perch-output/"`.\n\n```python\nimport csv\nfrom pathlib import Path\n\nout_dir = Path(folder_path) / "perch-output"\nout_dir.mkdir(parents=True, exist_ok=True)\nwith (out_dir / "findings.csv").open("w", newline="", encoding="utf-8") as f:\n writer = csv.DictWriter(f, fieldnames=["id", "severity", "category", "title", "financial_exposure", "evidence"])\n writer.writeheader()\n writer.writerows(rows)\n```\n\n## Conventions\n\n- Preserve exact IDs, names, amounts, accounts, dates, and source filenames from the analyst or sandbox output.\n- Quote fields through the CSV writer; do not hand-escape rows.\n- Use blank cells for missing values rather than invented values.\n- If there are no findings, still write the header row.',
89860
+ "systemPrompt": '# Statement Publisher\n\nYou receive the analyst\'s structured output. Your job is to write deliverable files to disk \u2014 not to summarize in prose.\n\n## What to write\n\nUsing `writeLocalFile`, write two files to `{folderPath}/perch-output/`:\n\n**1. `nav-reconciliation.csv`** \u2014 one row per finding from `sandboxArtifact.findings`:\n```\nid,severity,investor,lp_nav,admin_nav,gap,detail\nstmt-nav-mismatch-1,critical,Alpha Capital,200000,115000,85000,"LP ending $200k vs fund admin $115k"\n```\n\n**2. `statement-report.md`** \u2014 structured markdown:\n```markdown\n# LP Statement Audit Report \u2014 {folder name}\nGenerated: {ISO date}\n\n## Executive Summary\n{analyst executiveSummary}\n\n## Risk Assessment\n**Level:** {level} \u2014 {justification}\n\n## NAV Reconciliation\n{For each finding: investor name, LP NAV, admin NAV, gap amount, source files}\n\n## Recommended Actions\n{Numbered list \u2014 e.g. "Reconcile Alpha Capital $85,000 gap with fund admin before LP distribution"}\n\n## Coverage\n- LP statement records: {metrics.lpStatementRecords}\n- Fund admin records: {metrics.fundAdminRecords}\n- Total findings: {metrics.findingsCount}\n```\n\nThe `folderPath` is in `priorOutput.folderPath`. Use it to build output paths.\n\n## Rules\n\n1. **Write files first, return paths.** The CSV is what compliance reviews.\n2. Investor names and NAV figures must be verbatim from findings \u2014 never paraphrase amounts.\n3. Never fabricate mismatches not in `sandboxArtifact`.\n4. If sandboxArtifact or analyst output is missing, write a report.md explaining what failed.\n\n## Producing Office Files\n\nThe markdown and CSV instructions above still apply. When an Excel workbook is requested or useful for review, produce it as an additional artifact:\n\n1. Use `writeLocalFile` to write a Python script under `/tmp/`.\n2. Run the script with Bash (`python3 /tmp/<script>.py`) using `bash` or `runBashTerminalCommand`.\n3. Save outputs under `{folderPath}/perch-output/`.\n4. Verify the files exist with `ls -l "{folderPath}/perch-output/"`.\n5. Return the verified path in `outputFiles`.\n\nIf the Python Office library is unavailable, fall back to a CSV artifact and return that path clearly.\n\n## Output Schema\n\n```json\n{\n "status": "completed|partial|failed",\n "outputFiles": {\n "reconciliation": "{folderPath}/perch-output/nav-reconciliation.csv",\n "report": "{folderPath}/perch-output/statement-report.md"\n },\n "findingsCount": 0,\n "topFinding": "string or null"\n}\n```\n\n---\n\n# Core Perch skill: spreadsheets\n\n\n# spreadsheets\n\nUse this skill when the user asks for a spreadsheet artifact, spreadsheet inspection, import-ready CSV, formulas, reconciliation tabs, exception tabs, or workbook-quality output.\n\n## Operating Rules\n\n- Profile incoming sheets before changing them: row counts, columns, types, key IDs, totals, and empty fields.\n- Preserve identifiers as text when they are invoice numbers, account numbers, vendor IDs, employee IDs, tickers, or other non-math values.\n- Put source values on input tabs and formulas on analysis tabs when producing XLSX workbooks.\n- Add checks for totals, row counts, formulas, and exception counts when the workbook drives a decision.\n- Use deterministic code or workbook inspection for fragile calculations; do not hand-wave formula behavior.\n\n## Verification\n\nBefore delivery, verify that the file exists, formulas are present where expected, totals reconcile, and CSV exports keep exact values. See `references/workbook-quality.md`.\n\n---\n\n---\nname: csv-findings\ndescription: Produce clean findings CSV files that spreadsheet tools can open reliably.\n---\n\n# csv-findings\n\nUse this skill for required findings, exceptions, reconciliation, or signal CSV deliverables.\n\n## Output contract\n\n- Write CSV files to `{folderPath}/perch-output/`.\n- Use deterministic column order from the base prompt.\n- Return the CSV path in `outputFiles`.\n\n## How to build the CSV\n\nPrefer writing a small Python script to `/tmp/<workflow>-csv-findings.py` and running it with Bash when quoting, commas, newlines, or currency values could be tricky. Use the standard library `csv` module, create `{folderPath}/perch-output/`, and verify with `ls -l "{folderPath}/perch-output/"`.\n\n```python\nimport csv\nfrom pathlib import Path\n\nout_dir = Path(folder_path) / "perch-output"\nout_dir.mkdir(parents=True, exist_ok=True)\nwith (out_dir / "findings.csv").open("w", newline="", encoding="utf-8") as f:\n writer = csv.DictWriter(f, fieldnames=["id", "severity", "category", "title", "financial_exposure", "evidence"])\n writer.writeheader()\n writer.writerows(rows)\n```\n\n## Conventions\n\n- Preserve exact IDs, names, amounts, accounts, dates, and source filenames from the analyst or sandbox output.\n- Quote fields through the CSV writer; do not hand-escape rows.\n- Use blank cells for missing values rather than invented values.\n- If there are no findings, still write the header row.',
89862
89861
  "allowedTools": [
89863
89862
  "writeLocalFile",
89864
89863
  "bash",
@@ -92161,6 +92160,607 @@ Final message: concise for findings and receipts, usually 10 lines or fewer; con
92161
92160
  }
92162
92161
  });
92163
92162
 
92163
+ // generated/agentSkillRegistry.ts
92164
+ function getAgentSkillManifest(id) {
92165
+ return AGENT_SKILL_INDEX.get(id) ?? null;
92166
+ }
92167
+ function listAgentSkillMetadataRows() {
92168
+ return AGENT_SKILL_REGISTRY.map((skill) => ({
92169
+ id: skill.id,
92170
+ name: skill.name,
92171
+ description: skill.description,
92172
+ surface: skill.surface,
92173
+ triggerHints: skill.triggerHints.slice()
92174
+ }));
92175
+ }
92176
+ var AGENT_SKILL_REGISTRY, AGENT_SKILL_INDEX;
92177
+ var init_agentSkillRegistry = __esm({
92178
+ "generated/agentSkillRegistry.ts"() {
92179
+ "use strict";
92180
+ AGENT_SKILL_REGISTRY = [
92181
+ {
92182
+ "id": "browser-operator",
92183
+ "name": "browser-operator",
92184
+ "description": "Operate Perch's logged-in browser surface using visual state, semantic DOM targets, and coordinate actions when available.",
92185
+ "path": "features/perchTerminal/agentPlatform/skills/browser-operator/SKILL.md",
92186
+ "body": "\n# browser-operator\n\nUse this skill when Perch must operate a live browser page, especially logged-in apps like Google Docs, Gmail, Calendar, SaaS portals, admin consoles, and forms.\n\n## Operating Loop\n\n1. Observe the current page before acting.\n2. For a short multi-step job, use `browser_execute_task` with `goal`, `requiredValues`, `successCriteria`, and a bounded `steps` list.\n3. For a one-off recovery action, decide whether the next action is semantic or visual.\n4. Take one action.\n5. Observe again and verify the result before continuing.\n6. Stop with a clear blocker when the required action surface is missing.\n\n## Tool Choice\n\n- Use semantic tools for normal web controls: buttons, links, menus, named fields, and visible labels.\n- Prefer `browser_execute_task` when the sequence includes fill \u2192 verify \u2192 submit/save/send. Put the exact recipient, subject, body excerpt, title, date/time, or other must-land values in `requiredValues`.\n- Use `browser_visual_click` or `browser_focus_type` for canvas, iframe, whiteboard, document-body, drag/drop, or visually obvious targets that are not exposed as DOM elements.\n- If coordinate tools are unavailable and the target is only visually reachable, do not loop semantic clicks. Report the missing visual-action capability.\n- For text entry, first focus the target, then type into the focused surface. Do not assume a successful keypress means the text landed.\n\n## Google Docs Rules\n\n- Do not inspect, open, or change the Docs mode dropdown during normal writing. New Docs should already start in Editing mode.\n- If the visible toolbar is already read-only, `Viewing mode`, or `Suggesting mode`, stop and report a permission/mode blocker instead of trying to switch modes yourself.\n- Never click the eye/pencil/mode control or hidden zero-size `Viewing mode` entries. Treat those as fragile controls, not recovery targets.\n- Collapse Document tabs / outline panels before typing if they cover the page.\n- Do not rely on DOM text readback for the document body. Google Docs Kix can show text visually while DOM snapshots stay blank.\n- Body writing should use `browser_focus_type`: visual click into the paper area plus focused typing/key events.\n- Verify by URL, title, save status, and screenshot/visual proof. Treat DOM body text as optional evidence only.\n- Never keep retrying `browser_click` with an empty target. If the paper body is only reachable by coordinate, use coordinate click or stop.\n\n## Completion Proof\n\nReturn success only when there is visible proof, such as:\n\n- a saved/created/sent status,\n- a stable URL,\n- a title or body marker visible in the screenshot,\n- a sent toast or delivery receipt.\n\nIf proof is missing, continue with the next valid recovery path or report the blocker. Do not claim completion from intent alone.",
92187
+ "surface": {
92188
+ "gui": "supported",
92189
+ "cli": "limited"
92190
+ },
92191
+ "triggerHints": [
92192
+ "browser",
92193
+ "web app",
92194
+ "logged-in app",
92195
+ "browser google docs",
92196
+ "gmail browser",
92197
+ "calendar browser",
92198
+ "saas portal",
92199
+ "browser form"
92200
+ ],
92201
+ "companionSkills": [
92202
+ "documents",
92203
+ "research",
92204
+ "workspace"
92205
+ ],
92206
+ "preferredTools": [
92207
+ "browser_execute_task",
92208
+ "browser_observe",
92209
+ "browser_click",
92210
+ "browser_type",
92211
+ "browser_visual_click",
92212
+ "browser_focus_type",
92213
+ "browser_press_key",
92214
+ "browser_read",
92215
+ "browser_wait",
92216
+ "browser_navigate"
92217
+ ]
92218
+ },
92219
+ {
92220
+ "id": "data-quality",
92221
+ "name": "data-quality",
92222
+ "description": "Profile messy data, find anomalies, reconcile systems, explain exceptions, score risk, and produce audit-ready evidence through deterministic math before narrative.",
92223
+ "path": "features/perchTerminal/agentPlatform/skills/data-quality/SKILL.md",
92224
+ "body": "\n# data-quality\n\nUse this skill when the user asks for anomalies, duplicates, reconciliations, risk scoring, exception analysis, or audit-ready data evidence.\n\n## Operating Rules\n\n- Run deterministic math before narrative whenever source tables or files are available.\n- Profile schemas, row counts, nulls, key fields, duplicate keys, totals, and join coverage.\n- Preserve exact identifiers, dates, amounts, source filenames, and exception IDs.\n- Explain each exception with evidence and uncertainty, not only a risk label.\n- Use `spreadsheets` for workbook deliverables and `diagrams` for relationship or network outputs.\n\n## Verification\n\nTie totals, duplicate counts, exception rows, and risk outputs back to deterministic evidence. See `references/reconciliation.md`.",
92225
+ "surface": {
92226
+ "gui": "supported",
92227
+ "cli": "supported"
92228
+ },
92229
+ "triggerHints": [
92230
+ "data quality",
92231
+ "anomaly",
92232
+ "reconciliation",
92233
+ "duplicate payment",
92234
+ "duplicate invoice",
92235
+ "tie out",
92236
+ "row totals",
92237
+ "audit exceptions",
92238
+ "vendor risk",
92239
+ "outlier",
92240
+ "missing receipt",
92241
+ "cost anomaly"
92242
+ ],
92243
+ "companionSkills": [
92244
+ "spreadsheets",
92245
+ "workspace",
92246
+ "pdf",
92247
+ "diagrams"
92248
+ ],
92249
+ "preferredTools": [
92250
+ "run_sandbox_code",
92251
+ "runSandboxAnalysis",
92252
+ "prepareSandboxInputs",
92253
+ "generateAPAuditPacket",
92254
+ "render_ap_control_graph",
92255
+ "readLocalFile",
92256
+ "writeLocalFile"
92257
+ ]
92258
+ },
92259
+ {
92260
+ "id": "diagrams",
92261
+ "name": "diagrams",
92262
+ "description": "Create, edit, and verify diagrams, graphs, workflows, org charts, entity networks, control graphs, architecture maps, and evidence-backed visual structures.",
92263
+ "path": "features/perchTerminal/agentPlatform/skills/diagrams/SKILL.md",
92264
+ "body": "\n# diagrams\n\nUse this skill when the user asks for a diagram, graph, workflow, architecture map, network, org chart, or visual relationship model.\n\n## Operating Rules\n\n- Choose diagram format by use: Mermaid for simple structural diagrams, richer rendered artifacts for shareable or visually complex output.\n- Keep charts and diagrams distinct. Charts quantify data; diagrams explain structure, flow, responsibility, or relationships.\n- Tie graph nodes and edges to evidence when the diagram represents real entities, controls, risks, or findings.\n- Use `data-quality` for anomaly and reconciliation networks, and `presentations` or `documents` when the diagram needs to land in a deck or memo.\n\n## Verification\n\nRender or inspect complex diagrams before delivery. Confirm labels are readable, layout is not tangled, and evidence-backed nodes are traceable. See `references/diagram-quality.md`.",
92265
+ "surface": {
92266
+ "gui": "supported",
92267
+ "cli": "supported"
92268
+ },
92269
+ "triggerHints": [
92270
+ "diagram",
92271
+ "mermaid",
92272
+ "drawio",
92273
+ "workflow graph",
92274
+ "org chart",
92275
+ "entity network",
92276
+ "control graph",
92277
+ "architecture map",
92278
+ "vendor-risk graph",
92279
+ "node edge"
92280
+ ],
92281
+ "companionSkills": [
92282
+ "data-quality",
92283
+ "documents",
92284
+ "presentations",
92285
+ "workspace"
92286
+ ],
92287
+ "preferredTools": [
92288
+ "render_ap_control_graph",
92289
+ "createTextArtifact",
92290
+ "writeLocalFile",
92291
+ "run_sandbox_code",
92292
+ "visionInspect"
92293
+ ]
92294
+ },
92295
+ {
92296
+ "id": "documents",
92297
+ "name": "documents",
92298
+ "description": "Create, edit, verify, and deliver professional document artifacts such as DOCX, Google Docs, memos, letters, reports, redlines, and formal written deliverables.",
92299
+ "path": "features/perchTerminal/agentPlatform/skills/documents/SKILL.md",
92300
+ "body": "\n# documents\n\nUse this skill when the user asks Perch to create, revise, review, redline, or deliver a document-shaped artifact.\n\n## Operating Rules\n\n- For long documents, outline the structure before writing the full body.\n- Keep the output as a real deliverable: DOCX, Markdown, Google Doc, or another explicitly requested document target.\n- Preserve source-grounded claims. Load `research`, `pdf`, or `workspace` when citations, page evidence, local files, or source packets drive the content.\n- For Google Docs, use a verified Google Docs shortcut when it can return proof, or hand off to `browser-operator` for live browser editing.\n- Do not claim a header, footer, comment, tracked edit, or page layout feature if the current surface cannot verify it.\n\n## Verification\n\nBefore delivery, check title, body, section order, missing placeholders, citation handoff, and saved file state. Use `references/verification.md` for the compact QA checklist.",
92301
+ "surface": {
92302
+ "gui": "supported",
92303
+ "cli": "supported"
92304
+ },
92305
+ "triggerHints": [
92306
+ "document",
92307
+ "docx",
92308
+ "word file",
92309
+ "google doc",
92310
+ "memo",
92311
+ "letter",
92312
+ "report",
92313
+ "redline",
92314
+ "tracked changes",
92315
+ "policy",
92316
+ "formal artifact"
92317
+ ],
92318
+ "companionSkills": [
92319
+ "research",
92320
+ "pdf",
92321
+ "workspace",
92322
+ "browser-operator"
92323
+ ],
92324
+ "preferredTools": [
92325
+ "createDocumentArtifact",
92326
+ "writeLocalFile",
92327
+ "google_docs_create_verified",
92328
+ "google_docs_create",
92329
+ "browser_execute_task",
92330
+ "readLocalFile",
92331
+ "visionInspect"
92332
+ ]
92333
+ },
92334
+ {
92335
+ "id": "pdf",
92336
+ "name": "pdf",
92337
+ "description": "Extract, summarize, cite, split, merge, create, inspect, and verify PDF content, including text PDFs, scanned PDFs, tables, forms, page citations, and generated reports.",
92338
+ "path": "features/perchTerminal/agentPlatform/skills/pdf/SKILL.md",
92339
+ "body": "\n# pdf\n\nUse this skill when PDF content is the source, destination, or verification target.\n\n## Operating Rules\n\n- Preserve page awareness. Cite exact pages when answering from a PDF.\n- Treat PDF-to-Markdown and OCR as intermediate views, not final truth.\n- Use table-aware extraction or deterministic code when numbers, rows, or column alignment matter.\n- For scanned PDFs, use visual or OCR fallback and report uncertainty when text is unreadable.\n- Do not invent citations, fields, signatures, or form values.\n\n## Verification\n\nCheck that cited pages support the answer, table values are not flattened incorrectly, and generated PDFs render as expected. See `references/page-citations.md`.",
92340
+ "surface": {
92341
+ "gui": "supported",
92342
+ "cli": "supported"
92343
+ },
92344
+ "triggerHints": [
92345
+ "pdf",
92346
+ "annual report pdf",
92347
+ "scanned pdf",
92348
+ "page citation",
92349
+ "pdf table",
92350
+ "split pdf",
92351
+ "merge pdf",
92352
+ "form pdf",
92353
+ "ocr",
92354
+ "pdf report"
92355
+ ],
92356
+ "companionSkills": [
92357
+ "research",
92358
+ "documents",
92359
+ "workspace",
92360
+ "data-quality"
92361
+ ],
92362
+ "preferredTools": [
92363
+ "extractDocument",
92364
+ "extractDocumentSection",
92365
+ "visionInspect",
92366
+ "readLocalFile",
92367
+ "run_sandbox_code",
92368
+ "webFetch"
92369
+ ]
92370
+ },
92371
+ {
92372
+ "id": "presentations",
92373
+ "name": "presentations",
92374
+ "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.",
92375
+ "path": "features/perchTerminal/agentPlatform/skills/presentations/SKILL.md",
92376
+ "body": "\n# presentations\n\nUse this skill when the user asks for slides, a deck, a board packet, a pitch, or a visual narrative.\n\n## Operating Rules\n\n- Start with audience, decision, and slide outline before building a long deck.\n- Each slide should carry one takeaway; supporting charts, tables, and bullets must serve that takeaway.\n- Source every important number or claim from provided evidence, workspace files, research, or companion skills.\n- Use `spreadsheets` for chart data, `diagrams` for networks and workflows, and `pdf` when source packets are page-cited.\n- Prefer real deck files when requested. HTML slide experiences are acceptable only when design iteration or sharing calls for them.\n\n## Verification\n\nCheck slide count, readable text, non-overflowing layout, chart/image rendering, and file existence before delivery. See `references/deck-quality.md`.",
92377
+ "surface": {
92378
+ "gui": "supported",
92379
+ "cli": "supported"
92380
+ },
92381
+ "triggerHints": [
92382
+ "deck",
92383
+ "pptx",
92384
+ "powerpoint",
92385
+ "board deck",
92386
+ "slide deck",
92387
+ "presentation",
92388
+ "executive deck",
92389
+ "pitch deck",
92390
+ "sales deck",
92391
+ "speaker notes",
92392
+ "chart slide"
92393
+ ],
92394
+ "companionSkills": [
92395
+ "documents",
92396
+ "spreadsheets",
92397
+ "research",
92398
+ "diagrams",
92399
+ "pdf",
92400
+ "workspace"
92401
+ ],
92402
+ "preferredTools": [
92403
+ "writeLocalFile",
92404
+ "run_sandbox_code",
92405
+ "createDocumentArtifact",
92406
+ "visionInspect",
92407
+ "readLocalFile"
92408
+ ]
92409
+ },
92410
+ {
92411
+ "id": "research",
92412
+ "name": "research",
92413
+ "description": "Gather, compare, rank, and cite evidence from public web sources, scholarly sources, workspace files, source packets, Qdrant knowledge, and attached local folders.",
92414
+ "path": "features/perchTerminal/agentPlatform/skills/research/SKILL.md",
92415
+ "body": "\n# research\n\nUse this skill when the user asks Perch to gather, compare, verify, or cite evidence.\n\n## Operating Rules\n\n- Choose the source lane intentionally: current public questions use web; attached folders use workspace or local sources; internal product questions use repo/workspace evidence; stored examples use knowledge.\n- Do not use knowledge retrieval as a catch-all when a domain or workspace tool is more direct.\n- Rank sources by authority, recency, relevance, and proximity to the claim.\n- Surface contradictions instead of averaging them away.\n- Keep citations concise and visibly tied to titles, URLs, files, pages, or evidence IDs.\n\n## Verification\n\nCheck freshness requirements, cite the actual sources used, and label uncertainty when sources are missing or disagree. See `references/source-quality.md`.",
92416
+ "surface": {
92417
+ "gui": "supported",
92418
+ "cli": "supported"
92419
+ },
92420
+ "triggerHints": [
92421
+ "research",
92422
+ "web research",
92423
+ "credible sources",
92424
+ "current source",
92425
+ "citation",
92426
+ "source comparison",
92427
+ "filing",
92428
+ "scholarly source",
92429
+ "public web",
92430
+ "fact check",
92431
+ "latest"
92432
+ ],
92433
+ "companionSkills": [
92434
+ "pdf",
92435
+ "documents",
92436
+ "workspace",
92437
+ "data-quality"
92438
+ ],
92439
+ "preferredTools": [
92440
+ "webSearch",
92441
+ "webFetch",
92442
+ "scholarSearch",
92443
+ "searchKnowledge",
92444
+ "searchWorkspaceSources",
92445
+ "readLocalSourceFile",
92446
+ "listLocalSources"
92447
+ ]
92448
+ },
92449
+ {
92450
+ "id": "skill-creator",
92451
+ "name": "skill-creator",
92452
+ "description": "Create, validate, package, and test Perch skills with frontmatter checks, trigger collision detection, reference validation, surface metadata, and acceptance tasks.",
92453
+ "path": "features/perchTerminal/agentPlatform/skills/skill-creator/SKILL.md",
92454
+ "body": "\n# skill-creator\n\nUse this skill when creating, updating, validating, or evaluating Perch skills.\n\n## Operating Rules\n\n- Keep each skill as one reusable workflow unit with `SKILL.md`, optional `references/`, optional `scripts/`, and optional `assets/`.\n- Frontmatter must include `name`, `description`, and Perch metadata for GUI and CLI surface support.\n- Trigger hints should be precise enough to activate the skill without colliding with unrelated skills.\n- Put detailed reusable rules in reference files and fragile deterministic checks in scripts.\n- Add at least one acceptance task for each supported surface when the skill is productized.\n\n## Verification\n\nRun skill codegen and registry tests after edits. Use `references/validation-checklist.md` before declaring a skill ready.",
92455
+ "surface": {
92456
+ "gui": "supported",
92457
+ "cli": "supported"
92458
+ },
92459
+ "triggerHints": [
92460
+ "skill creator",
92461
+ "create skill",
92462
+ "skill.md",
92463
+ "agent skill",
92464
+ "skill validation",
92465
+ "trigger collision",
92466
+ "skill test",
92467
+ "skill authoring",
92468
+ "perch skill"
92469
+ ],
92470
+ "companionSkills": [
92471
+ "workspace",
92472
+ "data-quality"
92473
+ ],
92474
+ "preferredTools": [
92475
+ "glob",
92476
+ "grep",
92477
+ "readLocalFile",
92478
+ "writeLocalFile",
92479
+ "bash"
92480
+ ]
92481
+ },
92482
+ {
92483
+ "id": "spreadsheets",
92484
+ "name": "spreadsheets",
92485
+ "description": "Create, inspect, clean, calculate, format, and verify spreadsheet artifacts such as XLSX, CSV, TSV, Google Sheets, formulas, pivots, charts, and reconciliation workbooks.",
92486
+ "path": "features/perchTerminal/agentPlatform/skills/spreadsheets/SKILL.md",
92487
+ "body": "\n# spreadsheets\n\nUse this skill when the user asks for a spreadsheet artifact, spreadsheet inspection, import-ready CSV, formulas, reconciliation tabs, exception tabs, or workbook-quality output.\n\n## Operating Rules\n\n- Profile incoming sheets before changing them: row counts, columns, types, key IDs, totals, and empty fields.\n- Preserve identifiers as text when they are invoice numbers, account numbers, vendor IDs, employee IDs, tickers, or other non-math values.\n- Put source values on input tabs and formulas on analysis tabs when producing XLSX workbooks.\n- Add checks for totals, row counts, formulas, and exception counts when the workbook drives a decision.\n- Use deterministic code or workbook inspection for fragile calculations; do not hand-wave formula behavior.\n\n## Verification\n\nBefore delivery, verify that the file exists, formulas are present where expected, totals reconcile, and CSV exports keep exact values. See `references/workbook-quality.md`.",
92488
+ "surface": {
92489
+ "gui": "supported",
92490
+ "cli": "supported"
92491
+ },
92492
+ "triggerHints": [
92493
+ "spreadsheet",
92494
+ "xlsx",
92495
+ "excel workbook",
92496
+ "csv",
92497
+ "tsv",
92498
+ "google sheet",
92499
+ "formula",
92500
+ "pivot",
92501
+ "reconciliation tab",
92502
+ "exception tab",
92503
+ "import template",
92504
+ "row count",
92505
+ "workbook check"
92506
+ ],
92507
+ "companionSkills": [
92508
+ "data-quality",
92509
+ "workspace",
92510
+ "pdf",
92511
+ "research"
92512
+ ],
92513
+ "preferredTools": [
92514
+ "inspectWorkbookSheets",
92515
+ "google_sheets_create",
92516
+ "google_sheets_append_rows",
92517
+ "writeLocalFile",
92518
+ "run_sandbox_code",
92519
+ "bash",
92520
+ "readLocalFile"
92521
+ ]
92522
+ },
92523
+ {
92524
+ "id": "workspace",
92525
+ "name": "workspace",
92526
+ "description": "Navigate, inspect, search, summarize, and safely edit local workspaces, attached folders, source inventories, project files, and codebases with exact file-grounded evidence.",
92527
+ "path": "features/perchTerminal/agentPlatform/skills/workspace/SKILL.md",
92528
+ "body": "\n# workspace\n\nUse this skill when the task requires local files, attached folders, repo search, safe edits, or source inventory.\n\n## Operating Rules\n\n- Start with inventory or targeted search before reading many files.\n- Cite exact files when answering codebase or workspace questions.\n- Distinguish direct filesystem reads, indexed local source retrieval, Supabase source packets, and Qdrant knowledge.\n- Do not say Perch cannot access a file path when the local runtime and permissions make it accessible.\n- For edits, keep changes scoped and preserve unrelated user work.\n\n## Verification\n\nCheck file existence, paths, and write results before reporting completion. Use `references/source-boundaries.md` to keep source lanes clear.",
92529
+ "surface": {
92530
+ "gui": "supported",
92531
+ "cli": "supported"
92532
+ },
92533
+ "triggerHints": [
92534
+ "repo",
92535
+ "codebase",
92536
+ "local folder",
92537
+ "workspace",
92538
+ "find file",
92539
+ "inspect folder",
92540
+ "search files",
92541
+ "edit file",
92542
+ "source inventory",
92543
+ "project files"
92544
+ ],
92545
+ "companionSkills": [
92546
+ "research",
92547
+ "pdf",
92548
+ "data-quality",
92549
+ "skill-creator"
92550
+ ],
92551
+ "preferredTools": [
92552
+ "glob",
92553
+ "grep",
92554
+ "readLocalFile",
92555
+ "writeLocalFile",
92556
+ "statPath",
92557
+ "listLocalSources",
92558
+ "readLocalSourceFile",
92559
+ "bash"
92560
+ ]
92561
+ }
92562
+ ];
92563
+ AGENT_SKILL_INDEX = /* @__PURE__ */ new Map();
92564
+ for (const skill of AGENT_SKILL_REGISTRY) {
92565
+ if (AGENT_SKILL_INDEX.has(skill.id)) {
92566
+ throw new Error(`generated registry duplicate skill id: ${skill.id}`);
92567
+ }
92568
+ AGENT_SKILL_INDEX.set(skill.id, skill);
92569
+ }
92570
+ }
92571
+ });
92572
+
92573
+ // features/perchTerminal/agentPlatform/skills/skillSelector.ts
92574
+ function normalizeText(input) {
92575
+ return input.toLowerCase().replace(/\s+/g, " ").trim();
92576
+ }
92577
+ function escapeRegExp2(input) {
92578
+ return input.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
92579
+ }
92580
+ function triggerMatches(text, trigger) {
92581
+ const normalized = normalizeText(trigger);
92582
+ if (!normalized) return false;
92583
+ return new RegExp(`(^|[^a-z0-9])${escapeRegExp2(normalized)}([^a-z0-9]|$)`, "i").test(text);
92584
+ }
92585
+ function skillMatches(skill, text) {
92586
+ if (skill.triggerHints.some((trigger) => triggerMatches(text, trigger))) {
92587
+ return true;
92588
+ }
92589
+ return (INTENT_PATTERNS[skill.id] ?? []).some(
92590
+ (pattern) => pattern.test(text)
92591
+ );
92592
+ }
92593
+ function isMarketDeskSignalTask(text, availableToolNames) {
92594
+ const hasMarketDeskTool = [...MARKET_DESK_TOOL_NAMES].some(
92595
+ (toolName) => availableToolNames.has(toolName)
92596
+ );
92597
+ return hasMarketDeskTool && /\b(market\s+desk|signal|strategy|backtest|track\s+record)\b/i.test(text) && /\b[A-Z]{1,5}\b/.test(text);
92598
+ }
92599
+ function selectionText(input) {
92600
+ return [
92601
+ input.userMessage,
92602
+ input.currentMode ?? "",
92603
+ input.personaId ?? "",
92604
+ input.selectedFolder ?? "",
92605
+ ...input.selectedFiles ?? [],
92606
+ input.sourceSummary ?? ""
92607
+ ].filter(Boolean).join("\n");
92608
+ }
92609
+ function canUseSkillOnSurface(skill, input, availableToolNames) {
92610
+ if (skill.surface[input.surface] === "unsupported") {
92611
+ return `${input.surface.toUpperCase()} surface is unsupported`;
92612
+ }
92613
+ if (skill.id === "browser-operator" && ![...availableToolNames].some((toolName) => BROWSER_TOOL_NAMES.has(toolName))) {
92614
+ return "browser operator tools are not available this turn";
92615
+ }
92616
+ return null;
92617
+ }
92618
+ function priorityIndex(skillId) {
92619
+ const index = CORE_PRIORITY.indexOf(skillId);
92620
+ return index === -1 ? CORE_PRIORITY.length : index;
92621
+ }
92622
+ function sortSkillIds(ids) {
92623
+ return [...ids].sort(
92624
+ (a, b2) => priorityIndex(a) - priorityIndex(b2) || a.localeCompare(b2)
92625
+ );
92626
+ }
92627
+ function selectAgentSkills(input) {
92628
+ const maxSelected = Math.max(
92629
+ 0,
92630
+ input.maxSelectedSkills ?? DEFAULT_MAX_SELECTED_SKILLS
92631
+ );
92632
+ const availableToolNames = new Set(input.availableToolNames ?? []);
92633
+ const text = selectionText(input);
92634
+ const normalizedText = normalizeText(text);
92635
+ const byId = new Map(AGENT_SKILL_REGISTRY.map((skill) => [skill.id, skill]));
92636
+ const metadataRows = AGENT_SKILL_REGISTRY.map((skill) => ({
92637
+ id: skill.id,
92638
+ name: skill.name,
92639
+ description: skill.description
92640
+ }));
92641
+ const omitted = [];
92642
+ const explicit = /* @__PURE__ */ new Set();
92643
+ for (const skill of AGENT_SKILL_REGISTRY) {
92644
+ if (skillMatches(skill, normalizedText)) explicit.add(skill.id);
92645
+ }
92646
+ if (/\bgoogle\s+docs?\b/i.test(text) && availableToolNames.has("browser_execute_task")) {
92647
+ explicit.add("documents");
92648
+ explicit.add("browser-operator");
92649
+ }
92650
+ if (/\b(this|attached|local)\s+folder\b/i.test(text) || Boolean(input.selectedFolder) && /\bfolder\b/i.test(text)) {
92651
+ explicit.add("workspace");
92652
+ }
92653
+ if (/\b(ap|accounts?\s+payable)\b/i.test(text)) {
92654
+ explicit.add("data-quality");
92655
+ }
92656
+ if (isMarketDeskSignalTask(text, availableToolNames)) {
92657
+ explicit.delete("research");
92658
+ omitted.push({
92659
+ id: "research",
92660
+ reason: "market-desk signal tasks should use Market Desk tools before public research"
92661
+ });
92662
+ }
92663
+ const companionExpanded = /* @__PURE__ */ new Set();
92664
+ for (const id of sortSkillIds(explicit)) {
92665
+ const skill = byId.get(id);
92666
+ if (!skill) continue;
92667
+ for (const companionId of skill.companionSkills) {
92668
+ if (!explicit.has(companionId)) companionExpanded.add(companionId);
92669
+ }
92670
+ }
92671
+ const selected = [];
92672
+ for (const id of [
92673
+ ...sortSkillIds(explicit),
92674
+ ...sortSkillIds(companionExpanded)
92675
+ ]) {
92676
+ const skill = byId.get(id);
92677
+ if (!skill) continue;
92678
+ const omittedReason = canUseSkillOnSurface(skill, input, availableToolNames);
92679
+ if (omittedReason) {
92680
+ omitted.push({ id, reason: omittedReason });
92681
+ continue;
92682
+ }
92683
+ if (selected.length >= maxSelected) {
92684
+ omitted.push({ id, reason: `max selected skills reached (${maxSelected})` });
92685
+ continue;
92686
+ }
92687
+ selected.push(skill);
92688
+ }
92689
+ return { metadataRows, selected, omitted };
92690
+ }
92691
+ var DEFAULT_MAX_SELECTED_SKILLS, BROWSER_TOOL_NAMES, CORE_PRIORITY, INTENT_PATTERNS;
92692
+ var init_skillSelector = __esm({
92693
+ "features/perchTerminal/agentPlatform/skills/skillSelector.ts"() {
92694
+ init_agentSkillRegistry();
92695
+ init_marketDeskAccess();
92696
+ DEFAULT_MAX_SELECTED_SKILLS = 4;
92697
+ BROWSER_TOOL_NAMES = /* @__PURE__ */ new Set([
92698
+ "browser_execute_task",
92699
+ "browser_observe",
92700
+ "browser_click",
92701
+ "browser_type",
92702
+ "browser_visual_click",
92703
+ "browser_focus_type",
92704
+ "browser_press_key",
92705
+ "browser_read",
92706
+ "browser_wait",
92707
+ "browser_navigate"
92708
+ ]);
92709
+ CORE_PRIORITY = [
92710
+ "data-quality",
92711
+ "presentations",
92712
+ "spreadsheets",
92713
+ "documents",
92714
+ "pdf",
92715
+ "research",
92716
+ "diagrams",
92717
+ "workspace",
92718
+ "browser-operator",
92719
+ "skill-creator"
92720
+ ];
92721
+ INTENT_PATTERNS = {
92722
+ "browser-operator": [
92723
+ /\b(browser|web\s+app|logged[-\s]?in|saas|portal|form)\b/i,
92724
+ /\b(gmail|google\s+docs?|google\s+calendar|notion|salesforce)\b/i
92725
+ ],
92726
+ documents: [
92727
+ /\b(docx|word\s+file|google\s+docs?|memo|letter|report|redline|tracked\s+changes?|policy|formal\s+artifact)\b/i,
92728
+ /\b(create|write|draft|revise|edit|publish|deliver)\b.{0,48}\b(document|doc|memo|report|letter)\b/i
92729
+ ],
92730
+ spreadsheets: [
92731
+ /\b(spreadsheet|xlsx|excel|workbook|csv|tsv|google\s+sheet|formula|pivot|reconciliation\s+tab|exception\s+tab|import\s+template)\b/i,
92732
+ /\b(row\s+count|totals?|tie[-\s]?out|sheet)\b/i
92733
+ ],
92734
+ presentations: [
92735
+ /\b(deck|pptx|powerpoint|slides?|presentation|board\s+deck|executive\s+deck|pitch\s+deck|sales\s+deck|speaker\s+notes?)\b/i
92736
+ ],
92737
+ pdf: [
92738
+ /\b(pdf|scanned\s+pdf|page\s+citation|pdf\s+table|split\s+pdf|merge\s+pdf|ocr|annual\s+report)\b/i,
92739
+ /\.pdf\b/i
92740
+ ],
92741
+ research: [
92742
+ /\b(research|credible\s+sources?|current\s+source|citations?|source\s+comparison|scholarly|public\s+web|fact[-\s]?check|latest|recent|filing)\b/i,
92743
+ /\b(find|gather|compare|verify)\b.{0,48}\b(sources?|evidence|filings?)\b/i
92744
+ ],
92745
+ diagrams: [
92746
+ /\b(diagram|mermaid|drawio|workflow\s+graph|org\s+chart|entity\s+network|control\s+graph|architecture\s+map|vendor[-\s]?risk\s+graph|nodes?\s+and\s+edges?)\b/i,
92747
+ /\b(graph|map)\b.{0,32}\b(risk|workflow|architecture|network|relationship)\b/i
92748
+ ],
92749
+ workspace: [
92750
+ /\b(repo|codebase|local\s+folder|workspace|find\s+file|inspect\s+folder|search\s+files?|edit\s+file|source\s+inventory|project\s+files?)\b/i,
92751
+ /\b(this\s+repo|this\s+folder|attached\s+folder|in\s+this\s+workspace)\b/i
92752
+ ],
92753
+ "data-quality": [
92754
+ /\b(data\s+quality|anomal(?:y|ies)|reconciliation|duplicate\s+(?:payment|invoice)|tie[-\s]?out|row\s+totals?|audit\s+exceptions?|vendor\s+risk|outliers?|missing\s+receipt|cost\s+anomal(?:y|ies))\b/i,
92755
+ /\b(audit|reconcile|profile|clean)\b.{0,56}\b(ap|accounts?\s+payable|payments?|invoices?|vendors?|data|folder)\b/i
92756
+ ],
92757
+ "skill-creator": [
92758
+ /\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
92759
+ ]
92760
+ };
92761
+ }
92762
+ });
92763
+
92164
92764
  // features/perchTerminal/runtime/context/tokenEstimates.ts
92165
92765
  function estimateTokensForLane(text, lane, modelTokenize) {
92166
92766
  return countTokensForLane(
@@ -92237,6 +92837,26 @@ function assembleContext(input) {
92237
92837
  const contextCompaction = input.threadId ? getThreadSessionFromMemory(input.threadId)?.contextCompaction ?? null : null;
92238
92838
  const threadSession = input.threadId ? getThreadSessionFromMemory(input.threadId) : null;
92239
92839
  const coreSystem = buildCoreSystemSection(input);
92840
+ const selectedSkillPack = selectAgentSkills({
92841
+ userMessage: input.trimmedInput,
92842
+ currentMode: input.chatMode,
92843
+ personaId: input.personaId ?? null,
92844
+ selectedFiles: [
92845
+ ...input.workspaceFileResolution?.matchedFiles.map(
92846
+ (file) => file.relativePath
92847
+ ) ?? [],
92848
+ ...(input.localSources ?? []).slice(0, 12).map((source) => source.relativePath || source.fileName)
92849
+ ],
92850
+ selectedFolder: input.activeRootPath,
92851
+ sourceSummary: [
92852
+ input.selectedSourceId ? `selected source ${input.selectedSourceId}` : "",
92853
+ input.folderIndexSummary ? `folder indexed ${input.folderIndexSummary.totalFiles} files` : "",
92854
+ input.selectedSourceBundle?.fileName ?? ""
92855
+ ].filter(Boolean).join("\n"),
92856
+ availableToolNames: input.enabledToolNames ?? [],
92857
+ surface: input.skillSelectionSurface ?? (input.cliLocalTools === true && !input.desktopConnected ? "cli" : "gui")
92858
+ });
92859
+ const selectedSkillsSection = buildSelectedSkillsSection(selectedSkillPack);
92240
92860
  const selectedModelSection = buildSelectedModelSection(
92241
92861
  input.selectedModel ?? null
92242
92862
  );
@@ -92300,6 +92920,7 @@ function assembleContext(input) {
92300
92920
  content: coreSystem,
92301
92921
  reason: "Core operator instructions for the current turn."
92302
92922
  },
92923
+ selectedSkillsSection,
92303
92924
  ...toolsJson ? [
92304
92925
  {
92305
92926
  id: "tool-schemas",
@@ -92401,6 +93022,7 @@ function assembleContext(input) {
92401
93022
  });
92402
93023
  const systemPrompt = systemSections.filter((row) => row.trim()).join("\n\n");
92403
93024
  const sectionBreakdown = buildSectionBreakdown(rows);
93025
+ const selectedSkillIds = selectedSkillPack.selected.map((skill) => skill.id);
92404
93026
  const totalContextTokens = rows.filter((row) => row.status !== "reserved").reduce((sum, row) => sum + row.estimatedTokens, 0);
92405
93027
  const compactedCount = rows.filter(
92406
93028
  (row) => row.status === "sent_compacted"
@@ -92439,7 +93061,9 @@ function assembleContext(input) {
92439
93061
  activeRootPath: input.activeRootPath,
92440
93062
  recentMessageCount: input.recentMessages.length,
92441
93063
  selectedModel: input.selectedModel ?? null,
92442
- untrustedContextPresent
93064
+ untrustedContextPresent,
93065
+ selectedSkills: selectedSkillIds,
93066
+ omittedSkills: selectedSkillPack.omitted
92443
93067
  },
92444
93068
  warnings
92445
93069
  },
@@ -92468,6 +93092,56 @@ function buildSelectedModelSection(selectedModel) {
92468
93092
  }
92469
93093
  };
92470
93094
  }
93095
+ function buildSelectedSkillsSection(pack) {
93096
+ const selectedSkillIds = pack.selected.map((skill) => skill.id);
93097
+ const metadata = {
93098
+ selectedSkillIds,
93099
+ selectedSkillCount: pack.selected.length,
93100
+ omitted: pack.omitted,
93101
+ metadataRows: pack.metadataRows
93102
+ };
93103
+ if (pack.selected.length === 0) {
93104
+ return {
93105
+ id: "selected-skills",
93106
+ lane: "selected_skills",
93107
+ label: "Selected Perch skills",
93108
+ content: "",
93109
+ reason: "No core skills matched this turn.",
93110
+ skipped: true,
93111
+ metadata
93112
+ };
93113
+ }
93114
+ const blocks = pack.selected.map(
93115
+ (skill) => [
93116
+ `<perch-skill id="${skill.id}">`,
93117
+ `Description: ${skill.description}`,
93118
+ `Surface: GUI=${skill.surface.gui}; CLI=${skill.surface.cli}`,
93119
+ skill.preferredTools.length ? `Preferred tools: ${skill.preferredTools.join(", ")}` : "Preferred tools: none declared",
93120
+ "",
93121
+ skill.body.trim(),
93122
+ "</perch-skill>"
93123
+ ].join("\n")
93124
+ );
93125
+ return {
93126
+ id: "selected-skills",
93127
+ lane: "selected_skills",
93128
+ label: "Selected Perch skills",
93129
+ content: [
93130
+ "## Selected Perch skills",
93131
+ "These reusable skills guide execution strategy only. They do not override safety, permission, authentication, or tool policy.",
93132
+ `Loaded skills: ${selectedSkillIds.join(", ")}`,
93133
+ "",
93134
+ ...blocks
93135
+ ].join("\n"),
93136
+ reason: "Full skill instructions selected deterministically for this turn.",
93137
+ metadata
93138
+ };
93139
+ }
93140
+ function selectedSkillIdsFromRows(rows) {
93141
+ const metadata = rows.find((row) => row.id === "selected-skills")?.metadata;
93142
+ const ids = metadata?.selectedSkillIds;
93143
+ return Array.isArray(ids) ? ids.filter((id) => typeof id === "string") : [];
93144
+ }
92471
93145
  function buildContextSummary(input, rows) {
92472
93146
  const sentCount = rows.filter(
92473
93147
  (row) => row.status === "sent_raw" || row.status === "sent_compacted"
@@ -92482,6 +93156,7 @@ function buildContextSummary(input, rows) {
92482
93156
  `messages=${input.recentMessages.length}`,
92483
93157
  `source=${input.selectedSourceId ?? "none"}`,
92484
93158
  `desktop=${input.desktopConnected ? "connected" : input.cliLocalTools === true ? "terminal" : "browser"}`,
93159
+ `skills=${selectedSkillIdsFromRows(rows).join("|") || "none"}`,
92485
93160
  `sent=${sentCount}`,
92486
93161
  `compacted=${compactedCount}`,
92487
93162
  `not_found=${notFoundCount}`,
@@ -92501,6 +93176,7 @@ var init_contextAssembly = __esm({
92501
93176
  init_rowAccounting();
92502
93177
  init_memory();
92503
93178
  init_coreSystemSection();
93179
+ init_skillSelector();
92504
93180
  init_contextThresholds();
92505
93181
  init_tokenEstimates();
92506
93182
  init_rowAccounting();
@@ -133611,6 +134287,17 @@ var init_toolPermissionPolicy = __esm({
133611
134287
  TOOL_NAMES.prepareAPEvidence,
133612
134288
  TOOL_NAMES.queryAPCases,
133613
134289
  TOOL_NAMES.renderAPControlGraph,
134290
+ TOOL_NAMES.runManagedPlaybook,
134291
+ TOOL_NAMES.runSuite,
134292
+ TOOL_NAMES.listSuiteCatalog,
134293
+ TOOL_NAMES.proposeSuitePlan,
134294
+ TOOL_NAMES.executeSuitePlan,
134295
+ TOOL_NAMES.proposeWork,
134296
+ TOOL_NAMES.executeWork,
134297
+ TOOL_NAMES.sendWorkerMessage,
134298
+ TOOL_NAMES.taskStop,
134299
+ TOOL_NAMES.spawnWorker,
134300
+ TOOL_NAMES.dispatchAgent,
133614
134301
  TOOL_NAMES.runSandboxCode
133615
134302
  ];
133616
134303
  CLI_LOCAL_TOOL_NAME_SET = new Set(CLI_LOCAL_TOOL_NAMES);
@@ -168151,7 +168838,7 @@ function template(text, settings, oldSettings) {
168151
168838
  var index = 0;
168152
168839
  var source = "__p+='";
168153
168840
  text.replace(matcher2, function(match, escape4, interpolate, evaluate, offset) {
168154
- source += text.slice(index, offset).replace(escapeRegExp2, escapeChar);
168841
+ source += text.slice(index, offset).replace(escapeRegExp3, escapeChar);
168155
168842
  index = offset + match.length;
168156
168843
  if (escape4) {
168157
168844
  source += "'+\n((__t=(" + escape4 + "))==null?'':_.escape(__t))+\n'";
@@ -168186,7 +168873,7 @@ function template(text, settings, oldSettings) {
168186
168873
  template2.source = "function(" + argument + "){\n" + source + "}";
168187
168874
  return template2;
168188
168875
  }
168189
- var noMatch, escapes, escapeRegExp2, bareIdentifier;
168876
+ var noMatch, escapes, escapeRegExp3, bareIdentifier;
168190
168877
  var init_template = __esm({
168191
168878
  "node_modules/underscore/modules/template.js"() {
168192
168879
  init_defaults();
@@ -168201,7 +168888,7 @@ var init_template = __esm({
168201
168888
  "\u2028": "u2028",
168202
168889
  "\u2029": "u2029"
168203
168890
  };
168204
- escapeRegExp2 = /\\|'|\r|\n|\u2028|\u2029/g;
168891
+ escapeRegExp3 = /\\|'|\r|\n|\u2028|\u2029/g;
168205
168892
  bareIdentifier = /^\s*(\w|\$)+\s*$/;
168206
168893
  }
168207
168894
  });
@@ -199949,7 +200636,6 @@ function containsBrowserDeliveryTask(tasks) {
199949
200636
  var BROWSER_DELIVERY_ROLE_IDS;
199950
200637
  var init_browserDeliveryLock = __esm({
199951
200638
  "features/perchTerminal/agentPlatform/browserDeliveryLock.ts"() {
199952
- "use strict";
199953
200639
  BROWSER_DELIVERY_ROLE_IDS = /* @__PURE__ */ new Set([
199954
200640
  "doc_writer",
199955
200641
  "email_sender",
@@ -201862,7 +202548,7 @@ function lifecycleState(ctx) {
201862
202548
  }
201863
202549
  return state;
201864
202550
  }
201865
- function normalizeText(value) {
202551
+ function normalizeText2(value) {
201866
202552
  if (typeof value === "string") return value.toLowerCase().replace(/\s+/g, " ").trim();
201867
202553
  if (value === null || value === void 0) return "";
201868
202554
  try {
@@ -201873,7 +202559,7 @@ function normalizeText(value) {
201873
202559
  }
201874
202560
  function explicitRetryRequested(tasks) {
201875
202561
  const text = tasks.map((task) => `${task.objective}
201876
- ${normalizeText(task.context)}`).join("\n");
202562
+ ${normalizeText2(task.context)}`).join("\n");
201877
202563
  return /\b(retry|re-run|rerun|run again|fresh pass|fresh read|repeat|redo)\b/i.test(text);
201878
202564
  }
201879
202565
  function isSourceReaderLifecycleRole(roleId) {
@@ -201884,7 +202570,7 @@ function isSourceReaderLifecycleRole(roleId) {
201884
202570
  }
201885
202571
  function extractSourceScopes(task) {
201886
202572
  const text = `${task.objective}
201887
- ${normalizeText(task.context)}`;
202573
+ ${normalizeText2(task.context)}`;
201888
202574
  const scopes = /* @__PURE__ */ new Set();
201889
202575
  const pathPattern = /\b(?:\d{2}-[a-z0-9_-]+|[a-z0-9_-]+\/[a-z0-9_ .&/-]+)(?:\/[a-z0-9_ .&-]+)*/gi;
201890
202576
  for (const match of text.matchAll(pathPattern)) {
@@ -201894,14 +202580,14 @@ ${normalizeText(task.context)}`;
201894
202580
  }
201895
202581
  if (raw.length >= 4) scopes.add(raw.toLowerCase());
201896
202582
  }
201897
- if (scopes.size === 0) scopes.add(normalizeText(task.objective).slice(0, 120));
202583
+ if (scopes.size === 0) scopes.add(normalizeText2(task.objective).slice(0, 120));
201898
202584
  return [...scopes].sort();
201899
202585
  }
201900
202586
  function taskSignature(task) {
201901
202587
  return JSON.stringify({
201902
202588
  roleId: task.roleId,
201903
- objective: normalizeText(task.objective),
201904
- context: normalizeText(task.context)
202589
+ objective: normalizeText2(task.objective),
202590
+ context: normalizeText2(task.context)
201905
202591
  });
201906
202592
  }
201907
202593
  function batchSignature(tasks) {
@@ -217660,7 +218346,7 @@ function isBrowserSnapshotToolResult(messages, index) {
217660
218346
  const toolName = toolNameForMessage(messages, index);
217661
218347
  const text = messageContentText(msg);
217662
218348
  if (!text.includes("snapshot")) return false;
217663
- if (toolName && BROWSER_TOOL_NAMES.has(toolName)) return true;
218349
+ if (toolName && BROWSER_TOOL_NAMES2.has(toolName)) return true;
217664
218350
  try {
217665
218351
  const parsed = JSON.parse(text);
217666
218352
  return typeof parsed.snapshot === "string" && parsed.snapshot.length > 200;
@@ -218135,7 +218821,7 @@ async function prepareLoopMessagesForSend(input) {
218135
218821
  microcompactedToolResults
218136
218822
  };
218137
218823
  }
218138
- var OPERATOR_SCREENSHOT_MARKER2, KEPT_SNAPSHOT_MAX_CHARS, KEPT_SNAPSHOT_TRUNCATED_MARKER, BROWSER_TOOL_NAMES, RECENT_MESSAGES_KEEP_COUNT, RECENT_TOOL_RESULTS_KEEP_COUNT, POST_COMPACT_TARGET_TOKENS, POST_COMPACT_MIN_TARGET_TOKENS, POST_COMPACT_MAX_TARGET_TOKENS, POST_COMPACT_RECENT_TAIL_MIN_TOKENS, POST_COMPACT_RECENT_TAIL_MAX_TOKENS, MICROCOMPACT_CLEARED_MARKER, COMPACT_SUMMARY_RESERVE_TOKENS, COMPACT_SYSTEM_PROMPT;
218824
+ var OPERATOR_SCREENSHOT_MARKER2, KEPT_SNAPSHOT_MAX_CHARS, KEPT_SNAPSHOT_TRUNCATED_MARKER, BROWSER_TOOL_NAMES2, RECENT_MESSAGES_KEEP_COUNT, RECENT_TOOL_RESULTS_KEEP_COUNT, POST_COMPACT_TARGET_TOKENS, POST_COMPACT_MIN_TARGET_TOKENS, POST_COMPACT_MAX_TARGET_TOKENS, POST_COMPACT_RECENT_TAIL_MIN_TOKENS, POST_COMPACT_RECENT_TAIL_MAX_TOKENS, MICROCOMPACT_CLEARED_MARKER, COMPACT_SUMMARY_RESERVE_TOKENS, COMPACT_SYSTEM_PROMPT;
218139
218825
  var init_contextLoopCompaction = __esm({
218140
218826
  "features/perchTerminal/runtime/services/contextLoopCompaction.ts"() {
218141
218827
  "use strict";
@@ -218150,7 +218836,7 @@ var init_contextLoopCompaction = __esm({
218150
218836
  OPERATOR_SCREENSHOT_MARKER2 = "[operator-screen]";
218151
218837
  KEPT_SNAPSHOT_MAX_CHARS = 12e3;
218152
218838
  KEPT_SNAPSHOT_TRUNCATED_MARKER = "[snapshot truncated]";
218153
- BROWSER_TOOL_NAMES = /* @__PURE__ */ new Set([
218839
+ BROWSER_TOOL_NAMES2 = /* @__PURE__ */ new Set([
218154
218840
  TOOL_NAMES.browserNavigate,
218155
218841
  TOOL_NAMES.browserObserve,
218156
218842
  TOOL_NAMES.browserClick,
@@ -221650,7 +222336,7 @@ var init_preTurnRetrieval = __esm({
221650
222336
  // features/perchTerminal/rag/chunking.ts
221651
222337
  import { createHash as createHash2 } from "crypto";
221652
222338
  function buildRetrievalChunks(text, metadata, options = {}) {
221653
- const normalized = normalizeText2(text);
222339
+ const normalized = normalizeText3(text);
221654
222340
  if (!normalized) return [];
221655
222341
  const targetChars = Math.max(400, (options.targetTokens ?? DEFAULT_TARGET_TOKENS) * APPROX_CHARS_PER_TOKEN);
221656
222342
  const overlapChars = Math.max(0, Math.min(targetChars / 2, (options.overlapTokens ?? DEFAULT_OVERLAP_TOKENS) * APPROX_CHARS_PER_TOKEN));
@@ -221680,7 +222366,7 @@ function buildRetrievalChunks(text, metadata, options = {}) {
221680
222366
  return chunk2;
221681
222367
  });
221682
222368
  }
221683
- function normalizeText2(text) {
222369
+ function normalizeText3(text) {
221684
222370
  return text.replace(/\r\n/g, "\n").replace(/[ \t]+/g, " ").replace(/\n[ \t]+/g, "\n").replace(/\n{3,}/g, "\n\n").trim();
221685
222371
  }
221686
222372
  function stableContentHash(value) {
@@ -224323,6 +225009,8 @@ function buildClientContextSnapshot(input) {
224323
225009
  compacted: context.snapshot.compacted,
224324
225010
  chatMode: input.input.chatMode,
224325
225011
  selectedSourceId: input.input.selectedSourceId,
225012
+ selectedSkills: extractSelectedSkills(context),
225013
+ omittedSkills: extractOmittedSkills(context),
224326
225014
  desktopConnected: input.input.desktopConnected,
224327
225015
  cliLocalTools: input.input.cliLocalTools === true,
224328
225016
  sectionBreakdown: context.snapshot.sectionBreakdown,
@@ -224361,6 +225049,23 @@ function buildClientContextSnapshot(input) {
224361
225049
  } : {}
224362
225050
  };
224363
225051
  }
225052
+ function extractSelectedSkills(context) {
225053
+ const metadata = context.snapshot.rows.find(
225054
+ (row) => row.id === "selected-skills"
225055
+ )?.metadata;
225056
+ const ids = metadata?.selectedSkillIds;
225057
+ return Array.isArray(ids) ? ids.filter((id) => typeof id === "string") : [];
225058
+ }
225059
+ function extractOmittedSkills(context) {
225060
+ const metadata = context.snapshot.rows.find(
225061
+ (row) => row.id === "selected-skills"
225062
+ )?.metadata;
225063
+ const omitted = metadata?.omitted;
225064
+ if (!Array.isArray(omitted)) return [];
225065
+ return omitted.filter(
225066
+ (item) => typeof item === "object" && item !== null && typeof item.id === "string" && typeof item.reason === "string"
225067
+ );
225068
+ }
224364
225069
  var init_turnContextSnapshot = __esm({
224365
225070
  "features/perchTerminal/runtime/turn/turnContextSnapshot.ts"() {
224366
225071
  "use strict";
@@ -283119,6 +283824,10 @@ ${HELP_TEXT}`);
283119
283824
  if (parsed.domain === "auth") {
283120
283825
  return runAuthCommand(parsed, writer);
283121
283826
  }
283827
+ if (parsed.domain === "skills") {
283828
+ writer.stdout(renderCliSkills(parsed.name));
283829
+ return 0;
283830
+ }
283122
283831
  if (parsed.domain === "run") {
283123
283832
  const connectModelProxy = deps.connectModelProxy ?? connectCliModelProxy;
283124
283833
  const connection = await connectModelProxy({ appUrl: parsed.appUrl });
@@ -283225,6 +283934,7 @@ function parsePerchCli(argv) {
283225
283934
  if (domain === "login") return { ok: true, domain: "auth", action: "login", ...global2.appUrl ? { appUrl: global2.appUrl } : {} };
283226
283935
  if (domain === "logout") return { ok: true, domain: "auth", action: "logout" };
283227
283936
  if (domain === "status") return { ok: true, domain: "auth", action: "status" };
283937
+ if (domain === "skills") return { ok: true, domain: "skills", ...action ? { name: action } : {} };
283228
283938
  if (domain !== "ap" && domain !== "test") return { ok: false, error: `Unknown domain: ${domain ?? "(missing)"}` };
283229
283939
  if (domain === "test") {
283230
283940
  if (action !== "ap") return { ok: false, error: `Unknown test command: ${action ?? "(missing)"}` };
@@ -284259,6 +284969,9 @@ async function runInteractiveSlashCommand(input) {
284259
284969
  ));
284260
284970
  return "continue";
284261
284971
  }
284972
+ case "skills":
284973
+ input.writer.stdout(renderCliSkills(parsed.value));
284974
+ return "continue";
284262
284975
  case "context":
284263
284976
  input.writer.stdout(renderCliContextDetails(input.state.contextSnapshot));
284264
284977
  return "continue";
@@ -284378,6 +285091,8 @@ function parseInteractiveSlashCommand(input) {
284378
285091
  return { ok: true, kind: "help" };
284379
285092
  case "status":
284380
285093
  return { ok: true, kind: "status" };
285094
+ case "skills":
285095
+ return { ok: true, kind: "skills", ...value ? { value } : {} };
284381
285096
  case "context":
284382
285097
  return { ok: true, kind: "context" };
284383
285098
  case "memory-audit":
@@ -284433,6 +285148,7 @@ function renderInteractiveStatus(state, connection, session, workspaceId) {
284433
285148
  ["connection", connectionStatus],
284434
285149
  ["memory", renderCliMemoryAvailability(state.cwd, workspaceId ?? null, signedIn)],
284435
285150
  ["tools", renderCliCapabilityCount(state, workspaceId ?? null)],
285151
+ ["skills", renderCliLoadedSkills(state.contextSnapshot)],
284436
285152
  ["permission", state.permissionMode],
284437
285153
  ["mode", state.chatMode],
284438
285154
  ["persona", state.personaId],
@@ -284463,6 +285179,43 @@ function renderCliCapabilityCount(state, workspaceId) {
284463
285179
  }).length;
284464
285180
  return `${count} visible`;
284465
285181
  }
285182
+ function renderCliLoadedSkills(snapshot) {
285183
+ const skills = snapshot?.selectedSkills ?? [];
285184
+ return skills.length > 0 ? skills.join(", ") : "none loaded";
285185
+ }
285186
+ function renderCliSkills(name) {
285187
+ const color = shouldUseCliColor();
285188
+ if (name) {
285189
+ const skill = getAgentSkillManifest(name);
285190
+ if (!skill) {
285191
+ return `Unknown skill: ${name}
285192
+ Run /skills to list available skills.
285193
+ `;
285194
+ }
285195
+ return [
285196
+ `${paint(skill.name, "accent", color)}`,
285197
+ skill.description,
285198
+ `surface: GUI=${skill.surface.gui}; CLI=${skill.surface.cli}`,
285199
+ skill.triggerHints.length ? `triggers: ${skill.triggerHints.join(", ")}` : "triggers: none",
285200
+ skill.companionSkills.length ? `companions: ${skill.companionSkills.join(", ")}` : "companions: none",
285201
+ "",
285202
+ skill.body.trim(),
285203
+ ""
285204
+ ].join("\n");
285205
+ }
285206
+ const rows = listAgentSkillMetadataRows();
285207
+ return [
285208
+ "Perch core skills",
285209
+ ...rows.map(
285210
+ (skill) => [
285211
+ `- ${skill.id}`,
285212
+ ` ${skill.description}`,
285213
+ ` surface: GUI=${skill.surface.gui}; CLI=${skill.surface.cli}`
285214
+ ].join("\n")
285215
+ ),
285216
+ ""
285217
+ ].join("\n");
285218
+ }
284466
285219
  function renderInteractiveStartup(state, connection) {
284467
285220
  const color = shouldUseCliColor();
284468
285221
  const auth = renderCliAuthSummary(connection);
@@ -285514,6 +286267,7 @@ var init_perch_cli = __esm({
285514
286267
  init_learningMemory();
285515
286268
  init_toolDefinitions();
285516
286269
  init_nodeLocalBridge();
286270
+ init_agentSkillRegistry();
285517
286271
  execFileAsync3 = promisify3(execFile3);
285518
286272
  DEFAULT_CLI_LOGIN_APP_URL = "https://app.perchai.app";
285519
286273
  CLI_PACKAGE_VERSION = readCliPackageVersion();
@@ -285547,6 +286301,7 @@ Usage:
285547
286301
  perch login
285548
286302
  perch status
285549
286303
  perch logout
286304
+ perch skills [name]
285550
286305
  perch run "<task>" [--json] [--thread <id>] [--cwd <dir>] [--mode ask|agents|plan] [--persona saffron|quill]
285551
286306
  perch ap evidence <folder> [--json] [--out <dir>] [--timestamp <id>]
285552
286307
  perch ap packet <folder> [--json] [--out <dir>] [--timestamp <id>]
@@ -285557,6 +286312,7 @@ Commands:
285557
286312
  login Open browser sign-in and save this terminal's Perch session.
285558
286313
  status Show the saved CLI auth/app connection status.
285559
286314
  logout Clear the saved CLI auth session.
286315
+ skills List Perch core skills, or show one skill body.
285560
286316
  run Run a natural-language Perch turn through the shared runtime.
285561
286317
  ap evidence Prepare AP evidence, cases, metrics, and graph artifacts.
285562
286318
  ap packet Generate the full AP audit packet and report artifacts.
@@ -285566,6 +286322,7 @@ Commands:
285566
286322
  While Perch is working: type steering text and press Enter; Ctrl-C stops; Ctrl-E expands details.
285567
286323
  /help Show this list.
285568
286324
  /status Show cwd, auth, mode, persona, permission, and thread.
286325
+ /skills [name] List Perch core skills, or show one skill body.
285569
286326
  /cwd [dir] Show or change the working directory.
285570
286327
  /permission [mode] Show or set default, auto_review, take_the_wheel, or plan.
285571
286328
  /permissions [mode] Alias for /permission.
@@ -285616,6 +286373,7 @@ Commands:
285616
286373
  ];
285617
286374
  PERCH_SPLASH_COMMANDS = [
285618
286375
  ["/status", "show auth, route, tools, and thread"],
286376
+ ["/skills", "show reusable operator skills"],
285619
286377
  ["/persona", "swap saffron or quill"],
285620
286378
  ["/permission", "change autonomy for the next turns"],
285621
286379
  ["/login", "connect your Perch account"]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "perchai-cli",
3
- "version": "2.4.22",
3
+ "version": "2.4.23",
4
4
  "description": "Perch AI command-line interface",
5
5
  "bin": {
6
6
  "perch": "bin/perch"