diff --git a/CHANGELOG.md b/CHANGELOG.md index 272d5d1..79c39d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,24 @@ # Changelog +## 0.14.0 — 2026-06-19 + +New features and fixes across channels, memory, and usage tracking. + +**Discord channel** +- New Discord channel adapter (Gateway over WebSocket + REST), matching the Slack/Telegram model: `@agent-name` routing, `/agents` slash command, image attachments, allowlist on the authenticated sender, and reconnect/resume. See `DISCORD_SETUP.md`. + +**Per-task & heartbeat channel delivery** +- Any scheduled/manual task can post its full output to a channel via `channel:` (broadcast/DM) and `channel_target:` (a specific Discord/Slack channel id or Telegram chat id). Heartbeats gain the same `channel_target`. New `sendToTarget` on all three transports, with task/agent form fields. + +**Reflection visibility** +- Memory reflection now shows live in the overview "Active Agents" card and writes a run log to Recent Activity with its full output, so you can see what it consolidated. Fixed duplicate pinned-preference accumulation (reflection now trusts the model's consolidated pins). + +**Comprehensive token & cost tracking** +- The dashboard "Tokens Used" and per-agent "Total Tokens" now include chat and channel turns (a new usage ledger), not just tasks/heartbeats. Cost uses the CLI's reported amount with a per-model pricing-table fallback. + +**Chat** +- Enter sends a message; Shift+Enter inserts a newline (IME-safe). + ## 0.13.6 — 2026-06-14 Code-quality cleanup from the community review (all non-blocking). No user-facing behavior changes. diff --git a/DISCORD_SETUP.md b/DISCORD_SETUP.md new file mode 100644 index 0000000..2934646 --- /dev/null +++ b/DISCORD_SETUP.md @@ -0,0 +1,101 @@ +# Discord Setup Guide + +Connect your Agent Fleet agents to Discord so you can chat with them from your phone, desktop, or anywhere Discord runs — in servers, channels, threads, or DMs. + +--- + +## Prerequisites + +Before you start, make sure you have: + +- **Agent Fleet plugin** installed and running in Obsidian +- **At least one agent** configured in your fleet (the default Fleet Orchestrator works) +- **A Discord server** where you have permission to add bots (your own server or admin access) + +--- + +## Part 1: Create the Discord Application + +### Step 1 — Create a new application + +1. Go to **https://discord.com/developers/applications** +2. Click **New Application** +3. **Name:** `Agent Fleet` (or any name you prefer) +4. Accept the terms and click **Create** + +### Step 2 — Create the bot and copy its token + +1. In the left sidebar, click **Bot** +2. Click **Reset Token** (or **Add Bot** on older UIs) and confirm +3. **Copy the token** — this is your **Bot Token**. Save it somewhere safe; you won't see it again without resetting. + +### Step 3 — Enable the Message Content intent (required) + +The bot can't read message text without this privileged intent. + +1. Still on the **Bot** page, scroll to **Privileged Gateway Intents** +2. Toggle **Message Content Intent** → ON +3. Save changes + +> If you skip this, the bot connects but every message arrives with empty text, and the Gateway may close with error code `4014`. The plugin logs a clear hint in the console when this happens. + +### Step 4 — Invite the bot to your server + +1. In the left sidebar, open **OAuth2 → URL Generator** +2. Under **Scopes**, check: + - `bot` + - `applications.commands` (for the `/agents` slash command) +3. Under **Bot Permissions**, check: + - **Send Messages** + - **Send Messages in Threads** + - **Read Message History** + - **Use Slash Commands** +4. Copy the generated URL at the bottom, open it in your browser, pick your server, and click **Authorize**. + +--- + +## Part 2: Configure Agent Fleet + +### Step 5 — Add the credential + +1. In Obsidian, open **Settings → Agent Fleet → Channel Credentials** +2. Under **Add a channel credential**: + - **Reference name:** `discord-creds` (any name; referenced by `credential_ref` in the channel file) + - **Type:** `Discord` + - **Bot token:** paste the token from Step 2 +3. Click **Add credential** + +### Step 6 — Create the channel + +1. Open the Agent Fleet dashboard → **Channels** → **New Channel** +2. Fill in: + - **Name:** e.g. `my-discord` + - **Type:** `discord` + - **Credential:** `discord-creds` + - **Default agent:** the agent to use when no `@agent-name` prefix is given + - **Allowed agents:** (optional) the agents reachable via `@agent-name` or `/agents` + - **Allowed users:** your numeric Discord user ID(s), one per line. Only listed users can reach the bot. (Enable Developer Mode in Discord → right-click your name → **Copy User ID**.) +3. Save. The channel should show **connected** in the dashboard within a few seconds. + +--- + +## Using it + +- **Mention or DM the bot.** In a server channel, `@Agent Fleet do the thing` (or just message it in a DM). +- **Pick an agent per conversation.** Prefix a message with `@agent-name ...`, or run the **`/agents`** slash command and click a button. The choice sticks for that channel/thread until you change it. + - Global slash commands can take up to ~1 hour to appear the first time after the bot joins. +- **Threads and channels are isolated.** Each channel or thread keeps its own conversation and agent binding. +- **Images.** Attach an image and the agent receives it (saved into your vault). +- **Long replies** are automatically split to fit Discord's 2000-character limit. +- **Heartbeats.** If an agent's `heartbeatChannel` points at this Discord channel, the heartbeat result is delivered as a DM to the first allowed user. + +--- + +## Troubleshooting + +| Symptom | Fix | +|---------|-----| +| Channel stuck on **needs-auth** | Check the bot token; ensure the **Message Content intent** is enabled (Step 3). | +| Bot replies but ignores your text / empty messages | Message Content intent is off — enable it and reconnect. | +| `/agents` command missing | Global commands can take up to ~1h to propagate; ensure the bot was invited with the `applications.commands` scope. | +| Bot doesn't respond at all | Confirm your numeric user ID is in **Allowed users**, and the bot has Send Messages / Read Message History in that channel. | diff --git a/README.md b/README.md index 45eda78..3bc20eb 100644 --- a/README.md +++ b/README.md @@ -270,6 +270,33 @@ Chat with your agents from Telegram — simpler setup than Slack, no @mention re --- +### Discord + +Chat with your agents from Discord — in servers, channels, threads, or DMs. + +> **📖 [Step-by-step Discord setup guide →](DISCORD_SETUP.md)** — complete walkthrough from creating the Discord app to sending your first message. + +**Setup:** +1. Create an application at [discord.com/developers](https://discord.com/developers/applications), add a bot, and copy its token +2. **Enable the Message Content privileged intent** (required to read messages) +3. Invite the bot with the `bot` + `applications.commands` scopes +4. Add the token in Settings → Agent Fleet → Channel Credentials (type: Discord) +5. Create a channel via the dashboard or as `_fleet/channels/my-discord.md` +6. Mention or DM the bot in Discord + +**Features:** +- **Gateway over WebSocket** — outbound connection, works behind NAT/firewalls, no public URL; hand-rolled over `ws`, no `discord.js` dependency +- **Servers, threads & DMs** — every channel or thread is an isolated conversation with its own agent binding +- **Typing indicator** — native typing dots while the agent works (auto-refreshed) +- **Button agent picker** — the `/agents` slash command shows interactive buttons to switch agents; `@agent-name` prefix routing also works +- **Image attachments** — images are downloaded into your vault and passed to the agent +- **Session persistence** — conversations survive Obsidian restarts via the backend's resume (Claude `--resume` / Codex `exec resume`) +- **Allowlist** — only approved Discord users (by numeric ID) can reach the bot +- **2000-char message splitting** — long replies automatically chunked at paragraph boundaries +- **Auto-reconnect & resume** — Gateway RESUME on transient drops with exponential backoff + +--- + ### Interactive Chat The chat panel is a first-class Obsidian view — dock it in the sidebar, center, or any split. @@ -549,6 +576,7 @@ An autonomous periodic run — what an agent does on a schedule without user inp ## Links - [Slack Setup Guide](SLACK_SETUP.md) +- [Discord Setup Guide](DISCORD_SETUP.md) - [Releases](https://github.com/denberek/obsidian-agent-fleet/releases) - [npm package](https://www.npmjs.com/package/obsidian-agent-fleet) - [Report Issues](https://github.com/denberek/obsidian-agent-fleet/issues) diff --git a/defaults/skills/agent-fleet-system/references.md b/defaults/skills/agent-fleet-system/references.md index e620b97..22d7b95 100644 --- a/defaults/skills/agent-fleet-system/references.md +++ b/defaults/skills/agent-fleet-system/references.md @@ -73,7 +73,7 @@ These are inherited by all agent processes. Never store tokens in vault files. | Defined in | HEARTBEAT.md in agent folder | _fleet/tasks/.md | | Prompt source | Heartbeat body | Task body | | "Run Now" button | Uses heartbeat instruction | Uses task prompt | -| Delivery | Optional Slack channel post | Run log only | +| Delivery | Run log + optional channel post (`channel` in HEARTBEAT.md) | Run log + optional channel post (`channel` in task frontmatter) | | Scope | One per agent | Many per agent | | Best for | Autonomous periodic monitoring | Specific scheduled work items | diff --git a/defaults/skills/agent-fleet-system/tools.md b/defaults/skills/agent-fleet-system/tools.md index 0a17516..ff250b3 100644 --- a/defaults/skills/agent-fleet-system/tools.md +++ b/defaults/skills/agent-fleet-system/tools.md @@ -141,6 +141,9 @@ enabled: true schedule: "0 */6 * * *" # Cron expression — every 6 hours notify: true # Show Obsidian notice on completion channel: my-slack # Post results to this channel (optional) +channel_target: "" # Specific channel/conversation id within `channel` to post to + # (e.g. a Discord channel id). Empty = broadcast as a DM to the + # channel's first allowed user. Quote numeric ids to preserve precision. --- Check the following endpoints for availability and response time: @@ -156,6 +159,8 @@ a one-line "all clear". Use [REMEMBER] to track trends across heartbeats. - `schedule` — cron expression (same format as tasks) - `notify` — show Obsidian notice when heartbeat completes (default true) - `channel` — name of a configured channel to post results to (optional) +- `channel_target` — specific channel/conversation id within `channel` to post to (optional; + empty broadcasts a DM to the channel's first allowed user). Mirrors a task's `channel_target`. ## Creating a Channel @@ -230,6 +235,12 @@ model: "" # Override agent model for this task only. # Use aliases like "haiku" for cheap/simple tasks, # or leave empty to inherit agent's model. # Resolution order: task.model → agent.model → settings.defaultModel. +channel: "" # Post this task's output to a channel (e.g. "my-discord"). + # Empty = run log only. Lets scheduled tasks deliver to chat, + # not just the heartbeat. +channel_target: "" # Optional destination id within `channel`: a Discord/Slack + # channel id or Telegram chat id. Set = post directly to that + # channel; empty = DM the channel's first allowed user. tags: - monitoring --- @@ -238,6 +249,23 @@ Task prompt goes here. This is what the agent should do each run. Be specific and clear about expected output. ``` +**Channel delivery (`channel` + `channel_target`).** Any task can post its output to a +configured channel by setting `channel:` to a channel name. Previously only an agent's +heartbeat could post to a channel; now every scheduled/manual task can too. The output +is the task run's **full** output text, prefixed with `**`. + +Two delivery modes: +- **`channel_target` empty** → broadcast (a DM to the channel's first allowed user) — + same path the heartbeat uses. +- **`channel_target` set** → post directly to that specific channel/conversation. The + target is a transport-native id: a Discord/Slack channel id or a Telegram chat id. + (Discord: enable Developer Mode → right-click the channel → Copy Channel ID. The bot + must have permission to post there.) + +Keep the task prompt's output concise/skimmable if it's going to chat. Heartbeats still +use `channel` in HEARTBEAT.md (broadcast only); tasks use `channel`/`channel_target` in +their own frontmatter. + ### Task Types - **recurring** — runs on a cron schedule. Requires `schedule` field. - **once** — runs at a specific time. Requires `run_at` field (ISO datetime). diff --git a/main.js b/main.js index 4441eda..f97f1d3 100644 --- a/main.js +++ b/main.js @@ -1,10 +1,10 @@ -"use strict";var Ho=Object.create;var Bs=Object.defineProperty;var qo=Object.getOwnPropertyDescriptor;var zo=Object.getOwnPropertyNames;var Go=Object.getPrototypeOf,Vo=Object.prototype.hasOwnProperty;var qe=(i,t)=>()=>(t||i((t={exports:{}}).exports,t),t.exports),Yo=(i,t)=>{for(var e in t)Bs(i,e,{get:t[e],enumerable:!0})},Ka=(i,t,e,s)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of zo(t))!Vo.call(i,n)&&n!==e&&Bs(i,n,{get:()=>t[n],enumerable:!(s=qo(t,n))||s.enumerable});return i};var ze=(i,t,e)=>(e=i!=null?Ho(Go(i)):{},Ka(t||!i||!i.__esModule?Bs(e,"default",{value:i,enumerable:!0}):e,i)),Ko=i=>Ka(Bs({},"__esModule",{value:!0}),i);var mt=qe((Uu,br)=>{"use strict";var vr=["nodebuffer","arraybuffer","fragments"],wr=typeof Blob<"u";wr&&vr.push("blob");br.exports={BINARY_TYPES:vr,CLOSE_TIMEOUT:3e4,EMPTY_BUFFER:Buffer.alloc(0),GUID:"258EAFA5-E914-47DA-95CA-C5AB0DC85B11",hasBlob:wr,kForOnEventAttribute:Symbol("kIsForOnEventAttribute"),kListener:Symbol("kListener"),kStatusCode:Symbol("status-code"),kWebSocket:Symbol("websocket"),NOOP:()=>{}}});var Ss=qe(($u,kn)=>{"use strict";var{EMPTY_BUFFER:rc}=mt(),wa=Buffer[Symbol.species];function oc(i,t){if(i.length===0)return rc;if(i.length===1)return i[0];let e=Buffer.allocUnsafe(t),s=0;for(let n=0;n{"use strict";var Sr=Symbol("kDone"),ka=Symbol("kRun"),xa=class{constructor(t){this[Sr]=()=>{this.pending--,this[ka]()},this.concurrency=t||1/0,this.jobs=[],this.pending=0}add(t){this.jobs.push(t),this[ka]()}[ka](){if(this.pending!==this.concurrency&&this.jobs.length){let t=this.jobs.shift();this.pending++,t(this[Sr])}}};Cr.exports=xa});var Jt=qe((Wu,Pr)=>{"use strict";var Cs=require("zlib"),_r=Ss(),cc=Tr(),{kStatusCode:Ar}=mt(),dc=Buffer[Symbol.species],hc=Buffer.from([0,0,255,255]),Sn=Symbol("permessage-deflate"),ft=Symbol("total-length"),Yt=Symbol("callback"),kt=Symbol("buffers"),Kt=Symbol("error"),xn,Sa=class{constructor(t){if(this._options=t||{},this._threshold=this._options.threshold!==void 0?this._options.threshold:1024,this._maxPayload=this._options.maxPayload|0,this._isServer=!!this._options.isServer,this._deflate=null,this._inflate=null,this.params=null,!xn){let e=this._options.concurrencyLimit!==void 0?this._options.concurrencyLimit:10;xn=new cc(e)}}static get extensionName(){return"permessage-deflate"}offer(){let t={};return this._options.serverNoContextTakeover&&(t.server_no_context_takeover=!0),this._options.clientNoContextTakeover&&(t.client_no_context_takeover=!0),this._options.serverMaxWindowBits&&(t.server_max_window_bits=this._options.serverMaxWindowBits),this._options.clientMaxWindowBits?t.client_max_window_bits=this._options.clientMaxWindowBits:this._options.clientMaxWindowBits==null&&(t.client_max_window_bits=!0),t}accept(t){return t=this.normalizeParams(t),this.params=this._isServer?this.acceptAsServer(t):this.acceptAsClient(t),this.params}cleanup(){if(this._inflate&&(this._inflate.close(),this._inflate=null),this._deflate){let t=this._deflate[Yt];this._deflate.close(),this._deflate=null,t&&t(new Error("The deflate stream was closed while data was being processed"))}}acceptAsServer(t){let e=this._options,s=t.find(n=>!(e.serverNoContextTakeover===!1&&n.server_no_context_takeover||n.server_max_window_bits&&(e.serverMaxWindowBits===!1||typeof e.serverMaxWindowBits=="number"&&e.serverMaxWindowBits>n.server_max_window_bits)||typeof e.clientMaxWindowBits=="number"&&!n.client_max_window_bits));if(!s)throw new Error("None of the extension offers can be accepted");return e.serverNoContextTakeover&&(s.server_no_context_takeover=!0),e.clientNoContextTakeover&&(s.client_no_context_takeover=!0),typeof e.serverMaxWindowBits=="number"&&(s.server_max_window_bits=e.serverMaxWindowBits),typeof e.clientMaxWindowBits=="number"?s.client_max_window_bits=e.clientMaxWindowBits:(s.client_max_window_bits===!0||e.clientMaxWindowBits===!1)&&delete s.client_max_window_bits,s}acceptAsClient(t){let e=t[0];if(this._options.clientNoContextTakeover===!1&&e.client_no_context_takeover)throw new Error('Unexpected parameter "client_no_context_takeover"');if(!e.client_max_window_bits)typeof this._options.clientMaxWindowBits=="number"&&(e.client_max_window_bits=this._options.clientMaxWindowBits);else if(this._options.clientMaxWindowBits===!1||typeof this._options.clientMaxWindowBits=="number"&&e.client_max_window_bits>this._options.clientMaxWindowBits)throw new Error('Unexpected or invalid parameter "client_max_window_bits"');return e}normalizeParams(t){return t.forEach(e=>{Object.keys(e).forEach(s=>{let n=e[s];if(n.length>1)throw new Error(`Parameter "${s}" must have only a single value`);if(n=n[0],s==="client_max_window_bits"){if(n!==!0){let a=+n;if(!Number.isInteger(a)||a<8||a>15)throw new TypeError(`Invalid value for parameter "${s}": ${n}`);n=a}else if(!this._isServer)throw new TypeError(`Invalid value for parameter "${s}": ${n}`)}else if(s==="server_max_window_bits"){let a=+n;if(!Number.isInteger(a)||a<8||a>15)throw new TypeError(`Invalid value for parameter "${s}": ${n}`);n=a}else if(s==="client_no_context_takeover"||s==="server_no_context_takeover"){if(n!==!0)throw new TypeError(`Invalid value for parameter "${s}": ${n}`)}else throw new Error(`Unknown parameter "${s}"`);e[s]=n})}),t}decompress(t,e,s){xn.add(n=>{this._decompress(t,e,(a,r)=>{n(),s(a,r)})})}compress(t,e,s){xn.add(n=>{this._compress(t,e,(a,r)=>{n(),s(a,r)})})}_decompress(t,e,s){let n=this._isServer?"client":"server";if(!this._inflate){let a=`${n}_max_window_bits`,r=typeof this.params[a]!="number"?Cs.Z_DEFAULT_WINDOWBITS:this.params[a];this._inflate=Cs.createInflateRaw({...this._options.zlibInflateOptions,windowBits:r}),this._inflate[Sn]=this,this._inflate[ft]=0,this._inflate[kt]=[],this._inflate.on("error",pc),this._inflate.on("data",Er)}this._inflate[Yt]=s,this._inflate.write(t),e&&this._inflate.write(hc),this._inflate.flush(()=>{let a=this._inflate[Kt];if(a){this._inflate.close(),this._inflate=null,s(a);return}let r=_r.concat(this._inflate[kt],this._inflate[ft]);this._inflate._readableState.endEmitted?(this._inflate.close(),this._inflate=null):(this._inflate[ft]=0,this._inflate[kt]=[],e&&this.params[`${n}_no_context_takeover`]&&this._inflate.reset()),s(null,r)})}_compress(t,e,s){let n=this._isServer?"server":"client";if(!this._deflate){let a=`${n}_max_window_bits`,r=typeof this.params[a]!="number"?Cs.Z_DEFAULT_WINDOWBITS:this.params[a];this._deflate=Cs.createDeflateRaw({...this._options.zlibDeflateOptions,windowBits:r}),this._deflate[ft]=0,this._deflate[kt]=[],this._deflate.on("data",uc)}this._deflate[Yt]=s,this._deflate.write(t),this._deflate.flush(Cs.Z_SYNC_FLUSH,()=>{if(!this._deflate)return;let a=_r.concat(this._deflate[kt],this._deflate[ft]);e&&(a=new dc(a.buffer,a.byteOffset,a.length-4)),this._deflate[Yt]=null,this._deflate[ft]=0,this._deflate[kt]=[],e&&this.params[`${n}_no_context_takeover`]&&this._deflate.reset(),s(null,a)})}};Pr.exports=Sa;function uc(i){this[kt].push(i),this[ft]+=i.length}function Er(i){if(this[ft]+=i.length,this[Sn]._maxPayload<1||this[ft]<=this[Sn]._maxPayload){this[kt].push(i);return}this[Kt]=new RangeError("Max payload size exceeded"),this[Kt].code="WS_ERR_UNSUPPORTED_MESSAGE_LENGTH",this[Kt][Ar]=1009,this.removeListener("data",Er),this.reset()}function pc(i){if(this[Sn]._inflate=null,this[Kt]){this[Yt](this[Kt]);return}i[Ar]=1007,this[Yt](i)}});var Xt=qe((Hu,Cn)=>{"use strict";var{isUtf8:Rr}=require("buffer"),{hasBlob:mc}=mt(),fc=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,1,1,1,1,0,0,1,1,0,1,1,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0,1,0];function gc(i){return i>=1e3&&i<=1014&&i!==1004&&i!==1005&&i!==1006||i>=3e3&&i<=4999}function Ca(i){let t=i.length,e=0;for(;e=t||(i[e+1]&192)!==128||(i[e+2]&192)!==128||i[e]===224&&(i[e+1]&224)===128||i[e]===237&&(i[e+1]&224)===160)return!1;e+=3}else if((i[e]&248)===240){if(e+3>=t||(i[e+1]&192)!==128||(i[e+2]&192)!==128||(i[e+3]&192)!==128||i[e]===240&&(i[e+1]&240)===128||i[e]===244&&i[e+1]>143||i[e]>244)return!1;e+=4}else return!1;return!0}function yc(i){return mc&&typeof i=="object"&&typeof i.arrayBuffer=="function"&&typeof i.type=="string"&&typeof i.stream=="function"&&(i[Symbol.toStringTag]==="Blob"||i[Symbol.toStringTag]==="File")}Cn.exports={isBlob:yc,isValidStatusCode:gc,isValidUTF8:Ca,tokenChars:fc};if(Rr)Cn.exports.isValidUTF8=function(i){return i.length<24?Ca(i):Rr(i)};else if(!process.env.WS_NO_UTF_8_VALIDATE)try{let i=require("utf-8-validate");Cn.exports.isValidUTF8=function(t){return t.length<32?Ca(t):i(t)}}catch{}});var Pa=qe((qu,Nr)=>{"use strict";var{Writable:vc}=require("stream"),Dr=Jt(),{BINARY_TYPES:wc,EMPTY_BUFFER:Ir,kStatusCode:bc,kWebSocket:kc}=mt(),{concat:Ta,toArrayBuffer:xc,unmask:Sc}=Ss(),{isValidStatusCode:Cc,isValidUTF8:Mr}=Xt(),Tn=Buffer[Symbol.species],Xe=0,Lr=1,Fr=2,Or=3,_a=4,Aa=5,_n=6,Ea=class extends vc{constructor(t={}){super(),this._allowSynchronousEvents=t.allowSynchronousEvents!==void 0?t.allowSynchronousEvents:!0,this._binaryType=t.binaryType||wc[0],this._extensions=t.extensions||{},this._isServer=!!t.isServer,this._maxBufferedChunks=t.maxBufferedChunks|0,this._maxFragments=t.maxFragments|0,this._maxPayload=t.maxPayload|0,this._skipUTF8Validation=!!t.skipUTF8Validation,this[kc]=void 0,this._bufferedBytes=0,this._buffers=[],this._compressed=!1,this._payloadLength=0,this._mask=void 0,this._fragmented=0,this._masked=!1,this._fin=!1,this._opcode=0,this._totalPayloadLength=0,this._messageLength=0,this._fragments=[],this._errored=!1,this._loop=!1,this._state=Xe}_write(t,e,s){if(this._opcode===8&&this._state==Xe)return s();if(this._maxBufferedChunks>0&&this._buffers.length>=this._maxBufferedChunks){s(this.createError(RangeError,"Too many buffered chunks",!1,1008,"WS_ERR_TOO_MANY_BUFFERED_PARTS"));return}this._bufferedBytes+=t.length,this._buffers.push(t),this.startLoop(s)}consume(t){if(this._bufferedBytes-=t,t===this._buffers[0].length)return this._buffers.shift();if(t=s.length?e.set(this._buffers.shift(),n):(e.set(new Uint8Array(s.buffer,s.byteOffset,t),n),this._buffers[0]=new Tn(s.buffer,s.byteOffset+t,s.length-t)),t-=s.length}while(t>0);return e}startLoop(t){this._loop=!0;do switch(this._state){case Xe:this.getInfo(t);break;case Lr:this.getPayloadLength16(t);break;case Fr:this.getPayloadLength64(t);break;case Or:this.getMask();break;case _a:this.getData(t);break;case Aa:case _n:this._loop=!1;return}while(this._loop);this._errored||t()}getInfo(t){if(this._bufferedBytes<2){this._loop=!1;return}let e=this.consume(2);if((e[0]&48)!==0){let n=this.createError(RangeError,"RSV2 and RSV3 must be clear",!0,1002,"WS_ERR_UNEXPECTED_RSV_2_3");t(n);return}let s=(e[0]&64)===64;if(s&&!this._extensions[Dr.extensionName]){let n=this.createError(RangeError,"RSV1 must be clear",!0,1002,"WS_ERR_UNEXPECTED_RSV_1");t(n);return}if(this._fin=(e[0]&128)===128,this._opcode=e[0]&15,this._payloadLength=e[1]&127,this._opcode===0){if(s){let n=this.createError(RangeError,"RSV1 must be clear",!0,1002,"WS_ERR_UNEXPECTED_RSV_1");t(n);return}if(!this._fragmented){let n=this.createError(RangeError,"invalid opcode 0",!0,1002,"WS_ERR_INVALID_OPCODE");t(n);return}this._opcode=this._fragmented}else if(this._opcode===1||this._opcode===2){if(this._fragmented){let n=this.createError(RangeError,`invalid opcode ${this._opcode}`,!0,1002,"WS_ERR_INVALID_OPCODE");t(n);return}this._compressed=s}else if(this._opcode>7&&this._opcode<11){if(!this._fin){let n=this.createError(RangeError,"FIN must be set",!0,1002,"WS_ERR_EXPECTED_FIN");t(n);return}if(s){let n=this.createError(RangeError,"RSV1 must be clear",!0,1002,"WS_ERR_UNEXPECTED_RSV_1");t(n);return}if(this._payloadLength>125||this._opcode===8&&this._payloadLength===1){let n=this.createError(RangeError,`invalid payload length ${this._payloadLength}`,!0,1002,"WS_ERR_INVALID_CONTROL_PAYLOAD_LENGTH");t(n);return}}else{let n=this.createError(RangeError,`invalid opcode ${this._opcode}`,!0,1002,"WS_ERR_INVALID_OPCODE");t(n);return}if(!this._fin&&!this._fragmented&&(this._fragmented=this._opcode),this._masked=(e[1]&128)===128,this._isServer){if(!this._masked){let n=this.createError(RangeError,"MASK must be set",!0,1002,"WS_ERR_EXPECTED_MASK");t(n);return}}else if(this._masked){let n=this.createError(RangeError,"MASK must be clear",!0,1002,"WS_ERR_UNEXPECTED_MASK");t(n);return}this._payloadLength===126?this._state=Lr:this._payloadLength===127?this._state=Fr:this.haveLength(t)}getPayloadLength16(t){if(this._bufferedBytes<2){this._loop=!1;return}this._payloadLength=this.consume(2).readUInt16BE(0),this.haveLength(t)}getPayloadLength64(t){if(this._bufferedBytes<8){this._loop=!1;return}let e=this.consume(8),s=e.readUInt32BE(0);if(s>Math.pow(2,21)-1){let n=this.createError(RangeError,"Unsupported WebSocket frame: payload length > 2^53 - 1",!1,1009,"WS_ERR_UNSUPPORTED_DATA_PAYLOAD_LENGTH");t(n);return}this._payloadLength=s*Math.pow(2,32)+e.readUInt32BE(4),this.haveLength(t)}haveLength(t){if(this._payloadLength&&this._opcode<8&&(this._totalPayloadLength+=this._payloadLength,this._totalPayloadLength>this._maxPayload&&this._maxPayload>0)){let e=this.createError(RangeError,"Max payload size exceeded",!1,1009,"WS_ERR_UNSUPPORTED_MESSAGE_LENGTH");t(e);return}this._masked?this._state=Or:this._state=_a}getMask(){if(this._bufferedBytes<4){this._loop=!1;return}this._mask=this.consume(4),this._state=_a}getData(t){let e=Ir;if(this._payloadLength){if(this._bufferedBytes7){this.controlMessage(e,t);return}if(this._compressed){this._state=Aa,this.decompress(e,t);return}if(e.length){if(this._maxFragments>0&&this._fragments.length>=this._maxFragments){let s=this.createError(RangeError,"Too many message fragments",!1,1008,"WS_ERR_TOO_MANY_BUFFERED_PARTS");t(s);return}this._messageLength=this._totalPayloadLength,this._fragments.push(e)}this.dataMessage(t)}decompress(t,e){this._extensions[Dr.extensionName].decompress(t,this._fin,(n,a)=>{if(n)return e(n);if(a.length){if(this._messageLength+=a.length,this._messageLength>this._maxPayload&&this._maxPayload>0){let r=this.createError(RangeError,"Max payload size exceeded",!1,1009,"WS_ERR_UNSUPPORTED_MESSAGE_LENGTH");e(r);return}if(this._maxFragments>0&&this._fragments.length>=this._maxFragments){let r=this.createError(RangeError,"Too many message fragments",!1,1008,"WS_ERR_TOO_MANY_BUFFERED_PARTS");e(r);return}this._fragments.push(a)}this.dataMessage(e),this._state===Xe&&this.startLoop(e)})}dataMessage(t){if(!this._fin){this._state=Xe;return}let e=this._messageLength,s=this._fragments;if(this._totalPayloadLength=0,this._messageLength=0,this._fragmented=0,this._fragments=[],this._opcode===2){let n;this._binaryType==="nodebuffer"?n=Ta(s,e):this._binaryType==="arraybuffer"?n=xc(Ta(s,e)):this._binaryType==="blob"?n=new Blob(s):n=s,this._allowSynchronousEvents?(this.emit("message",n,!0),this._state=Xe):(this._state=_n,setImmediate(()=>{this.emit("message",n,!0),this._state=Xe,this.startLoop(t)}))}else{let n=Ta(s,e);if(!this._skipUTF8Validation&&!Mr(n)){let a=this.createError(Error,"invalid UTF-8 sequence",!0,1007,"WS_ERR_INVALID_UTF8");t(a);return}this._state===Aa||this._allowSynchronousEvents?(this.emit("message",n,!1),this._state=Xe):(this._state=_n,setImmediate(()=>{this.emit("message",n,!1),this._state=Xe,this.startLoop(t)}))}}controlMessage(t,e){if(this._opcode===8){if(t.length===0)this._loop=!1,this.emit("conclude",1005,Ir),this.end();else{let s=t.readUInt16BE(0);if(!Cc(s)){let a=this.createError(RangeError,`invalid status code ${s}`,!0,1002,"WS_ERR_INVALID_CLOSE_CODE");e(a);return}let n=new Tn(t.buffer,t.byteOffset+2,t.length-2);if(!this._skipUTF8Validation&&!Mr(n)){let a=this.createError(Error,"invalid UTF-8 sequence",!0,1007,"WS_ERR_INVALID_UTF8");e(a);return}this._loop=!1,this.emit("conclude",s,n),this.end()}this._state=Xe;return}this._allowSynchronousEvents?(this.emit(this._opcode===9?"ping":"pong",t),this._state=Xe):(this._state=_n,setImmediate(()=>{this.emit(this._opcode===9?"ping":"pong",t),this._state=Xe,this.startLoop(e)}))}createError(t,e,s,n,a){this._loop=!1,this._errored=!0;let r=new t(s?`Invalid WebSocket frame: ${e}`:e);return Error.captureStackTrace(r,this.createError),r.code=a,r[bc]=n,r}};Nr.exports=Ea});var Ia=qe((Gu,$r)=>{"use strict";var{Duplex:zu}=require("stream"),{randomFillSync:Tc}=require("crypto"),{types:{isUint8Array:_c}}=require("util"),Br=Jt(),{EMPTY_BUFFER:Ac,kWebSocket:Ec,NOOP:Pc}=mt(),{isBlob:Qt,isValidStatusCode:Rc}=Xt(),{mask:Ur,toBuffer:Rt}=Ss(),Qe=Symbol("kByteLength"),Dc=Buffer.alloc(4),An=8*1024,Dt,Zt=An,rt=0,Ic=1,Mc=2,Ra=class i{constructor(t,e,s){this._extensions=e||{},s&&(this._generateMask=s,this._maskBuffer=Buffer.alloc(4)),this._socket=t,this._firstFragment=!0,this._compress=!1,this._bufferedBytes=0,this._queue=[],this._state=rt,this.onerror=Pc,this[Ec]=void 0}static frame(t,e){let s,n=!1,a=2,r=!1;e.mask&&(s=e.maskBuffer||Dc,e.generateMask?e.generateMask(s):(Zt===An&&(Dt===void 0&&(Dt=Buffer.alloc(An)),Tc(Dt,0,An),Zt=0),s[0]=Dt[Zt++],s[1]=Dt[Zt++],s[2]=Dt[Zt++],s[3]=Dt[Zt++]),r=(s[0]|s[1]|s[2]|s[3])===0,a=6);let o;typeof t=="string"?(!e.mask||r)&&e[Qe]!==void 0?o=e[Qe]:(t=Buffer.from(t),o=t.length):(o=t.length,n=e.mask&&e.readOnly&&!r);let c=o;o>=65536?(a+=8,c=127):o>125&&(a+=2,c=126);let l=Buffer.allocUnsafe(n?o+a:a);return l[0]=e.fin?e.opcode|128:e.opcode,e.rsv1&&(l[0]|=64),l[1]=c,c===126?l.writeUInt16BE(o,2):c===127&&(l[2]=l[3]=0,l.writeUIntBE(o,4,6)),e.mask?(l[1]|=128,l[a-4]=s[0],l[a-3]=s[1],l[a-2]=s[2],l[a-1]=s[3],r?[l,t]:n?(Ur(t,s,l,a,o),[l]):(Ur(t,s,t,0,o),[l,t])):[l,t]}close(t,e,s,n){let a;if(t===void 0)a=Ac;else{if(typeof t!="number"||!Rc(t))throw new TypeError("First argument must be a valid error code number");if(e===void 0||!e.length)a=Buffer.allocUnsafe(2),a.writeUInt16BE(t,0);else{let o=Buffer.byteLength(e);if(o>123)throw new RangeError("The message must not be greater than 123 bytes");if(a=Buffer.allocUnsafe(2+o),a.writeUInt16BE(t,0),typeof e=="string")a.write(e,2);else if(_c(e))a.set(e,2);else throw new TypeError("Second argument must be a string or a Uint8Array")}}let r={[Qe]:a.length,fin:!0,generateMask:this._generateMask,mask:s,maskBuffer:this._maskBuffer,opcode:8,readOnly:!1,rsv1:!1};this._state!==rt?this.enqueue([this.dispatch,a,!1,r,n]):this.sendFrame(i.frame(a,r),n)}ping(t,e,s){let n,a;if(typeof t=="string"?(n=Buffer.byteLength(t),a=!1):Qt(t)?(n=t.size,a=!1):(t=Rt(t),n=t.length,a=Rt.readOnly),n>125)throw new RangeError("The data size must not be greater than 125 bytes");let r={[Qe]:n,fin:!0,generateMask:this._generateMask,mask:e,maskBuffer:this._maskBuffer,opcode:9,readOnly:a,rsv1:!1};Qt(t)?this._state!==rt?this.enqueue([this.getBlobData,t,!1,r,s]):this.getBlobData(t,!1,r,s):this._state!==rt?this.enqueue([this.dispatch,t,!1,r,s]):this.sendFrame(i.frame(t,r),s)}pong(t,e,s){let n,a;if(typeof t=="string"?(n=Buffer.byteLength(t),a=!1):Qt(t)?(n=t.size,a=!1):(t=Rt(t),n=t.length,a=Rt.readOnly),n>125)throw new RangeError("The data size must not be greater than 125 bytes");let r={[Qe]:n,fin:!0,generateMask:this._generateMask,mask:e,maskBuffer:this._maskBuffer,opcode:10,readOnly:a,rsv1:!1};Qt(t)?this._state!==rt?this.enqueue([this.getBlobData,t,!1,r,s]):this.getBlobData(t,!1,r,s):this._state!==rt?this.enqueue([this.dispatch,t,!1,r,s]):this.sendFrame(i.frame(t,r),s)}send(t,e,s){let n=this._extensions[Br.extensionName],a=e.binary?2:1,r=e.compress,o,c;typeof t=="string"?(o=Buffer.byteLength(t),c=!1):Qt(t)?(o=t.size,c=!1):(t=Rt(t),o=t.length,c=Rt.readOnly),this._firstFragment?(this._firstFragment=!1,r&&n&&n.params[n._isServer?"server_no_context_takeover":"client_no_context_takeover"]&&(r=o>=n._threshold),this._compress=r):(r=!1,a=0),e.fin&&(this._firstFragment=!0);let l={[Qe]:o,fin:e.fin,generateMask:this._generateMask,mask:e.mask,maskBuffer:this._maskBuffer,opcode:a,readOnly:c,rsv1:r};Qt(t)?this._state!==rt?this.enqueue([this.getBlobData,t,this._compress,l,s]):this.getBlobData(t,this._compress,l,s):this._state!==rt?this.enqueue([this.dispatch,t,this._compress,l,s]):this.dispatch(t,this._compress,l,s)}getBlobData(t,e,s,n){this._bufferedBytes+=s[Qe],this._state=Mc,t.arrayBuffer().then(a=>{if(this._socket.destroyed){let o=new Error("The socket was closed while the blob was being read");process.nextTick(Da,this,o,n);return}this._bufferedBytes-=s[Qe];let r=Rt(a);e?this.dispatch(r,e,s,n):(this._state=rt,this.sendFrame(i.frame(r,s),n),this.dequeue())}).catch(a=>{process.nextTick(Lc,this,a,n)})}dispatch(t,e,s,n){if(!e){this.sendFrame(i.frame(t,s),n);return}let a=this._extensions[Br.extensionName];this._bufferedBytes+=s[Qe],this._state=Ic,a.compress(t,s.fin,(r,o)=>{if(this._socket.destroyed){let c=new Error("The socket was closed while data was being compressed");Da(this,c,n);return}this._bufferedBytes-=s[Qe],this._state=rt,s.readOnly=!1,this.sendFrame(i.frame(o,s),n),this.dequeue()})}dequeue(){for(;this._state===rt&&this._queue.length;){let t=this._queue.shift();this._bufferedBytes-=t[3][Qe],Reflect.apply(t[0],this,t.slice(1))}}enqueue(t){this._bufferedBytes+=t[3][Qe],this._queue.push(t)}sendFrame(t,e){t.length===2?(this._socket.cork(),this._socket.write(t[0]),this._socket.write(t[1],e),this._socket.uncork()):this._socket.write(t[0],e)}};$r.exports=Ra;function Da(i,t,e){typeof e=="function"&&e(t);for(let s=0;s{"use strict";var{kForOnEventAttribute:Ts,kListener:Ma}=mt(),jr=Symbol("kCode"),Wr=Symbol("kData"),Hr=Symbol("kError"),qr=Symbol("kMessage"),zr=Symbol("kReason"),es=Symbol("kTarget"),Gr=Symbol("kType"),Vr=Symbol("kWasClean"),gt=class{constructor(t){this[es]=null,this[Gr]=t}get target(){return this[es]}get type(){return this[Gr]}};Object.defineProperty(gt.prototype,"target",{enumerable:!0});Object.defineProperty(gt.prototype,"type",{enumerable:!0});var It=class extends gt{constructor(t,e={}){super(t),this[jr]=e.code===void 0?0:e.code,this[zr]=e.reason===void 0?"":e.reason,this[Vr]=e.wasClean===void 0?!1:e.wasClean}get code(){return this[jr]}get reason(){return this[zr]}get wasClean(){return this[Vr]}};Object.defineProperty(It.prototype,"code",{enumerable:!0});Object.defineProperty(It.prototype,"reason",{enumerable:!0});Object.defineProperty(It.prototype,"wasClean",{enumerable:!0});var ts=class extends gt{constructor(t,e={}){super(t),this[Hr]=e.error===void 0?null:e.error,this[qr]=e.message===void 0?"":e.message}get error(){return this[Hr]}get message(){return this[qr]}};Object.defineProperty(ts.prototype,"error",{enumerable:!0});Object.defineProperty(ts.prototype,"message",{enumerable:!0});var _s=class extends gt{constructor(t,e={}){super(t),this[Wr]=e.data===void 0?null:e.data}get data(){return this[Wr]}};Object.defineProperty(_s.prototype,"data",{enumerable:!0});var Fc={addEventListener(i,t,e={}){for(let n of this.listeners(i))if(!e[Ts]&&n[Ma]===t&&!n[Ts])return;let s;if(i==="message")s=function(a,r){let o=new _s("message",{data:r?a:a.toString()});o[es]=this,En(t,this,o)};else if(i==="close")s=function(a,r){let o=new It("close",{code:a,reason:r.toString(),wasClean:this._closeFrameReceived&&this._closeFrameSent});o[es]=this,En(t,this,o)};else if(i==="error")s=function(a){let r=new ts("error",{error:a,message:a.message});r[es]=this,En(t,this,r)};else if(i==="open")s=function(){let a=new gt("open");a[es]=this,En(t,this,a)};else return;s[Ts]=!!e[Ts],s[Ma]=t,e.once?this.once(i,s):this.on(i,s)},removeEventListener(i,t){for(let e of this.listeners(i))if(e[Ma]===t&&!e[Ts]){this.removeListener(i,e);break}}};Yr.exports={CloseEvent:It,ErrorEvent:ts,Event:gt,EventTarget:Fc,MessageEvent:_s};function En(i,t,e){typeof i=="object"&&i.handleEvent?i.handleEvent.call(i,e):i.call(t,e)}});var Pn=qe((Yu,Jr)=>{"use strict";var{tokenChars:As}=Xt();function ht(i,t,e){i[t]===void 0?i[t]=[e]:i[t].push(e)}function Oc(i){let t=Object.create(null),e=Object.create(null),s=!1,n=!1,a=!1,r,o,c=-1,l=-1,h=-1,d=0;for(;d{let e=i[t];return Array.isArray(e)||(e=[e]),e.map(s=>[t].concat(Object.keys(s).map(n=>{let a=s[n];return Array.isArray(a)||(a=[a]),a.map(r=>r===!0?n:`${n}=${r}`).join("; ")})).join("; ")).join(", ")}).join(", ")}Jr.exports={format:Nc,parse:Oc}});var Mn=qe((Xu,lo)=>{"use strict";var Bc=require("events"),Uc=require("https"),$c=require("http"),Zr=require("net"),jc=require("tls"),{randomBytes:Wc,createHash:Hc}=require("crypto"),{Duplex:Ku,Readable:Ju}=require("stream"),{URL:La}=require("url"),xt=Jt(),qc=Pa(),zc=Ia(),{isBlob:Gc}=Xt(),{BINARY_TYPES:Xr,CLOSE_TIMEOUT:Vc,EMPTY_BUFFER:Rn,GUID:Yc,kForOnEventAttribute:Fa,kListener:Kc,kStatusCode:Jc,kWebSocket:Ie,NOOP:eo}=mt(),{EventTarget:{addEventListener:Xc,removeEventListener:Qc}}=Kr(),{format:Zc,parse:ed}=Pn(),{toBuffer:td}=Ss(),to=Symbol("kAborted"),Oa=[8,13],yt=["CONNECTING","OPEN","CLOSING","CLOSED"],sd=/^[!#$%&'*+\-.0-9A-Z^_`|a-z~]+$/,be=class i extends Bc{constructor(t,e,s){super(),this._binaryType=Xr[0],this._closeCode=1006,this._closeFrameReceived=!1,this._closeFrameSent=!1,this._closeMessage=Rn,this._closeTimer=null,this._errorEmitted=!1,this._extensions={},this._paused=!1,this._protocol="",this._readyState=i.CONNECTING,this._receiver=null,this._sender=null,this._socket=null,t!==null?(this._bufferedAmount=0,this._isServer=!1,this._redirects=0,e===void 0?e=[]:Array.isArray(e)||(typeof e=="object"&&e!==null?(s=e,e=[]):e=[e]),so(this,t,e,s)):(this._autoPong=s.autoPong,this._closeTimeout=s.closeTimeout,this._isServer=!0)}get binaryType(){return this._binaryType}set binaryType(t){Xr.includes(t)&&(this._binaryType=t,this._receiver&&(this._receiver._binaryType=t))}get bufferedAmount(){return this._socket?this._socket._writableState.length+this._sender._bufferedBytes:this._bufferedAmount}get extensions(){return Object.keys(this._extensions).join()}get isPaused(){return this._paused}get onclose(){return null}get onerror(){return null}get onopen(){return null}get onmessage(){return null}get protocol(){return this._protocol}get readyState(){return this._readyState}get url(){return this._url}setSocket(t,e,s){let n=new qc({allowSynchronousEvents:s.allowSynchronousEvents,binaryType:this.binaryType,extensions:this._extensions,isServer:this._isServer,maxBufferedChunks:s.maxBufferedChunks,maxFragments:s.maxFragments,maxPayload:s.maxPayload,skipUTF8Validation:s.skipUTF8Validation}),a=new zc(t,this._extensions,s.generateMask);this._receiver=n,this._sender=a,this._socket=t,n[Ie]=this,a[Ie]=this,t[Ie]=this,n.on("conclude",id),n.on("drain",rd),n.on("error",od),n.on("message",ld),n.on("ping",cd),n.on("pong",dd),a.onerror=hd,t.setTimeout&&t.setTimeout(0),t.setNoDelay&&t.setNoDelay(),e.length>0&&t.unshift(e),t.on("close",io),t.on("data",In),t.on("end",ro),t.on("error",oo),this._readyState=i.OPEN,this.emit("open")}emitClose(){if(!this._socket){this._readyState=i.CLOSED,this.emit("close",this._closeCode,this._closeMessage);return}this._extensions[xt.extensionName]&&this._extensions[xt.extensionName].cleanup(),this._receiver.removeAllListeners(),this._readyState=i.CLOSED,this.emit("close",this._closeCode,this._closeMessage)}close(t,e){if(this.readyState!==i.CLOSED){if(this.readyState===i.CONNECTING){Ke(this,this._req,"WebSocket was closed before the connection was established");return}if(this.readyState===i.CLOSING){this._closeFrameSent&&(this._closeFrameReceived||this._receiver._writableState.errorEmitted)&&this._socket.end();return}this._readyState=i.CLOSING,this._sender.close(t,e,!this._isServer,s=>{s||(this._closeFrameSent=!0,(this._closeFrameReceived||this._receiver._writableState.errorEmitted)&&this._socket.end())}),ao(this)}}pause(){this.readyState===i.CONNECTING||this.readyState===i.CLOSED||(this._paused=!0,this._socket.pause())}ping(t,e,s){if(this.readyState===i.CONNECTING)throw new Error("WebSocket is not open: readyState 0 (CONNECTING)");if(typeof t=="function"?(s=t,t=e=void 0):typeof e=="function"&&(s=e,e=void 0),typeof t=="number"&&(t=t.toString()),this.readyState!==i.OPEN){Na(this,t,s);return}e===void 0&&(e=!this._isServer),this._sender.ping(t||Rn,e,s)}pong(t,e,s){if(this.readyState===i.CONNECTING)throw new Error("WebSocket is not open: readyState 0 (CONNECTING)");if(typeof t=="function"?(s=t,t=e=void 0):typeof e=="function"&&(s=e,e=void 0),typeof t=="number"&&(t=t.toString()),this.readyState!==i.OPEN){Na(this,t,s);return}e===void 0&&(e=!this._isServer),this._sender.pong(t||Rn,e,s)}resume(){this.readyState===i.CONNECTING||this.readyState===i.CLOSED||(this._paused=!1,this._receiver._writableState.needDrain||this._socket.resume())}send(t,e,s){if(this.readyState===i.CONNECTING)throw new Error("WebSocket is not open: readyState 0 (CONNECTING)");if(typeof e=="function"&&(s=e,e={}),typeof t=="number"&&(t=t.toString()),this.readyState!==i.OPEN){Na(this,t,s);return}let n={binary:typeof t!="string",mask:!this._isServer,compress:!0,fin:!0,...e};this._extensions[xt.extensionName]||(n.compress=!1),this._sender.send(t||Rn,n,s)}terminate(){if(this.readyState!==i.CLOSED){if(this.readyState===i.CONNECTING){Ke(this,this._req,"WebSocket was closed before the connection was established");return}this._socket&&(this._readyState=i.CLOSING,this._socket.destroy())}}};Object.defineProperty(be,"CONNECTING",{enumerable:!0,value:yt.indexOf("CONNECTING")});Object.defineProperty(be.prototype,"CONNECTING",{enumerable:!0,value:yt.indexOf("CONNECTING")});Object.defineProperty(be,"OPEN",{enumerable:!0,value:yt.indexOf("OPEN")});Object.defineProperty(be.prototype,"OPEN",{enumerable:!0,value:yt.indexOf("OPEN")});Object.defineProperty(be,"CLOSING",{enumerable:!0,value:yt.indexOf("CLOSING")});Object.defineProperty(be.prototype,"CLOSING",{enumerable:!0,value:yt.indexOf("CLOSING")});Object.defineProperty(be,"CLOSED",{enumerable:!0,value:yt.indexOf("CLOSED")});Object.defineProperty(be.prototype,"CLOSED",{enumerable:!0,value:yt.indexOf("CLOSED")});["binaryType","bufferedAmount","extensions","isPaused","protocol","readyState","url"].forEach(i=>{Object.defineProperty(be.prototype,i,{enumerable:!0})});["open","error","close","message"].forEach(i=>{Object.defineProperty(be.prototype,`on${i}`,{enumerable:!0,get(){for(let t of this.listeners(i))if(t[Fa])return t[Kc];return null},set(t){for(let e of this.listeners(i))if(e[Fa]){this.removeListener(i,e);break}typeof t=="function"&&this.addEventListener(i,t,{[Fa]:!0})}})});be.prototype.addEventListener=Xc;be.prototype.removeEventListener=Qc;lo.exports=be;function so(i,t,e,s){let n={allowSynchronousEvents:!0,autoPong:!0,closeTimeout:Vc,protocolVersion:Oa[1],maxBufferedChunks:1048576,maxFragments:131072,maxPayload:104857600,skipUTF8Validation:!1,perMessageDeflate:!0,followRedirects:!1,maxRedirects:10,...s,socketPath:void 0,hostname:void 0,protocol:void 0,timeout:void 0,method:"GET",host:void 0,path:void 0,port:void 0};if(i._autoPong=n.autoPong,i._closeTimeout=n.closeTimeout,!Oa.includes(n.protocolVersion))throw new RangeError(`Unsupported protocol version: ${n.protocolVersion} (supported versions: ${Oa.join(", ")})`);let a;if(t instanceof La)a=t;else try{a=new La(t)}catch{throw new SyntaxError(`Invalid URL: ${t}`)}a.protocol==="http:"?a.protocol="ws:":a.protocol==="https:"&&(a.protocol="wss:"),i._url=a.href;let r=a.protocol==="wss:",o=a.protocol==="ws+unix:",c;if(a.protocol!=="ws:"&&!r&&!o?c=`The URL's protocol must be one of "ws:", "wss:", "http:", "https:", or "ws+unix:"`:o&&!a.pathname?c="The URL's pathname is empty":a.hash&&(c="The URL contains a fragment identifier"),c){let f=new SyntaxError(c);if(i._redirects===0)throw f;Dn(i,f);return}let l=r?443:80,h=Wc(16).toString("base64"),d=r?Uc.request:$c.request,u=new Set,p;if(n.createConnection=n.createConnection||(r?ad:nd),n.defaultPort=n.defaultPort||l,n.port=a.port||l,n.host=a.hostname.startsWith("[")?a.hostname.slice(1,-1):a.hostname,n.headers={...n.headers,"Sec-WebSocket-Version":n.protocolVersion,"Sec-WebSocket-Key":h,Connection:"Upgrade",Upgrade:"websocket"},n.path=a.pathname+a.search,n.timeout=n.handshakeTimeout,n.perMessageDeflate&&(p=new xt({...n.perMessageDeflate,isServer:!1,maxPayload:n.maxPayload}),n.headers["Sec-WebSocket-Extensions"]=Zc({[xt.extensionName]:p.offer()})),e.length){for(let f of e){if(typeof f!="string"||!sd.test(f)||u.has(f))throw new SyntaxError("An invalid or duplicated subprotocol was specified");u.add(f)}n.headers["Sec-WebSocket-Protocol"]=e.join(",")}if(n.origin&&(n.protocolVersion<13?n.headers["Sec-WebSocket-Origin"]=n.origin:n.headers.Origin=n.origin),(a.username||a.password)&&(n.auth=`${a.username}:${a.password}`),o){let f=n.path.split(":");n.socketPath=f[0],n.path=f[1]}let m;if(n.followRedirects){if(i._redirects===0){i._originalIpc=o,i._originalSecure=r,i._originalHostOrSocketPath=o?n.socketPath:a.host;let f=s&&s.headers;if(s={...s,headers:{}},f)for(let[g,v]of Object.entries(f))s.headers[g.toLowerCase()]=v}else if(i.listenerCount("redirect")===0){let f=o?i._originalIpc?n.socketPath===i._originalHostOrSocketPath:!1:i._originalIpc?!1:a.host===i._originalHostOrSocketPath;(!f||i._originalSecure&&!r)&&(delete n.headers.authorization,delete n.headers.cookie,f||delete n.headers.host,n.auth=void 0)}n.auth&&!s.headers.authorization&&(s.headers.authorization="Basic "+Buffer.from(n.auth).toString("base64")),m=i._req=d(n),i._redirects&&i.emit("redirect",i.url,m)}else m=i._req=d(n);n.timeout&&m.on("timeout",()=>{Ke(i,m,"Opening handshake has timed out")}),m.on("error",f=>{m===null||m[to]||(m=i._req=null,Dn(i,f))}),m.on("response",f=>{let g=f.headers.location,v=f.statusCode;if(g&&n.followRedirects&&v>=300&&v<400){if(++i._redirects>n.maxRedirects){Ke(i,m,"Maximum redirects exceeded");return}m.abort();let b;try{b=new La(g,t)}catch{let y=new SyntaxError(`Invalid URL: ${g}`);Dn(i,y);return}so(i,b,e,s)}else i.emit("unexpected-response",m,f)||Ke(i,m,`Unexpected server response: ${f.statusCode}`)}),m.on("upgrade",(f,g,v)=>{if(i.emit("upgrade",f),i.readyState!==be.CONNECTING)return;m=i._req=null;let b=f.headers.upgrade;if(b===void 0||b.toLowerCase()!=="websocket"){Ke(i,g,"Invalid Upgrade header");return}let k=Hc("sha1").update(h+Yc).digest("base64");if(f.headers["sec-websocket-accept"]!==k){Ke(i,g,"Invalid Sec-WebSocket-Accept header");return}let y=f.headers["sec-websocket-protocol"],S;if(y!==void 0?u.size?u.has(y)||(S="Server sent an invalid subprotocol"):S="Server sent a subprotocol but none was requested":u.size&&(S="Server sent no subprotocol"),S){Ke(i,g,S);return}y&&(i._protocol=y);let T=f.headers["sec-websocket-extensions"];if(T!==void 0){if(!p){Ke(i,g,"Server sent a Sec-WebSocket-Extensions header but no extension was requested");return}let _;try{_=ed(T)}catch{Ke(i,g,"Invalid Sec-WebSocket-Extensions header");return}let D=Object.keys(_);if(D.length!==1||D[0]!==xt.extensionName){Ke(i,g,"Server indicated an extension that was not requested");return}try{p.accept(_[xt.extensionName])}catch{Ke(i,g,"Invalid Sec-WebSocket-Extensions header");return}i._extensions[xt.extensionName]=p}i.setSocket(g,v,{allowSynchronousEvents:n.allowSynchronousEvents,generateMask:n.generateMask,maxBufferedChunks:n.maxBufferedChunks,maxFragments:n.maxFragments,maxPayload:n.maxPayload,skipUTF8Validation:n.skipUTF8Validation})}),n.finishRequest?n.finishRequest(m,i):m.end()}function Dn(i,t){i._readyState=be.CLOSING,i._errorEmitted=!0,i.emit("error",t),i.emitClose()}function nd(i){return i.path=i.socketPath,Zr.connect(i)}function ad(i){return i.path=void 0,!i.servername&&i.servername!==""&&(i.servername=Zr.isIP(i.host)?"":i.host),jc.connect(i)}function Ke(i,t,e){i._readyState=be.CLOSING;let s=new Error(e);Error.captureStackTrace(s,Ke),t.setHeader?(t[to]=!0,t.abort(),t.socket&&!t.socket.destroyed&&t.socket.destroy(),process.nextTick(Dn,i,s)):(t.destroy(s),t.once("error",i.emit.bind(i,"error")),t.once("close",i.emitClose.bind(i)))}function Na(i,t,e){if(t){let s=Gc(t)?t.size:td(t).length;i._socket?i._sender._bufferedBytes+=s:i._bufferedAmount+=s}if(e){let s=new Error(`WebSocket is not open: readyState ${i.readyState} (${yt[i.readyState]})`);process.nextTick(e,s)}}function id(i,t){let e=this[Ie];e._closeFrameReceived=!0,e._closeMessage=t,e._closeCode=i,e._socket[Ie]!==void 0&&(e._socket.removeListener("data",In),process.nextTick(no,e._socket),i===1005?e.close():e.close(i,t))}function rd(){let i=this[Ie];i.isPaused||i._socket.resume()}function od(i){let t=this[Ie];t._socket[Ie]!==void 0&&(t._socket.removeListener("data",In),process.nextTick(no,t._socket),t.close(i[Jc])),t._errorEmitted||(t._errorEmitted=!0,t.emit("error",i))}function Qr(){this[Ie].emitClose()}function ld(i,t){this[Ie].emit("message",i,t)}function cd(i){let t=this[Ie];t._autoPong&&t.pong(i,!this._isServer,eo),t.emit("ping",i)}function dd(i){this[Ie].emit("pong",i)}function no(i){i.resume()}function hd(i){let t=this[Ie];t.readyState!==be.CLOSED&&(t.readyState===be.OPEN&&(t._readyState=be.CLOSING,ao(t)),this._socket.end(),t._errorEmitted||(t._errorEmitted=!0,t.emit("error",i)))}function ao(i){i._closeTimer=setTimeout(i._socket.destroy.bind(i._socket),i._closeTimeout)}function io(){let i=this[Ie];if(this.removeListener("close",io),this.removeListener("data",In),this.removeListener("end",ro),i._readyState=be.CLOSING,!this._readableState.endEmitted&&!i._closeFrameReceived&&!i._receiver._writableState.errorEmitted&&this._readableState.length!==0){let t=this.read(this._readableState.length);i._receiver.write(t)}i._receiver.end(),this[Ie]=void 0,clearTimeout(i._closeTimer),i._receiver._writableState.finished||i._receiver._writableState.errorEmitted?i.emitClose():(i._receiver.on("error",Qr),i._receiver.on("finish",Qr))}function In(i){this[Ie]._receiver.write(i)||this.pause()}function ro(){let i=this[Ie];i._readyState=be.CLOSING,i._receiver.end(),this.end()}function oo(){let i=this[Ie];this.removeListener("error",oo),this.on("error",eo),i&&(i._readyState=be.CLOSING,this.destroy())}});var po=qe((Zu,uo)=>{"use strict";var Qu=Mn(),{Duplex:ud}=require("stream");function co(i){i.emit("close")}function pd(){!this.destroyed&&this._writableState.finished&&this.destroy()}function ho(i){this.removeListener("error",ho),this.destroy(),this.listenerCount("error")===0&&this.emit("error",i)}function md(i,t){let e=!0,s=new ud({...t,autoDestroy:!1,emitClose:!1,objectMode:!1,writableObjectMode:!1});return i.on("message",function(a,r){let o=!r&&s._readableState.objectMode?a.toString():a;s.push(o)||i.pause()}),i.once("error",function(a){s.destroyed||(e=!1,s.destroy(a))}),i.once("close",function(){s.destroyed||s.push(null)}),s._destroy=function(n,a){if(i.readyState===i.CLOSED){a(n),process.nextTick(co,s);return}let r=!1;i.once("error",function(c){r=!0,a(c)}),i.once("close",function(){r||a(n),process.nextTick(co,s)}),e&&i.terminate()},s._final=function(n){if(i.readyState===i.CONNECTING){i.once("open",function(){s._final(n)});return}i._socket!==null&&(i._socket._writableState.finished?(n(),s._readableState.endEmitted&&s.destroy()):(i._socket.once("finish",function(){n()}),i.close()))},s._read=function(){i.isPaused&&i.resume()},s._write=function(n,a,r){if(i.readyState===i.CONNECTING){i.once("open",function(){s._write(n,a,r)});return}i.send(n,r)},s.on("end",pd),s.on("error",ho),s}uo.exports=md});var Ba=qe((ep,mo)=>{"use strict";var{tokenChars:fd}=Xt();function gd(i){let t=new Set,e=-1,s=-1,n=0;for(n;n{"use strict";var yd=require("events"),Ln=require("http"),{Duplex:tp}=require("stream"),{createHash:vd}=require("crypto"),fo=Pn(),Mt=Jt(),wd=Ba(),bd=Mn(),{CLOSE_TIMEOUT:kd,GUID:xd,kWebSocket:Sd}=mt(),Cd=/^[+/0-9A-Za-z]{22}==$/,go=0,yo=1,wo=2,Ua=class extends yd{constructor(t,e){if(super(),t={allowSynchronousEvents:!0,autoPong:!0,maxBufferedChunks:1024*1024,maxFragments:128*1024,maxPayload:100*1024*1024,skipUTF8Validation:!1,perMessageDeflate:!1,handleProtocols:null,clientTracking:!0,closeTimeout:kd,verifyClient:null,noServer:!1,backlog:null,server:null,host:null,path:null,port:null,WebSocket:bd,...t},t.port==null&&!t.server&&!t.noServer||t.port!=null&&(t.server||t.noServer)||t.server&&t.noServer)throw new TypeError('One and only one of the "port", "server", or "noServer" options must be specified');if(t.port!=null?(this._server=Ln.createServer((s,n)=>{let a=Ln.STATUS_CODES[426];n.writeHead(426,{"Content-Length":a.length,"Content-Type":"text/plain"}),n.end(a)}),this._server.listen(t.port,t.host,t.backlog,e)):t.server&&(this._server=t.server),this._server){let s=this.emit.bind(this,"connection");this._removeListeners=Td(this._server,{listening:this.emit.bind(this,"listening"),error:this.emit.bind(this,"error"),upgrade:(n,a,r)=>{this.handleUpgrade(n,a,r,s)}})}t.perMessageDeflate===!0&&(t.perMessageDeflate={}),t.clientTracking&&(this.clients=new Set,this._shouldEmitClose=!1),this.options=t,this._state=go}address(){if(this.options.noServer)throw new Error('The server is operating in "noServer" mode');return this._server?this._server.address():null}close(t){if(this._state===wo){t&&this.once("close",()=>{t(new Error("The server is not running"))}),process.nextTick(Es,this);return}if(t&&this.once("close",t),this._state!==yo)if(this._state=yo,this.options.noServer||this.options.server)this._server&&(this._removeListeners(),this._removeListeners=this._server=null),this.clients?this.clients.size?this._shouldEmitClose=!0:process.nextTick(Es,this):process.nextTick(Es,this);else{let e=this._server;this._removeListeners(),this._removeListeners=this._server=null,e.close(()=>{Es(this)})}}shouldHandle(t){if(this.options.path){let e=t.url.indexOf("?");if((e!==-1?t.url.slice(0,e):t.url)!==this.options.path)return!1}return!0}handleUpgrade(t,e,s,n){e.on("error",vo);let a=t.headers["sec-websocket-key"],r=t.headers.upgrade,o=+t.headers["sec-websocket-version"];if(t.method!=="GET"){Lt(this,t,e,405,"Invalid HTTP method");return}if(r===void 0||r.toLowerCase()!=="websocket"){Lt(this,t,e,400,"Invalid Upgrade header");return}if(a===void 0||!Cd.test(a)){Lt(this,t,e,400,"Missing or invalid Sec-WebSocket-Key header");return}if(o!==13&&o!==8){Lt(this,t,e,400,"Missing or invalid Sec-WebSocket-Version header",{"Sec-WebSocket-Version":"13, 8"});return}if(!this.shouldHandle(t)){Ps(e,400);return}let c=t.headers["sec-websocket-protocol"],l=new Set;if(c!==void 0)try{l=wd.parse(c)}catch{Lt(this,t,e,400,"Invalid Sec-WebSocket-Protocol header");return}let h=t.headers["sec-websocket-extensions"],d={};if(this.options.perMessageDeflate&&h!==void 0){let u=new Mt({...this.options.perMessageDeflate,isServer:!0,maxPayload:this.options.maxPayload});try{let p=fo.parse(h);p[Mt.extensionName]&&(u.accept(p[Mt.extensionName]),d[Mt.extensionName]=u)}catch{Lt(this,t,e,400,"Invalid or unacceptable Sec-WebSocket-Extensions header");return}}if(this.options.verifyClient){let u={origin:t.headers[`${o===8?"sec-websocket-origin":"origin"}`],secure:!!(t.socket.authorized||t.socket.encrypted),req:t};if(this.options.verifyClient.length===2){this.options.verifyClient(u,(p,m,f,g)=>{if(!p)return Ps(e,m||401,f,g);this.completeUpgrade(d,a,l,t,e,s,n)});return}if(!this.options.verifyClient(u))return Ps(e,401)}this.completeUpgrade(d,a,l,t,e,s,n)}completeUpgrade(t,e,s,n,a,r,o){if(!a.readable||!a.writable)return a.destroy();if(a[Sd])throw new Error("server.handleUpgrade() was called more than once with the same socket, possibly due to a misconfiguration");if(this._state>go)return Ps(a,503);let l=["HTTP/1.1 101 Switching Protocols","Upgrade: websocket","Connection: Upgrade",`Sec-WebSocket-Accept: ${vd("sha1").update(e+xd).digest("base64")}`],h=new this.options.WebSocket(null,void 0,this.options);if(s.size){let d=this.options.handleProtocols?this.options.handleProtocols(s,n):s.values().next().value;d&&(l.push(`Sec-WebSocket-Protocol: ${d}`),h._protocol=d)}if(t[Mt.extensionName]){let d=t[Mt.extensionName].params,u=fo.format({[Mt.extensionName]:[d]});l.push(`Sec-WebSocket-Extensions: ${u}`),h._extensions=t}this.emit("headers",l,n),a.write(l.concat(`\r +"use strict";var al=Object.create;var $s=Object.defineProperty;var il=Object.getOwnPropertyDescriptor;var rl=Object.getOwnPropertyNames;var ol=Object.getPrototypeOf,ll=Object.prototype.hasOwnProperty;var Ve=(i,t)=>()=>(t||i((t={exports:{}}).exports,t),t.exports),cl=(i,t)=>{for(var e in t)$s(i,e,{get:t[e],enumerable:!0})},ai=(i,t,e,s)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of rl(t))!ll.call(i,n)&&n!==e&&$s(i,n,{get:()=>t[n],enumerable:!(s=il(t,n))||s.enumerable});return i};var Ye=(i,t,e)=>(e=i!=null?al(ol(i)):{},ai(t||!i||!i.__esModule?$s(e,"default",{value:i,enumerable:!0}):e,i)),dl=i=>ai($s({},"__esModule",{value:!0}),i);var gt=Ve((wp,Er)=>{"use strict";var _r=["nodebuffer","arraybuffer","fragments"],Ar=typeof Blob<"u";Ar&&_r.push("blob");Er.exports={BINARY_TYPES:_r,CLOSE_TIMEOUT:3e4,EMPTY_BUFFER:Buffer.alloc(0),GUID:"258EAFA5-E914-47DA-95CA-C5AB0DC85B11",hasBlob:Ar,kForOnEventAttribute:Symbol("kIsForOnEventAttribute"),kListener:Symbol("kListener"),kStatusCode:Symbol("status-code"),kWebSocket:Symbol("websocket"),NOOP:()=>{}}});var _s=Ve((bp,xn)=>{"use strict";var{EMPTY_BUFFER:kc}=gt(),Ca=Buffer[Symbol.species];function xc(i,t){if(i.length===0)return kc;if(i.length===1)return i[0];let e=Buffer.allocUnsafe(t),s=0;for(let n=0;n{"use strict";var Dr=Symbol("kDone"),_a=Symbol("kRun"),Aa=class{constructor(t){this[Dr]=()=>{this.pending--,this[_a]()},this.concurrency=t||1/0,this.jobs=[],this.pending=0}add(t){this.jobs.push(t),this[_a]()}[_a](){if(this.pending!==this.concurrency&&this.jobs.length){let t=this.jobs.shift();this.pending++,t(this[Dr])}}};Ir.exports=Aa});var Xt=Ve((xp,Nr)=>{"use strict";var As=require("zlib"),Lr=_s(),Cc=Mr(),{kStatusCode:Fr}=gt(),Tc=Buffer[Symbol.species],_c=Buffer.from([0,0,255,255]),Cn=Symbol("permessage-deflate"),yt=Symbol("total-length"),Kt=Symbol("callback"),St=Symbol("buffers"),Jt=Symbol("error"),Sn,Ea=class{constructor(t){if(this._options=t||{},this._threshold=this._options.threshold!==void 0?this._options.threshold:1024,this._maxPayload=this._options.maxPayload|0,this._isServer=!!this._options.isServer,this._deflate=null,this._inflate=null,this.params=null,!Sn){let e=this._options.concurrencyLimit!==void 0?this._options.concurrencyLimit:10;Sn=new Cc(e)}}static get extensionName(){return"permessage-deflate"}offer(){let t={};return this._options.serverNoContextTakeover&&(t.server_no_context_takeover=!0),this._options.clientNoContextTakeover&&(t.client_no_context_takeover=!0),this._options.serverMaxWindowBits&&(t.server_max_window_bits=this._options.serverMaxWindowBits),this._options.clientMaxWindowBits?t.client_max_window_bits=this._options.clientMaxWindowBits:this._options.clientMaxWindowBits==null&&(t.client_max_window_bits=!0),t}accept(t){return t=this.normalizeParams(t),this.params=this._isServer?this.acceptAsServer(t):this.acceptAsClient(t),this.params}cleanup(){if(this._inflate&&(this._inflate.close(),this._inflate=null),this._deflate){let t=this._deflate[Kt];this._deflate.close(),this._deflate=null,t&&t(new Error("The deflate stream was closed while data was being processed"))}}acceptAsServer(t){let e=this._options,s=t.find(n=>!(e.serverNoContextTakeover===!1&&n.server_no_context_takeover||n.server_max_window_bits&&(e.serverMaxWindowBits===!1||typeof e.serverMaxWindowBits=="number"&&e.serverMaxWindowBits>n.server_max_window_bits)||typeof e.clientMaxWindowBits=="number"&&!n.client_max_window_bits));if(!s)throw new Error("None of the extension offers can be accepted");return e.serverNoContextTakeover&&(s.server_no_context_takeover=!0),e.clientNoContextTakeover&&(s.client_no_context_takeover=!0),typeof e.serverMaxWindowBits=="number"&&(s.server_max_window_bits=e.serverMaxWindowBits),typeof e.clientMaxWindowBits=="number"?s.client_max_window_bits=e.clientMaxWindowBits:(s.client_max_window_bits===!0||e.clientMaxWindowBits===!1)&&delete s.client_max_window_bits,s}acceptAsClient(t){let e=t[0];if(this._options.clientNoContextTakeover===!1&&e.client_no_context_takeover)throw new Error('Unexpected parameter "client_no_context_takeover"');if(!e.client_max_window_bits)typeof this._options.clientMaxWindowBits=="number"&&(e.client_max_window_bits=this._options.clientMaxWindowBits);else if(this._options.clientMaxWindowBits===!1||typeof this._options.clientMaxWindowBits=="number"&&e.client_max_window_bits>this._options.clientMaxWindowBits)throw new Error('Unexpected or invalid parameter "client_max_window_bits"');return e}normalizeParams(t){return t.forEach(e=>{Object.keys(e).forEach(s=>{let n=e[s];if(n.length>1)throw new Error(`Parameter "${s}" must have only a single value`);if(n=n[0],s==="client_max_window_bits"){if(n!==!0){let a=+n;if(!Number.isInteger(a)||a<8||a>15)throw new TypeError(`Invalid value for parameter "${s}": ${n}`);n=a}else if(!this._isServer)throw new TypeError(`Invalid value for parameter "${s}": ${n}`)}else if(s==="server_max_window_bits"){let a=+n;if(!Number.isInteger(a)||a<8||a>15)throw new TypeError(`Invalid value for parameter "${s}": ${n}`);n=a}else if(s==="client_no_context_takeover"||s==="server_no_context_takeover"){if(n!==!0)throw new TypeError(`Invalid value for parameter "${s}": ${n}`)}else throw new Error(`Unknown parameter "${s}"`);e[s]=n})}),t}decompress(t,e,s){Sn.add(n=>{this._decompress(t,e,(a,r)=>{n(),s(a,r)})})}compress(t,e,s){Sn.add(n=>{this._compress(t,e,(a,r)=>{n(),s(a,r)})})}_decompress(t,e,s){let n=this._isServer?"client":"server";if(!this._inflate){let a=`${n}_max_window_bits`,r=typeof this.params[a]!="number"?As.Z_DEFAULT_WINDOWBITS:this.params[a];this._inflate=As.createInflateRaw({...this._options.zlibInflateOptions,windowBits:r}),this._inflate[Cn]=this,this._inflate[yt]=0,this._inflate[St]=[],this._inflate.on("error",Ec),this._inflate.on("data",Or)}this._inflate[Kt]=s,this._inflate.write(t),e&&this._inflate.write(_c),this._inflate.flush(()=>{let a=this._inflate[Jt];if(a){this._inflate.close(),this._inflate=null,s(a);return}let r=Lr.concat(this._inflate[St],this._inflate[yt]);this._inflate._readableState.endEmitted?(this._inflate.close(),this._inflate=null):(this._inflate[yt]=0,this._inflate[St]=[],e&&this.params[`${n}_no_context_takeover`]&&this._inflate.reset()),s(null,r)})}_compress(t,e,s){let n=this._isServer?"server":"client";if(!this._deflate){let a=`${n}_max_window_bits`,r=typeof this.params[a]!="number"?As.Z_DEFAULT_WINDOWBITS:this.params[a];this._deflate=As.createDeflateRaw({...this._options.zlibDeflateOptions,windowBits:r}),this._deflate[yt]=0,this._deflate[St]=[],this._deflate.on("data",Ac)}this._deflate[Kt]=s,this._deflate.write(t),this._deflate.flush(As.Z_SYNC_FLUSH,()=>{if(!this._deflate)return;let a=Lr.concat(this._deflate[St],this._deflate[yt]);e&&(a=new Tc(a.buffer,a.byteOffset,a.length-4)),this._deflate[Kt]=null,this._deflate[yt]=0,this._deflate[St]=[],e&&this.params[`${n}_no_context_takeover`]&&this._deflate.reset(),s(null,a)})}};Nr.exports=Ea;function Ac(i){this[St].push(i),this[yt]+=i.length}function Or(i){if(this[yt]+=i.length,this[Cn]._maxPayload<1||this[yt]<=this[Cn]._maxPayload){this[St].push(i);return}this[Jt]=new RangeError("Max payload size exceeded"),this[Jt].code="WS_ERR_UNSUPPORTED_MESSAGE_LENGTH",this[Jt][Fr]=1009,this.removeListener("data",Or),this.reset()}function Ec(i){if(this[Cn]._inflate=null,this[Jt]){this[Kt](this[Jt]);return}i[Fr]=1007,this[Kt](i)}});var Qt=Ve((Sp,Tn)=>{"use strict";var{isUtf8:Br}=require("buffer"),{hasBlob:Pc}=gt(),Rc=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,1,1,1,1,0,0,1,1,0,1,1,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0,1,0];function Dc(i){return i>=1e3&&i<=1014&&i!==1004&&i!==1005&&i!==1006||i>=3e3&&i<=4999}function Pa(i){let t=i.length,e=0;for(;e=t||(i[e+1]&192)!==128||(i[e+2]&192)!==128||i[e]===224&&(i[e+1]&224)===128||i[e]===237&&(i[e+1]&224)===160)return!1;e+=3}else if((i[e]&248)===240){if(e+3>=t||(i[e+1]&192)!==128||(i[e+2]&192)!==128||(i[e+3]&192)!==128||i[e]===240&&(i[e+1]&240)===128||i[e]===244&&i[e+1]>143||i[e]>244)return!1;e+=4}else return!1;return!0}function Ic(i){return Pc&&typeof i=="object"&&typeof i.arrayBuffer=="function"&&typeof i.type=="string"&&typeof i.stream=="function"&&(i[Symbol.toStringTag]==="Blob"||i[Symbol.toStringTag]==="File")}Tn.exports={isBlob:Ic,isValidStatusCode:Dc,isValidUTF8:Pa,tokenChars:Rc};if(Br)Tn.exports.isValidUTF8=function(i){return i.length<24?Pa(i):Br(i)};else if(!process.env.WS_NO_UTF_8_VALIDATE)try{let i=require("utf-8-validate");Tn.exports.isValidUTF8=function(t){return t.length<32?Pa(t):i(t)}}catch{}});var La=Ve((Cp,zr)=>{"use strict";var{Writable:Mc}=require("stream"),Ur=Xt(),{BINARY_TYPES:Lc,EMPTY_BUFFER:$r,kStatusCode:Fc,kWebSocket:Oc}=gt(),{concat:Ra,toArrayBuffer:Nc,unmask:Bc}=_s(),{isValidStatusCode:Uc,isValidUTF8:jr}=Qt(),_n=Buffer[Symbol.species],et=0,Wr=1,Hr=2,qr=3,Da=4,Ia=5,An=6,Ma=class extends Mc{constructor(t={}){super(),this._allowSynchronousEvents=t.allowSynchronousEvents!==void 0?t.allowSynchronousEvents:!0,this._binaryType=t.binaryType||Lc[0],this._extensions=t.extensions||{},this._isServer=!!t.isServer,this._maxBufferedChunks=t.maxBufferedChunks|0,this._maxFragments=t.maxFragments|0,this._maxPayload=t.maxPayload|0,this._skipUTF8Validation=!!t.skipUTF8Validation,this[Oc]=void 0,this._bufferedBytes=0,this._buffers=[],this._compressed=!1,this._payloadLength=0,this._mask=void 0,this._fragmented=0,this._masked=!1,this._fin=!1,this._opcode=0,this._totalPayloadLength=0,this._messageLength=0,this._fragments=[],this._errored=!1,this._loop=!1,this._state=et}_write(t,e,s){if(this._opcode===8&&this._state==et)return s();if(this._maxBufferedChunks>0&&this._buffers.length>=this._maxBufferedChunks){s(this.createError(RangeError,"Too many buffered chunks",!1,1008,"WS_ERR_TOO_MANY_BUFFERED_PARTS"));return}this._bufferedBytes+=t.length,this._buffers.push(t),this.startLoop(s)}consume(t){if(this._bufferedBytes-=t,t===this._buffers[0].length)return this._buffers.shift();if(t=s.length?e.set(this._buffers.shift(),n):(e.set(new Uint8Array(s.buffer,s.byteOffset,t),n),this._buffers[0]=new _n(s.buffer,s.byteOffset+t,s.length-t)),t-=s.length}while(t>0);return e}startLoop(t){this._loop=!0;do switch(this._state){case et:this.getInfo(t);break;case Wr:this.getPayloadLength16(t);break;case Hr:this.getPayloadLength64(t);break;case qr:this.getMask();break;case Da:this.getData(t);break;case Ia:case An:this._loop=!1;return}while(this._loop);this._errored||t()}getInfo(t){if(this._bufferedBytes<2){this._loop=!1;return}let e=this.consume(2);if((e[0]&48)!==0){let n=this.createError(RangeError,"RSV2 and RSV3 must be clear",!0,1002,"WS_ERR_UNEXPECTED_RSV_2_3");t(n);return}let s=(e[0]&64)===64;if(s&&!this._extensions[Ur.extensionName]){let n=this.createError(RangeError,"RSV1 must be clear",!0,1002,"WS_ERR_UNEXPECTED_RSV_1");t(n);return}if(this._fin=(e[0]&128)===128,this._opcode=e[0]&15,this._payloadLength=e[1]&127,this._opcode===0){if(s){let n=this.createError(RangeError,"RSV1 must be clear",!0,1002,"WS_ERR_UNEXPECTED_RSV_1");t(n);return}if(!this._fragmented){let n=this.createError(RangeError,"invalid opcode 0",!0,1002,"WS_ERR_INVALID_OPCODE");t(n);return}this._opcode=this._fragmented}else if(this._opcode===1||this._opcode===2){if(this._fragmented){let n=this.createError(RangeError,`invalid opcode ${this._opcode}`,!0,1002,"WS_ERR_INVALID_OPCODE");t(n);return}this._compressed=s}else if(this._opcode>7&&this._opcode<11){if(!this._fin){let n=this.createError(RangeError,"FIN must be set",!0,1002,"WS_ERR_EXPECTED_FIN");t(n);return}if(s){let n=this.createError(RangeError,"RSV1 must be clear",!0,1002,"WS_ERR_UNEXPECTED_RSV_1");t(n);return}if(this._payloadLength>125||this._opcode===8&&this._payloadLength===1){let n=this.createError(RangeError,`invalid payload length ${this._payloadLength}`,!0,1002,"WS_ERR_INVALID_CONTROL_PAYLOAD_LENGTH");t(n);return}}else{let n=this.createError(RangeError,`invalid opcode ${this._opcode}`,!0,1002,"WS_ERR_INVALID_OPCODE");t(n);return}if(!this._fin&&!this._fragmented&&(this._fragmented=this._opcode),this._masked=(e[1]&128)===128,this._isServer){if(!this._masked){let n=this.createError(RangeError,"MASK must be set",!0,1002,"WS_ERR_EXPECTED_MASK");t(n);return}}else if(this._masked){let n=this.createError(RangeError,"MASK must be clear",!0,1002,"WS_ERR_UNEXPECTED_MASK");t(n);return}this._payloadLength===126?this._state=Wr:this._payloadLength===127?this._state=Hr:this.haveLength(t)}getPayloadLength16(t){if(this._bufferedBytes<2){this._loop=!1;return}this._payloadLength=this.consume(2).readUInt16BE(0),this.haveLength(t)}getPayloadLength64(t){if(this._bufferedBytes<8){this._loop=!1;return}let e=this.consume(8),s=e.readUInt32BE(0);if(s>Math.pow(2,21)-1){let n=this.createError(RangeError,"Unsupported WebSocket frame: payload length > 2^53 - 1",!1,1009,"WS_ERR_UNSUPPORTED_DATA_PAYLOAD_LENGTH");t(n);return}this._payloadLength=s*Math.pow(2,32)+e.readUInt32BE(4),this.haveLength(t)}haveLength(t){if(this._payloadLength&&this._opcode<8&&(this._totalPayloadLength+=this._payloadLength,this._totalPayloadLength>this._maxPayload&&this._maxPayload>0)){let e=this.createError(RangeError,"Max payload size exceeded",!1,1009,"WS_ERR_UNSUPPORTED_MESSAGE_LENGTH");t(e);return}this._masked?this._state=qr:this._state=Da}getMask(){if(this._bufferedBytes<4){this._loop=!1;return}this._mask=this.consume(4),this._state=Da}getData(t){let e=$r;if(this._payloadLength){if(this._bufferedBytes7){this.controlMessage(e,t);return}if(this._compressed){this._state=Ia,this.decompress(e,t);return}if(e.length){if(this._maxFragments>0&&this._fragments.length>=this._maxFragments){let s=this.createError(RangeError,"Too many message fragments",!1,1008,"WS_ERR_TOO_MANY_BUFFERED_PARTS");t(s);return}this._messageLength=this._totalPayloadLength,this._fragments.push(e)}this.dataMessage(t)}decompress(t,e){this._extensions[Ur.extensionName].decompress(t,this._fin,(n,a)=>{if(n)return e(n);if(a.length){if(this._messageLength+=a.length,this._messageLength>this._maxPayload&&this._maxPayload>0){let r=this.createError(RangeError,"Max payload size exceeded",!1,1009,"WS_ERR_UNSUPPORTED_MESSAGE_LENGTH");e(r);return}if(this._maxFragments>0&&this._fragments.length>=this._maxFragments){let r=this.createError(RangeError,"Too many message fragments",!1,1008,"WS_ERR_TOO_MANY_BUFFERED_PARTS");e(r);return}this._fragments.push(a)}this.dataMessage(e),this._state===et&&this.startLoop(e)})}dataMessage(t){if(!this._fin){this._state=et;return}let e=this._messageLength,s=this._fragments;if(this._totalPayloadLength=0,this._messageLength=0,this._fragmented=0,this._fragments=[],this._opcode===2){let n;this._binaryType==="nodebuffer"?n=Ra(s,e):this._binaryType==="arraybuffer"?n=Nc(Ra(s,e)):this._binaryType==="blob"?n=new Blob(s):n=s,this._allowSynchronousEvents?(this.emit("message",n,!0),this._state=et):(this._state=An,setImmediate(()=>{this.emit("message",n,!0),this._state=et,this.startLoop(t)}))}else{let n=Ra(s,e);if(!this._skipUTF8Validation&&!jr(n)){let a=this.createError(Error,"invalid UTF-8 sequence",!0,1007,"WS_ERR_INVALID_UTF8");t(a);return}this._state===Ia||this._allowSynchronousEvents?(this.emit("message",n,!1),this._state=et):(this._state=An,setImmediate(()=>{this.emit("message",n,!1),this._state=et,this.startLoop(t)}))}}controlMessage(t,e){if(this._opcode===8){if(t.length===0)this._loop=!1,this.emit("conclude",1005,$r),this.end();else{let s=t.readUInt16BE(0);if(!Uc(s)){let a=this.createError(RangeError,`invalid status code ${s}`,!0,1002,"WS_ERR_INVALID_CLOSE_CODE");e(a);return}let n=new _n(t.buffer,t.byteOffset+2,t.length-2);if(!this._skipUTF8Validation&&!jr(n)){let a=this.createError(Error,"invalid UTF-8 sequence",!0,1007,"WS_ERR_INVALID_UTF8");e(a);return}this._loop=!1,this.emit("conclude",s,n),this.end()}this._state=et;return}this._allowSynchronousEvents?(this.emit(this._opcode===9?"ping":"pong",t),this._state=et):(this._state=An,setImmediate(()=>{this.emit(this._opcode===9?"ping":"pong",t),this._state=et,this.startLoop(e)}))}createError(t,e,s,n,a){this._loop=!1,this._errored=!0;let r=new t(s?`Invalid WebSocket frame: ${e}`:e);return Error.captureStackTrace(r,this.createError),r.code=a,r[Fc]=n,r}};zr.exports=Ma});var Na=Ve((_p,Yr)=>{"use strict";var{Duplex:Tp}=require("stream"),{randomFillSync:$c}=require("crypto"),{types:{isUint8Array:jc}}=require("util"),Gr=Xt(),{EMPTY_BUFFER:Wc,kWebSocket:Hc,NOOP:qc}=gt(),{isBlob:Zt,isValidStatusCode:zc}=Qt(),{mask:Vr,toBuffer:It}=_s(),tt=Symbol("kByteLength"),Gc=Buffer.alloc(4),En=8*1024,Mt,es=En,lt=0,Vc=1,Yc=2,Fa=class i{constructor(t,e,s){this._extensions=e||{},s&&(this._generateMask=s,this._maskBuffer=Buffer.alloc(4)),this._socket=t,this._firstFragment=!0,this._compress=!1,this._bufferedBytes=0,this._queue=[],this._state=lt,this.onerror=qc,this[Hc]=void 0}static frame(t,e){let s,n=!1,a=2,r=!1;e.mask&&(s=e.maskBuffer||Gc,e.generateMask?e.generateMask(s):(es===En&&(Mt===void 0&&(Mt=Buffer.alloc(En)),$c(Mt,0,En),es=0),s[0]=Mt[es++],s[1]=Mt[es++],s[2]=Mt[es++],s[3]=Mt[es++]),r=(s[0]|s[1]|s[2]|s[3])===0,a=6);let o;typeof t=="string"?(!e.mask||r)&&e[tt]!==void 0?o=e[tt]:(t=Buffer.from(t),o=t.length):(o=t.length,n=e.mask&&e.readOnly&&!r);let c=o;o>=65536?(a+=8,c=127):o>125&&(a+=2,c=126);let l=Buffer.allocUnsafe(n?o+a:a);return l[0]=e.fin?e.opcode|128:e.opcode,e.rsv1&&(l[0]|=64),l[1]=c,c===126?l.writeUInt16BE(o,2):c===127&&(l[2]=l[3]=0,l.writeUIntBE(o,4,6)),e.mask?(l[1]|=128,l[a-4]=s[0],l[a-3]=s[1],l[a-2]=s[2],l[a-1]=s[3],r?[l,t]:n?(Vr(t,s,l,a,o),[l]):(Vr(t,s,t,0,o),[l,t])):[l,t]}close(t,e,s,n){let a;if(t===void 0)a=Wc;else{if(typeof t!="number"||!zc(t))throw new TypeError("First argument must be a valid error code number");if(e===void 0||!e.length)a=Buffer.allocUnsafe(2),a.writeUInt16BE(t,0);else{let o=Buffer.byteLength(e);if(o>123)throw new RangeError("The message must not be greater than 123 bytes");if(a=Buffer.allocUnsafe(2+o),a.writeUInt16BE(t,0),typeof e=="string")a.write(e,2);else if(jc(e))a.set(e,2);else throw new TypeError("Second argument must be a string or a Uint8Array")}}let r={[tt]:a.length,fin:!0,generateMask:this._generateMask,mask:s,maskBuffer:this._maskBuffer,opcode:8,readOnly:!1,rsv1:!1};this._state!==lt?this.enqueue([this.dispatch,a,!1,r,n]):this.sendFrame(i.frame(a,r),n)}ping(t,e,s){let n,a;if(typeof t=="string"?(n=Buffer.byteLength(t),a=!1):Zt(t)?(n=t.size,a=!1):(t=It(t),n=t.length,a=It.readOnly),n>125)throw new RangeError("The data size must not be greater than 125 bytes");let r={[tt]:n,fin:!0,generateMask:this._generateMask,mask:e,maskBuffer:this._maskBuffer,opcode:9,readOnly:a,rsv1:!1};Zt(t)?this._state!==lt?this.enqueue([this.getBlobData,t,!1,r,s]):this.getBlobData(t,!1,r,s):this._state!==lt?this.enqueue([this.dispatch,t,!1,r,s]):this.sendFrame(i.frame(t,r),s)}pong(t,e,s){let n,a;if(typeof t=="string"?(n=Buffer.byteLength(t),a=!1):Zt(t)?(n=t.size,a=!1):(t=It(t),n=t.length,a=It.readOnly),n>125)throw new RangeError("The data size must not be greater than 125 bytes");let r={[tt]:n,fin:!0,generateMask:this._generateMask,mask:e,maskBuffer:this._maskBuffer,opcode:10,readOnly:a,rsv1:!1};Zt(t)?this._state!==lt?this.enqueue([this.getBlobData,t,!1,r,s]):this.getBlobData(t,!1,r,s):this._state!==lt?this.enqueue([this.dispatch,t,!1,r,s]):this.sendFrame(i.frame(t,r),s)}send(t,e,s){let n=this._extensions[Gr.extensionName],a=e.binary?2:1,r=e.compress,o,c;typeof t=="string"?(o=Buffer.byteLength(t),c=!1):Zt(t)?(o=t.size,c=!1):(t=It(t),o=t.length,c=It.readOnly),this._firstFragment?(this._firstFragment=!1,r&&n&&n.params[n._isServer?"server_no_context_takeover":"client_no_context_takeover"]&&(r=o>=n._threshold),this._compress=r):(r=!1,a=0),e.fin&&(this._firstFragment=!0);let l={[tt]:o,fin:e.fin,generateMask:this._generateMask,mask:e.mask,maskBuffer:this._maskBuffer,opcode:a,readOnly:c,rsv1:r};Zt(t)?this._state!==lt?this.enqueue([this.getBlobData,t,this._compress,l,s]):this.getBlobData(t,this._compress,l,s):this._state!==lt?this.enqueue([this.dispatch,t,this._compress,l,s]):this.dispatch(t,this._compress,l,s)}getBlobData(t,e,s,n){this._bufferedBytes+=s[tt],this._state=Yc,t.arrayBuffer().then(a=>{if(this._socket.destroyed){let o=new Error("The socket was closed while the blob was being read");process.nextTick(Oa,this,o,n);return}this._bufferedBytes-=s[tt];let r=It(a);e?this.dispatch(r,e,s,n):(this._state=lt,this.sendFrame(i.frame(r,s),n),this.dequeue())}).catch(a=>{process.nextTick(Kc,this,a,n)})}dispatch(t,e,s,n){if(!e){this.sendFrame(i.frame(t,s),n);return}let a=this._extensions[Gr.extensionName];this._bufferedBytes+=s[tt],this._state=Vc,a.compress(t,s.fin,(r,o)=>{if(this._socket.destroyed){let c=new Error("The socket was closed while data was being compressed");Oa(this,c,n);return}this._bufferedBytes-=s[tt],this._state=lt,s.readOnly=!1,this.sendFrame(i.frame(o,s),n),this.dequeue()})}dequeue(){for(;this._state===lt&&this._queue.length;){let t=this._queue.shift();this._bufferedBytes-=t[3][tt],Reflect.apply(t[0],this,t.slice(1))}}enqueue(t){this._bufferedBytes+=t[3][tt],this._queue.push(t)}sendFrame(t,e){t.length===2?(this._socket.cork(),this._socket.write(t[0]),this._socket.write(t[1],e),this._socket.uncork()):this._socket.write(t[0],e)}};Yr.exports=Fa;function Oa(i,t,e){typeof e=="function"&&e(t);for(let s=0;s{"use strict";var{kForOnEventAttribute:Es,kListener:Ba}=gt(),Kr=Symbol("kCode"),Jr=Symbol("kData"),Xr=Symbol("kError"),Qr=Symbol("kMessage"),Zr=Symbol("kReason"),ts=Symbol("kTarget"),eo=Symbol("kType"),to=Symbol("kWasClean"),vt=class{constructor(t){this[ts]=null,this[eo]=t}get target(){return this[ts]}get type(){return this[eo]}};Object.defineProperty(vt.prototype,"target",{enumerable:!0});Object.defineProperty(vt.prototype,"type",{enumerable:!0});var Lt=class extends vt{constructor(t,e={}){super(t),this[Kr]=e.code===void 0?0:e.code,this[Zr]=e.reason===void 0?"":e.reason,this[to]=e.wasClean===void 0?!1:e.wasClean}get code(){return this[Kr]}get reason(){return this[Zr]}get wasClean(){return this[to]}};Object.defineProperty(Lt.prototype,"code",{enumerable:!0});Object.defineProperty(Lt.prototype,"reason",{enumerable:!0});Object.defineProperty(Lt.prototype,"wasClean",{enumerable:!0});var ss=class extends vt{constructor(t,e={}){super(t),this[Xr]=e.error===void 0?null:e.error,this[Qr]=e.message===void 0?"":e.message}get error(){return this[Xr]}get message(){return this[Qr]}};Object.defineProperty(ss.prototype,"error",{enumerable:!0});Object.defineProperty(ss.prototype,"message",{enumerable:!0});var Ps=class extends vt{constructor(t,e={}){super(t),this[Jr]=e.data===void 0?null:e.data}get data(){return this[Jr]}};Object.defineProperty(Ps.prototype,"data",{enumerable:!0});var Jc={addEventListener(i,t,e={}){for(let n of this.listeners(i))if(!e[Es]&&n[Ba]===t&&!n[Es])return;let s;if(i==="message")s=function(a,r){let o=new Ps("message",{data:r?a:a.toString()});o[ts]=this,Pn(t,this,o)};else if(i==="close")s=function(a,r){let o=new Lt("close",{code:a,reason:r.toString(),wasClean:this._closeFrameReceived&&this._closeFrameSent});o[ts]=this,Pn(t,this,o)};else if(i==="error")s=function(a){let r=new ss("error",{error:a,message:a.message});r[ts]=this,Pn(t,this,r)};else if(i==="open")s=function(){let a=new vt("open");a[ts]=this,Pn(t,this,a)};else return;s[Es]=!!e[Es],s[Ba]=t,e.once?this.once(i,s):this.on(i,s)},removeEventListener(i,t){for(let e of this.listeners(i))if(e[Ba]===t&&!e[Es]){this.removeListener(i,e);break}}};so.exports={CloseEvent:Lt,ErrorEvent:ss,Event:vt,EventTarget:Jc,MessageEvent:Ps};function Pn(i,t,e){typeof i=="object"&&i.handleEvent?i.handleEvent.call(i,e):i.call(t,e)}});var Rn=Ve((Ep,ao)=>{"use strict";var{tokenChars:Rs}=Qt();function pt(i,t,e){i[t]===void 0?i[t]=[e]:i[t].push(e)}function Xc(i){let t=Object.create(null),e=Object.create(null),s=!1,n=!1,a=!1,r,o,c=-1,l=-1,h=-1,d=0;for(;d{let e=i[t];return Array.isArray(e)||(e=[e]),e.map(s=>[t].concat(Object.keys(s).map(n=>{let a=s[n];return Array.isArray(a)||(a=[a]),a.map(r=>r===!0?n:`${n}=${r}`).join("; ")})).join("; ")).join(", ")}).join(", ")}ao.exports={format:Qc,parse:Xc}});var Ln=Ve((Dp,yo)=>{"use strict";var Zc=require("events"),ed=require("https"),td=require("http"),oo=require("net"),sd=require("tls"),{randomBytes:nd,createHash:ad}=require("crypto"),{Duplex:Pp,Readable:Rp}=require("stream"),{URL:Ua}=require("url"),Ct=Xt(),id=La(),rd=Na(),{isBlob:od}=Qt(),{BINARY_TYPES:io,CLOSE_TIMEOUT:ld,EMPTY_BUFFER:Dn,GUID:cd,kForOnEventAttribute:$a,kListener:dd,kStatusCode:hd,kWebSocket:De,NOOP:lo}=gt(),{EventTarget:{addEventListener:ud,removeEventListener:pd}}=no(),{format:md,parse:fd}=Rn(),{toBuffer:gd}=_s(),co=Symbol("kAborted"),ja=[8,13],wt=["CONNECTING","OPEN","CLOSING","CLOSED"],yd=/^[!#$%&'*+\-.0-9A-Z^_`|a-z~]+$/,be=class i extends Zc{constructor(t,e,s){super(),this._binaryType=io[0],this._closeCode=1006,this._closeFrameReceived=!1,this._closeFrameSent=!1,this._closeMessage=Dn,this._closeTimer=null,this._errorEmitted=!1,this._extensions={},this._paused=!1,this._protocol="",this._readyState=i.CONNECTING,this._receiver=null,this._sender=null,this._socket=null,t!==null?(this._bufferedAmount=0,this._isServer=!1,this._redirects=0,e===void 0?e=[]:Array.isArray(e)||(typeof e=="object"&&e!==null?(s=e,e=[]):e=[e]),ho(this,t,e,s)):(this._autoPong=s.autoPong,this._closeTimeout=s.closeTimeout,this._isServer=!0)}get binaryType(){return this._binaryType}set binaryType(t){io.includes(t)&&(this._binaryType=t,this._receiver&&(this._receiver._binaryType=t))}get bufferedAmount(){return this._socket?this._socket._writableState.length+this._sender._bufferedBytes:this._bufferedAmount}get extensions(){return Object.keys(this._extensions).join()}get isPaused(){return this._paused}get onclose(){return null}get onerror(){return null}get onopen(){return null}get onmessage(){return null}get protocol(){return this._protocol}get readyState(){return this._readyState}get url(){return this._url}setSocket(t,e,s){let n=new id({allowSynchronousEvents:s.allowSynchronousEvents,binaryType:this.binaryType,extensions:this._extensions,isServer:this._isServer,maxBufferedChunks:s.maxBufferedChunks,maxFragments:s.maxFragments,maxPayload:s.maxPayload,skipUTF8Validation:s.skipUTF8Validation}),a=new rd(t,this._extensions,s.generateMask);this._receiver=n,this._sender=a,this._socket=t,n[De]=this,a[De]=this,t[De]=this,n.on("conclude",bd),n.on("drain",kd),n.on("error",xd),n.on("message",Sd),n.on("ping",Cd),n.on("pong",Td),a.onerror=_d,t.setTimeout&&t.setTimeout(0),t.setNoDelay&&t.setNoDelay(),e.length>0&&t.unshift(e),t.on("close",mo),t.on("data",Mn),t.on("end",fo),t.on("error",go),this._readyState=i.OPEN,this.emit("open")}emitClose(){if(!this._socket){this._readyState=i.CLOSED,this.emit("close",this._closeCode,this._closeMessage);return}this._extensions[Ct.extensionName]&&this._extensions[Ct.extensionName].cleanup(),this._receiver.removeAllListeners(),this._readyState=i.CLOSED,this.emit("close",this._closeCode,this._closeMessage)}close(t,e){if(this.readyState!==i.CLOSED){if(this.readyState===i.CONNECTING){Qe(this,this._req,"WebSocket was closed before the connection was established");return}if(this.readyState===i.CLOSING){this._closeFrameSent&&(this._closeFrameReceived||this._receiver._writableState.errorEmitted)&&this._socket.end();return}this._readyState=i.CLOSING,this._sender.close(t,e,!this._isServer,s=>{s||(this._closeFrameSent=!0,(this._closeFrameReceived||this._receiver._writableState.errorEmitted)&&this._socket.end())}),po(this)}}pause(){this.readyState===i.CONNECTING||this.readyState===i.CLOSED||(this._paused=!0,this._socket.pause())}ping(t,e,s){if(this.readyState===i.CONNECTING)throw new Error("WebSocket is not open: readyState 0 (CONNECTING)");if(typeof t=="function"?(s=t,t=e=void 0):typeof e=="function"&&(s=e,e=void 0),typeof t=="number"&&(t=t.toString()),this.readyState!==i.OPEN){Wa(this,t,s);return}e===void 0&&(e=!this._isServer),this._sender.ping(t||Dn,e,s)}pong(t,e,s){if(this.readyState===i.CONNECTING)throw new Error("WebSocket is not open: readyState 0 (CONNECTING)");if(typeof t=="function"?(s=t,t=e=void 0):typeof e=="function"&&(s=e,e=void 0),typeof t=="number"&&(t=t.toString()),this.readyState!==i.OPEN){Wa(this,t,s);return}e===void 0&&(e=!this._isServer),this._sender.pong(t||Dn,e,s)}resume(){this.readyState===i.CONNECTING||this.readyState===i.CLOSED||(this._paused=!1,this._receiver._writableState.needDrain||this._socket.resume())}send(t,e,s){if(this.readyState===i.CONNECTING)throw new Error("WebSocket is not open: readyState 0 (CONNECTING)");if(typeof e=="function"&&(s=e,e={}),typeof t=="number"&&(t=t.toString()),this.readyState!==i.OPEN){Wa(this,t,s);return}let n={binary:typeof t!="string",mask:!this._isServer,compress:!0,fin:!0,...e};this._extensions[Ct.extensionName]||(n.compress=!1),this._sender.send(t||Dn,n,s)}terminate(){if(this.readyState!==i.CLOSED){if(this.readyState===i.CONNECTING){Qe(this,this._req,"WebSocket was closed before the connection was established");return}this._socket&&(this._readyState=i.CLOSING,this._socket.destroy())}}};Object.defineProperty(be,"CONNECTING",{enumerable:!0,value:wt.indexOf("CONNECTING")});Object.defineProperty(be.prototype,"CONNECTING",{enumerable:!0,value:wt.indexOf("CONNECTING")});Object.defineProperty(be,"OPEN",{enumerable:!0,value:wt.indexOf("OPEN")});Object.defineProperty(be.prototype,"OPEN",{enumerable:!0,value:wt.indexOf("OPEN")});Object.defineProperty(be,"CLOSING",{enumerable:!0,value:wt.indexOf("CLOSING")});Object.defineProperty(be.prototype,"CLOSING",{enumerable:!0,value:wt.indexOf("CLOSING")});Object.defineProperty(be,"CLOSED",{enumerable:!0,value:wt.indexOf("CLOSED")});Object.defineProperty(be.prototype,"CLOSED",{enumerable:!0,value:wt.indexOf("CLOSED")});["binaryType","bufferedAmount","extensions","isPaused","protocol","readyState","url"].forEach(i=>{Object.defineProperty(be.prototype,i,{enumerable:!0})});["open","error","close","message"].forEach(i=>{Object.defineProperty(be.prototype,`on${i}`,{enumerable:!0,get(){for(let t of this.listeners(i))if(t[$a])return t[dd];return null},set(t){for(let e of this.listeners(i))if(e[$a]){this.removeListener(i,e);break}typeof t=="function"&&this.addEventListener(i,t,{[$a]:!0})}})});be.prototype.addEventListener=ud;be.prototype.removeEventListener=pd;yo.exports=be;function ho(i,t,e,s){let n={allowSynchronousEvents:!0,autoPong:!0,closeTimeout:ld,protocolVersion:ja[1],maxBufferedChunks:1048576,maxFragments:131072,maxPayload:104857600,skipUTF8Validation:!1,perMessageDeflate:!0,followRedirects:!1,maxRedirects:10,...s,socketPath:void 0,hostname:void 0,protocol:void 0,timeout:void 0,method:"GET",host:void 0,path:void 0,port:void 0};if(i._autoPong=n.autoPong,i._closeTimeout=n.closeTimeout,!ja.includes(n.protocolVersion))throw new RangeError(`Unsupported protocol version: ${n.protocolVersion} (supported versions: ${ja.join(", ")})`);let a;if(t instanceof Ua)a=t;else try{a=new Ua(t)}catch{throw new SyntaxError(`Invalid URL: ${t}`)}a.protocol==="http:"?a.protocol="ws:":a.protocol==="https:"&&(a.protocol="wss:"),i._url=a.href;let r=a.protocol==="wss:",o=a.protocol==="ws+unix:",c;if(a.protocol!=="ws:"&&!r&&!o?c=`The URL's protocol must be one of "ws:", "wss:", "http:", "https:", or "ws+unix:"`:o&&!a.pathname?c="The URL's pathname is empty":a.hash&&(c="The URL contains a fragment identifier"),c){let f=new SyntaxError(c);if(i._redirects===0)throw f;In(i,f);return}let l=r?443:80,h=nd(16).toString("base64"),d=r?ed.request:td.request,u=new Set,p;if(n.createConnection=n.createConnection||(r?wd:vd),n.defaultPort=n.defaultPort||l,n.port=a.port||l,n.host=a.hostname.startsWith("[")?a.hostname.slice(1,-1):a.hostname,n.headers={...n.headers,"Sec-WebSocket-Version":n.protocolVersion,"Sec-WebSocket-Key":h,Connection:"Upgrade",Upgrade:"websocket"},n.path=a.pathname+a.search,n.timeout=n.handshakeTimeout,n.perMessageDeflate&&(p=new Ct({...n.perMessageDeflate,isServer:!1,maxPayload:n.maxPayload}),n.headers["Sec-WebSocket-Extensions"]=md({[Ct.extensionName]:p.offer()})),e.length){for(let f of e){if(typeof f!="string"||!yd.test(f)||u.has(f))throw new SyntaxError("An invalid or duplicated subprotocol was specified");u.add(f)}n.headers["Sec-WebSocket-Protocol"]=e.join(",")}if(n.origin&&(n.protocolVersion<13?n.headers["Sec-WebSocket-Origin"]=n.origin:n.headers.Origin=n.origin),(a.username||a.password)&&(n.auth=`${a.username}:${a.password}`),o){let f=n.path.split(":");n.socketPath=f[0],n.path=f[1]}let m;if(n.followRedirects){if(i._redirects===0){i._originalIpc=o,i._originalSecure=r,i._originalHostOrSocketPath=o?n.socketPath:a.host;let f=s&&s.headers;if(s={...s,headers:{}},f)for(let[g,y]of Object.entries(f))s.headers[g.toLowerCase()]=y}else if(i.listenerCount("redirect")===0){let f=o?i._originalIpc?n.socketPath===i._originalHostOrSocketPath:!1:i._originalIpc?!1:a.host===i._originalHostOrSocketPath;(!f||i._originalSecure&&!r)&&(delete n.headers.authorization,delete n.headers.cookie,f||delete n.headers.host,n.auth=void 0)}n.auth&&!s.headers.authorization&&(s.headers.authorization="Basic "+Buffer.from(n.auth).toString("base64")),m=i._req=d(n),i._redirects&&i.emit("redirect",i.url,m)}else m=i._req=d(n);n.timeout&&m.on("timeout",()=>{Qe(i,m,"Opening handshake has timed out")}),m.on("error",f=>{m===null||m[co]||(m=i._req=null,In(i,f))}),m.on("response",f=>{let g=f.headers.location,y=f.statusCode;if(g&&n.followRedirects&&y>=300&&y<400){if(++i._redirects>n.maxRedirects){Qe(i,m,"Maximum redirects exceeded");return}m.abort();let v;try{v=new Ua(g,t)}catch{let w=new SyntaxError(`Invalid URL: ${g}`);In(i,w);return}ho(i,v,e,s)}else i.emit("unexpected-response",m,f)||Qe(i,m,`Unexpected server response: ${f.statusCode}`)}),m.on("upgrade",(f,g,y)=>{if(i.emit("upgrade",f),i.readyState!==be.CONNECTING)return;m=i._req=null;let v=f.headers.upgrade;if(v===void 0||v.toLowerCase()!=="websocket"){Qe(i,g,"Invalid Upgrade header");return}let k=ad("sha1").update(h+cd).digest("base64");if(f.headers["sec-websocket-accept"]!==k){Qe(i,g,"Invalid Sec-WebSocket-Accept header");return}let w=f.headers["sec-websocket-protocol"],S;if(w!==void 0?u.size?u.has(w)||(S="Server sent an invalid subprotocol"):S="Server sent a subprotocol but none was requested":u.size&&(S="Server sent no subprotocol"),S){Qe(i,g,S);return}w&&(i._protocol=w);let T=f.headers["sec-websocket-extensions"];if(T!==void 0){if(!p){Qe(i,g,"Server sent a Sec-WebSocket-Extensions header but no extension was requested");return}let _;try{_=fd(T)}catch{Qe(i,g,"Invalid Sec-WebSocket-Extensions header");return}let D=Object.keys(_);if(D.length!==1||D[0]!==Ct.extensionName){Qe(i,g,"Server indicated an extension that was not requested");return}try{p.accept(_[Ct.extensionName])}catch{Qe(i,g,"Invalid Sec-WebSocket-Extensions header");return}i._extensions[Ct.extensionName]=p}i.setSocket(g,y,{allowSynchronousEvents:n.allowSynchronousEvents,generateMask:n.generateMask,maxBufferedChunks:n.maxBufferedChunks,maxFragments:n.maxFragments,maxPayload:n.maxPayload,skipUTF8Validation:n.skipUTF8Validation})}),n.finishRequest?n.finishRequest(m,i):m.end()}function In(i,t){i._readyState=be.CLOSING,i._errorEmitted=!0,i.emit("error",t),i.emitClose()}function vd(i){return i.path=i.socketPath,oo.connect(i)}function wd(i){return i.path=void 0,!i.servername&&i.servername!==""&&(i.servername=oo.isIP(i.host)?"":i.host),sd.connect(i)}function Qe(i,t,e){i._readyState=be.CLOSING;let s=new Error(e);Error.captureStackTrace(s,Qe),t.setHeader?(t[co]=!0,t.abort(),t.socket&&!t.socket.destroyed&&t.socket.destroy(),process.nextTick(In,i,s)):(t.destroy(s),t.once("error",i.emit.bind(i,"error")),t.once("close",i.emitClose.bind(i)))}function Wa(i,t,e){if(t){let s=od(t)?t.size:gd(t).length;i._socket?i._sender._bufferedBytes+=s:i._bufferedAmount+=s}if(e){let s=new Error(`WebSocket is not open: readyState ${i.readyState} (${wt[i.readyState]})`);process.nextTick(e,s)}}function bd(i,t){let e=this[De];e._closeFrameReceived=!0,e._closeMessage=t,e._closeCode=i,e._socket[De]!==void 0&&(e._socket.removeListener("data",Mn),process.nextTick(uo,e._socket),i===1005?e.close():e.close(i,t))}function kd(){let i=this[De];i.isPaused||i._socket.resume()}function xd(i){let t=this[De];t._socket[De]!==void 0&&(t._socket.removeListener("data",Mn),process.nextTick(uo,t._socket),t.close(i[hd])),t._errorEmitted||(t._errorEmitted=!0,t.emit("error",i))}function ro(){this[De].emitClose()}function Sd(i,t){this[De].emit("message",i,t)}function Cd(i){let t=this[De];t._autoPong&&t.pong(i,!this._isServer,lo),t.emit("ping",i)}function Td(i){this[De].emit("pong",i)}function uo(i){i.resume()}function _d(i){let t=this[De];t.readyState!==be.CLOSED&&(t.readyState===be.OPEN&&(t._readyState=be.CLOSING,po(t)),this._socket.end(),t._errorEmitted||(t._errorEmitted=!0,t.emit("error",i)))}function po(i){i._closeTimer=setTimeout(i._socket.destroy.bind(i._socket),i._closeTimeout)}function mo(){let i=this[De];if(this.removeListener("close",mo),this.removeListener("data",Mn),this.removeListener("end",fo),i._readyState=be.CLOSING,!this._readableState.endEmitted&&!i._closeFrameReceived&&!i._receiver._writableState.errorEmitted&&this._readableState.length!==0){let t=this.read(this._readableState.length);i._receiver.write(t)}i._receiver.end(),this[De]=void 0,clearTimeout(i._closeTimer),i._receiver._writableState.finished||i._receiver._writableState.errorEmitted?i.emitClose():(i._receiver.on("error",ro),i._receiver.on("finish",ro))}function Mn(i){this[De]._receiver.write(i)||this.pause()}function fo(){let i=this[De];i._readyState=be.CLOSING,i._receiver.end(),this.end()}function go(){let i=this[De];this.removeListener("error",go),this.on("error",lo),i&&(i._readyState=be.CLOSING,this.destroy())}});var ko=Ve((Mp,bo)=>{"use strict";var Ip=Ln(),{Duplex:Ad}=require("stream");function vo(i){i.emit("close")}function Ed(){!this.destroyed&&this._writableState.finished&&this.destroy()}function wo(i){this.removeListener("error",wo),this.destroy(),this.listenerCount("error")===0&&this.emit("error",i)}function Pd(i,t){let e=!0,s=new Ad({...t,autoDestroy:!1,emitClose:!1,objectMode:!1,writableObjectMode:!1});return i.on("message",function(a,r){let o=!r&&s._readableState.objectMode?a.toString():a;s.push(o)||i.pause()}),i.once("error",function(a){s.destroyed||(e=!1,s.destroy(a))}),i.once("close",function(){s.destroyed||s.push(null)}),s._destroy=function(n,a){if(i.readyState===i.CLOSED){a(n),process.nextTick(vo,s);return}let r=!1;i.once("error",function(c){r=!0,a(c)}),i.once("close",function(){r||a(n),process.nextTick(vo,s)}),e&&i.terminate()},s._final=function(n){if(i.readyState===i.CONNECTING){i.once("open",function(){s._final(n)});return}i._socket!==null&&(i._socket._writableState.finished?(n(),s._readableState.endEmitted&&s.destroy()):(i._socket.once("finish",function(){n()}),i.close()))},s._read=function(){i.isPaused&&i.resume()},s._write=function(n,a,r){if(i.readyState===i.CONNECTING){i.once("open",function(){s._write(n,a,r)});return}i.send(n,r)},s.on("end",Ed),s.on("error",wo),s}bo.exports=Pd});var Ha=Ve((Lp,xo)=>{"use strict";var{tokenChars:Rd}=Qt();function Dd(i){let t=new Set,e=-1,s=-1,n=0;for(n;n{"use strict";var Id=require("events"),Fn=require("http"),{Duplex:Fp}=require("stream"),{createHash:Md}=require("crypto"),So=Rn(),Ft=Xt(),Ld=Ha(),Fd=Ln(),{CLOSE_TIMEOUT:Od,GUID:Nd,kWebSocket:Bd}=gt(),Ud=/^[+/0-9A-Za-z]{22}==$/,Co=0,To=1,Ao=2,qa=class extends Id{constructor(t,e){if(super(),t={allowSynchronousEvents:!0,autoPong:!0,maxBufferedChunks:1024*1024,maxFragments:128*1024,maxPayload:100*1024*1024,skipUTF8Validation:!1,perMessageDeflate:!1,handleProtocols:null,clientTracking:!0,closeTimeout:Od,verifyClient:null,noServer:!1,backlog:null,server:null,host:null,path:null,port:null,WebSocket:Fd,...t},t.port==null&&!t.server&&!t.noServer||t.port!=null&&(t.server||t.noServer)||t.server&&t.noServer)throw new TypeError('One and only one of the "port", "server", or "noServer" options must be specified');if(t.port!=null?(this._server=Fn.createServer((s,n)=>{let a=Fn.STATUS_CODES[426];n.writeHead(426,{"Content-Length":a.length,"Content-Type":"text/plain"}),n.end(a)}),this._server.listen(t.port,t.host,t.backlog,e)):t.server&&(this._server=t.server),this._server){let s=this.emit.bind(this,"connection");this._removeListeners=$d(this._server,{listening:this.emit.bind(this,"listening"),error:this.emit.bind(this,"error"),upgrade:(n,a,r)=>{this.handleUpgrade(n,a,r,s)}})}t.perMessageDeflate===!0&&(t.perMessageDeflate={}),t.clientTracking&&(this.clients=new Set,this._shouldEmitClose=!1),this.options=t,this._state=Co}address(){if(this.options.noServer)throw new Error('The server is operating in "noServer" mode');return this._server?this._server.address():null}close(t){if(this._state===Ao){t&&this.once("close",()=>{t(new Error("The server is not running"))}),process.nextTick(Ds,this);return}if(t&&this.once("close",t),this._state!==To)if(this._state=To,this.options.noServer||this.options.server)this._server&&(this._removeListeners(),this._removeListeners=this._server=null),this.clients?this.clients.size?this._shouldEmitClose=!0:process.nextTick(Ds,this):process.nextTick(Ds,this);else{let e=this._server;this._removeListeners(),this._removeListeners=this._server=null,e.close(()=>{Ds(this)})}}shouldHandle(t){if(this.options.path){let e=t.url.indexOf("?");if((e!==-1?t.url.slice(0,e):t.url)!==this.options.path)return!1}return!0}handleUpgrade(t,e,s,n){e.on("error",_o);let a=t.headers["sec-websocket-key"],r=t.headers.upgrade,o=+t.headers["sec-websocket-version"];if(t.method!=="GET"){Ot(this,t,e,405,"Invalid HTTP method");return}if(r===void 0||r.toLowerCase()!=="websocket"){Ot(this,t,e,400,"Invalid Upgrade header");return}if(a===void 0||!Ud.test(a)){Ot(this,t,e,400,"Missing or invalid Sec-WebSocket-Key header");return}if(o!==13&&o!==8){Ot(this,t,e,400,"Missing or invalid Sec-WebSocket-Version header",{"Sec-WebSocket-Version":"13, 8"});return}if(!this.shouldHandle(t)){Is(e,400);return}let c=t.headers["sec-websocket-protocol"],l=new Set;if(c!==void 0)try{l=Ld.parse(c)}catch{Ot(this,t,e,400,"Invalid Sec-WebSocket-Protocol header");return}let h=t.headers["sec-websocket-extensions"],d={};if(this.options.perMessageDeflate&&h!==void 0){let u=new Ft({...this.options.perMessageDeflate,isServer:!0,maxPayload:this.options.maxPayload});try{let p=So.parse(h);p[Ft.extensionName]&&(u.accept(p[Ft.extensionName]),d[Ft.extensionName]=u)}catch{Ot(this,t,e,400,"Invalid or unacceptable Sec-WebSocket-Extensions header");return}}if(this.options.verifyClient){let u={origin:t.headers[`${o===8?"sec-websocket-origin":"origin"}`],secure:!!(t.socket.authorized||t.socket.encrypted),req:t};if(this.options.verifyClient.length===2){this.options.verifyClient(u,(p,m,f,g)=>{if(!p)return Is(e,m||401,f,g);this.completeUpgrade(d,a,l,t,e,s,n)});return}if(!this.options.verifyClient(u))return Is(e,401)}this.completeUpgrade(d,a,l,t,e,s,n)}completeUpgrade(t,e,s,n,a,r,o){if(!a.readable||!a.writable)return a.destroy();if(a[Bd])throw new Error("server.handleUpgrade() was called more than once with the same socket, possibly due to a misconfiguration");if(this._state>Co)return Is(a,503);let l=["HTTP/1.1 101 Switching Protocols","Upgrade: websocket","Connection: Upgrade",`Sec-WebSocket-Accept: ${Md("sha1").update(e+Nd).digest("base64")}`],h=new this.options.WebSocket(null,void 0,this.options);if(s.size){let d=this.options.handleProtocols?this.options.handleProtocols(s,n):s.values().next().value;d&&(l.push(`Sec-WebSocket-Protocol: ${d}`),h._protocol=d)}if(t[Ft.extensionName]){let d=t[Ft.extensionName].params,u=So.format({[Ft.extensionName]:[d]});l.push(`Sec-WebSocket-Extensions: ${u}`),h._extensions=t}this.emit("headers",l,n),a.write(l.concat(`\r `).join(`\r -`)),a.removeListener("error",vo),h.setSocket(a,r,{allowSynchronousEvents:this.options.allowSynchronousEvents,maxBufferedChunks:this.options.maxBufferedChunks,maxFragments:this.options.maxFragments,maxPayload:this.options.maxPayload,skipUTF8Validation:this.options.skipUTF8Validation}),this.clients&&(this.clients.add(h),h.on("close",()=>{this.clients.delete(h),this._shouldEmitClose&&!this.clients.size&&process.nextTick(Es,this)})),o(h,n)}};bo.exports=Ua;function Td(i,t){for(let e of Object.keys(t))i.on(e,t[e]);return function(){for(let s of Object.keys(t))i.removeListener(s,t[s])}}function Es(i){i._state=wo,i.emit("close")}function vo(){this.destroy()}function Ps(i,t,e,s){e=e||Ln.STATUS_CODES[t],s={Connection:"close","Content-Type":"text/html","Content-Length":Buffer.byteLength(e),...s},i.once("finish",i.destroy),i.end(`HTTP/1.1 ${t} ${Ln.STATUS_CODES[t]}\r +`)),a.removeListener("error",_o),h.setSocket(a,r,{allowSynchronousEvents:this.options.allowSynchronousEvents,maxBufferedChunks:this.options.maxBufferedChunks,maxFragments:this.options.maxFragments,maxPayload:this.options.maxPayload,skipUTF8Validation:this.options.skipUTF8Validation}),this.clients&&(this.clients.add(h),h.on("close",()=>{this.clients.delete(h),this._shouldEmitClose&&!this.clients.size&&process.nextTick(Ds,this)})),o(h,n)}};Eo.exports=qa;function $d(i,t){for(let e of Object.keys(t))i.on(e,t[e]);return function(){for(let s of Object.keys(t))i.removeListener(s,t[s])}}function Ds(i){i._state=Ao,i.emit("close")}function _o(){this.destroy()}function Is(i,t,e,s){e=e||Fn.STATUS_CODES[t],s={Connection:"close","Content-Type":"text/html","Content-Length":Buffer.byteLength(e),...s},i.once("finish",i.destroy),i.end(`HTTP/1.1 ${t} ${Fn.STATUS_CODES[t]}\r `+Object.keys(s).map(n=>`${n}: ${s[n]}`).join(`\r `)+`\r \r -`+e)}function Lt(i,t,e,s,n,a){if(i.listenerCount("wsClientError")){let r=new Error(n);Error.captureStackTrace(r,Lt),i.emit("wsClientError",r,e,t)}else Ps(e,s,n,a)}});var Gd={};Yo(Gd,{default:()=>Hn});module.exports=Ko(Gd);var Ms=require("fs"),qa=require("os"),za=require("path"),Ce=require("obsidian");var Ut="agent-fleet-agents";var Ct="agent-fleet-dashboard",nt="agent-fleet-chat",at={fleetFolder:"_fleet",claudeCliPath:"claude",codexCliPath:"codex",defaultModel:"default",awsRegion:"us-east-1",maxConcurrentRuns:2,runLogRetentionDays:30,catchUpMissedTasks:!0,notificationLevel:"all",showStatusBar:!0,mcpApiKeys:{},mcpTokens:{},channelCredentials:{},maxConcurrentChannelSessions:5,channelIdleTimeoutMinutes:15,channelRateLimitPerConversation:20,channelRateLimitWindowMinutes:5,chatWatchdogMinutes:10,defaultFileHashes:{}},Ja=["agents","skills","tasks","runs","memory","channels","mcp"],Us=1500,Xa="0 3 * * *",Qa=3;var Si=require("path"),x=require("obsidian");var Gn=[{path:"agents/fleet-orchestrator/CONTEXT.md",content:`--- +`+e)}function Ot(i,t,e,s,n,a){if(i.listenerCount("wsClientError")){let r=new Error(n);Error.captureStackTrace(r,Ot),i.emit("wsClientError",r,e,t)}else Is(e,s,n,a)}});var _h={};cl(_h,{default:()=>zn});module.exports=dl(_h);var Os=require("fs"),Xa=require("os"),Qa=require("path"),Se=require("obsidian");var $t="agent-fleet-agents";var _t="agent-fleet-dashboard",it="agent-fleet-chat",rt={fleetFolder:"_fleet",claudeCliPath:"claude",codexCliPath:"codex",defaultModel:"default",awsRegion:"us-east-1",maxConcurrentRuns:2,runLogRetentionDays:30,catchUpMissedTasks:!0,notificationLevel:"all",showStatusBar:!0,mcpApiKeys:{},mcpTokens:{},channelCredentials:{},maxConcurrentChannelSessions:5,channelIdleTimeoutMinutes:15,channelRateLimitPerConversation:20,channelRateLimitWindowMinutes:5,chatWatchdogMinutes:10,defaultFileHashes:{}},ii=["agents","skills","tasks","runs","memory","channels","mcp","usage"],js=1500,ri="0 3 * * *",oi=3;var Ii=require("path"),x=require("obsidian");var Jn=[{path:"agents/fleet-orchestrator/CONTEXT.md",content:`--- {} --- @@ -519,7 +519,7 @@ These are inherited by all agent processes. Never store tokens in vault files. | Defined in | HEARTBEAT.md in agent folder | _fleet/tasks/.md | | Prompt source | Heartbeat body | Task body | | "Run Now" button | Uses heartbeat instruction | Uses task prompt | -| Delivery | Optional Slack channel post | Run log only | +| Delivery | Run log + optional channel post (\`channel\` in HEARTBEAT.md) | Run log + optional channel post (\`channel\` in task frontmatter) | | Scope | One per agent | Many per agent | | Best for | Autonomous periodic monitoring | Specific scheduled work items | @@ -722,6 +722,9 @@ enabled: true schedule: "0 */6 * * *" # Cron expression \u2014 every 6 hours notify: true # Show Obsidian notice on completion channel: my-slack # Post results to this channel (optional) +channel_target: "" # Specific channel/conversation id within \`channel\` to post to + # (e.g. a Discord channel id). Empty = broadcast as a DM to the + # channel's first allowed user. Quote numeric ids to preserve precision. --- Check the following endpoints for availability and response time: @@ -737,6 +740,8 @@ a one-line "all clear". Use [REMEMBER] to track trends across heartbeats. - \`schedule\` \u2014 cron expression (same format as tasks) - \`notify\` \u2014 show Obsidian notice when heartbeat completes (default true) - \`channel\` \u2014 name of a configured channel to post results to (optional) +- \`channel_target\` \u2014 specific channel/conversation id within \`channel\` to post to (optional; + empty broadcasts a DM to the channel's first allowed user). Mirrors a task's \`channel_target\`. ## Creating a Channel @@ -811,6 +816,12 @@ model: "" # Override agent model for this task only. # Use aliases like "haiku" for cheap/simple tasks, # or leave empty to inherit agent's model. # Resolution order: task.model \u2192 agent.model \u2192 settings.defaultModel. +channel: "" # Post this task's output to a channel (e.g. "my-discord"). + # Empty = run log only. Lets scheduled tasks deliver to chat, + # not just the heartbeat. +channel_target: "" # Optional destination id within \`channel\`: a Discord/Slack + # channel id or Telegram chat id. Set = post directly to that + # channel; empty = DM the channel's first allowed user. tags: - monitoring --- @@ -819,6 +830,23 @@ Task prompt goes here. This is what the agent should do each run. Be specific and clear about expected output. \`\`\` +**Channel delivery (\`channel\` + \`channel_target\`).** Any task can post its output to a +configured channel by setting \`channel:\` to a channel name. Previously only an agent's +heartbeat could post to a channel; now every scheduled/manual task can too. The output +is the task run's **full** output text, prefixed with \`* \u2014 *\`. + +Two delivery modes: +- **\`channel_target\` empty** \u2192 broadcast (a DM to the channel's first allowed user) \u2014 + same path the heartbeat uses. +- **\`channel_target\` set** \u2192 post directly to that specific channel/conversation. The + target is a transport-native id: a Discord/Slack channel id or a Telegram chat id. + (Discord: enable Developer Mode \u2192 right-click the channel \u2192 Copy Channel ID. The bot + must have permission to post there.) + +Keep the task prompt's output concise/skimmable if it's going to chat. Heartbeats still +use \`channel\` in HEARTBEAT.md (broadcast only); tasks use \`channel\`/\`channel_target\` in +their own frontmatter. + ### Task Types - **recurring** \u2014 runs on a cron schedule. Requires \`schedule\` field. - **once** \u2014 runs at a specific time. Requires \`run_at\` field (ISO datetime). @@ -11694,29 +11722,29 @@ python scripts/office/validate.py [--original ] [--auto-re - \`paraId\`/\`durableId\` values that exceed OOXML limits - Missing \`xml:space="preserve"\` on \`w:t\` elements with whitespace -`}];var $s=require("obsidian");function J(i){let t=i.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);if(!t)return{frontmatter:{},body:i.trim()};let e=t[1]??"",s=t[2]??"",n;try{n=(0,$s.parseYaml)(e)??{}}catch(a){console.warn("Agent Fleet: malformed YAML frontmatter, treating as empty",a),n={}}return{frontmatter:n,body:s.trim()}}function W(i,t){let e=(0,$s.stringifyYaml)(i).trim(),s=t.trim();return`--- +`}];var Ws=require("obsidian");function J(i){let t=i.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);if(!t)return{frontmatter:{},body:i.trim()};let e=t[1]??"",s=t[2]??"",n;try{n=(0,Ws.parseYaml)(e)??{}}catch(a){console.warn("Agent Fleet: malformed YAML frontmatter, treating as empty",a),n={}}return{frontmatter:n,body:s.trim()}}function H(i,t){let e=(0,Ws.stringifyYaml)(i).trim(),s=t.trim();return`--- ${e} --- ${s} -`}function oe(i){return i.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/(^-|-$)/g,"")}function $t(i,t){return i.length<=t?i:`${i.slice(0,t-1)}\u2026`}var js=2,ti=["Preferences","Procedures","Observations","Recent"],si="Recent",Yn=/\[REMEMBER(?::([a-zA-Z]+))?\]([\s\S]*?)\[\/REMEMBER\]/g;function Jo(i){switch((i??"").toLowerCase()){case"pin":case"preference":case"preferences":return{pinned:!0,section:"Preferences"};case"procedure":case"procedures":return{pinned:!1,section:"Procedures"};case"observation":case"observations":return{pinned:!1,section:"Observations"};default:return{pinned:!1}}}function Ws(i){let t=[];for(let e of i.matchAll(Yn)){let s=(e[2]??"").trim();if(!s)continue;let n=Jo(e[1]);t.push({text:s,pinned:n.pinned,section:n.section})}return t}function cs(i){return i.replace(Yn,"").replace(/[ \t]+\n/g,` +`}function oe(i){return i.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/(^-|-$)/g,"")}function jt(i,t){return i.length<=t?i:`${i.slice(0,t-1)}\u2026`}var Hs=2,di=["Preferences","Procedures","Observations","Recent"],hi="Recent",Qn=/\[REMEMBER(?::([a-zA-Z]+))?\]([\s\S]*?)\[\/REMEMBER\]/g;function hl(i){switch((i??"").toLowerCase()){case"pin":case"preference":case"preferences":return{pinned:!0,section:"Preferences"};case"procedure":case"procedures":return{pinned:!1,section:"Procedures"};case"observation":case"observations":return{pinned:!1,section:"Observations"};default:return{pinned:!1}}}function qs(i){let t=[];for(let e of i.matchAll(Qn)){let s=(e[2]??"").trim();if(!s)continue;let n=hl(e[1]);t.push({text:s,pinned:n.pinned,section:n.section})}return t}function hs(i){return i.replace(Qn,"").replace(/[ \t]+\n/g,` `).replace(/\n{3,}/g,` -`).trim()}function ni(i){let t=i.replace(Yn,""),e=t.match(/\[REMEMBER(?::[a-zA-Z]+)?\][\s\S]*$/i);if(e&&e.index!==void 0)return t.slice(0,e.index);let s=t.match(/\[(?:R(?:E(?:M(?:E(?:M(?:B(?:E(?:R(?::[a-zA-Z]*)?)?)?)?)?)?)?)?)?$/i);return s&&s.index!==void 0?t.slice(0,s.index):t}var Xo={Preferences:"Preferences",Procedures:"Procedures",Observations:"Observations",Recent:"Recent (uncurated)"};function Qo(i){let t=i.trim().toLowerCase();return t.startsWith("preference")?"Preferences":t.startsWith("procedure")?"Procedures":t.startsWith("observation")?"Observations":t.startsWith("recent")?"Recent":"Observations"}function ds(i){return Math.ceil(i.length/4)}var Za=500;function ai(i){let t=i.replace(/\s+/g," ").trim();return t.length<=Za?t:`${t.slice(0,Za-1).trimEnd()}\u2026`}function Vn(i){return typeof i=="string"?i:void 0}function Zo(i,t){return typeof i=="number"&&Number.isFinite(i)?i:t}function ii(i,t){return{filePath:i,agent:t,schema:js,tokenEstimate:0,sections:[]}}var el=/\s*\s*$/,ei=/^\[pin\]\s+/i;function tl(i){let t=i.match(/^[-*]\s+(.*)$/);if(!t)return null;let e=(t[1]??"").trim();if(!e)return null;let s,n,a=e.match(el);if(a){e=e.slice(0,a.index).trim();let o=(a[1]??"").trim(),c=o.match(/^src:(\S+)(?:\s+(.+))?$/);c?(s=c[1],n=c[2]?.trim()||void 0):o&&(n=o)}let r=!1;return ei.test(e)&&(r=!0,e=e.replace(ei,"").trim()),e?{text:e,source:s,date:n,pinned:r}:null}function ri(i){let t=i.pinned?"[pin] ":"",e="";if(i.source||i.date){let s=[];i.source&&s.push(`src:${i.source}`),i.date&&s.push(i.date),e=` `}return`- ${t}${i.text}${e}`}function Ge(i){return sl(i).map(e=>{let s=e.entries.map(ri).join(` -`);return`## ${Xo[e.name]} +`).trim()}function ui(i){let t=i.replace(Qn,""),e=t.match(/\[REMEMBER(?::[a-zA-Z]+)?\][\s\S]*$/i);if(e&&e.index!==void 0)return t.slice(0,e.index);let s=t.match(/\[(?:R(?:E(?:M(?:E(?:M(?:B(?:E(?:R(?::[a-zA-Z]*)?)?)?)?)?)?)?)?)?$/i);return s&&s.index!==void 0?t.slice(0,s.index):t}var ul={Preferences:"Preferences",Procedures:"Procedures",Observations:"Observations",Recent:"Recent (uncurated)"};function pl(i){let t=i.trim().toLowerCase();return t.startsWith("preference")?"Preferences":t.startsWith("procedure")?"Procedures":t.startsWith("observation")?"Observations":t.startsWith("recent")?"Recent":"Observations"}function us(i){return Math.ceil(i.length/4)}var li=500;function pi(i){let t=i.replace(/\s+/g," ").trim();return t.length<=li?t:`${t.slice(0,li-1).trimEnd()}\u2026`}function Xn(i){return typeof i=="string"?i:void 0}function ml(i,t){return typeof i=="number"&&Number.isFinite(i)?i:t}function mi(i,t){return{filePath:i,agent:t,schema:Hs,tokenEstimate:0,sections:[]}}var fl=/\s*\s*$/,ci=/^\[pin\]\s+/i;function gl(i){let t=i.match(/^[-*]\s+(.*)$/);if(!t)return null;let e=(t[1]??"").trim();if(!e)return null;let s,n,a=e.match(fl);if(a){e=e.slice(0,a.index).trim();let o=(a[1]??"").trim(),c=o.match(/^src:(\S+)(?:\s+(.+))?$/);c?(s=c[1],n=c[2]?.trim()||void 0):o&&(n=o)}let r=!1;return ci.test(e)&&(r=!0,e=e.replace(ci,"").trim()),e?{text:e,source:s,date:n,pinned:r}:null}function fi(i){let t=i.pinned?"[pin] ":"",e="";if(i.source||i.date){let s=[];i.source&&s.push(`src:${i.source}`),i.date&&s.push(i.date),e=` `}return`- ${t}${i.text}${e}`}function Ke(i){return yl(i).map(e=>{let s=e.entries.map(fi).join(` +`);return`## ${ul[e.name]} ${s}`}).join(` -`)}function sl(i){let t=[];for(let e of ti){let s=i.find(n=>n.name===e);s&&s.entries.length>0&&t.push(s)}return t}function oi(i,t,e){let{frontmatter:s,body:n}=J(i),a=Hs(n);return{filePath:t,agent:Vn(s.agent)??e,schema:Zo(s.schema,js),lastUpdated:Vn(s.last_updated),lastReflection:Vn(s.last_reflection),tokenEstimate:ds(Ge(a)),sections:a}}function Hs(i){let t=new Map,e=(a,r)=>{let o=t.get(a)??[];o.push(r),t.set(a,o)},s="Observations";for(let a of i.split(` -`)){let r=a.match(/^#{1,6}\s+(.+?)\s*$/);if(r){s=Qo(r[1]??"");continue}let o=tl(a);o&&e(s,o)}let n=[];for(let a of ti){let r=t.get(a);r&&r.length&&n.push({name:a,entries:r})}return n}function li(i){let t=Ge(i.sections),e={agent:i.agent,schema:i.schema||js,last_updated:i.lastUpdated??"",token_estimate:ds(t)};return i.lastReflection&&(e.last_reflection=i.lastReflection),W(e,t||"## Observations")}function ci(i,t,e,s){if(t.length===0)return i;let n=i.sections.map(o=>({name:o.name,entries:[...o.entries]})),a=n.find(o=>o.name===e);return a||(a={name:e,entries:[]},n.push(a)),a.entries.push(...t),{...i,sections:n,lastUpdated:s??i.lastUpdated,tokenEstimate:ds(Ge(n))}}var nl=["Recent","Observations","Procedures"];function di(i,t){if(i.tokenEstimate<=t)return{wm:i,spilled:[]};let e=i.sections.map(o=>({name:o.name,entries:[...o.entries]})),s=[],n=Ge(e).length,a=()=>Math.ceil(n/4)>t;for(let o of nl){if(!a())break;let c=e.find(l=>l.name===o);if(c)for(;c.entries.length>0&&a();){let l=c.entries.findIndex(d=>!d.pinned);if(l===-1)break;let h=c.entries.splice(l,1)[0];h&&(s.push(h),n-=ri(h).length+1)}}let r=e.filter(o=>o.entries.length>0);return{wm:{...i,sections:r,tokenEstimate:ds(Ge(r))},spilled:s}}function Kn(i,t,e,s){let n=Hs(i);return{filePath:t,agent:e,schema:js,lastUpdated:s,tokenEstimate:ds(Ge(n)),sections:n}}function hi(i,t){let e=(i?.sections??[]).flatMap(o=>o.entries).filter(o=>o.pinned);if(e.length===0)return t;let s=new Set(t.flatMap(o=>o.entries).map(o=>o.text.trim().toLowerCase())),n=e.filter(o=>!s.has(o.text.trim().toLowerCase()));if(n.length===0)return t;let a=t.map(o=>({name:o.name,entries:[...o.entries]})),r=a.find(o=>o.name==="Preferences");return r||(r={name:"Preferences",entries:[]},a.unshift(r)),r.entries.unshift(...n),a}var al="When you learn a durable fact about the user, their preferences, or how to do your work better, save it to memory. Prefer the `remember` tool \u2014 call remember(fact, pin?, section?); it is the reliable way to record a memory. If that tool is not available, fall back to writing [REMEMBER] [/REMEMBER] in your reply (use [REMEMBER:pin] for standing preferences and hard constraints). Record only durable, reusable facts \u2014 not transient task details. These notes persist into your future runs.";function qs(i,t){if(!i.memory)return"";let s=(t?Ge(t.sections).trim():"")||"Nothing yet \u2014 this is a fresh agent.";return`## Memory -${al} +`)}function yl(i){let t=[];for(let e of di){let s=i.find(n=>n.name===e);s&&s.entries.length>0&&t.push(s)}return t}function gi(i,t,e){let{frontmatter:s,body:n}=J(i),a=zs(n);return{filePath:t,agent:Xn(s.agent)??e,schema:ml(s.schema,Hs),lastUpdated:Xn(s.last_updated),lastReflection:Xn(s.last_reflection),tokenEstimate:us(Ke(a)),sections:a}}function zs(i){let t=new Map,e=(a,r)=>{let o=t.get(a)??[];o.push(r),t.set(a,o)},s="Observations";for(let a of i.split(` +`)){let r=a.match(/^#{1,6}\s+(.+?)\s*$/);if(r){s=pl(r[1]??"");continue}let o=gl(a);o&&e(s,o)}let n=[];for(let a of di){let r=t.get(a);r&&r.length&&n.push({name:a,entries:r})}return n}function yi(i){let t=Ke(i.sections),e={agent:i.agent,schema:i.schema||Hs,last_updated:i.lastUpdated??"",token_estimate:us(t)};return i.lastReflection&&(e.last_reflection=i.lastReflection),H(e,t||"## Observations")}function vi(i,t,e,s){if(t.length===0)return i;let n=i.sections.map(o=>({name:o.name,entries:[...o.entries]})),a=n.find(o=>o.name===e);return a||(a={name:e,entries:[]},n.push(a)),a.entries.push(...t),{...i,sections:n,lastUpdated:s??i.lastUpdated,tokenEstimate:us(Ke(n))}}var vl=["Recent","Observations","Procedures"];function wi(i,t){if(i.tokenEstimate<=t)return{wm:i,spilled:[]};let e=i.sections.map(o=>({name:o.name,entries:[...o.entries]})),s=[],n=Ke(e).length,a=()=>Math.ceil(n/4)>t;for(let o of vl){if(!a())break;let c=e.find(l=>l.name===o);if(c)for(;c.entries.length>0&&a();){let l=c.entries.findIndex(d=>!d.pinned);if(l===-1)break;let h=c.entries.splice(l,1)[0];h&&(s.push(h),n-=fi(h).length+1)}}let r=e.filter(o=>o.entries.length>0);return{wm:{...i,sections:r,tokenEstimate:us(Ke(r))},spilled:s}}function Zn(i,t,e,s){let n=zs(i);return{filePath:t,agent:e,schema:Hs,lastUpdated:s,tokenEstimate:us(Ke(n)),sections:n}}function bi(i,t){let e=(i?.sections??[]).flatMap(r=>r.entries).filter(r=>r.pinned);if(e.length===0||t.some(r=>r.entries.some(o=>o.pinned)))return t;let n=t.map(r=>({name:r.name,entries:[...r.entries]})),a=n.find(r=>r.name==="Preferences");return a||(a={name:"Preferences",entries:[]},n.unshift(a)),a.entries.unshift(...e),n}var wl="When you learn a durable fact about the user, their preferences, or how to do your work better, save it to memory. Prefer the `remember` tool \u2014 call remember(fact, pin?, section?); it is the reliable way to record a memory. If that tool is not available, fall back to writing [REMEMBER] [/REMEMBER] in your reply (use [REMEMBER:pin] for standing preferences and hard constraints). Record only durable, reusable facts \u2014 not transient task details. These notes persist into your future runs.";function Gs(i,t){if(!i.memory)return"";let s=(t?Ke(t.sections).trim():"")||"Nothing yet \u2014 this is a fresh agent.";return`## Memory +${wl} ### What you've learned so far -${s}`}var ut=require("child_process"),ui=require("fs"),Gs=require("os"),Tt=require("path");function Jn(){return(0,Gs.homedir)()}function pi(){if(process.platform==="darwin")return"/bin/zsh";for(let i of["/bin/bash","/bin/zsh","/bin/sh"])if((0,ui.existsSync)(i))return i;return"/bin/sh"}function il(i){return`'${i.replace(/'/g,"'\\''")}'`}function lt(i,t,e){let s={cwd:e?.cwd,env:e?.env};if(process.platform==="win32")return(0,ut.spawn)(i,t,s);let n=pi(),a=[i,...t].map(il).join(" ");return(0,ut.spawn)(n,["-l","-c",a],s)}function mi(i,t){let e={cwd:t?.cwd,env:t?.env,stdio:["pipe","pipe","pipe"]};if(process.platform==="win32")return(0,ut.spawn)(i,[],{...e,shell:!0});let s=pi();return(0,ut.spawn)(s,["-l","-c",i],e)}function fi(i){try{require("electron").shell.openExternal(i)}catch{switch(process.platform){case"darwin":(0,ut.spawn)("open",[i],{stdio:"ignore"});break;case"win32":(0,ut.spawn)("cmd.exe",["/c","start","",i.replace(/&/g,"^&")],{stdio:"ignore"});break;default:(0,ut.spawn)("xdg-open",[i],{stdio:"ignore"});break}}}function ge(i){return i.split(/\r?\n/)}function gi(i){let t=(0,Gs.homedir)();if(process.platform==="win32")return[i,(0,Tt.join)(process.env.APPDATA??"","Claude","claude.exe"),(0,Tt.join)(process.env.LOCALAPPDATA??"","Claude","claude.exe"),(0,Tt.join)(t,".local","bin","claude.exe"),"claude.exe","claude"].filter(s=>!!s&&zs(s));let e=[i,(0,Tt.join)(t,".local","bin","claude")];return process.platform==="darwin"&&e.push("/opt/homebrew/bin/claude"),e.push("/usr/local/bin/claude","/usr/bin/claude","claude"),e.filter(s=>!!s&&zs(s))}function Xn(i){let t=(0,Gs.homedir)();if(process.platform==="win32")return[i,(0,Tt.join)(t,".local","bin","codex.exe"),"codex.exe","codex"].filter(s=>!!s&&zs(s));let e=[i,(0,Tt.join)(t,".local","bin","codex")];return process.platform==="darwin"&&e.push("/opt/homebrew/bin/codex"),e.push("/usr/local/bin/codex","/usr/bin/codex","codex"),e.filter(s=>!!s&&zs(s))}function zs(i){return!i||/[\n\r\0]/.test(i)?!1:i.startsWith("/")?/^[\w/.@+-]+$/.test(i):i.startsWith("~")?/^~[\w/.@+-]*$/.test(i):/^[a-zA-Z]:[\\/]/.test(i)?/^[a-zA-Z]:[\\/][\w\\/. @+-]+$/.test(i):i.startsWith("\\\\")?/^\\\\[\w\\/. @+-]+$/.test(i):!i.includes("/")&&!i.includes("\\")?/^[\w.@+-]+$/.test(i):!1}function Qn(i){return!!(i.includes("/")||i.includes("\\"))}function jt(i){return typeof i=="object"&&i!==null}function A(i){return typeof i=="string"?i:void 0}function Be(i,t){return typeof i=="boolean"?i:t}function De(i,t){return typeof i=="number"&&Number.isFinite(i)?i:t}function ue(i){return Array.isArray(i)?i.filter(t=>typeof t=="string"):[]}function yi(i){if(!jt(i))return;let t={};for(let[e,s]of Object.entries(i))typeof s=="string"&&(t[e]=s);return Object.keys(t).length>0?t:void 0}var vi=!1;function wi(i,t={}){let e=i.memory_token_budget??t.memory_token_budget;return e!==void 0?De(e,Us):((i.memory_max_entries??t.memory_max_entries)!==void 0&&!vi&&(vi=!0,console.info(`Agent Fleet: \`memory_max_entries\` is deprecated and no longer enforced; memory is now bounded by \`memory_token_budget\` (default ${Us}). Set that field to tune memory size.`)),Us)}function bi(i,t={}){let e=s=>i[s]??t[s];return{enabled:Be(e("reflection_enabled"),!1),schedule:A(e("reflection_schedule"))??Xa,recurrenceThreshold:De(e("reflection_recurrence_threshold"),Qa),proposeSkills:Be(e("reflection_propose_skills"),!1),model:A(e("reflection_model"))}}function ki(i){return(0,x.normalizePath)(i.replace(/\.md$/,".permissions.json"))}function xi(i){let t=0;for(let e=0;ee.path.startsWith(`${this.getFleetRoot()}/`));for(let e of t)await this.loadFile(e);return this.validateReferences(),this.getSnapshot()}async loadFile(t){let e=typeof t=="string"?this.vault.getAbstractFileByPath(t):t;if(!(e instanceof x.TFile)||e.extension!=="md")return;if(this.isInsideAgentFolder(e.path)){await this.reloadFolderAgentContaining(e.path);return}if(this.isInsideSkillFolder(e.path)){await this.reloadFolderSkillContaining(e.path);return}this.clearStoredFile(e.path);let s=`${this.getSubfolder("channels")}/`;if(e.path.startsWith(s)){if(!e.path.slice(s.length).includes("/")){let c=await this.vault.cachedRead(e),l=this.parseChannelFile(e.path,c);l&&this.channels.set(e.path,l)}return}let n=`${this.getSubfolder("mcp")}/`;if(e.path.startsWith(n)){if(!e.path.slice(n.length).includes("/")){let c=await this.vault.cachedRead(e),l=this.parseMcpServerFile(e.path,c);l&&this.mcpServers.set(e.path,l)}return}let a=await this.vault.cachedRead(e),r=this.parseFile(e.path,a);if(r)if("taskId"in r)this.tasks.set(e.path,r);else if("model"in r){if(!r.isFolder){let o=ki(e.path),c=this.vault.getAbstractFileByPath(o);if(c instanceof x.TFile)try{let l=await this.vault.cachedRead(c),h=JSON.parse(l);r.permissionRules={allow:ue(h.allow),deny:ue(h.deny)}}catch{}}this.agents.set(e.path,r)}else this.skills.set(e.path,r)}async reloadFolderAgentContaining(t){let e=`${this.getSubfolder("agents")}/`,n=t.slice(e.length).split("/")[0];if(!n)return;let a=(0,x.normalizePath)(`${e}${n}`),r=(0,x.normalizePath)(`${a}/agent.md`);if(this.agents.delete(r),!(this.vault.getAbstractFileByPath(a)instanceof x.TFolder))return;let c=this.vault.getAbstractFileByPath(r);if(!(c instanceof x.TFile))return;let l=await this.loadFolderAgent(a,c);l&&this.agents.set(r,l)}isInsideAgentFolder(t){let e=`${this.getSubfolder("agents")}/`;return t.startsWith(e)?t.slice(e.length).includes("/"):!1}isInsideSkillFolder(t){let e=`${this.getSubfolder("skills")}/`;return t.startsWith(e)?t.slice(e.length).includes("/"):!1}async reloadFolderSkillContaining(t){let e=`${this.getSubfolder("skills")}/`,n=t.slice(e.length).split("/")[0];if(!n)return;let a=(0,x.normalizePath)(`${e}${n}`),r=(0,x.normalizePath)(`${a}/skill.md`);if(this.skills.delete(r),!(this.vault.getAbstractFileByPath(a)instanceof x.TFolder))return;let c=this.vault.getAbstractFileByPath(r);if(!(c instanceof x.TFile))return;let l=await this.loadFolderSkill(a,c);l&&this.skills.set(r,l)}async loadFolderSkills(){let t=this.vault.getAbstractFileByPath(this.getSubfolder("skills"));if(t instanceof x.TFolder)for(let e of t.children){if(!(e instanceof x.TFolder))continue;let s=(0,x.normalizePath)(`${e.path}/skill.md`),n=this.vault.getAbstractFileByPath(s);if(!(n instanceof x.TFile))continue;let a=await this.loadFolderSkill(e.path,n);a&&this.skills.set(s,a)}}async loadFolderSkill(t,e){let s=await this.vault.cachedRead(e),{frontmatter:n,body:a}=J(s),r=A(n.name);if(!r)return this.setIssue(e.path,"Folder skill skill.md requires string field `name`."),null;let o=async c=>{let l=(0,x.normalizePath)(`${t}/${c}`),h=this.vault.getAbstractFileByPath(l);if(!(h instanceof x.TFile))return"";let d=await this.vault.cachedRead(h);return J(d).body};return{filePath:e.path,name:r,description:A(n.description),tags:ue(n.tags),body:a,toolsBody:await o("tools.md"),referencesBody:await o("references.md"),examplesBody:await o("examples.md"),isFolder:!0}}async loadFolderAgents(){let t=this.vault.getAbstractFileByPath(this.getSubfolder("agents"));if(t instanceof x.TFolder)for(let e of t.children){if(!(e instanceof x.TFolder))continue;let s=(0,x.normalizePath)(`${e.path}/agent.md`),n=this.vault.getAbstractFileByPath(s);if(!(n instanceof x.TFile))continue;let a=await this.loadFolderAgent(e.path,n);a&&this.agents.set(s,a)}}async loadFolderAgent(t,e){let s=await this.vault.cachedRead(e),{frontmatter:n,body:a}=J(s),r=A(n.name);if(!r)return this.setIssue(e.path,"Folder agent agent.md requires string field `name`."),null;let o={},c=(0,x.normalizePath)(`${t}/config.md`),l=this.vault.getAbstractFileByPath(c);if(l instanceof x.TFile){let L=await this.vault.cachedRead(l);o=J(L).frontmatter}let h={allow:[],deny:[]},d=(0,x.normalizePath)(`${t}/permissions.json`),u=this.vault.getAbstractFileByPath(d);if(u instanceof x.TFile)try{let L=await this.vault.cachedRead(u),F=JSON.parse(L);h={allow:ue(F.allow),deny:ue(F.deny)}}catch{}if(h.allow.length===0&&h.deny.length===0){let L=ue(o.allowed_tools),F=ue(o.blocked_tools);(L.length>0||F.length>0)&&(h={allow:L,deny:F},this.warnedLegacyPerms.has(r)||(this.warnedLegacyPerms.add(r),console.warn(`Agent Fleet: "${r}" still uses legacy allowed_tools/blocked_tools in config.md. Permission rules now live in permissions.json. Open this agent in Edit and Save to migrate.`)))}let p="",m=(0,x.normalizePath)(`${t}/SKILLS.md`),f=this.vault.getAbstractFileByPath(m);if(f instanceof x.TFile){let L=await this.vault.cachedRead(f);p=J(L).body}let g="",v=(0,x.normalizePath)(`${t}/CONTEXT.md`),b=this.vault.getAbstractFileByPath(v);if(b instanceof x.TFile){let L=await this.vault.cachedRead(b);g=J(L).body}let k=!1,y="",S="",T=!0,_="",D=(0,x.normalizePath)(`${t}/HEARTBEAT.md`),M=this.vault.getAbstractFileByPath(D);if(M instanceof x.TFile){let L=await this.vault.cachedRead(M),F=J(L);k=Be(F.frontmatter.enabled,!1),y=A(F.frontmatter.schedule)??"",T=Be(F.frontmatter.notify,!0),_=A(F.frontmatter.channel)??"",S=F.body}let C=A(n.model),I=A(o.model);C&&I&&C!==I&&(this.warnedFolderAgentModelConflict.has(r)||(this.warnedFolderAgentModelConflict.add(r),console.warn(`Agent Fleet: "${r}" has conflicting model fields \u2014 agent.md says "${C}", config.md says "${I}". config.md wins. Remove agent.md's model field or sync the values to silence this warning.`)));let P=I??C??this.settings.defaultModel;return{filePath:e.path,name:r,description:A(n.description),model:P,adapter:A(o.adapter)??"claude-code",permissionMode:A(o.permission_mode)??"bypassPermissions",effort:A(o.effort),maxRetries:De(o.max_retries,1),skills:ue(n.skills),mcpServers:ue(n.mcp_servers),cwd:A(o.cwd)||A(n.cwd),enabled:Be(n.enabled,!0),timeout:De(o.timeout,De(n.timeout,300)),approvalRequired:ue(o.approval_required),memory:Be(o.memory,Be(n.memory,!1)),memoryMaxEntries:De(o.memory_max_entries,100),memoryTokenBudget:wi(o,n),reflection:bi(o,n),autoCompactThreshold:De(o.auto_compact_threshold??n.auto_compact_threshold,85),tags:ue(n.tags),avatar:A(n.avatar)??"",body:a,contextBody:g,skillsBody:p,env:this.parseEnvMap(o.env),permissionRules:h,isFolder:!0,heartbeatEnabled:k,heartbeatSchedule:y,heartbeatBody:S,heartbeatNotify:T,heartbeatChannel:_,wikiKeeper:this.parseWikiKeeperConfig(o.wiki_keeper??n.wiki_keeper),wikiReferences:this.parseWikiReferences(o.wiki_references??n.wiki_references)}}parseWikiReferences(t){if(!Array.isArray(t))return;let e=[];for(let s of t)if(typeof s=="string"&&s.trim())e.push({agent:s.trim()});else if(s&&typeof s=="object"){let n=s.agent;typeof n=="string"&&n.trim()&&e.push({agent:n.trim()})}return e.length>0?e:void 0}parseWikiKeeperConfig(t){if(!t||typeof t!="object")return;let e=t;return{scopeRoot:A(e.scope_root)??"",inboxPath:A(e.inbox_path)??"_sources/inbox",archivePath:A(e.archive_path)??"_sources/archive",failedPath:A(e.failed_path)??"_sources/failed",topicsRoot:A(e.topics_root)??"_topics",indexPath:A(e.index_path)??"index.md",logPath:A(e.log_path)??"log.md",watchedFolders:ue(e.watched_folders),excludePatterns:ue(e.exclude_patterns),watchedSince:A(e.watched_since)??"",fileSubstantiveAnswers:Be(e.file_substantive_answers,!0),obsidianUrlScheme:Be(e.obsidian_url_scheme,!0),maxTokensPerIngest:De(e.max_tokens_per_ingest,6e4),maxTokensPerRefresh:De(e.max_tokens_per_refresh,3e4),indexSplitThreshold:De(e.index_split_threshold,100),dedupSimilarityThreshold:De(e.dedup_similarity_threshold,.82),summaryStaleDays:De(e.summary_stale_days,30),stateFile:A(e.state_file)??".wiki-keeper-state.json"}}removeFile(t){this.clearStoredFile(t)}getSnapshot(){return{agents:Array.from(this.agents.values()).sort((t,e)=>t.name.localeCompare(e.name)),skills:Array.from(this.skills.values()).sort((t,e)=>t.name.localeCompare(e.name)),tasks:Array.from(this.tasks.values()).sort((t,e)=>t.taskId.localeCompare(e.taskId)),channels:Array.from(this.channels.values()).sort((t,e)=>t.name.localeCompare(e.name)),mcpServers:Array.from(this.mcpServers.values()).sort((t,e)=>t.name.localeCompare(e.name)),validationIssues:Array.from(this.validationIssues.values()).flat()}}getAgentByName(t){return Array.from(this.agents.values()).find(e=>e.name===t)}getSkillByName(t){return Array.from(this.skills.values()).find(e=>e.name===t)}getTaskById(t){return Array.from(this.tasks.values()).find(e=>e.taskId===t)}getTasksForAgent(t){return Array.from(this.tasks.values()).filter(e=>e.agent===t)}getChannelByName(t){return Array.from(this.channels.values()).find(e=>e.name===t)}getChannelsForAgent(t){return Array.from(this.channels.values()).filter(e=>e.defaultAgent===t)}getMcpServers(){return Array.from(this.mcpServers.values()).sort((t,e)=>t.name.localeCompare(e.name))}getMcpServerByName(t){return Array.from(this.mcpServers.values()).find(e=>e.name===t)}getRunsRoot(){return this.getSubfolder("runs")}getMemoryPath(t){return(0,x.normalizePath)(`${this.getSubfolder("memory")}/${oe(t)}.md`)}getMemoryDir(t){return(0,x.normalizePath)(`${this.getSubfolder("memory")}/${oe(t)}`)}getWorkingMemoryPath(t){return(0,x.normalizePath)(`${this.getMemoryDir(t)}/working.md`)}getRawMemoryPath(t,e){let s=e.slice(0,10);return(0,x.normalizePath)(`${this.getMemoryDir(t)}/raw/${s}.md`)}async readWorkingMemory(t){let e=this.getWorkingMemoryPath(t),s=this.vault.getAbstractFileByPath(e);if(s instanceof x.TFile){let r=await this.vault.cachedRead(s);return oi(r,e,t)}let n=this.getMemoryPath(t),a=this.vault.getAbstractFileByPath(n);if(a instanceof x.TFile){let r=await this.vault.cachedRead(a),{body:o}=J(r);return Kn(o,e,t)}return null}async writeWorkingMemory(t,e){let s=this.getWorkingMemoryPath(t);await this.ensureFolder(this.getMemoryDir(t));let n=li(e),a=this.vault.getAbstractFileByPath(s);a instanceof x.TFile?await this.vault.modify(a,n):await this.createFileIfMissing(s,n);let r=this.getMemoryPath(t);r!==s&&this.vault.getAbstractFileByPath(r)&&await this.trashFile(r)}async migrateLegacyMemory(t){let e=this.getWorkingMemoryPath(t);if(this.vault.getAbstractFileByPath(e))return;let s=this.getMemoryPath(t),n=this.vault.getAbstractFileByPath(s);if(n instanceof x.TFile&&!this.migratingMemory.has(t)){this.migratingMemory.add(t);try{let a=await this.vault.cachedRead(n),{body:r}=J(a),o=new Date().toISOString(),c=r.split(` -`).map(d=>d.replace(/^\s*[-*]\s+/,"").trim()).filter(d=>d.length>0&&!/^#{1,6}\s/.test(d)),l=[`- ${o} [migrated] Imported ${c.length} legacy memory entr${c.length===1?"y":"ies"} (pre-v2).`,...c.map(d=>`- ${o} [migrated] ${d}`)];await this.appendRawMemory(t,l,o);let h=Kn(r,e,t,o);await this.writeWorkingMemory(t,h)}finally{this.migratingMemory.delete(t)}}}async migrateAllLegacyMemory(){for(let t of this.getSnapshot().agents)if(t.memory)try{await this.migrateLegacyMemory(t.name)}catch(e){console.warn(`Agent Fleet: legacy memory migration failed for "${t.name}"`,e)}}getPendingDir(t){return(0,x.normalizePath)(`${this.getMemoryDir(t)}/pending`)}getPendingDirAbsolutePath(t){let e=this.vault.adapter;return e instanceof x.FileSystemAdapter?(0,Si.join)(e.getBasePath(),this.getPendingDir(t)):null}async ensureMemoryDir(t){await this.ensureFolder(this.getMemoryDir(t))}async readAndClearPending(t){let e=this.vault.adapter,s=this.getPendingDir(t),n;try{if(!await e.exists(s))return[];n=(await e.list(s)).files}catch{return[]}let a=[];for(let r of n)if(r.endsWith(".json"))try{let o=await e.read(r);await e.remove(r);for(let c of o.split(` +${s}`}var mt=require("child_process"),ki=require("fs"),Ys=require("os"),At=require("path");function ea(){return(0,Ys.homedir)()}function xi(){if(process.platform==="darwin")return"/bin/zsh";for(let i of["/bin/bash","/bin/zsh","/bin/sh"])if((0,ki.existsSync)(i))return i;return"/bin/sh"}function bl(i){return`'${i.replace(/'/g,"'\\''")}'`}function dt(i,t,e){let s={cwd:e?.cwd,env:e?.env};if(process.platform==="win32")return(0,mt.spawn)(i,t,s);let n=xi(),a=[i,...t].map(bl).join(" ");return(0,mt.spawn)(n,["-l","-c",a],s)}function Si(i,t){let e={cwd:t?.cwd,env:t?.env,stdio:["pipe","pipe","pipe"]};if(process.platform==="win32")return(0,mt.spawn)(i,[],{...e,shell:!0});let s=xi();return(0,mt.spawn)(s,["-l","-c",i],e)}function Ci(i){try{require("electron").shell.openExternal(i)}catch{switch(process.platform){case"darwin":(0,mt.spawn)("open",[i],{stdio:"ignore"});break;case"win32":(0,mt.spawn)("cmd.exe",["/c","start","",i.replace(/&/g,"^&")],{stdio:"ignore"});break;default:(0,mt.spawn)("xdg-open",[i],{stdio:"ignore"});break}}}function ye(i){return i.split(/\r?\n/)}function Ti(i){let t=(0,Ys.homedir)();if(process.platform==="win32")return[i,(0,At.join)(process.env.APPDATA??"","Claude","claude.exe"),(0,At.join)(process.env.LOCALAPPDATA??"","Claude","claude.exe"),(0,At.join)(t,".local","bin","claude.exe"),"claude.exe","claude"].filter(s=>!!s&&Vs(s));let e=[i,(0,At.join)(t,".local","bin","claude")];return process.platform==="darwin"&&e.push("/opt/homebrew/bin/claude"),e.push("/usr/local/bin/claude","/usr/bin/claude","claude"),e.filter(s=>!!s&&Vs(s))}function ta(i){let t=(0,Ys.homedir)();if(process.platform==="win32")return[i,(0,At.join)(t,".local","bin","codex.exe"),"codex.exe","codex"].filter(s=>!!s&&Vs(s));let e=[i,(0,At.join)(t,".local","bin","codex")];return process.platform==="darwin"&&e.push("/opt/homebrew/bin/codex"),e.push("/usr/local/bin/codex","/usr/bin/codex","codex"),e.filter(s=>!!s&&Vs(s))}function Vs(i){return!i||/[\n\r\0]/.test(i)?!1:i.startsWith("/")?/^[\w/.@+-]+$/.test(i):i.startsWith("~")?/^~[\w/.@+-]*$/.test(i):/^[a-zA-Z]:[\\/]/.test(i)?/^[a-zA-Z]:[\\/][\w\\/. @+-]+$/.test(i):i.startsWith("\\\\")?/^\\\\[\w\\/. @+-]+$/.test(i):!i.includes("/")&&!i.includes("\\")?/^[\w.@+-]+$/.test(i):!1}function sa(i){return!!(i.includes("/")||i.includes("\\"))}function Wt(i){return typeof i=="object"&&i!==null}function A(i){return typeof i=="string"?i:void 0}function Be(i,t){return typeof i=="boolean"?i:t}function Re(i,t){return typeof i=="number"&&Number.isFinite(i)?i:t}function ue(i){return Array.isArray(i)?i.filter(t=>typeof t=="string"):[]}function _i(i){if(!Wt(i))return;let t={};for(let[e,s]of Object.entries(i))typeof s=="string"&&(t[e]=s);return Object.keys(t).length>0?t:void 0}var Ai=!1;function Ei(i,t={}){let e=i.memory_token_budget??t.memory_token_budget;return e!==void 0?Re(e,js):((i.memory_max_entries??t.memory_max_entries)!==void 0&&!Ai&&(Ai=!0,console.info(`Agent Fleet: \`memory_max_entries\` is deprecated and no longer enforced; memory is now bounded by \`memory_token_budget\` (default ${js}). Set that field to tune memory size.`)),js)}function Pi(i,t={}){let e=s=>i[s]??t[s];return{enabled:Be(e("reflection_enabled"),!1),schedule:A(e("reflection_schedule"))??ri,recurrenceThreshold:Re(e("reflection_recurrence_threshold"),oi),proposeSkills:Be(e("reflection_propose_skills"),!1),model:A(e("reflection_model"))}}function Ri(i){return(0,x.normalizePath)(i.replace(/\.md$/,".permissions.json"))}function Di(i){let t=0;for(let e=0;ee.path.startsWith(`${this.getFleetRoot()}/`));for(let e of t)await this.loadFile(e);return this.validateReferences(),this.getSnapshot()}async loadFile(t){let e=typeof t=="string"?this.vault.getAbstractFileByPath(t):t;if(!(e instanceof x.TFile)||e.extension!=="md")return;if(this.isInsideAgentFolder(e.path)){await this.reloadFolderAgentContaining(e.path);return}if(this.isInsideSkillFolder(e.path)){await this.reloadFolderSkillContaining(e.path);return}this.clearStoredFile(e.path);let s=`${this.getSubfolder("channels")}/`;if(e.path.startsWith(s)){if(!e.path.slice(s.length).includes("/")){let c=await this.vault.cachedRead(e),l=this.parseChannelFile(e.path,c);l&&this.channels.set(e.path,l)}return}let n=`${this.getSubfolder("mcp")}/`;if(e.path.startsWith(n)){if(!e.path.slice(n.length).includes("/")){let c=await this.vault.cachedRead(e),l=this.parseMcpServerFile(e.path,c);l&&this.mcpServers.set(e.path,l)}return}let a=await this.vault.cachedRead(e),r=this.parseFile(e.path,a);if(r)if("taskId"in r)this.tasks.set(e.path,r);else if("model"in r){if(!r.isFolder){let o=Ri(e.path),c=this.vault.getAbstractFileByPath(o);if(c instanceof x.TFile)try{let l=await this.vault.cachedRead(c),h=JSON.parse(l);r.permissionRules={allow:ue(h.allow),deny:ue(h.deny)}}catch{}}this.agents.set(e.path,r)}else this.skills.set(e.path,r)}async reloadFolderAgentContaining(t){let e=`${this.getSubfolder("agents")}/`,n=t.slice(e.length).split("/")[0];if(!n)return;let a=(0,x.normalizePath)(`${e}${n}`),r=(0,x.normalizePath)(`${a}/agent.md`);if(this.agents.delete(r),!(this.vault.getAbstractFileByPath(a)instanceof x.TFolder))return;let c=this.vault.getAbstractFileByPath(r);if(!(c instanceof x.TFile))return;let l=await this.loadFolderAgent(a,c);l&&this.agents.set(r,l)}isInsideAgentFolder(t){let e=`${this.getSubfolder("agents")}/`;return t.startsWith(e)?t.slice(e.length).includes("/"):!1}isInsideSkillFolder(t){let e=`${this.getSubfolder("skills")}/`;return t.startsWith(e)?t.slice(e.length).includes("/"):!1}async reloadFolderSkillContaining(t){let e=`${this.getSubfolder("skills")}/`,n=t.slice(e.length).split("/")[0];if(!n)return;let a=(0,x.normalizePath)(`${e}${n}`),r=(0,x.normalizePath)(`${a}/skill.md`);if(this.skills.delete(r),!(this.vault.getAbstractFileByPath(a)instanceof x.TFolder))return;let c=this.vault.getAbstractFileByPath(r);if(!(c instanceof x.TFile))return;let l=await this.loadFolderSkill(a,c);l&&this.skills.set(r,l)}async loadFolderSkills(){let t=this.vault.getAbstractFileByPath(this.getSubfolder("skills"));if(t instanceof x.TFolder)for(let e of t.children){if(!(e instanceof x.TFolder))continue;let s=(0,x.normalizePath)(`${e.path}/skill.md`),n=this.vault.getAbstractFileByPath(s);if(!(n instanceof x.TFile))continue;let a=await this.loadFolderSkill(e.path,n);a&&this.skills.set(s,a)}}async loadFolderSkill(t,e){let s=await this.vault.cachedRead(e),{frontmatter:n,body:a}=J(s),r=A(n.name);if(!r)return this.setIssue(e.path,"Folder skill skill.md requires string field `name`."),null;let o=async c=>{let l=(0,x.normalizePath)(`${t}/${c}`),h=this.vault.getAbstractFileByPath(l);if(!(h instanceof x.TFile))return"";let d=await this.vault.cachedRead(h);return J(d).body};return{filePath:e.path,name:r,description:A(n.description),tags:ue(n.tags),body:a,toolsBody:await o("tools.md"),referencesBody:await o("references.md"),examplesBody:await o("examples.md"),isFolder:!0}}async loadFolderAgents(){let t=this.vault.getAbstractFileByPath(this.getSubfolder("agents"));if(t instanceof x.TFolder)for(let e of t.children){if(!(e instanceof x.TFolder))continue;let s=(0,x.normalizePath)(`${e.path}/agent.md`),n=this.vault.getAbstractFileByPath(s);if(!(n instanceof x.TFile))continue;let a=await this.loadFolderAgent(e.path,n);a&&this.agents.set(s,a)}}async loadFolderAgent(t,e){let s=await this.vault.cachedRead(e),{frontmatter:n,body:a}=J(s),r=A(n.name);if(!r)return this.setIssue(e.path,"Folder agent agent.md requires string field `name`."),null;let o={},c=(0,x.normalizePath)(`${t}/config.md`),l=this.vault.getAbstractFileByPath(c);if(l instanceof x.TFile){let M=await this.vault.cachedRead(l);o=J(M).frontmatter}let h={allow:[],deny:[]},d=(0,x.normalizePath)(`${t}/permissions.json`),u=this.vault.getAbstractFileByPath(d);if(u instanceof x.TFile)try{let M=await this.vault.cachedRead(u),U=JSON.parse(M);h={allow:ue(U.allow),deny:ue(U.deny)}}catch{}if(h.allow.length===0&&h.deny.length===0){let M=ue(o.allowed_tools),U=ue(o.blocked_tools);(M.length>0||U.length>0)&&(h={allow:M,deny:U},this.warnedLegacyPerms.has(r)||(this.warnedLegacyPerms.add(r),console.warn(`Agent Fleet: "${r}" still uses legacy allowed_tools/blocked_tools in config.md. Permission rules now live in permissions.json. Open this agent in Edit and Save to migrate.`)))}let p="",m=(0,x.normalizePath)(`${t}/SKILLS.md`),f=this.vault.getAbstractFileByPath(m);if(f instanceof x.TFile){let M=await this.vault.cachedRead(f);p=J(M).body}let g="",y=(0,x.normalizePath)(`${t}/CONTEXT.md`),v=this.vault.getAbstractFileByPath(y);if(v instanceof x.TFile){let M=await this.vault.cachedRead(v);g=J(M).body}let k=!1,w="",S="",T=!0,_="",D="",O=(0,x.normalizePath)(`${t}/HEARTBEAT.md`),C=this.vault.getAbstractFileByPath(O);if(C instanceof x.TFile){let M=await this.vault.cachedRead(C),U=J(M);k=Be(U.frontmatter.enabled,!1),w=A(U.frontmatter.schedule)??"",T=Be(U.frontmatter.notify,!0),_=A(U.frontmatter.channel)??"",D=A(U.frontmatter.channel_target)??"",S=U.body}let E=A(n.model),P=A(o.model);E&&P&&E!==P&&(this.warnedFolderAgentModelConflict.has(r)||(this.warnedFolderAgentModelConflict.add(r),console.warn(`Agent Fleet: "${r}" has conflicting model fields \u2014 agent.md says "${E}", config.md says "${P}". config.md wins. Remove agent.md's model field or sync the values to silence this warning.`)));let N=P??E??this.settings.defaultModel;return{filePath:e.path,name:r,description:A(n.description),model:N,adapter:A(o.adapter)??"claude-code",permissionMode:A(o.permission_mode)??"bypassPermissions",effort:A(o.effort),maxRetries:Re(o.max_retries,1),skills:ue(n.skills),mcpServers:ue(n.mcp_servers),cwd:A(o.cwd)||A(n.cwd),enabled:Be(n.enabled,!0),timeout:Re(o.timeout,Re(n.timeout,300)),approvalRequired:ue(o.approval_required),memory:Be(o.memory,Be(n.memory,!1)),memoryMaxEntries:Re(o.memory_max_entries,100),memoryTokenBudget:Ei(o,n),reflection:Pi(o,n),autoCompactThreshold:Re(o.auto_compact_threshold??n.auto_compact_threshold,85),tags:ue(n.tags),avatar:A(n.avatar)??"",body:a,contextBody:g,skillsBody:p,env:this.parseEnvMap(o.env),permissionRules:h,isFolder:!0,heartbeatEnabled:k,heartbeatSchedule:w,heartbeatBody:S,heartbeatNotify:T,heartbeatChannel:_,heartbeatChannelTarget:D,wikiKeeper:this.parseWikiKeeperConfig(o.wiki_keeper??n.wiki_keeper),wikiReferences:this.parseWikiReferences(o.wiki_references??n.wiki_references)}}parseWikiReferences(t){if(!Array.isArray(t))return;let e=[];for(let s of t)if(typeof s=="string"&&s.trim())e.push({agent:s.trim()});else if(s&&typeof s=="object"){let n=s.agent;typeof n=="string"&&n.trim()&&e.push({agent:n.trim()})}return e.length>0?e:void 0}parseWikiKeeperConfig(t){if(!t||typeof t!="object")return;let e=t;return{scopeRoot:A(e.scope_root)??"",inboxPath:A(e.inbox_path)??"_sources/inbox",archivePath:A(e.archive_path)??"_sources/archive",failedPath:A(e.failed_path)??"_sources/failed",topicsRoot:A(e.topics_root)??"_topics",indexPath:A(e.index_path)??"index.md",logPath:A(e.log_path)??"log.md",watchedFolders:ue(e.watched_folders),excludePatterns:ue(e.exclude_patterns),watchedSince:A(e.watched_since)??"",fileSubstantiveAnswers:Be(e.file_substantive_answers,!0),obsidianUrlScheme:Be(e.obsidian_url_scheme,!0),maxTokensPerIngest:Re(e.max_tokens_per_ingest,6e4),maxTokensPerRefresh:Re(e.max_tokens_per_refresh,3e4),indexSplitThreshold:Re(e.index_split_threshold,100),dedupSimilarityThreshold:Re(e.dedup_similarity_threshold,.82),summaryStaleDays:Re(e.summary_stale_days,30),stateFile:A(e.state_file)??".wiki-keeper-state.json"}}removeFile(t){this.clearStoredFile(t)}getSnapshot(){return{agents:Array.from(this.agents.values()).sort((t,e)=>t.name.localeCompare(e.name)),skills:Array.from(this.skills.values()).sort((t,e)=>t.name.localeCompare(e.name)),tasks:Array.from(this.tasks.values()).sort((t,e)=>t.taskId.localeCompare(e.taskId)),channels:Array.from(this.channels.values()).sort((t,e)=>t.name.localeCompare(e.name)),mcpServers:Array.from(this.mcpServers.values()).sort((t,e)=>t.name.localeCompare(e.name)),validationIssues:Array.from(this.validationIssues.values()).flat()}}getAgentByName(t){return Array.from(this.agents.values()).find(e=>e.name===t)}getSkillByName(t){return Array.from(this.skills.values()).find(e=>e.name===t)}getTaskById(t){return Array.from(this.tasks.values()).find(e=>e.taskId===t)}getTasksForAgent(t){return Array.from(this.tasks.values()).filter(e=>e.agent===t)}getChannelByName(t){return Array.from(this.channels.values()).find(e=>e.name===t)}getChannelsForAgent(t){return Array.from(this.channels.values()).filter(e=>e.defaultAgent===t)}getMcpServers(){return Array.from(this.mcpServers.values()).sort((t,e)=>t.name.localeCompare(e.name))}getMcpServerByName(t){return Array.from(this.mcpServers.values()).find(e=>e.name===t)}getRunsRoot(){return this.getSubfolder("runs")}getMemoryPath(t){return(0,x.normalizePath)(`${this.getSubfolder("memory")}/${oe(t)}.md`)}getMemoryDir(t){return(0,x.normalizePath)(`${this.getSubfolder("memory")}/${oe(t)}`)}getWorkingMemoryPath(t){return(0,x.normalizePath)(`${this.getMemoryDir(t)}/working.md`)}getRawMemoryPath(t,e){let s=e.slice(0,10);return(0,x.normalizePath)(`${this.getMemoryDir(t)}/raw/${s}.md`)}async readWorkingMemory(t){let e=this.getWorkingMemoryPath(t),s=this.vault.getAbstractFileByPath(e);if(s instanceof x.TFile){let r=await this.vault.cachedRead(s);return gi(r,e,t)}let n=this.getMemoryPath(t),a=this.vault.getAbstractFileByPath(n);if(a instanceof x.TFile){let r=await this.vault.cachedRead(a),{body:o}=J(r);return Zn(o,e,t)}return null}async writeWorkingMemory(t,e){let s=this.getWorkingMemoryPath(t);await this.ensureFolder(this.getMemoryDir(t));let n=yi(e),a=this.vault.getAbstractFileByPath(s);a instanceof x.TFile?await this.vault.modify(a,n):await this.createFileIfMissing(s,n);let r=this.getMemoryPath(t);r!==s&&this.vault.getAbstractFileByPath(r)&&await this.trashFile(r)}async migrateLegacyMemory(t){let e=this.getWorkingMemoryPath(t);if(this.vault.getAbstractFileByPath(e))return;let s=this.getMemoryPath(t),n=this.vault.getAbstractFileByPath(s);if(n instanceof x.TFile&&!this.migratingMemory.has(t)){this.migratingMemory.add(t);try{let a=await this.vault.cachedRead(n),{body:r}=J(a),o=new Date().toISOString(),c=r.split(` +`).map(d=>d.replace(/^\s*[-*]\s+/,"").trim()).filter(d=>d.length>0&&!/^#{1,6}\s/.test(d)),l=[`- ${o} [migrated] Imported ${c.length} legacy memory entr${c.length===1?"y":"ies"} (pre-v2).`,...c.map(d=>`- ${o} [migrated] ${d}`)];await this.appendRawMemory(t,l,o);let h=Zn(r,e,t,o);await this.writeWorkingMemory(t,h)}finally{this.migratingMemory.delete(t)}}}async migrateAllLegacyMemory(){for(let t of this.getSnapshot().agents)if(t.memory)try{await this.migrateLegacyMemory(t.name)}catch(e){console.warn(`Agent Fleet: legacy memory migration failed for "${t.name}"`,e)}}getPendingDir(t){return(0,x.normalizePath)(`${this.getMemoryDir(t)}/pending`)}getPendingDirAbsolutePath(t){let e=this.vault.adapter;return e instanceof x.FileSystemAdapter?(0,Ii.join)(e.getBasePath(),this.getPendingDir(t)):null}async ensureMemoryDir(t){await this.ensureFolder(this.getMemoryDir(t))}async readAndClearPending(t){let e=this.vault.adapter,s=this.getPendingDir(t),n;try{if(!await e.exists(s))return[];n=(await e.list(s)).files}catch{return[]}let a=[];for(let r of n)if(r.endsWith(".json"))try{let o=await e.read(r);await e.remove(r);for(let c of o.split(` `))c.trim().length>0&&a.push(c)}catch{}return a}async readRecentRaw(t,e=2){let s=(0,x.normalizePath)(`${this.getMemoryDir(t)}/raw`),n=this.vault.getAbstractFileByPath(s);if(!(n instanceof x.TFolder))return"";let a=n.children.filter(o=>o instanceof x.TFile&&o.extension==="md").sort((o,c)=>c.name.localeCompare(o.name)).slice(0,e),r=[];for(let o of a.reverse())r.push(`### ${o.basename} ${await this.vault.cachedRead(o)}`);return r.join(` -`)}getCandidatesPath(t){return(0,x.normalizePath)(`${this.getMemoryDir(t)}/candidates.json`)}async readCandidates(t){let e=this.getCandidatesPath(t),s=this.vault.getAbstractFileByPath(e);if(!(s instanceof x.TFile))return[];try{let n=JSON.parse(await this.vault.cachedRead(s));return Array.isArray(n)?n:[]}catch{return[]}}async writeCandidates(t,e){let s=this.getCandidatesPath(t);await this.ensureFolder(this.getMemoryDir(t));let n=JSON.stringify(e,null,2),a=this.vault.getAbstractFileByPath(s);a instanceof x.TFile?await this.vault.modify(a,n):await this.createFileIfMissing(s,n)}getProposalsDir(){return(0,x.normalizePath)(`${this.getFleetRoot()}/proposals`)}async listProposals(){let t=this.vault.getAbstractFileByPath(this.getProposalsDir());if(!(t instanceof x.TFolder))return[];let e=[];for(let s of t.children){if(!(s instanceof x.TFile)||s.extension!=="md")continue;let n=await this.readProposal(s.basename);n&&e.push(n)}return e.sort((s,n)=>n.created.localeCompare(s.created)),e}async readProposal(t){let e=(0,x.normalizePath)(`${this.getProposalsDir()}/${t}.md`),s=this.vault.getAbstractFileByPath(e);if(!(s instanceof x.TFile))return null;let{frontmatter:n,body:a}=J(await this.vault.cachedRead(s)),r=n.type==="skill_modify"?"skill_modify":"skill_create",o=n.status==="accepted"||n.status==="rejected"?n.status:"pending";return{id:t,type:r,agent:A(n.agent)??"",status:o,created:A(n.created)??"",targetSkill:A(n.target_skill),candidate:A(n.candidate),evidence:ue(n.evidence),rationale:A(n.rationale)??"",body:a}}async writeProposal(t){await this.ensureFolder(this.getProposalsDir());let e=(0,x.normalizePath)(`${this.getProposalsDir()}/${t.id}.md`),s={id:t.id,type:t.type,agent:t.agent,status:t.status,created:t.created,target_skill:t.targetSkill||void 0,candidate:t.candidate||void 0,evidence:t.evidence.length?t.evidence:void 0,rationale:t.rationale||void 0},n=W(s,t.body||""),a=this.vault.getAbstractFileByPath(e);a instanceof x.TFile?await this.vault.modify(a,n):await this.createFileIfMissing(e,n)}async setProposalStatus(t,e){let s=await this.readProposal(t);s&&(s.status=e,await this.writeProposal(s))}async applyProposal(t){if(t.type==="skill_create"){let e=t.targetSkill||`learned-${oe(t.rationale).slice(0,24)||"skill"}`,s=await this.getAvailablePath(this.getSubfolder("skills"),oe(e)),a={name:s.split("/").pop()?.replace(/\.md$/,"")||oe(e),description:t.rationale||`Auto-proposed from recurring pattern for ${t.agent}.`,tags:["proposed"]};return await this.vault.create(s,W(a,t.body||"Skill instructions go here.")),s}if(t.targetSkill){let e=this.getSkillByName(t.targetSkill);if(e){let s=this.vault.getAbstractFileByPath(e.filePath);if(s instanceof x.TFile){let n=await this.vault.cachedRead(s),a=` +`)}getCandidatesPath(t){return(0,x.normalizePath)(`${this.getMemoryDir(t)}/candidates.json`)}async readCandidates(t){let e=this.getCandidatesPath(t),s=this.vault.getAbstractFileByPath(e);if(!(s instanceof x.TFile))return[];try{let n=JSON.parse(await this.vault.cachedRead(s));return Array.isArray(n)?n:[]}catch{return[]}}async writeCandidates(t,e){let s=this.getCandidatesPath(t);await this.ensureFolder(this.getMemoryDir(t));let n=JSON.stringify(e,null,2),a=this.vault.getAbstractFileByPath(s);a instanceof x.TFile?await this.vault.modify(a,n):await this.createFileIfMissing(s,n)}getProposalsDir(){return(0,x.normalizePath)(`${this.getFleetRoot()}/proposals`)}async listProposals(){let t=this.vault.getAbstractFileByPath(this.getProposalsDir());if(!(t instanceof x.TFolder))return[];let e=[];for(let s of t.children){if(!(s instanceof x.TFile)||s.extension!=="md")continue;let n=await this.readProposal(s.basename);n&&e.push(n)}return e.sort((s,n)=>n.created.localeCompare(s.created)),e}async readProposal(t){let e=(0,x.normalizePath)(`${this.getProposalsDir()}/${t}.md`),s=this.vault.getAbstractFileByPath(e);if(!(s instanceof x.TFile))return null;let{frontmatter:n,body:a}=J(await this.vault.cachedRead(s)),r=n.type==="skill_modify"?"skill_modify":"skill_create",o=n.status==="accepted"||n.status==="rejected"?n.status:"pending";return{id:t,type:r,agent:A(n.agent)??"",status:o,created:A(n.created)??"",targetSkill:A(n.target_skill),candidate:A(n.candidate),evidence:ue(n.evidence),rationale:A(n.rationale)??"",body:a}}async writeProposal(t){await this.ensureFolder(this.getProposalsDir());let e=(0,x.normalizePath)(`${this.getProposalsDir()}/${t.id}.md`),s={id:t.id,type:t.type,agent:t.agent,status:t.status,created:t.created,target_skill:t.targetSkill||void 0,candidate:t.candidate||void 0,evidence:t.evidence.length?t.evidence:void 0,rationale:t.rationale||void 0},n=H(s,t.body||""),a=this.vault.getAbstractFileByPath(e);a instanceof x.TFile?await this.vault.modify(a,n):await this.createFileIfMissing(e,n)}async setProposalStatus(t,e){let s=await this.readProposal(t);s&&(s.status=e,await this.writeProposal(s))}async applyProposal(t){if(t.type==="skill_create"){let e=t.targetSkill||`learned-${oe(t.rationale).slice(0,24)||"skill"}`,s=await this.getAvailablePath(this.getSubfolder("skills"),oe(e)),a={name:s.split("/").pop()?.replace(/\.md$/,"")||oe(e),description:t.rationale||`Auto-proposed from recurring pattern for ${t.agent}.`,tags:["proposed"]};return await this.vault.create(s,H(a,t.body||"Skill instructions go here.")),s}if(t.targetSkill){let e=this.getSkillByName(t.targetSkill);if(e){let s=this.vault.getAbstractFileByPath(e.filePath);if(s instanceof x.TFile){let n=await this.vault.cachedRead(s),a=` ## Proposed update (${new Date().toISOString().slice(0,10)}) ${t.body}`;return await this.vault.modify(s,`${n.trimEnd()}${a} @@ -11724,13 +11752,15 @@ ${t.body}`;return await this.vault.modify(s,`${n.trimEnd()}${a} `),r=this.vault.getAbstractFileByPath(n);if(r instanceof x.TFile){let o=await this.vault.cachedRead(r);await this.vault.modify(r,`${o.trimEnd()} ${a} `)}else await this.createFileIfMissing(n,`${a} -`)}getConversationsDir(t){if(t.isFolder){let s=t.filePath.replace(/\/agent\.md$/,"");return(0,x.normalizePath)(`${s}/conversations`)}let e=this.getMemoryPath(t.name).replace(/\/[^/]+$/,"");return(0,x.normalizePath)(`${e}/${t.name}-conversations`)}getConversationPath(t,e){let s=this.getConversationsDir(t),n=oe(e)||"conversation";return(0,x.normalizePath)(`${s}/${n}.json`)}async listConversations(t){let e=[],s=this.getConversationsDir(t),n=this.vault.getAbstractFileByPath(s);if(n instanceof x.TFolder)for(let a of n.children){if(!(a instanceof x.TFile)||a.extension!=="json")continue;let r=a.basename,o=await this.readConversationMeta(a,r);o&&e.push(o)}return e.sort((a,r)=>r.lastActive.localeCompare(a.lastActive)),e}async readConversationMeta(t,e){try{let s=await this.vault.cachedRead(t),n=JSON.parse(s);return{id:e,name:n.name?.trim()||"Untitled",lastActive:n.lastActive??new Date(t.stat.mtime).toISOString(),messageCount:Array.isArray(n.messages)?n.messages.length:0}}catch{return null}}async createConversation(t,e,s){let n=this.getConversationPath(t,e);if(this.vault.getAbstractFileByPath(n)instanceof x.TFile)return;let r=n.replace(/\/[^/]+$/,"");await this.ensureFolder(r);let o=new Date().toISOString(),c={sessionId:null,messages:[],lastActive:o,createdAt:o,name:s.trim()};await this.vault.create(n,JSON.stringify(c,null,2))}async renameConversation(t,e,s){let n=this.getConversationPath(t,e),a=this.vault.getAbstractFileByPath(n);if(!(a instanceof x.TFile))return;let r=await this.vault.cachedRead(a),o;try{o=JSON.parse(r)}catch{return}o.name=s.trim(),await this.vault.modify(a,JSON.stringify(o,null,2))}async deleteConversation(t,e){let s=this.getConversationPath(t,e),n=this.vault.getAbstractFileByPath(s);n instanceof x.TFile&&await this.app.fileManager.trashFile(n);let a=s.replace(/\.json$/,".threads"),r=this.vault.getAbstractFileByPath(a);r instanceof x.TFolder&&await this.app.fileManager.trashFile(r);let o=this.getConversationsDir(t),c=this.vault.getAbstractFileByPath(o);c instanceof x.TFolder&&c.children.length===0&&await this.app.fileManager.trashFile(c)}async getMemory(t){let e=await this.readWorkingMemory(t);return e?{filePath:e.filePath,agent:e.agent,lastUpdated:e.lastUpdated,body:Ge(e.sections)}:null}async appendMemory(t,e){if(e.length===0)return;let s=this.getMemoryPath(t),n=this.vault.getAbstractFileByPath(s),a=new Date().toISOString(),r=e.map(o=>`- ${o.trim()}`).join(` +`)}getConversationsDir(t){if(t.isFolder){let s=t.filePath.replace(/\/agent\.md$/,"");return(0,x.normalizePath)(`${s}/conversations`)}let e=this.getMemoryPath(t.name).replace(/\/[^/]+$/,"");return(0,x.normalizePath)(`${e}/${t.name}-conversations`)}getConversationPath(t,e){let s=this.getConversationsDir(t),n=oe(e)||"conversation";return(0,x.normalizePath)(`${s}/${n}.json`)}async listConversations(t){let e=[],s=this.getConversationsDir(t),n=this.vault.getAbstractFileByPath(s);if(n instanceof x.TFolder)for(let a of n.children){if(!(a instanceof x.TFile)||a.extension!=="json")continue;let r=a.basename,o=await this.readConversationMeta(a,r);o&&e.push(o)}return e.sort((a,r)=>r.lastActive.localeCompare(a.lastActive)),e}async readConversationMeta(t,e){try{let s=await this.vault.cachedRead(t),n=JSON.parse(s);return{id:e,name:n.name?.trim()||"Untitled",lastActive:n.lastActive??new Date(t.stat.mtime).toISOString(),messageCount:Array.isArray(n.messages)?n.messages.length:0}}catch{return null}}async createConversation(t,e,s){let n=this.getConversationPath(t,e);if(this.vault.getAbstractFileByPath(n)instanceof x.TFile)return;let r=n.replace(/\/[^/]+$/,"");await this.ensureFolder(r);let o=new Date().toISOString(),c={sessionId:null,messages:[],lastActive:o,createdAt:o,name:s.trim()};await this.vault.create(n,JSON.stringify(c,null,2))}async renameConversation(t,e,s){let n=this.getConversationPath(t,e),a=this.vault.getAbstractFileByPath(n);if(!(a instanceof x.TFile))return;let r=await this.vault.cachedRead(a),o;try{o=JSON.parse(r)}catch{return}o.name=s.trim(),await this.vault.modify(a,JSON.stringify(o,null,2))}async deleteConversation(t,e){let s=this.getConversationPath(t,e),n=this.vault.getAbstractFileByPath(s);n instanceof x.TFile&&await this.app.fileManager.trashFile(n);let a=s.replace(/\.json$/,".threads"),r=this.vault.getAbstractFileByPath(a);r instanceof x.TFolder&&await this.app.fileManager.trashFile(r);let o=this.getConversationsDir(t),c=this.vault.getAbstractFileByPath(o);c instanceof x.TFolder&&c.children.length===0&&await this.app.fileManager.trashFile(c)}async getMemory(t){let e=await this.readWorkingMemory(t);return e?{filePath:e.filePath,agent:e.agent,lastUpdated:e.lastUpdated,body:Ke(e.sections)}:null}async appendMemory(t,e){if(e.length===0)return;let s=this.getMemoryPath(t),n=this.vault.getAbstractFileByPath(s),a=new Date().toISOString(),r=e.map(o=>`- ${o.trim()}`).join(` `);if(n instanceof x.TFile){let c=`${(await this.getMemory(t))?.body.trim()||"## Learned Context"} -${r}`.trim();await this.vault.modify(n,W({agent:t,last_updated:a},c));return}await this.createFileIfMissing(s,W({agent:t,last_updated:a},`## Learned Context +${r}`.trim();await this.vault.modify(n,H({agent:t,last_updated:a},c));return}await this.createFileIfMissing(s,H({agent:t,last_updated:a},`## Learned Context -${r}`))}async listRecentRuns(t=50){let e=this.vault.getAbstractFileByPath(this.getRunsRoot());if(!(e instanceof x.TFolder))return[];let s=[];this.collectMarkdownChildren(e,s),s.sort((r,o)=>o.path.localeCompare(r.path));let n=s.slice(0,t),a=[];for(let r of n){let o=await this.readRunLog(r);o&&a.push(o)}return a.sort((r,o)=>o.started.localeCompare(r.started))}async listRunsSince(t){let e=this.vault.getAbstractFileByPath(this.getRunsRoot());if(!(e instanceof x.TFolder))return[];let s=`${t.getFullYear()}-${String(t.getMonth()+1).padStart(2,"0")}-${String(t.getDate()).padStart(2,"0")}`,n=[];for(let r of e.children)r instanceof x.TFolder&&(r.nameo.started.localeCompare(r.started))}async readRunLog(t){let e=await this.vault.cachedRead(t),{frontmatter:s,body:n}=J(e),a=n.match(/## Prompt\n([\s\S]*?)(?:\n## Result\n|\n## Output\n|$)/),r=n.match(/## Result\n([\s\S]*?)(?:\n## Output\n|$)/),o=n.match(/## Output\n([\s\S]*?)(?:\n## Tools Used\n|$)/),c=n.match(/## Tools Used\n([\s\S]*?)(?:\n## STDERR\n|$)/);return{filePath:t.path,runId:A(s.run_id)??t.basename,agent:A(s.agent)??"unknown",task:A(s.task)??"unknown",status:A(s.status)??"failure",started:A(s.started)??new Date(t.stat.ctime).toISOString(),completed:A(s.completed),durationSeconds:De(s.duration_seconds,0),tokensUsed:typeof s.tokens_used=="number"?s.tokens_used:void 0,costUsd:typeof s.cost_usd=="number"?s.cost_usd:void 0,model:A(s.model)??at.defaultModel,modelSource:(()=>{let l=A(s.model_source);if(l==="task"||l==="agent"||l==="settings"||l==="cli-default")return l})(),concreteModel:A(s.resolved_concrete_model),exitCode:typeof s.exit_code=="number"?s.exit_code:null,tags:ue(s.tags),prompt:a?.[1]?.trim()??"",output:o?.[1]?.trim()??"",finalResult:r?.[1]?.trim()||void 0,toolsUsed:c?.[1]?ge(c[1]).map(l=>l.replace(/^- /,"").trim()).filter(Boolean):[],approvals:this.parseApprovals(s.approvals)}}async writeRunLog(t){let e=new Date(t.started),s=(0,x.normalizePath)(`${this.getRunsRoot()}/${e.toISOString().slice(0,10)}`);await this.ensureFolder(s);let n=`${e.toISOString().slice(11,19).replace(/:/g,"")}-${oe(t.agent)}-${oe(t.task)}.md`,a=(0,x.normalizePath)(`${s}/${n}`),r=W({run_id:t.runId,agent:t.agent,task:t.task,status:t.status,started:t.started,completed:t.completed,duration_seconds:t.durationSeconds,tokens_used:t.tokensUsed,cost_usd:t.costUsd,model:t.model,model_source:t.modelSource,resolved_concrete_model:t.concreteModel,exit_code:t.exitCode,tags:t.tags,approvals:t.approvals},["## Prompt","",t.prompt.trim(),"",...t.finalResult&&t.finalResult.trim()?["## Result","",t.finalResult.trim(),""]:[],"## Output","",t.output.trim()||"(no output)","","## Tools Used","",...t.toolsUsed.length>0?t.toolsUsed.map(c=>`- ${c}`):["- none"],...t.stderr?["","## STDERR","",t.stderr.trim()]:[]].join(` -`)),o=this.vault.getAbstractFileByPath(a);return o instanceof x.TFile?await this.vault.modify(o,r):await this.vault.create(a,r),a}async updateTaskRunMetadata(t,e){let s=this.vault.getAbstractFileByPath(t.filePath);if(!(s instanceof x.TFile))return;let n=await this.vault.cachedRead(s),{frontmatter:a,body:r}=J(n),o={...a,last_run:e.lastRun??t.lastRun,next_run:e.nextRun??t.nextRun,run_count:e.runCount??t.runCount};await this.vault.modify(s,W(o,r)),await this.loadFile(s)}async setApprovalDecision(t,e,s){let n=this.vault.getAbstractFileByPath(t);if(!(n instanceof x.TFile))return;let a=await this.vault.cachedRead(n),{frontmatter:r,body:o}=J(a),c=(this.parseApprovals(r.approvals)??[]).map(l=>l.tool===e?{...l,status:s,resolvedAt:new Date().toISOString()}:l);await this.vault.modify(n,W({...r,approvals:c},o))}async createAgentTemplate(t){let e=await this.getAvailablePath(this.getSubfolder("agents"),oe(t)),s=`--- +${r}`))}async listRecentRuns(t=50){let e=this.vault.getAbstractFileByPath(this.getRunsRoot());if(!(e instanceof x.TFolder))return[];let s=[];this.collectMarkdownChildren(e,s),s.sort((r,o)=>o.path.localeCompare(r.path));let n=s.slice(0,t),a=[];for(let r of n){let o=await this.readRunLog(r);o&&a.push(o)}return a.sort((r,o)=>o.started.localeCompare(r.started))}async listRunsSince(t){let e=this.vault.getAbstractFileByPath(this.getRunsRoot());if(!(e instanceof x.TFolder))return[];let s=`${t.getFullYear()}-${String(t.getMonth()+1).padStart(2,"0")}-${String(t.getDate()).padStart(2,"0")}`,n=[];for(let r of e.children)r instanceof x.TFolder&&(r.nameo.started.localeCompare(r.started))}usageLedgerPath(t){return(0,x.normalizePath)(`${this.getSubfolder("usage")}/${t.slice(0,10)}.jsonl`)}async appendUsage(t){await this.ensureFolder(this.getSubfolder("usage"));let e=this.usageLedgerPath(t.ts),s=`${JSON.stringify(t)} +`,n=this.vault.adapter;await n.exists(e)?await n.append(e,s):await n.write(e,s)}async readUsageSince(t){let e=this.getSubfolder("usage"),s=this.vault.adapter;if(!await s.exists(e))return[];let n=`${t.getFullYear()}-${String(t.getMonth()+1).padStart(2,"0")}-${String(t.getDate()).padStart(2,"0")}`,a=[],r=await s.list(e);for(let o of r.files){if(!o.endsWith(".jsonl")||(o.split("/").pop()??"").replace(/\.jsonl$/,""){let l=A(s.model_source);if(l==="task"||l==="agent"||l==="settings"||l==="cli-default")return l})(),concreteModel:A(s.resolved_concrete_model),exitCode:typeof s.exit_code=="number"?s.exit_code:null,tags:ue(s.tags),prompt:a?.[1]?.trim()??"",output:o?.[1]?.trim()??"",finalResult:r?.[1]?.trim()||void 0,toolsUsed:c?.[1]?ye(c[1]).map(l=>l.replace(/^- /,"").trim()).filter(Boolean):[],approvals:this.parseApprovals(s.approvals)}}async writeRunLog(t){let e=new Date(t.started),s=(0,x.normalizePath)(`${this.getRunsRoot()}/${e.toISOString().slice(0,10)}`);await this.ensureFolder(s);let n=`${e.toISOString().slice(11,19).replace(/:/g,"")}-${oe(t.agent)}-${oe(t.task)}.md`,a=(0,x.normalizePath)(`${s}/${n}`),r=H({run_id:t.runId,agent:t.agent,task:t.task,status:t.status,started:t.started,completed:t.completed,duration_seconds:t.durationSeconds,tokens_used:t.tokensUsed,cost_usd:t.costUsd,model:t.model,model_source:t.modelSource,resolved_concrete_model:t.concreteModel,exit_code:t.exitCode,tags:t.tags,approvals:t.approvals},["## Prompt","",t.prompt.trim(),"",...t.finalResult&&t.finalResult.trim()?["## Result","",t.finalResult.trim(),""]:[],"## Output","",t.output.trim()||"(no output)","","## Tools Used","",...t.toolsUsed.length>0?t.toolsUsed.map(c=>`- ${c}`):["- none"],...t.stderr?["","## STDERR","",t.stderr.trim()]:[]].join(` +`)),o=this.vault.getAbstractFileByPath(a);return o instanceof x.TFile?await this.vault.modify(o,r):await this.vault.create(a,r),a}async updateTaskRunMetadata(t,e){let s=this.vault.getAbstractFileByPath(t.filePath);if(!(s instanceof x.TFile))return;let n=await this.vault.cachedRead(s),{frontmatter:a,body:r}=J(n),o={...a,last_run:e.lastRun??t.lastRun,next_run:e.nextRun??t.nextRun,run_count:e.runCount??t.runCount};await this.vault.modify(s,H(o,r)),await this.loadFile(s)}async setApprovalDecision(t,e,s){let n=this.vault.getAbstractFileByPath(t);if(!(n instanceof x.TFile))return;let a=await this.vault.cachedRead(n),{frontmatter:r,body:o}=J(a),c=(this.parseApprovals(r.approvals)??[]).map(l=>l.tool===e?{...l,status:s,resolvedAt:new Date().toISOString()}:l);await this.vault.modify(n,H({...r,approvals:c},o))}async createAgentTemplate(t){let e=await this.getAvailablePath(this.getSubfolder("agents"),oe(t)),s=`--- name: ${oe(t)} description: enabled: true @@ -11739,7 +11769,7 @@ tags: [] --- Agent instructions go here. -`;return await this.vault.create(e,s)}async createAgentFolder(t){let e=oe(t.name),s=(0,x.normalizePath)(`${this.getSubfolder("agents")}/${e}`);await this.ensureFolder(s);let n={name:t.name,description:t.description||void 0,avatar:t.avatar||void 0,enabled:t.enabled??!0,tags:t.tags,skills:t.skills,mcp_servers:t.mcpServers?.length?t.mcpServers:void 0};t.model&&t.model!=="default"&&(n.model=t.model);let a=(0,x.normalizePath)(`${s}/agent.md`);await this.vault.create(a,W(n,t.systemPrompt||""));let r={model:t.model||"default",adapter:t.adapter||"claude-code",timeout:t.timeout,max_retries:1,cwd:t.cwd||"",permission_mode:t.permissionMode||"bypassPermissions",effort:t.effort||void 0,approval_required:t.approvalRequired,memory:t.memory,memory_max_entries:t.memoryMaxEntries};typeof t.autoCompactThreshold=="number"&&(r.auto_compact_threshold=t.autoCompactThreshold),t.wikiReferences&&t.wikiReferences.length>0&&(r.wiki_references=t.wikiReferences.map(d=>({agent:d})));let o=(0,x.normalizePath)(`${s}/config.md`);await this.vault.create(o,W(r,""));let c=(0,x.normalizePath)(`${s}/SKILLS.md`);await this.vault.create(c,W({},t.skillsBody||""));let l=(0,x.normalizePath)(`${s}/CONTEXT.md`);await this.vault.create(l,W({},t.contextBody||""));let h=t.permissionRules;if(h&&(h.allow.length>0||h.deny.length>0)){let d=(0,x.normalizePath)(`${s}/permissions.json`);await this.vault.create(d,JSON.stringify(h,null,2)+` +`;return await this.vault.create(e,s)}async createAgentFolder(t){let e=oe(t.name),s=(0,x.normalizePath)(`${this.getSubfolder("agents")}/${e}`);await this.ensureFolder(s);let n={name:t.name,description:t.description||void 0,avatar:t.avatar||void 0,enabled:t.enabled??!0,tags:t.tags,skills:t.skills,mcp_servers:t.mcpServers?.length?t.mcpServers:void 0};t.model&&t.model!=="default"&&(n.model=t.model);let a=(0,x.normalizePath)(`${s}/agent.md`);await this.vault.create(a,H(n,t.systemPrompt||""));let r={model:t.model||"default",adapter:t.adapter||"claude-code",timeout:t.timeout,max_retries:1,cwd:t.cwd||"",permission_mode:t.permissionMode||"bypassPermissions",effort:t.effort||void 0,approval_required:t.approvalRequired,memory:t.memory,memory_max_entries:t.memoryMaxEntries};typeof t.autoCompactThreshold=="number"&&(r.auto_compact_threshold=t.autoCompactThreshold),t.wikiReferences&&t.wikiReferences.length>0&&(r.wiki_references=t.wikiReferences.map(d=>({agent:d})));let o=(0,x.normalizePath)(`${s}/config.md`);await this.vault.create(o,H(r,""));let c=(0,x.normalizePath)(`${s}/SKILLS.md`);await this.vault.create(c,H({},t.skillsBody||""));let l=(0,x.normalizePath)(`${s}/CONTEXT.md`);await this.vault.create(l,H({},t.contextBody||""));let h=t.permissionRules;if(h&&(h.allow.length>0||h.deny.length>0)){let d=(0,x.normalizePath)(`${s}/permissions.json`);await this.vault.create(d,JSON.stringify(h,null,2)+` `)}return a}async createSkillTemplate(t){let e=await this.getAvailablePath(this.getSubfolder("skills"),oe(t)),s=`--- name: ${oe(t)} description: @@ -11747,15 +11777,15 @@ tags: [] --- Skill instructions go here. -`;return await this.vault.create(e,s)}async createSkillFolder(t){let e=(0,x.normalizePath)(`${this.getSubfolder("skills")}/${oe(t.name)}`);await this.ensureFolder(e);let s={name:t.name,description:t.description||void 0,tags:t.tags.length>0?t.tags:void 0},n=(0,x.normalizePath)(`${e}/skill.md`);if(await this.createFileIfMissing(n,W(s,t.body||"Skill instructions go here.")),t.toolsBody){let a=(0,x.normalizePath)(`${e}/tools.md`);await this.createFileIfMissing(a,`# Tools +`;return await this.vault.create(e,s)}async createSkillFolder(t){let e=(0,x.normalizePath)(`${this.getSubfolder("skills")}/${oe(t.name)}`);await this.ensureFolder(e);let s={name:t.name,description:t.description||void 0,tags:t.tags.length>0?t.tags:void 0},n=(0,x.normalizePath)(`${e}/skill.md`);if(await this.createFileIfMissing(n,H(s,t.body||"Skill instructions go here.")),t.toolsBody){let a=(0,x.normalizePath)(`${e}/tools.md`);await this.createFileIfMissing(a,`# Tools ${t.toolsBody}`)}if(t.referencesBody){let a=(0,x.normalizePath)(`${e}/references.md`);await this.createFileIfMissing(a,`# References ${t.referencesBody}`)}if(t.examplesBody){let a=(0,x.normalizePath)(`${e}/examples.md`);await this.createFileIfMissing(a,`# Examples -${t.examplesBody}`)}}async updateAgent(t,e){let s=this.getAgentByName(t);if(s)if(s.isFolder){let n=(0,x.normalizePath)(s.filePath.replace(/\/agent\.md$/,"")),a=this.vault.getAbstractFileByPath(s.filePath);if(a instanceof x.TFile){let c=await this.vault.cachedRead(a),{frontmatter:l,body:h}=J(c);e.description!==void 0&&(l.description=e.description||void 0),e.avatar!==void 0&&(l.avatar=e.avatar||void 0),e.tags!==void 0&&(l.tags=e.tags),e.skills!==void 0&&(l.skills=e.skills),e.mcpServers!==void 0&&(l.mcp_servers=e.mcpServers.length>0?e.mcpServers:void 0),e.enabled!==void 0&&(l.enabled=e.enabled),e.model!==void 0&&e.model!=="default"&&(l.model=e.model);let d=e.systemPrompt!==void 0?e.systemPrompt:h;await this.vault.modify(a,W(l,d))}let r=(0,x.normalizePath)(`${n}/config.md`),o=this.vault.getAbstractFileByPath(r);if(o instanceof x.TFile){let c=await this.vault.cachedRead(o),{frontmatter:l,body:h}=J(c);e.model!==void 0&&(l.model=e.model),e.adapter!==void 0&&(l.adapter=e.adapter),e.timeout!==void 0&&(l.timeout=e.timeout),e.cwd!==void 0&&(l.cwd=e.cwd),e.permissionMode!==void 0&&(l.permission_mode=e.permissionMode),e.effort!==void 0&&(l.effort=e.effort||void 0),e.approvalRequired!==void 0&&(l.approval_required=e.approvalRequired),e.memory!==void 0&&(l.memory=e.memory),e.memoryTokenBudget!==void 0&&(l.memory_token_budget=e.memoryTokenBudget),e.reflectionEnabled!==void 0&&(l.reflection_enabled=e.reflectionEnabled),e.reflectionSchedule!==void 0&&(l.reflection_schedule=e.reflectionSchedule),e.reflectionProposeSkills!==void 0&&(l.reflection_propose_skills=e.reflectionProposeSkills),e.autoCompactThreshold!==void 0&&(l.auto_compact_threshold=e.autoCompactThreshold),e.wikiReferences!==void 0&&(l.wiki_references=e.wikiReferences.length>0?e.wikiReferences.map(d=>({agent:d})):void 0),delete l.allowed_tools,delete l.blocked_tools,await this.vault.modify(o,W(l,h))}if(e.skillsBody!==void 0){let c=(0,x.normalizePath)(`${n}/SKILLS.md`),l=this.vault.getAbstractFileByPath(c);l instanceof x.TFile?await this.vault.modify(l,W({},e.skillsBody)):await this.vault.create(c,W({},e.skillsBody))}if(e.contextBody!==void 0){let c=(0,x.normalizePath)(`${n}/CONTEXT.md`),l=this.vault.getAbstractFileByPath(c);l instanceof x.TFile?await this.vault.modify(l,W({},e.contextBody)):await this.vault.create(c,W({},e.contextBody))}if(e.permissionRules!==void 0){let c=(0,x.normalizePath)(`${n}/permissions.json`),l=this.vault.getAbstractFileByPath(c),h=e.permissionRules;if(h.allow.length>0||h.deny.length>0){let d=JSON.stringify(h,null,2)+` -`;l instanceof x.TFile?await this.vault.modify(l,d):await this.vault.create(c,d)}else l instanceof x.TFile&&await this.app.fileManager.trashFile(l)}}else{let n=this.vault.getAbstractFileByPath(s.filePath);if(!(n instanceof x.TFile))return;let a=await this.vault.cachedRead(n),{frontmatter:r,body:o}=J(a);e.description!==void 0&&(r.description=e.description||void 0),e.avatar!==void 0&&(r.avatar=e.avatar||void 0),e.tags!==void 0&&(r.tags=e.tags),e.skills!==void 0&&(r.skills=e.skills),e.mcpServers!==void 0&&(r.mcp_servers=e.mcpServers.length>0?e.mcpServers:void 0),e.enabled!==void 0&&(r.enabled=e.enabled),e.model!==void 0&&(r.model=e.model),e.adapter!==void 0&&(r.adapter=e.adapter),e.timeout!==void 0&&(r.timeout=e.timeout),e.cwd!==void 0&&(r.cwd=e.cwd),e.permissionMode!==void 0&&(r.permission_mode=e.permissionMode),e.effort!==void 0&&(r.effort=e.effort||void 0),e.approvalRequired!==void 0&&(r.approval_required=e.approvalRequired),e.memory!==void 0&&(r.memory=e.memory),e.autoCompactThreshold!==void 0&&(r.auto_compact_threshold=e.autoCompactThreshold),e.wikiReferences!==void 0&&(r.wiki_references=e.wikiReferences.length>0?e.wikiReferences.map(l=>({agent:l})):void 0),delete r.allowed_tools,delete r.blocked_tools;let c=e.systemPrompt!==void 0?e.systemPrompt:o;if(await this.vault.modify(n,W(r,c)),e.permissionRules!==void 0){let l=ki(s.filePath),h=this.vault.getAbstractFileByPath(l),d=e.permissionRules;if(d.allow.length>0||d.deny.length>0){let u=JSON.stringify(d,null,2)+` -`;h instanceof x.TFile?await this.vault.modify(h,u):await this.vault.create(l,u)}else h instanceof x.TFile&&await this.app.fileManager.trashFile(h)}}}async updateTask(t,e){let s=this.getTaskById(t);if(!s)return;let n=this.vault.getAbstractFileByPath(s.filePath);if(!(n instanceof x.TFile))return;let a=await this.vault.cachedRead(n),{frontmatter:r,body:o}=J(a);e.agent!==void 0&&(r.agent=e.agent),e.type!==void 0&&(r.type=e.type),e.schedule!==void 0&&(r.schedule=e.schedule||void 0),e.runAt!==void 0&&(r.run_at=e.runAt||void 0),e.enabled!==void 0&&(r.enabled=e.enabled),e.priority!==void 0&&(r.priority=e.priority),e.catch_up!==void 0&&(r.catch_up=e.catch_up),e.effort!==void 0&&(r.effort=e.effort||void 0),e.model!==void 0&&(r.model=e.model||void 0),e.tags!==void 0&&(r.tags=e.tags);let c=e.body!==void 0?e.body:o;await this.vault.modify(n,W(r,c))}async updateSkill(t,e){let s=this.getSkillByName(t);if(s)if(s.isFolder){let n=(0,x.normalizePath)(s.filePath.replace(/\/skill\.md$/,"")),a=this.vault.getAbstractFileByPath(s.filePath);if(a instanceof x.TFile){let r=await this.vault.cachedRead(a),{frontmatter:o,body:c}=J(r);e.description!==void 0&&(o.description=e.description||void 0),e.tags!==void 0&&(o.tags=e.tags.length>0?e.tags:void 0);let l=e.body!==void 0?e.body:c;await this.vault.modify(a,W(o,l))}if(e.toolsBody!==void 0){let r=(0,x.normalizePath)(`${n}/tools.md`),o=this.vault.getAbstractFileByPath(r);e.toolsBody&&(o instanceof x.TFile?await this.vault.modify(o,`# Tools +${t.examplesBody}`)}}async updateAgent(t,e){let s=this.getAgentByName(t);if(s)if(s.isFolder){let n=(0,x.normalizePath)(s.filePath.replace(/\/agent\.md$/,"")),a=this.vault.getAbstractFileByPath(s.filePath);if(a instanceof x.TFile){let c=await this.vault.cachedRead(a),{frontmatter:l,body:h}=J(c);e.description!==void 0&&(l.description=e.description||void 0),e.avatar!==void 0&&(l.avatar=e.avatar||void 0),e.tags!==void 0&&(l.tags=e.tags),e.skills!==void 0&&(l.skills=e.skills),e.mcpServers!==void 0&&(l.mcp_servers=e.mcpServers.length>0?e.mcpServers:void 0),e.enabled!==void 0&&(l.enabled=e.enabled),e.model!==void 0&&e.model!=="default"&&(l.model=e.model);let d=e.systemPrompt!==void 0?e.systemPrompt:h;await this.vault.modify(a,H(l,d))}let r=(0,x.normalizePath)(`${n}/config.md`),o=this.vault.getAbstractFileByPath(r);if(o instanceof x.TFile){let c=await this.vault.cachedRead(o),{frontmatter:l,body:h}=J(c);e.model!==void 0&&(l.model=e.model),e.adapter!==void 0&&(l.adapter=e.adapter),e.timeout!==void 0&&(l.timeout=e.timeout),e.cwd!==void 0&&(l.cwd=e.cwd),e.permissionMode!==void 0&&(l.permission_mode=e.permissionMode),e.effort!==void 0&&(l.effort=e.effort||void 0),e.approvalRequired!==void 0&&(l.approval_required=e.approvalRequired),e.memory!==void 0&&(l.memory=e.memory),e.memoryTokenBudget!==void 0&&(l.memory_token_budget=e.memoryTokenBudget),e.reflectionEnabled!==void 0&&(l.reflection_enabled=e.reflectionEnabled),e.reflectionSchedule!==void 0&&(l.reflection_schedule=e.reflectionSchedule),e.reflectionProposeSkills!==void 0&&(l.reflection_propose_skills=e.reflectionProposeSkills),e.autoCompactThreshold!==void 0&&(l.auto_compact_threshold=e.autoCompactThreshold),e.wikiReferences!==void 0&&(l.wiki_references=e.wikiReferences.length>0?e.wikiReferences.map(d=>({agent:d})):void 0),delete l.allowed_tools,delete l.blocked_tools,await this.vault.modify(o,H(l,h))}if(e.skillsBody!==void 0){let c=(0,x.normalizePath)(`${n}/SKILLS.md`),l=this.vault.getAbstractFileByPath(c);l instanceof x.TFile?await this.vault.modify(l,H({},e.skillsBody)):await this.vault.create(c,H({},e.skillsBody))}if(e.contextBody!==void 0){let c=(0,x.normalizePath)(`${n}/CONTEXT.md`),l=this.vault.getAbstractFileByPath(c);l instanceof x.TFile?await this.vault.modify(l,H({},e.contextBody)):await this.vault.create(c,H({},e.contextBody))}if(e.permissionRules!==void 0){let c=(0,x.normalizePath)(`${n}/permissions.json`),l=this.vault.getAbstractFileByPath(c),h=e.permissionRules;if(h.allow.length>0||h.deny.length>0){let d=JSON.stringify(h,null,2)+` +`;l instanceof x.TFile?await this.vault.modify(l,d):await this.vault.create(c,d)}else l instanceof x.TFile&&await this.app.fileManager.trashFile(l)}}else{let n=this.vault.getAbstractFileByPath(s.filePath);if(!(n instanceof x.TFile))return;let a=await this.vault.cachedRead(n),{frontmatter:r,body:o}=J(a);e.description!==void 0&&(r.description=e.description||void 0),e.avatar!==void 0&&(r.avatar=e.avatar||void 0),e.tags!==void 0&&(r.tags=e.tags),e.skills!==void 0&&(r.skills=e.skills),e.mcpServers!==void 0&&(r.mcp_servers=e.mcpServers.length>0?e.mcpServers:void 0),e.enabled!==void 0&&(r.enabled=e.enabled),e.model!==void 0&&(r.model=e.model),e.adapter!==void 0&&(r.adapter=e.adapter),e.timeout!==void 0&&(r.timeout=e.timeout),e.cwd!==void 0&&(r.cwd=e.cwd),e.permissionMode!==void 0&&(r.permission_mode=e.permissionMode),e.effort!==void 0&&(r.effort=e.effort||void 0),e.approvalRequired!==void 0&&(r.approval_required=e.approvalRequired),e.memory!==void 0&&(r.memory=e.memory),e.autoCompactThreshold!==void 0&&(r.auto_compact_threshold=e.autoCompactThreshold),e.wikiReferences!==void 0&&(r.wiki_references=e.wikiReferences.length>0?e.wikiReferences.map(l=>({agent:l})):void 0),delete r.allowed_tools,delete r.blocked_tools;let c=e.systemPrompt!==void 0?e.systemPrompt:o;if(await this.vault.modify(n,H(r,c)),e.permissionRules!==void 0){let l=Ri(s.filePath),h=this.vault.getAbstractFileByPath(l),d=e.permissionRules;if(d.allow.length>0||d.deny.length>0){let u=JSON.stringify(d,null,2)+` +`;h instanceof x.TFile?await this.vault.modify(h,u):await this.vault.create(l,u)}else h instanceof x.TFile&&await this.app.fileManager.trashFile(h)}}}async updateTask(t,e){let s=this.getTaskById(t);if(!s)return;let n=this.vault.getAbstractFileByPath(s.filePath);if(!(n instanceof x.TFile))return;let a=await this.vault.cachedRead(n),{frontmatter:r,body:o}=J(a);e.agent!==void 0&&(r.agent=e.agent),e.type!==void 0&&(r.type=e.type),e.schedule!==void 0&&(r.schedule=e.schedule||void 0),e.runAt!==void 0&&(r.run_at=e.runAt||void 0),e.enabled!==void 0&&(r.enabled=e.enabled),e.priority!==void 0&&(r.priority=e.priority),e.catch_up!==void 0&&(r.catch_up=e.catch_up),e.effort!==void 0&&(r.effort=e.effort||void 0),e.model!==void 0&&(r.model=e.model||void 0),e.channel!==void 0&&(r.channel=e.channel||void 0),e.channelTarget!==void 0&&(r.channel_target=e.channelTarget||void 0),e.tags!==void 0&&(r.tags=e.tags);let c=e.body!==void 0?e.body:o;await this.vault.modify(n,H(r,c))}async updateSkill(t,e){let s=this.getSkillByName(t);if(s)if(s.isFolder){let n=(0,x.normalizePath)(s.filePath.replace(/\/skill\.md$/,"")),a=this.vault.getAbstractFileByPath(s.filePath);if(a instanceof x.TFile){let r=await this.vault.cachedRead(a),{frontmatter:o,body:c}=J(r);e.description!==void 0&&(o.description=e.description||void 0),e.tags!==void 0&&(o.tags=e.tags.length>0?e.tags:void 0);let l=e.body!==void 0?e.body:c;await this.vault.modify(a,H(o,l))}if(e.toolsBody!==void 0){let r=(0,x.normalizePath)(`${n}/tools.md`),o=this.vault.getAbstractFileByPath(r);e.toolsBody&&(o instanceof x.TFile?await this.vault.modify(o,`# Tools ${e.toolsBody}`):await this.vault.create(r,`# Tools @@ -11767,9 +11797,9 @@ ${e.referencesBody}`))}if(e.examplesBody!==void 0){let r=(0,x.normalizePath)(`${ ${e.examplesBody}`):await this.vault.create(r,`# Examples -${e.examplesBody}`))}}else{let n=this.vault.getAbstractFileByPath(s.filePath);if(!(n instanceof x.TFile))return;let a=await this.vault.cachedRead(n),{frontmatter:r,body:o}=J(a);e.description!==void 0&&(r.description=e.description||void 0),e.tags!==void 0&&(r.tags=e.tags.length>0?e.tags:void 0);let c=e.body!==void 0?e.body:o;await this.vault.modify(n,W(r,c))}}async deleteSkill(t){let e=this.getSkillByName(t);if(e)if(e.isFolder){let s=(0,x.normalizePath)(e.filePath.replace(/\/skill\.md$/,"")),n=this.vault.getAbstractFileByPath(s);n instanceof x.TFolder&&await this.app.fileManager.trashFile(n)}else await this.trashFile(e.filePath)}async deleteTask(t){let e=this.getTaskById(t);e&&await this.trashFile(e.filePath)}async updateChannel(t,e){let s=this.getChannelByName(t);if(!s)return;let n=this.vault.getAbstractFileByPath(s.filePath);if(!(n instanceof x.TFile))return;let a=await this.vault.cachedRead(n),{frontmatter:r,body:o}=J(a);e.default_agent!==void 0&&(r.default_agent=e.default_agent,delete r.agent),e.allowed_agents!==void 0&&(r.allowed_agents=e.allowed_agents),e.enabled!==void 0&&(r.enabled=e.enabled),e.credential_ref!==void 0&&(r.credential_ref=e.credential_ref),e.allowed_users!==void 0&&(r.allowed_users=e.allowed_users),e.per_user_sessions!==void 0&&(r.per_user_sessions=e.per_user_sessions),e.channel_context!==void 0&&(r.channel_context=e.channel_context||void 0),e.tags!==void 0&&(r.tags=e.tags),e.type!==void 0&&(r.type=e.type),e.transport!==void 0&&(r.transport=e.transport);let c=e.body!==void 0?e.body:o;await this.vault.modify(n,W(r,c))}async deleteChannel(t){let e=this.getChannelByName(t);if(!e)return;await this.trashFile(e.filePath);let s=(0,x.normalizePath)(`${this.getSubfolder("channels")}/${oe(t)}/sessions`),n=this.vault.getAbstractFileByPath(s);n instanceof x.TFolder&&await this.app.fileManager.trashFile(n)}mcpServerFrontmatter(t){let e={name:t.name,transport:t.type,enabled:t.enabled};return t.source&&(e.source=t.source),t.type==="stdio"?(t.command&&(e.command=t.command),t.args&&t.args.length>0&&(e.args=t.args),t.env&&Object.keys(t.env).length>0&&(e.env=t.env),t.envSecretKeys&&t.envSecretKeys.length>0&&(e.env_secret_keys=t.envSecretKeys)):(t.url&&(e.url=t.url),t.headers&&Object.keys(t.headers).length>0&&(e.headers=t.headers),t.auth&&(e.auth=t.auth),t.oauth&&(t.oauth.clientId||t.oauth.resource||(t.oauth.scopes?.length??0)>0)&&(e.oauth={client_id:t.oauth.clientId||void 0,resource:t.oauth.resource||void 0,scopes:t.oauth.scopes&&t.oauth.scopes.length>0?t.oauth.scopes:void 0})),e}async saveMcpServer(t,e=""){let s=this.mcpServerFrontmatter(t),n=W(s,e.trim()),a=t.filePath?this.vault.getAbstractFileByPath(t.filePath):null;if(a instanceof x.TFile)return await this.vault.modify(a,n),a.path;let r=await this.getAvailablePath(this.getSubfolder("mcp"),oe(t.name));return await this.ensureFolder(this.getSubfolder("mcp")),await this.vault.create(r,n),r}async setMcpServerEnabled(t,e){let s=this.getMcpServerByName(t);if(!s?.filePath)return;let n=this.vault.getAbstractFileByPath(s.filePath);if(!(n instanceof x.TFile))return;let a=await this.vault.cachedRead(n),{frontmatter:r,body:o}=J(a);r.enabled=e,await this.vault.modify(n,W(r,o))}async deleteMcpServer(t){let e=this.getMcpServerByName(t);e?.filePath&&await this.trashFile(e.filePath)}async updateHeartbeat(t,e){let s=this.getAgentByName(t);if(!s||!s.isFolder)return;let n=(0,x.normalizePath)(s.filePath.replace(/\/agent\.md$/,"")),a=(0,x.normalizePath)(`${n}/HEARTBEAT.md`),r=this.vault.getAbstractFileByPath(a);if(r instanceof x.TFile){let o=await this.vault.cachedRead(r),{frontmatter:c,body:l}=J(o);e.enabled!==void 0&&(c.enabled=e.enabled),e.schedule!==void 0&&(c.schedule=e.schedule||void 0),e.notify!==void 0&&(c.notify=e.notify),e.channel!==void 0&&(c.channel=e.channel||void 0);let h=e.body!==void 0?e.body:l;await this.vault.modify(r,W(c,h))}else{let o={enabled:e.enabled??!1};e.schedule&&(o.schedule=e.schedule),e.notify!==void 0&&(o.notify=e.notify),e.channel&&(o.channel=e.channel);let c=e.body??"";await this.vault.create(a,W(o,c))}}async deleteAgent(t,e){let s=[],n=this.getAgentByName(t);if(!n)return{trashedFiles:s};if(n.isFolder){let c=(0,x.normalizePath)(n.filePath.replace(/\/agent\.md$/,"")),l=this.vault.getAbstractFileByPath(c);if(l instanceof x.TFolder){let h=[];this.collectMarkdownChildren(l,h);for(let d of h)s.push(d.path);await this.app.fileManager.trashFile(l)}}else await this.trashFile(n.filePath),s.push(n.filePath);let a=this.getMemoryPath(t);this.vault.getAbstractFileByPath(a)&&(await this.trashFile(a),s.push(a));let r=this.getMemoryDir(t),o=this.vault.getAbstractFileByPath(r);if(o instanceof x.TFolder&&(await this.app.fileManager.trashFile(o),s.push(r)),e){let c=this.getTasksForAgent(t);for(let l of c)await this.trashFile(l.filePath),s.push(l.filePath)}return{trashedFiles:s}}async trashFile(t){let e=this.vault.getAbstractFileByPath(t);e&&await this.app.fileManager.trashFile(e)}async ensureFolder(t){if(!this.vault.getAbstractFileByPath(t))try{await this.vault.createFolder(t)}catch(e){if(!(e instanceof Error?e.message:String(e)).includes("Folder already exists"))throw e}}async getAvailablePath(t,e){let s=0;for(;;){let n=s===0?"":`-${s+1}`,a=(0,x.normalizePath)(`${t}/${e}${n}.md`);if(!this.vault.getAbstractFileByPath(a))return a;s+=1}}async createFileIfMissing(t,e){if(!this.vault.getAbstractFileByPath(t))try{await this.vault.create(t,e)}catch(s){if(!(s instanceof Error?s.message:String(s)).includes("File already exists"))throw s}}collectMarkdownChildren(t,e){for(let s of t.children)s instanceof x.TFile&&s.extension==="md"&&e.push(s),s instanceof x.TFolder&&this.collectMarkdownChildren(s,e)}clearStoredFile(t){this.agents.delete(t),this.skills.delete(t),this.tasks.delete(t),this.channels.delete(t),this.mcpServers.delete(t),this.validationIssues.delete(t)}setIssue(t,e){let s=this.validationIssues.get(t)??[];s.push({path:t,message:e}),this.validationIssues.set(t,s)}parseFile(t,e){return t.startsWith(`${this.getSubfolder("agents")}/`)?this.parseAgent(t,e):t.startsWith(`${this.getSubfolder("skills")}/`)?this.parseSkill(t,e):t.startsWith(`${this.getSubfolder("tasks")}/`)?this.parseTask(t,e):null}parseAgent(t,e){let{frontmatter:s,body:n}=J(e);if(!jt(s))return this.setIssue(t,"Invalid frontmatter."),null;let a=A(s.name),r=A(s.model)??this.settings.defaultModel;if(!a||!r)return this.setIssue(t,"Agent requires string field `name` and a valid model or default model setting."),null;let o=ue(s.allowed_tools),c=ue(s.blocked_tools);return(o.length>0||c.length>0)&&!this.warnedLegacyPerms.has(a)&&(this.warnedLegacyPerms.add(a),console.warn(`Agent Fleet: "${a}" still uses legacy allowed_tools/blocked_tools in its frontmatter. Permission rules now live in .permissions.json beside the .md. Open Edit and Save to migrate.`)),{filePath:t,name:a,description:A(s.description),model:r,adapter:A(s.adapter)??"claude-code",permissionMode:A(s.permission_mode)??"bypassPermissions",effort:A(s.effort),maxRetries:De(s.max_retries,1),skills:ue(s.skills),mcpServers:ue(s.mcp_servers),cwd:A(s.cwd),enabled:Be(s.enabled,!0),timeout:De(s.timeout,300),approvalRequired:ue(s.approval_required),memory:Be(s.memory,!1),memoryMaxEntries:De(s.memory_max_entries,100),memoryTokenBudget:wi(s),reflection:bi(s),autoCompactThreshold:De(s.auto_compact_threshold,85),tags:ue(s.tags),avatar:A(s.avatar)??"",body:n,contextBody:"",skillsBody:"",env:this.parseEnvMap(s.env),permissionRules:{allow:o,deny:c},isFolder:!1,heartbeatEnabled:!1,heartbeatSchedule:"",heartbeatBody:"",heartbeatNotify:!0,heartbeatChannel:""}}parseSkill(t,e){let{frontmatter:s,body:n}=J(e),a=A(s.name);return a?{filePath:t,name:a,description:A(s.description),tags:ue(s.tags),body:n,toolsBody:"",referencesBody:"",examplesBody:"",isFolder:!1}:(this.setIssue(t,"Skill requires string field `name`."),null)}parseTask(t,e){let{frontmatter:s,body:n}=J(e),a=A(s.task_id),r=A(s.agent),o=A(s.type);if(!a||!r||!o)return this.setIssue(t,"Task requires `task_id`, `agent`, and `type`."),null;if(o==="recurring"&&!A(s.schedule))return this.setIssue(t,"Recurring task requires `schedule`."),null;if(o==="once"&&!A(s.run_at))return this.setIssue(t,"One-time task requires `run_at`."),null;let c=A(s.priority),h=c&&["low","medium","high","critical"].includes(c)?c:"medium";return{filePath:t,taskId:a,agent:r,schedule:A(s.schedule),runAt:A(s.run_at),type:o,priority:h,enabled:Be(s.enabled,!0),created:A(s.created)??new Date().toISOString(),lastRun:A(s.last_run),nextRun:A(s.next_run),runCount:De(s.run_count,0),catchUp:Be(s.catch_up,this.settings.catchUpMissedTasks),effort:A(s.effort),model:A(s.model),tags:ue(s.tags),body:n}}parseChannelFile(t,e){let{frontmatter:s,body:n}=J(e),a=A(s.name);if(!a)return this.setIssue(t,"Channel requires string field `name`."),null;let r=A(s.type),o=["slack","telegram"];if(!r||!o.includes(r))return this.setIssue(t,`Channel \`${a}\` requires \`type\` to be one of: ${o.join(", ")}.`),null;let c=r,l=A(s.default_agent)??A(s.agent);if(!l)return this.setIssue(t,`Channel \`${a}\` requires \`default_agent\` (or \`agent\`).`),null;let h=ue(s.allowed_agents),d=A(s.credential_ref);if(!d)return this.setIssue(t,`Channel \`${a}\` requires \`credential_ref\` pointing at a configured credential.`),null;let u=jt(s.transport)?s.transport:{};return{filePath:t,name:a,type:c,defaultAgent:l,allowedAgents:h,enabled:Be(s.enabled,!0),credentialRef:d,allowedUsers:ue(s.allowed_users),perUserSessions:Be(s.per_user_sessions,!0),channelContext:A(s.channel_context)??"",transport:u,tags:ue(s.tags),body:n}}parseMcpServerFile(t,e){let{frontmatter:s,body:n}=J(e),a=A(s.name);if(!a)return this.setIssue(t,"MCP server requires string field `name`."),null;let r=(A(s.transport)??A(s.type)??"").toLowerCase(),o=["stdio","http","sse"];if(!o.includes(r))return this.setIssue(t,`MCP server \`${a}\` requires \`transport\` to be one of: ${o.join(", ")}.`),null;let c=r;if(c==="stdio"){if(!A(s.command))return this.setIssue(t,`MCP server \`${a}\` (stdio) requires a \`command\`.`),null}else if(!A(s.url))return this.setIssue(t,`MCP server \`${a}\` (${c}) requires a \`url\`.`),null;let l=(A(s.auth)??"").toLowerCase(),h=l==="bearer"||l==="oauth"||l==="none"?l:void 0,d;if(jt(s.oauth)){let u=s.oauth;d={clientId:A(u.client_id)??A(u.clientId),resource:A(u.resource),scopes:ue(u.scopes)}}return{name:a,filePath:t,type:c,enabled:Be(s.enabled,!0),description:A(s.description)??(n.trim()||void 0),source:A(s.source)==="imported"?"imported":"manual",command:A(s.command),args:ue(s.args),env:yi(s.env),envSecretKeys:ue(s.env_secret_keys),url:A(s.url),headers:yi(s.headers),auth:h,oauth:d,status:"disconnected",scope:"user",tools:[],toolDetails:[]}}validateReferences(){let t=new Set;for(let o of this.skills.values())t.has(o.name)&&this.setIssue(o.filePath,`Duplicate skill name \`${o.name}\`.`),t.add(o.name);let e=new Set;for(let o of this.agents.values()){e.has(o.name)&&this.setIssue(o.filePath,`Duplicate agent name \`${o.name}\`.`),e.add(o.name);for(let c of o.skills)t.has(c)||this.setIssue(o.filePath,`Agent references missing skill \`${c}\`.`)}for(let o of this.tasks.values())e.has(o.agent)||this.setIssue(o.filePath,`Task references missing agent \`${o.agent}\`.`);let s=new Set,n=new Map;for(let o of this.agents.values())n.set(o.name,o);let a=this.channelCredentialGetter?.()??this.settings.channelCredentials??{};for(let o of this.channels.values()){s.has(o.name)&&this.setIssue(o.filePath,`Duplicate channel name \`${o.name}\`.`),s.add(o.name);let c=n.get(o.defaultAgent);c?c.approvalRequired.length>0&&this.setIssue(o.filePath,`Channel \`${o.name}\` cannot bind to agent \`${c.name}\` because the agent has \`approval_required\` set (${c.approvalRequired.join(", ")}). Clear the approval list on the agent or pick a different agent.`):this.setIssue(o.filePath,`Channel \`${o.name}\` references missing agent \`${o.defaultAgent}\`.`);let l=a[o.credentialRef];l?l.type!==o.type&&this.setIssue(o.filePath,`Channel \`${o.name}\` is type \`${o.type}\` but credential \`${o.credentialRef}\` is type \`${l.type}\`.`):this.setIssue(o.filePath,`Channel \`${o.name}\` references missing credential \`${o.credentialRef}\`. Add it under Settings \u2192 Channel Credentials.`)}let r=new Set;for(let o of this.mcpServers.values())r.has(o.name)&&this.setIssue(o.filePath??o.name,`Duplicate MCP server name \`${o.name}\`.`),r.add(o.name)}parseEnvMap(t){if(!jt(t))return{};let e={};for(let[s,n]of Object.entries(t))typeof n=="string"?e[s]=n:(typeof n=="number"||typeof n=="boolean")&&(e[s]=String(n));return e}parseApprovals(t){if(Array.isArray(t))return t.flatMap(e=>{if(!jt(e)||!A(e.tool))return[];let s=A(e.tool);return s?[{tool:s,command:A(e.command),reason:A(e.reason),status:A(e.status)??"pending",resolvedAt:A(e.resolvedAt),note:A(e.note)}]:[]})}};var _t=require("obsidian"),Vs=class extends _t.Modal{constructor(e,s,n){super(e);this.info=s;this.onConfirm=n}deleteTasks=!0;onOpen(){let{contentEl:e}=this;e.empty(),e.addClass("af-confirm-delete-modal");let s=e.createDiv({cls:"af-delete-header"}),n=s.createSpan({cls:"af-delete-header-icon"});(0,_t.setIcon)(n,"alert-triangle"),s.createEl("h3",{text:`Delete agent "${this.info.agentName}"?`});let a=e.createDiv({cls:"af-delete-summary"});a.createDiv({text:"This action will:"});let r=a.createEl("ul",{cls:"af-delete-impact-list"});r.createEl("li",{text:"Move the agent definition to trash"}),this.info.hasMemory&&r.createEl("li",{text:"Move the agent's memory file to trash"}),this.info.taskCount>0&&r.createEl("li").setText(`${this.info.taskCount} task${this.info.taskCount!==1?"s":""} reference this agent`),this.info.runCount>0&&r.createEl("li",{cls:"af-delete-preserved"}).setText(`${this.info.runCount} run log${this.info.runCount!==1?"s":""} will be preserved`),e.createDiv({cls:"af-delete-note",text:"Files are moved to your system trash and can be recovered."}),this.info.taskCount>0&&new _t.Setting(e).setName("Also delete associated tasks").setDesc(`Delete ${this.info.taskCount} task${this.info.taskCount!==1?"s":""} that reference this agent`).addToggle(d=>{d.setValue(this.deleteTasks),d.onChange(u=>{this.deleteTasks=u})});let o=e.createDiv({cls:"af-delete-actions"}),c=o.createEl("button",{text:"Cancel"});c.onclick=()=>this.close();let l=o.createEl("button",{cls:"af-delete-confirm-btn",text:"Delete Agent"}),h=l.createSpan({cls:"af-delete-btn-icon"});(0,_t.setIcon)(h,"trash-2"),l.onclick=()=>{this.onConfirm(this.deleteTasks),this.close()}}};var N=require("obsidian");var Ys=require("obsidian"),Wt=class extends Ys.Modal{constructor(e,s){super(e);this.opts=s}onOpen(){let{contentEl:e}=this;e.empty(),e.createEl("h3",{text:this.opts.title});for(let s of this.opts.body.split(` +${e.examplesBody}`))}}else{let n=this.vault.getAbstractFileByPath(s.filePath);if(!(n instanceof x.TFile))return;let a=await this.vault.cachedRead(n),{frontmatter:r,body:o}=J(a);e.description!==void 0&&(r.description=e.description||void 0),e.tags!==void 0&&(r.tags=e.tags.length>0?e.tags:void 0);let c=e.body!==void 0?e.body:o;await this.vault.modify(n,H(r,c))}}async deleteSkill(t){let e=this.getSkillByName(t);if(e)if(e.isFolder){let s=(0,x.normalizePath)(e.filePath.replace(/\/skill\.md$/,"")),n=this.vault.getAbstractFileByPath(s);n instanceof x.TFolder&&await this.app.fileManager.trashFile(n)}else await this.trashFile(e.filePath)}async deleteTask(t){let e=this.getTaskById(t);e&&await this.trashFile(e.filePath)}async updateChannel(t,e){let s=this.getChannelByName(t);if(!s)return;let n=this.vault.getAbstractFileByPath(s.filePath);if(!(n instanceof x.TFile))return;let a=await this.vault.cachedRead(n),{frontmatter:r,body:o}=J(a);e.default_agent!==void 0&&(r.default_agent=e.default_agent,delete r.agent),e.allowed_agents!==void 0&&(r.allowed_agents=e.allowed_agents),e.enabled!==void 0&&(r.enabled=e.enabled),e.credential_ref!==void 0&&(r.credential_ref=e.credential_ref),e.allowed_users!==void 0&&(r.allowed_users=e.allowed_users),e.per_user_sessions!==void 0&&(r.per_user_sessions=e.per_user_sessions),e.channel_context!==void 0&&(r.channel_context=e.channel_context||void 0),e.tags!==void 0&&(r.tags=e.tags),e.type!==void 0&&(r.type=e.type),e.transport!==void 0&&(r.transport=e.transport);let c=e.body!==void 0?e.body:o;await this.vault.modify(n,H(r,c))}async deleteChannel(t){let e=this.getChannelByName(t);if(!e)return;await this.trashFile(e.filePath);let s=(0,x.normalizePath)(`${this.getSubfolder("channels")}/${oe(t)}/sessions`),n=this.vault.getAbstractFileByPath(s);n instanceof x.TFolder&&await this.app.fileManager.trashFile(n)}mcpServerFrontmatter(t){let e={name:t.name,transport:t.type,enabled:t.enabled};return t.source&&(e.source=t.source),t.type==="stdio"?(t.command&&(e.command=t.command),t.args&&t.args.length>0&&(e.args=t.args),t.env&&Object.keys(t.env).length>0&&(e.env=t.env),t.envSecretKeys&&t.envSecretKeys.length>0&&(e.env_secret_keys=t.envSecretKeys)):(t.url&&(e.url=t.url),t.headers&&Object.keys(t.headers).length>0&&(e.headers=t.headers),t.auth&&(e.auth=t.auth),t.oauth&&(t.oauth.clientId||t.oauth.resource||(t.oauth.scopes?.length??0)>0)&&(e.oauth={client_id:t.oauth.clientId||void 0,resource:t.oauth.resource||void 0,scopes:t.oauth.scopes&&t.oauth.scopes.length>0?t.oauth.scopes:void 0})),e}async saveMcpServer(t,e=""){let s=this.mcpServerFrontmatter(t),n=H(s,e.trim()),a=t.filePath?this.vault.getAbstractFileByPath(t.filePath):null;if(a instanceof x.TFile)return await this.vault.modify(a,n),a.path;let r=await this.getAvailablePath(this.getSubfolder("mcp"),oe(t.name));return await this.ensureFolder(this.getSubfolder("mcp")),await this.vault.create(r,n),r}async setMcpServerEnabled(t,e){let s=this.getMcpServerByName(t);if(!s?.filePath)return;let n=this.vault.getAbstractFileByPath(s.filePath);if(!(n instanceof x.TFile))return;let a=await this.vault.cachedRead(n),{frontmatter:r,body:o}=J(a);r.enabled=e,await this.vault.modify(n,H(r,o))}async deleteMcpServer(t){let e=this.getMcpServerByName(t);e?.filePath&&await this.trashFile(e.filePath)}async updateHeartbeat(t,e){let s=this.getAgentByName(t);if(!s||!s.isFolder)return;let n=(0,x.normalizePath)(s.filePath.replace(/\/agent\.md$/,"")),a=(0,x.normalizePath)(`${n}/HEARTBEAT.md`),r=this.vault.getAbstractFileByPath(a);if(r instanceof x.TFile){let o=await this.vault.cachedRead(r),{frontmatter:c,body:l}=J(o);e.enabled!==void 0&&(c.enabled=e.enabled),e.schedule!==void 0&&(c.schedule=e.schedule||void 0),e.notify!==void 0&&(c.notify=e.notify),e.channel!==void 0&&(c.channel=e.channel||void 0),e.channelTarget!==void 0&&(c.channel_target=e.channelTarget||void 0);let h=e.body!==void 0?e.body:l;await this.vault.modify(r,H(c,h))}else{let o={enabled:e.enabled??!1};e.schedule&&(o.schedule=e.schedule),e.notify!==void 0&&(o.notify=e.notify),e.channel&&(o.channel=e.channel),e.channelTarget&&(o.channel_target=e.channelTarget);let c=e.body??"";await this.vault.create(a,H(o,c))}}async deleteAgent(t,e){let s=[],n=this.getAgentByName(t);if(!n)return{trashedFiles:s};if(n.isFolder){let c=(0,x.normalizePath)(n.filePath.replace(/\/agent\.md$/,"")),l=this.vault.getAbstractFileByPath(c);if(l instanceof x.TFolder){let h=[];this.collectMarkdownChildren(l,h);for(let d of h)s.push(d.path);await this.app.fileManager.trashFile(l)}}else await this.trashFile(n.filePath),s.push(n.filePath);let a=this.getMemoryPath(t);this.vault.getAbstractFileByPath(a)&&(await this.trashFile(a),s.push(a));let r=this.getMemoryDir(t),o=this.vault.getAbstractFileByPath(r);if(o instanceof x.TFolder&&(await this.app.fileManager.trashFile(o),s.push(r)),e){let c=this.getTasksForAgent(t);for(let l of c)await this.trashFile(l.filePath),s.push(l.filePath)}return{trashedFiles:s}}async trashFile(t){let e=this.vault.getAbstractFileByPath(t);e&&await this.app.fileManager.trashFile(e)}async ensureFolder(t){if(!this.vault.getAbstractFileByPath(t))try{await this.vault.createFolder(t)}catch(e){if(!(e instanceof Error?e.message:String(e)).includes("Folder already exists"))throw e}}async getAvailablePath(t,e){let s=0;for(;;){let n=s===0?"":`-${s+1}`,a=(0,x.normalizePath)(`${t}/${e}${n}.md`);if(!this.vault.getAbstractFileByPath(a))return a;s+=1}}async createFileIfMissing(t,e){if(!this.vault.getAbstractFileByPath(t))try{await this.vault.create(t,e)}catch(s){if(!(s instanceof Error?s.message:String(s)).includes("File already exists"))throw s}}collectMarkdownChildren(t,e){for(let s of t.children)s instanceof x.TFile&&s.extension==="md"&&e.push(s),s instanceof x.TFolder&&this.collectMarkdownChildren(s,e)}clearStoredFile(t){this.agents.delete(t),this.skills.delete(t),this.tasks.delete(t),this.channels.delete(t),this.mcpServers.delete(t),this.validationIssues.delete(t)}setIssue(t,e){let s=this.validationIssues.get(t)??[];s.push({path:t,message:e}),this.validationIssues.set(t,s)}parseFile(t,e){return t.startsWith(`${this.getSubfolder("agents")}/`)?this.parseAgent(t,e):t.startsWith(`${this.getSubfolder("skills")}/`)?this.parseSkill(t,e):t.startsWith(`${this.getSubfolder("tasks")}/`)?this.parseTask(t,e):null}parseAgent(t,e){let{frontmatter:s,body:n}=J(e);if(!Wt(s))return this.setIssue(t,"Invalid frontmatter."),null;let a=A(s.name),r=A(s.model)??this.settings.defaultModel;if(!a||!r)return this.setIssue(t,"Agent requires string field `name` and a valid model or default model setting."),null;let o=ue(s.allowed_tools),c=ue(s.blocked_tools);return(o.length>0||c.length>0)&&!this.warnedLegacyPerms.has(a)&&(this.warnedLegacyPerms.add(a),console.warn(`Agent Fleet: "${a}" still uses legacy allowed_tools/blocked_tools in its frontmatter. Permission rules now live in .permissions.json beside the .md. Open Edit and Save to migrate.`)),{filePath:t,name:a,description:A(s.description),model:r,adapter:A(s.adapter)??"claude-code",permissionMode:A(s.permission_mode)??"bypassPermissions",effort:A(s.effort),maxRetries:Re(s.max_retries,1),skills:ue(s.skills),mcpServers:ue(s.mcp_servers),cwd:A(s.cwd),enabled:Be(s.enabled,!0),timeout:Re(s.timeout,300),approvalRequired:ue(s.approval_required),memory:Be(s.memory,!1),memoryMaxEntries:Re(s.memory_max_entries,100),memoryTokenBudget:Ei(s),reflection:Pi(s),autoCompactThreshold:Re(s.auto_compact_threshold,85),tags:ue(s.tags),avatar:A(s.avatar)??"",body:n,contextBody:"",skillsBody:"",env:this.parseEnvMap(s.env),permissionRules:{allow:o,deny:c},isFolder:!1,heartbeatEnabled:!1,heartbeatSchedule:"",heartbeatBody:"",heartbeatNotify:!0,heartbeatChannel:"",heartbeatChannelTarget:""}}parseSkill(t,e){let{frontmatter:s,body:n}=J(e),a=A(s.name);return a?{filePath:t,name:a,description:A(s.description),tags:ue(s.tags),body:n,toolsBody:"",referencesBody:"",examplesBody:"",isFolder:!1}:(this.setIssue(t,"Skill requires string field `name`."),null)}parseTask(t,e){let{frontmatter:s,body:n}=J(e),a=A(s.task_id),r=A(s.agent),o=A(s.type);if(!a||!r||!o)return this.setIssue(t,"Task requires `task_id`, `agent`, and `type`."),null;if(o==="recurring"&&!A(s.schedule))return this.setIssue(t,"Recurring task requires `schedule`."),null;if(o==="once"&&!A(s.run_at))return this.setIssue(t,"One-time task requires `run_at`."),null;let c=A(s.priority),h=c&&["low","medium","high","critical"].includes(c)?c:"medium";return{filePath:t,taskId:a,agent:r,schedule:A(s.schedule),runAt:A(s.run_at),type:o,priority:h,enabled:Be(s.enabled,!0),created:A(s.created)??new Date().toISOString(),lastRun:A(s.last_run),nextRun:A(s.next_run),runCount:Re(s.run_count,0),catchUp:Be(s.catch_up,this.settings.catchUpMissedTasks),effort:A(s.effort),model:A(s.model),channel:A(s.channel),channelTarget:A(s.channel_target),tags:ue(s.tags),body:n}}parseChannelFile(t,e){let{frontmatter:s,body:n}=J(e),a=A(s.name);if(!a)return this.setIssue(t,"Channel requires string field `name`."),null;let r=A(s.type),o=["slack","telegram","discord"];if(!r||!o.includes(r))return this.setIssue(t,`Channel \`${a}\` requires \`type\` to be one of: ${o.join(", ")}.`),null;let c=r,l=A(s.default_agent)??A(s.agent);if(!l)return this.setIssue(t,`Channel \`${a}\` requires \`default_agent\` (or \`agent\`).`),null;let h=ue(s.allowed_agents),d=A(s.credential_ref);if(!d)return this.setIssue(t,`Channel \`${a}\` requires \`credential_ref\` pointing at a configured credential.`),null;let u=Wt(s.transport)?s.transport:{};return{filePath:t,name:a,type:c,defaultAgent:l,allowedAgents:h,enabled:Be(s.enabled,!0),credentialRef:d,allowedUsers:ue(s.allowed_users),perUserSessions:Be(s.per_user_sessions,!0),channelContext:A(s.channel_context)??"",transport:u,tags:ue(s.tags),body:n}}parseMcpServerFile(t,e){let{frontmatter:s,body:n}=J(e),a=A(s.name);if(!a)return this.setIssue(t,"MCP server requires string field `name`."),null;let r=(A(s.transport)??A(s.type)??"").toLowerCase(),o=["stdio","http","sse"];if(!o.includes(r))return this.setIssue(t,`MCP server \`${a}\` requires \`transport\` to be one of: ${o.join(", ")}.`),null;let c=r;if(c==="stdio"){if(!A(s.command))return this.setIssue(t,`MCP server \`${a}\` (stdio) requires a \`command\`.`),null}else if(!A(s.url))return this.setIssue(t,`MCP server \`${a}\` (${c}) requires a \`url\`.`),null;let l=(A(s.auth)??"").toLowerCase(),h=l==="bearer"||l==="oauth"||l==="none"?l:void 0,d;if(Wt(s.oauth)){let u=s.oauth;d={clientId:A(u.client_id)??A(u.clientId),resource:A(u.resource),scopes:ue(u.scopes)}}return{name:a,filePath:t,type:c,enabled:Be(s.enabled,!0),description:A(s.description)??(n.trim()||void 0),source:A(s.source)==="imported"?"imported":"manual",command:A(s.command),args:ue(s.args),env:_i(s.env),envSecretKeys:ue(s.env_secret_keys),url:A(s.url),headers:_i(s.headers),auth:h,oauth:d,status:"disconnected",scope:"user",tools:[],toolDetails:[]}}validateReferences(){let t=new Set;for(let o of this.skills.values())t.has(o.name)&&this.setIssue(o.filePath,`Duplicate skill name \`${o.name}\`.`),t.add(o.name);let e=new Set;for(let o of this.agents.values()){e.has(o.name)&&this.setIssue(o.filePath,`Duplicate agent name \`${o.name}\`.`),e.add(o.name);for(let c of o.skills)t.has(c)||this.setIssue(o.filePath,`Agent references missing skill \`${c}\`.`)}for(let o of this.tasks.values())e.has(o.agent)||this.setIssue(o.filePath,`Task references missing agent \`${o.agent}\`.`);let s=new Set,n=new Map;for(let o of this.agents.values())n.set(o.name,o);let a=this.channelCredentialGetter?.()??this.settings.channelCredentials??{};for(let o of this.channels.values()){s.has(o.name)&&this.setIssue(o.filePath,`Duplicate channel name \`${o.name}\`.`),s.add(o.name);let c=n.get(o.defaultAgent);c?c.approvalRequired.length>0&&this.setIssue(o.filePath,`Channel \`${o.name}\` cannot bind to agent \`${c.name}\` because the agent has \`approval_required\` set (${c.approvalRequired.join(", ")}). Clear the approval list on the agent or pick a different agent.`):this.setIssue(o.filePath,`Channel \`${o.name}\` references missing agent \`${o.defaultAgent}\`.`);let l=a[o.credentialRef];l?l.type!==o.type&&this.setIssue(o.filePath,`Channel \`${o.name}\` is type \`${o.type}\` but credential \`${o.credentialRef}\` is type \`${l.type}\`.`):this.setIssue(o.filePath,`Channel \`${o.name}\` references missing credential \`${o.credentialRef}\`. Add it under Settings \u2192 Channel Credentials.`)}let r=new Set;for(let o of this.mcpServers.values())r.has(o.name)&&this.setIssue(o.filePath??o.name,`Duplicate MCP server name \`${o.name}\`.`),r.add(o.name)}parseEnvMap(t){if(!Wt(t))return{};let e={};for(let[s,n]of Object.entries(t))typeof n=="string"?e[s]=n:(typeof n=="number"||typeof n=="boolean")&&(e[s]=String(n));return e}parseApprovals(t){if(Array.isArray(t))return t.flatMap(e=>{if(!Wt(e)||!A(e.tool))return[];let s=A(e.tool);return s?[{tool:s,command:A(e.command),reason:A(e.reason),status:A(e.status)??"pending",resolvedAt:A(e.resolvedAt),note:A(e.note)}]:[]})}};var Et=require("obsidian"),Ks=class extends Et.Modal{constructor(e,s,n){super(e);this.info=s;this.onConfirm=n}deleteTasks=!0;onOpen(){let{contentEl:e}=this;e.empty(),e.addClass("af-confirm-delete-modal");let s=e.createDiv({cls:"af-delete-header"}),n=s.createSpan({cls:"af-delete-header-icon"});(0,Et.setIcon)(n,"alert-triangle"),s.createEl("h3",{text:`Delete agent "${this.info.agentName}"?`});let a=e.createDiv({cls:"af-delete-summary"});a.createDiv({text:"This action will:"});let r=a.createEl("ul",{cls:"af-delete-impact-list"});r.createEl("li",{text:"Move the agent definition to trash"}),this.info.hasMemory&&r.createEl("li",{text:"Move the agent's memory file to trash"}),this.info.taskCount>0&&r.createEl("li").setText(`${this.info.taskCount} task${this.info.taskCount!==1?"s":""} reference this agent`),this.info.runCount>0&&r.createEl("li",{cls:"af-delete-preserved"}).setText(`${this.info.runCount} run log${this.info.runCount!==1?"s":""} will be preserved`),e.createDiv({cls:"af-delete-note",text:"Files are moved to your system trash and can be recovered."}),this.info.taskCount>0&&new Et.Setting(e).setName("Also delete associated tasks").setDesc(`Delete ${this.info.taskCount} task${this.info.taskCount!==1?"s":""} that reference this agent`).addToggle(d=>{d.setValue(this.deleteTasks),d.onChange(u=>{this.deleteTasks=u})});let o=e.createDiv({cls:"af-delete-actions"}),c=o.createEl("button",{text:"Cancel"});c.onclick=()=>this.close();let l=o.createEl("button",{cls:"af-delete-confirm-btn",text:"Delete Agent"}),h=l.createSpan({cls:"af-delete-btn-icon"});(0,Et.setIcon)(h,"trash-2"),l.onclick=()=>{this.onConfirm(this.deleteTasks),this.close()}}};var F=require("obsidian");var Js=require("obsidian"),Ht=class extends Js.Modal{constructor(e,s){super(e);this.opts=s}onOpen(){let{contentEl:e}=this;e.empty(),e.createEl("h3",{text:this.opts.title});for(let s of this.opts.body.split(` -`))s.trim()&&e.createEl("p",{text:s});new Ys.Setting(e).addButton(s=>s.setButtonText("Cancel").onClick(()=>this.close())).addButton(s=>{s.setButtonText(this.opts.confirmText??"Confirm").onClick(()=>{this.close(),this.opts.onConfirm()}),this.opts.danger&&s.setWarning()})}onClose(){this.contentEl.empty()}};var Js=[{value:"opus",label:"opus \u2014 latest Opus"},{value:"sonnet",label:"sonnet \u2014 latest Sonnet"},{value:"haiku",label:"haiku \u2014 latest Haiku"},{value:"opusplan",label:"opusplan \u2014 plan-mode Opus"}],Xs=[{value:"gpt-5.5",label:"gpt-5.5 \u2014 frontier"},{value:"gpt-5.4",label:"gpt-5.4"},{value:"gpt-5.4-mini",label:"gpt-5.4-mini \u2014 fast"},{value:"gpt-5.3-codex",label:"gpt-5.3-codex"}],Ks="__custom__";function Ci(i){let t=(i??"").trim().toLowerCase();return t==="codex"||t==="openai-codex"}function Ti(i){return Ci(i)?Xs:Js}function rl(i,t){let e=i.trim();return!e||e==="default"||e==="subscription"?"inherit":Ti(t).some(s=>s.value===e)?"alias":"custom"}function At(i,t){i.empty(),i.addClass("af-model-picker");let e=Ci(t.adapter),s=Ti(t.adapter),n=rl(t.value,t.adapter),a=i.createEl("select",{cls:"af-form-select af-mp-select"}),r=t.allowInherit?t.inheritPlaceholder??"Inherit from agent":e?"Default (let Codex pick)":"Default (let Claude Code pick)";a.createEl("option",{text:r,attr:{value:""}});let o=a.createEl("optgroup",{attr:{label:e?"Codex models":"Aliases (any backend)"}});for(let l of s)o.createEl("option",{text:l.label,attr:{value:l.value}});a.createEl("option",{text:"Custom\u2026",attr:{value:Ks}});let c=i.createEl("input",{cls:"af-form-input af-mp-custom-input",attr:{type:"text",placeholder:e?"e.g. gpt-5.5 \xB7 gpt-5.4-mini":"e.g. claude-opus-4-7 \xB7 us.anthropic.claude-opus-4-7 \xB7 claude-opus-4-7@20251101",spellcheck:"false"}});n==="inherit"?(a.value="",c.value="",c.setCssStyles({display:"none"})):n==="alias"?(a.value=t.value.trim(),c.value="",c.setCssStyles({display:"none"})):(a.value=Ks,c.value=t.value.trim(),c.setCssStyles({display:""})),a.addEventListener("change",()=>{a.value===Ks?(c.setCssStyles({display:""}),c.focus(),t.onChange(c.value.trim())):(c.setCssStyles({display:"none"}),t.onChange(a.value))}),c.addEventListener("input",()=>{a.value===Ks&&t.onChange(c.value.trim())})}var fe=require("obsidian");function ol(){let i=new Date,t=i.getFullYear(),e=String(i.getMonth()+1).padStart(2,"0"),s=String(i.getDate()).padStart(2,"0");return`${t}-${e}-${s}`}var ll="0 3 * * *",cl="0 9 * * 0";function Zn(){return{scopeSlug:"",scopeRoot:"",inboxPath:"_sources/inbox",archivePath:"_sources/archive",topicsRoot:"_topics",indexPath:"index.md",logPath:"log.md",watchedFolders:[],excludePatterns:[],watchedSince:ol(),heartbeatChannel:"",fileSubstantiveAnswers:!0,obsidianUrlScheme:!0,maxTokensPerIngest:6e4,maxTokensPerRefresh:3e4,indexSplitThreshold:100,dedupSimilarityThreshold:.82,summaryStaleDays:30,failedPath:"_sources/failed",stateFile:".wiki-keeper-state.json"}}var Ei=Zn();function ea(i){let t=oe(i).trim();return t?`wiki-keeper-${t}`:"wiki-keeper"}function dl(i,t){let e=t||"the whole vault",s={name:i,description:`Cultivates ${e} as a self-maintaining wiki. Ingests new sources, extracts durable claims from watched folders, answers questions with citations, and periodically lints.`,avatar:"library",enabled:!0,skills:["wiki-ingest","wiki-query","wiki-lint","wiki-refresh"],tags:t?["wiki-keeper",`scope:${oe(t)}`]:["wiki-keeper"]},n=`You are the Wiki Keeper for the \`${e}\` scope. Maintain it as a persistent, interlinked knowledge base (Karpathy's "LLM wiki" pattern). +`))s.trim()&&e.createEl("p",{text:s});new Js.Setting(e).addButton(s=>s.setButtonText("Cancel").onClick(()=>this.close())).addButton(s=>{s.setButtonText(this.opts.confirmText??"Confirm").onClick(()=>{this.close(),this.opts.onConfirm()}),this.opts.danger&&s.setWarning()})}onClose(){this.contentEl.empty()}};var Qs=[{value:"opus",label:"opus \u2014 latest Opus"},{value:"sonnet",label:"sonnet \u2014 latest Sonnet"},{value:"haiku",label:"haiku \u2014 latest Haiku"},{value:"opusplan",label:"opusplan \u2014 plan-mode Opus"}],Zs=[{value:"gpt-5.5",label:"gpt-5.5 \u2014 frontier"},{value:"gpt-5.4",label:"gpt-5.4"},{value:"gpt-5.4-mini",label:"gpt-5.4-mini \u2014 fast"},{value:"gpt-5.3-codex",label:"gpt-5.3-codex"}],Xs="__custom__";function Mi(i){let t=(i??"").trim().toLowerCase();return t==="codex"||t==="openai-codex"}function Li(i){return Mi(i)?Zs:Qs}function kl(i,t){let e=i.trim();return!e||e==="default"||e==="subscription"?"inherit":Li(t).some(s=>s.value===e)?"alias":"custom"}function Pt(i,t){i.empty(),i.addClass("af-model-picker");let e=Mi(t.adapter),s=Li(t.adapter),n=kl(t.value,t.adapter),a=i.createEl("select",{cls:"af-form-select af-mp-select"}),r=t.allowInherit?t.inheritPlaceholder??"Inherit from agent":e?"Default (let Codex pick)":"Default (let Claude Code pick)";a.createEl("option",{text:r,attr:{value:""}});let o=a.createEl("optgroup",{attr:{label:e?"Codex models":"Aliases (any backend)"}});for(let l of s)o.createEl("option",{text:l.label,attr:{value:l.value}});a.createEl("option",{text:"Custom\u2026",attr:{value:Xs}});let c=i.createEl("input",{cls:"af-form-input af-mp-custom-input",attr:{type:"text",placeholder:e?"e.g. gpt-5.5 \xB7 gpt-5.4-mini":"e.g. claude-opus-4-7 \xB7 us.anthropic.claude-opus-4-7 \xB7 claude-opus-4-7@20251101",spellcheck:"false"}});n==="inherit"?(a.value="",c.value="",c.setCssStyles({display:"none"})):n==="alias"?(a.value=t.value.trim(),c.value="",c.setCssStyles({display:"none"})):(a.value=Xs,c.value=t.value.trim(),c.setCssStyles({display:""})),a.addEventListener("change",()=>{a.value===Xs?(c.setCssStyles({display:""}),c.focus(),t.onChange(c.value.trim())):(c.setCssStyles({display:"none"}),t.onChange(a.value))}),c.addEventListener("input",()=>{a.value===Xs&&t.onChange(c.value.trim())})}var fe=require("obsidian");function xl(){let i=new Date,t=i.getFullYear(),e=String(i.getMonth()+1).padStart(2,"0"),s=String(i.getDate()).padStart(2,"0");return`${t}-${e}-${s}`}var Sl="0 3 * * *",Cl="0 9 * * 0";function na(){return{scopeSlug:"",scopeRoot:"",inboxPath:"_sources/inbox",archivePath:"_sources/archive",topicsRoot:"_topics",indexPath:"index.md",logPath:"log.md",watchedFolders:[],excludePatterns:[],watchedSince:xl(),heartbeatChannel:"",fileSubstantiveAnswers:!0,obsidianUrlScheme:!0,maxTokensPerIngest:6e4,maxTokensPerRefresh:3e4,indexSplitThreshold:100,dedupSimilarityThreshold:.82,summaryStaleDays:30,failedPath:"_sources/failed",stateFile:".wiki-keeper-state.json"}}var Ni=na();function aa(i){let t=oe(i).trim();return t?`wiki-keeper-${t}`:"wiki-keeper"}function Tl(i,t){let e=t||"the whole vault",s={name:i,description:`Cultivates ${e} as a self-maintaining wiki. Ingests new sources, extracts durable claims from watched folders, answers questions with citations, and periodically lints.`,avatar:"library",enabled:!0,skills:["wiki-ingest","wiki-query","wiki-lint","wiki-refresh"],tags:t?["wiki-keeper",`scope:${oe(t)}`]:["wiki-keeper"]},n=`You are the Wiki Keeper for the \`${e}\` scope. Maintain it as a persistent, interlinked knowledge base (Karpathy's "LLM wiki" pattern). ## Scope isolation @@ -11811,7 +11841,7 @@ Use \`memory\` for procedural learning ("user prefers concept pages under sub-fo - Never write outside the scope root. - Never edit \`## Claims\` history (append only). - Never edit \`## Summary\` blocks by hand \u2014 \`wiki-refresh\` owns them. -`;return W(s,n)}function hl(i){let t={model:"opus",adapter:"claude-code",timeout:900,max_retries:1,cwd:"",permission_mode:"acceptEdits",approval_required:["Write"],memory:!0,memory_max_entries:200,wiki_keeper:{scope_root:i.scopeRoot,inbox_path:i.inboxPath,archive_path:i.archivePath,failed_path:i.failedPath,topics_root:i.topicsRoot,index_path:i.indexPath,log_path:i.logPath,watched_folders:i.watchedFolders,exclude_patterns:i.excludePatterns,watched_since:i.watchedSince,file_substantive_answers:i.fileSubstantiveAnswers,obsidian_url_scheme:i.obsidianUrlScheme,max_tokens_per_ingest:i.maxTokensPerIngest,max_tokens_per_refresh:i.maxTokensPerRefresh,index_split_threshold:i.indexSplitThreshold,dedup_similarity_threshold:i.dedupSimilarityThreshold,summary_stale_days:i.summaryStaleDays,state_file:i.stateFile}};return W(t,"")}function ul(i){let t={enabled:!0,schedule:ll,notify:!0};return i.heartbeatChannel&&(t.channel=i.heartbeatChannel),W(t,`Run wiki-ingest in both modes: +`;return H(s,n)}function _l(i){let t={model:"opus",adapter:"claude-code",timeout:900,max_retries:1,cwd:"",permission_mode:"acceptEdits",approval_required:["Write"],memory:!0,memory_max_entries:200,wiki_keeper:{scope_root:i.scopeRoot,inbox_path:i.inboxPath,archive_path:i.archivePath,failed_path:i.failedPath,topics_root:i.topicsRoot,index_path:i.indexPath,log_path:i.logPath,watched_folders:i.watchedFolders,exclude_patterns:i.excludePatterns,watched_since:i.watchedSince,file_substantive_answers:i.fileSubstantiveAnswers,obsidian_url_scheme:i.obsidianUrlScheme,max_tokens_per_ingest:i.maxTokensPerIngest,max_tokens_per_refresh:i.maxTokensPerRefresh,index_split_threshold:i.indexSplitThreshold,dedup_similarity_threshold:i.dedupSimilarityThreshold,summary_stale_days:i.summaryStaleDays,state_file:i.stateFile}};return H(t,"")}function Al(i){let t={enabled:!0,schedule:Sl,notify:!0};return i.heartbeatChannel&&(t.channel=i.heartbeatChannel),H(t,`Run wiki-ingest in both modes: 1. Drain every unprocessed file in the configured inbox (inbox mode). 2. Diff watched folders against the state file; process changed or new files (watched mode). @@ -11819,37 +11849,37 @@ Use \`memory\` for procedural learning ("user prefers concept pages under sub-fo Lint runs on its own schedule via the sibling \`*-lint\` task. Change the schedule by editing this file's \`schedule:\` frontmatter directly, or via the agent editor in the dashboard. -`)}function Pi(i){let e={task_id:`${i}-lint`,agent:i,type:"recurring",schedule:cl,priority:"low",enabled:!0,created:new Date().toISOString(),run_count:0,catch_up:!1,tags:["wiki-keeper","lint"]};return W(e,"Run the `wiki-lint` skill on the current scope.\n\nWrite the report as a new `## Lint YYYY-MM-DD` section inside the fenced block in log.md.\n")}function ta(i,t){return(0,fe.normalizePath)(`${i}/tasks/${t}-lint.md`)}var pl="# Wiki Schema\n\n## Page types\n\n- **Entity pages** \u2014 people, orgs, products. Frontmatter: `type: entity`.\n- **Concept pages** \u2014 ideas, techniques, patterns. `type: concept`.\n- **Event pages** \u2014 meetings, releases, decisions. `type: event, date: YYYY-MM-DD`.\n- **Summary pages** (inbox-mode only) \u2014 one per ingested inbox source. `type: summary, source: `.\n- **Synthesis pages** \u2014 filed answers from `wiki-query` substantive responses. `type: synthesis, question: , refreshed: `. Live under `_topics/syntheses/`.\n\nTopic pages of type entity/concept/event SHOULD also carry:\n- `summary_refreshed: ` \u2014 last time `wiki-refresh` regenerated this page's summary block. Empty string on creation.\n- `claims_at_refresh: ` \u2014 how many claims existed at last refresh. Drives the staleness threshold.\n\n## Page anatomy\n\nEvery entity/concept/event page has this structure:\n\n```markdown\n---\ntype: entity\nname: Vendor X\nsummary_refreshed: 2026-04-26\nclaims_at_refresh: 14\n---\n\n\n## Summary\n\n- 3-7 bullet synthesis maintained by wiki-refresh.\n- Forward-links to other topics with [[wikilinks]].\n\n\n## Claims\n\n- 2026-04-18 [meeting]: from [[meetings/2026-04-18-vendor-sync|vendor-sync]]: Vendor X raised prices 15%\n- 2026-04-12 [doc]: from [[summaries/2026-04-12-renewal-brief|renewal-brief]]: contract auto-renews 2026-12-01\n\n## Contradictions\n\n(only present when contradictions exist)\n```\n\nThe fenced summary block is auto-managed by `wiki-refresh`. The `## Claims` section is append-only history written by `wiki-ingest` and `wiki-query` compounding. `## Contradictions` is created on demand. **Anything outside these structures is user-authored** and must be preserved.\n\n## Source-type tags\n\nEvery dated bullet in `## Claims` carries a tag indicating the source type:\n\n- `[doc]` \u2014 PDF, DOCX, XLSX, or other document\n- `[meeting]` \u2014 meeting note or transcript\n- `[email]` \u2014 email forward\n- `[note]` \u2014 markdown daily note or in-vault note\n- `[web]` \u2014 web clipping or URL drop\n- `[synthesis]` \u2014 bullet emitted by `wiki-query` compounding (links to a synthesis page)\n- `[other]` \u2014 fallback\n\nTags help `wiki-query` weight evidence quality and help `wiki-lint` identify drift.\n\n## Naming\n\n- Slug-case filenames: `vendor-x.md`, not `Vendor X.md`.\n- Group by type under `_topics//` when there are >5 pages of a type.\n\n## Links\n\n- Every entity/concept page MUST have \u22651 inbound link from `index.md` (or a sub-MOC) or a sibling.\n- Summary pages MUST forward-link to every entity/concept they mention.\n- Watched-mode extractions append dated entries to topic pages; forward-link to the source file path so readers can find the raw note.\n- Synthesis pages forward-link to every cited topic and earn their place that way (lint exempts them from orphan flagging if they have any outbound links to topics).\n\n## Index split\n\nWhen the topic count exceeds the `index_split_threshold` config value (default 100) or any single type exceeds 30 pages, `index.md` becomes a hub of hubs and per-type sub-MOCs live at `/index/.md`. After split, new topic-page entries go into the matching sub-MOC, not the root index.\n\n## Conflict resolution\n\n- New claim contradicts existing? Add a `## Contradictions` section with a dated entry. Do NOT overwrite.\n- Flag in `log.md` for user review.\n- `wiki-refresh` reflects unresolved contradictions in the summary so query-time readers see them.\n\n## Failed sources\n\nFiles that fail ingest 3 times in a row are quarantined to `/` (default `_sources/failed/`) with a sidecar `.error.md`. Lint surfaces the quarantine count weekly.\n",ml=["Read","Write","Edit","Glob","Grep","Bash(mv *)","Bash(mkdir *)"],fl=["Bash(rm -rf *)","Bash(git push *)","Bash(rm -rf /*)","Bash(mv * /*)","Bash(cp -r * /*)"];async function Ri(i,t){let e=(0,fe.normalizePath)(`${t}/permissions.json`),s=i.getAbstractFileByPath(e),n={allow:[],deny:[]};if(s instanceof fe.TFile)try{let o=await i.cachedRead(s),c=JSON.parse(o),l=Array.isArray(c.allow)?c.allow.filter(d=>typeof d=="string"):[],h=Array.isArray(c.deny)?c.deny.filter(d=>typeof d=="string"):[];n={allow:l,deny:h}}catch{}let a={allow:_i(n.allow,ml),deny:_i(n.deny,fl)},r=JSON.stringify(a,null,2)+` -`;return s instanceof fe.TFile?await i.modify(s,r):await i.create(e,r),a}function _i(i,t){let e=new Set,s=[];for(let n of i)e.has(n)||(e.add(n),s.push(n));for(let n of t)e.has(n)||(e.add(n),s.push(n));return s}async function Di(i,t,e){let s=ea(e.scopeSlug||e.scopeRoot),n=(0,fe.normalizePath)(`${t}/agents/${s}`);if(await Ht(i,n),i.getAbstractFileByPath((0,fe.normalizePath)(`${n}/agent.md`)))throw new Error(`Wiki Keeper agent already exists at ${n}. Delete it first or choose a different scope slug.`);await i.create((0,fe.normalizePath)(`${n}/agent.md`),dl(s,e.scopeRoot)),await i.create((0,fe.normalizePath)(`${n}/config.md`),hl(e)),await i.create((0,fe.normalizePath)(`${n}/HEARTBEAT.md`),ul(e)),await i.create((0,fe.normalizePath)(`${n}/CONTEXT.md`),pl),await Ri(i,n);let r=ta(t,s);i.getAbstractFileByPath(r)||(await Ht(i,(0,fe.normalizePath)(`${t}/tasks`)),await i.create(r,Pi(s)));let o=e.scopeRoot.trim(),c=o?`${o}/`:"";return await Ht(i,(0,fe.normalizePath)(`${c}${e.inboxPath}`)),await Ht(i,(0,fe.normalizePath)(`${c}${e.topicsRoot}`)),await Ai(i,(0,fe.normalizePath)(`${c}${e.indexPath}`),`# Index +`)}function Bi(i){let e={task_id:`${i}-lint`,agent:i,type:"recurring",schedule:Cl,priority:"low",enabled:!0,created:new Date().toISOString(),run_count:0,catch_up:!1,tags:["wiki-keeper","lint"]};return H(e,"Run the `wiki-lint` skill on the current scope.\n\nWrite the report as a new `## Lint YYYY-MM-DD` section inside the fenced block in log.md.\n")}function ia(i,t){return(0,fe.normalizePath)(`${i}/tasks/${t}-lint.md`)}var El="# Wiki Schema\n\n## Page types\n\n- **Entity pages** \u2014 people, orgs, products. Frontmatter: `type: entity`.\n- **Concept pages** \u2014 ideas, techniques, patterns. `type: concept`.\n- **Event pages** \u2014 meetings, releases, decisions. `type: event, date: YYYY-MM-DD`.\n- **Summary pages** (inbox-mode only) \u2014 one per ingested inbox source. `type: summary, source: `.\n- **Synthesis pages** \u2014 filed answers from `wiki-query` substantive responses. `type: synthesis, question: , refreshed: `. Live under `_topics/syntheses/`.\n\nTopic pages of type entity/concept/event SHOULD also carry:\n- `summary_refreshed: ` \u2014 last time `wiki-refresh` regenerated this page's summary block. Empty string on creation.\n- `claims_at_refresh: ` \u2014 how many claims existed at last refresh. Drives the staleness threshold.\n\n## Page anatomy\n\nEvery entity/concept/event page has this structure:\n\n```markdown\n---\ntype: entity\nname: Vendor X\nsummary_refreshed: 2026-04-26\nclaims_at_refresh: 14\n---\n\n\n## Summary\n\n- 3-7 bullet synthesis maintained by wiki-refresh.\n- Forward-links to other topics with [[wikilinks]].\n\n\n## Claims\n\n- 2026-04-18 [meeting]: from [[meetings/2026-04-18-vendor-sync|vendor-sync]]: Vendor X raised prices 15%\n- 2026-04-12 [doc]: from [[summaries/2026-04-12-renewal-brief|renewal-brief]]: contract auto-renews 2026-12-01\n\n## Contradictions\n\n(only present when contradictions exist)\n```\n\nThe fenced summary block is auto-managed by `wiki-refresh`. The `## Claims` section is append-only history written by `wiki-ingest` and `wiki-query` compounding. `## Contradictions` is created on demand. **Anything outside these structures is user-authored** and must be preserved.\n\n## Source-type tags\n\nEvery dated bullet in `## Claims` carries a tag indicating the source type:\n\n- `[doc]` \u2014 PDF, DOCX, XLSX, or other document\n- `[meeting]` \u2014 meeting note or transcript\n- `[email]` \u2014 email forward\n- `[note]` \u2014 markdown daily note or in-vault note\n- `[web]` \u2014 web clipping or URL drop\n- `[synthesis]` \u2014 bullet emitted by `wiki-query` compounding (links to a synthesis page)\n- `[other]` \u2014 fallback\n\nTags help `wiki-query` weight evidence quality and help `wiki-lint` identify drift.\n\n## Naming\n\n- Slug-case filenames: `vendor-x.md`, not `Vendor X.md`.\n- Group by type under `_topics//` when there are >5 pages of a type.\n\n## Links\n\n- Every entity/concept page MUST have \u22651 inbound link from `index.md` (or a sub-MOC) or a sibling.\n- Summary pages MUST forward-link to every entity/concept they mention.\n- Watched-mode extractions append dated entries to topic pages; forward-link to the source file path so readers can find the raw note.\n- Synthesis pages forward-link to every cited topic and earn their place that way (lint exempts them from orphan flagging if they have any outbound links to topics).\n\n## Index split\n\nWhen the topic count exceeds the `index_split_threshold` config value (default 100) or any single type exceeds 30 pages, `index.md` becomes a hub of hubs and per-type sub-MOCs live at `/index/.md`. After split, new topic-page entries go into the matching sub-MOC, not the root index.\n\n## Conflict resolution\n\n- New claim contradicts existing? Add a `## Contradictions` section with a dated entry. Do NOT overwrite.\n- Flag in `log.md` for user review.\n- `wiki-refresh` reflects unresolved contradictions in the summary so query-time readers see them.\n\n## Failed sources\n\nFiles that fail ingest 3 times in a row are quarantined to `/` (default `_sources/failed/`) with a sidecar `.error.md`. Lint surfaces the quarantine count weekly.\n",Pl=["Read","Write","Edit","Glob","Grep","Bash(mv *)","Bash(mkdir *)"],Rl=["Bash(rm -rf *)","Bash(git push *)","Bash(rm -rf /*)","Bash(mv * /*)","Bash(cp -r * /*)"];async function Ui(i,t){let e=(0,fe.normalizePath)(`${t}/permissions.json`),s=i.getAbstractFileByPath(e),n={allow:[],deny:[]};if(s instanceof fe.TFile)try{let o=await i.cachedRead(s),c=JSON.parse(o),l=Array.isArray(c.allow)?c.allow.filter(d=>typeof d=="string"):[],h=Array.isArray(c.deny)?c.deny.filter(d=>typeof d=="string"):[];n={allow:l,deny:h}}catch{}let a={allow:Fi(n.allow,Pl),deny:Fi(n.deny,Rl)},r=JSON.stringify(a,null,2)+` +`;return s instanceof fe.TFile?await i.modify(s,r):await i.create(e,r),a}function Fi(i,t){let e=new Set,s=[];for(let n of i)e.has(n)||(e.add(n),s.push(n));for(let n of t)e.has(n)||(e.add(n),s.push(n));return s}async function $i(i,t,e){let s=aa(e.scopeSlug||e.scopeRoot),n=(0,fe.normalizePath)(`${t}/agents/${s}`);if(await qt(i,n),i.getAbstractFileByPath((0,fe.normalizePath)(`${n}/agent.md`)))throw new Error(`Wiki Keeper agent already exists at ${n}. Delete it first or choose a different scope slug.`);await i.create((0,fe.normalizePath)(`${n}/agent.md`),Tl(s,e.scopeRoot)),await i.create((0,fe.normalizePath)(`${n}/config.md`),_l(e)),await i.create((0,fe.normalizePath)(`${n}/HEARTBEAT.md`),Al(e)),await i.create((0,fe.normalizePath)(`${n}/CONTEXT.md`),El),await Ui(i,n);let r=ia(t,s);i.getAbstractFileByPath(r)||(await qt(i,(0,fe.normalizePath)(`${t}/tasks`)),await i.create(r,Bi(s)));let o=e.scopeRoot.trim(),c=o?`${o}/`:"";return await qt(i,(0,fe.normalizePath)(`${c}${e.inboxPath}`)),await qt(i,(0,fe.normalizePath)(`${c}${e.topicsRoot}`)),await Oi(i,(0,fe.normalizePath)(`${c}${e.indexPath}`),`# Index -`),await Ai(i,(0,fe.normalizePath)(`${c}${e.logPath}`),`# Log +`),await Oi(i,(0,fe.normalizePath)(`${c}${e.logPath}`),`# Log -`),{path:n,name:s}}async function Ht(i,t){if(i.getAbstractFileByPath(t))return;let e=t.split("/"),s="";for(let n of e)if(s=s?`${s}/${n}`:n,!i.getAbstractFileByPath(s))try{await i.createFolder(s)}catch(a){if(!(a instanceof Error?a.message:String(a)).includes("already exists"))throw a}}async function Ai(i,t,e){if(i.getAbstractFileByPath(t))return;let s=t.replace(/\/[^/]+$/,"");s&&s!==t&&await Ht(i,s),await i.create(t,e)}async function Ii(i,t,e,s){let n=(0,fe.normalizePath)(`${t}/agents/${e}`),a=(0,fe.normalizePath)(`${n}/config.md`),r=i.getAbstractFileByPath(a);if(!(r instanceof fe.TFile))throw new Error(`Config file not found for ${e} at ${a}`);let o=await i.cachedRead(r),{frontmatter:c,body:l}=J(o),h=c.wiki_keeper??{};h.watched_folders=s.watchedFolders,h.exclude_patterns=s.excludePatterns,s.watchedSince?h.watched_since=s.watchedSince:delete h.watched_since,delete h.ingest_schedule,delete h.lint_schedule,delete h.lint_day,h.file_substantive_answers=s.fileSubstantiveAnswers,h.obsidian_url_scheme=s.obsidianUrlScheme,h.max_tokens_per_ingest=s.maxTokensPerIngest,h.max_tokens_per_refresh=s.maxTokensPerRefresh,h.index_split_threshold=s.indexSplitThreshold,h.dedup_similarity_threshold=s.dedupSimilarityThreshold,h.summary_stale_days=s.summaryStaleDays,(typeof h.failed_path!="string"||!h.failed_path)&&(h.failed_path="_sources/failed"),c.wiki_keeper=h,delete c.allowed_tools,delete c.blocked_tools,await i.modify(r,W(c,l)),await Ri(i,n);let d=(0,fe.normalizePath)(`${n}/HEARTBEAT.md`),u=i.getAbstractFileByPath(d);if(u instanceof fe.TFile){let f=await i.cachedRead(u),{frontmatter:g,body:v}=J(f);s.heartbeatChannel?g.channel=s.heartbeatChannel:delete g.channel,await i.modify(u,W(g,v))}let p=ta(t,e);i.getAbstractFileByPath(p)instanceof fe.TFile||(await Ht(i,(0,fe.normalizePath)(`${t}/tasks`)),await i.create(p,Pi(e)))}async function Mi(i,t,e){let s=i.vault,n=(0,fe.normalizePath)(`${t}/agents/${e}`),a=s.getAbstractFileByPath(n);if(!a)return;if(!(a instanceof fe.TFolder))throw new Error(`Expected folder at ${n}`);await i.fileManager.trashFile(a);let r=ta(t,e),o=s.getAbstractFileByPath(r);o instanceof fe.TFile&&await i.fileManager.trashFile(o)}var Qs=class extends N.PluginSettingTab{constructor(e){super(e.app,e);this.plugin=e}display(){let{containerEl:e}=this;e.empty(),new N.Setting(e).setName("Fleet folder").addText(a=>a.setValue(this.plugin.settings.fleetFolder).onChange(async r=>{this.plugin.settings.fleetFolder=r.trim()||at.fleetFolder,await this.plugin.saveSettings()})),new N.Setting(e).setName("Claude CLI path").addText(a=>a.setValue(this.plugin.settings.claudeCliPath).onChange(async r=>{this.plugin.settings.claudeCliPath=r.trim()||at.claudeCliPath,await this.plugin.saveSettings()})),new N.Setting(e).setName("Codex CLI path").setDesc("Used by agents with adapter \u201Ccodex\u201D. Install via `npm i -g @openai/codex` and authenticate with `codex login` in a terminal first.").addText(a=>a.setValue(this.plugin.settings.codexCliPath).onChange(async r=>{this.plugin.settings.codexCliPath=r.trim()||at.codexCliPath,await this.plugin.saveSettings()}));let n=new N.Setting(e).setName("Default model").setDesc("Fallback for agents that don\u2019t set their own. Aliases (opus/sonnet/haiku) work on any backend; use Custom for pinned IDs or Bedrock/Vertex/Foundry.").controlEl.createDiv();At(n,{value:this.plugin.settings.defaultModel,onChange:async a=>{this.plugin.settings.defaultModel=a||at.defaultModel,await this.plugin.saveSettings()}}),new N.Setting(e).setName("AWS region").addText(a=>a.setValue(this.plugin.settings.awsRegion).onChange(async r=>{this.plugin.settings.awsRegion=r.trim()||at.awsRegion,await this.plugin.saveSettings()})),new N.Setting(e).setName("Max concurrent runs").addSlider(a=>a.setLimits(1,10,1).setValue(this.plugin.settings.maxConcurrentRuns).setDynamicTooltip().onChange(async r=>{this.plugin.settings.maxConcurrentRuns=r,await this.plugin.saveSettings()})),new N.Setting(e).setName("Run log retention").setDesc("Days to keep run logs before auto-prune.").addSlider(a=>a.setLimits(1,365,1).setValue(this.plugin.settings.runLogRetentionDays).setDynamicTooltip().onChange(async r=>{this.plugin.settings.runLogRetentionDays=r,await this.plugin.saveSettings()})),new N.Setting(e).setName("Catch up missed tasks").addToggle(a=>a.setValue(this.plugin.settings.catchUpMissedTasks).onChange(async r=>{this.plugin.settings.catchUpMissedTasks=r,await this.plugin.saveSettings()})),new N.Setting(e).setName("Notification level").addDropdown(a=>a.addOption("all","All").addOption("failures-only","Failures only").addOption("none","None").setValue(this.plugin.settings.notificationLevel).onChange(async r=>{this.plugin.settings.notificationLevel=r,await this.plugin.saveSettings()})),new N.Setting(e).setName("Status bar").addToggle(a=>a.setValue(this.plugin.settings.showStatusBar).onChange(async r=>{this.plugin.settings.showStatusBar=r,await this.plugin.saveSettings(),this.plugin.refreshStatusBar()})),new N.Setting(e).setName("Chat watchdog timeout (minutes)").setDesc("How long a chat session waits for any output from the Claude CLI before killing the subprocess and surfacing a timeout error. Raise this if your agents run long tools (e.g. large web fetches, builds) and you're seeing spurious 'no response' errors.").addSlider(a=>a.setLimits(1,60,1).setValue(this.plugin.settings.chatWatchdogMinutes).setDynamicTooltip().onChange(async r=>{this.plugin.settings.chatWatchdogMinutes=r,await this.plugin.saveSettings()})),new N.Setting(e).setName("Verify Claude CLI").setDesc("Checks that the configured binary is reachable.").addButton(a=>a.setButtonText("Verify").onClick(async()=>{let r=await this.plugin.verifyClaudeCli();new N.Notice(r?"Claude CLI detected.":"Claude CLI check failed. See console for details.")})),new N.Setting(e).setName("Verify Codex CLI").setDesc("Checks that the configured Codex binary is reachable.").addButton(a=>a.setButtonText("Verify").onClick(async()=>{let r=await this.plugin.verifyCodexCli();new N.Notice(r?"Codex CLI detected.":"Codex CLI check failed. See console for details.")})),this.renderWikiKeepersSection(e),this.renderChannelsSection(e)}renderChannelsSection(e){new N.Setting(e).setName("Channels").setHeading();let s=e.createDiv({cls:"af-settings-warning"});s.setCssStyles({padding:"12px"}),s.setCssStyles({margin:"8px 0 16px 0"}),s.setCssStyles({border:"1px solid var(--background-modifier-border)"}),s.setCssStyles({borderRadius:"6px"}),s.setCssStyles({background:"var(--background-secondary)"}),s.createEl("strong",{text:"Credential storage: "});let n=this.plugin.app.vault.configDir;s.createSpan({text:`Channel credentials are stored in this plugin's data.json inside your vault's ${n} folder. If you sync your ${n} folder across devices, credentials will sync with it. Do not commit this file to a public git repository.`}),new N.Setting(e).setName("Max concurrent channel sessions").setDesc("Hard cap on live claude subprocesses across all channels. Oldest idle session is hibernated when exceeded.").addSlider(h=>h.setLimits(1,20,1).setValue(this.plugin.settings.maxConcurrentChannelSessions).setDynamicTooltip().onChange(async d=>{this.plugin.settings.maxConcurrentChannelSessions=d,await this.plugin.saveSettings()})),new N.Setting(e).setName("Idle timeout (minutes)").setDesc("Channel sessions with no activity for this long get their subprocess hibernated. State is preserved and the next message resumes transparently.").addSlider(h=>h.setLimits(1,120,1).setValue(this.plugin.settings.channelIdleTimeoutMinutes).setDynamicTooltip().onChange(async d=>{this.plugin.settings.channelIdleTimeoutMinutes=d,await this.plugin.saveSettings()})),new N.Setting(e).setName("Rate limit per conversation").setDesc("Maximum messages allowed per external conversation within the rolling window.").addSlider(h=>h.setLimits(1,100,1).setValue(this.plugin.settings.channelRateLimitPerConversation).setDynamicTooltip().onChange(async d=>{this.plugin.settings.channelRateLimitPerConversation=d,await this.plugin.saveSettings()})),new N.Setting(e).setName("Rate limit window (minutes)").addSlider(h=>h.setLimits(1,60,1).setValue(this.plugin.settings.channelRateLimitWindowMinutes).setDynamicTooltip().onChange(async d=>{this.plugin.settings.channelRateLimitWindowMinutes=d,await this.plugin.saveSettings()})),new N.Setting(e).setName("Channel credentials").setHeading();let a=e.createDiv({cls:"af-channel-credentials"});this.renderCredentialList(a);let r=e.createDiv({cls:"af-channel-credential-add"});r.setCssStyles({marginTop:"12px"}),r.setCssStyles({padding:"12px"}),r.setCssStyles({border:"1px dashed var(--background-modifier-border)"}),r.setCssStyles({borderRadius:"6px"}),r.createEl("strong",{text:"Add a channel credential"});let o={ref:"",type:"slack",botToken:"",appToken:""};new N.Setting(r).setName("Reference name").setDesc("Used by `credential_ref` in _fleet/channels/*.md files.").addText(h=>h.setPlaceholder("my-creds").onChange(d=>{o.ref=d.trim()})),new N.Setting(r).setName("Type").addDropdown(h=>h.addOption("slack","Slack").addOption("telegram","Telegram").setValue("slack").onChange(d=>{o.type=d,c.setCssStyles({display:d==="slack"?"":"none"}),l.setCssStyles({display:d==="telegram"?"":"none"})}));let c=r.createDiv();new N.Setting(c).setName("Bot token (xoxb-...)").addText(h=>{h.inputEl.type="password",h.setPlaceholder("xoxb-...").onChange(d=>{o.botToken=d.trim()})}),new N.Setting(c).setName("App-level token (xapp-...)").setDesc("Generated in your Slack app's Basic Information \u2192 App-Level Tokens.").addText(h=>{h.inputEl.type="password",h.setPlaceholder("xapp-...").onChange(d=>{o.appToken=d.trim()})});let l=r.createDiv();l.setCssStyles({display:"none"}),new N.Setting(l).setName("Bot token").setDesc("From @BotFather on Telegram.").addText(h=>{h.inputEl.type="password",h.setPlaceholder("123456:ABC-DEF1234...").onChange(d=>{o.botToken=d.trim()})}),new N.Setting(r).addButton(h=>h.setButtonText("Add credential").setCta().onClick(async()=>{if(!o.ref||!o.botToken){new N.Notice("Fill in the reference name and bot token.");return}let d;if(o.type==="telegram")d={type:"telegram",botToken:o.botToken};else{if(!o.appToken){new N.Notice("Slack requires both bot token and app-level token.");return}d={type:"slack",botToken:o.botToken,appToken:o.appToken}}this.plugin.channelCredentials.set(o.ref,d),new N.Notice(`Added credential \`${o.ref}\`.`),this.display()}))}renderCredentialList(e){let s=this.plugin.channelCredentials.list();if(s.length===0){e.createDiv({text:"No channel credentials configured yet.",cls:"af-muted"}).setCssStyles({color:"var(--text-muted)"});return}for(let{ref:n,entry:a}of s){let r=e.createDiv({cls:"af-channel-credential-row"});r.setCssStyles({display:"flex"}),r.setCssStyles({justifyContent:"space-between"}),r.setCssStyles({alignItems:"center"}),r.setCssStyles({padding:"8px 12px"}),r.setCssStyles({border:"1px solid var(--background-modifier-border)"}),r.setCssStyles({borderRadius:"6px"}),r.setCssStyles({marginBottom:"6px"});let o=r.createDiv();o.createEl("strong",{text:n}),o.createEl("span",{text:` \xB7 ${a.type} \xB7 ${yl(gl(a))}`,cls:"af-muted"}).setCssStyles({color:"var(--text-muted)"});let c=r.createEl("button",{text:"Remove"});c.onclick=()=>{this.plugin.channelCredentials.delete(n),new N.Notice(`Removed credential \`${n}\`.`),this.display()}}}renderWikiKeepersSection(e){new N.Setting(e).setName("Wiki Keepers").setHeading();let s=e.createEl("p",{cls:"af-settings-hint",text:"Per-scope wiki agents. Each runs its own inbox + watched folders + topics + heartbeat. All fields on the Add form are optional \u2014 click Create with everything blank to get a whole-vault keeper with defaults."});s.setCssStyles({color:"var(--af-text-secondary)"}),s.setCssStyles({fontSize:"12px"});let a=this.plugin.runtime.getSnapshot().agents.filter(o=>o.wikiKeeper!==void 0),r=e.createDiv({cls:"af-wk-list"});if(a.length===0)r.createDiv({cls:"af-wk-empty",text:"No Wiki Keepers yet. Click Add to create one."});else for(let o of a){let c=r.createDiv({cls:"af-wk-row"}),l=c.createDiv({cls:"af-wk-row-left"});l.createSpan({cls:"af-wk-name",text:o.name});let h=o.wikiKeeper.scopeRoot||"(whole vault)";l.createSpan({cls:"af-wk-scope",text:`scope: ${h}`});let d=c.createDiv({cls:"af-wk-row-actions"}),u=d.createEl("button",{text:"Edit",cls:"af-wk-row-btn"});u.onclick=()=>{new na(this.plugin,o,()=>{this.scheduleRerender()}).open()};let p=d.createEl("button",{text:"Delete",cls:"af-wk-row-btn af-wk-row-btn-danger"});p.onclick=()=>{new Wt(this.plugin.app,{title:`Delete Wiki Keeper "${o.name}"?`,body:`This removes the agent folder at _fleet/agents/${o.name}/. +`),{path:n,name:s}}async function qt(i,t){if(i.getAbstractFileByPath(t))return;let e=t.split("/"),s="";for(let n of e)if(s=s?`${s}/${n}`:n,!i.getAbstractFileByPath(s))try{await i.createFolder(s)}catch(a){if(!(a instanceof Error?a.message:String(a)).includes("already exists"))throw a}}async function Oi(i,t,e){if(i.getAbstractFileByPath(t))return;let s=t.replace(/\/[^/]+$/,"");s&&s!==t&&await qt(i,s),await i.create(t,e)}async function ji(i,t,e,s){let n=(0,fe.normalizePath)(`${t}/agents/${e}`),a=(0,fe.normalizePath)(`${n}/config.md`),r=i.getAbstractFileByPath(a);if(!(r instanceof fe.TFile))throw new Error(`Config file not found for ${e} at ${a}`);let o=await i.cachedRead(r),{frontmatter:c,body:l}=J(o),h=c.wiki_keeper??{};h.watched_folders=s.watchedFolders,h.exclude_patterns=s.excludePatterns,s.watchedSince?h.watched_since=s.watchedSince:delete h.watched_since,delete h.ingest_schedule,delete h.lint_schedule,delete h.lint_day,h.file_substantive_answers=s.fileSubstantiveAnswers,h.obsidian_url_scheme=s.obsidianUrlScheme,h.max_tokens_per_ingest=s.maxTokensPerIngest,h.max_tokens_per_refresh=s.maxTokensPerRefresh,h.index_split_threshold=s.indexSplitThreshold,h.dedup_similarity_threshold=s.dedupSimilarityThreshold,h.summary_stale_days=s.summaryStaleDays,(typeof h.failed_path!="string"||!h.failed_path)&&(h.failed_path="_sources/failed"),c.wiki_keeper=h,delete c.allowed_tools,delete c.blocked_tools,await i.modify(r,H(c,l)),await Ui(i,n);let d=(0,fe.normalizePath)(`${n}/HEARTBEAT.md`),u=i.getAbstractFileByPath(d);if(u instanceof fe.TFile){let f=await i.cachedRead(u),{frontmatter:g,body:y}=J(f);s.heartbeatChannel?g.channel=s.heartbeatChannel:delete g.channel,await i.modify(u,H(g,y))}let p=ia(t,e);i.getAbstractFileByPath(p)instanceof fe.TFile||(await qt(i,(0,fe.normalizePath)(`${t}/tasks`)),await i.create(p,Bi(e)))}async function Wi(i,t,e){let s=i.vault,n=(0,fe.normalizePath)(`${t}/agents/${e}`),a=s.getAbstractFileByPath(n);if(!a)return;if(!(a instanceof fe.TFolder))throw new Error(`Expected folder at ${n}`);await i.fileManager.trashFile(a);let r=ia(t,e),o=s.getAbstractFileByPath(r);o instanceof fe.TFile&&await i.fileManager.trashFile(o)}var en=class extends F.PluginSettingTab{constructor(e){super(e.app,e);this.plugin=e}display(){let{containerEl:e}=this;e.empty(),new F.Setting(e).setName("Fleet folder").addText(a=>a.setValue(this.plugin.settings.fleetFolder).onChange(async r=>{this.plugin.settings.fleetFolder=r.trim()||rt.fleetFolder,await this.plugin.saveSettings()})),new F.Setting(e).setName("Claude CLI path").addText(a=>a.setValue(this.plugin.settings.claudeCliPath).onChange(async r=>{this.plugin.settings.claudeCliPath=r.trim()||rt.claudeCliPath,await this.plugin.saveSettings()})),new F.Setting(e).setName("Codex CLI path").setDesc("Used by agents with adapter \u201Ccodex\u201D. Install via `npm i -g @openai/codex` and authenticate with `codex login` in a terminal first.").addText(a=>a.setValue(this.plugin.settings.codexCliPath).onChange(async r=>{this.plugin.settings.codexCliPath=r.trim()||rt.codexCliPath,await this.plugin.saveSettings()}));let n=new F.Setting(e).setName("Default model").setDesc("Fallback for agents that don\u2019t set their own. Aliases (opus/sonnet/haiku) work on any backend; use Custom for pinned IDs or Bedrock/Vertex/Foundry.").controlEl.createDiv();Pt(n,{value:this.plugin.settings.defaultModel,onChange:async a=>{this.plugin.settings.defaultModel=a||rt.defaultModel,await this.plugin.saveSettings()}}),new F.Setting(e).setName("AWS region").addText(a=>a.setValue(this.plugin.settings.awsRegion).onChange(async r=>{this.plugin.settings.awsRegion=r.trim()||rt.awsRegion,await this.plugin.saveSettings()})),new F.Setting(e).setName("Max concurrent runs").addSlider(a=>a.setLimits(1,10,1).setValue(this.plugin.settings.maxConcurrentRuns).setDynamicTooltip().onChange(async r=>{this.plugin.settings.maxConcurrentRuns=r,await this.plugin.saveSettings()})),new F.Setting(e).setName("Run log retention").setDesc("Days to keep run logs before auto-prune.").addSlider(a=>a.setLimits(1,365,1).setValue(this.plugin.settings.runLogRetentionDays).setDynamicTooltip().onChange(async r=>{this.plugin.settings.runLogRetentionDays=r,await this.plugin.saveSettings()})),new F.Setting(e).setName("Catch up missed tasks").addToggle(a=>a.setValue(this.plugin.settings.catchUpMissedTasks).onChange(async r=>{this.plugin.settings.catchUpMissedTasks=r,await this.plugin.saveSettings()})),new F.Setting(e).setName("Notification level").addDropdown(a=>a.addOption("all","All").addOption("failures-only","Failures only").addOption("none","None").setValue(this.plugin.settings.notificationLevel).onChange(async r=>{this.plugin.settings.notificationLevel=r,await this.plugin.saveSettings()})),new F.Setting(e).setName("Status bar").addToggle(a=>a.setValue(this.plugin.settings.showStatusBar).onChange(async r=>{this.plugin.settings.showStatusBar=r,await this.plugin.saveSettings(),this.plugin.refreshStatusBar()})),new F.Setting(e).setName("Chat watchdog timeout (minutes)").setDesc("How long a chat session waits for any output from the Claude CLI before killing the subprocess and surfacing a timeout error. Raise this if your agents run long tools (e.g. large web fetches, builds) and you're seeing spurious 'no response' errors.").addSlider(a=>a.setLimits(1,60,1).setValue(this.plugin.settings.chatWatchdogMinutes).setDynamicTooltip().onChange(async r=>{this.plugin.settings.chatWatchdogMinutes=r,await this.plugin.saveSettings()})),new F.Setting(e).setName("Verify Claude CLI").setDesc("Checks that the configured binary is reachable.").addButton(a=>a.setButtonText("Verify").onClick(async()=>{let r=await this.plugin.verifyClaudeCli();new F.Notice(r?"Claude CLI detected.":"Claude CLI check failed. See console for details.")})),new F.Setting(e).setName("Verify Codex CLI").setDesc("Checks that the configured Codex binary is reachable.").addButton(a=>a.setButtonText("Verify").onClick(async()=>{let r=await this.plugin.verifyCodexCli();new F.Notice(r?"Codex CLI detected.":"Codex CLI check failed. See console for details.")})),this.renderWikiKeepersSection(e),this.renderChannelsSection(e)}renderChannelsSection(e){new F.Setting(e).setName("Channels").setHeading();let s=e.createDiv({cls:"af-settings-warning"});s.setCssStyles({padding:"12px"}),s.setCssStyles({margin:"8px 0 16px 0"}),s.setCssStyles({border:"1px solid var(--background-modifier-border)"}),s.setCssStyles({borderRadius:"6px"}),s.setCssStyles({background:"var(--background-secondary)"}),s.createEl("strong",{text:"Credential storage: "});let n=this.plugin.app.vault.configDir;s.createSpan({text:`Channel credentials are stored in this plugin's data.json inside your vault's ${n} folder. If you sync your ${n} folder across devices, credentials will sync with it. Do not commit this file to a public git repository.`}),new F.Setting(e).setName("Max concurrent channel sessions").setDesc("Hard cap on live claude subprocesses across all channels. Oldest idle session is hibernated when exceeded.").addSlider(d=>d.setLimits(1,20,1).setValue(this.plugin.settings.maxConcurrentChannelSessions).setDynamicTooltip().onChange(async u=>{this.plugin.settings.maxConcurrentChannelSessions=u,await this.plugin.saveSettings()})),new F.Setting(e).setName("Idle timeout (minutes)").setDesc("Channel sessions with no activity for this long get their subprocess hibernated. State is preserved and the next message resumes transparently.").addSlider(d=>d.setLimits(1,120,1).setValue(this.plugin.settings.channelIdleTimeoutMinutes).setDynamicTooltip().onChange(async u=>{this.plugin.settings.channelIdleTimeoutMinutes=u,await this.plugin.saveSettings()})),new F.Setting(e).setName("Rate limit per conversation").setDesc("Maximum messages allowed per external conversation within the rolling window.").addSlider(d=>d.setLimits(1,100,1).setValue(this.plugin.settings.channelRateLimitPerConversation).setDynamicTooltip().onChange(async u=>{this.plugin.settings.channelRateLimitPerConversation=u,await this.plugin.saveSettings()})),new F.Setting(e).setName("Rate limit window (minutes)").addSlider(d=>d.setLimits(1,60,1).setValue(this.plugin.settings.channelRateLimitWindowMinutes).setDynamicTooltip().onChange(async u=>{this.plugin.settings.channelRateLimitWindowMinutes=u,await this.plugin.saveSettings()})),new F.Setting(e).setName("Channel credentials").setHeading();let a=e.createDiv({cls:"af-channel-credentials"});this.renderCredentialList(a);let r=e.createDiv({cls:"af-channel-credential-add"});r.setCssStyles({marginTop:"12px"}),r.setCssStyles({padding:"12px"}),r.setCssStyles({border:"1px dashed var(--background-modifier-border)"}),r.setCssStyles({borderRadius:"6px"}),r.createEl("strong",{text:"Add a channel credential"});let o={ref:"",type:"slack",botToken:"",appToken:""};new F.Setting(r).setName("Reference name").setDesc("Used by `credential_ref` in _fleet/channels/*.md files.").addText(d=>d.setPlaceholder("my-creds").onChange(u=>{o.ref=u.trim()})),new F.Setting(r).setName("Type").addDropdown(d=>d.addOption("slack","Slack").addOption("telegram","Telegram").addOption("discord","Discord").setValue("slack").onChange(u=>{o.type=u,c.setCssStyles({display:u==="slack"?"":"none"}),l.setCssStyles({display:u==="telegram"?"":"none"}),h.setCssStyles({display:u==="discord"?"":"none"})}));let c=r.createDiv();new F.Setting(c).setName("Bot token (xoxb-...)").addText(d=>{d.inputEl.type="password",d.setPlaceholder("xoxb-...").onChange(u=>{o.botToken=u.trim()})}),new F.Setting(c).setName("App-level token (xapp-...)").setDesc("Generated in your Slack app's Basic Information \u2192 App-Level Tokens.").addText(d=>{d.inputEl.type="password",d.setPlaceholder("xapp-...").onChange(u=>{o.appToken=u.trim()})});let l=r.createDiv();l.setCssStyles({display:"none"}),new F.Setting(l).setName("Bot token").setDesc("From @BotFather on Telegram.").addText(d=>{d.inputEl.type="password",d.setPlaceholder("123456:ABC-DEF1234...").onChange(u=>{o.botToken=u.trim()})});let h=r.createDiv();h.setCssStyles({display:"none"}),new F.Setting(h).setName("Bot token").setDesc("From the Discord Developer Portal \u2192 your application \u2192 Bot \u2192 Reset Token. Enable the Message Content intent on the same page.").addText(d=>{d.inputEl.type="password",d.setPlaceholder("MTA...").onChange(u=>{o.botToken=u.trim()})}),new F.Setting(r).addButton(d=>d.setButtonText("Add credential").setCta().onClick(async()=>{if(!o.ref||!o.botToken){new F.Notice("Fill in the reference name and bot token.");return}let u;if(o.type==="telegram")u={type:"telegram",botToken:o.botToken};else if(o.type==="discord")u={type:"discord",botToken:o.botToken};else{if(!o.appToken){new F.Notice("Slack requires both bot token and app-level token.");return}u={type:"slack",botToken:o.botToken,appToken:o.appToken}}this.plugin.channelCredentials.set(o.ref,u),new F.Notice(`Added credential \`${o.ref}\`.`),this.display()}))}renderCredentialList(e){let s=this.plugin.channelCredentials.list();if(s.length===0){e.createDiv({text:"No channel credentials configured yet.",cls:"af-muted"}).setCssStyles({color:"var(--text-muted)"});return}for(let{ref:n,entry:a}of s){let r=e.createDiv({cls:"af-channel-credential-row"});r.setCssStyles({display:"flex"}),r.setCssStyles({justifyContent:"space-between"}),r.setCssStyles({alignItems:"center"}),r.setCssStyles({padding:"8px 12px"}),r.setCssStyles({border:"1px solid var(--background-modifier-border)"}),r.setCssStyles({borderRadius:"6px"}),r.setCssStyles({marginBottom:"6px"});let o=r.createDiv();o.createEl("strong",{text:n}),o.createEl("span",{text:` \xB7 ${a.type} \xB7 ${Il(Dl(a))}`,cls:"af-muted"}).setCssStyles({color:"var(--text-muted)"});let c=r.createEl("button",{text:"Remove"});c.onclick=()=>{this.plugin.channelCredentials.delete(n),new F.Notice(`Removed credential \`${n}\`.`),this.display()}}}renderWikiKeepersSection(e){new F.Setting(e).setName("Wiki Keepers").setHeading();let s=e.createEl("p",{cls:"af-settings-hint",text:"Per-scope wiki agents. Each runs its own inbox + watched folders + topics + heartbeat. All fields on the Add form are optional \u2014 click Create with everything blank to get a whole-vault keeper with defaults."});s.setCssStyles({color:"var(--af-text-secondary)"}),s.setCssStyles({fontSize:"12px"});let a=this.plugin.runtime.getSnapshot().agents.filter(o=>o.wikiKeeper!==void 0),r=e.createDiv({cls:"af-wk-list"});if(a.length===0)r.createDiv({cls:"af-wk-empty",text:"No Wiki Keepers yet. Click Add to create one."});else for(let o of a){let c=r.createDiv({cls:"af-wk-row"}),l=c.createDiv({cls:"af-wk-row-left"});l.createSpan({cls:"af-wk-name",text:o.name});let h=o.wikiKeeper.scopeRoot||"(whole vault)";l.createSpan({cls:"af-wk-scope",text:`scope: ${h}`});let d=c.createDiv({cls:"af-wk-row-actions"}),u=d.createEl("button",{text:"Edit",cls:"af-wk-row-btn"});u.onclick=()=>{new oa(this.plugin,o,()=>{this.scheduleRerender()}).open()};let p=d.createEl("button",{text:"Delete",cls:"af-wk-row-btn af-wk-row-btn-danger"});p.onclick=()=>{new Ht(this.plugin.app,{title:`Delete Wiki Keeper "${o.name}"?`,body:`This removes the agent folder at _fleet/agents/${o.name}/. -Your scope's inbox, topics, index, and log are NOT deleted.`,confirmText:"Delete",danger:!0,onConfirm:async()=>{try{await Mi(this.plugin.app,this.plugin.settings.fleetFolder,o.name),await this.plugin.refreshFromVault(),new N.Notice(`Wiki Keeper "${o.name}" deleted.`),this.scheduleRerender()}catch(m){let f=m instanceof Error?m.message:String(m);new N.Notice(`Failed to delete: ${f}`)}}}).open()}}new N.Setting(e).setName("Add Wiki Keeper").setDesc("Create a new scoped wiki agent. All fields optional.").addButton(o=>o.setButtonText("+ Add").onClick(()=>{new sa(this.plugin,()=>{this.scheduleRerender()}).open()}))}scheduleRerender(){window.setTimeout(()=>{(async()=>{try{await this.plugin.refreshFromVault()}catch{}this.display()})()},600)}},sa=class extends N.Modal{constructor(e,s){super(e.app);this.plugin=e;this.onCreated=s;this.input=Zn()}input;onOpen(){let{contentEl:e}=this;e.empty(),e.addClass("af-wk-modal"),e.createEl("h2",{text:"Add Wiki Keeper"});let s=e.createDiv({cls:"af-wk-banner"});s.createEl("strong",{text:"All fields are optional. "}),s.createSpan({text:"Click Create with everything blank and you'll get a whole-vault keeper with sensible defaults (inbox at _sources/inbox, topics at topics/, nightly ingest at 3am, Sunday lint)."}),e.createEl("h3",{text:"Scope",cls:"af-wk-section-h"}),new N.Setting(e).setName("Scope folder").setDesc("Optional. Vault-relative path. Empty = whole vault.").addText(h=>h.setPlaceholder("(empty = whole vault)").setValue(this.input.scopeRoot).onChange(d=>{this.input.scopeRoot=d.trim(),this.renderPreview()})),new N.Setting(e).setName("Slug").setDesc("Optional. Agent name suffix. Default: derived from scope folder name, or 'wiki-keeper' for whole-vault.").addText(h=>h.setPlaceholder("(auto)").setValue(this.input.scopeSlug).onChange(d=>{this.input.scopeSlug=d.trim(),this.renderPreview()})),this.renderPreview(),e.createEl("h3",{text:"Sources",cls:"af-wk-section-h"}),new N.Setting(e).setName("Watched folders").setDesc("Optional. Comma- or newline-separated vault-relative paths. Read-only \u2014 claims are extracted but files are never moved. Leave empty for inbox-only mode (you drop files into _sources/inbox/ and they get archived after processing).").addTextArea(h=>h.setPlaceholder("(empty = inbox-only)").setValue(this.input.watchedFolders.join(", ")).onChange(d=>{this.input.watchedFolders=d.split(/[,\n]/).map(u=>u.trim()).filter(Boolean)})),new N.Setting(e).setName("Exclude patterns").setDesc("Optional. Glob patterns to skip. Leave empty to process everything under watched + inbox.").addTextArea(h=>h.setPlaceholder("(empty = no excludes)").setValue(this.input.excludePatterns.join(", ")).onChange(d=>{this.input.excludePatterns=d.split(/[,\n]/).map(u=>u.trim()).filter(Boolean)})),new N.Setting(e).setName("Watch files modified since").setDesc("Watched mode only \u2014 files whose last modification date is before this are skipped. Defaults to today so an established vault doesn't flood the keeper on first run. Clear the field to process everything.").addText(h=>{h.inputEl.type="date",h.setValue(this.input.watchedSince).onChange(d=>{this.input.watchedSince=d.trim()})});let n=this.plugin.runtime.getSnapshot().channels.map(h=>h.name);new N.Setting(e).setName("Heartbeat channel").setDesc("Optional. Slack/Telegram channel for ingest + lint summaries. Leave as (none) to disable.").addDropdown(h=>{h.addOption("","(none)");for(let d of n)h.addOption(d,d);h.setValue(this.input.heartbeatChannel).onChange(d=>{this.input.heartbeatChannel=d})}),e.createEl("h3",{text:"Advanced",cls:"af-wk-section-h"}),new N.Setting(e).setName("Max tokens per ingest").setDesc("Hard cap on token spend per run. Unprocessed files resume next cycle.").addText(h=>h.setValue(String(this.input.maxTokensPerIngest)).onChange(d=>{let u=parseInt(d,10);!isNaN(u)&&u>0&&(this.input.maxTokensPerIngest=u)})),new N.Setting(e).setName("File substantive answers").setDesc("If on, wiki-query files long answers under _topics/syntheses/. Off = answers live only in chat.").addToggle(h=>h.setValue(this.input.fileSubstantiveAnswers).onChange(d=>{this.input.fileSubstantiveAnswers=d})),new N.Setting(e).setName("Obsidian URL scheme for citations").setDesc("If on, external-channel (Slack/Telegram) replies rewrite [[wikilinks]] as obsidian:// URLs.").addToggle(h=>h.setValue(this.input.obsidianUrlScheme).onChange(d=>{this.input.obsidianUrlScheme=d}));let a=e.createEl("h3",{text:"Paths (expert)",cls:"af-wk-section-h"});a.title="Paths are relative to scope_root. Defaults work for almost everyone. Override only if your vault layout demands it \u2014 these cannot be changed after creation.";let r=(h,d,u)=>{new N.Setting(e).setName(h).setDesc(d).addText(p=>p.setPlaceholder(String(this.input[u]??"")).setValue(String(this.input[u]??"")).onChange(m=>{this.input[u]=m.trim()||Ei[u]}))};r("Inbox path","Where one-off source drops land.","inboxPath"),r("Archive path","Where processed inbox files are moved after summarization.","archivePath"),r("Topics root","Folder under scope_root that holds the generated topic pages.","topicsRoot"),r("Index path","Relative file path for the curated index.","indexPath"),r("Log path","Relative file path for the keeper's activity log.","logPath"),r("State file","Hidden JSON file that tracks watched-source mtimes.","stateFile");let o=e.createDiv({cls:"af-wk-modal-footer"}),c=o.createEl("button",{text:"Cancel"});c.onclick=()=>this.close();let l=o.createEl("button",{text:"Create",cls:"mod-cta"});l.onclick=async()=>{l.setText("Creating\u2026"),l.disabled=!0;try{let{name:h}=await Di(this.app.vault,this.plugin.settings.fleetFolder,this.input);await this.plugin.refreshFromVault(),this.close(),new N.Notice(`Wiki Keeper "${h}" created.`),this.onCreated()}catch(h){let d=h instanceof Error?h.message:String(h);new N.Notice(`Failed to create Wiki Keeper: ${d}`),l.setText("Create"),l.disabled=!1}}}renderPreview(){let e=this.contentEl.querySelector(".af-wk-name-preview");e||(e=this.contentEl.createDiv({cls:"af-wk-name-preview"}));let s=this.input.scopeSlug||this.input.scopeRoot;e.setText(`\u2192 Will create agent: ${ea(s)}`)}onClose(){this.contentEl.empty()}},na=class extends N.Modal{constructor(e,s,n){super(e.app);this.plugin=e;this.agent=s;this.onSaved=n;let a=s.wikiKeeper;this.edit={watchedFolders:[...a.watchedFolders],excludePatterns:[...a.excludePatterns],watchedSince:a.watchedSince,heartbeatChannel:s.heartbeatChannel??"",fileSubstantiveAnswers:a.fileSubstantiveAnswers,obsidianUrlScheme:a.obsidianUrlScheme,maxTokensPerIngest:a.maxTokensPerIngest,maxTokensPerRefresh:a.maxTokensPerRefresh,indexSplitThreshold:a.indexSplitThreshold,dedupSimilarityThreshold:a.dedupSimilarityThreshold,summaryStaleDays:a.summaryStaleDays}}edit;onOpen(){let{contentEl:e}=this;e.empty(),e.addClass("af-wk-modal"),e.createEl("h2",{text:`Edit ${this.agent.name}`});let s=e.createDiv({cls:"af-wk-banner"}),n=this.agent.wikiKeeper.scopeRoot||"(whole vault)";s.createEl("strong",{text:"Scope: "}),s.createSpan({text:n}),s.createEl("br"),s.createSpan({text:"Scope and paths are fixed after creation \u2014 changing them would orphan existing topic pages. To move a Wiki Keeper, delete this one and create a new one at the new scope."}),e.createEl("h3",{text:"Sources",cls:"af-wk-section-h"}),new N.Setting(e).setName("Watched folders").setDesc("Comma- or newline-separated vault-relative paths.").addTextArea(l=>l.setValue(this.edit.watchedFolders.join(", ")).onChange(h=>{this.edit.watchedFolders=h.split(/[,\n]/).map(d=>d.trim()).filter(Boolean)})),new N.Setting(e).setName("Exclude patterns").setDesc("Glob patterns to skip.").addTextArea(l=>l.setValue(this.edit.excludePatterns.join(", ")).onChange(h=>{this.edit.excludePatterns=h.split(/[,\n]/).map(d=>d.trim()).filter(Boolean)})),new N.Setting(e).setName("Watch files modified since").setDesc("Watched mode skips files older than this date. Clear to process everything.").addText(l=>{l.inputEl.type="date",l.setValue(this.edit.watchedSince).onChange(h=>{this.edit.watchedSince=h.trim()})});let a=this.plugin.runtime.getSnapshot().channels.map(l=>l.name);new N.Setting(e).setName("Heartbeat channel").addDropdown(l=>{l.addOption("","(none)");for(let h of a)l.addOption(h,h);l.setValue(this.edit.heartbeatChannel).onChange(h=>{this.edit.heartbeatChannel=h})}),e.createEl("h3",{text:"Advanced",cls:"af-wk-section-h"}),new N.Setting(e).setName("Max tokens per ingest").addText(l=>l.setValue(String(this.edit.maxTokensPerIngest)).onChange(h=>{let d=parseInt(h,10);!isNaN(d)&&d>0&&(this.edit.maxTokensPerIngest=d)})),new N.Setting(e).setName("Max tokens per refresh").setDesc("Separate budget for the synthesis-refresh phase that regenerates topic-page summaries.").addText(l=>l.setValue(String(this.edit.maxTokensPerRefresh)).onChange(h=>{let d=parseInt(h,10);!isNaN(d)&&d>0&&(this.edit.maxTokensPerRefresh=d)})),new N.Setting(e).setName("Index split threshold").setDesc("Topic-page count above which index.md splits into per-type sub-MOCs.").addText(l=>l.setValue(String(this.edit.indexSplitThreshold)).onChange(h=>{let d=parseInt(h,10);!isNaN(d)&&d>0&&(this.edit.indexSplitThreshold=d)})),new N.Setting(e).setName("Summary stale (days)").setDesc("Lint flags topic-page summaries older than this and chains wiki-refresh.").addText(l=>l.setValue(String(this.edit.summaryStaleDays)).onChange(h=>{let d=parseInt(h,10);!isNaN(d)&&d>0&&(this.edit.summaryStaleDays=d)})),new N.Setting(e).setName("Dedup similarity threshold").setDesc("Levenshtein-ratio above which lint proposes merging two same-type pages (0.0\u20131.0).").addText(l=>l.setValue(String(this.edit.dedupSimilarityThreshold)).onChange(h=>{let d=parseFloat(h);!isNaN(d)&&d>0&&d<=1&&(this.edit.dedupSimilarityThreshold=d)})),new N.Setting(e).setName("File substantive answers").setDesc("Compounding: wiki-query files long answers under _topics/syntheses/ and bullets each cited topic page. Default ON.").addToggle(l=>l.setValue(this.edit.fileSubstantiveAnswers).onChange(h=>{this.edit.fileSubstantiveAnswers=h})),new N.Setting(e).setName("Obsidian URL scheme for citations").setDesc("Rewrite [[wikilinks]] as obsidian:// URLs when replying via Slack/Telegram.").addToggle(l=>l.setValue(this.edit.obsidianUrlScheme).onChange(h=>{this.edit.obsidianUrlScheme=h}));let r=e.createDiv({cls:"af-wk-modal-footer"}),o=r.createEl("button",{text:"Cancel"});o.onclick=()=>this.close();let c=r.createEl("button",{text:"Save",cls:"mod-cta"});c.onclick=async()=>{c.setText("Saving\u2026"),c.disabled=!0;try{await Ii(this.app.vault,this.plugin.settings.fleetFolder,this.agent.name,this.edit),await this.plugin.refreshFromVault(),this.close(),new N.Notice("Saved."),this.onSaved()}catch(l){let h=l instanceof Error?l.message:String(l);new N.Notice(`Failed to save: ${h}`),c.setText("Save"),c.disabled=!1}}}onClose(){this.contentEl.empty()}};function gl(i){return i.type==="slack",i.botToken}function yl(i){return i.length<=10?"***":`${i.slice(0,6)}\u2026${i.slice(-4)}`}var cr=require("crypto");function je(i,t,e,s,n,a,r,o){return je.fromTZ(je.tp(i,t,e,s,n,a,r),o)}je.fromTZISO=(i,t,e)=>je.fromTZ(vl(i,t),e);je.fromTZ=function(i,t){let e=new Date(Date.UTC(i.y,i.m-1,i.d,i.h,i.i,i.s)),s=aa(i.tz,e),n=new Date(e.getTime()-s),a=aa(i.tz,n);if(a-s===0)return n;{let r=new Date(e.getTime()-a),o=aa(i.tz,r);if(o-a===0)return r;if(!t&&o-a>0)return r;if(t)throw new Error("Invalid date passed to fromTZ()");return n}};je.toTZ=function(i,t){let e=i.toLocaleString("en-US",{timeZone:t}).replace(/[\u202f]/," "),s=new Date(e);return{y:s.getFullYear(),m:s.getMonth()+1,d:s.getDate(),h:s.getHours(),i:s.getMinutes(),s:s.getSeconds(),tz:t}};je.tp=(i,t,e,s,n,a,r)=>({y:i,m:t,d:e,h:s,i:n,s:a,tz:r});function aa(i,t=new Date){let e=t.toLocaleString("en-US",{timeZone:i,timeZoneName:"shortOffset"}).split(" ").slice(-1)[0],s=t.toLocaleString("en-US").replace(/[\u202f]/," ");return Date.parse(`${s} GMT`)-Date.parse(`${s} ${e}`)}function vl(i,t){let e=new Date(Date.parse(i));if(isNaN(e))throw new Error("minitz: Invalid ISO8601 passed to parser.");let s=i.substring(9);return i.includes("Z")||s.includes("-")||s.includes("+")?je.tp(e.getUTCFullYear(),e.getUTCMonth()+1,e.getUTCDate(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds(),"Etc/UTC"):je.tp(e.getFullYear(),e.getMonth()+1,e.getDate(),e.getHours(),e.getMinutes(),e.getSeconds(),t)}je.minitz=je;function wl(i){if(i===void 0&&(i={}),delete i.name,i.legacyMode=i.legacyMode===void 0?!0:i.legacyMode,i.paused=i.paused===void 0?!1:i.paused,i.maxRuns=i.maxRuns===void 0?1/0:i.maxRuns,i.catch=i.catch===void 0?!1:i.catch,i.interval=i.interval===void 0?0:parseInt(i.interval,10),i.utcOffset=i.utcOffset===void 0?void 0:parseInt(i.utcOffset,10),i.unref=i.unref===void 0?!1:i.unref,i.startAt&&(i.startAt=new Ae(i.startAt,i.timezone)),i.stopAt&&(i.stopAt=new Ae(i.stopAt,i.timezone)),i.interval!==null){if(isNaN(i.interval))throw new Error("CronOptions: Supplied value for interval is not a number");if(i.interval<0)throw new Error("CronOptions: Supplied value for interval can not be negative")}if(i.utcOffset!==void 0){if(isNaN(i.utcOffset))throw new Error("CronOptions: Invalid value passed for utcOffset, should be number representing minutes offset from UTC.");if(i.utcOffset<-870||i.utcOffset>870)throw new Error("CronOptions: utcOffset out of bounds.");if(i.utcOffset!==void 0&&i.timezone)throw new Error("CronOptions: Combining 'utcOffset' with 'timezone' is not allowed.")}if(i.unref!==!0&&i.unref!==!1)throw new Error("CronOptions: Unref should be either true, false or undefined(false).");return i}var ia=32,us=31|ia,Fi=[1,2,4,8,16];function He(i,t){this.pattern=i,this.timezone=t,this.second=Array(60).fill(0),this.minute=Array(60).fill(0),this.hour=Array(24).fill(0),this.day=Array(31).fill(0),this.month=Array(12).fill(0),this.dayOfWeek=Array(7).fill(0),this.lastDayOfMonth=!1,this.starDOM=!1,this.starDOW=!1,this.parse()}He.prototype.parse=function(){if(!(typeof this.pattern=="string"||this.pattern.constructor===String))throw new TypeError("CronPattern: Pattern has to be of type string.");this.pattern.indexOf("@")>=0&&(this.pattern=this.handleNicknames(this.pattern).trim());let i=this.pattern.replace(/\s+/g," ").split(" ");if(i.length<5||i.length>6)throw new TypeError("CronPattern: invalid configuration format ('"+this.pattern+"'), exactly five or six space separated parts are required.");if(i.length===5&&i.unshift("0"),i[3].indexOf("L")>=0&&(i[3]=i[3].replace("L",""),this.lastDayOfMonth=!0),i[3]=="*"&&(this.starDOM=!0),i[4].length>=3&&(i[4]=this.replaceAlphaMonths(i[4])),i[5].length>=3&&(i[5]=this.replaceAlphaDays(i[5])),i[5]=="*"&&(this.starDOW=!0),this.pattern.indexOf("?")>=0){let t=new Ae(new Date,this.timezone).getDate(!0);i[0]=i[0].replace("?",t.getSeconds()),i[1]=i[1].replace("?",t.getMinutes()),i[2]=i[2].replace("?",t.getHours()),this.starDOM||(i[3]=i[3].replace("?",t.getDate())),i[4]=i[4].replace("?",t.getMonth()+1),this.starDOW||(i[5]=i[5].replace("?",t.getDay()))}this.throwAtIllegalCharacters(i),this.partToArray("second",i[0],0,1),this.partToArray("minute",i[1],0,1),this.partToArray("hour",i[2],0,1),this.partToArray("day",i[3],-1,1),this.partToArray("month",i[4],-1,1),this.partToArray("dayOfWeek",i[5],0,us),this.dayOfWeek[7]&&(this.dayOfWeek[0]=this.dayOfWeek[7])};He.prototype.partToArray=function(i,t,e,s){let n=this[i],a=i==="day"&&this.lastDayOfMonth;if(t===""&&!a)throw new TypeError("CronPattern: configuration entry "+i+" ("+t+") is empty, check for trailing spaces.");if(t==="*")return n.fill(s);let r=t.split(",");if(r.length>1)for(let o=0;o6)&&t!=="L")throw new RangeError("CronPattern: Invalid value for dayOfWeek: "+t);this.setNthWeekdayOfMonth(t,e);return}if(i==="second"||i==="minute"){if(t<0||t>=60)throw new RangeError("CronPattern: Invalid value for "+i+": "+t)}else if(i==="hour"){if(t<0||t>=24)throw new RangeError("CronPattern: Invalid value for "+i+": "+t)}else if(i==="day"){if(t<0||t>=31)throw new RangeError("CronPattern: Invalid value for "+i+": "+t)}else if(i==="month"&&(t<0||t>=12))throw new RangeError("CronPattern: Invalid value for "+i+": "+t);this[i][t]=e};He.prototype.handleRangeWithStepping=function(i,t,e,s){let n=this.extractNth(i,t),a=n[0].match(/^(\d+)-(\d+)\/(\d+)$/);if(a===null)throw new TypeError("CronPattern: Syntax error, illegal range with stepping: '"+i+"'");let[,r,o,c]=a;if(r=parseInt(r,10)+e,o=parseInt(o,10)+e,c=parseInt(c,10),isNaN(r))throw new TypeError("CronPattern: Syntax error, illegal lower range (NaN)");if(isNaN(o))throw new TypeError("CronPattern: Syntax error, illegal upper range (NaN)");if(isNaN(c))throw new TypeError("CronPattern: Syntax error, illegal stepping: (NaN)");if(c===0)throw new TypeError("CronPattern: Syntax error, illegal stepping: 0");if(c>this[t].length)throw new TypeError("CronPattern: Syntax error, steps cannot be greater than maximum value of part ("+this[t].length+")");if(r>o)throw new TypeError("CronPattern: From value is larger than to value: '"+i+"'");for(let l=r;l<=o;l+=c)this.setPart(t,l,n[1]||s)};He.prototype.extractNth=function(i,t){let e=i,s;if(e.includes("#")){if(t!=="dayOfWeek")throw new Error("CronPattern: nth (#) only allowed in day-of-week field");s=e.split("#")[1],e=e.split("#")[0]}return[e,s]};He.prototype.handleRange=function(i,t,e,s){let n=this.extractNth(i,t),a=n[0].split("-");if(a.length!==2)throw new TypeError("CronPattern: Syntax error, illegal range: '"+i+"'");let r=parseInt(a[0],10)+e,o=parseInt(a[1],10)+e;if(isNaN(r))throw new TypeError("CronPattern: Syntax error, illegal lower range (NaN)");if(isNaN(o))throw new TypeError("CronPattern: Syntax error, illegal upper range (NaN)");if(r>o)throw new TypeError("CronPattern: From value is larger than to value: '"+i+"'");for(let c=r;c<=o;c++)this.setPart(t,c,n[1]||s)};He.prototype.handleStepping=function(i,t,e,s){let n=this.extractNth(i,t),a=n[0].split("/");if(a.length!==2)throw new TypeError("CronPattern: Syntax error, illegal stepping: '"+i+"'");let r=0;a[0]!=="*"&&(r=parseInt(a[0],10)+e);let o=parseInt(a[1],10);if(isNaN(o))throw new TypeError("CronPattern: Syntax error, illegal stepping: (NaN)");if(o===0)throw new TypeError("CronPattern: Syntax error, illegal stepping: 0");if(o>this[t].length)throw new TypeError("CronPattern: Syntax error, max steps for part is ("+this[t].length+")");for(let c=r;c0)this.dayOfWeek[i]=this.dayOfWeek[i]|Fi[t-1];else if(t===us)this.dayOfWeek[i]=us;else throw new TypeError(`CronPattern: nth weekday of of range, should be 1-5 or L. Value: ${t}`)};var Oi=[31,28,31,30,31,30,31,31,30,31,30,31],pt=[["month","year",0],["day","month",-1],["hour","day",0],["minute","hour",0],["second","minute",0]];function Ae(i,t){if(this.tz=t,i&&i instanceof Date)if(!isNaN(i))this.fromDate(i);else throw new TypeError("CronDate: Invalid date passed to CronDate constructor");else if(i===void 0)this.fromDate(new Date);else if(i&&typeof i=="string")this.fromString(i);else if(i instanceof Ae)this.fromCronDate(i);else throw new TypeError("CronDate: Invalid type ("+typeof i+") passed to CronDate constructor")}Ae.prototype.isNthWeekdayOfMonth=function(i,t,e,s){let a=new Date(Date.UTC(i,t,e)).getUTCDay(),r=0;for(let o=1;o<=e;o++)new Date(Date.UTC(i,t,o)).getUTCDay()===a&&r++;if(s&us&&Fi[r-1]&s)return!0;if(s&ia){let o=new Date(Date.UTC(i,t+1,0)).getUTCDate();for(let c=e+1;c<=o;c++)if(new Date(Date.UTC(i,t,c)).getUTCDay()===a)return!1;return!0}return!1};Ae.prototype.fromDate=function(i){if(this.tz!==void 0)if(typeof this.tz=="number")this.ms=i.getUTCMilliseconds(),this.second=i.getUTCSeconds(),this.minute=i.getUTCMinutes()+this.tz,this.hour=i.getUTCHours(),this.day=i.getUTCDate(),this.month=i.getUTCMonth(),this.year=i.getUTCFullYear(),this.apply();else{let t=je.toTZ(i,this.tz);this.ms=i.getMilliseconds(),this.second=t.s,this.minute=t.i,this.hour=t.h,this.day=t.d,this.month=t.m-1,this.year=t.y}else this.ms=i.getMilliseconds(),this.second=i.getSeconds(),this.minute=i.getMinutes(),this.hour=i.getHours(),this.day=i.getDate(),this.month=i.getMonth(),this.year=i.getFullYear()};Ae.prototype.fromCronDate=function(i){this.tz=i.tz,this.year=i.year,this.month=i.month,this.day=i.day,this.hour=i.hour,this.minute=i.minute,this.second=i.second,this.ms=i.ms};Ae.prototype.apply=function(){if(this.month>11||this.day>Oi[this.month]||this.hour>59||this.minute>59||this.second>59||this.hour<0||this.minute<0||this.second<0){let i=new Date(Date.UTC(this.year,this.month,this.day,this.hour,this.minute,this.second,this.ms));return this.ms=i.getUTCMilliseconds(),this.second=i.getUTCSeconds(),this.minute=i.getUTCMinutes(),this.hour=i.getUTCHours(),this.day=i.getUTCDate(),this.month=i.getUTCMonth(),this.year=i.getUTCFullYear(),!0}else return!1};Ae.prototype.fromString=function(i){if(typeof this.tz=="number"){let t=je.fromTZISO(i);this.ms=t.getUTCMilliseconds(),this.second=t.getUTCSeconds(),this.minute=t.getUTCMinutes(),this.hour=t.getUTCHours(),this.day=t.getUTCDate(),this.month=t.getUTCMonth(),this.year=t.getUTCFullYear(),this.apply()}else return this.fromDate(je.fromTZISO(i,this.tz))};Ae.prototype.findNext=function(i,t,e,s){let n=this[t],a;e.lastDayOfMonth&&(this.month!==1?a=Oi[this.month]:a=new Date(Date.UTC(this.year,this.month+1,0,0,0,0,0)).getUTCDate());let r=!e.starDOW&&t=="day"?new Date(Date.UTC(this.year,this.month,1,0,0,0,0)).getUTCDay():void 0;for(let o=this[t]+s;o1){let n=e+1;for(;n=pt.length?this:this.year>=3e3?null:this.recurse(i,t,e)};Ae.prototype.increment=function(i,t,e){return this.second+=t.interval>1&&e?t.interval:1,this.ms=0,this.apply(),this.recurse(i,t,0)};Ae.prototype.getDate=function(i){return i||this.tz===void 0?new Date(this.year,this.month,this.day,this.hour,this.minute,this.second,this.ms):typeof this.tz=="number"?new Date(Date.UTC(this.year,this.month,this.day,this.hour,this.minute-this.tz,this.second,this.ms)):je(this.year,this.month+1,this.day,this.hour,this.minute,this.second,this.tz)};Ae.prototype.getTime=function(){return this.getDate().getTime()};function Zs(i){return Object.prototype.toString.call(i)==="[object Function]"||typeof i=="function"||i instanceof Function}function bl(i){typeof Deno<"u"&&typeof Deno.unrefTimer<"u"?Deno.unrefTimer(i):i&&typeof i.unref<"u"&&i.unref()}var Li=30*1e3,ps=[];function pe(i,t,e){if(!(this instanceof pe))return new pe(i,t,e);let s,n;if(Zs(t))n=t;else if(typeof t=="object")s=t;else if(t!==void 0)throw new Error("Cron: Invalid argument passed for optionsIn. Should be one of function, or object (options).");if(Zs(e))n=e;else if(typeof e=="object")s=e;else if(e!==void 0)throw new Error("Cron: Invalid argument passed for funcIn. Should be one of function, or object (options).");if(this.name=s?s.name:void 0,this.options=wl(s),this._states={kill:!1,blocking:!1,previousRun:void 0,currentRun:void 0,once:void 0,currentTimeout:void 0,maxRuns:s?s.maxRuns:void 0,paused:s?s.paused:!1,pattern:void 0},i&&(i instanceof Date||typeof i=="string"&&i.indexOf(":")>0)?this._states.once=new Ae(i,this.options.timezone||this.options.utcOffset):this._states.pattern=new He(i,this.options.timezone),this.name){if(ps.find(r=>r.name===this.name))throw new Error("Cron: Tried to initialize new named job '"+this.name+"', but name already taken.");ps.push(this)}return n!==void 0&&(this.fn=n,this.schedule()),this}pe.prototype.nextRun=function(i){let t=this._next(i);return t?t.getDate():null};pe.prototype.nextRuns=function(i,t){i>this._states.maxRuns&&(i=this._states.maxRuns);let e=[],s=t||this._states.currentRun;for(;i--&&(s=this.nextRun(s));)e.push(s);return e};pe.prototype.getPattern=function(){return this._states.pattern?this._states.pattern.pattern:void 0};pe.prototype.isRunning=function(){let i=this.nextRun(this._states.currentRun),t=!this._states.paused,e=this.fn!==void 0,s=!this._states.kill;return t&&e&&s&&i!==null};pe.prototype.isStopped=function(){return this._states.kill};pe.prototype.isBusy=function(){return this._states.blocking};pe.prototype.currentRun=function(){return this._states.currentRun?this._states.currentRun.getDate():null};pe.prototype.previousRun=function(){return this._states.previousRun?this._states.previousRun.getDate():null};pe.prototype.msToNext=function(i){i=i||new Date;let t=this._next(i);return t?t.getTime()-i.getTime():null};pe.prototype.stop=function(){this._states.kill=!0,this._states.currentTimeout&&clearTimeout(this._states.currentTimeout);let i=ps.indexOf(this);i>=0&&ps.splice(i,1)};pe.prototype.pause=function(){return this._states.paused=!0,!this._states.kill};pe.prototype.resume=function(){return this._states.paused=!1,!this._states.kill};pe.prototype.schedule=function(i){if(i&&this.fn)throw new Error("Cron: It is not allowed to schedule two functions using the same Croner instance.");i&&(this.fn=i);let t=this.msToNext(),e=this.nextRun(this._states.currentRun);return t==null||isNaN(t)||e===null?this:(t>Li&&(t=Li),this._states.currentTimeout=setTimeout(()=>this._checkTrigger(e),t),this._states.currentTimeout&&this.options.unref&&bl(this._states.currentTimeout),this)};pe.prototype._trigger=async function(i){if(this._states.blocking=!0,this._states.currentRun=new Ae(void 0,this.options.timezone||this.options.utcOffset),this.options.catch)try{await this.fn(this,this.options.context)}catch(t){Zs(this.options.catch)&&this.options.catch(t,this)}else await this.fn(this,this.options.context);this._states.previousRun=new Ae(i,this.options.timezone||this.options.utcOffset),this._states.blocking=!1};pe.prototype.trigger=async function(){await this._trigger()};pe.prototype._checkTrigger=function(i){let t=new Date,e=!this._states.paused&&t.getTime()>=i,s=this._states.blocking&&this.options.protect;e&&!s?(this._states.maxRuns--,this._trigger()):e&&s&&Zs(this.options.protect)&&setTimeout(()=>this.options.protect(this),0),this.schedule()};pe.prototype._next=function(i){let t=!!(i||this._states.currentRun),e=!1;!i&&this.options.startAt&&this.options.interval&&([i,t]=this._calculatePreviousRun(i,t),e=!i),i=new Ae(i,this.options.timezone||this.options.utcOffset),this.options.startAt&&i&&i.getTime()=this.options.stopAt.getTime()?null:s};pe.prototype._calculatePreviousRun=function(i,t){let e=new Ae(void 0,this.options.timezone||this.options.utcOffset);if(this.options.startAt.getTime()<=e.getTime()){i=this.options.startAt;let s=i.getTime()+this.options.interval*1e3;for(;s<=e.getTime();)i=new Ae(i,this.options.timezone||this.options.utcOffset).increment(this._states.pattern,this.options,!0),s=i.getTime()+this.options.interval*1e3;t=!0}return[i,t]};pe.Cron=pe;pe.scheduledJobs=ps;var dr=require("obsidian");var tr=require("crypto");var Ve=require("fs"),ra=require("path");function kl(i){return`mcp__${i.replace(/[\s.]+/g,"_")}`}function en(i,t,e={}){let s=t.permissionRules.allow.length>0||t.permissionRules.deny.length>0,n=t.permissionMode?.trim(),a=!!n&&n!=="default",r=e.mcpAllowServers??[],o=r.length>0;if(!(s||a||o))return null;let l=(0,ra.join)(i,".claude"),h=(0,ra.join)(l,"settings.local.json");(0,Ve.existsSync)(l)||(0,Ve.mkdirSync)(l,{recursive:!0});let d=null;if((0,Ve.existsSync)(h))try{d=(0,Ve.readFileSync)(h,"utf-8")}catch{d=null}let u={};if(a&&(u.defaultMode=n),s||o){let p=[...t.permissionRules.allow];for(let m of r)p.push(kl(m));u.permissions={allow:p,deny:t.permissionRules.deny}}return(0,Ve.writeFileSync)(h,JSON.stringify(u,null,2)+` -`,"utf-8"),{path:h,backupContent:d}}function Et(i){if(i)try{i.backupContent!==null?(0,Ve.writeFileSync)(i.path,i.backupContent,"utf-8"):(0,Ve.existsSync)(i.path)&&(0,Ve.unlinkSync)(i.path)}catch{}}function Ni(i){if(typeof i=="string")return i;if(Array.isArray(i))return i.map(e=>{if(typeof e=="string")return e;if(e&&typeof e=="object"&&"text"in e){let s=e.text;if(typeof s=="string")return s}return""}).filter(Boolean).join(` -`);if(i&&typeof i=="object"){for(let t of["output","result","text","message"])if(t in i)return Ni(i[t])}}function oa(i,t=[]){if(Array.isArray(i)){for(let r of i)oa(r,t);return t}if(!i||typeof i!="object")return t;let e=i,s=typeof e.tool_name=="string"&&e.tool_name||typeof e.tool=="string"&&e.tool||typeof e.name=="string"&&e.name,n=typeof e.command=="string"?e.command:typeof e.input=="string"?e.input:typeof e.cmd=="string"?e.cmd:void 0,a=typeof e.reason=="string"?e.reason:void 0;s&&["tool_use","tool","name","tool_name"].some(r=>r in e)&&t.push({tool:s,command:n,reason:a});for(let r of Object.values(e))oa(r,t);return t}function Bi(i){if(!i||typeof i!="object")return;let t=i,e=t.usage;if(e&&typeof e=="object"){let n=typeof e.input_tokens=="number"?e.input_tokens:0,a=typeof e.output_tokens=="number"?e.output_tokens:0,r=typeof e.cache_creation_input_tokens=="number"?e.cache_creation_input_tokens:0,o=typeof e.cache_read_input_tokens=="number"?e.cache_read_input_tokens:0,c=n+a+r+o;if(c>0)return c}let s=t.modelUsage;if(s&&typeof s=="object"){let n=0;for(let a of Object.values(s)){if(!a||typeof a!="object")continue;let r=a;n+=typeof r.inputTokens=="number"?r.inputTokens:0,n+=typeof r.outputTokens=="number"?r.outputTokens:0,n+=typeof r.cacheReadInputTokens=="number"?r.cacheReadInputTokens:0,n+=typeof r.cacheCreationInputTokens=="number"?r.cacheCreationInputTokens:0}if(n>0)return n}for(let n of["tokens_used","total_tokens","totalTokens"])if(typeof t[n]=="number")return t[n];for(let n of Object.values(t)){let a=Bi(n);if(typeof a=="number")return a}}function Ui(i){if(!i||typeof i!="object")return;let t=i;if(typeof t.total_cost_usd=="number")return t.total_cost_usd;for(let e of Object.values(t)){let s=Ui(e);if(typeof s=="number")return s}}function la(i){if(!i||typeof i!="object")return;let t=i;if(t.type==="result"&&typeof t.result=="string"&&t.result.trim())return t.result}function tn(i){if(!i||typeof i!="object")return;let t=i;if(t.modelUsage&&typeof t.modelUsage=="object"){let s=Object.keys(t.modelUsage);if(s.length>0&&s[0])return s[0]}let e=t.message;if(e&&typeof e.model=="string"&&e.model)return e.model;if(typeof t.model=="string"&&t.model)return t.model;for(let s of Object.values(t)){let n=tn(s);if(n)return n}}function xl(i){return/^gpt-|codex/i.test(i.trim())}var $i={id:"claude-code",label:"Claude Code",cliPath(i){return i.claudeCliPath},buildExec(i){let t=["-p",i.prompt,"--output-format",i.streaming?"stream-json":"json"];i.streaming&&t.push("--verbose");let e=i.modelSource==="settings"&&xl(i.model);i.model&&!e&&t.push("--model",i.model),i.effort&&t.push("--effort",i.effort);let s=i.agent.permissionMode?.trim();return s&&s!=="default"?t.push("--permission-mode",s):t.push("--permission-mode","bypassPermissions"),Promise.resolve({cliPath:i.settings.claudeCliPath,args:t})},parseExecOutput(i,t,e){let s=i.trim(),n;if(e){let c=ge(s);for(let l=c.length-1;l>=0;l--){let h=c[l]?.trim();if(h)try{let d=JSON.parse(h);if(d&&typeof d=="object"){n=d;break}}catch{}}}else if(s.startsWith("{")||s.startsWith("["))try{n=JSON.parse(s)}catch{n=void 0}let a=Ni(n)??"";if(!a&&e){let c=[];for(let l of ge(s)){let h=l.trim();if(h)try{let d=JSON.parse(h);if(d.type==="assistant"&&d.message?.content)for(let u of d.message.content)u.type==="text"&&u.text&&c.push(u.text);else d.type==="result"&&typeof d.result=="string"&&c.push(d.result)}catch{}}a=c.join(` -`).trim()}a||(a=t.trim()||"(no output)");let r=tn(n),o=la(n);if((!r||!o)&&e)for(let c of ge(s)){let l=c.trim();if(l)try{let h=JSON.parse(l);if(!r){let d=tn(h);d&&(r=d)}if(!o){let d=la(h);d&&(o=d)}if(r&&o)break}catch{}}return{outputText:a,finalResult:o,tokensUsed:Bi(n),costUsd:Ui(n),toolsUsed:oa(n),concreteModel:r,rawJson:n}},extractStreamChunk(i){let t=i.trim();if(!t)return null;let e;try{e=JSON.parse(t)}catch{return null}let s=e.type;if(s==="assistant"){let n=e.message;if(n?.content&&Array.isArray(n.content)){let a=[];for(let r of n.content)if(r.type==="text"&&typeof r.text=="string")a.push(r.text);else if(r.type==="tool_use"){let o=String(r.name??"tool"),c=r.input,l=c?.command??c?.content??"";a.push(` +Your scope's inbox, topics, index, and log are NOT deleted.`,confirmText:"Delete",danger:!0,onConfirm:async()=>{try{await Wi(this.plugin.app,this.plugin.settings.fleetFolder,o.name),await this.plugin.refreshFromVault(),new F.Notice(`Wiki Keeper "${o.name}" deleted.`),this.scheduleRerender()}catch(m){let f=m instanceof Error?m.message:String(m);new F.Notice(`Failed to delete: ${f}`)}}}).open()}}new F.Setting(e).setName("Add Wiki Keeper").setDesc("Create a new scoped wiki agent. All fields optional.").addButton(o=>o.setButtonText("+ Add").onClick(()=>{new ra(this.plugin,()=>{this.scheduleRerender()}).open()}))}scheduleRerender(){window.setTimeout(()=>{(async()=>{try{await this.plugin.refreshFromVault()}catch{}this.display()})()},600)}},ra=class extends F.Modal{constructor(e,s){super(e.app);this.plugin=e;this.onCreated=s;this.input=na()}input;onOpen(){let{contentEl:e}=this;e.empty(),e.addClass("af-wk-modal"),e.createEl("h2",{text:"Add Wiki Keeper"});let s=e.createDiv({cls:"af-wk-banner"});s.createEl("strong",{text:"All fields are optional. "}),s.createSpan({text:"Click Create with everything blank and you'll get a whole-vault keeper with sensible defaults (inbox at _sources/inbox, topics at topics/, nightly ingest at 3am, Sunday lint)."}),e.createEl("h3",{text:"Scope",cls:"af-wk-section-h"}),new F.Setting(e).setName("Scope folder").setDesc("Optional. Vault-relative path. Empty = whole vault.").addText(h=>h.setPlaceholder("(empty = whole vault)").setValue(this.input.scopeRoot).onChange(d=>{this.input.scopeRoot=d.trim(),this.renderPreview()})),new F.Setting(e).setName("Slug").setDesc("Optional. Agent name suffix. Default: derived from scope folder name, or 'wiki-keeper' for whole-vault.").addText(h=>h.setPlaceholder("(auto)").setValue(this.input.scopeSlug).onChange(d=>{this.input.scopeSlug=d.trim(),this.renderPreview()})),this.renderPreview(),e.createEl("h3",{text:"Sources",cls:"af-wk-section-h"}),new F.Setting(e).setName("Watched folders").setDesc("Optional. Comma- or newline-separated vault-relative paths. Read-only \u2014 claims are extracted but files are never moved. Leave empty for inbox-only mode (you drop files into _sources/inbox/ and they get archived after processing).").addTextArea(h=>h.setPlaceholder("(empty = inbox-only)").setValue(this.input.watchedFolders.join(", ")).onChange(d=>{this.input.watchedFolders=d.split(/[,\n]/).map(u=>u.trim()).filter(Boolean)})),new F.Setting(e).setName("Exclude patterns").setDesc("Optional. Glob patterns to skip. Leave empty to process everything under watched + inbox.").addTextArea(h=>h.setPlaceholder("(empty = no excludes)").setValue(this.input.excludePatterns.join(", ")).onChange(d=>{this.input.excludePatterns=d.split(/[,\n]/).map(u=>u.trim()).filter(Boolean)})),new F.Setting(e).setName("Watch files modified since").setDesc("Watched mode only \u2014 files whose last modification date is before this are skipped. Defaults to today so an established vault doesn't flood the keeper on first run. Clear the field to process everything.").addText(h=>{h.inputEl.type="date",h.setValue(this.input.watchedSince).onChange(d=>{this.input.watchedSince=d.trim()})});let n=this.plugin.runtime.getSnapshot().channels.map(h=>h.name);new F.Setting(e).setName("Heartbeat channel").setDesc("Optional. Slack/Telegram channel for ingest + lint summaries. Leave as (none) to disable.").addDropdown(h=>{h.addOption("","(none)");for(let d of n)h.addOption(d,d);h.setValue(this.input.heartbeatChannel).onChange(d=>{this.input.heartbeatChannel=d})}),e.createEl("h3",{text:"Advanced",cls:"af-wk-section-h"}),new F.Setting(e).setName("Max tokens per ingest").setDesc("Hard cap on token spend per run. Unprocessed files resume next cycle.").addText(h=>h.setValue(String(this.input.maxTokensPerIngest)).onChange(d=>{let u=parseInt(d,10);!isNaN(u)&&u>0&&(this.input.maxTokensPerIngest=u)})),new F.Setting(e).setName("File substantive answers").setDesc("If on, wiki-query files long answers under _topics/syntheses/. Off = answers live only in chat.").addToggle(h=>h.setValue(this.input.fileSubstantiveAnswers).onChange(d=>{this.input.fileSubstantiveAnswers=d})),new F.Setting(e).setName("Obsidian URL scheme for citations").setDesc("If on, external-channel (Slack/Telegram) replies rewrite [[wikilinks]] as obsidian:// URLs.").addToggle(h=>h.setValue(this.input.obsidianUrlScheme).onChange(d=>{this.input.obsidianUrlScheme=d}));let a=e.createEl("h3",{text:"Paths (expert)",cls:"af-wk-section-h"});a.title="Paths are relative to scope_root. Defaults work for almost everyone. Override only if your vault layout demands it \u2014 these cannot be changed after creation.";let r=(h,d,u)=>{new F.Setting(e).setName(h).setDesc(d).addText(p=>p.setPlaceholder(String(this.input[u]??"")).setValue(String(this.input[u]??"")).onChange(m=>{this.input[u]=m.trim()||Ni[u]}))};r("Inbox path","Where one-off source drops land.","inboxPath"),r("Archive path","Where processed inbox files are moved after summarization.","archivePath"),r("Topics root","Folder under scope_root that holds the generated topic pages.","topicsRoot"),r("Index path","Relative file path for the curated index.","indexPath"),r("Log path","Relative file path for the keeper's activity log.","logPath"),r("State file","Hidden JSON file that tracks watched-source mtimes.","stateFile");let o=e.createDiv({cls:"af-wk-modal-footer"}),c=o.createEl("button",{text:"Cancel"});c.onclick=()=>this.close();let l=o.createEl("button",{text:"Create",cls:"mod-cta"});l.onclick=async()=>{l.setText("Creating\u2026"),l.disabled=!0;try{let{name:h}=await $i(this.app.vault,this.plugin.settings.fleetFolder,this.input);await this.plugin.refreshFromVault(),this.close(),new F.Notice(`Wiki Keeper "${h}" created.`),this.onCreated()}catch(h){let d=h instanceof Error?h.message:String(h);new F.Notice(`Failed to create Wiki Keeper: ${d}`),l.setText("Create"),l.disabled=!1}}}renderPreview(){let e=this.contentEl.querySelector(".af-wk-name-preview");e||(e=this.contentEl.createDiv({cls:"af-wk-name-preview"}));let s=this.input.scopeSlug||this.input.scopeRoot;e.setText(`\u2192 Will create agent: ${aa(s)}`)}onClose(){this.contentEl.empty()}},oa=class extends F.Modal{constructor(e,s,n){super(e.app);this.plugin=e;this.agent=s;this.onSaved=n;let a=s.wikiKeeper;this.edit={watchedFolders:[...a.watchedFolders],excludePatterns:[...a.excludePatterns],watchedSince:a.watchedSince,heartbeatChannel:s.heartbeatChannel??"",fileSubstantiveAnswers:a.fileSubstantiveAnswers,obsidianUrlScheme:a.obsidianUrlScheme,maxTokensPerIngest:a.maxTokensPerIngest,maxTokensPerRefresh:a.maxTokensPerRefresh,indexSplitThreshold:a.indexSplitThreshold,dedupSimilarityThreshold:a.dedupSimilarityThreshold,summaryStaleDays:a.summaryStaleDays}}edit;onOpen(){let{contentEl:e}=this;e.empty(),e.addClass("af-wk-modal"),e.createEl("h2",{text:`Edit ${this.agent.name}`});let s=e.createDiv({cls:"af-wk-banner"}),n=this.agent.wikiKeeper.scopeRoot||"(whole vault)";s.createEl("strong",{text:"Scope: "}),s.createSpan({text:n}),s.createEl("br"),s.createSpan({text:"Scope and paths are fixed after creation \u2014 changing them would orphan existing topic pages. To move a Wiki Keeper, delete this one and create a new one at the new scope."}),e.createEl("h3",{text:"Sources",cls:"af-wk-section-h"}),new F.Setting(e).setName("Watched folders").setDesc("Comma- or newline-separated vault-relative paths.").addTextArea(l=>l.setValue(this.edit.watchedFolders.join(", ")).onChange(h=>{this.edit.watchedFolders=h.split(/[,\n]/).map(d=>d.trim()).filter(Boolean)})),new F.Setting(e).setName("Exclude patterns").setDesc("Glob patterns to skip.").addTextArea(l=>l.setValue(this.edit.excludePatterns.join(", ")).onChange(h=>{this.edit.excludePatterns=h.split(/[,\n]/).map(d=>d.trim()).filter(Boolean)})),new F.Setting(e).setName("Watch files modified since").setDesc("Watched mode skips files older than this date. Clear to process everything.").addText(l=>{l.inputEl.type="date",l.setValue(this.edit.watchedSince).onChange(h=>{this.edit.watchedSince=h.trim()})});let a=this.plugin.runtime.getSnapshot().channels.map(l=>l.name);new F.Setting(e).setName("Heartbeat channel").addDropdown(l=>{l.addOption("","(none)");for(let h of a)l.addOption(h,h);l.setValue(this.edit.heartbeatChannel).onChange(h=>{this.edit.heartbeatChannel=h})}),e.createEl("h3",{text:"Advanced",cls:"af-wk-section-h"}),new F.Setting(e).setName("Max tokens per ingest").addText(l=>l.setValue(String(this.edit.maxTokensPerIngest)).onChange(h=>{let d=parseInt(h,10);!isNaN(d)&&d>0&&(this.edit.maxTokensPerIngest=d)})),new F.Setting(e).setName("Max tokens per refresh").setDesc("Separate budget for the synthesis-refresh phase that regenerates topic-page summaries.").addText(l=>l.setValue(String(this.edit.maxTokensPerRefresh)).onChange(h=>{let d=parseInt(h,10);!isNaN(d)&&d>0&&(this.edit.maxTokensPerRefresh=d)})),new F.Setting(e).setName("Index split threshold").setDesc("Topic-page count above which index.md splits into per-type sub-MOCs.").addText(l=>l.setValue(String(this.edit.indexSplitThreshold)).onChange(h=>{let d=parseInt(h,10);!isNaN(d)&&d>0&&(this.edit.indexSplitThreshold=d)})),new F.Setting(e).setName("Summary stale (days)").setDesc("Lint flags topic-page summaries older than this and chains wiki-refresh.").addText(l=>l.setValue(String(this.edit.summaryStaleDays)).onChange(h=>{let d=parseInt(h,10);!isNaN(d)&&d>0&&(this.edit.summaryStaleDays=d)})),new F.Setting(e).setName("Dedup similarity threshold").setDesc("Levenshtein-ratio above which lint proposes merging two same-type pages (0.0\u20131.0).").addText(l=>l.setValue(String(this.edit.dedupSimilarityThreshold)).onChange(h=>{let d=parseFloat(h);!isNaN(d)&&d>0&&d<=1&&(this.edit.dedupSimilarityThreshold=d)})),new F.Setting(e).setName("File substantive answers").setDesc("Compounding: wiki-query files long answers under _topics/syntheses/ and bullets each cited topic page. Default ON.").addToggle(l=>l.setValue(this.edit.fileSubstantiveAnswers).onChange(h=>{this.edit.fileSubstantiveAnswers=h})),new F.Setting(e).setName("Obsidian URL scheme for citations").setDesc("Rewrite [[wikilinks]] as obsidian:// URLs when replying via Slack/Telegram.").addToggle(l=>l.setValue(this.edit.obsidianUrlScheme).onChange(h=>{this.edit.obsidianUrlScheme=h}));let r=e.createDiv({cls:"af-wk-modal-footer"}),o=r.createEl("button",{text:"Cancel"});o.onclick=()=>this.close();let c=r.createEl("button",{text:"Save",cls:"mod-cta"});c.onclick=async()=>{c.setText("Saving\u2026"),c.disabled=!0;try{await ji(this.app.vault,this.plugin.settings.fleetFolder,this.agent.name,this.edit),await this.plugin.refreshFromVault(),this.close(),new F.Notice("Saved."),this.onSaved()}catch(l){let h=l instanceof Error?l.message:String(l);new F.Notice(`Failed to save: ${h}`),c.setText("Save"),c.disabled=!1}}}onClose(){this.contentEl.empty()}};function Dl(i){return i.type==="slack",i.botToken}function Il(i){return i.length<=10?"***":`${i.slice(0,6)}\u2026${i.slice(-4)}`}var Sa=require("crypto");function He(i,t,e,s,n,a,r,o){return He.fromTZ(He.tp(i,t,e,s,n,a,r),o)}He.fromTZISO=(i,t,e)=>He.fromTZ(Ml(i,t),e);He.fromTZ=function(i,t){let e=new Date(Date.UTC(i.y,i.m-1,i.d,i.h,i.i,i.s)),s=la(i.tz,e),n=new Date(e.getTime()-s),a=la(i.tz,n);if(a-s===0)return n;{let r=new Date(e.getTime()-a),o=la(i.tz,r);if(o-a===0)return r;if(!t&&o-a>0)return r;if(t)throw new Error("Invalid date passed to fromTZ()");return n}};He.toTZ=function(i,t){let e=i.toLocaleString("en-US",{timeZone:t}).replace(/[\u202f]/," "),s=new Date(e);return{y:s.getFullYear(),m:s.getMonth()+1,d:s.getDate(),h:s.getHours(),i:s.getMinutes(),s:s.getSeconds(),tz:t}};He.tp=(i,t,e,s,n,a,r)=>({y:i,m:t,d:e,h:s,i:n,s:a,tz:r});function la(i,t=new Date){let e=t.toLocaleString("en-US",{timeZone:i,timeZoneName:"shortOffset"}).split(" ").slice(-1)[0],s=t.toLocaleString("en-US").replace(/[\u202f]/," ");return Date.parse(`${s} GMT`)-Date.parse(`${s} ${e}`)}function Ml(i,t){let e=new Date(Date.parse(i));if(isNaN(e))throw new Error("minitz: Invalid ISO8601 passed to parser.");let s=i.substring(9);return i.includes("Z")||s.includes("-")||s.includes("+")?He.tp(e.getUTCFullYear(),e.getUTCMonth()+1,e.getUTCDate(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds(),"Etc/UTC"):He.tp(e.getFullYear(),e.getMonth()+1,e.getDate(),e.getHours(),e.getMinutes(),e.getSeconds(),t)}He.minitz=He;function Ll(i){if(i===void 0&&(i={}),delete i.name,i.legacyMode=i.legacyMode===void 0?!0:i.legacyMode,i.paused=i.paused===void 0?!1:i.paused,i.maxRuns=i.maxRuns===void 0?1/0:i.maxRuns,i.catch=i.catch===void 0?!1:i.catch,i.interval=i.interval===void 0?0:parseInt(i.interval,10),i.utcOffset=i.utcOffset===void 0?void 0:parseInt(i.utcOffset,10),i.unref=i.unref===void 0?!1:i.unref,i.startAt&&(i.startAt=new _e(i.startAt,i.timezone)),i.stopAt&&(i.stopAt=new _e(i.stopAt,i.timezone)),i.interval!==null){if(isNaN(i.interval))throw new Error("CronOptions: Supplied value for interval is not a number");if(i.interval<0)throw new Error("CronOptions: Supplied value for interval can not be negative")}if(i.utcOffset!==void 0){if(isNaN(i.utcOffset))throw new Error("CronOptions: Invalid value passed for utcOffset, should be number representing minutes offset from UTC.");if(i.utcOffset<-870||i.utcOffset>870)throw new Error("CronOptions: utcOffset out of bounds.");if(i.utcOffset!==void 0&&i.timezone)throw new Error("CronOptions: Combining 'utcOffset' with 'timezone' is not allowed.")}if(i.unref!==!0&&i.unref!==!1)throw new Error("CronOptions: Unref should be either true, false or undefined(false).");return i}var ca=32,ms=31|ca,qi=[1,2,4,8,16];function Ge(i,t){this.pattern=i,this.timezone=t,this.second=Array(60).fill(0),this.minute=Array(60).fill(0),this.hour=Array(24).fill(0),this.day=Array(31).fill(0),this.month=Array(12).fill(0),this.dayOfWeek=Array(7).fill(0),this.lastDayOfMonth=!1,this.starDOM=!1,this.starDOW=!1,this.parse()}Ge.prototype.parse=function(){if(!(typeof this.pattern=="string"||this.pattern.constructor===String))throw new TypeError("CronPattern: Pattern has to be of type string.");this.pattern.indexOf("@")>=0&&(this.pattern=this.handleNicknames(this.pattern).trim());let i=this.pattern.replace(/\s+/g," ").split(" ");if(i.length<5||i.length>6)throw new TypeError("CronPattern: invalid configuration format ('"+this.pattern+"'), exactly five or six space separated parts are required.");if(i.length===5&&i.unshift("0"),i[3].indexOf("L")>=0&&(i[3]=i[3].replace("L",""),this.lastDayOfMonth=!0),i[3]=="*"&&(this.starDOM=!0),i[4].length>=3&&(i[4]=this.replaceAlphaMonths(i[4])),i[5].length>=3&&(i[5]=this.replaceAlphaDays(i[5])),i[5]=="*"&&(this.starDOW=!0),this.pattern.indexOf("?")>=0){let t=new _e(new Date,this.timezone).getDate(!0);i[0]=i[0].replace("?",t.getSeconds()),i[1]=i[1].replace("?",t.getMinutes()),i[2]=i[2].replace("?",t.getHours()),this.starDOM||(i[3]=i[3].replace("?",t.getDate())),i[4]=i[4].replace("?",t.getMonth()+1),this.starDOW||(i[5]=i[5].replace("?",t.getDay()))}this.throwAtIllegalCharacters(i),this.partToArray("second",i[0],0,1),this.partToArray("minute",i[1],0,1),this.partToArray("hour",i[2],0,1),this.partToArray("day",i[3],-1,1),this.partToArray("month",i[4],-1,1),this.partToArray("dayOfWeek",i[5],0,ms),this.dayOfWeek[7]&&(this.dayOfWeek[0]=this.dayOfWeek[7])};Ge.prototype.partToArray=function(i,t,e,s){let n=this[i],a=i==="day"&&this.lastDayOfMonth;if(t===""&&!a)throw new TypeError("CronPattern: configuration entry "+i+" ("+t+") is empty, check for trailing spaces.");if(t==="*")return n.fill(s);let r=t.split(",");if(r.length>1)for(let o=0;o6)&&t!=="L")throw new RangeError("CronPattern: Invalid value for dayOfWeek: "+t);this.setNthWeekdayOfMonth(t,e);return}if(i==="second"||i==="minute"){if(t<0||t>=60)throw new RangeError("CronPattern: Invalid value for "+i+": "+t)}else if(i==="hour"){if(t<0||t>=24)throw new RangeError("CronPattern: Invalid value for "+i+": "+t)}else if(i==="day"){if(t<0||t>=31)throw new RangeError("CronPattern: Invalid value for "+i+": "+t)}else if(i==="month"&&(t<0||t>=12))throw new RangeError("CronPattern: Invalid value for "+i+": "+t);this[i][t]=e};Ge.prototype.handleRangeWithStepping=function(i,t,e,s){let n=this.extractNth(i,t),a=n[0].match(/^(\d+)-(\d+)\/(\d+)$/);if(a===null)throw new TypeError("CronPattern: Syntax error, illegal range with stepping: '"+i+"'");let[,r,o,c]=a;if(r=parseInt(r,10)+e,o=parseInt(o,10)+e,c=parseInt(c,10),isNaN(r))throw new TypeError("CronPattern: Syntax error, illegal lower range (NaN)");if(isNaN(o))throw new TypeError("CronPattern: Syntax error, illegal upper range (NaN)");if(isNaN(c))throw new TypeError("CronPattern: Syntax error, illegal stepping: (NaN)");if(c===0)throw new TypeError("CronPattern: Syntax error, illegal stepping: 0");if(c>this[t].length)throw new TypeError("CronPattern: Syntax error, steps cannot be greater than maximum value of part ("+this[t].length+")");if(r>o)throw new TypeError("CronPattern: From value is larger than to value: '"+i+"'");for(let l=r;l<=o;l+=c)this.setPart(t,l,n[1]||s)};Ge.prototype.extractNth=function(i,t){let e=i,s;if(e.includes("#")){if(t!=="dayOfWeek")throw new Error("CronPattern: nth (#) only allowed in day-of-week field");s=e.split("#")[1],e=e.split("#")[0]}return[e,s]};Ge.prototype.handleRange=function(i,t,e,s){let n=this.extractNth(i,t),a=n[0].split("-");if(a.length!==2)throw new TypeError("CronPattern: Syntax error, illegal range: '"+i+"'");let r=parseInt(a[0],10)+e,o=parseInt(a[1],10)+e;if(isNaN(r))throw new TypeError("CronPattern: Syntax error, illegal lower range (NaN)");if(isNaN(o))throw new TypeError("CronPattern: Syntax error, illegal upper range (NaN)");if(r>o)throw new TypeError("CronPattern: From value is larger than to value: '"+i+"'");for(let c=r;c<=o;c++)this.setPart(t,c,n[1]||s)};Ge.prototype.handleStepping=function(i,t,e,s){let n=this.extractNth(i,t),a=n[0].split("/");if(a.length!==2)throw new TypeError("CronPattern: Syntax error, illegal stepping: '"+i+"'");let r=0;a[0]!=="*"&&(r=parseInt(a[0],10)+e);let o=parseInt(a[1],10);if(isNaN(o))throw new TypeError("CronPattern: Syntax error, illegal stepping: (NaN)");if(o===0)throw new TypeError("CronPattern: Syntax error, illegal stepping: 0");if(o>this[t].length)throw new TypeError("CronPattern: Syntax error, max steps for part is ("+this[t].length+")");for(let c=r;c0)this.dayOfWeek[i]=this.dayOfWeek[i]|qi[t-1];else if(t===ms)this.dayOfWeek[i]=ms;else throw new TypeError(`CronPattern: nth weekday of of range, should be 1-5 or L. Value: ${t}`)};var zi=[31,28,31,30,31,30,31,31,30,31,30,31],ft=[["month","year",0],["day","month",-1],["hour","day",0],["minute","hour",0],["second","minute",0]];function _e(i,t){if(this.tz=t,i&&i instanceof Date)if(!isNaN(i))this.fromDate(i);else throw new TypeError("CronDate: Invalid date passed to CronDate constructor");else if(i===void 0)this.fromDate(new Date);else if(i&&typeof i=="string")this.fromString(i);else if(i instanceof _e)this.fromCronDate(i);else throw new TypeError("CronDate: Invalid type ("+typeof i+") passed to CronDate constructor")}_e.prototype.isNthWeekdayOfMonth=function(i,t,e,s){let a=new Date(Date.UTC(i,t,e)).getUTCDay(),r=0;for(let o=1;o<=e;o++)new Date(Date.UTC(i,t,o)).getUTCDay()===a&&r++;if(s&ms&&qi[r-1]&s)return!0;if(s&ca){let o=new Date(Date.UTC(i,t+1,0)).getUTCDate();for(let c=e+1;c<=o;c++)if(new Date(Date.UTC(i,t,c)).getUTCDay()===a)return!1;return!0}return!1};_e.prototype.fromDate=function(i){if(this.tz!==void 0)if(typeof this.tz=="number")this.ms=i.getUTCMilliseconds(),this.second=i.getUTCSeconds(),this.minute=i.getUTCMinutes()+this.tz,this.hour=i.getUTCHours(),this.day=i.getUTCDate(),this.month=i.getUTCMonth(),this.year=i.getUTCFullYear(),this.apply();else{let t=He.toTZ(i,this.tz);this.ms=i.getMilliseconds(),this.second=t.s,this.minute=t.i,this.hour=t.h,this.day=t.d,this.month=t.m-1,this.year=t.y}else this.ms=i.getMilliseconds(),this.second=i.getSeconds(),this.minute=i.getMinutes(),this.hour=i.getHours(),this.day=i.getDate(),this.month=i.getMonth(),this.year=i.getFullYear()};_e.prototype.fromCronDate=function(i){this.tz=i.tz,this.year=i.year,this.month=i.month,this.day=i.day,this.hour=i.hour,this.minute=i.minute,this.second=i.second,this.ms=i.ms};_e.prototype.apply=function(){if(this.month>11||this.day>zi[this.month]||this.hour>59||this.minute>59||this.second>59||this.hour<0||this.minute<0||this.second<0){let i=new Date(Date.UTC(this.year,this.month,this.day,this.hour,this.minute,this.second,this.ms));return this.ms=i.getUTCMilliseconds(),this.second=i.getUTCSeconds(),this.minute=i.getUTCMinutes(),this.hour=i.getUTCHours(),this.day=i.getUTCDate(),this.month=i.getUTCMonth(),this.year=i.getUTCFullYear(),!0}else return!1};_e.prototype.fromString=function(i){if(typeof this.tz=="number"){let t=He.fromTZISO(i);this.ms=t.getUTCMilliseconds(),this.second=t.getUTCSeconds(),this.minute=t.getUTCMinutes(),this.hour=t.getUTCHours(),this.day=t.getUTCDate(),this.month=t.getUTCMonth(),this.year=t.getUTCFullYear(),this.apply()}else return this.fromDate(He.fromTZISO(i,this.tz))};_e.prototype.findNext=function(i,t,e,s){let n=this[t],a;e.lastDayOfMonth&&(this.month!==1?a=zi[this.month]:a=new Date(Date.UTC(this.year,this.month+1,0,0,0,0,0)).getUTCDate());let r=!e.starDOW&&t=="day"?new Date(Date.UTC(this.year,this.month,1,0,0,0,0)).getUTCDay():void 0;for(let o=this[t]+s;o1){let n=e+1;for(;n=ft.length?this:this.year>=3e3?null:this.recurse(i,t,e)};_e.prototype.increment=function(i,t,e){return this.second+=t.interval>1&&e?t.interval:1,this.ms=0,this.apply(),this.recurse(i,t,0)};_e.prototype.getDate=function(i){return i||this.tz===void 0?new Date(this.year,this.month,this.day,this.hour,this.minute,this.second,this.ms):typeof this.tz=="number"?new Date(Date.UTC(this.year,this.month,this.day,this.hour,this.minute-this.tz,this.second,this.ms)):He(this.year,this.month+1,this.day,this.hour,this.minute,this.second,this.tz)};_e.prototype.getTime=function(){return this.getDate().getTime()};function tn(i){return Object.prototype.toString.call(i)==="[object Function]"||typeof i=="function"||i instanceof Function}function Fl(i){typeof Deno<"u"&&typeof Deno.unrefTimer<"u"?Deno.unrefTimer(i):i&&typeof i.unref<"u"&&i.unref()}var Hi=30*1e3,fs=[];function pe(i,t,e){if(!(this instanceof pe))return new pe(i,t,e);let s,n;if(tn(t))n=t;else if(typeof t=="object")s=t;else if(t!==void 0)throw new Error("Cron: Invalid argument passed for optionsIn. Should be one of function, or object (options).");if(tn(e))n=e;else if(typeof e=="object")s=e;else if(e!==void 0)throw new Error("Cron: Invalid argument passed for funcIn. Should be one of function, or object (options).");if(this.name=s?s.name:void 0,this.options=Ll(s),this._states={kill:!1,blocking:!1,previousRun:void 0,currentRun:void 0,once:void 0,currentTimeout:void 0,maxRuns:s?s.maxRuns:void 0,paused:s?s.paused:!1,pattern:void 0},i&&(i instanceof Date||typeof i=="string"&&i.indexOf(":")>0)?this._states.once=new _e(i,this.options.timezone||this.options.utcOffset):this._states.pattern=new Ge(i,this.options.timezone),this.name){if(fs.find(r=>r.name===this.name))throw new Error("Cron: Tried to initialize new named job '"+this.name+"', but name already taken.");fs.push(this)}return n!==void 0&&(this.fn=n,this.schedule()),this}pe.prototype.nextRun=function(i){let t=this._next(i);return t?t.getDate():null};pe.prototype.nextRuns=function(i,t){i>this._states.maxRuns&&(i=this._states.maxRuns);let e=[],s=t||this._states.currentRun;for(;i--&&(s=this.nextRun(s));)e.push(s);return e};pe.prototype.getPattern=function(){return this._states.pattern?this._states.pattern.pattern:void 0};pe.prototype.isRunning=function(){let i=this.nextRun(this._states.currentRun),t=!this._states.paused,e=this.fn!==void 0,s=!this._states.kill;return t&&e&&s&&i!==null};pe.prototype.isStopped=function(){return this._states.kill};pe.prototype.isBusy=function(){return this._states.blocking};pe.prototype.currentRun=function(){return this._states.currentRun?this._states.currentRun.getDate():null};pe.prototype.previousRun=function(){return this._states.previousRun?this._states.previousRun.getDate():null};pe.prototype.msToNext=function(i){i=i||new Date;let t=this._next(i);return t?t.getTime()-i.getTime():null};pe.prototype.stop=function(){this._states.kill=!0,this._states.currentTimeout&&clearTimeout(this._states.currentTimeout);let i=fs.indexOf(this);i>=0&&fs.splice(i,1)};pe.prototype.pause=function(){return this._states.paused=!0,!this._states.kill};pe.prototype.resume=function(){return this._states.paused=!1,!this._states.kill};pe.prototype.schedule=function(i){if(i&&this.fn)throw new Error("Cron: It is not allowed to schedule two functions using the same Croner instance.");i&&(this.fn=i);let t=this.msToNext(),e=this.nextRun(this._states.currentRun);return t==null||isNaN(t)||e===null?this:(t>Hi&&(t=Hi),this._states.currentTimeout=setTimeout(()=>this._checkTrigger(e),t),this._states.currentTimeout&&this.options.unref&&Fl(this._states.currentTimeout),this)};pe.prototype._trigger=async function(i){if(this._states.blocking=!0,this._states.currentRun=new _e(void 0,this.options.timezone||this.options.utcOffset),this.options.catch)try{await this.fn(this,this.options.context)}catch(t){tn(this.options.catch)&&this.options.catch(t,this)}else await this.fn(this,this.options.context);this._states.previousRun=new _e(i,this.options.timezone||this.options.utcOffset),this._states.blocking=!1};pe.prototype.trigger=async function(){await this._trigger()};pe.prototype._checkTrigger=function(i){let t=new Date,e=!this._states.paused&&t.getTime()>=i,s=this._states.blocking&&this.options.protect;e&&!s?(this._states.maxRuns--,this._trigger()):e&&s&&tn(this.options.protect)&&setTimeout(()=>this.options.protect(this),0),this.schedule()};pe.prototype._next=function(i){let t=!!(i||this._states.currentRun),e=!1;!i&&this.options.startAt&&this.options.interval&&([i,t]=this._calculatePreviousRun(i,t),e=!i),i=new _e(i,this.options.timezone||this.options.utcOffset),this.options.startAt&&i&&i.getTime()=this.options.stopAt.getTime()?null:s};pe.prototype._calculatePreviousRun=function(i,t){let e=new _e(void 0,this.options.timezone||this.options.utcOffset);if(this.options.startAt.getTime()<=e.getTime()){i=this.options.startAt;let s=i.getTime()+this.options.interval*1e3;for(;s<=e.getTime();)i=new _e(i,this.options.timezone||this.options.utcOffset).increment(this._states.pattern,this.options,!0),s=i.getTime()+this.options.interval*1e3;t=!0}return[i,t]};pe.Cron=pe;pe.scheduledJobs=fs;var vr=require("obsidian");var dr=require("crypto");var Je=require("fs"),da=require("path");function Ol(i){return`mcp__${i.replace(/[\s.]+/g,"_")}`}function sn(i,t,e={}){let s=t.permissionRules.allow.length>0||t.permissionRules.deny.length>0,n=t.permissionMode?.trim(),a=!!n&&n!=="default",r=e.mcpAllowServers??[],o=r.length>0;if(!(s||a||o))return null;let l=(0,da.join)(i,".claude"),h=(0,da.join)(l,"settings.local.json");(0,Je.existsSync)(l)||(0,Je.mkdirSync)(l,{recursive:!0});let d=null;if((0,Je.existsSync)(h))try{d=(0,Je.readFileSync)(h,"utf-8")}catch{d=null}let u={};if(a&&(u.defaultMode=n),s||o){let p=[...t.permissionRules.allow];for(let m of r)p.push(Ol(m));u.permissions={allow:p,deny:t.permissionRules.deny}}return(0,Je.writeFileSync)(h,JSON.stringify(u,null,2)+` +`,"utf-8"),{path:h,backupContent:d}}function Rt(i){if(i)try{i.backupContent!==null?(0,Je.writeFileSync)(i.path,i.backupContent,"utf-8"):(0,Je.existsSync)(i.path)&&(0,Je.unlinkSync)(i.path)}catch{}}function Gi(i){if(typeof i=="string")return i;if(Array.isArray(i))return i.map(e=>{if(typeof e=="string")return e;if(e&&typeof e=="object"&&"text"in e){let s=e.text;if(typeof s=="string")return s}return""}).filter(Boolean).join(` +`);if(i&&typeof i=="object"){for(let t of["output","result","text","message"])if(t in i)return Gi(i[t])}}function ha(i,t=[]){if(Array.isArray(i)){for(let r of i)ha(r,t);return t}if(!i||typeof i!="object")return t;let e=i,s=typeof e.tool_name=="string"&&e.tool_name||typeof e.tool=="string"&&e.tool||typeof e.name=="string"&&e.name,n=typeof e.command=="string"?e.command:typeof e.input=="string"?e.input:typeof e.cmd=="string"?e.cmd:void 0,a=typeof e.reason=="string"?e.reason:void 0;s&&["tool_use","tool","name","tool_name"].some(r=>r in e)&&t.push({tool:s,command:n,reason:a});for(let r of Object.values(e))ha(r,t);return t}function Vi(i){if(!i||typeof i!="object")return;let t=i,e=t.usage;if(e&&typeof e=="object"){let n=typeof e.input_tokens=="number"?e.input_tokens:0,a=typeof e.output_tokens=="number"?e.output_tokens:0,r=typeof e.cache_creation_input_tokens=="number"?e.cache_creation_input_tokens:0,o=typeof e.cache_read_input_tokens=="number"?e.cache_read_input_tokens:0,c=n+a+r+o;if(c>0)return c}let s=t.modelUsage;if(s&&typeof s=="object"){let n=0;for(let a of Object.values(s)){if(!a||typeof a!="object")continue;let r=a;n+=typeof r.inputTokens=="number"?r.inputTokens:0,n+=typeof r.outputTokens=="number"?r.outputTokens:0,n+=typeof r.cacheReadInputTokens=="number"?r.cacheReadInputTokens:0,n+=typeof r.cacheCreationInputTokens=="number"?r.cacheCreationInputTokens:0}if(n>0)return n}for(let n of["tokens_used","total_tokens","totalTokens"])if(typeof t[n]=="number")return t[n];for(let n of Object.values(t)){let a=Vi(n);if(typeof a=="number")return a}}function Yi(i){if(!i||typeof i!="object")return;let t=i;if(typeof t.total_cost_usd=="number")return t.total_cost_usd;for(let e of Object.values(t)){let s=Yi(e);if(typeof s=="number")return s}}function ua(i){if(!i||typeof i!="object")return;let t=i;if(t.type==="result"&&typeof t.result=="string"&&t.result.trim())return t.result}function nn(i){if(!i||typeof i!="object")return;let t=i;if(t.modelUsage&&typeof t.modelUsage=="object"){let s=Object.keys(t.modelUsage);if(s.length>0&&s[0])return s[0]}let e=t.message;if(e&&typeof e.model=="string"&&e.model)return e.model;if(typeof t.model=="string"&&t.model)return t.model;for(let s of Object.values(t)){let n=nn(s);if(n)return n}}function Nl(i){return/^gpt-|codex/i.test(i.trim())}var Ki={id:"claude-code",label:"Claude Code",cliPath(i){return i.claudeCliPath},buildExec(i){let t=["-p",i.prompt,"--output-format",i.streaming?"stream-json":"json"];i.streaming&&t.push("--verbose");let e=i.modelSource==="settings"&&Nl(i.model);i.model&&!e&&t.push("--model",i.model),i.effort&&t.push("--effort",i.effort);let s=i.agent.permissionMode?.trim();return s&&s!=="default"?t.push("--permission-mode",s):t.push("--permission-mode","bypassPermissions"),Promise.resolve({cliPath:i.settings.claudeCliPath,args:t})},parseExecOutput(i,t,e){let s=i.trim(),n;if(e){let c=ye(s);for(let l=c.length-1;l>=0;l--){let h=c[l]?.trim();if(h)try{let d=JSON.parse(h);if(d&&typeof d=="object"){n=d;break}}catch{}}}else if(s.startsWith("{")||s.startsWith("["))try{n=JSON.parse(s)}catch{n=void 0}let a=Gi(n)??"";if(!a&&e){let c=[];for(let l of ye(s)){let h=l.trim();if(h)try{let d=JSON.parse(h);if(d.type==="assistant"&&d.message?.content)for(let u of d.message.content)u.type==="text"&&u.text&&c.push(u.text);else d.type==="result"&&typeof d.result=="string"&&c.push(d.result)}catch{}}a=c.join(` +`).trim()}a||(a=t.trim()||"(no output)");let r=nn(n),o=ua(n);if((!r||!o)&&e)for(let c of ye(s)){let l=c.trim();if(l)try{let h=JSON.parse(l);if(!r){let d=nn(h);d&&(r=d)}if(!o){let d=ua(h);d&&(o=d)}if(r&&o)break}catch{}}return{outputText:a,finalResult:o,tokensUsed:Vi(n),costUsd:Yi(n),toolsUsed:ha(n),concreteModel:r,rawJson:n}},extractStreamChunk(i){let t=i.trim();if(!t)return null;let e;try{e=JSON.parse(t)}catch{return null}let s=e.type;if(s==="assistant"){let n=e.message;if(n?.content&&Array.isArray(n.content)){let a=[];for(let r of n.content)if(r.type==="text"&&typeof r.text=="string")a.push(r.text);else if(r.type==="tool_use"){let o=String(r.name??"tool"),c=r.input,l=c?.command??c?.content??"";a.push(` \u25B8 ${o}${l?`: ${String(l).slice(0,200)}`:""} `)}if(a.length>0)return a.join("")}}if(s==="result"){let n=typeof e.result=="string"?e.result:null;if(n)return` -${n}`}return null},setupPermissions(i,t,e,s){let n=en(i,t,{mcpAllowServers:s?.mcpAllowServers});return n?Promise.resolve({restore:()=>Et(n)}):Promise.resolve(null)}};var le=require("fs"),ha=require("crypto"),fs=require("os"),Ye=require("path");var Sl=/^[A-Z][A-Za-z]*$/;function Cl(i){let t=i.trim();if(!t)return{error:"empty Bash() matches every command \u2014 use the read-only sandbox instead"};let e=t.split(/\s+/).filter(Boolean),s=[];for(let n=0;nJSON.stringify(s)).join(", ")}], decision="${t}", justification="agent-fleet")`}function _l(i){let t=[],e=[],s=(a,r)=>{let o=a.trim();if(!o||o.startsWith("mcp__"))return;let c=o.match(/^Bash\((.*)\)$/s);if(!c){e.push({rule:o,reason:Sl.test(o)?"tool-name rules are governed by the sandbox, not execpolicy":"not a Bash(...) command pattern"});return}let l=Cl(c[1]);if("error"in l){e.push({rule:o,reason:l.error});return}t.push(Tl(l.tokens,r))};for(let a of i.permissionRules.deny)s(a,"forbidden");for(let a of i.permissionRules.allow)s(a,"allow");return t.length===0?{rulesText:null,dropped:e,emitted:0}:{rulesText:`${`# Generated by Agent Fleet for agent "${i.name}". Do not edit \u2014 regenerated each run. +${n}`}return null},setupPermissions(i,t,e,s){let n=sn(i,t,{mcpAllowServers:s?.mcpAllowServers});return n?Promise.resolve({restore:()=>Rt(n)}):Promise.resolve(null)}};var le=require("fs"),fa=require("crypto"),ys=require("os"),Xe=require("path");var Bl=/^[A-Z][A-Za-z]*$/;function Ul(i){let t=i.trim();if(!t)return{error:"empty Bash() matches every command \u2014 use the read-only sandbox instead"};let e=t.split(/\s+/).filter(Boolean),s=[];for(let n=0;nJSON.stringify(s)).join(", ")}], decision="${t}", justification="agent-fleet")`}function jl(i){let t=[],e=[],s=(a,r)=>{let o=a.trim();if(!o||o.startsWith("mcp__"))return;let c=o.match(/^Bash\((.*)\)$/s);if(!c){e.push({rule:o,reason:Bl.test(o)?"tool-name rules are governed by the sandbox, not execpolicy":"not a Bash(...) command pattern"});return}let l=Ul(c[1]);if("error"in l){e.push({rule:o,reason:l.error});return}t.push($l(l.tokens,r))};for(let a of i.permissionRules.deny)s(a,"forbidden");for(let a of i.permissionRules.allow)s(a,"allow");return t.length===0?{rulesText:null,dropped:e,emitted:0}:{rulesText:`${`# Generated by Agent Fleet for agent "${i.name}". Do not edit \u2014 regenerated each run. # Source: this agent's permissionRules (allow/deny), translated to Codex execpolicy. `}${t.join(` `)} -`,dropped:e,emitted:t.length}}function Al(){let i=process.env.CODEX_HOME?.trim();return i||(0,Ye.join)((0,fs.homedir)(),".codex")}var Wi=(0,Ye.join)((0,fs.tmpdir)(),"agent-fleet-codex");function El(i){let t=i.name.replace(/[^A-Za-z0-9_-]+/g,"-").replace(/^-+|-+$/g,"")||"agent",e=(0,ha.createHash)("sha1").update(i.filePath).digest("hex").slice(0,8);return(0,Ye.join)(Wi,`${t}-${e}`)}function Pl(i,t){let e=`${i}.${process.pid}.${Date.now()}.tmp`;(0,le.writeFileSync)(e,t,"utf-8"),(0,le.renameSync)(e,i)}function Rl(i,t){try{let e=(0,le.lstatSync)(t);if(e.isSymbolicLink()){try{if((0,le.readlinkSync)(t)===i)return}catch{}(0,le.unlinkSync)(t)}else e.isDirectory()?(0,le.rmSync)(t,{recursive:!0,force:!0}):(0,le.unlinkSync)(t)}catch{}(0,le.symlinkSync)(i,t)}function Dl(i,t,e=Al()){if(!(0,le.existsSync)(e))return null;let s=El(i);(0,le.mkdirSync)(s,{recursive:!0});for(let o of(0,le.readdirSync)(e))o!=="rules"&&Rl((0,Ye.join)(e,o),(0,Ye.join)(s,o));let n=(0,Ye.join)(s,"rules");(0,le.mkdirSync)(n,{recursive:!0});let a=(0,Ye.join)(e,"rules");if((0,le.existsSync)(a)){for(let o of(0,le.readdirSync)(a))if(o.endsWith(".rules"))try{(0,le.copyFileSync)((0,Ye.join)(a,o),(0,Ye.join)(n,`user-${o}`))}catch{}}let r=(0,Ye.join)(n,"agent-fleet.rules");return Pl(r,t),{codexHome:s,rulesPath:r}}function Hi(){try{(0,le.rmSync)(Wi,{recursive:!0,force:!0})}catch{}}var ca=new Map,da=new Map;function qi(){ca.clear(),da.clear()}function zi(i,t,e){return new Promise(s=>{let n=!1,a=r=>{n||(n=!0,s(r))};try{let r=lt(i,["execpolicy","check","--rules",t,...e]),o=window.setTimeout(()=>{try{r.kill()}catch{}a(null)},8e3);r.on("error",()=>{window.clearTimeout(o),a(null)}),r.on("close",c=>{window.clearTimeout(o),a(c)})}catch{a(null)}})}function Il(i){let t=ca.get(i);if(t)return t;let e=(async()=>{let s=null;try{s=(0,le.mkdtempSync)((0,Ye.join)((0,fs.tmpdir)(),"af-codex-probe-"));let n=(0,Ye.join)(s,"probe.rules");return(0,le.writeFileSync)(n,`prefix_rule(pattern=["ls"], decision="allow") -`,"utf-8"),await zi(i,n,["ls"])===0}catch{return!1}finally{if(s)try{(0,le.rmSync)(s,{recursive:!0,force:!0})}catch{}}})();return ca.set(i,e),e}async function Ml(i,t,e){let s=`${i}:${(0,ha.createHash)("sha1").update(e).digest("hex")}`,n=da.get(s);if(n!==void 0)return n;let r=await zi(i,t,["true"])===0;return da.set(s,r),r}var ji=new Set;function ms(i,t){ji.has(i)||(ji.add(i),console.warn(`Agent Fleet: ${t}`))}async function Gi(i,t){if(!(i.permissionRules.allow.length>0||i.permissionRules.deny.length>0))return null;let{rulesText:s,dropped:n}=_l(i);if(n.length>0&&ms(`dropped:${i.name}:${n.map(l=>l.rule).join("|")}`,`agent "${i.name}": ${n.length} permission rule(s) can't be enforced by Codex and were ignored \u2014 ${n.map(l=>`"${l.rule}" (${l.reason})`).join("; ")}. File/network access is governed by the sandbox (Permission Mode).`),!s)return null;let a=t.codexCliPath;if(!await Il(a))return ms(`unsupported:${a}`,`this Codex build doesn't support execpolicy rules; agent "${i.name}" runs with sandbox-only enforcement. Update Codex to enforce command allow/deny rules.`),null;let o;try{o=Dl(i,s)}catch(l){return ms(`overlay-fail:${i.name}`,`couldn't build the Codex permission overlay for agent "${i.name}" (${l instanceof Error?l.message:String(l)}); falling back to sandbox-only (symlinks may be unavailable on this platform).`),null}return o?await Ml(a,o.rulesPath,s)?{restore:()=>{},env:{CODEX_HOME:o.codexHome}}:(ms(`invalid-rules:${i.name}`,`generated Codex rules for agent "${i.name}" failed validation; falling back to sandbox-only.`),null):(ms("no-codex-home",`Codex home (~/.codex) not found; agent "${i.name}" runs with sandbox-only enforcement. Run \`codex login\` first.`),null)}function Ll(i){let t=i.trim();return/^(opus|sonnet|haiku|opusplan)$/i.test(t)||/claude|anthropic/i.test(t)}function Fl(i){let t=i.trim().toLowerCase();return t?t==="max"?"xhigh":["low","medium","high","xhigh"].includes(t)?t:"":""}function Ol(i){switch((i??"").trim()){case"plan":case"read-only":return["--sandbox","read-only"];case"acceptEdits":case"default":case"workspace-write":return["--sandbox","workspace-write"];case"danger-full-access":return["--sandbox","danger-full-access"];default:return["--dangerously-bypass-approvals-and-sandbox"]}}function Nl(i){let t=["exec","--json","--skip-git-repo-check"],e=i.modelSource==="settings"&&Ll(i.model);i.model&&!e&&t.push("-m",i.model);let s=Fl(i.effort);return s&&t.push("-c",`model_reasoning_effort="${s}"`),t.push(...Ol(i.agent.permissionMode)),i.resumeSessionId&&t.push("resume",i.resumeSessionId),t.push("-"),{args:t,stdinPayload:i.prompt}}function Vi(i){let t=i.trim();if(!t)return null;try{let e=JSON.parse(t);return e&&typeof e=="object"&&!Array.isArray(e)?e:null}catch{return null}}function sn(i){let t=i.item;return t&&typeof t=="object"?t:null}function ua(i){let t=typeof i.type=="string"?i.type:"";if(t==="command_execution")return{tool:"shell",command:typeof i.command=="string"?i.command:void 0};if(t==="mcp_tool_call"){let e=typeof i.server=="string"?i.server:"mcp",s=typeof i.tool=="string"?i.tool:"tool";return{tool:`mcp__${e}__${s}`}}return t==="web_search"?{tool:"web_search",command:typeof i.query=="string"?i.query:void 0}:t==="file_change"?{tool:"file_change",command:(Array.isArray(i.changes)?i.changes:[]).map(n=>`${typeof n.kind=="string"?n.kind:"update"} ${typeof n.path=="string"?n.path:""}`.trim()).filter(Boolean).join(", ")||void 0}:null}function Yi(i){let t=i.usage;if(!t||typeof t!="object")return 0;let e=typeof t.input_tokens=="number"?t.input_tokens:0,s=typeof t.output_tokens=="number"?t.output_tokens:0;return e+s}function Bl(i){let t=i.usage;return!t||typeof t!="object"?0:typeof t.input_tokens=="number"?t.input_tokens:0}function pa(){return{emittedTextLengths:new Map}}function Ki(i,t){let e=typeof i.type=="string"?i.type:"",s=[];if(e==="thread.started"&&typeof i.thread_id=="string"&&i.thread_id)return s.push({kind:"session",sessionId:i.thread_id}),s;if(e==="turn.completed")return s.push({kind:"usage",contextTokens:Bl(i),totalTokens:Yi(i)}),s;if(e==="turn.failed"){let n=i.error,a=n&&typeof n.message=="string"?n.message:"turn failed";return s.push({kind:"turn-failed",message:a}),s}if(e==="error"){let n=typeof i.message=="string"?i.message:"stream error";return s.push({kind:"error",message:n}),s}if(e==="item.started"||e==="item.updated"||e==="item.completed"){let n=sn(i);if(!n)return s;let a=typeof n.type=="string"?n.type:"";if(a==="agent_message"){let r=typeof n.text=="string"?n.text:"",o=typeof n.id=="string"?n.id:"__single__",c=t.emittedTextLengths.get(o)??0;return r.length>c&&(s.push({kind:"text",text:r.slice(c)}),t.emittedTextLengths.set(o,r.length)),s}if(a==="error"){let r=typeof n.message=="string"?n.message:"item error";return s.push({kind:"error",message:r}),s}if(e==="item.started"){let r=ua(n);r&&s.push({kind:"tool",toolName:r.tool,command:r.command})}return s}return s}var gs={id:"codex",label:"Codex",cliPath(i){return i.codexCliPath},buildExec(i){let{args:t,stdinPayload:e}=Nl(i);return Promise.resolve({cliPath:i.settings.codexCliPath,args:t,stdinPayload:e})},parseExecOutput(i,t,e){let s=[],n=[],a=[],r=0,o,c;for(let d of ge(i)){let u=Vi(d);if(!u)continue;let p=typeof u.type=="string"?u.type:"";if(p==="thread.started"&&typeof u.thread_id=="string")o=u.thread_id;else if(p==="turn.completed")r+=Yi(u),c=u;else if(p==="turn.failed"){let m=u.error;a.push(m&&typeof m.message=="string"?m.message:"turn failed")}else if(p==="error")a.push(typeof u.message=="string"?u.message:"stream error");else if(p==="item.completed"){let m=sn(u);if(!m)continue;let f=typeof m.type=="string"?m.type:"";if(f==="agent_message"&&typeof m.text=="string"&&m.text.trim())s.push(m.text);else if(f==="error"&&typeof m.message=="string")a.push(m.message);else{let g=ua(m);g&&n.push(g)}}}let l=s.join(` +`,dropped:e,emitted:t.length}}function Wl(){let i=process.env.CODEX_HOME?.trim();return i||(0,Xe.join)((0,ys.homedir)(),".codex")}var Xi=(0,Xe.join)((0,ys.tmpdir)(),"agent-fleet-codex");function Hl(i){let t=i.name.replace(/[^A-Za-z0-9_-]+/g,"-").replace(/^-+|-+$/g,"")||"agent",e=(0,fa.createHash)("sha1").update(i.filePath).digest("hex").slice(0,8);return(0,Xe.join)(Xi,`${t}-${e}`)}function ql(i,t){let e=`${i}.${process.pid}.${Date.now()}.tmp`;(0,le.writeFileSync)(e,t,"utf-8"),(0,le.renameSync)(e,i)}function zl(i,t){try{let e=(0,le.lstatSync)(t);if(e.isSymbolicLink()){try{if((0,le.readlinkSync)(t)===i)return}catch{}(0,le.unlinkSync)(t)}else e.isDirectory()?(0,le.rmSync)(t,{recursive:!0,force:!0}):(0,le.unlinkSync)(t)}catch{}(0,le.symlinkSync)(i,t)}function Gl(i,t,e=Wl()){if(!(0,le.existsSync)(e))return null;let s=Hl(i);(0,le.mkdirSync)(s,{recursive:!0});for(let o of(0,le.readdirSync)(e))o!=="rules"&&zl((0,Xe.join)(e,o),(0,Xe.join)(s,o));let n=(0,Xe.join)(s,"rules");(0,le.mkdirSync)(n,{recursive:!0});let a=(0,Xe.join)(e,"rules");if((0,le.existsSync)(a)){for(let o of(0,le.readdirSync)(a))if(o.endsWith(".rules"))try{(0,le.copyFileSync)((0,Xe.join)(a,o),(0,Xe.join)(n,`user-${o}`))}catch{}}let r=(0,Xe.join)(n,"agent-fleet.rules");return ql(r,t),{codexHome:s,rulesPath:r}}function Qi(){try{(0,le.rmSync)(Xi,{recursive:!0,force:!0})}catch{}}var pa=new Map,ma=new Map;function Zi(){pa.clear(),ma.clear()}function er(i,t,e){return new Promise(s=>{let n=!1,a=r=>{n||(n=!0,s(r))};try{let r=dt(i,["execpolicy","check","--rules",t,...e]),o=window.setTimeout(()=>{try{r.kill()}catch{}a(null)},8e3);r.on("error",()=>{window.clearTimeout(o),a(null)}),r.on("close",c=>{window.clearTimeout(o),a(c)})}catch{a(null)}})}function Vl(i){let t=pa.get(i);if(t)return t;let e=(async()=>{let s=null;try{s=(0,le.mkdtempSync)((0,Xe.join)((0,ys.tmpdir)(),"af-codex-probe-"));let n=(0,Xe.join)(s,"probe.rules");return(0,le.writeFileSync)(n,`prefix_rule(pattern=["ls"], decision="allow") +`,"utf-8"),await er(i,n,["ls"])===0}catch{return!1}finally{if(s)try{(0,le.rmSync)(s,{recursive:!0,force:!0})}catch{}}})();return pa.set(i,e),e}async function Yl(i,t,e){let s=`${i}:${(0,fa.createHash)("sha1").update(e).digest("hex")}`,n=ma.get(s);if(n!==void 0)return n;let r=await er(i,t,["true"])===0;return ma.set(s,r),r}var Ji=new Set;function gs(i,t){Ji.has(i)||(Ji.add(i),console.warn(`Agent Fleet: ${t}`))}async function tr(i,t){if(!(i.permissionRules.allow.length>0||i.permissionRules.deny.length>0))return null;let{rulesText:s,dropped:n}=jl(i);if(n.length>0&&gs(`dropped:${i.name}:${n.map(l=>l.rule).join("|")}`,`agent "${i.name}": ${n.length} permission rule(s) can't be enforced by Codex and were ignored \u2014 ${n.map(l=>`"${l.rule}" (${l.reason})`).join("; ")}. File/network access is governed by the sandbox (Permission Mode).`),!s)return null;let a=t.codexCliPath;if(!await Vl(a))return gs(`unsupported:${a}`,`this Codex build doesn't support execpolicy rules; agent "${i.name}" runs with sandbox-only enforcement. Update Codex to enforce command allow/deny rules.`),null;let o;try{o=Gl(i,s)}catch(l){return gs(`overlay-fail:${i.name}`,`couldn't build the Codex permission overlay for agent "${i.name}" (${l instanceof Error?l.message:String(l)}); falling back to sandbox-only (symlinks may be unavailable on this platform).`),null}return o?await Yl(a,o.rulesPath,s)?{restore:()=>{},env:{CODEX_HOME:o.codexHome}}:(gs(`invalid-rules:${i.name}`,`generated Codex rules for agent "${i.name}" failed validation; falling back to sandbox-only.`),null):(gs("no-codex-home",`Codex home (~/.codex) not found; agent "${i.name}" runs with sandbox-only enforcement. Run \`codex login\` first.`),null)}function Kl(i){let t=i.trim();return/^(opus|sonnet|haiku|opusplan)$/i.test(t)||/claude|anthropic/i.test(t)}function Jl(i){let t=i.trim().toLowerCase();return t?t==="max"?"xhigh":["low","medium","high","xhigh"].includes(t)?t:"":""}function Xl(i){switch((i??"").trim()){case"plan":case"read-only":return["--sandbox","read-only"];case"acceptEdits":case"default":case"workspace-write":return["--sandbox","workspace-write"];case"danger-full-access":return["--sandbox","danger-full-access"];default:return["--dangerously-bypass-approvals-and-sandbox"]}}function Ql(i){let t=["exec","--json","--skip-git-repo-check"],e=i.modelSource==="settings"&&Kl(i.model);i.model&&!e&&t.push("-m",i.model);let s=Jl(i.effort);return s&&t.push("-c",`model_reasoning_effort="${s}"`),t.push(...Xl(i.agent.permissionMode)),i.resumeSessionId&&t.push("resume",i.resumeSessionId),t.push("-"),{args:t,stdinPayload:i.prompt}}function sr(i){let t=i.trim();if(!t)return null;try{let e=JSON.parse(t);return e&&typeof e=="object"&&!Array.isArray(e)?e:null}catch{return null}}function an(i){let t=i.item;return t&&typeof t=="object"?t:null}function ga(i){let t=typeof i.type=="string"?i.type:"";if(t==="command_execution")return{tool:"shell",command:typeof i.command=="string"?i.command:void 0};if(t==="mcp_tool_call"){let e=typeof i.server=="string"?i.server:"mcp",s=typeof i.tool=="string"?i.tool:"tool";return{tool:`mcp__${e}__${s}`}}return t==="web_search"?{tool:"web_search",command:typeof i.query=="string"?i.query:void 0}:t==="file_change"?{tool:"file_change",command:(Array.isArray(i.changes)?i.changes:[]).map(n=>`${typeof n.kind=="string"?n.kind:"update"} ${typeof n.path=="string"?n.path:""}`.trim()).filter(Boolean).join(", ")||void 0}:null}function nr(i){let t=i.usage;if(!t||typeof t!="object")return 0;let e=typeof t.input_tokens=="number"?t.input_tokens:0,s=typeof t.output_tokens=="number"?t.output_tokens:0;return e+s}function Zl(i){let t=i.usage;return!t||typeof t!="object"?0:typeof t.input_tokens=="number"?t.input_tokens:0}function ya(){return{emittedTextLengths:new Map}}function ar(i,t){let e=typeof i.type=="string"?i.type:"",s=[];if(e==="thread.started"&&typeof i.thread_id=="string"&&i.thread_id)return s.push({kind:"session",sessionId:i.thread_id}),s;if(e==="turn.completed")return s.push({kind:"usage",contextTokens:Zl(i),totalTokens:nr(i)}),s;if(e==="turn.failed"){let n=i.error,a=n&&typeof n.message=="string"?n.message:"turn failed";return s.push({kind:"turn-failed",message:a}),s}if(e==="error"){let n=typeof i.message=="string"?i.message:"stream error";return s.push({kind:"error",message:n}),s}if(e==="item.started"||e==="item.updated"||e==="item.completed"){let n=an(i);if(!n)return s;let a=typeof n.type=="string"?n.type:"";if(a==="agent_message"){let r=typeof n.text=="string"?n.text:"",o=typeof n.id=="string"?n.id:"__single__",c=t.emittedTextLengths.get(o)??0;return r.length>c&&(s.push({kind:"text",text:r.slice(c)}),t.emittedTextLengths.set(o,r.length)),s}if(a==="error"){let r=typeof n.message=="string"?n.message:"item error";return s.push({kind:"error",message:r}),s}if(e==="item.started"){let r=ga(n);r&&s.push({kind:"tool",toolName:r.tool,command:r.command})}return s}return s}var vs={id:"codex",label:"Codex",cliPath(i){return i.codexCliPath},buildExec(i){let{args:t,stdinPayload:e}=Ql(i);return Promise.resolve({cliPath:i.settings.codexCliPath,args:t,stdinPayload:e})},parseExecOutput(i,t,e){let s=[],n=[],a=[],r=0,o,c;for(let d of ye(i)){let u=sr(d);if(!u)continue;let p=typeof u.type=="string"?u.type:"";if(p==="thread.started"&&typeof u.thread_id=="string")o=u.thread_id;else if(p==="turn.completed")r+=nr(u),c=u;else if(p==="turn.failed"){let m=u.error;a.push(m&&typeof m.message=="string"?m.message:"turn failed")}else if(p==="error")a.push(typeof u.message=="string"?u.message:"stream error");else if(p==="item.completed"){let m=an(u);if(!m)continue;let f=typeof m.type=="string"?m.type:"";if(f==="agent_message"&&typeof m.text=="string"&&m.text.trim())s.push(m.text);else if(f==="error"&&typeof m.message=="string")a.push(m.message);else{let g=ga(m);g&&n.push(g)}}}let l=s.join(` `).trim();l||(l=a.join(` -`).trim()),l||(l=t.trim()||"(no output)");let h=s[s.length-1];return{outputText:l,finalResult:h?.trim()?h:void 0,tokensUsed:r>0?r:void 0,costUsd:void 0,toolsUsed:n,concreteModel:void 0,rawJson:c,sessionId:o}},extractStreamChunk(i){let t=Vi(i);if(!t)return null;let e=typeof t.type=="string"?t.type:"";if(e==="item.completed"){let s=sn(t);return s&&s.type==="agent_message"&&typeof s.text=="string"&&s.text.trim()?s.text:null}if(e==="item.started"){let s=sn(t);if(!s)return null;let n=ua(s);if(n){let a=n.command?`: ${n.command.slice(0,200)}`:"";return` +`).trim()),l||(l=t.trim()||"(no output)");let h=s[s.length-1];return{outputText:l,finalResult:h?.trim()?h:void 0,tokensUsed:r>0?r:void 0,costUsd:void 0,toolsUsed:n,concreteModel:void 0,rawJson:c,sessionId:o}},extractStreamChunk(i){let t=sr(i);if(!t)return null;let e=typeof t.type=="string"?t.type:"";if(e==="item.completed"){let s=an(t);return s&&s.type==="agent_message"&&typeof s.text=="string"&&s.text.trim()?s.text:null}if(e==="item.started"){let s=an(t);if(!s)return null;let n=ga(s);if(n){let a=n.command?`: ${n.command.slice(0,200)}`:"";return` \u25B8 ${n.tool}${a} `}return null}if(e==="turn.failed"){let s=t.error;return` \u2716 ${s&&typeof s.message=="string"?s.message:"turn failed"} -`}return null},setupPermissions(i,t,e,s){return Gi(t,e)}};function wt(i){let t=(i??"").trim().toLowerCase();return t==="codex"||t==="openai-codex"?"codex":"claude-code"}function Ji(i){return wt(i)==="codex"?gs:$i}var Xi=new Set(["","default","subscription"]);function Pt(i,t,e){let s=ma(i?.model);if(s)return{value:fa(s),source:"task"};let n=ma(t.model);if(n)return{value:fa(n),source:"agent"};let a=ma(e.defaultModel);return a?{value:fa(a),source:"settings"}:{value:"",source:"cli-default"}}function ys(i){return!Xi.has(i.trim())}function ma(i){if(!i)return"";let t=i.trim();return t||""}function fa(i){return Xi.has(i)?"":i}function nn(i,t){let e=i.wikiReferences;if(!e||e.length===0)return null;let s=[];for(let a of e){let r=t.getAgentByName(a.agent);if(!r||!r.wikiKeeper)continue;let o=r.wikiKeeper,c=o.scopeRoot?`${o.scopeRoot.replace(/\/+$/,"")}/`:"";s.push({agentName:r.name,scopeRoot:o.scopeRoot||"(whole vault)",topicsPath:`${c}${o.topicsRoot}`,inboxPath:`${c}${o.inboxPath}`,indexPath:`${c}${o.indexPath}`})}if(s.length===0)return null;let n=["## Wiki Access"];n.push("You have read access to the following wikis maintained by other agents. Use the `wiki-query` skill in consumer mode when the user asks something a wiki may already cover."),n.push("");for(let a of s)n.push(`### Wiki: \`${a.agentName}\``),n.push(`- scope root: \`${a.scopeRoot}\``),n.push(`- topics: \`${a.topicsPath}/\``),n.push(`- index: \`${a.indexPath}\``),n.push(`- inbox: \`${a.inboxPath}/\``),n.push("");return n.push("### Rules"),n.push("- **Cite every factual claim** from a wiki using `[[/]]`. If a claim has no page, say the wiki doesn't cover it \u2014 do not fabricate."),n.push("- **When the user shares something durable** that isn't yet in a wiki (a decision, a new entity mention, a competitor change, a meeting outcome), write a short markdown file to the relevant wiki's inbox at `/YYYY-MM-DD-.md` with a one-line note + the source. The wiki keeper files it canonically on its next ingest."),n.push("- **Do NOT write to `/` directly.** The wiki keeper is the canonical curator of topic pages. Use the inbox as your deposit point."),n.push("- **When the question spans multiple wikis**, be explicit about which scope each cited page belongs to."),n.join(` -`)}var ct=require("fs"),rn=require("path");var an=require("fs"),Ul=require("path");var ga="remember",Fh=`mcp__${ga}`,Qi=String.raw` +`}return null},setupPermissions(i,t,e,s){return tr(t,e)}};function kt(i){let t=(i??"").trim().toLowerCase();return t==="codex"||t==="openai-codex"?"codex":"claude-code"}function ir(i){return kt(i)==="codex"?vs:Ki}var rr=new Set(["","default","subscription"]);function Dt(i,t,e){let s=va(i?.model);if(s)return{value:wa(s),source:"task"};let n=va(t.model);if(n)return{value:wa(n),source:"agent"};let a=va(e.defaultModel);return a?{value:wa(a),source:"settings"}:{value:"",source:"cli-default"}}function ws(i){return!rr.has(i.trim())}function va(i){if(!i)return"";let t=i.trim();return t||""}function wa(i){return rr.has(i)?"":i}function rn(i,t){let e=i.wikiReferences;if(!e||e.length===0)return null;let s=[];for(let a of e){let r=t.getAgentByName(a.agent);if(!r||!r.wikiKeeper)continue;let o=r.wikiKeeper,c=o.scopeRoot?`${o.scopeRoot.replace(/\/+$/,"")}/`:"";s.push({agentName:r.name,scopeRoot:o.scopeRoot||"(whole vault)",topicsPath:`${c}${o.topicsRoot}`,inboxPath:`${c}${o.inboxPath}`,indexPath:`${c}${o.indexPath}`})}if(s.length===0)return null;let n=["## Wiki Access"];n.push("You have read access to the following wikis maintained by other agents. Use the `wiki-query` skill in consumer mode when the user asks something a wiki may already cover."),n.push("");for(let a of s)n.push(`### Wiki: \`${a.agentName}\``),n.push(`- scope root: \`${a.scopeRoot}\``),n.push(`- topics: \`${a.topicsPath}/\``),n.push(`- index: \`${a.indexPath}\``),n.push(`- inbox: \`${a.inboxPath}/\``),n.push("");return n.push("### Rules"),n.push("- **Cite every factual claim** from a wiki using `[[/]]`. If a claim has no page, say the wiki doesn't cover it \u2014 do not fabricate."),n.push("- **When the user shares something durable** that isn't yet in a wiki (a decision, a new entity mention, a competitor change, a meeting outcome), write a short markdown file to the relevant wiki's inbox at `/YYYY-MM-DD-.md` with a one-line note + the source. The wiki keeper files it canonically on its next ingest."),n.push("- **Do NOT write to `/` directly.** The wiki keeper is the canonical curator of topic pages. Use the inbox as your deposit point."),n.push("- **When the question spans multiple wikis**, be explicit about which scope each cited page belongs to."),n.join(` +`)}var ht=require("fs"),ln=require("path");var on=require("fs"),ec=require("path");var ba="remember",fu=`mcp__${ba}`,or=String.raw` const fs = require("fs"); const path = require("path"); const PENDING_DIR = process.env.AF_PENDING_DIR; @@ -11941,7 +11971,7 @@ function handle(req) { send({ jsonrpc: "2.0", id, error: { code: -32601, message: "method not found: " + method } }); } } -`;function Zi(i){let t=[];for(let e of i){let s=e.trim();if(s)try{let n=JSON.parse(s),a=typeof n.text=="string"?n.text.trim():"";if(!a)continue;let r=n.section==="Preferences"||n.section==="Procedures"||n.section==="Observations"?n.section:void 0;t.push({text:a,pinned:n.pinned===!0,section:r,source:typeof n.source=="string"?n.source:"mcp"})}catch{}}return t}var $l=0;function jl(i,t){return{def:{name:ga,type:"stdio",enabled:!0,command:"node",env:{AF_PENDING_DIR:i,AF_SOURCE:t},status:"connected",scope:"user",tools:[],toolDetails:[]},inlineScript:Qi}}function on(i){let t=i.agentGrants.map(n=>n.trim().toLowerCase()).filter(Boolean),e=t.length>0?new Set(t):null,s=[];for(let n of i.registry){if(!n.enabled||e&&!e.has(n.name.trim().toLowerCase())||n.type==="unknown")continue;let a={};if(n.type==="stdio"){if(n.envSecretKeys&&n.envSecretKeys.length>0){let r={};for(let o of n.envSecretKeys){let c=process.env[o];c&&(r[o]=c)}Object.keys(r).length>0&&(a.env=r)}}else if(n.auth!=="none"){let r=i.getBearerToken(n.name);r&&(a.bearerToken=r)}s.push({def:n,secrets:a})}return i.remember&&s.push(jl(i.remember.pendingDir,i.remember.source)),s}function Wl(i){return`AF_MCP_${i.toUpperCase().replace(/[^A-Z0-9]+/g,"_").replace(/^_+|_+$/g,"")||"SERVER"}_TOKEN`}function er(i){return/^[A-Za-z0-9_-]+$/.test(i)?i:`"${i.replace(/"/g,'\\"')}"`}function Hl(i,t){let{def:e,secrets:s}=i;if(e.type==="unknown")throw new Error(`server ${e.name} has unknown transport`);let n=e.type;if(n==="stdio"){let a=e.command??"node",r=t?[t]:e.args??[],o={...e.env??{},...s?.env??{}};return{name:e.name,type:n,command:a,args:r,env:o}}if(!e.url)throw new Error(`server ${e.name} (${n}) has no url`);return{name:e.name,type:n,url:e.url,headers:e.headers,bearerToken:s?.bearerToken,oauthResource:e.oauth?.resource,oauthClientId:e.oauth?.clientId}}function ql(i){if(i.type==="stdio"){let s={command:i.command,args:i.args??[]};return i.env&&Object.keys(i.env).length>0&&(s.env=i.env),s}let t={...i.headers??{}};i.bearerToken&&(t.Authorization=`Bearer ${i.bearerToken}`);let e={type:i.type,url:i.url};return Object.keys(t).length>0&&(e.headers=t),e}function zl(i){let t=er(i.name),e=[],s={};if(i.type==="stdio"){e.push("-c",`mcp_servers.${t}.command=${JSON.stringify(i.command??"node")}`),i.args&&i.args.length>0&&e.push("-c",`mcp_servers.${t}.args=${JSON.stringify(i.args)}`);for(let[n,a]of Object.entries(i.env??{}))e.push("-c",`mcp_servers.${t}.env.${er(n)}=${JSON.stringify(a)}`)}else{if(e.push("-c",`mcp_servers.${t}.url=${JSON.stringify(i.url)}`),i.bearerToken){let n=Wl(i.name);s[n]=i.bearerToken,e.push("-c",`mcp_servers.${t}.bearer_token_env_var=${JSON.stringify(n)}`)}i.oauthResource&&e.push("-c",`mcp_servers.${t}.oauth_resource=${JSON.stringify(i.oauthResource)}`),i.oauthClientId&&e.push("-c",`mcp_servers.${t}.oauth.client_id=${JSON.stringify(i.oauthClientId)}`)}return e.push("-c",`mcp_servers.${t}.enabled=true`),{args:e,env:s}}function ln(i,t,e){if(e.length===0)return null;let s=wt(t)==="codex",n=[],a=(0,rn.join)(i,".claude");try{(0,ct.existsSync)(a)||(0,ct.mkdirSync)(a,{recursive:!0});let r=`${process.pid}-${Date.now()}-${$l++}`,o=[],c=0;for(let d of e){try{let u=null;d.inlineScript&&(u=(0,rn.join)(a,`af-mcp-${Gl(d.def.name)}.${r}-${c}.cjs`),(0,ct.writeFileSync)(u,d.inlineScript,"utf-8"),n.push(u)),o.push(Hl(d,u))}catch(u){console.warn(`Agent Fleet: skipping MCP server "${d.def.name}" in projection:`,u)}c++}if(o.length===0)return ya(n),null;if(s){let d=[],u={};for(let p of o){let m=zl(p);d.push(...m.args),Object.assign(u,m.env)}return{args:d,env:u,tempFiles:n}}let l={};for(let d of o)l[d.name]=ql(d);let h=(0,rn.join)(a,`af-mcp.${r}.json`);return(0,ct.writeFileSync)(h,JSON.stringify({mcpServers:l},null,2),"utf-8"),n.push(h),{args:["--mcp-config",h],env:{},tempFiles:n}}catch(r){return console.warn("Agent Fleet: couldn't install MCP projection; run proceeds without fleet MCP.",r),ya(n),null}}function bt(i){i&&ya(i.tempFiles)}function ya(i){for(let t of i)try{(0,ct.existsSync)(t)&&(0,ct.unlinkSync)(t)}catch{}}function Gl(i){return i.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"")||"server"}var cn=class{constructor(t,e,s){this.settings=t;this.repository=e;this.mcpAuth=s}runningProcesses=new Map;abortAgent(t){let e=this.runningProcesses.get(t);return e?(e.kill(),this.runningProcesses.delete(t),!0):!1}async buildPrompt(t,e,s,n=t.memory){let a=[t.body.trim()];for(let o of t.skills){let c=this.repository.getSkillByName(o);if(c){let l=[c.body.trim()];c.toolsBody.trim()&&l.push(`### Tools +`;function lr(i){let t=[];for(let e of i){let s=e.trim();if(s)try{let n=JSON.parse(s),a=typeof n.text=="string"?n.text.trim():"";if(!a)continue;let r=n.section==="Preferences"||n.section==="Procedures"||n.section==="Observations"?n.section:void 0;t.push({text:a,pinned:n.pinned===!0,section:r,source:typeof n.source=="string"?n.source:"mcp"})}catch{}}return t}var tc=0;function sc(i,t){return{def:{name:ba,type:"stdio",enabled:!0,command:"node",env:{AF_PENDING_DIR:i,AF_SOURCE:t},status:"connected",scope:"user",tools:[],toolDetails:[]},inlineScript:or}}function cn(i){let t=i.agentGrants.map(n=>n.trim().toLowerCase()).filter(Boolean),e=t.length>0?new Set(t):null,s=[];for(let n of i.registry){if(!n.enabled||e&&!e.has(n.name.trim().toLowerCase())||n.type==="unknown")continue;let a={};if(n.type==="stdio"){if(n.envSecretKeys&&n.envSecretKeys.length>0){let r={};for(let o of n.envSecretKeys){let c=process.env[o];c&&(r[o]=c)}Object.keys(r).length>0&&(a.env=r)}}else if(n.auth!=="none"){let r=i.getBearerToken(n.name);r&&(a.bearerToken=r)}s.push({def:n,secrets:a})}return i.remember&&s.push(sc(i.remember.pendingDir,i.remember.source)),s}function nc(i){return`AF_MCP_${i.toUpperCase().replace(/[^A-Z0-9]+/g,"_").replace(/^_+|_+$/g,"")||"SERVER"}_TOKEN`}function cr(i){return/^[A-Za-z0-9_-]+$/.test(i)?i:`"${i.replace(/"/g,'\\"')}"`}function ac(i,t){let{def:e,secrets:s}=i;if(e.type==="unknown")throw new Error(`server ${e.name} has unknown transport`);let n=e.type;if(n==="stdio"){let a=e.command??"node",r=t?[t]:e.args??[],o={...e.env??{},...s?.env??{}};return{name:e.name,type:n,command:a,args:r,env:o}}if(!e.url)throw new Error(`server ${e.name} (${n}) has no url`);return{name:e.name,type:n,url:e.url,headers:e.headers,bearerToken:s?.bearerToken,oauthResource:e.oauth?.resource,oauthClientId:e.oauth?.clientId}}function ic(i){if(i.type==="stdio"){let s={command:i.command,args:i.args??[]};return i.env&&Object.keys(i.env).length>0&&(s.env=i.env),s}let t={...i.headers??{}};i.bearerToken&&(t.Authorization=`Bearer ${i.bearerToken}`);let e={type:i.type,url:i.url};return Object.keys(t).length>0&&(e.headers=t),e}function rc(i){let t=cr(i.name),e=[],s={};if(i.type==="stdio"){e.push("-c",`mcp_servers.${t}.command=${JSON.stringify(i.command??"node")}`),i.args&&i.args.length>0&&e.push("-c",`mcp_servers.${t}.args=${JSON.stringify(i.args)}`);for(let[n,a]of Object.entries(i.env??{}))e.push("-c",`mcp_servers.${t}.env.${cr(n)}=${JSON.stringify(a)}`)}else{if(e.push("-c",`mcp_servers.${t}.url=${JSON.stringify(i.url)}`),i.bearerToken){let n=nc(i.name);s[n]=i.bearerToken,e.push("-c",`mcp_servers.${t}.bearer_token_env_var=${JSON.stringify(n)}`)}i.oauthResource&&e.push("-c",`mcp_servers.${t}.oauth_resource=${JSON.stringify(i.oauthResource)}`),i.oauthClientId&&e.push("-c",`mcp_servers.${t}.oauth.client_id=${JSON.stringify(i.oauthClientId)}`)}return e.push("-c",`mcp_servers.${t}.enabled=true`),{args:e,env:s}}function dn(i,t,e){if(e.length===0)return null;let s=kt(t)==="codex",n=[],a=(0,ln.join)(i,".claude");try{(0,ht.existsSync)(a)||(0,ht.mkdirSync)(a,{recursive:!0});let r=`${process.pid}-${Date.now()}-${tc++}`,o=[],c=0;for(let d of e){try{let u=null;d.inlineScript&&(u=(0,ln.join)(a,`af-mcp-${oc(d.def.name)}.${r}-${c}.cjs`),(0,ht.writeFileSync)(u,d.inlineScript,"utf-8"),n.push(u)),o.push(ac(d,u))}catch(u){console.warn(`Agent Fleet: skipping MCP server "${d.def.name}" in projection:`,u)}c++}if(o.length===0)return ka(n),null;if(s){let d=[],u={};for(let p of o){let m=rc(p);d.push(...m.args),Object.assign(u,m.env)}return{args:d,env:u,tempFiles:n}}let l={};for(let d of o)l[d.name]=ic(d);let h=(0,ln.join)(a,`af-mcp.${r}.json`);return(0,ht.writeFileSync)(h,JSON.stringify({mcpServers:l},null,2),"utf-8"),n.push(h),{args:["--mcp-config",h],env:{},tempFiles:n}}catch(r){return console.warn("Agent Fleet: couldn't install MCP projection; run proceeds without fleet MCP.",r),ka(n),null}}function xt(i){i&&ka(i.tempFiles)}function ka(i){for(let t of i)try{(0,ht.existsSync)(t)&&(0,ht.unlinkSync)(t)}catch{}}function oc(i){return i.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"")||"server"}var hn=class{constructor(t,e,s){this.settings=t;this.repository=e;this.mcpAuth=s}runningProcesses=new Map;abortAgent(t){let e=this.runningProcesses.get(t);return e?(e.kill(),this.runningProcesses.delete(t),!0):!1}async buildPrompt(t,e,s,n=t.memory){let a=[t.body.trim()];for(let o of t.skills){let c=this.repository.getSkillByName(o);if(c){let l=[c.body.trim()];c.toolsBody.trim()&&l.push(`### Tools ${c.toolsBody.trim()}`),c.referencesBody.trim()&&l.push(`### References ${c.referencesBody.trim()}`),c.examplesBody.trim()&&l.push(`### Examples ${c.examplesBody.trim()}`),a.push(`## Skill: ${c.name} @@ -11949,25 +11979,25 @@ ${l.join(` `)}`)}}if(t.skillsBody.trim()&&a.push(`## Agent Skills ${t.skillsBody.trim()}`),t.contextBody.trim()&&a.push(`## Agent Context -${t.contextBody.trim()}`),n){let o=await this.repository.readWorkingMemory(t.name),c=qs(t,o);c&&a.push(c)}let r=nn(t,this.repository);return r&&a.push(r),a.push(`## Task +${t.contextBody.trim()}`),n){let o=await this.repository.readWorkingMemory(t.name),c=Gs(t,o);c&&a.push(c)}let r=rn(t,this.repository);return r&&a.push(r),a.push(`## Task ${(s??e.body).trim()}`),a.filter(Boolean).join(` -`)}async execute(t,e,s,n,a){let r=t.memory&&!a?.suppressMemoryCapture,o=await this.buildPrompt(t,e,s,r),c=(0,tr.randomUUID)(),l=Pt(e,t,this.settings),h=n!=null,d=Ji(t.adapter),u=await d.buildExec({prompt:o,model:ys(l.value)?l.value:"",modelSource:l.source,effort:e.effort||t.effort||"",agent:t,settings:this.settings,streaming:h}),p=t.cwd?.trim()?t.cwd:this.repository.getVaultBasePath()??".",m=r?this.repository.getPendingDirAbsolutePath(t.name):null,f=on({registry:this.repository.getMcpServers(),agentGrants:t.mcpServers??[],getBearerToken:k=>this.mcpAuth?.getToken(k),remember:m?{pendingDir:m,source:"mcp"}:null}),g=ln(p,t.adapter,f);g&&u.args.push(...g.args);let v=await d.setupPermissions(p,t,this.settings,{mcpAllowServers:f.map(k=>k.def.name)}),b=Date.now();try{return await new Promise((k,y)=>{let S=lt(u.cliPath,u.args,{cwd:p,env:{...process.env,AWS_REGION:this.settings.awsRegion,...g?.env??{},...v?.env??{}}});if(u.stdinPayload!==void 0)try{S.stdin?.write(u.stdinPayload),S.stdin?.end()}catch(C){S.kill(),y(C instanceof Error?C:new Error(String(C)));return}this.runningProcesses.set(t.name,S);let T="",_="",D=!1,M=window.setTimeout(()=>{D=!0,S.kill()},t.timeout*1e3);S.stdout.on("data",C=>{let I=C.toString();if(T+=I,h&&n)for(let P of ge(I)){let L=d.extractStreamChunk(P);L&&n(L)}}),S.stderr.on("data",C=>{_+=C.toString()}),S.on("error",C=>{window.clearTimeout(M),y(C)}),S.on("close",C=>{window.clearTimeout(M),this.runningProcesses.delete(t.name);let I=d.parseExecOutput(T,_,h);k({runId:c,prompt:o,exitCode:C,durationSeconds:Math.max(1,Math.round((Date.now()-b)/1e3)),stdout:T,stderr:_,outputText:I.outputText,rawJson:I.rawJson,tokensUsed:I.tokensUsed,costUsd:I.costUsd,toolsUsed:I.toolsUsed,timedOut:D,resolvedModel:l.value,modelSource:l.source,concreteModel:I.concreteModel,finalResult:I.finalResult})})})}finally{v?.restore(),bt(g)}}};function sr(i){return oe(i).slice(0,60)||"candidate"}function ar(i){let t=Math.max(400,i.tokenBudget*4);return[`You are running a nightly memory **reflection** for the agent "${i.agentName}".`,"Review the raw memory log and your current working memory below, then produce an","updated, consolidated working memory.","","Rules:","- Remove duplicates; resolve contradictions in favor of the NEWEST fact.","- File each fact under exactly one of: Preferences, Procedures, Observations.","- Keep durable user/process preferences, marking them `[pin]` \u2014 never drop or"," summarize pinned Preferences.","- Summarize the Observations section FROM THE RAW LOG (not from prior summaries)",` if needed so the whole memory fits within roughly ${i.tokenBudget} tokens`,` (~${t} characters).`,"- Note any friction that recurred across multiple runs as a skill candidate.","","Reply with EXACTLY these two fenced blocks and nothing else:","","```memory","## Preferences","- [pin] ","## Procedures","- ","## Observations","- ","```","","```candidates",'[{"key":"short-stable-key","pattern":"what recurred","evidence":["runs/..."],"suggestedSkill":"optional-name"}]',"```","","If there is nothing worth remembering, still emit a `memory` block preserving the","existing pinned Preferences. Use an empty array `[]` for candidates when none.","","\u2500\u2500 CURRENT WORKING MEMORY \u2500\u2500",i.workingMemoryBody.trim()||"(empty)","","\u2500\u2500 RAW MEMORY LOG (ground truth) \u2500\u2500",i.recentRaw.trim()||"(empty)",i.recentRunsSummary?` +`)}async execute(t,e,s,n,a){let r=t.memory&&!a?.suppressMemoryCapture,o=await this.buildPrompt(t,e,s,r),c=(0,dr.randomUUID)(),l=Dt(e,t,this.settings),h=n!=null,d=ir(t.adapter),u=await d.buildExec({prompt:o,model:ws(l.value)?l.value:"",modelSource:l.source,effort:e.effort||t.effort||"",agent:t,settings:this.settings,streaming:h}),p=t.cwd?.trim()?t.cwd:this.repository.getVaultBasePath()??".",m=r?this.repository.getPendingDirAbsolutePath(t.name):null,f=cn({registry:this.repository.getMcpServers(),agentGrants:t.mcpServers??[],getBearerToken:k=>this.mcpAuth?.getToken(k),remember:m?{pendingDir:m,source:"mcp"}:null}),g=dn(p,t.adapter,f);g&&u.args.push(...g.args);let y=await d.setupPermissions(p,t,this.settings,{mcpAllowServers:f.map(k=>k.def.name)}),v=Date.now();try{return await new Promise((k,w)=>{let S=dt(u.cliPath,u.args,{cwd:p,env:{...process.env,AWS_REGION:this.settings.awsRegion,...g?.env??{},...y?.env??{}}});if(u.stdinPayload!==void 0)try{S.stdin?.write(u.stdinPayload),S.stdin?.end()}catch(C){S.kill(),w(C instanceof Error?C:new Error(String(C)));return}this.runningProcesses.set(t.name,S);let T="",_="",D=!1,O=window.setTimeout(()=>{D=!0,S.kill()},t.timeout*1e3);S.stdout.on("data",C=>{let E=C.toString();if(T+=E,h&&n)for(let P of ye(E)){let N=d.extractStreamChunk(P);N&&n(N)}}),S.stderr.on("data",C=>{_+=C.toString()}),S.on("error",C=>{window.clearTimeout(O),w(C)}),S.on("close",C=>{window.clearTimeout(O),this.runningProcesses.delete(t.name);let E=d.parseExecOutput(T,_,h);k({runId:c,prompt:o,exitCode:C,durationSeconds:Math.max(1,Math.round((Date.now()-v)/1e3)),stdout:T,stderr:_,outputText:E.outputText,rawJson:E.rawJson,tokensUsed:E.tokensUsed,costUsd:E.costUsd,toolsUsed:E.toolsUsed,timedOut:D,resolvedModel:l.value,modelSource:l.source,concreteModel:E.concreteModel,finalResult:E.finalResult})})})}finally{y?.restore(),xt(g)}}};function hr(i){return oe(i).slice(0,60)||"candidate"}function pr(i){let t=Math.max(400,i.tokenBudget*4);return[`You are running a nightly memory **reflection** for the agent "${i.agentName}".`,"Review the raw memory log and your current working memory below, then produce an","updated, consolidated working memory.","","Rules:","- Remove duplicates; resolve contradictions in favor of the NEWEST fact.","- File each fact under exactly one of: Preferences, Procedures, Observations.","- Keep durable user/process preferences, marking them `[pin]` \u2014 never drop or"," summarize pinned Preferences.","- Summarize the Observations section FROM THE RAW LOG (not from prior summaries)",` if needed so the whole memory fits within roughly ${i.tokenBudget} tokens`,` (~${t} characters).`,"- Note any friction that recurred across multiple runs as a skill candidate.","","Reply with EXACTLY these two fenced blocks and nothing else:","","```memory","## Preferences","- [pin] ","## Procedures","- ","## Observations","- ","```","","```candidates",'[{"key":"short-stable-key","pattern":"what recurred","evidence":["runs/..."],"suggestedSkill":"optional-name"}]',"```","","If there is nothing worth remembering, still emit a `memory` block preserving the","existing pinned Preferences. Use an empty array `[]` for candidates when none.","","\u2500\u2500 CURRENT WORKING MEMORY \u2500\u2500",i.workingMemoryBody.trim()||"(empty)","","\u2500\u2500 RAW MEMORY LOG (ground truth) \u2500\u2500",i.recentRaw.trim()||"(empty)",i.recentRunsSummary?` \u2500\u2500 RECENT ACTIVITY \u2500\u2500 ${i.recentRunsSummary.trim()}`:""].join(` -`)}function nr(i,t){let e=new RegExp("```"+t+"(?![A-Za-z0-9-])[^\\n]*\\n([\\s\\S]*?)```","i"),s=i.match(e);return s?s[1]??"":null}function ir(i){let t=nr(i,"memory"),e=t!==null?Hs(t):null,s=[],n=nr(i,"candidates");if(n!==null&&n.trim())try{let a=JSON.parse(n.trim());Array.isArray(a)&&(s=a.map(r=>Vl(r)).filter(r=>r!==null))}catch{}return{sections:e,candidates:s}}function Vl(i){if(typeof i!="object"||i===null)return null;let t=i,e=typeof t.pattern=="string"?t.pattern.trim():"";return e?{key:typeof t.key=="string"&&t.key.trim()?sr(t.key):sr(e),pattern:e,evidence:Array.isArray(t.evidence)?t.evidence.filter(n=>typeof n=="string"):[],suggestedSkill:typeof t.suggestedSkill=="string"?t.suggestedSkill:void 0}:null}function rr(i,t,e){let s=new Map;for(let a of i)s.set(a.key,{...a,evidence:[...a.evidence]});let n=new Map;for(let a of t){let r=n.get(a.key);r?(r.evidence=Array.from(new Set([...r.evidence??[],...a.evidence??[]])),a.suggestedSkill&&(r.suggestedSkill=a.suggestedSkill)):n.set(a.key,{...a,evidence:[...a.evidence??[]]})}for(let a of n.values()){let r=s.get(a.key);r?(r.occurrences+=1,r.lastSeen=e,r.evidence=Array.from(new Set([...r.evidence,...a.evidence??[]])),a.suggestedSkill&&(r.suggestedSkill=a.suggestedSkill)):s.set(a.key,{key:a.key,pattern:a.pattern,occurrences:1,firstSeen:e,lastSeen:e,evidence:[...a.evidence??[]],proposed:!1,suggestedSkill:a.suggestedSkill})}return[...s.values()]}var Yl=1.5,qt=class i{constructor(t){this.store=t}static locks=new Map;static __resetLocksForTest(){i.locks.clear()}async capture(t,e,s,n){!t.memory||e.length===0||await this.withLock(t.name,()=>this.applyCaptures(t,e,s,n))}async drainPending(t,e){t.memory&&await this.withLock(t.name,async()=>{let s=await this.store.readAndClearPending(t.name),n=Zi(s);if(n.length===0)return;let a=[];for(let r of n){let o=a[a.length-1],c={text:r.text,pinned:r.pinned,section:r.section};o&&o.source===r.source?o.entries.push(c):a.push({source:r.source,entries:[c]})}for(let r of a)await this.applyCaptures(t,r.entries,r.source,e)})}async reflect(t,e,s){return!t.memory||e===null||!e.some(n=>n.entries.length>0)?!1:(await this.withLock(t.name,async()=>{let n=this.store.getWorkingMemoryPath(t.name),a=await this.store.readWorkingMemory(t.name),r=hi(a,e),o={filePath:a?.filePath??n,agent:t.name,schema:a?.schema??2,lastUpdated:s,lastReflection:s,tokenEstimate:0,sections:r};await this.store.writeWorkingMemory(t.name,o)}),!0)}async applyCaptures(t,e,s,n){let a=e.map(p=>({...p,text:ai(p.text)})).filter(p=>p.text);if(a.length===0)return;await this.store.migrateLegacyMemory?.(t.name);let r=this.store.getWorkingMemoryPath(t.name),o=await this.store.readWorkingMemory(t.name)??ii(r,t.name),c=n.slice(0,10),l=a.map(p=>`- ${n} [${s}]${p.pinned?" [pin]":""} ${p.text}`);await this.store.appendRawMemory(t.name,l,n);let h=new Set(o.sections.flatMap(p=>p.entries).map(p=>p.text.trim().toLowerCase())),d=o;for(let p of a){let m=p.text.trim().toLowerCase();if(h.has(m))continue;h.add(m);let f={text:p.text,source:s,date:c,pinned:p.pinned??!1},g=p.section??(p.pinned?"Preferences":si);d=ci(d,[f],g,n)}let u=Math.ceil((t.memoryTokenBudget||0)*Yl);if(u>0){let{wm:p,spilled:m}=di(d,u);m.length>0&&console.info(`Agent Fleet: working memory for "${t.name}" exceeded hard cap; spilled ${m.length} entr${m.length===1?"y":"ies"} to raw-only (still in the archive). A reflection pass will consolidate.`),d=p}await this.store.writeWorkingMemory(t.name,d)}withLock(t,e){let n=(i.locks.get(t)??Promise.resolve()).then(e,e);return i.locks.set(t,n.then(()=>{},()=>{})),n}};var vs=ze(require("crypto")),or=ze(require("https")),dn=ze(require("http")),lr=ze(require("fs")),va=ze(require("path"));var hn=class{constructor(t){this.settings=t;this.settings}authManager;setAuthManager(t){this.authManager=t}async probeServer(t){try{if(t.type==="stdio"&&t.command)return(await this.probeStdioServer(t.command,t.args)).tools;if((t.type==="http"||t.type==="sse")&&t.url)return await this.probeHttpServer(t)}catch(e){console.warn(`McpManager: probe failed for ${t.name}:`,e)}return[]}async authenticateServer(t,e,s="http"){let n=await this.discoverOAuthMetadata(e);if(!n)throw new Error("Server does not support OAuth \u2014 no authorization metadata found.");let{metadata:a,resourceScopes:r,resource:o}=n;if(!a.registration_endpoint)throw new Error("Server does not support Dynamic Client Registration.");let c=await this.startOAuthCallbackServer(),l=`http://localhost:${c.port}/callback`;try{let h=await this.registerOAuthClient(a.registration_endpoint,l),d=vs.randomBytes(32).toString("base64url"),u=vs.createHash("sha256").update(d).digest("base64url"),p=vs.randomBytes(16).toString("hex"),m=new URLSearchParams({response_type:"code",client_id:h,code_challenge:u,code_challenge_method:"S256",redirect_uri:l,state:p,resource:o}),f=r??a.scopes_supported;f?.length&&m.set("scope",f.join(" "));let g=new URL(a.authorization_endpoint);for(let[y,S]of m)g.searchParams.set(y,S);let v=g.toString();fi(v);let b=await c.waitForCode(p,18e4),k=await this.exchangeOAuthCode(a.token_endpoint,b,l,h,d);this.authManager?.storeOAuthToken(t,{accessToken:k.access_token,refreshToken:k.refresh_token,expiresAt:k.expires_in?Date.now()+k.expires_in*1e3:void 0,tokenEndpoint:a.token_endpoint,clientId:h,resource:o})}finally{c.close()}}async refreshProbeTokens(){if(!this.authManager)return;let t=this.authManager.getExpiringTokens();for(let[e,s]of t)if(s.refreshToken)try{let n=new URLSearchParams({grant_type:"refresh_token",refresh_token:s.refreshToken,client_id:s.clientId}).toString(),a=await this.oauthFetch(s.tokenEndpoint,"POST",n,"application/x-www-form-urlencoded");if(a.status===200){let r=JSON.parse(a.body),o=r.access_token;o&&this.authManager.storeOAuthToken(e,{accessToken:o,refreshToken:r.refresh_token??s.refreshToken,expiresAt:typeof r.expires_in=="number"?Date.now()+r.expires_in*1e3:void 0,tokenEndpoint:s.tokenEndpoint,clientId:s.clientId,resource:s.resource})}}catch(n){console.warn(`McpManager: failed to refresh token for ${e}:`,n)}}async discoverOAuthMetadata(t){let e=t.endsWith("/sse")?t.replace(/\/sse$/,"/mcp"):t,s=new URL(e),n;try{let d=await this.oauthFetch(e,"POST","{}","application/json");d.status===401&&(n=d.headers?.["www-authenticate"]?.match(/resource_metadata="([^"]+)"/)?.[1])}catch{}n||(n=`${s.origin}/.well-known/oauth-protected-resource${s.pathname}`);let a=s.origin,r,o=e;try{let d=await this.oauthFetch(n,"GET");if(d.status===200){let u=JSON.parse(d.body),p=u.authorization_servers;p?.[0]&&(a=p[0]),Array.isArray(u.scopes_supported)&&(r=u.scopes_supported),typeof u.resource=="string"&&(o=u.resource)}}catch{}let c=new URL(a),l=c.pathname==="/"?"":c.pathname,h=`${c.origin}/.well-known/oauth-authorization-server${l}`;try{let d=await this.oauthFetch(h,"GET");if(d.status===200)return{metadata:JSON.parse(d.body),resourceScopes:r,resource:o}}catch{}if(l){let d=`${c.origin}/.well-known/oauth-authorization-server`;try{let u=await this.oauthFetch(d,"GET");if(u.status===200)return{metadata:JSON.parse(u.body),resourceScopes:r,resource:o}}catch{}}return null}async registerOAuthClient(t,e){let s=JSON.stringify({client_name:"Agent Fleet Obsidian Plugin",redirect_uris:[e],grant_types:["authorization_code","refresh_token"],response_types:["code"],token_endpoint_auth_method:"none"}),n=await this.oauthFetch(t,"POST",s,"application/json");if(n.status!==200&&n.status!==201)throw new Error(`Client registration failed (HTTP ${n.status})`);let a=JSON.parse(n.body);if(!a.client_id)throw new Error("Client registration response missing client_id");return a.client_id}async startOAuthCallbackServer(){let t=null,e=null,s=dn.createServer((a,r)=>{let o=new URL(a.url??"/","http://localhost");if(o.pathname!=="/callback"){r.writeHead(404),r.end();return}let c=o.searchParams.get("error");if(c){let d=o.searchParams.get("error_description")??c;r.writeHead(200,{"Content-Type":"text/html"}),r.end("

Authorization Failed

"+d+"

You can close this tab.

"),e?.(new Error(`OAuth denied: ${d}`));return}let l=o.searchParams.get("code"),h=o.searchParams.get("state");if(!l||!h){r.writeHead(400),r.end("Missing code or state");return}r.writeHead(200,{"Content-Type":"text/html"}),r.end("

Authenticated!

You can close this tab and return to Obsidian.

"),t?.(l,h)});return{port:await new Promise((a,r)=>{s.listen(0,"127.0.0.1",()=>{let o=s.address();if(!o||typeof o=="string"){r(new Error("Failed to bind callback server"));return}a(o.port)}),s.on("error",r)}),waitForCode:(a,r)=>new Promise((o,c)=>{let l=window.setTimeout(()=>{c(new Error("Authentication timed out \u2014 complete authorization in your browser and try again."))},r);t=(h,d)=>{window.clearTimeout(l),d!==a?c(new Error("OAuth state mismatch \u2014 possible CSRF attack")):o(h)},e=h=>{window.clearTimeout(l),c(h)}}),close:()=>{try{s.close()}catch{}}}}async exchangeOAuthCode(t,e,s,n,a){let r=new URLSearchParams({grant_type:"authorization_code",code:e,redirect_uri:s,client_id:n,code_verifier:a}).toString(),o=await this.oauthFetch(t,"POST",r,"application/x-www-form-urlencoded");if(o.status!==200)throw new Error(`Token exchange failed (HTTP ${o.status}): ${o.body}`);let c=JSON.parse(o.body);if(!c.access_token)throw new Error("Token response missing access_token");return c}oauthFetch(t,e,s,n){return new Promise((a,r)=>{let o=new URL(t),c=o.protocol==="https:",l={Accept:"application/json"};s&&(l["Content-Type"]=n??"application/json",l["Content-Length"]=String(Buffer.byteLength(s)));let h={hostname:o.hostname,port:o.port||(c?443:80),path:o.pathname+o.search,method:e,headers:l},u=(c?or:dn).request(h,m=>{let f="";m.on("data",g=>{f+=g.toString()}),m.on("end",()=>{let g={};for(let[v,b]of Object.entries(m.headers))typeof b=="string"?g[v]=b:Array.isArray(b)&&(g[v]=b.join(", "));a({status:m.statusCode??0,body:f,headers:g})})});u.on("error",r);let p=window.setTimeout(()=>{u.destroy(),r(new Error("OAuth request timed out"))},15e3);u.on("close",()=>window.clearTimeout(p)),s&&u.write(s),u.end()})}probeStdioServer(t,e){return new Promise(s=>{let n=e&&e.length>0?`${t} ${e.join(" ")}`:t,a=mi(n,{env:{...process.env}}),r="",o,c=[],l=!1,h=!1,d=!1,u=()=>{l||(l=!0,a.kill(),s({description:o,tools:c}))},p=window.setTimeout(u,1e4);a.stdout.on("data",m=>{r+=m.toString();let f=ge(r);r=f.pop()??"";for(let g of f){let v=g.trim();if(v){try{let b=JSON.parse(v);if(b.id===1&&b.result){o=b.result.instructions??b.result.serverInfo?.description,h=!0;try{a.stdin.write(JSON.stringify({jsonrpc:"2.0",method:"notifications/initialized"})+` +`)}function ur(i,t){let e=new RegExp("```"+t+"(?![A-Za-z0-9-])[^\\n]*\\n([\\s\\S]*?)```","i"),s=i.match(e);return s?s[1]??"":null}function mr(i){let t=ur(i,"memory"),e=t!==null?zs(t):null,s=[],n=ur(i,"candidates");if(n!==null&&n.trim())try{let a=JSON.parse(n.trim());Array.isArray(a)&&(s=a.map(r=>lc(r)).filter(r=>r!==null))}catch{}return{sections:e,candidates:s}}function lc(i){if(typeof i!="object"||i===null)return null;let t=i,e=typeof t.pattern=="string"?t.pattern.trim():"";return e?{key:typeof t.key=="string"&&t.key.trim()?hr(t.key):hr(e),pattern:e,evidence:Array.isArray(t.evidence)?t.evidence.filter(n=>typeof n=="string"):[],suggestedSkill:typeof t.suggestedSkill=="string"?t.suggestedSkill:void 0}:null}function fr(i,t,e){let s=new Map;for(let a of i)s.set(a.key,{...a,evidence:[...a.evidence]});let n=new Map;for(let a of t){let r=n.get(a.key);r?(r.evidence=Array.from(new Set([...r.evidence??[],...a.evidence??[]])),a.suggestedSkill&&(r.suggestedSkill=a.suggestedSkill)):n.set(a.key,{...a,evidence:[...a.evidence??[]]})}for(let a of n.values()){let r=s.get(a.key);r?(r.occurrences+=1,r.lastSeen=e,r.evidence=Array.from(new Set([...r.evidence,...a.evidence??[]])),a.suggestedSkill&&(r.suggestedSkill=a.suggestedSkill)):s.set(a.key,{key:a.key,pattern:a.pattern,occurrences:1,firstSeen:e,lastSeen:e,evidence:[...a.evidence??[]],proposed:!1,suggestedSkill:a.suggestedSkill})}return[...s.values()]}var cc=1.5,zt=class i{constructor(t){this.store=t}static locks=new Map;static __resetLocksForTest(){i.locks.clear()}async capture(t,e,s,n){!t.memory||e.length===0||await this.withLock(t.name,()=>this.applyCaptures(t,e,s,n))}async drainPending(t,e){t.memory&&await this.withLock(t.name,async()=>{let s=await this.store.readAndClearPending(t.name),n=lr(s);if(n.length===0)return;let a=[];for(let r of n){let o=a[a.length-1],c={text:r.text,pinned:r.pinned,section:r.section};o&&o.source===r.source?o.entries.push(c):a.push({source:r.source,entries:[c]})}for(let r of a)await this.applyCaptures(t,r.entries,r.source,e)})}async reflect(t,e,s){return!t.memory||e===null||!e.some(n=>n.entries.length>0)?!1:(await this.withLock(t.name,async()=>{let n=this.store.getWorkingMemoryPath(t.name),a=await this.store.readWorkingMemory(t.name),r=bi(a,e),o={filePath:a?.filePath??n,agent:t.name,schema:a?.schema??2,lastUpdated:s,lastReflection:s,tokenEstimate:0,sections:r};await this.store.writeWorkingMemory(t.name,o)}),!0)}async applyCaptures(t,e,s,n){let a=e.map(p=>({...p,text:pi(p.text)})).filter(p=>p.text);if(a.length===0)return;await this.store.migrateLegacyMemory?.(t.name);let r=this.store.getWorkingMemoryPath(t.name),o=await this.store.readWorkingMemory(t.name)??mi(r,t.name),c=n.slice(0,10),l=a.map(p=>`- ${n} [${s}]${p.pinned?" [pin]":""} ${p.text}`);await this.store.appendRawMemory(t.name,l,n);let h=new Set(o.sections.flatMap(p=>p.entries).map(p=>p.text.trim().toLowerCase())),d=o;for(let p of a){let m=p.text.trim().toLowerCase();if(h.has(m))continue;h.add(m);let f={text:p.text,source:s,date:c,pinned:p.pinned??!1},g=p.section??(p.pinned?"Preferences":hi);d=vi(d,[f],g,n)}let u=Math.ceil((t.memoryTokenBudget||0)*cc);if(u>0){let{wm:p,spilled:m}=wi(d,u);m.length>0&&console.info(`Agent Fleet: working memory for "${t.name}" exceeded hard cap; spilled ${m.length} entr${m.length===1?"y":"ies"} to raw-only (still in the archive). A reflection pass will consolidate.`),d=p}await this.store.writeWorkingMemory(t.name,d)}withLock(t,e){let n=(i.locks.get(t)??Promise.resolve()).then(e,e);return i.locks.set(t,n.then(()=>{},()=>{})),n}};var bs=Ye(require("crypto")),gr=Ye(require("https")),un=Ye(require("http")),yr=Ye(require("fs")),xa=Ye(require("path"));var pn=class{constructor(t){this.settings=t;this.settings}authManager;setAuthManager(t){this.authManager=t}async probeServer(t){try{if(t.type==="stdio"&&t.command)return(await this.probeStdioServer(t.command,t.args)).tools;if((t.type==="http"||t.type==="sse")&&t.url)return await this.probeHttpServer(t)}catch(e){console.warn(`McpManager: probe failed for ${t.name}:`,e)}return[]}async authenticateServer(t,e,s="http"){let n=await this.discoverOAuthMetadata(e);if(!n)throw new Error("Server does not support OAuth \u2014 no authorization metadata found.");let{metadata:a,resourceScopes:r,resource:o}=n;if(!a.registration_endpoint)throw new Error("Server does not support Dynamic Client Registration.");let c=await this.startOAuthCallbackServer(),l=`http://localhost:${c.port}/callback`;try{let h=await this.registerOAuthClient(a.registration_endpoint,l),d=bs.randomBytes(32).toString("base64url"),u=bs.createHash("sha256").update(d).digest("base64url"),p=bs.randomBytes(16).toString("hex"),m=new URLSearchParams({response_type:"code",client_id:h,code_challenge:u,code_challenge_method:"S256",redirect_uri:l,state:p,resource:o}),f=r??a.scopes_supported;f?.length&&m.set("scope",f.join(" "));let g=new URL(a.authorization_endpoint);for(let[w,S]of m)g.searchParams.set(w,S);let y=g.toString();Ci(y);let v=await c.waitForCode(p,18e4),k=await this.exchangeOAuthCode(a.token_endpoint,v,l,h,d);this.authManager?.storeOAuthToken(t,{accessToken:k.access_token,refreshToken:k.refresh_token,expiresAt:k.expires_in?Date.now()+k.expires_in*1e3:void 0,tokenEndpoint:a.token_endpoint,clientId:h,resource:o})}finally{c.close()}}async refreshProbeTokens(){if(!this.authManager)return;let t=this.authManager.getExpiringTokens();for(let[e,s]of t)if(s.refreshToken)try{let n=new URLSearchParams({grant_type:"refresh_token",refresh_token:s.refreshToken,client_id:s.clientId}).toString(),a=await this.oauthFetch(s.tokenEndpoint,"POST",n,"application/x-www-form-urlencoded");if(a.status===200){let r=JSON.parse(a.body),o=r.access_token;o&&this.authManager.storeOAuthToken(e,{accessToken:o,refreshToken:r.refresh_token??s.refreshToken,expiresAt:typeof r.expires_in=="number"?Date.now()+r.expires_in*1e3:void 0,tokenEndpoint:s.tokenEndpoint,clientId:s.clientId,resource:s.resource})}}catch(n){console.warn(`McpManager: failed to refresh token for ${e}:`,n)}}async discoverOAuthMetadata(t){let e=t.endsWith("/sse")?t.replace(/\/sse$/,"/mcp"):t,s=new URL(e),n;try{let d=await this.oauthFetch(e,"POST","{}","application/json");d.status===401&&(n=d.headers?.["www-authenticate"]?.match(/resource_metadata="([^"]+)"/)?.[1])}catch{}n||(n=`${s.origin}/.well-known/oauth-protected-resource${s.pathname}`);let a=s.origin,r,o=e;try{let d=await this.oauthFetch(n,"GET");if(d.status===200){let u=JSON.parse(d.body),p=u.authorization_servers;p?.[0]&&(a=p[0]),Array.isArray(u.scopes_supported)&&(r=u.scopes_supported),typeof u.resource=="string"&&(o=u.resource)}}catch{}let c=new URL(a),l=c.pathname==="/"?"":c.pathname,h=`${c.origin}/.well-known/oauth-authorization-server${l}`;try{let d=await this.oauthFetch(h,"GET");if(d.status===200)return{metadata:JSON.parse(d.body),resourceScopes:r,resource:o}}catch{}if(l){let d=`${c.origin}/.well-known/oauth-authorization-server`;try{let u=await this.oauthFetch(d,"GET");if(u.status===200)return{metadata:JSON.parse(u.body),resourceScopes:r,resource:o}}catch{}}return null}async registerOAuthClient(t,e){let s=JSON.stringify({client_name:"Agent Fleet Obsidian Plugin",redirect_uris:[e],grant_types:["authorization_code","refresh_token"],response_types:["code"],token_endpoint_auth_method:"none"}),n=await this.oauthFetch(t,"POST",s,"application/json");if(n.status!==200&&n.status!==201)throw new Error(`Client registration failed (HTTP ${n.status})`);let a=JSON.parse(n.body);if(!a.client_id)throw new Error("Client registration response missing client_id");return a.client_id}async startOAuthCallbackServer(){let t=null,e=null,s=un.createServer((a,r)=>{let o=new URL(a.url??"/","http://localhost");if(o.pathname!=="/callback"){r.writeHead(404),r.end();return}let c=o.searchParams.get("error");if(c){let d=o.searchParams.get("error_description")??c;r.writeHead(200,{"Content-Type":"text/html"}),r.end("

Authorization Failed

"+d+"

You can close this tab.

"),e?.(new Error(`OAuth denied: ${d}`));return}let l=o.searchParams.get("code"),h=o.searchParams.get("state");if(!l||!h){r.writeHead(400),r.end("Missing code or state");return}r.writeHead(200,{"Content-Type":"text/html"}),r.end("

Authenticated!

You can close this tab and return to Obsidian.

"),t?.(l,h)});return{port:await new Promise((a,r)=>{s.listen(0,"127.0.0.1",()=>{let o=s.address();if(!o||typeof o=="string"){r(new Error("Failed to bind callback server"));return}a(o.port)}),s.on("error",r)}),waitForCode:(a,r)=>new Promise((o,c)=>{let l=window.setTimeout(()=>{c(new Error("Authentication timed out \u2014 complete authorization in your browser and try again."))},r);t=(h,d)=>{window.clearTimeout(l),d!==a?c(new Error("OAuth state mismatch \u2014 possible CSRF attack")):o(h)},e=h=>{window.clearTimeout(l),c(h)}}),close:()=>{try{s.close()}catch{}}}}async exchangeOAuthCode(t,e,s,n,a){let r=new URLSearchParams({grant_type:"authorization_code",code:e,redirect_uri:s,client_id:n,code_verifier:a}).toString(),o=await this.oauthFetch(t,"POST",r,"application/x-www-form-urlencoded");if(o.status!==200)throw new Error(`Token exchange failed (HTTP ${o.status}): ${o.body}`);let c=JSON.parse(o.body);if(!c.access_token)throw new Error("Token response missing access_token");return c}oauthFetch(t,e,s,n){return new Promise((a,r)=>{let o=new URL(t),c=o.protocol==="https:",l={Accept:"application/json"};s&&(l["Content-Type"]=n??"application/json",l["Content-Length"]=String(Buffer.byteLength(s)));let h={hostname:o.hostname,port:o.port||(c?443:80),path:o.pathname+o.search,method:e,headers:l},u=(c?gr:un).request(h,m=>{let f="";m.on("data",g=>{f+=g.toString()}),m.on("end",()=>{let g={};for(let[y,v]of Object.entries(m.headers))typeof v=="string"?g[y]=v:Array.isArray(v)&&(g[y]=v.join(", "));a({status:m.statusCode??0,body:f,headers:g})})});u.on("error",r);let p=window.setTimeout(()=>{u.destroy(),r(new Error("OAuth request timed out"))},15e3);u.on("close",()=>window.clearTimeout(p)),s&&u.write(s),u.end()})}probeStdioServer(t,e){return new Promise(s=>{let n=e&&e.length>0?`${t} ${e.join(" ")}`:t,a=Si(n,{env:{...process.env}}),r="",o,c=[],l=!1,h=!1,d=!1,u=()=>{l||(l=!0,a.kill(),s({description:o,tools:c}))},p=window.setTimeout(u,1e4);a.stdout.on("data",m=>{r+=m.toString();let f=ye(r);r=f.pop()??"";for(let g of f){let y=g.trim();if(y){try{let v=JSON.parse(y);if(v.id===1&&v.result){o=v.result.instructions??v.result.serverInfo?.description,h=!0;try{a.stdin.write(JSON.stringify({jsonrpc:"2.0",method:"notifications/initialized"})+` `),a.stdin.write(JSON.stringify({jsonrpc:"2.0",id:2,method:"tools/list"})+` -`)}catch{window.clearTimeout(p),u();return}}else if(b.id===2&&b.result){for(let k of b.result.tools??[])c.push({name:k.name,description:k.description,inputSchema:k.inputSchema});d=!0}}catch{}h&&d&&(window.clearTimeout(p),u())}}}),a.on("error",()=>{window.clearTimeout(p),u()}),a.on("close",()=>{window.clearTimeout(p),u()});try{a.stdin.write(JSON.stringify({jsonrpc:"2.0",id:1,method:"initialize",params:{protocolVersion:"2024-11-05",capabilities:{},clientInfo:{name:"agent-fleet",version:"1.0.0"}}})+` -`)}catch{window.clearTimeout(p),u()}})}async probeHttpServer(t){let e=await this.findServerToken(t);if(!e)return[];let s=t.url.endsWith("/sse")?t.url.replace(/\/sse$/,"/mcp"):t.url;try{let a=(await this.httpRequest(s,e,{jsonrpc:"2.0",id:1,method:"initialize",params:{protocolVersion:"2025-03-26",capabilities:{},clientInfo:{name:"agent-fleet",version:"1.0.0"}}}))?._sessionId;await this.httpRequest(s,e,{jsonrpc:"2.0",method:"notifications/initialized"},a);let r=await this.httpRequest(s,e,{jsonrpc:"2.0",id:2,method:"tools/list"},a),o=[],h=r?.result?.tools??[];for(let d of h)o.push({name:d.name,description:d.description,inputSchema:d.inputSchema});return o}catch(n){return console.warn(`McpManager: HTTP probe failed for ${t.name}:`,n),[]}}async findServerToken(t){if(this.authManager){let a=this.authManager.getToken(t.name);if(a)return a}let e=t.name.replace(/^claude\.ai\s+/i,"").replace(/\s+/g,"_").toUpperCase(),s=[`${e}_API_KEY`,`${e}_API_KEY_MILO`,`${e}_TOKEN`];for(let a of s){let r=process.env[a];if(r)return r}let n=[va.join(Jn(),".openclaw","workspace",".env"),va.join(Jn(),".env")];for(let a of n)try{let r=lr.readFileSync(a,"utf8");for(let o of ge(r)){let c=o.trim().match(/^(?:export\s+)?([A-Za-z_]\w*)=(.*)$/);if(c){let l=c[1],h=c[2].replace(/^["']|["']$/g,"");if(s.includes(l))return h}}}catch{}}httpRequest(t,e,s,n){return new Promise((a,r)=>{let o=JSON.stringify(s),c=new URL(t),l=c.protocol==="https:",h={"Content-Type":"application/json",Accept:"application/json, text/event-stream",Authorization:`Bearer ${e}`,"Content-Length":String(Buffer.byteLength(o))};n&&(h["mcp-session-id"]=n);let d={hostname:c.hostname,port:c.port||(l?443:80),path:c.pathname+c.search,method:"POST",headers:h},p=(l?or:dn).request(d,f=>{let g="";f.on("data",v=>{g+=v.toString()}),f.on("end",()=>{let v=f.headers["mcp-session-id"];if((f.headers["content-type"]??"").includes("text/event-stream")){for(let k of ge(g))if(k.startsWith("data: "))try{let y=JSON.parse(k.slice(6));v&&(y._sessionId=v),a(y);return}catch{}a(null)}else try{let k=JSON.parse(g);v&&(k._sessionId=v),a(k)}catch{a(null)}})});p.on("error",r);let m=window.setTimeout(()=>{p.destroy(),a(null)},15e3);p.on("close",()=>window.clearTimeout(m)),p.write(o),p.end()})}};var Kl={"every 5m":"*/5 * * * *","every 10m":"*/10 * * * *","every 15m":"*/15 * * * *","every 30m":"*/30 * * * *","every 1h":"0 * * * *","every 2h":"0 */2 * * *",hourly:"0 * * * *","daily at 9am":"0 9 * * *","daily at 6pm":"0 18 * * *","weekdays at 9am":"0 9 * * 1-5","weekly on monday":"0 9 * * 1","monthly on 1st":"0 9 1 * *"},un=class{constructor(t,e){this.maxConcurrentRuns=t;this.callbacks=e}jobs=new Map;activeRuns=0;queue=[];paused=!1;setMaxConcurrentRuns(t){this.maxConcurrentRuns=t}parseSchedule(t){return Kl[t.toLowerCase()]??t}toLocalISOString(t){let e=s=>String(s).padStart(2,"0");return`${t.getFullYear()}-${e(t.getMonth()+1)}-${e(t.getDate())}T${e(t.getHours())}:${e(t.getMinutes())}:${e(t.getSeconds())}`}async registerTask(t){if(this.unregisterTask(t.taskId),!(!t.enabled||t.type==="immediate"))try{if(t.type==="once"&&t.runAt){let e=new pe(new Date(t.runAt),{name:t.taskId,catch:!0},()=>{this.enqueue({task:t,reason:"scheduled"})});this.jobs.set(t.taskId,e),await this.callbacks.onTaskScheduled(t,t.runAt);return}if(t.type==="recurring"&&t.schedule){let e=this.parseSchedule(t.schedule),s=new pe(e,{name:t.taskId,catch:!0,protect:!0,timezone:Intl.DateTimeFormat().resolvedOptions().timeZone},()=>{this.enqueue({task:t,reason:"scheduled"})});this.jobs.set(t.taskId,s);let n=s.nextRun();await this.callbacks.onTaskScheduled(t,n?this.toLocalISOString(n):void 0)}}catch(e){console.error(`Agent Fleet: Failed to register task "${t.taskId}":`,e)}}unregisterTask(t){let e=this.jobs.get(t);e&&(e.stop(),this.jobs.delete(t))}async loadTasks(t){for(let e of t)await this.registerTask(e)}async handleStartupCatchUp(t){let e=Date.now();for(let s of t)!s.enabled||!s.catchUp||!s.nextRun||new Date(s.nextRun).getTime()t.pause())}resumeAll(){this.paused=!1,this.jobs.forEach(t=>t.resume()),this.processQueue()}shutdown(){this.paused=!0,this.jobs.forEach(t=>t.stop()),this.jobs.clear(),this.queue.length=0}getQueueSize(){return this.queue.length}async processQueue(){if(!this.paused)for(;this.activeRuns0;){let t=this.queue.shift();if(!t)return;this.activeRuns+=1,this.callbacks.onTaskTriggered(t).finally(async()=>{this.activeRuns-=1,await this.processQueue()})}}};var ws=class i{constructor(t,e,s){this.repository=t;this.settings=e;this.executor=new cn(e,t,s),this.mcpManager=new hn(e),s&&this.mcpManager.setAuthManager(s),this.memoryWriter=new qt(t),this.scheduler=new un(e.maxConcurrentRuns,{onTaskTriggered:n=>this.runPendingTask(n),onTaskScheduled:(n,a)=>this.repository.updateTaskRunMetadata(n,{nextRun:a})})}scheduler;executor;mcpManager;snapshot={agents:[],skills:[],tasks:[],channels:[],mcpServers:[],validationIssues:[]};runtimeState=new Map;recentRuns=[];chartRuns=[];static CHART_WINDOW_DAYS=14;statusChangeListeners=new Set;runOutputListeners=new Map;runOutputBuffers=new Map;heartbeatJobs=new Map;reflectionJobs=new Map;reflectionsInFlight=new Set;reflectionRunning=0;reflectionWaiters=[];heartbeatRegisteredAt=0;heartbeatsInFlight=new Set;heartbeatResultHandler;memoryWriter;async initialize(){this.snapshot=await this.repository.loadAll(),await this.repository.migrateAllLegacyMemory(),await this.refreshRunCaches();let t=this.snapshot.tasks.filter(e=>this.repository.getAgentByName(e.agent)?.enabled!==!1);await this.scheduler.loadTasks(t),await this.scheduler.handleStartupCatchUp(t),this.registerHeartbeats(),this.registerReflections(),this.emitStatusChange()}onHeartbeatResult(t){this.heartbeatResultHandler=t}async refreshFromVault(){this.snapshot=await this.repository.loadAll(),await this.rebuildSchedules(),await this.refreshRunCaches(),this.emitStatusChange()}getSnapshot(){return this.snapshot}getRecentRuns(){return this.recentRuns}getChartRuns(){return this.chartRuns}async refreshRunCaches(){this.recentRuns=await this.repository.listRecentRuns();let t=new Date;t.setDate(t.getDate()-(i.CHART_WINDOW_DAYS-1)),this.chartRuns=await this.repository.listRunsSince(t)}getAgentState(t){let e=this.runtimeState.get(t),s=this.snapshot.agents.find(n=>n.name===t);return s&&!s.enabled?{status:"disabled",lastRun:e?.lastRun,currentRunId:e?.currentRunId}:e??{status:"idle"}}getFleetStatus(){let t=new Date,e=o=>String(o).padStart(2,"0"),s=`${t.getFullYear()}-${e(t.getMonth()+1)}-${e(t.getDate())}`,n=this.recentRuns.filter(o=>{let c=new Date(o.started);return`${c.getFullYear()}-${e(c.getMonth()+1)}-${e(c.getDate())}`===s}).length,a=Array.from(this.runtimeState.values()).filter(o=>o.status==="running").length,r=this.recentRuns.flatMap(o=>o.approvals??[]).filter(o=>o.status==="pending").length;return{running:a,pending:r,completedToday:n}}subscribe(t){return this.statusChangeListeners.add(t),()=>this.statusChangeListeners.delete(t)}onRunOutput(t,e){let s=this.runOutputListeners.get(t);s||(s=new Set,this.runOutputListeners.set(t,s)),s.add(e);let n=this.runOutputBuffers.get(t);return n&&e(n),()=>{this.runOutputListeners.get(t)?.delete(e)}}getRunOutputBuffer(t){return this.runOutputBuffers.get(t)??""}async handleVaultChange(t){await this.repository.loadFile(t),this.snapshot=this.repository.getSnapshot(),await this.rebuildSchedules(),await this.refreshRunCaches(),this.emitStatusChange()}async handleVaultDelete(t){this.repository.removeFile(t),this.snapshot=this.repository.getSnapshot(),await this.rebuildSchedules(),await this.refreshRunCaches(),this.emitStatusChange()}abortedAgents=new Set;abortAgentRun(t){let e=this.executor.abortAgent(t);return e&&(this.abortedAgents.add(t),this.runtimeState.set(t,{status:"idle"}),this.emitStatusChange()),e}wasAborted(t){return this.abortedAgents.has(t)}consumeAborted(t){let e=this.abortedAgents.has(t);return this.abortedAgents.delete(t),e}async runTaskNow(t,e){await this.scheduler.enqueue({task:{...t,type:"immediate"},reason:"manual",promptOverride:e})}async runAgentNow(t,e){let s=t.heartbeatBody.trim()&&e==="Run now and summarize the current state."?t.heartbeatBody.trim():e,n=s===t.heartbeatBody.trim()&&t.heartbeatBody.trim().length>0,a={filePath:"",taskId:n?`heartbeat-${Date.now()}`:`manual-${Date.now()}`,agent:t.name,type:"immediate",priority:"medium",enabled:!0,created:new Date().toISOString(),runCount:0,catchUp:!1,tags:n?[...t.tags,"heartbeat"]:t.tags,body:s};await this.scheduler.enqueue({task:a,reason:n?"heartbeat":"manual",promptOverride:s})}async resolveApproval(t,e,s){t.filePath&&(await this.repository.setApprovalDecision(t.filePath,e,s),await this.refreshRunCaches(),this.emitStatusChange())}async pruneOldRuns(){let t=Date.now()-this.settings.runLogRetentionDays*24*60*60*1e3,e=await this.repository.listRecentRuns(500);for(let s of e)s.filePath&&new Date(s.started).getTime()this.repository.getAgentByName(t.agent)?.enabled!==!1)),this.scheduler.resumeAll(),this.registerHeartbeats(),this.registerReflections()}shutdown(){for(let[,t]of this.heartbeatJobs)t.stop();this.heartbeatJobs.clear(),this.heartbeatsInFlight.clear();for(let[,t]of this.reflectionJobs)t.stop();this.reflectionJobs.clear(),this.reflectionsInFlight.clear(),this.scheduler.shutdown()}registerHeartbeats(){for(let[,t]of this.heartbeatJobs)t.stop();this.heartbeatJobs.clear(),this.heartbeatRegisteredAt=Date.now();for(let t of this.snapshot.agents)if(!(!t.enabled||!t.heartbeatEnabled||!t.heartbeatSchedule.trim()||!t.heartbeatBody.trim()))try{let e=new pe(t.heartbeatSchedule,{name:`heartbeat:${t.name}`,catch:!0,protect:!0,timezone:Intl.DateTimeFormat().resolvedOptions().timeZone},()=>{this.runHeartbeat(t.name)});this.heartbeatJobs.set(t.name,e)}catch(e){console.error(`Agent Fleet: failed to register heartbeat for "${t.name}":`,e)}}async runHeartbeat(t){if(!(Date.now()-this.heartbeatRegisteredAt<1e4)&&!this.heartbeatsInFlight.has(t)){this.heartbeatsInFlight.add(t);try{let e=this.repository.getAgentByName(t);if(!e||!e.enabled||!e.heartbeatBody.trim())return;let s={filePath:"",taskId:`heartbeat-${Date.now()}`,agent:e.name,type:"immediate",priority:"medium",enabled:!0,created:new Date().toISOString(),runCount:0,catchUp:!1,tags:[...e.tags,"heartbeat"],body:e.heartbeatBody.trim()};await this.scheduler.enqueue({task:s,reason:"heartbeat",promptOverride:e.heartbeatBody.trim()})}finally{this.heartbeatsInFlight.delete(t)}}}getNextHeartbeat(t){let e=this.heartbeatJobs.get(t);return e?e.nextRun()??null:null}registerReflections(){for(let[,t]of this.reflectionJobs)t.stop();this.reflectionJobs.clear();for(let t of this.snapshot.agents){if(!t.enabled||!t.memory||!t.reflection.enabled)continue;let e=t.reflection.schedule.trim();if(e)try{let s=new pe(e,{name:`reflection:${t.name}`,catch:!0,protect:!0,timezone:Intl.DateTimeFormat().resolvedOptions().timeZone},()=>{this.runReflection(t.name)});this.reflectionJobs.set(t.name,s)}catch(s){console.error(`Agent Fleet: failed to register reflection for "${t.name}":`,s)}}}getNextReflection(t){return this.reflectionJobs.get(t)?.nextRun()??null}async runReflectionNow(t){return this.runReflection(t)}async listPendingProposals(){return(await this.repository.listProposals()).filter(e=>e.status==="pending")}async acceptProposal(t){let e=await this.repository.readProposal(t);if(!e)return{ok:!1,message:"Proposal not found."};try{let s=await this.repository.applyProposal(e);return s?(await this.repository.setProposalStatus(t,"accepted"),await this.refreshFromVault(),{ok:!0,message:`Applied \u2192 ${s}`}):{ok:!1,message:"Couldn't apply \u2014 target skill not found; proposal left pending."}}catch(s){return{ok:!1,message:`Failed to apply: ${s instanceof Error?s.message:String(s)}`}}}async rejectProposal(t){await this.repository.setProposalStatus(t,"rejected"),this.emitStatusChange()}async withReflectionSlot(t){let e=Math.max(1,this.settings.maxConcurrentRuns);this.reflectionRunning>=e?await new Promise(s=>this.reflectionWaiters.push(s)):this.reflectionRunning++;try{return await t()}finally{let s=this.reflectionWaiters.shift();s?s():this.reflectionRunning--}}async runReflection(t){let e=this.repository.getAgentByName(t);if(!e||!e.enabled||!e.memory)return{ok:!1,message:"Agent not found, disabled, or memory off."};if(this.reflectionsInFlight.has(t))return{ok:!1,message:"A reflection is already in progress."};this.reflectionsInFlight.add(t);let s=new Date().toISOString();try{await this.repository.migrateLegacyMemory(t);let n=await this.repository.readWorkingMemory(t),a=n?Ge(n.sections):"",r=await this.repository.readRecentRaw(t,2),o=ar({agentName:t,workingMemoryBody:a,recentRaw:r,tokenBudget:e.memoryTokenBudget}),c={filePath:"",taskId:`reflection-${Date.now()}`,agent:e.name,type:"immediate",priority:"low",enabled:!0,created:s,runCount:0,catchUp:!1,tags:[...e.tags,"reflection"],body:o,model:e.reflection.model||void 0},l=await this.withReflectionSlot(()=>this.executor.execute(e,c,o,void 0,{suppressMemoryCapture:!0})),h=ir(l.outputText),d=await this.memoryWriter.reflect(e,h.sections,s);if(h.candidates.length>0){let u=await this.repository.readCandidates(t),p=rr(u,h.candidates,s);await this.repository.writeCandidates(t,p),await this.generateProposals(e,p,s)}return await this.refreshRunCaches(),this.emitStatusChange(),d?{ok:!0,message:"Reflection complete \u2014 working memory consolidated."}:{ok:!1,message:"Reflection produced no memory block; working memory left unchanged."}}catch(n){let a=n instanceof Error?n.message:String(n);return console.warn(`Agent Fleet: reflection failed for "${t}":`,n),{ok:!1,message:`Reflection failed: ${a}`}}finally{this.reflectionsInFlight.delete(t)}}async generateProposals(t,e,s){if(!t.reflection.proposeSkills)return;let n=t.reflection.recurrenceThreshold||3;for(let a of e){if(a.proposed||a.occurrences{window.clearTimeout(p),u()}),a.on("close",()=>{window.clearTimeout(p),u()});try{a.stdin.write(JSON.stringify({jsonrpc:"2.0",id:1,method:"initialize",params:{protocolVersion:"2024-11-05",capabilities:{},clientInfo:{name:"agent-fleet",version:"1.0.0"}}})+` +`)}catch{window.clearTimeout(p),u()}})}async probeHttpServer(t){let e=await this.findServerToken(t);if(!e)return[];let s=t.url.endsWith("/sse")?t.url.replace(/\/sse$/,"/mcp"):t.url;try{let a=(await this.httpRequest(s,e,{jsonrpc:"2.0",id:1,method:"initialize",params:{protocolVersion:"2025-03-26",capabilities:{},clientInfo:{name:"agent-fleet",version:"1.0.0"}}}))?._sessionId;await this.httpRequest(s,e,{jsonrpc:"2.0",method:"notifications/initialized"},a);let r=await this.httpRequest(s,e,{jsonrpc:"2.0",id:2,method:"tools/list"},a),o=[],h=r?.result?.tools??[];for(let d of h)o.push({name:d.name,description:d.description,inputSchema:d.inputSchema});return o}catch(n){return console.warn(`McpManager: HTTP probe failed for ${t.name}:`,n),[]}}async findServerToken(t){if(this.authManager){let a=this.authManager.getToken(t.name);if(a)return a}let e=t.name.replace(/^claude\.ai\s+/i,"").replace(/\s+/g,"_").toUpperCase(),s=[`${e}_API_KEY`,`${e}_API_KEY_MILO`,`${e}_TOKEN`];for(let a of s){let r=process.env[a];if(r)return r}let n=[xa.join(ea(),".openclaw","workspace",".env"),xa.join(ea(),".env")];for(let a of n)try{let r=yr.readFileSync(a,"utf8");for(let o of ye(r)){let c=o.trim().match(/^(?:export\s+)?([A-Za-z_]\w*)=(.*)$/);if(c){let l=c[1],h=c[2].replace(/^["']|["']$/g,"");if(s.includes(l))return h}}}catch{}}httpRequest(t,e,s,n){return new Promise((a,r)=>{let o=JSON.stringify(s),c=new URL(t),l=c.protocol==="https:",h={"Content-Type":"application/json",Accept:"application/json, text/event-stream",Authorization:`Bearer ${e}`,"Content-Length":String(Buffer.byteLength(o))};n&&(h["mcp-session-id"]=n);let d={hostname:c.hostname,port:c.port||(l?443:80),path:c.pathname+c.search,method:"POST",headers:h},p=(l?gr:un).request(d,f=>{let g="";f.on("data",y=>{g+=y.toString()}),f.on("end",()=>{let y=f.headers["mcp-session-id"];if((f.headers["content-type"]??"").includes("text/event-stream")){for(let k of ye(g))if(k.startsWith("data: "))try{let w=JSON.parse(k.slice(6));y&&(w._sessionId=y),a(w);return}catch{}a(null)}else try{let k=JSON.parse(g);y&&(k._sessionId=y),a(k)}catch{a(null)}})});p.on("error",r);let m=window.setTimeout(()=>{p.destroy(),a(null)},15e3);p.on("close",()=>window.clearTimeout(m)),p.write(o),p.end()})}};var dc={"every 5m":"*/5 * * * *","every 10m":"*/10 * * * *","every 15m":"*/15 * * * *","every 30m":"*/30 * * * *","every 1h":"0 * * * *","every 2h":"0 */2 * * *",hourly:"0 * * * *","daily at 9am":"0 9 * * *","daily at 6pm":"0 18 * * *","weekdays at 9am":"0 9 * * 1-5","weekly on monday":"0 9 * * 1","monthly on 1st":"0 9 1 * *"},mn=class{constructor(t,e){this.maxConcurrentRuns=t;this.callbacks=e}jobs=new Map;activeRuns=0;queue=[];paused=!1;setMaxConcurrentRuns(t){this.maxConcurrentRuns=t}parseSchedule(t){return dc[t.toLowerCase()]??t}toLocalISOString(t){let e=s=>String(s).padStart(2,"0");return`${t.getFullYear()}-${e(t.getMonth()+1)}-${e(t.getDate())}T${e(t.getHours())}:${e(t.getMinutes())}:${e(t.getSeconds())}`}async registerTask(t){if(this.unregisterTask(t.taskId),!(!t.enabled||t.type==="immediate"))try{if(t.type==="once"&&t.runAt){let e=new pe(new Date(t.runAt),{name:t.taskId,catch:!0},()=>{this.enqueue({task:t,reason:"scheduled"})});this.jobs.set(t.taskId,e),await this.callbacks.onTaskScheduled(t,t.runAt);return}if(t.type==="recurring"&&t.schedule){let e=this.parseSchedule(t.schedule),s=new pe(e,{name:t.taskId,catch:!0,protect:!0,timezone:Intl.DateTimeFormat().resolvedOptions().timeZone},()=>{this.enqueue({task:t,reason:"scheduled"})});this.jobs.set(t.taskId,s);let n=s.nextRun();await this.callbacks.onTaskScheduled(t,n?this.toLocalISOString(n):void 0)}}catch(e){console.error(`Agent Fleet: Failed to register task "${t.taskId}":`,e)}}unregisterTask(t){let e=this.jobs.get(t);e&&(e.stop(),this.jobs.delete(t))}async loadTasks(t){for(let e of t)await this.registerTask(e)}async handleStartupCatchUp(t){let e=Date.now();for(let s of t)!s.enabled||!s.catchUp||!s.nextRun||new Date(s.nextRun).getTime()t.pause())}resumeAll(){this.paused=!1,this.jobs.forEach(t=>t.resume()),this.processQueue()}shutdown(){this.paused=!0,this.jobs.forEach(t=>t.stop()),this.jobs.clear(),this.queue.length=0}getQueueSize(){return this.queue.length}async processQueue(){if(!this.paused)for(;this.activeRuns0;){let t=this.queue.shift();if(!t)return;this.activeRuns+=1,this.callbacks.onTaskTriggered(t).finally(async()=>{this.activeRuns-=1,await this.processQueue()})}}};var ks=class i{constructor(t,e,s){this.repository=t;this.settings=e;this.executor=new hn(e,t,s),this.mcpManager=new pn(e),s&&this.mcpManager.setAuthManager(s),this.memoryWriter=new zt(t),this.scheduler=new mn(e.maxConcurrentRuns,{onTaskTriggered:n=>this.runPendingTask(n),onTaskScheduled:(n,a)=>this.repository.updateTaskRunMetadata(n,{nextRun:a})})}scheduler;executor;mcpManager;snapshot={agents:[],skills:[],tasks:[],channels:[],mcpServers:[],validationIssues:[]};runtimeState=new Map;recentRuns=[];chartRuns=[];static CHART_WINDOW_DAYS=14;recentUsage=[];statusChangeListeners=new Set;runOutputListeners=new Map;runOutputBuffers=new Map;heartbeatJobs=new Map;reflectionJobs=new Map;reflectionsInFlight=new Set;reflectionRunning=0;reflectionWaiters=[];heartbeatRegisteredAt=0;heartbeatsInFlight=new Set;channelResultHandler;memoryWriter;async initialize(){this.snapshot=await this.repository.loadAll(),await this.repository.migrateAllLegacyMemory(),await this.refreshRunCaches();let t=this.snapshot.tasks.filter(e=>this.repository.getAgentByName(e.agent)?.enabled!==!1);await this.scheduler.loadTasks(t),await this.scheduler.handleStartupCatchUp(t),this.registerHeartbeats(),this.registerReflections(),this.emitStatusChange()}onChannelResult(t){this.channelResultHandler=t}async refreshFromVault(){this.snapshot=await this.repository.loadAll(),await this.rebuildSchedules(),await this.refreshRunCaches(),this.emitStatusChange()}getSnapshot(){return this.snapshot}getRecentRuns(){return this.recentRuns}getChartRuns(){return this.chartRuns}async refreshRunCaches(){this.recentRuns=await this.repository.listRecentRuns();let t=new Date;t.setDate(t.getDate()-(i.CHART_WINDOW_DAYS-1)),this.chartRuns=await this.repository.listRunsSince(t),this.recentUsage=await this.repository.readUsageSince(t)}getUsageRecords(){return this.recentUsage}recordUsage(t){this.recentUsage.push(t),this.repository.appendUsage(t).catch(e=>{console.warn("Agent Fleet: failed to append usage record",e)}),this.emitStatusChange()}getAgentState(t){let e=this.runtimeState.get(t),s=this.snapshot.agents.find(n=>n.name===t);return s&&!s.enabled?{status:"disabled",lastRun:e?.lastRun,currentRunId:e?.currentRunId}:e??{status:"idle"}}getFleetStatus(){let t=new Date,e=o=>String(o).padStart(2,"0"),s=`${t.getFullYear()}-${e(t.getMonth()+1)}-${e(t.getDate())}`,n=this.recentRuns.filter(o=>{let c=new Date(o.started);return`${c.getFullYear()}-${e(c.getMonth()+1)}-${e(c.getDate())}`===s}).length,a=Array.from(this.runtimeState.values()).filter(o=>o.status==="running").length,r=this.recentRuns.flatMap(o=>o.approvals??[]).filter(o=>o.status==="pending").length;return{running:a,pending:r,completedToday:n}}subscribe(t){return this.statusChangeListeners.add(t),()=>this.statusChangeListeners.delete(t)}onRunOutput(t,e){let s=this.runOutputListeners.get(t);s||(s=new Set,this.runOutputListeners.set(t,s)),s.add(e);let n=this.runOutputBuffers.get(t);return n&&e(n),()=>{this.runOutputListeners.get(t)?.delete(e)}}getRunOutputBuffer(t){return this.runOutputBuffers.get(t)??""}async handleVaultChange(t){await this.repository.loadFile(t),this.snapshot=this.repository.getSnapshot(),await this.rebuildSchedules(),await this.refreshRunCaches(),this.emitStatusChange()}async handleVaultDelete(t){this.repository.removeFile(t),this.snapshot=this.repository.getSnapshot(),await this.rebuildSchedules(),await this.refreshRunCaches(),this.emitStatusChange()}abortedAgents=new Set;abortAgentRun(t){let e=this.executor.abortAgent(t);return e&&(this.abortedAgents.add(t),this.runtimeState.set(t,{status:"idle"}),this.emitStatusChange()),e}wasAborted(t){return this.abortedAgents.has(t)}consumeAborted(t){let e=this.abortedAgents.has(t);return this.abortedAgents.delete(t),e}async runTaskNow(t,e){await this.scheduler.enqueue({task:{...t,type:"immediate"},reason:"manual",promptOverride:e})}async runAgentNow(t,e){let s=t.heartbeatBody.trim()&&e==="Run now and summarize the current state."?t.heartbeatBody.trim():e,n=s===t.heartbeatBody.trim()&&t.heartbeatBody.trim().length>0,a={filePath:"",taskId:n?`heartbeat-${Date.now()}`:`manual-${Date.now()}`,agent:t.name,type:"immediate",priority:"medium",enabled:!0,created:new Date().toISOString(),runCount:0,catchUp:!1,tags:n?[...t.tags,"heartbeat"]:t.tags,body:s};await this.scheduler.enqueue({task:a,reason:n?"heartbeat":"manual",promptOverride:s})}async resolveApproval(t,e,s){t.filePath&&(await this.repository.setApprovalDecision(t.filePath,e,s),await this.refreshRunCaches(),this.emitStatusChange())}async pruneOldRuns(){let t=Date.now()-this.settings.runLogRetentionDays*24*60*60*1e3,e=await this.repository.listRecentRuns(500);for(let s of e)s.filePath&&new Date(s.started).getTime()this.repository.getAgentByName(t.agent)?.enabled!==!1)),this.scheduler.resumeAll(),this.registerHeartbeats(),this.registerReflections()}shutdown(){for(let[,t]of this.heartbeatJobs)t.stop();this.heartbeatJobs.clear(),this.heartbeatsInFlight.clear();for(let[,t]of this.reflectionJobs)t.stop();this.reflectionJobs.clear(),this.reflectionsInFlight.clear(),this.scheduler.shutdown()}registerHeartbeats(){for(let[,t]of this.heartbeatJobs)t.stop();this.heartbeatJobs.clear(),this.heartbeatRegisteredAt=Date.now();for(let t of this.snapshot.agents)if(!(!t.enabled||!t.heartbeatEnabled||!t.heartbeatSchedule.trim()||!t.heartbeatBody.trim()))try{let e=new pe(t.heartbeatSchedule,{name:`heartbeat:${t.name}`,catch:!0,protect:!0,timezone:Intl.DateTimeFormat().resolvedOptions().timeZone},()=>{this.runHeartbeat(t.name)});this.heartbeatJobs.set(t.name,e)}catch(e){console.error(`Agent Fleet: failed to register heartbeat for "${t.name}":`,e)}}async runHeartbeat(t){if(!(Date.now()-this.heartbeatRegisteredAt<1e4)&&!this.heartbeatsInFlight.has(t)){this.heartbeatsInFlight.add(t);try{let e=this.repository.getAgentByName(t);if(!e||!e.enabled||!e.heartbeatBody.trim())return;let s={filePath:"",taskId:`heartbeat-${Date.now()}`,agent:e.name,type:"immediate",priority:"medium",enabled:!0,created:new Date().toISOString(),runCount:0,catchUp:!1,tags:[...e.tags,"heartbeat"],body:e.heartbeatBody.trim()};await this.scheduler.enqueue({task:s,reason:"heartbeat",promptOverride:e.heartbeatBody.trim()})}finally{this.heartbeatsInFlight.delete(t)}}}getNextHeartbeat(t){let e=this.heartbeatJobs.get(t);return e?e.nextRun()??null:null}registerReflections(){for(let[,t]of this.reflectionJobs)t.stop();this.reflectionJobs.clear();for(let t of this.snapshot.agents){if(!t.enabled||!t.memory||!t.reflection.enabled)continue;let e=t.reflection.schedule.trim();if(e)try{let s=new pe(e,{name:`reflection:${t.name}`,catch:!0,protect:!0,timezone:Intl.DateTimeFormat().resolvedOptions().timeZone},()=>{this.runReflection(t.name)});this.reflectionJobs.set(t.name,s)}catch(s){console.error(`Agent Fleet: failed to register reflection for "${t.name}":`,s)}}}getNextReflection(t){return this.reflectionJobs.get(t)?.nextRun()??null}async runReflectionNow(t){return this.runReflection(t)}async listPendingProposals(){return(await this.repository.listProposals()).filter(e=>e.status==="pending")}async acceptProposal(t){let e=await this.repository.readProposal(t);if(!e)return{ok:!1,message:"Proposal not found."};try{let s=await this.repository.applyProposal(e);return s?(await this.repository.setProposalStatus(t,"accepted"),await this.refreshFromVault(),{ok:!0,message:`Applied \u2192 ${s}`}):{ok:!1,message:"Couldn't apply \u2014 target skill not found; proposal left pending."}}catch(s){return{ok:!1,message:`Failed to apply: ${s instanceof Error?s.message:String(s)}`}}}async rejectProposal(t){await this.repository.setProposalStatus(t,"rejected"),this.emitStatusChange()}async withReflectionSlot(t){let e=Math.max(1,this.settings.maxConcurrentRuns);this.reflectionRunning>=e?await new Promise(s=>this.reflectionWaiters.push(s)):this.reflectionRunning++;try{return await t()}finally{let s=this.reflectionWaiters.shift();s?s():this.reflectionRunning--}}async runReflection(t){let e=this.repository.getAgentByName(t);if(!e||!e.enabled||!e.memory)return{ok:!1,message:"Agent not found, disabled, or memory off."};if(this.reflectionsInFlight.has(t))return{ok:!1,message:"A reflection is already in progress."};this.reflectionsInFlight.add(t);let s=new Date().toISOString(),n=s,a=`reflection-${Date.now()}`;this.runtimeState.set(t,{status:"running",currentTaskId:a,runStarted:s}),this.runOutputBuffers.set(t,""),this.emitStatusChange();try{await this.repository.migrateLegacyMemory(t);let r=await this.repository.readWorkingMemory(t),o=r?Ke(r.sections):"",c=await this.repository.readRecentRaw(t,2),l=pr({agentName:t,workingMemoryBody:o,recentRaw:c,tokenBudget:e.memoryTokenBudget}),h={filePath:"",taskId:a,agent:e.name,type:"immediate",priority:"low",enabled:!0,created:n,runCount:0,catchUp:!1,tags:[...e.tags,"reflection"],body:l,model:e.reflection.model||void 0},d=await this.withReflectionSlot(()=>this.executor.execute(e,h,l,v=>{let k=this.runOutputBuffers.get(t)??"";this.runOutputBuffers.set(t,k+v);let w=this.runOutputListeners.get(t);if(w)for(let S of w)S(v)},{suppressMemoryCapture:!0})),u=mr(d.outputText),p=await this.memoryWriter.reflect(e,u.sections,n);if(u.candidates.length>0){let v=await this.repository.readCandidates(t),k=fr(v,u.candidates,n);await this.repository.writeCandidates(t,k),await this.generateProposals(e,k,n)}let m=p?"Reflection complete \u2014 working memory consolidated.":"Reflection produced no memory block; working memory left unchanged.",f=d.exitCode===0||d.exitCode===null?"success":"failure",g={runId:d.runId,agent:e.name,task:a,status:f,started:s,completed:new Date().toISOString(),durationSeconds:d.durationSeconds,tokensUsed:d.tokensUsed,costUsd:d.costUsd,model:d.resolvedModel||e.reflection.model||e.model,modelSource:d.modelSource,concreteModel:d.concreteModel,exitCode:d.exitCode,tags:Array.from(new Set([...e.tags,"reflection"])),prompt:d.prompt,output:d.outputText,toolsUsed:d.toolsUsed.map(v=>`${v.tool}${v.command?`: ${v.command}`:""}`),finalResult:m,stderr:d.stderr},y=await this.repository.writeRunLog(g);return await this.refreshRunCaches(),this.runtimeState.set(t,{status:f==="success"?"idle":"error",currentRunId:d.runId,lastRun:{...g,filePath:y}}),{ok:p,message:m}}catch(r){let o=r instanceof Error?r.message:String(r);console.warn(`Agent Fleet: reflection failed for "${t}":`,r);let c={runId:(0,Sa.randomUUID)(),agent:t,task:a,status:"failure",started:s,completed:new Date().toISOString(),durationSeconds:Math.round((Date.now()-new Date(s).getTime())/1e3),model:e.reflection.model||e.model,exitCode:1,tags:Array.from(new Set([...e.tags,"reflection"])),prompt:"",output:`Reflection failed: ${o}`,toolsUsed:[]},l=c;try{let h=await this.repository.writeRunLog(c);await this.refreshRunCaches(),l={...c,filePath:h}}catch(h){console.warn(`Agent Fleet: failed to write reflection run log for "${t}"`,h)}return this.runtimeState.set(t,{status:"error",lastRun:l}),{ok:!1,message:`Reflection failed: ${o}`}}finally{this.reflectionsInFlight.delete(t),this.runOutputBuffers.delete(t),this.runOutputListeners.delete(t),this.emitStatusChange()}}async generateProposals(t,e,s){if(!t.reflection.proposeSkills)return;let n=t.reflection.recurrenceThreshold||3;for(let a of e){if(a.proposed||a.occurrences ${a.pattern} -Document the reliable steps to handle this so future runs don't rediscover them.`};try{await this.repository.writeProposal(o),a.proposed=!0,await this.repository.writeCandidates(t.name,e)}catch(c){console.warn(`Agent Fleet: failed to write proposal for "${t.name}"`,c)}}}async runPendingTask({task:t,promptOverride:e}){let s=this.repository.getAgentByName(t.agent);if(!s||!s.enabled)return;let n=new Date().toISOString();this.runtimeState.set(s.name,{status:"running",currentTaskId:t.taskId,runStarted:n}),this.runOutputBuffers.set(s.name,""),this.emitStatusChange();try{let a=await this.executor.execute(s,t,e,u=>{let p=this.runOutputBuffers.get(s.name)??"";this.runOutputBuffers.set(s.name,p+u);let m=this.runOutputListeners.get(s.name);if(m)for(let f of m)f(u)}),r=this.consumeAborted(s.name),o=r?[]:this.buildApprovals(s,a.toolsUsed),c=r?"cancelled":this.resolveRunStatus(a,o),l={runId:a.runId,agent:s.name,task:t.taskId,status:c,started:n,completed:new Date().toISOString(),durationSeconds:a.durationSeconds,tokensUsed:a.tokensUsed,costUsd:a.costUsd,model:a.resolvedModel||s.model,modelSource:a.modelSource,concreteModel:a.concreteModel,exitCode:a.exitCode,tags:Array.from(new Set([...s.tags,...t.tags])),prompt:a.prompt,output:a.outputText,toolsUsed:a.toolsUsed.map(u=>`${u.tool}${u.command?`: ${u.command}`:""}`),finalResult:a.finalResult,stderr:a.stderr,approvals:o},h=await this.repository.writeRunLog(l);if(await this.repository.updateTaskRunMetadata(t,{lastRun:n,runCount:t.runCount+1}),s.memory){let p=t.tags.includes("heartbeat")?"heartbeat":`task:${t.taskId}`,m=Ws(a.outputText);try{await this.memoryWriter.capture(s,m,p,new Date().toISOString())}catch(f){console.warn(`Agent Fleet: failed to append memory for "${s.name}"`,f)}}if(t.tags.includes("heartbeat")&&!r&&s.heartbeatChannel&&l.output.trim())try{this.heartbeatResultHandler?.(s.name,s.heartbeatChannel,l.output)}catch(u){console.warn(`Agent Fleet: heartbeat channel delivery failed for ${s.name}`,u)}r&&(l.output="Task was manually stopped."),await this.refreshRunCaches(),this.runtimeState.set(s.name,{status:r||c==="success"?"idle":c==="pending_approval"?"pending":"error",currentRunId:a.runId,lastRun:{...l,filePath:h}}),r||this.notify(l)}catch(a){let r=this.consumeAborted(s.name),o=r?"cancelled":"failure",c=Pt(t,s,this.settings),l={runId:(0,cr.randomUUID)(),agent:s.name,task:t.taskId,status:o,started:n,completed:new Date().toISOString(),durationSeconds:Math.round((Date.now()-new Date(n).getTime())/1e3),model:c.value||s.model,modelSource:c.source,exitCode:r?-1:1,tags:Array.from(new Set([...s.tags,...t.tags])),prompt:e??t.body,output:r?"Task was manually stopped.":a instanceof Error?a.message:String(a),toolsUsed:[]},h=await this.repository.writeRunLog(l);await this.refreshRunCaches(),this.runtimeState.set(s.name,{status:r?"idle":"error",lastRun:{...l,filePath:h}}),r||this.notify(l)}finally{if(s.memory)try{await this.memoryWriter.drainPending(s,new Date().toISOString())}catch(a){console.warn(`Agent Fleet: failed to drain pending memory for "${s.name}"`,a)}this.runOutputBuffers.delete(s.name),this.runOutputListeners.delete(s.name),this.emitStatusChange()}}buildApprovals(t,e){let s=e.filter(n=>t.approvalRequired.includes(n.tool)).map(n=>({tool:n.tool,command:n.command,reason:n.reason,status:"pending"}));return s.length>0?s:void 0}resolveRunStatus(t,e){return e?.length?"pending_approval":t.timedOut?"timeout":t.exitCode===0?"success":"failure"}notify(t){if(this.settings.notificationLevel==="none"||this.settings.notificationLevel==="failures-only"&&t.status==="success")return;let s=(ge(t.output).map(a=>a.trim()).find(a=>a&&!a.startsWith("{")&&!a.startsWith("["))??"").slice(0,120)||t.status,n=t.status==="success"?`\u2705 ${t.agent}: ${s}`:t.status==="pending_approval"?`\u{1F535} ${t.agent} needs approval: ${(t.approvals??[])[0]?.tool??"tool action"}`:`\u274C ${t.agent}: ${s}`;new dr.Notice(n,t.status==="success"?5e3:0)}emitStatusChange(){for(let t of this.statusChangeListeners)t()}};function Jl(i){return i.toLowerCase().replace(/[^a-z0-9-]/g,"-").replace(/-{2,}/g,"-").replace(/^-|-$/g,"")}function bs(i,t){return`${i}-${Jl(t)}`}var zt="af-channel-cred",ks="af-mcp-secret",pn=class{constructor(t){this.storage=t;t||console.warn("Agent Fleet: SecretStorage unavailable (Obsidian < 1.11.4). Secrets will use plaintext fallback.")}get available(){return!!this.storage}setJson(t,e,s){if(!this.storage)return;let n=bs(t,e);this.storage.setSecret(n,JSON.stringify(s))}getJson(t,e){if(!this.storage)return null;let s=bs(t,e),n=this.storage.getSecret(s);if(!n)return null;try{return JSON.parse(n)}catch{return null}}setString(t,e,s){if(!this.storage)return;let n=bs(t,e);this.storage.setSecret(n,s)}getString(t,e){if(!this.storage)return null;let s=bs(t,e);return this.storage.getSecret(s)||null}delete(t,e){if(!this.storage)return;let s=bs(t,e);this.storage.setSecret(s,"")}listByPrefix(t){return this.storage?this.storage.listSecrets().filter(e=>e.startsWith(t+"-")):[]}};var mn=class{tokens=new Map;oauthTokens=new Map;secretStore;setSecretStore(t){this.secretStore=t}storeProbeToken(t,e){this.tokens.set(t,e)}storeStaticToken(t,e){this.tokens.set(t,e),this.secretStore?.setJson(ks,t,{accessToken:e})}storeOAuthToken(t,e){this.oauthTokens.set(t,e),this.tokens.set(t,e.accessToken),this.secretStore?.setJson(ks,t,{accessToken:e.accessToken,refreshToken:e.refreshToken,expiresAt:e.expiresAt,tokenEndpoint:e.tokenEndpoint,clientId:e.clientId,resource:e.resource})}hydrate(t){if(!this.secretStore)return null;let e=this.secretStore.getJson(ks,t);return e?.accessToken?(this.tokens.set(t,e.accessToken),e.tokenEndpoint&&e.clientId&&e.resource&&this.oauthTokens.set(t,{accessToken:e.accessToken,refreshToken:e.refreshToken,expiresAt:e.expiresAt,tokenEndpoint:e.tokenEndpoint,clientId:e.clientId,resource:e.resource}),e):null}getToken(t){return this.tokens.get(t)??this.hydrate(t)?.accessToken??void 0}getOAuthToken(t){return this.oauthTokens.has(t)?this.oauthTokens.get(t):(this.hydrate(t),this.oauthTokens.get(t))}hasToken(t){return this.tokens.has(t)||!!this.hydrate(t)}removeToken(t){this.tokens.delete(t),this.oauthTokens.delete(t),this.secretStore?.delete(ks,t)}getExpiringTokens(t=5*6e4){let e=new Map,s=Date.now();for(let[n,a]of this.oauthTokens)a.refreshToken&&a.expiresAt&&a.expiresAt-s0?t:i.WATCHDOG_FALLBACK_MINUTES)*60*1e3}armWatchdog(){this.clearWatchdog();let t=this.getWatchdogMs();this.watchdogTimer=window.setTimeout(()=>{if(this.watchdogTimer=null,!this.isStreaming)return;this.activeOnEvent?.({type:"error",content:"",errorMessage:`no response from the CLI for ${Math.round(t/6e4)} minutes \u2014 giving up`});let e=new Error("Watchdog timeout");this.handleProcessError(e);try{this.process?.kill()}catch{}},t)}clearWatchdog(){this.watchdogTimer&&(window.clearTimeout(this.watchdogTimer),this.watchdogTimer=null)}currentToolName;activityListeners=new Set;onActivityChange(t){return this.activityListeners.add(t),t(),()=>{this.activityListeners.delete(t)}}emitActivity(){for(let t of this.activityListeners)t()}setStreaming(t){this.isStreaming!==t&&(this.isStreaming=t,t||(this.currentToolName=void 0),this.emitActivity())}setCurrentTool(t){this.currentToolName!==t&&(this.currentToolName=t,this.emitActivity())}stats={costTotalUsd:0,turnCount:0};statsListeners=new Set;onStatsChange(t){return this.statsListeners.add(t),t({...this.stats}),()=>{this.statsListeners.delete(t)}}getStats(){return{...this.stats}}emitStats(){let t={...this.stats};for(let e of this.statsListeners)e(t)}mcpAuth;buildMcpProjection(t){let e=this.agent.memory?this.repository.getPendingDirAbsolutePath(this.agent.name):null,s=on({registry:this.repository.getMcpServers(),agentGrants:this.agent.mcpServers??[],getBearerToken:n=>this.mcpAuth?.getToken(n),remember:e?{pendingDir:e,source:`mcp:${this.captureSource()}`}:null});return this.mcpProjection=ln(t,this.agent.adapter,s),{args:this.mcpProjection?.args??[],env:this.mcpProjection?.env??{},allowServers:s.map(n=>n.def.name)}}memoryWriter;captureSource(){return`chat:${this.inAppConversationId||this.conversationId||"default"}`}getConversationName(){return this.conversationName}async setConversationName(t){this.conversationName=t,await this.persist()}refreshAgent(){let t=this.repository.getAgentByName(this.agent.name);t&&(this.agent=t)}get isThread(){return!!this.threadAnchorId}async loadPersistedState(){let t=this.getChatFilePath(),e=this.vault.getAbstractFileByPath(t);if(!(e instanceof it.TFile))return!1;try{let s=await this.vault.cachedRead(e);if(this.isThread){let a=JSON.parse(s);return a.messages?.length>0||a.sessionId?(this.messages=(a.messages??[]).map(r=>r.id?r:{...r,id:hr(r)}),this.claudeSessionId=a.sessionId??null,this.threadAnchorIndex=a.anchorIndex,this.claudeSessionId&&(this.basePromptSent=!0),!0):!1}let n=JSON.parse(s);if(n.name&&(this.conversationName=n.name),n.messages?.length>0)return this.messages=n.messages.map(a=>a.id?a:{...a,id:hr(a)}),this.claudeSessionId=n.sessionId??null,this.threadIndex=n.threads??{},this.claudeSessionId&&(this.basePromptSent=!0),!0}catch{}return!1}getThreadIndex(){return{...this.threadIndex}}async persist(){let t=new Date().toISOString(),e=this.getChatFilePath(),s;if(this.isThread){let a={anchorMessageId:this.threadAnchorId,anchorIndex:this.threadAnchorIndex??0,sessionId:this.claudeSessionId,messages:this.messages,createdAt:this.parentSession?.threadIndex[this.threadAnchorId]?.createdAt??t,lastActive:t};s=JSON.stringify(a,null,2)}else{let a={sessionId:this.claudeSessionId,messages:this.messages,lastActive:t,name:this.conversationName||void 0,threads:Object.keys(this.threadIndex).length>0?this.threadIndex:void 0};s=JSON.stringify(a,null,2)}let n=this.vault.getAbstractFileByPath(e);n instanceof it.TFile?await this.vault.modify(n,s):(await this.ensureParentFolders(e),await this.vault.create(e,s)),this.isThread&&this.parentSession&&this.threadAnchorId&&await this.parentSession.upsertThreadIndex(this.threadAnchorId,{path:e,createdAt:this.parentSession.threadIndex[this.threadAnchorId]?.createdAt??t,messageCount:this.messages.length,lastActive:t})}async upsertThreadIndex(t,e){this.threadIndex[t]=e,await this.persist()}async ensureParentFolders(t){let e=t.lastIndexOf("/");if(e<=0)return;let n=t.slice(0,e).split("/"),a="";for(let r of n)if(a=a?`${a}/${r}`:r,!this.vault.getAbstractFileByPath(a))try{await this.vault.createFolder(a)}catch(o){if(!(o instanceof Error?o.message:String(o)).includes("already exists"))throw o}}async clearPersistedState(){let t=this.getChatFilePath();await this.repository.trashFile(t),this.messages=[],this.claudeSessionId=null,this.basePromptSent=!1}getChatFilePath(){if(this.threadAnchorId&&this.parentSession)return this.parentSession.getThreadFilePath(this.threadAnchorId);if(this.channelName&&this.conversationId){let s=this.settings.fleetFolder,n=oe(this.conversationId)||"conversation";return(0,it.normalizePath)(`${s}/channels/${this.channelName}/sessions/${n}.json`)}if(!this.inAppConversationId)throw new Error("ChatSession requires inAppConversationId (or channel options) to resolve a file path");let t=oe(this.inAppConversationId)||"conversation";if(this.agent.isFolder){let s=this.agent.filePath.replace(/\/agent\.md$/,"");return(0,it.normalizePath)(`${s}/conversations/${t}.json`)}let e=this.repository.getMemoryPath(this.agent.name).replace(/\/[^/]+$/,"");return(0,it.normalizePath)(`${e}/${this.agent.name}-conversations/${t}.json`)}getThreadFilePath(t){let e=this.getParentChatFilePath(),s=e.replace(/\/[^/]+$/,""),n=e.slice(s.length+1).replace(/\.json$/,"");return(0,it.normalizePath)(`${s}/${n}.threads/${t}.json`)}getParentChatFilePath(){if(this.channelName&&this.conversationId){let t=this.settings.fleetFolder,e=oe(this.conversationId)||"conversation";return(0,it.normalizePath)(`${t}/channels/${this.channelName}/sessions/${e}.json`)}if(this.inAppConversationId){let t=oe(this.inAppConversationId)||"conversation";if(this.agent.isFolder){let s=this.agent.filePath.replace(/\/agent\.md$/,"");return(0,it.normalizePath)(`${s}/conversations/${t}.json`)}let e=this.repository.getMemoryPath(this.agent.name).replace(/\/[^/]+$/,"");return(0,it.normalizePath)(`${e}/${this.agent.name}-conversations/${t}.json`)}throw new Error("ChatSession requires inAppConversationId (or channel options) to resolve a parent file path")}async ensureProcess(){if(this.process&&this.isProcessAlive)return;this.refreshAgent();let t=["--input-format","stream-json","--output-format","stream-json","--verbose"];this.claudeSessionId?(t.push("--resume",this.claudeSessionId),this.basePromptSent=!0,this.claudeResumeAttempted=!0):this.claudeResumeAttempted=!1;let e=Pt(null,this.agent,this.settings);ys(e.value)&&t.push("--model",e.value);let s=this.agent.permissionMode?.trim();s&&s!=="default"?t.push("--permission-mode",s):t.push("--permission-mode","bypassPermissions"),this.agent.effort&&t.push("--effort",this.agent.effort);let n=this.agent.cwd?.trim()?this.agent.cwd:this.repository.getVaultBasePath()??".",a=this.buildMcpProjection(n);t.push(...a.args),this.settingsState=en(n,this.agent,{mcpAllowServers:a.allowServers});let r=lt(this.settings.claudeCliPath,t,{cwd:n,env:{...process.env,AWS_REGION:this.settings.awsRegion,...a.env}});this.process=r,this.isProcessAlive=!0,this.stdoutBuffer="",this.processListeners={onStdout:o=>this.handleStdout(o),onStderr:()=>{},onError:o=>this.handleProcessError(o),onClose:()=>this.handleProcessClose()},r.stdout.on("data",this.processListeners.onStdout),r.stderr.on("data",this.processListeners.onStderr),r.on("error",this.processListeners.onError),r.on("close",this.processListeners.onClose)}detachProcessListeners(){this.process&&this.processListeners&&(this.process.stdout?.removeListener("data",this.processListeners.onStdout),this.process.stderr?.removeListener("data",this.processListeners.onStderr),this.process.removeListener("error",this.processListeners.onError),this.process.removeListener("close",this.processListeners.onClose)),this.processListeners=null}handleStdout(t){this.isStreaming&&this.armWatchdog(),this.stdoutBuffer+=t.toString();let e=ge(this.stdoutBuffer);this.stdoutBuffer=e.pop()??"";for(let s of e){let n=s.trim();if(n)try{let a=JSON.parse(n);this.handleEvent(a)}catch{}}}handleEvent(t){if(this.isCodex){this.handleCodexEvent(t);return}if(typeof t.session_id=="string"&&(this.claudeSessionId=t.session_id),this.updateStatsFromEvent(t),t.type==="system"&&t.subtype==="compact_boundary"){let s=t.compact_metadata,n=s&&typeof s.pre_tokens=="number"?s.pre_tokens:0,a=s&&typeof s.post_tokens=="number"?s.post_tokens:0;a>0&&(this.stats.contextTokensUsed=a),this.stats.lastCompact={preTokens:n,postTokens:a},this.emitStats(),this.needsCompactBeforeNextTurn=!1,this.activeOnEvent?.({type:"compacted",content:"",compact:{preTokens:n,postTokens:a}});return}if(t.type==="result"){let s=typeof t.api_error_status=="string"&&!!t.api_error_status;if(t.is_error===!0||s){let n=this.describeResultError(t);this.activeOnEvent?.({type:"error",content:"",errorMessage:n}),this.claudeResumeAttempted&&t.is_error===!0&&!s&&this.clearSessionId()}this.handleTurnEnd();return}let e=this.parseStreamEvent(t);e&&this.dispatchStreamEvent(e)}dispatchStreamEvent(t){let e=t;if(t.type==="text"){let s=this.turnResponseText.length===0;if(s&&(this.displayedLen=0),this.turnResponseText+=t.content,this.setCurrentTool(void 0),s&&this.turnResponseText.length>0&&this.emitActivity(),this.agent.memory){let n=ni(this.turnResponseText),a=n.length>this.displayedLen?n.slice(this.displayedLen):"";this.displayedLen=n.length,e={...t,content:a}}}else t.type==="tool_use"&&t.toolName&&(this.turnToolCalls.push({name:t.toolName,command:t.content||void 0}),this.setCurrentTool(t.toolName));this.activeOnEvent?.(e)}handleCodexEvent(t){this.codexTurnState||(this.codexTurnState=pa());let e=Ki(t,this.codexTurnState);for(let s of e)switch(s.kind){case"session":this.claudeSessionId=s.sessionId;break;case"text":this.dispatchStreamEvent({type:"text",content:s.text});break;case"tool":this.dispatchStreamEvent({type:"tool_use",content:s.command?s.command.slice(0,150):"",toolName:s.toolName});break;case"usage":{s.contextTokens>0&&s.contextTokens!==this.stats.contextTokensUsed&&(this.stats.contextTokensUsed=s.contextTokens),this.stats.turnCount+=1,this.emitStats();break}case"turn-failed":case"error":this.codexTurnErrors.push(s.message),this.activeOnEvent?.({type:"error",content:"",errorMessage:s.message});break}}get hasCurrentTurnText(){return this.turnResponseText.length>0}describeResultError(t){let e=[],s=typeof t.api_error_status=="string"?t.api_error_status:"",n=typeof t.subtype=="string"?t.subtype:"",a=typeof t.result=="string"?t.result:"";return s?e.push(`API ${s}`):n?e.push(n.replace(/_/g," ")):e.push("unknown error"),a&&e.push(`\u2014 ${a}`),e.join(" ")}updateStatsFromEvent(t){let e=!1,s=typeof t.model=="string"?t.model:void 0,n=t.message,a=n&&typeof n.model=="string"?n.model:void 0,r=s||a;if(r&&r!==this.stats.concreteModel&&(this.stats.concreteModel=r,e=!0),t.type==="rate_limit_event"){let o=t.rate_limit_info;o&&(this.stats.rateLimit={type:typeof o.rateLimitType=="string"?o.rateLimitType:"unknown",resetsAt:typeof o.resetsAt=="number"?o.resetsAt:void 0,status:typeof o.status=="string"?o.status:void 0,isUsingOverage:typeof o.isUsingOverage=="boolean"?o.isUsingOverage:void 0},e=!0)}if(t.type==="assistant"&&n){let o=n.usage;if(o){let c=typeof o.input_tokens=="number"?o.input_tokens:0,l=typeof o.cache_read_input_tokens=="number"?o.cache_read_input_tokens:0,h=typeof o.cache_creation_input_tokens=="number"?o.cache_creation_input_tokens:0,d=c+l+h;d>0&&d!==this.stats.contextTokensUsed&&(this.stats.contextTokensUsed=d,e=!0)}}if(t.type==="result"){let o=typeof t.total_cost_usd=="number"?t.total_cost_usd:0;o>0&&(this.stats.costTotalUsd+=o,e=!0);let c=t.modelUsage;if(c)for(let l of Object.values(c)){let h=l;typeof h.contextWindow=="number"&&h.contextWindow!==this.stats.contextWindow&&(this.stats.contextWindow=h.contextWindow,e=!0)}this.stats.turnCount+=1,e=!0}t.type==="result"&&this.evaluateAutoCompact(),e&&this.emitStats()}evaluateAutoCompact(){if(this.isCodex)return;let t=this.agent.autoCompactThreshold??0;if(t<=0||t>=100)return;let e=this.stats.contextWindow,s=this.stats.contextTokensUsed;!e||!s||s/e*1000&&this.memoryWriter.capture(this.agent,n,this.captureSource(),new Date().toISOString()).catch(a=>console.warn(`Agent Fleet: chat memory capture failed for "${this.agent.name}"`,a)),this.memoryWriter.drainPending(this.agent,new Date().toISOString()).catch(a=>console.warn(`Agent Fleet: chat pending drain failed for "${this.agent.name}"`,a))}e.trim()&&this.messages.push({id:xs(),role:"assistant",content:e,timestamp:new Date().toISOString(),toolCalls:this.turnToolCalls.length>0?[...this.turnToolCalls]:void 0});let s={text:this.turnResponseText,toolCalls:[...this.turnToolCalls]};if(this.activeOnEvent?.({type:"result",content:"",toolCalls:[...this.turnToolCalls]}),this.turnResponseText="",this.turnToolCalls=[],this.pendingTurns--,this.isCodex){let n=this.codexQueue.shift();if(n!==void 0){this.armWatchdog(),this.startCodexTurn(n).catch(a=>{this.handleProcessError(a instanceof Error?a:new Error(String(a)))});return}this.pendingTurns=0}if(this.pendingTurns<=0){this.pendingTurns=0,this.clearWatchdog(),this.setStreaming(!1),this.persist();let n=this.turnResolve;this.turnResolve=null,this.turnReject=null,n?.(s)}}handleProcessError(t){this.isProcessAlive=!1,this.process=null,this.pendingTurns=0,this.turnResponseText="",this.turnToolCalls=[],this.codexQueue=[],this.clearWatchdog(),this.setStreaming(!1),Et(this.settingsState),this.settingsState=null,this.codexPermState?.restore(),this.codexPermState=null,bt(this.mcpProjection),this.mcpProjection=null;let e=this.turnReject;this.turnResolve=null,this.turnReject=null,e?.(t)}handleProcessClose(){if(this.isProcessAlive=!1,this.process=null,Et(this.settingsState),this.settingsState=null,bt(this.mcpProjection),this.mcpProjection=null,this.turnResolve){this.claudeResumeAttempted&&!this.turnResponseText.trim()&&this.clearSessionId();let t={text:this.turnResponseText,toolCalls:[...this.turnToolCalls]};this.turnResponseText.trim()&&this.messages.push({id:xs(),role:"assistant",content:this.turnResponseText,timestamp:new Date().toISOString(),toolCalls:this.turnToolCalls.length>0?[...this.turnToolCalls]:void 0}),this.pendingTurns=0,this.turnResponseText="",this.turnToolCalls=[],this.clearWatchdog(),this.setStreaming(!1),this.persist();let e=this.turnResolve;this.turnResolve=null,this.turnReject=null,e?.(t)}}async sendMessage(t,e,s,n){this.lastActiveAt=Date.now(),this.messages.push({id:xs(),role:"user",content:t,timestamp:new Date().toISOString(),attachments:n&&n.length>0?n:void 0});let a=s??t;if(this.basePromptSent||(a=`${await this.buildBasePrompt()} +Document the reliable steps to handle this so future runs don't rediscover them.`};try{await this.repository.writeProposal(o),a.proposed=!0,await this.repository.writeCandidates(t.name,e)}catch(c){console.warn(`Agent Fleet: failed to write proposal for "${t.name}"`,c)}}}async runPendingTask({task:t,promptOverride:e}){let s=this.repository.getAgentByName(t.agent);if(!s||!s.enabled)return;let n=new Date().toISOString();this.runtimeState.set(s.name,{status:"running",currentTaskId:t.taskId,runStarted:n}),this.runOutputBuffers.set(s.name,""),this.emitStatusChange();try{let a=await this.executor.execute(s,t,e,m=>{let f=this.runOutputBuffers.get(s.name)??"";this.runOutputBuffers.set(s.name,f+m);let g=this.runOutputListeners.get(s.name);if(g)for(let y of g)y(m)}),r=this.consumeAborted(s.name),o=r?[]:this.buildApprovals(s,a.toolsUsed),c=r?"cancelled":this.resolveRunStatus(a,o),l={runId:a.runId,agent:s.name,task:t.taskId,status:c,started:n,completed:new Date().toISOString(),durationSeconds:a.durationSeconds,tokensUsed:a.tokensUsed,costUsd:a.costUsd,model:a.resolvedModel||s.model,modelSource:a.modelSource,concreteModel:a.concreteModel,exitCode:a.exitCode,tags:Array.from(new Set([...s.tags,...t.tags])),prompt:a.prompt,output:a.outputText,toolsUsed:a.toolsUsed.map(m=>`${m.tool}${m.command?`: ${m.command}`:""}`),finalResult:a.finalResult,stderr:a.stderr,approvals:o},h=await this.repository.writeRunLog(l);if(await this.repository.updateTaskRunMetadata(t,{lastRun:n,runCount:t.runCount+1}),s.memory){let f=t.tags.includes("heartbeat")?"heartbeat":`task:${t.taskId}`,g=qs(a.outputText);try{await this.memoryWriter.capture(s,g,f,new Date().toISOString())}catch(y){console.warn(`Agent Fleet: failed to append memory for "${s.name}"`,y)}}let d=t.tags.includes("heartbeat"),u=d?s.heartbeatChannel:t.channel??"",p=d?s.heartbeatChannelTarget??"":t.channelTarget??"";if(u&&!r&&l.output.trim())try{this.channelResultHandler?.(s.name,u,l.output,d?"heartbeat":t.taskId,p)}catch(m){console.warn(`Agent Fleet: channel delivery failed for ${s.name}`,m)}r&&(l.output="Task was manually stopped."),await this.refreshRunCaches(),this.runtimeState.set(s.name,{status:r||c==="success"?"idle":c==="pending_approval"?"pending":"error",currentRunId:a.runId,lastRun:{...l,filePath:h}}),r||this.notify(l)}catch(a){let r=this.consumeAborted(s.name),o=r?"cancelled":"failure",c=Dt(t,s,this.settings),l={runId:(0,Sa.randomUUID)(),agent:s.name,task:t.taskId,status:o,started:n,completed:new Date().toISOString(),durationSeconds:Math.round((Date.now()-new Date(n).getTime())/1e3),model:c.value||s.model,modelSource:c.source,exitCode:r?-1:1,tags:Array.from(new Set([...s.tags,...t.tags])),prompt:e??t.body,output:r?"Task was manually stopped.":a instanceof Error?a.message:String(a),toolsUsed:[]},h=await this.repository.writeRunLog(l);await this.refreshRunCaches(),this.runtimeState.set(s.name,{status:r?"idle":"error",lastRun:{...l,filePath:h}}),r||this.notify(l)}finally{if(s.memory)try{await this.memoryWriter.drainPending(s,new Date().toISOString())}catch(a){console.warn(`Agent Fleet: failed to drain pending memory for "${s.name}"`,a)}this.runOutputBuffers.delete(s.name),this.runOutputListeners.delete(s.name),this.emitStatusChange()}}buildApprovals(t,e){let s=e.filter(n=>t.approvalRequired.includes(n.tool)).map(n=>({tool:n.tool,command:n.command,reason:n.reason,status:"pending"}));return s.length>0?s:void 0}resolveRunStatus(t,e){return e?.length?"pending_approval":t.timedOut?"timeout":t.exitCode===0?"success":"failure"}notify(t){if(this.settings.notificationLevel==="none"||this.settings.notificationLevel==="failures-only"&&t.status==="success")return;let s=(ye(t.output).map(a=>a.trim()).find(a=>a&&!a.startsWith("{")&&!a.startsWith("["))??"").slice(0,120)||t.status,n=t.status==="success"?`\u2705 ${t.agent}: ${s}`:t.status==="pending_approval"?`\u{1F535} ${t.agent} needs approval: ${(t.approvals??[])[0]?.tool??"tool action"}`:`\u274C ${t.agent}: ${s}`;new vr.Notice(n,t.status==="success"?5e3:0)}emitStatusChange(){for(let t of this.statusChangeListeners)t()}};function hc(i){return i.toLowerCase().replace(/[^a-z0-9-]/g,"-").replace(/-{2,}/g,"-").replace(/^-|-$/g,"")}function xs(i,t){return`${i}-${hc(t)}`}var Gt="af-channel-cred",Ss="af-mcp-secret",fn=class{constructor(t){this.storage=t;t||console.warn("Agent Fleet: SecretStorage unavailable (Obsidian < 1.11.4). Secrets will use plaintext fallback.")}get available(){return!!this.storage}setJson(t,e,s){if(!this.storage)return;let n=xs(t,e);this.storage.setSecret(n,JSON.stringify(s))}getJson(t,e){if(!this.storage)return null;let s=xs(t,e),n=this.storage.getSecret(s);if(!n)return null;try{return JSON.parse(n)}catch{return null}}setString(t,e,s){if(!this.storage)return;let n=xs(t,e);this.storage.setSecret(n,s)}getString(t,e){if(!this.storage)return null;let s=xs(t,e);return this.storage.getSecret(s)||null}delete(t,e){if(!this.storage)return;let s=xs(t,e);this.storage.setSecret(s,"")}listByPrefix(t){return this.storage?this.storage.listSecrets().filter(e=>e.startsWith(t+"-")):[]}};var gn=class{tokens=new Map;oauthTokens=new Map;secretStore;setSecretStore(t){this.secretStore=t}storeProbeToken(t,e){this.tokens.set(t,e)}storeStaticToken(t,e){this.tokens.set(t,e),this.secretStore?.setJson(Ss,t,{accessToken:e})}storeOAuthToken(t,e){this.oauthTokens.set(t,e),this.tokens.set(t,e.accessToken),this.secretStore?.setJson(Ss,t,{accessToken:e.accessToken,refreshToken:e.refreshToken,expiresAt:e.expiresAt,tokenEndpoint:e.tokenEndpoint,clientId:e.clientId,resource:e.resource})}hydrate(t){if(!this.secretStore)return null;let e=this.secretStore.getJson(Ss,t);return e?.accessToken?(this.tokens.set(t,e.accessToken),e.tokenEndpoint&&e.clientId&&e.resource&&this.oauthTokens.set(t,{accessToken:e.accessToken,refreshToken:e.refreshToken,expiresAt:e.expiresAt,tokenEndpoint:e.tokenEndpoint,clientId:e.clientId,resource:e.resource}),e):null}getToken(t){return this.tokens.get(t)??this.hydrate(t)?.accessToken??void 0}getOAuthToken(t){return this.oauthTokens.has(t)?this.oauthTokens.get(t):(this.hydrate(t),this.oauthTokens.get(t))}hasToken(t){return this.tokens.has(t)||!!this.hydrate(t)}removeToken(t){this.tokens.delete(t),this.oauthTokens.delete(t),this.secretStore?.delete(Ss,t)}getExpiringTokens(t=5*6e4){let e=new Map,s=Date.now();for(let[n,a]of this.oauthTokens)a.refreshToken&&a.expiresAt&&a.expiresAt-s0?t:i.WATCHDOG_FALLBACK_MINUTES)*60*1e3}armWatchdog(){this.clearWatchdog();let t=this.getWatchdogMs();this.watchdogTimer=window.setTimeout(()=>{if(this.watchdogTimer=null,!this.isStreaming)return;this.activeOnEvent?.({type:"error",content:"",errorMessage:`no response from the CLI for ${Math.round(t/6e4)} minutes \u2014 giving up`});let e=new Error("Watchdog timeout");this.handleProcessError(e);try{this.process?.kill()}catch{}},t)}clearWatchdog(){this.watchdogTimer&&(window.clearTimeout(this.watchdogTimer),this.watchdogTimer=null)}currentToolName;activityListeners=new Set;onActivityChange(t){return this.activityListeners.add(t),t(),()=>{this.activityListeners.delete(t)}}emitActivity(){for(let t of this.activityListeners)t()}setStreaming(t){this.isStreaming!==t&&(this.isStreaming=t,t||(this.currentToolName=void 0),this.emitActivity())}setCurrentTool(t){this.currentToolName!==t&&(this.currentToolName=t,this.emitActivity())}stats={costTotalUsd:0,turnCount:0};usageRecorder;setUsageRecorder(t){this.usageRecorder=t}statsListeners=new Set;onStatsChange(t){return this.statsListeners.add(t),t({...this.stats}),()=>{this.statsListeners.delete(t)}}getStats(){return{...this.stats}}emitStats(){let t={...this.stats};for(let e of this.statsListeners)e(t)}mcpAuth;buildMcpProjection(t){let e=this.agent.memory?this.repository.getPendingDirAbsolutePath(this.agent.name):null,s=cn({registry:this.repository.getMcpServers(),agentGrants:this.agent.mcpServers??[],getBearerToken:n=>this.mcpAuth?.getToken(n),remember:e?{pendingDir:e,source:`mcp:${this.captureSource()}`}:null});return this.mcpProjection=dn(t,this.agent.adapter,s),{args:this.mcpProjection?.args??[],env:this.mcpProjection?.env??{},allowServers:s.map(n=>n.def.name)}}memoryWriter;captureSource(){return`chat:${this.inAppConversationId||this.conversationId||"default"}`}getConversationName(){return this.conversationName}async setConversationName(t){this.conversationName=t,await this.persist()}refreshAgent(){let t=this.repository.getAgentByName(this.agent.name);t&&(this.agent=t)}get isThread(){return!!this.threadAnchorId}async loadPersistedState(){let t=this.getChatFilePath(),e=this.vault.getAbstractFileByPath(t);if(!(e instanceof ot.TFile))return!1;try{let s=await this.vault.cachedRead(e);if(this.isThread){let a=JSON.parse(s);return a.messages?.length>0||a.sessionId?(this.messages=(a.messages??[]).map(r=>r.id?r:{...r,id:wr(r)}),this.claudeSessionId=a.sessionId??null,this.threadAnchorIndex=a.anchorIndex,this.claudeSessionId&&(this.basePromptSent=!0),!0):!1}let n=JSON.parse(s);if(n.name&&(this.conversationName=n.name),n.messages?.length>0)return this.messages=n.messages.map(a=>a.id?a:{...a,id:wr(a)}),this.claudeSessionId=n.sessionId??null,this.threadIndex=n.threads??{},this.claudeSessionId&&(this.basePromptSent=!0),!0}catch{}return!1}getThreadIndex(){return{...this.threadIndex}}async persist(){let t=new Date().toISOString(),e=this.getChatFilePath(),s;if(this.isThread){let a={anchorMessageId:this.threadAnchorId,anchorIndex:this.threadAnchorIndex??0,sessionId:this.claudeSessionId,messages:this.messages,createdAt:this.parentSession?.threadIndex[this.threadAnchorId]?.createdAt??t,lastActive:t};s=JSON.stringify(a,null,2)}else{let a={sessionId:this.claudeSessionId,messages:this.messages,lastActive:t,name:this.conversationName||void 0,threads:Object.keys(this.threadIndex).length>0?this.threadIndex:void 0};s=JSON.stringify(a,null,2)}let n=this.vault.getAbstractFileByPath(e);n instanceof ot.TFile?await this.vault.modify(n,s):(await this.ensureParentFolders(e),await this.vault.create(e,s)),this.isThread&&this.parentSession&&this.threadAnchorId&&await this.parentSession.upsertThreadIndex(this.threadAnchorId,{path:e,createdAt:this.parentSession.threadIndex[this.threadAnchorId]?.createdAt??t,messageCount:this.messages.length,lastActive:t})}async upsertThreadIndex(t,e){this.threadIndex[t]=e,await this.persist()}async ensureParentFolders(t){let e=t.lastIndexOf("/");if(e<=0)return;let n=t.slice(0,e).split("/"),a="";for(let r of n)if(a=a?`${a}/${r}`:r,!this.vault.getAbstractFileByPath(a))try{await this.vault.createFolder(a)}catch(o){if(!(o instanceof Error?o.message:String(o)).includes("already exists"))throw o}}async clearPersistedState(){let t=this.getChatFilePath();await this.repository.trashFile(t),this.messages=[],this.claudeSessionId=null,this.basePromptSent=!1}getChatFilePath(){if(this.threadAnchorId&&this.parentSession)return this.parentSession.getThreadFilePath(this.threadAnchorId);if(this.channelName&&this.conversationId){let s=this.settings.fleetFolder,n=oe(this.conversationId)||"conversation";return(0,ot.normalizePath)(`${s}/channels/${this.channelName}/sessions/${n}.json`)}if(!this.inAppConversationId)throw new Error("ChatSession requires inAppConversationId (or channel options) to resolve a file path");let t=oe(this.inAppConversationId)||"conversation";if(this.agent.isFolder){let s=this.agent.filePath.replace(/\/agent\.md$/,"");return(0,ot.normalizePath)(`${s}/conversations/${t}.json`)}let e=this.repository.getMemoryPath(this.agent.name).replace(/\/[^/]+$/,"");return(0,ot.normalizePath)(`${e}/${this.agent.name}-conversations/${t}.json`)}getThreadFilePath(t){let e=this.getParentChatFilePath(),s=e.replace(/\/[^/]+$/,""),n=e.slice(s.length+1).replace(/\.json$/,"");return(0,ot.normalizePath)(`${s}/${n}.threads/${t}.json`)}getParentChatFilePath(){if(this.channelName&&this.conversationId){let t=this.settings.fleetFolder,e=oe(this.conversationId)||"conversation";return(0,ot.normalizePath)(`${t}/channels/${this.channelName}/sessions/${e}.json`)}if(this.inAppConversationId){let t=oe(this.inAppConversationId)||"conversation";if(this.agent.isFolder){let s=this.agent.filePath.replace(/\/agent\.md$/,"");return(0,ot.normalizePath)(`${s}/conversations/${t}.json`)}let e=this.repository.getMemoryPath(this.agent.name).replace(/\/[^/]+$/,"");return(0,ot.normalizePath)(`${e}/${this.agent.name}-conversations/${t}.json`)}throw new Error("ChatSession requires inAppConversationId (or channel options) to resolve a parent file path")}async ensureProcess(){if(this.process&&this.isProcessAlive)return;this.refreshAgent();let t=["--input-format","stream-json","--output-format","stream-json","--verbose"];this.claudeSessionId?(t.push("--resume",this.claudeSessionId),this.basePromptSent=!0,this.claudeResumeAttempted=!0):this.claudeResumeAttempted=!1;let e=Dt(null,this.agent,this.settings);ws(e.value)&&t.push("--model",e.value);let s=this.agent.permissionMode?.trim();s&&s!=="default"?t.push("--permission-mode",s):t.push("--permission-mode","bypassPermissions"),this.agent.effort&&t.push("--effort",this.agent.effort);let n=this.agent.cwd?.trim()?this.agent.cwd:this.repository.getVaultBasePath()??".",a=this.buildMcpProjection(n);t.push(...a.args),this.settingsState=sn(n,this.agent,{mcpAllowServers:a.allowServers});let r=dt(this.settings.claudeCliPath,t,{cwd:n,env:{...process.env,AWS_REGION:this.settings.awsRegion,...a.env}});this.process=r,this.isProcessAlive=!0,this.stdoutBuffer="",this.processListeners={onStdout:o=>this.handleStdout(o),onStderr:()=>{},onError:o=>this.handleProcessError(o),onClose:()=>this.handleProcessClose()},r.stdout.on("data",this.processListeners.onStdout),r.stderr.on("data",this.processListeners.onStderr),r.on("error",this.processListeners.onError),r.on("close",this.processListeners.onClose)}detachProcessListeners(){this.process&&this.processListeners&&(this.process.stdout?.removeListener("data",this.processListeners.onStdout),this.process.stderr?.removeListener("data",this.processListeners.onStderr),this.process.removeListener("error",this.processListeners.onError),this.process.removeListener("close",this.processListeners.onClose)),this.processListeners=null}handleStdout(t){this.isStreaming&&this.armWatchdog(),this.stdoutBuffer+=t.toString();let e=ye(this.stdoutBuffer);this.stdoutBuffer=e.pop()??"";for(let s of e){let n=s.trim();if(n)try{let a=JSON.parse(n);this.handleEvent(a)}catch{}}}handleEvent(t){if(this.isCodex){this.handleCodexEvent(t);return}if(typeof t.session_id=="string"&&(this.claudeSessionId=t.session_id),this.updateStatsFromEvent(t),t.type==="system"&&t.subtype==="compact_boundary"){let s=t.compact_metadata,n=s&&typeof s.pre_tokens=="number"?s.pre_tokens:0,a=s&&typeof s.post_tokens=="number"?s.post_tokens:0;a>0&&(this.stats.contextTokensUsed=a),this.stats.lastCompact={preTokens:n,postTokens:a},this.emitStats(),this.needsCompactBeforeNextTurn=!1,this.activeOnEvent?.({type:"compacted",content:"",compact:{preTokens:n,postTokens:a}});return}if(t.type==="result"){let s=typeof t.api_error_status=="string"&&!!t.api_error_status;if(t.is_error===!0||s){let n=this.describeResultError(t);this.activeOnEvent?.({type:"error",content:"",errorMessage:n}),this.claudeResumeAttempted&&t.is_error===!0&&!s&&this.clearSessionId()}this.handleTurnEnd();return}let e=this.parseStreamEvent(t);e&&this.dispatchStreamEvent(e)}dispatchStreamEvent(t){let e=t;if(t.type==="text"){let s=this.turnResponseText.length===0;if(s&&(this.displayedLen=0),this.turnResponseText+=t.content,this.setCurrentTool(void 0),s&&this.turnResponseText.length>0&&this.emitActivity(),this.agent.memory){let n=ui(this.turnResponseText),a=n.length>this.displayedLen?n.slice(this.displayedLen):"";this.displayedLen=n.length,e={...t,content:a}}}else t.type==="tool_use"&&t.toolName&&(this.turnToolCalls.push({name:t.toolName,command:t.content||void 0}),this.setCurrentTool(t.toolName));this.activeOnEvent?.(e)}handleCodexEvent(t){this.codexTurnState||(this.codexTurnState=ya());let e=ar(t,this.codexTurnState);for(let s of e)switch(s.kind){case"session":this.claudeSessionId=s.sessionId;break;case"text":this.dispatchStreamEvent({type:"text",content:s.text});break;case"tool":this.dispatchStreamEvent({type:"tool_use",content:s.command?s.command.slice(0,150):"",toolName:s.toolName});break;case"usage":{s.contextTokens>0&&s.contextTokens!==this.stats.contextTokensUsed&&(this.stats.contextTokensUsed=s.contextTokens),this.stats.turnCount+=1,this.emitStats();break}case"turn-failed":case"error":this.codexTurnErrors.push(s.message),this.activeOnEvent?.({type:"error",content:"",errorMessage:s.message});break}}get hasCurrentTurnText(){return this.turnResponseText.length>0}describeResultError(t){let e=[],s=typeof t.api_error_status=="string"?t.api_error_status:"",n=typeof t.subtype=="string"?t.subtype:"",a=typeof t.result=="string"?t.result:"";return s?e.push(`API ${s}`):n?e.push(n.replace(/_/g," ")):e.push("unknown error"),a&&e.push(`\u2014 ${a}`),e.join(" ")}updateStatsFromEvent(t){let e=!1,s=typeof t.model=="string"?t.model:void 0,n=t.message,a=n&&typeof n.model=="string"?n.model:void 0,r=s||a;if(r&&r!==this.stats.concreteModel&&(this.stats.concreteModel=r,e=!0),t.type==="rate_limit_event"){let o=t.rate_limit_info;o&&(this.stats.rateLimit={type:typeof o.rateLimitType=="string"?o.rateLimitType:"unknown",resetsAt:typeof o.resetsAt=="number"?o.resetsAt:void 0,status:typeof o.status=="string"?o.status:void 0,isUsingOverage:typeof o.isUsingOverage=="boolean"?o.isUsingOverage:void 0},e=!0)}if(t.type==="assistant"&&n){let o=n.usage;if(o){let c=typeof o.input_tokens=="number"?o.input_tokens:0,l=typeof o.cache_read_input_tokens=="number"?o.cache_read_input_tokens:0,h=typeof o.cache_creation_input_tokens=="number"?o.cache_creation_input_tokens:0,d=c+l+h;d>0&&d!==this.stats.contextTokensUsed&&(this.stats.contextTokensUsed=d,e=!0)}}if(t.type==="result"){let o=typeof t.total_cost_usd=="number"?t.total_cost_usd:0;o>0&&(this.stats.costTotalUsd+=o,e=!0);let c=t.modelUsage;if(c)for(let l of Object.values(c)){let h=l;typeof h.contextWindow=="number"&&h.contextWindow!==this.stats.contextWindow&&(this.stats.contextWindow=h.contextWindow,e=!0)}this.stats.turnCount+=1,e=!0,this.recordTurnUsage(t,o)}t.type==="result"&&this.evaluateAutoCompact(),e&&this.emitStats()}recordTurnUsage(t,e){if(!this.usageRecorder)return;let s=t.usage,n=h=>typeof h=="number"?h:0,a=n(s?.input_tokens),r=n(s?.output_tokens),o=n(s?.cache_read_input_tokens),c=n(s?.cache_creation_input_tokens),l=a+r+o+c;l===0&&e===0||this.usageRecorder({ts:new Date().toISOString(),agent:this.agent.name,source:this.channelName?"channel":"chat",model:this.stats.concreteModel??this.agent.model,inputTokens:a,outputTokens:r,cacheReadTokens:o,cacheCreateTokens:c,totalTokens:l,...e>0?{costUsd:e}:{}})}evaluateAutoCompact(){if(this.isCodex)return;let t=this.agent.autoCompactThreshold??0;if(t<=0||t>=100)return;let e=this.stats.contextWindow,s=this.stats.contextTokensUsed;!e||!s||s/e*1000&&this.memoryWriter.capture(this.agent,n,this.captureSource(),new Date().toISOString()).catch(a=>console.warn(`Agent Fleet: chat memory capture failed for "${this.agent.name}"`,a)),this.memoryWriter.drainPending(this.agent,new Date().toISOString()).catch(a=>console.warn(`Agent Fleet: chat pending drain failed for "${this.agent.name}"`,a))}e.trim()&&this.messages.push({id:Cs(),role:"assistant",content:e,timestamp:new Date().toISOString(),toolCalls:this.turnToolCalls.length>0?[...this.turnToolCalls]:void 0});let s={text:this.turnResponseText,toolCalls:[...this.turnToolCalls]};if(this.activeOnEvent?.({type:"result",content:"",toolCalls:[...this.turnToolCalls]}),this.turnResponseText="",this.turnToolCalls=[],this.pendingTurns--,this.isCodex){let n=this.codexQueue.shift();if(n!==void 0){this.armWatchdog(),this.startCodexTurn(n).catch(a=>{this.handleProcessError(a instanceof Error?a:new Error(String(a)))});return}this.pendingTurns=0}if(this.pendingTurns<=0){this.pendingTurns=0,this.clearWatchdog(),this.setStreaming(!1),this.persist();let n=this.turnResolve;this.turnResolve=null,this.turnReject=null,n?.(s)}}handleProcessError(t){this.isProcessAlive=!1,this.process=null,this.pendingTurns=0,this.turnResponseText="",this.turnToolCalls=[],this.codexQueue=[],this.clearWatchdog(),this.setStreaming(!1),Rt(this.settingsState),this.settingsState=null,this.codexPermState?.restore(),this.codexPermState=null,xt(this.mcpProjection),this.mcpProjection=null;let e=this.turnReject;this.turnResolve=null,this.turnReject=null,e?.(t)}handleProcessClose(){if(this.isProcessAlive=!1,this.process=null,Rt(this.settingsState),this.settingsState=null,xt(this.mcpProjection),this.mcpProjection=null,this.turnResolve){this.claudeResumeAttempted&&!this.turnResponseText.trim()&&this.clearSessionId();let t={text:this.turnResponseText,toolCalls:[...this.turnToolCalls]};this.turnResponseText.trim()&&this.messages.push({id:Cs(),role:"assistant",content:this.turnResponseText,timestamp:new Date().toISOString(),toolCalls:this.turnToolCalls.length>0?[...this.turnToolCalls]:void 0}),this.pendingTurns=0,this.turnResponseText="",this.turnToolCalls=[],this.clearWatchdog(),this.setStreaming(!1),this.persist();let e=this.turnResolve;this.turnResolve=null,this.turnReject=null,e?.(t)}}async sendMessage(t,e,s,n){this.lastActiveAt=Date.now(),this.messages.push({id:Cs(),role:"user",content:t,timestamp:new Date().toISOString(),attachments:n&&n.length>0?n:void 0});let a=s??t;if(this.basePromptSent||(a=`${await this.buildBasePrompt()} ## Task ${a}`,this.basePromptSent=!0),this.isCodex){this.stats.lastCompact&&(this.stats.lastCompact=void 0,this.emitStats()),this.activeOnEvent=e,this.turnResponseText="",this.turnToolCalls=[],this.pendingTurns=1,this.setStreaming(!0),this.armWatchdog();try{await this.startCodexTurn(a)}catch(c){throw this.pendingTurns=0,this.clearWatchdog(),this.setStreaming(!1),new Error(`Failed to start Codex process: ${c instanceof Error?c.message:String(c)}`)}return new Promise((c,l)=>{this.turnResolve=c,this.turnReject=l})}await this.ensureProcess();let r=this.needsCompactBeforeNextTurn;r&&(this.needsCompactBeforeNextTurn=!1,this.lastCompactTriggerAt=Date.now()),this.stats.lastCompact&&(this.stats.lastCompact=void 0,this.emitStats()),this.activeOnEvent=e,this.turnResponseText="",this.turnToolCalls=[],this.pendingTurns=r?2:1,this.setStreaming(!0),this.armWatchdog();let o=c=>{let l=JSON.stringify({type:"user",message:{role:"user",content:c}});this.process.stdin.write(l+` -`)};try{r&&o("/compact"),o(a)}catch(c){throw this.pendingTurns=0,this.setStreaming(!1),new Error(`Failed to write to Claude process stdin: ${c instanceof Error?c.message:String(c)}`)}return new Promise((c,l)=>{this.turnResolve=c,this.turnReject=l})}scheduleCompact(){this.isCodex||(this.needsCompactBeforeNextTurn=!0)}async startCodexTurn(t){this.refreshAgent();let e=Pt(null,this.agent,this.settings),s=await gs.buildExec({prompt:t,model:ys(e.value)?e.value:"",modelSource:e.source,effort:this.agent.effort??"",agent:this.agent,settings:this.settings,streaming:!0,resumeSessionId:this.claudeSessionId});e.value&&this.stats.concreteModel!==e.value&&(this.stats.concreteModel=e.value,this.emitStats()),this.codexTurnState=pa(),this.codexResumeAttempted=!!this.claudeSessionId,this.codexTurnErrors=[],this.codexStderr="";let n=this.agent.cwd?.trim()?this.agent.cwd:this.repository.getVaultBasePath()??".",a=this.buildMcpProjection(n);s.args.push(...a.args),this.codexPermState=await gs.setupPermissions(n,this.agent,this.settings);let r=lt(s.cliPath,s.args,{cwd:n,env:{...process.env,AWS_REGION:this.settings.awsRegion,...a.env,...this.codexPermState?.env??{}}});this.process=r,this.isProcessAlive=!0,this.stdoutBuffer="",this.processListeners={onStdout:o=>this.handleStdout(o),onStderr:o=>{this.codexStderr+=o.toString()},onError:o=>this.handleProcessError(o),onClose:o=>this.handleCodexProcessClose(o)},r.stdout.on("data",this.processListeners.onStdout),r.stderr.on("data",this.processListeners.onStderr),r.on("error",this.processListeners.onError),r.on("close",this.processListeners.onClose);try{r.stdin.write(s.stdinPayload??t),r.stdin.end()}catch(o){try{r.kill()}catch{}throw o instanceof Error?o:new Error(String(o))}}handleCodexProcessClose(t){if(this.detachProcessListeners(),this.isProcessAlive=!1,this.process=null,this.stdoutBuffer="",this.codexPermState?.restore(),this.codexPermState=null,bt(this.mcpProjection),this.mcpProjection=null,t!==0&&!this.turnResponseText.trim()){let s=this.codexTurnErrors.join("; ")||this.codexStderr.trim().slice(-500)||`Codex CLI exited with code ${t??"unknown"}`;this.codexResumeAttempted&&this.clearSessionId(),this.codexQueue=[],this.activeOnEvent?.({type:"error",content:"",errorMessage:s})}this.handleTurnEnd()}injectMessage(t,e,s){if(this.isCodex){if(!this.isStreaming&&this.pendingTurns===0)return;this.messages.push({id:xs(),role:"user",content:t,timestamp:new Date().toISOString(),attachments:s&&s.length>0?s:void 0}),this.codexQueue.push(e??t),this.pendingTurns++;return}if(!this.process||!this.isProcessAlive)return;this.messages.push({id:xs(),role:"user",content:t,timestamp:new Date().toISOString(),attachments:s&&s.length>0?s:void 0});let a=JSON.stringify({type:"user",message:{role:"user",content:e??t}});try{this.process.stdin.write(a+` -`)}catch(r){console.warn("Agent Fleet: injectMessage stdin write failed",r);return}this.pendingTurns++}abort(){this.detachProcessListeners(),this.process&&(this.process.kill(),this.process=null),this.isProcessAlive=!1,this.stdoutBuffer="",this.turnResponseText="",this.turnToolCalls=[],this.pendingTurns=0,this.codexQueue=[],this.clearWatchdog(),this.setStreaming(!1),Et(this.settingsState),this.settingsState=null,this.codexPermState?.restore(),this.codexPermState=null,bt(this.mcpProjection),this.mcpProjection=null;let t=this.turnReject;this.turnResolve=null,this.turnReject=null,t?.(new Error("Aborted"))}dispose(){for(let t of this.threads.values())t.abort();this.threads.clear(),this.threadIndex={},this.abort()}hibernate(){this.isStreaming||this.pendingTurns>0||(this.detachProcessListeners(),this.process&&(this.process.kill(),this.process=null),this.isProcessAlive=!1,this.stdoutBuffer="",Et(this.settingsState),this.settingsState=null,bt(this.mcpProjection),this.mcpProjection=null)}clearSessionId(){this.claudeSessionId=null,this.basePromptSent=!1,this.claudeResumeAttempted=!1}async buildBasePrompt(){let t=[this.agent.body.trim()];for(let s of this.agent.skills){let n=this.repository.getSkillByName(s);if(n){let a=[n.body.trim()];n.toolsBody.trim()&&a.push(`### Tools +`)};try{r&&o("/compact"),o(a)}catch(c){throw this.pendingTurns=0,this.setStreaming(!1),new Error(`Failed to write to Claude process stdin: ${c instanceof Error?c.message:String(c)}`)}return new Promise((c,l)=>{this.turnResolve=c,this.turnReject=l})}scheduleCompact(){this.isCodex||(this.needsCompactBeforeNextTurn=!0)}async startCodexTurn(t){this.refreshAgent();let e=Dt(null,this.agent,this.settings),s=await vs.buildExec({prompt:t,model:ws(e.value)?e.value:"",modelSource:e.source,effort:this.agent.effort??"",agent:this.agent,settings:this.settings,streaming:!0,resumeSessionId:this.claudeSessionId});e.value&&this.stats.concreteModel!==e.value&&(this.stats.concreteModel=e.value,this.emitStats()),this.codexTurnState=ya(),this.codexResumeAttempted=!!this.claudeSessionId,this.codexTurnErrors=[],this.codexStderr="";let n=this.agent.cwd?.trim()?this.agent.cwd:this.repository.getVaultBasePath()??".",a=this.buildMcpProjection(n);s.args.push(...a.args),this.codexPermState=await vs.setupPermissions(n,this.agent,this.settings);let r=dt(s.cliPath,s.args,{cwd:n,env:{...process.env,AWS_REGION:this.settings.awsRegion,...a.env,...this.codexPermState?.env??{}}});this.process=r,this.isProcessAlive=!0,this.stdoutBuffer="",this.processListeners={onStdout:o=>this.handleStdout(o),onStderr:o=>{this.codexStderr+=o.toString()},onError:o=>this.handleProcessError(o),onClose:o=>this.handleCodexProcessClose(o)},r.stdout.on("data",this.processListeners.onStdout),r.stderr.on("data",this.processListeners.onStderr),r.on("error",this.processListeners.onError),r.on("close",this.processListeners.onClose);try{r.stdin.write(s.stdinPayload??t),r.stdin.end()}catch(o){try{r.kill()}catch{}throw o instanceof Error?o:new Error(String(o))}}handleCodexProcessClose(t){if(this.detachProcessListeners(),this.isProcessAlive=!1,this.process=null,this.stdoutBuffer="",this.codexPermState?.restore(),this.codexPermState=null,xt(this.mcpProjection),this.mcpProjection=null,t!==0&&!this.turnResponseText.trim()){let s=this.codexTurnErrors.join("; ")||this.codexStderr.trim().slice(-500)||`Codex CLI exited with code ${t??"unknown"}`;this.codexResumeAttempted&&this.clearSessionId(),this.codexQueue=[],this.activeOnEvent?.({type:"error",content:"",errorMessage:s})}this.handleTurnEnd()}injectMessage(t,e,s){if(this.isCodex){if(!this.isStreaming&&this.pendingTurns===0)return;this.messages.push({id:Cs(),role:"user",content:t,timestamp:new Date().toISOString(),attachments:s&&s.length>0?s:void 0}),this.codexQueue.push(e??t),this.pendingTurns++;return}if(!this.process||!this.isProcessAlive)return;this.messages.push({id:Cs(),role:"user",content:t,timestamp:new Date().toISOString(),attachments:s&&s.length>0?s:void 0});let a=JSON.stringify({type:"user",message:{role:"user",content:e??t}});try{this.process.stdin.write(a+` +`)}catch(r){console.warn("Agent Fleet: injectMessage stdin write failed",r);return}this.pendingTurns++}abort(){this.detachProcessListeners(),this.process&&(this.process.kill(),this.process=null),this.isProcessAlive=!1,this.stdoutBuffer="",this.turnResponseText="",this.turnToolCalls=[],this.pendingTurns=0,this.codexQueue=[],this.clearWatchdog(),this.setStreaming(!1),Rt(this.settingsState),this.settingsState=null,this.codexPermState?.restore(),this.codexPermState=null,xt(this.mcpProjection),this.mcpProjection=null;let t=this.turnReject;this.turnResolve=null,this.turnReject=null,t?.(new Error("Aborted"))}dispose(){for(let t of this.threads.values())t.abort();this.threads.clear(),this.threadIndex={},this.abort()}hibernate(){this.isStreaming||this.pendingTurns>0||(this.detachProcessListeners(),this.process&&(this.process.kill(),this.process=null),this.isProcessAlive=!1,this.stdoutBuffer="",Rt(this.settingsState),this.settingsState=null,xt(this.mcpProjection),this.mcpProjection=null)}clearSessionId(){this.claudeSessionId=null,this.basePromptSent=!1,this.claudeResumeAttempted=!1}async buildBasePrompt(){let t=[this.agent.body.trim()];for(let s of this.agent.skills){let n=this.repository.getSkillByName(s);if(n){let a=[n.body.trim()];n.toolsBody.trim()&&a.push(`### Tools ${n.toolsBody.trim()}`),n.referencesBody.trim()&&a.push(`### References ${n.referencesBody.trim()}`),n.examplesBody.trim()&&a.push(`### Examples ${n.examplesBody.trim()}`),t.push(`## Skill: ${n.name} @@ -11975,21 +12005,21 @@ ${a.join(` `)}`)}}if(this.agent.skillsBody.trim()&&t.push(`## Agent Skills ${this.agent.skillsBody.trim()}`),this.agent.contextBody.trim()&&t.push(`## Agent Context -${this.agent.contextBody.trim()}`),this.agent.memory){let s=await this.repository.readWorkingMemory(this.agent.name),n=qs(this.agent,s);n&&t.push(n)}this.channelContext&&this.channelContext.trim()&&t.push(`## Channel Context -${this.channelContext.trim()}`);let e=nn(this.agent,this.repository);if(e&&t.push(e),this.isThread&&this.parentSession&&this.threadAnchorIndex!==void 0){let s="You are continuing a side thread from this conversation. The user is following up on one of your earlier replies and wants to explore something specific without adding to the main thread. Your answers here stay in this thread only and will NOT be added back to the main conversation.",n=this.parentSession.messages.slice(0,this.threadAnchorIndex+1),a=["## Conversation so far"];for(let r of n){let o=r.role==="user"?"User":"Assistant";a.push(`${o}: ${r.content.trim()}`)}t.push(`## Thread Mode +${this.agent.contextBody.trim()}`),this.agent.memory){let s=await this.repository.readWorkingMemory(this.agent.name),n=Gs(this.agent,s);n&&t.push(n)}this.channelContext&&this.channelContext.trim()&&t.push(`## Channel Context +${this.channelContext.trim()}`);let e=rn(this.agent,this.repository);if(e&&t.push(e),this.isThread&&this.parentSession&&this.threadAnchorIndex!==void 0){let s="You are continuing a side thread from this conversation. The user is following up on one of your earlier replies and wants to explore something specific without adding to the main thread. Your answers here stay in this thread only and will NOT be added back to the main conversation.",n=this.parentSession.messages.slice(0,this.threadAnchorIndex+1),a=["## Conversation so far"];for(let r of n){let o=r.role==="user"?"User":"Assistant";a.push(`${o}: ${r.content.trim()}`)}t.push(`## Thread Mode ${s} ${a.join(` `)}`)}return t.filter(Boolean).join(` -`)}async openOrCreateThread(t){if(this.isThread)throw new Error("Nested threads are not supported.");let e=this.threads.get(t);if(e)return e;let s=this.messages.findIndex(r=>r.id===t);if(s<0)throw new Error(`Thread anchor message "${t}" not found in parent.`);let n=new i(this.agent,this.settings,this.repository,this.vault,{threadAnchorId:t,parentSession:this,mcpAuth:this.mcpAuth});return n.threadAnchorIndex=s,await n.loadPersistedState()||(n.threadAnchorIndex=s),this.threads.set(t,n),n}closeThread(t){let e=this.threads.get(t);e&&(e.abort(),this.threads.delete(t))}hibernateIdleThreads(t){let e=Date.now();for(let s of this.threads.values())s.isProcessAlive&&e-s.lastActiveAt>t&&s.hibernate()}parseStreamEvent(t){let e=t.type;if(e==="assistant"){let s=t.message;if(s?.content&&Array.isArray(s.content))for(let n of s.content){if(n.type==="text"&&typeof n.text=="string")return{type:"text",content:n.text};if(n.type==="tool_use"){let a=String(n.name??"tool"),r=n.input,o=r?.command??r?.content??r?.file_path??r?.path??"";return{type:"tool_use",content:o?String(o).slice(0,150):"",toolName:a}}}}if(e==="content_block_delta"){let s=t.delta;if(s?.type==="text_delta"&&typeof s.text=="string")return{type:"text",content:s.text}}return null}};var gn=class{constructor(t){this.config=t;this.now=t.now??Date.now}buckets=new Map;now;tryConsume(t){let e=this.now(),s=e-this.config.windowMs,a=(this.buckets.get(t)??[]).filter(r=>r>s);return a.length>=this.config.maxPerWindow?(this.buckets.set(t,a),!1):(a.push(e),this.buckets.set(t,a),!0)}currentCount(t){let s=this.now()-this.config.windowMs;return(this.buckets.get(t)??[]).filter(a=>a>s).length}reset(t){this.buckets.delete(t)}resetAll(){this.buckets.clear()}};function yn(i){let t=Xl(i),e=[];for(let s of t)if(s.kind==="code"){let n=s.text.replace(/^```[^\n]*\n/,"```\n");e.push(n)}else e.push(Ql(s.text));return e.join("")}function Xl(i){let t=[],e=0,s=!1,n=0;for(;en&&t.push({kind:"prose",text:i.slice(n,e)}),n=e,s=!0,e+=3):e+=1;return n{if(s.isCode)return s.text;let n=s.text;return n=n.replace(/&/g,"&").replace(//g,">"),n=n.replace(/\[([^\]]+)\]\(([^)]+)\)/g,(a,r,o)=>`<${o}|${r}>`),n=n.replace(/\*\*([^*]+)\*\*/g,"*$1*"),n=n.replace(/^#{1,6}\s+(.+)$/gm,"*$1*"),n}).join("")}function Zl(i){let t=[],e=/`([^`\n]+)`/g,s=0,n;for(;n=e.exec(i);)n.index>s&&t.push({text:i.slice(s,n.index),isCode:!1}),t.push({text:n[0],isCode:!0}),s=n.index+n[0].length;return st;){let n=s.slice(0,t),a=ec(n)%2===1,r;if(a){let o=tc(n);if(o>0)r=o;else{e.push(n+"\n```"),s="```\n"+s.slice(t);continue}}else{if(r=n.lastIndexOf(` +`)}async openOrCreateThread(t){if(this.isThread)throw new Error("Nested threads are not supported.");let e=this.threads.get(t);if(e)return e;let s=this.messages.findIndex(r=>r.id===t);if(s<0)throw new Error(`Thread anchor message "${t}" not found in parent.`);let n=new i(this.agent,this.settings,this.repository,this.vault,{threadAnchorId:t,parentSession:this,mcpAuth:this.mcpAuth});return n.threadAnchorIndex=s,await n.loadPersistedState()||(n.threadAnchorIndex=s),this.threads.set(t,n),n}closeThread(t){let e=this.threads.get(t);e&&(e.abort(),this.threads.delete(t))}hibernateIdleThreads(t){let e=Date.now();for(let s of this.threads.values())s.isProcessAlive&&e-s.lastActiveAt>t&&s.hibernate()}parseStreamEvent(t){let e=t.type;if(e==="assistant"){let s=t.message;if(s?.content&&Array.isArray(s.content))for(let n of s.content){if(n.type==="text"&&typeof n.text=="string")return{type:"text",content:n.text};if(n.type==="tool_use"){let a=String(n.name??"tool"),r=n.input,o=r?.command??r?.content??r?.file_path??r?.path??"";return{type:"tool_use",content:o?String(o).slice(0,150):"",toolName:a}}}}if(e==="content_block_delta"){let s=t.delta;if(s?.type==="text_delta"&&typeof s.text=="string")return{type:"text",content:s.text}}return null}};var vn=class{constructor(t){this.config=t;this.now=t.now??Date.now}buckets=new Map;now;tryConsume(t){let e=this.now(),s=e-this.config.windowMs,a=(this.buckets.get(t)??[]).filter(r=>r>s);return a.length>=this.config.maxPerWindow?(this.buckets.set(t,a),!1):(a.push(e),this.buckets.set(t,a),!0)}currentCount(t){let s=this.now()-this.config.windowMs;return(this.buckets.get(t)??[]).filter(a=>a>s).length}reset(t){this.buckets.delete(t)}resetAll(){this.buckets.clear()}};function Ts(i){let t=uc(i),e=[];for(let s of t)if(s.kind==="code"){let n=s.text.replace(/^```[^\n]*\n/,"```\n");e.push(n)}else e.push(pc(s.text));return e.join("")}function uc(i){let t=[],e=0,s=!1,n=0;for(;en&&t.push({kind:"prose",text:i.slice(n,e)}),n=e,s=!0,e+=3):e+=1;return n{if(s.isCode)return s.text;let n=s.text;return n=n.replace(/&/g,"&").replace(//g,">"),n=n.replace(/\[([^\]]+)\]\(([^)]+)\)/g,(a,r,o)=>`<${o}|${r}>`),n=n.replace(/\*\*([^*]+)\*\*/g,"*$1*"),n=n.replace(/^#{1,6}\s+(.+)$/gm,"*$1*"),n}).join("")}function mc(i){let t=[],e=/`([^`\n]+)`/g,s=0,n;for(;n=e.exec(i);)n.index>s&&t.push({text:i.slice(s,n.index),isCode:!1}),t.push({text:n[0],isCode:!0}),s=n.index+n[0].length;return st;){let n=s.slice(0,t),a=fc(n)%2===1,r;if(a){let o=gc(n);if(o>0)r=o;else{e.push(n+"\n```"),s="```\n"+s.slice(t);continue}}else{if(r=n.lastIndexOf(` `),rt/2?r=o:r=t}r<=0&&(r=t)}e.push(s.slice(0,r)),s=s.slice(r).replace(/^\n+/,"")}return s.length>0&&e.push(s),e}function ec(i){let t=0,e=0;for(;(e=i.indexOf("```",e))!==-1;)t+=1,e+=3;return t}function tc(i){let t=0,e=0,s=0;for(;sDate.now());let e=t.getSettings();this.rateLimiter=new gn({maxPerWindow:Math.max(1,e.channelRateLimitPerConversation),windowMs:Math.max(1e3,e.channelRateLimitWindowMinutes*6e4),now:this.now})}adapters=new Map;adapterConfigs=new Map;sessions=new Map;conversationLocks=new Map;rateLimiter;metrics=new Map;statusListeners=new Set;adapterUnsubscribes=new Map;now;threadBindings=new Map;hibernationInterval=null;started=!1;async start(t){if(!this.started){this.started=!0;for(let e of t.channels)await this.bringUpChannel(e,t);this.hibernationInterval=window.setInterval(()=>{this.runHibernationSweep()},6e4)}}async stop(){if(!this.started)return;this.started=!1,this.hibernationInterval&&(window.clearInterval(this.hibernationInterval),this.hibernationInterval=null);let t=Array.from(this.adapters.values());await Promise.all(t.map(async e=>{try{await e.stop()}catch(s){console.warn(`Agent Fleet: channel adapter ${e.config.name} stop() failed`,s)}})),this.adapters.clear(),this.adapterConfigs.clear();for(let e of this.adapterUnsubscribes.values())for(let s of e)s();this.adapterUnsubscribes.clear(),this.conversationLocks.size>0&&await Promise.allSettled(Array.from(this.conversationLocks.values()));for(let e of this.sessions.values())try{e.session.isStreaming?e.session.abort():e.session.hibernate()}catch{}this.sessions.clear(),this.conversationLocks.clear(),this.conversationLockGen.clear(),this.rateLimiter.resetAll()}async reconcile(t){if(!this.started)return;let e=new Map;for(let s of t.channels)e.set(s.name,s);for(let[s,n]of this.adapters){let a=e.get(s),r=a&&this.isChannelRuntimeValid(a,t);if(!a||!a.enabled||!r){await this.tearDownChannel(s);continue}let o=this.adapterConfigs.get(s);o&&this.requiresRestart(o,a)?(await this.tearDownChannel(s),await this.bringUpChannel(a,t)):(this.adapterConfigs.set(s,a),n.config=a)}for(let[s,n]of e)!this.adapters.has(s)&&n.enabled&&this.isChannelRuntimeValid(n,t)&&await this.bringUpChannel(n,t);this.notifyStatusListeners()}getCredentials(){return this.deps.getChannelCredentials?.()??this.deps.getSettings().channelCredentials??{}}isChannelRuntimeValid(t,e){let s=e.agents.find(a=>a.name===t.defaultAgent);if(!s||s.approvalRequired.length>0)return!1;let n=this.getCredentials()[t.credentialRef];return!(!n||n.type!==t.type)}async bringUpChannel(t,e){if(!t.enabled||!this.isChannelRuntimeValid(t,e))return;let s=this.getCredentials()[t.credentialRef];if(!s)return;let n;try{n=this.deps.adapterFactory(t,s)}catch(r){console.error(`Agent Fleet: failed to build adapter for channel ${t.name}`,r);return}let a=[];a.push(n.onInbound(r=>{this.handleInbound(n,r)})),a.push(n.onStatusChange(()=>this.notifyStatusListeners())),n.onAgentSwitch&&a.push(n.onAgentSwitch((r,o,c)=>{let l=`${t.name}:${r}`;this.threadBindings.set(l,o),this.persistBindings(t.name)})),n.setAllowedAgentsResolver?.(()=>this.resolveAllowedAgents(t)),this.adapters.set(t.name,n),this.adapterConfigs.set(t.name,t),this.adapterUnsubscribes.set(t.name,a),this.ensureMetrics(t.name),await this.loadBindings(t.name);try{await n.start()}catch(r){console.error(`Agent Fleet: channel adapter ${t.name} start() failed`,r)}this.notifyStatusListeners()}async tearDownChannel(t){let e=this.adapters.get(t);if(!e)return;try{await e.stop()}catch(a){console.warn(`Agent Fleet: channel adapter ${t} stop() failed`,a)}let s=this.adapterUnsubscribes.get(t);if(s)for(let a of s)a();this.adapterUnsubscribes.delete(t),this.adapters.delete(t),this.adapterConfigs.delete(t);let n=`${t}:`;for(let[a,r]of this.sessions)if(a.startsWith(n)){try{r.session.isStreaming?r.session.abort():r.session.hibernate()}catch{}this.sessions.delete(a)}}requiresRestart(t,e){return t.type!==e.type||t.credentialRef!==e.credentialRef||JSON.stringify(t.transport)!==JSON.stringify(e.transport)}async handleInbound(t,e){let s=t.config,n=`${s.name}:${e.conversationId}`;if(!(s.allowedUsers.length>0&&(!e.externalUserId||!s.allowedUsers.includes(e.externalUserId)))){if(!this.rateLimiter.tryConsume(n)){console.warn(`Agent Fleet: rate-limited message from ${e.externalUserId} on ${s.name} (conversation ${e.conversationId})`);try{await t.send(e.conversationId,"_Rate limit exceeded. Please slow down and try again in a few minutes._")}catch(a){console.warn(`Agent Fleet: rate-limit reply failed on ${s.name}`,a)}return}await this.withConversationLock(n,async()=>{let a=this.ensureMetrics(s.name);a.messagesReceived+=1,a.lastMessageAt=this.now();let r=this.resolveAllowedAgents(s),o=sc(e.text,r),c,l;if(o){c=o.agent,l=o.rest;let d=this.threadBindings.get(n);if(this.threadBindings.set(n,c),this.persistBindings(s.name),d!==c)try{await t.setThreadTitle?.(e.conversationId,c)}catch{}if(!l){try{await t.send(e.conversationId,`_Now chatting with *${c}*. Send your next message to start._`)}catch{}return}}else{let d=`${s.name}:${e.conversationId.replace(/:thread:[^:]+$/,`:user:${e.externalUserId}`)}`;c=this.threadBindings.get(n)??this.threadBindings.get(d)??s.defaultAgent,l=e.text}try{await t.setTyping(e.conversationId,!0)}catch{}if(e.images&&e.images.length>0){let d=await this.saveInboundImages(e.images);d&&(l=d+(l||"Please analyze this image."))}let h="";try{let u=await(await this.getOrCreateSession(s,e.conversationId,c)).sendMessage(l,()=>{});if(h=u.text.trim(),u.toolCalls.length>0){let p=nc(u.toolCalls);p&&(h+=` +`);o>t/2?r=o:r=t}r<=0&&(r=t)}e.push(s.slice(0,r)),s=s.slice(r).replace(/^\n+/,"")}return s.length>0&&e.push(s),e}function fc(i){let t=0,e=0;for(;(e=i.indexOf("```",e))!==-1;)t+=1,e+=3;return t}function gc(i){let t=0,e=0,s=0;for(;sDate.now());let e=t.getSettings();this.rateLimiter=new vn({maxPerWindow:Math.max(1,e.channelRateLimitPerConversation),windowMs:Math.max(1e3,e.channelRateLimitWindowMinutes*6e4),now:this.now})}adapters=new Map;adapterConfigs=new Map;sessions=new Map;conversationLocks=new Map;rateLimiter;metrics=new Map;statusListeners=new Set;adapterUnsubscribes=new Map;now;threadBindings=new Map;hibernationInterval=null;started=!1;async start(t){if(!this.started){this.started=!0;for(let e of t.channels)await this.bringUpChannel(e,t);this.hibernationInterval=window.setInterval(()=>{this.runHibernationSweep()},6e4)}}async stop(){if(!this.started)return;this.started=!1,this.hibernationInterval&&(window.clearInterval(this.hibernationInterval),this.hibernationInterval=null);let t=Array.from(this.adapters.values());await Promise.all(t.map(async e=>{try{await e.stop()}catch(s){console.warn(`Agent Fleet: channel adapter ${e.config.name} stop() failed`,s)}})),this.adapters.clear(),this.adapterConfigs.clear();for(let e of this.adapterUnsubscribes.values())for(let s of e)s();this.adapterUnsubscribes.clear(),this.conversationLocks.size>0&&await Promise.allSettled(Array.from(this.conversationLocks.values()));for(let e of this.sessions.values())try{e.session.isStreaming?e.session.abort():e.session.hibernate()}catch{}this.sessions.clear(),this.conversationLocks.clear(),this.conversationLockGen.clear(),this.rateLimiter.resetAll()}async reconcile(t){if(!this.started)return;let e=new Map;for(let s of t.channels)e.set(s.name,s);for(let[s,n]of this.adapters){let a=e.get(s),r=a&&this.isChannelRuntimeValid(a,t);if(!a||!a.enabled||!r){await this.tearDownChannel(s);continue}let o=this.adapterConfigs.get(s);o&&this.requiresRestart(o,a)?(await this.tearDownChannel(s),await this.bringUpChannel(a,t)):(this.adapterConfigs.set(s,a),n.config=a)}for(let[s,n]of e)!this.adapters.has(s)&&n.enabled&&this.isChannelRuntimeValid(n,t)&&await this.bringUpChannel(n,t);this.notifyStatusListeners()}getCredentials(){return this.deps.getChannelCredentials?.()??this.deps.getSettings().channelCredentials??{}}isChannelRuntimeValid(t,e){let s=e.agents.find(a=>a.name===t.defaultAgent);if(!s||s.approvalRequired.length>0)return!1;let n=this.getCredentials()[t.credentialRef];return!(!n||n.type!==t.type)}async bringUpChannel(t,e){if(!t.enabled||!this.isChannelRuntimeValid(t,e))return;let s=this.getCredentials()[t.credentialRef];if(!s)return;let n;try{n=this.deps.adapterFactory(t,s)}catch(r){console.error(`Agent Fleet: failed to build adapter for channel ${t.name}`,r);return}let a=[];a.push(n.onInbound(r=>{this.handleInbound(n,r)})),a.push(n.onStatusChange(()=>this.notifyStatusListeners())),n.onAgentSwitch&&a.push(n.onAgentSwitch((r,o,c)=>{let l=`${t.name}:${r}`;this.threadBindings.set(l,o),this.persistBindings(t.name)})),n.setAllowedAgentsResolver?.(()=>this.resolveAllowedAgents(t)),this.adapters.set(t.name,n),this.adapterConfigs.set(t.name,t),this.adapterUnsubscribes.set(t.name,a),this.ensureMetrics(t.name),await this.loadBindings(t.name);try{await n.start()}catch(r){console.error(`Agent Fleet: channel adapter ${t.name} start() failed`,r)}this.notifyStatusListeners()}async tearDownChannel(t){let e=this.adapters.get(t);if(!e)return;try{await e.stop()}catch(a){console.warn(`Agent Fleet: channel adapter ${t} stop() failed`,a)}let s=this.adapterUnsubscribes.get(t);if(s)for(let a of s)a();this.adapterUnsubscribes.delete(t),this.adapters.delete(t),this.adapterConfigs.delete(t);let n=`${t}:`;for(let[a,r]of this.sessions)if(a.startsWith(n)){try{r.session.isStreaming?r.session.abort():r.session.hibernate()}catch{}this.sessions.delete(a)}}requiresRestart(t,e){return t.type!==e.type||t.credentialRef!==e.credentialRef||JSON.stringify(t.transport)!==JSON.stringify(e.transport)}async handleInbound(t,e){let s=t.config,n=`${s.name}:${e.conversationId}`;if(!(s.allowedUsers.length>0&&(!e.externalUserId||!s.allowedUsers.includes(e.externalUserId)))){if(!this.rateLimiter.tryConsume(n)){console.warn(`Agent Fleet: rate-limited message from ${e.externalUserId} on ${s.name} (conversation ${e.conversationId})`);try{await t.send(e.conversationId,"_Rate limit exceeded. Please slow down and try again in a few minutes._")}catch(a){console.warn(`Agent Fleet: rate-limit reply failed on ${s.name}`,a)}return}await this.withConversationLock(n,async()=>{let a=this.ensureMetrics(s.name);a.messagesReceived+=1,a.lastMessageAt=this.now();let r=this.resolveAllowedAgents(s),o=yc(e.text,r),c,l;if(o){c=o.agent,l=o.rest;let d=this.threadBindings.get(n);if(this.threadBindings.set(n,c),this.persistBindings(s.name),d!==c)try{await t.setThreadTitle?.(e.conversationId,c)}catch{}if(!l){try{await t.send(e.conversationId,`_Now chatting with *${c}*. Send your next message to start._`)}catch{}return}}else{let d=`${s.name}:${e.conversationId.replace(/:thread:[^:]+$/,`:user:${e.externalUserId}`)}`;c=this.threadBindings.get(n)??this.threadBindings.get(d)??s.defaultAgent,l=e.text}try{await t.setTyping(e.conversationId,!0)}catch{}if(e.images&&e.images.length>0){let d=await this.saveInboundImages(e.images);d&&(l=d+(l||"Please analyze this image."))}let h="";try{let u=await(await this.getOrCreateSession(s,e.conversationId,c)).sendMessage(l,()=>{});if(h=u.text.trim(),u.toolCalls.length>0){let p=vc(u.toolCalls);p&&(h+=` _${p}_`)}}catch(d){console.error(`Agent Fleet: channel turn failed on ${s.name}/${e.conversationId}`,d),h=`_Sorry \u2014 the agent run failed. ${d instanceof Error?d.message:String(d)}_`}try{h&&((s.allowedAgents.length>1||s.allowedAgents.length===0&&this.resolveAllowedAgents(s).length>1)&&(h=`*[${c}]* -${h}`),await this.deliverReply(t,e.conversationId,h),a.messagesSent+=1)}catch(d){console.error(`Agent Fleet: reply delivery failed on ${s.name}`,d)}finally{try{await t.setTyping(e.conversationId,!1)}catch{}}this.enforceHardCap()})}}async deliverReply(t,e,s){let n=ur(s);for(let a of n)await t.send(e,a)}async saveInboundImages(t){let s=`${this.deps.getSettings().fleetFolder}/chat-images`,n=[];for(let a of t)try{this.deps.vault.getAbstractFileByPath((0,dt.normalizePath)(s))||await this.deps.vault.createFolder((0,dt.normalizePath)(s));let r=(0,dt.normalizePath)(`${s}/${a.filename}`),o=r;if(this.deps.vault.getAbstractFileByPath(r)){let d=a.filename.lastIndexOf("."),u=d>0?a.filename.slice(0,d):a.filename,p=d>0?a.filename.slice(d):"";o=(0,dt.normalizePath)(`${s}/${u}_${Date.now()}${p}`)}await this.deps.vault.createBinary(o,a.data);let l=this.deps.vault.adapter.basePath??"",h=l?`${l}/${o}`:o;n.push(`### Image: ${a.filename} +${h}`),await this.deliverReply(t,e.conversationId,h),a.messagesSent+=1)}catch(d){console.error(`Agent Fleet: reply delivery failed on ${s.name}`,d)}finally{try{await t.setTyping(e.conversationId,!1)}catch{}}this.enforceHardCap()})}}async deliverReply(t,e,s){let n=br(s);for(let a of n)await t.send(e,a)}async saveInboundImages(t){let s=`${this.deps.getSettings().fleetFolder}/chat-images`,n=[];for(let a of t)try{this.deps.vault.getAbstractFileByPath((0,ut.normalizePath)(s))||await this.deps.vault.createFolder((0,ut.normalizePath)(s));let r=(0,ut.normalizePath)(`${s}/${a.filename}`),o=r;if(this.deps.vault.getAbstractFileByPath(r)){let d=a.filename.lastIndexOf("."),u=d>0?a.filename.slice(0,d):a.filename,p=d>0?a.filename.slice(d):"";o=(0,ut.normalizePath)(`${s}/${u}_${Date.now()}${p}`)}await this.deps.vault.createBinary(o,a.data);let l=this.deps.vault.adapter.basePath??"",h=l?`${l}/${o}`:o;n.push(`### Image: ${a.filename} The image file is located at: ${h} Please read and analyze this image.`)}catch(r){console.warn("Agent Fleet: failed to save inbound image",a.filename,r)}return n.length===0?"":`## Attached Images @@ -11999,24 +12029,26 @@ ${n.join(` --- -`}async getOrCreateSession(t,e,s){let n=`${t.name}:${e}:${s}`,a=this.sessions.get(n);if(a)return a.session;let r=this.deps.getRepository(),o=r.getAgentByName(s);if(!o)throw new Error(`Channel ${t.name} bound to missing agent ${s}`);let c=new Gt(o,this.deps.getSettings(),r,this.deps.vault,{channelName:t.name,conversationId:`${e}:${s}`,channelContext:t.channelContext||void 0,mcpAuth:this.deps.getMcpAuth?.()});try{await c.loadPersistedState()}catch{}return this.sessions.set(n,{session:c,channelName:t.name,conversationId:e,sessionKey:n}),c}resolveAllowedAgents(t){let e=new Set(this.deps.getRepository().getSnapshot().agents.filter(n=>n.enabled).map(n=>n.name));return(t.allowedAgents.length>0?t.allowedAgents:[...e]).filter(n=>e.has(n))}async persistBindings(t){let e=`${t}:`,s={};for(let[o,c]of this.threadBindings)o.startsWith(e)&&(s[o.slice(e.length)]=c);let n=this.deps.getSettings(),a=(0,dt.normalizePath)(`${n.fleetFolder}/channels/${t}/bindings.json`),r=JSON.stringify(s,null,2);try{let o=this.deps.vault.getAbstractFileByPath(a);if(o instanceof dt.TFile)await this.deps.vault.modify(o,r);else{let c=a.slice(0,a.lastIndexOf("/"));if(!this.deps.vault.getAbstractFileByPath(c))try{await this.deps.vault.createFolder(c)}catch{}await this.deps.vault.create(a,r)}}catch(o){console.warn(`Agent Fleet: failed to persist thread bindings for ${t}`,o)}}async loadBindings(t){let e=this.deps.getSettings(),s=(0,dt.normalizePath)(`${e.fleetFolder}/channels/${t}/bindings.json`);try{let n=this.deps.vault.getAbstractFileByPath(s);if(!(n instanceof dt.TFile))return;let a=await this.deps.vault.cachedRead(n),r=JSON.parse(a);for(let[o,c]of Object.entries(r))typeof c=="string"&&this.threadBindings.set(`${t}:${o}`,c)}catch{}}getThreadAgent(t,e){return this.threadBindings.get(`${t}:${e}`)}enforceHardCap(){let t=Math.max(1,this.deps.getSettings().maxConcurrentChannelSessions),e=Array.from(this.sessions.values()).filter(n=>n.session.isProcessAlive&&!n.session.isStreaming);if(e.length<=t)return;e.sort((n,a)=>n.session.lastActiveAt-a.session.lastActiveAt);let s=e.length-t;for(let n=0;n{n=o}),r=(this.conversationLockGen.get(t)??0)+1;this.conversationLockGen.set(t,r),this.conversationLocks.set(t,s.then(()=>a));try{await s,await e()}finally{n(),this.conversationLockGen.get(t)===r&&(this.conversationLocks.delete(t),this.conversationLockGen.delete(t))}}ensureMetrics(t){let e=this.metrics.get(t);return e||(e={messagesReceived:0,messagesSent:0,lastMessageAt:null},this.metrics.set(t,e)),e}getMetrics(t){return{...this.ensureMetrics(t)}}getChannelStatus(t){let e=this.adapters.get(t);return e?e.getStatus():"disabled"}getConnectedCount(){let t=0;for(let e of this.adapters.values())e.getStatus()==="connected"&&(t+=1);return t}getSessionCount(t){let e=0,s=`${t}:`;for(let n of this.sessions.keys())n.startsWith(s)&&(e+=1);return e}onStatusChange(t){return this.statusListeners.add(t),()=>this.statusListeners.delete(t)}notifyStatusListeners(){for(let t of this.statusListeners)try{t()}catch{}}};function nc(i){if(i.length===0)return"";let t=new Map;for(let s of i)t.set(s.name,(t.get(s.name)??0)+1);let e=[];for(let[s,n]of t)e.push(n>1?`${s}\xD7${n}`:s);return`Used ${i.length} tool${i.length===1?"":"s"}: ${e.join(", ")}`}var wn=class{credentials=new Map;secretStore;persistCallback;setSecretStore(t){this.secretStore=t}loadCredentials(t){if(this.credentials.clear(),this.secretStore?.available){let e=this.secretStore.listByPrefix(zt);for(let s of e){let n=zt+"-",a=s.startsWith(n)?s.slice(n.length):s,r=this.secretStore.getJson(zt,a);if(r){let o=r._ref??a,c={...r};delete c._ref,this.credentials.set(o,c)}}}if(this.credentials.size===0&&t){for(let[e,s]of Object.entries(t))this.credentials.set(e,s);this.secretStore?.available&&this.credentials.size>0&&this.persistToSecretStore()}}onChanged(t){this.persistCallback=t}get(t){return this.credentials.get(t)}set(t,e){this.credentials.set(t,e),this.persist()}delete(t){this.credentials.delete(t),this.secretStore?.delete(zt,t),this.persist()}list(){return Array.from(this.credentials.entries()).map(([t,e])=>({ref:t,entry:e}))}toRecord(){let t={};for(let[e,s]of this.credentials.entries())t[e]=s;return t}persist(){this.persistToSecretStore(),this.persistCallback&&this.persistCallback(this.toRecord())}persistToSecretStore(){if(this.secretStore?.available)for(let[t,e]of this.credentials)this.secretStore.setJson(zt,t,{...e,_ref:t})}};function mr(){return{status:"disconnected",scope:"user",tools:[],toolDetails:[]}}function ac(i,t,e){if(e)return"stdio";let s=(i??"").toLowerCase();return s==="sse"?"sse":s==="http"||s==="streamable-http"||s==="streamable_http"?"http":t?.endsWith("/sse")?"sse":"http"}function fr(i){let t=[],e={},s;try{s=JSON.parse(i)}catch{return{servers:t,tokens:e}}let n=s.mcpServers;if(!n||typeof n!="object")return{servers:t,tokens:e};for(let[a,r]of Object.entries(n)){if(!r||typeof r!="object")continue;let o=r,c=typeof o.command=="string"?o.command:void 0,l=typeof o.url=="string"?o.url:void 0;if(!c&&!l)continue;let h=ac(typeof o.type=="string"?o.type:void 0,l,!!c),d={name:a,type:h,enabled:!0,source:"imported",...mr()};if(h==="stdio")d.command=c,Array.isArray(o.args)&&(d.args=o.args.filter(u=>typeof u=="string")),o.env&&typeof o.env=="object"&&(d.env=pr(o.env));else{d.url=l;let u=o.headers&&typeof o.headers=="object"?pr(o.headers):{},p=u.Authorization??u.authorization;p?.startsWith("Bearer ")?(e[a]=p.slice(7),delete u.Authorization,delete u.authorization,d.auth="oauth"):d.auth="none",Object.keys(u).length>0&&(d.headers=u)}t.push(d)}return{servers:t,tokens:e}}function gr(i){let t=new Map,e=null;for(let n of i.split(/\r?\n/)){let a=n.trim();if(!a||a.startsWith("#"))continue;let r=a.match(/^\[(.+)\]$/);if(r){let u=r[1].trim().match(/^mcp_servers\.(.+)$/);if(!u){e=null;continue}let p=u[1],m=null;p.endsWith(".env")?(m="env",p=p.slice(0,-4)):p.endsWith(".oauth")&&(m="oauth",p=p.slice(0,-6));let f=bn(p);if(!f){e=null;continue}t.has(f)||t.set(f,{name:f,enabled:!0}),e={name:f,sub:m};continue}if(!e)continue;let o=a.match(/^([^=]+?)\s*=\s*(.+)$/);if(!o)continue;let c=bn(o[1].trim()),l=o[2].trim(),h=t.get(e.name);if(e.sub==="env")h.env??={},h.env[c]=Vt(l);else if(e.sub==="oauth")c==="client_id"&&(h.oauthClientId=Vt(l));else switch(c){case"command":h.command=Vt(l);break;case"args":h.args=ic(l);break;case"url":h.url=Vt(l);break;case"bearer_token_env_var":h.bearerEnvVar=Vt(l);break;case"oauth_resource":h.oauthResource=Vt(l);break;case"enabled":h.enabled=l==="true";break;default:break}}let s=[];for(let n of t.values()){if(!n.command&&!n.url)continue;let a=n.command?"stdio":n.url?.endsWith("/sse")?"sse":"http",r={name:n.name,type:a,enabled:n.enabled,source:"imported",...mr()};a==="stdio"?(r.command=n.command,n.args&&n.args.length>0&&(r.args=n.args),n.env&&Object.keys(n.env).length>0&&(r.env=n.env)):(r.url=n.url,n.oauthClientId||n.oauthResource?(r.auth="oauth",r.oauth={clientId:n.oauthClientId,resource:n.oauthResource}):n.bearerEnvVar?(r.auth="bearer",r.envSecretKeys=[n.bearerEnvVar]):r.auth="none"),s.push(r)}return{servers:s,tokens:{}}}function yr(...i){let t=new Map,e={};for(let s of i){for(let n of s.servers){let a=n.name.trim().toLowerCase();t.has(a)||t.set(a,n)}Object.assign(e,s.tokens)}return{servers:[...t.values()],tokens:e}}function bn(i){let t=i.trim();return t.startsWith('"')&&t.endsWith('"')||t.startsWith("'")&&t.endsWith("'")?t.slice(1,-1):t}function Vt(i){let t=i.trim();if(t.startsWith('"')||t.startsWith("'")){let e=t[0],s=t.indexOf(e,1);if(s>0)return t.slice(1,s)}return bn(t.replace(/\s+#.*$/,""))}function ic(i){let t=i.trim();try{let s=JSON.parse(t);if(Array.isArray(s))return s.filter(n=>typeof n=="string")}catch{}return t.replace(/^\[/,"").replace(/\]$/,"").split(",").map(s=>bn(s.trim())).filter(Boolean)}function pr(i){let t={};for(let[e,s]of Object.entries(i))typeof s=="string"&&(t[e]=s);return t}var _d=ze(po(),1),Ad=ze(Pn(),1),Ed=ze(Jt(),1),Pd=ze(Pa(),1),Rd=ze(Ia(),1),Dd=ze(Ba(),1),xo=ze(Mn(),1),Id=ze(ko(),1);var $a=xo.default;var So=require("obsidian");var Md="https://slack.com/api";function Ld(i){let t=i.team??"unknown",e=i.channel??"unknown",s=i.thread_ts??i.ts??"unknown";return`slack:${t}:${e}:thread:${s}`}function Fd(i){let t=i.split(":");return t.length>=3&&t[0]==="slack"?t[2]??null:null}function Od(i){let t=i.split(":");if(t[3]==="thread"&&t[4])return t[4]}var Fn=class{type="slack";config;credential;ws=null;status="stopped";stopping=!1;backoffMs=1e3;reconnectTimer=null;inboundHandlers=new Set;statusHandlers=new Set;agentSwitchHandlers=new Set;allowedAgentsResolver;sendQueues=new Map;threadContext=new Map;constructor(t,e){if(e.type!=="slack")throw new Error(`SlackAdapter requires a slack credential, got ${e.type}`);this.config=t,this.credential=e}async start(){this.stopping=!1,await this.connect()}async stop(){if(this.stopping=!0,this.reconnectTimer&&(window.clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.ws){try{this.ws.close()}catch{}this.ws=null}this.threadContext.clear(),this.sendQueues.clear(),this.setStatus("stopped")}getStatus(){return this.status}async send(t,e){let s=Fd(t);if(!s){console.warn(`Agent Fleet: could not extract channel id from ${t}`);return}let n=Od(t),a=yn(e);await this.enqueueSend(s,async()=>{await this.slackApi("chat.postMessage",{channel:s,text:a,...n?{thread_ts:n}:{}})})}async broadcast(t){let e=this.config.allowedUsers[0];if(!e){console.warn(`Agent Fleet: broadcast on ${this.config.name} skipped \u2014 no allowed users configured`);return}try{let n=(await this.slackApi("conversations.open",{users:e})).channel?.id;if(!n){console.warn(`Agent Fleet: broadcast \u2014 conversations.open returned no channel for user ${e}`);return}let a=yn(t);await this.slackApi("chat.postMessage",{channel:n,text:a})}catch(s){console.error(`Agent Fleet: broadcast failed on ${this.config.name}`,s)}}async setThreadTitle(t,e){let s=this.threadContext.get(t);if(s)try{await this.slackApi("assistant.threads.setTitle",{channel_id:s.channelId,thread_ts:s.threadTs,title:e})}catch(n){console.warn(`Agent Fleet: assistant.threads.setTitle failed on ${this.config.name}`,n)}}async setTyping(t,e){let s=this.threadContext.get(t);if(s)try{await this.slackApi("assistant.threads.setStatus",{channel_id:s.channelId,thread_ts:s.threadTs,status:e?"is thinking...":""})}catch(n){console.warn(`Agent Fleet: assistant.threads.setStatus (${e?"on":"off"}) failed on ${this.config.name}`,n)}}onInbound(t){return this.inboundHandlers.add(t),()=>this.inboundHandlers.delete(t)}onStatusChange(t){return this.statusHandlers.add(t),()=>this.statusHandlers.delete(t)}setAllowedAgentsResolver(t){this.allowedAgentsResolver=t}onAgentSwitch(t){return this.agentSwitchHandlers.add(t),()=>this.agentSwitchHandlers.delete(t)}async connect(){if(this.stopping)return;this.setStatus(this.ws?"reconnecting":"connecting");let t;try{let e=await this.slackApi("apps.connections.open",{},{useAppToken:!0});if(!e.ok||!e.url)throw new Error(e.error??"apps.connections.open returned no URL");t=e.url}catch(e){console.error(`Agent Fleet: Slack apps.connections.open failed for channel ${this.config.name}`,e),this.setStatus("needs-auth"),this.scheduleReconnect();return}try{let e=new $a(t);e.on("open",()=>{}),e.on("message",a=>{this.handleSocketData(a)}),e.on("error",a=>{console.warn(`Agent Fleet: Slack WebSocket error on ${this.config.name}`,a),this.setStatus("error")}),e.on("close",()=>{this.ws=null,this.stopping||this.scheduleReconnect()}),this.ws=e;let s=window.setTimeout(()=>{if(this.status==="connecting"||this.status==="reconnecting"){console.warn(`Agent Fleet: Slack WebSocket connect timeout on ${this.config.name}`);try{e.close()}catch{}}},3e4);e.on("close",()=>window.clearTimeout(s));let n=this.onStatusChange(a=>{a==="connected"&&(window.clearTimeout(s),n())})}catch(e){console.error("Agent Fleet: Slack WebSocket open failed",e),this.setStatus("error"),this.scheduleReconnect();return}}handleSocketData(t){let e;try{e=JSON.parse(t.toString())}catch{return}if(e.type==="hello"){this.backoffMs=1e3,this.setStatus("connected");return}if(e.type==="disconnect"){try{this.ws?.close()}catch{}return}if(e.type==="events_api"&&e.envelope_id){this.ackEnvelope(e.envelope_id),this.routeEventPayload(e.payload);return}if(e.type==="slash_commands"&&e.envelope_id){this.ackEnvelope(e.envelope_id),this.handleSlashCommand(e.payload);return}if(e.type==="interactive"&&e.envelope_id){this.ackEnvelope(e.envelope_id),this.handleInteraction(e.payload);return}e.envelope_id&&this.ackEnvelope(e.envelope_id)}async handleSlashCommand(t){if(!t)return;let e=t.command,s=t.channel_id,n=t.user_id;if(!(!e||!s||!n)){if(e==="/agents"){let a=this.allowedAgentsResolver?this.allowedAgentsResolver():this.config.allowedAgents;if(a.length===0){await this.slackApi("chat.postEphemeral",{channel:s,user:n,text:"No agents available. Add existing, enabled agents to `allowed_agents` in the channel file."});return}let r=a.map(c=>({type:"button",text:{type:"plain_text",text:c===this.config.defaultAgent?`${c} \u2713`:c,emoji:!0},action_id:`switch_agent_${c}`,value:c})),o=[{type:"section",text:{type:"mrkdwn",text:"*Select an agent to chat with:*"}}];for(let c=0;c\s*/,"").trim(),!n.text))return;let o=Ld(n),c=n.thread_ts??n.ts;if(n.channel&&c&&(this.threadContext.set(o,{channelId:n.channel,threadTs:c}),this.threadContext.size>500)){let d=this.threadContext.keys().next();d.done||this.threadContext.delete(d.value)}let l={conversationId:o,externalUserId:n.user,text:n.text,timestamp:new Date().toISOString(),meta:{slack_channel:n.channel,slack_ts:n.ts,thread_ts:n.thread_ts}};for(let h of this.inboundHandlers)try{h(l)}catch(d){console.error("Agent Fleet: Slack inbound handler threw",d)}}scheduleReconnect(){if(this.stopping||this.reconnectTimer)return;let t=this.backoffMs;this.backoffMs=Math.min(3e4,this.backoffMs*2),console.warn(`Agent Fleet: Slack channel ${this.config.name} scheduling reconnect in ${t}ms`),this.reconnectTimer=window.setTimeout(()=>{this.reconnectTimer=null,!this.stopping&&this.connect()},t)}setStatus(t){if(this.status!==t){this.status=t;for(let e of this.statusHandlers)try{e(t)}catch{}}}async slackApi(t,e,s={}){let n=s.useAppToken?this.credential.appToken:this.credential.botToken,a=`${Md}/${t}`,r=await(0,So.requestUrl)({url:a,method:"POST",contentType:"application/json; charset=utf-8",headers:{Authorization:`Bearer ${n}`},body:JSON.stringify(e),throw:!1});if(r.status===429){let c=Number(r.headers["retry-after"]??"1");return await new Promise(l=>window.setTimeout(l,Math.max(1e3,c*1e3))),this.slackApi(t,e,s)}if(r.status<200||r.status>=300)throw new Error(`Slack ${t} HTTP ${r.status}`);let o=r.json;if(o.ok===!1)throw new Error(`Slack ${t} error: ${o.error??"unknown"}`);return o}async enqueueSend(t,e){let n=(this.sendQueues.get(t)??Promise.resolve()).then(async()=>{try{await e()}finally{await new Promise(r=>window.setTimeout(r,1e3))}}),a=n.catch(r=>{console.warn(`Agent Fleet: Slack send queue error for ${t}`,r)});this.sendQueues.set(t,a),await n,this.sendQueues.get(t)===a&&this.sendQueues.delete(t)}};var ja=require("obsidian"),Co="https://api.telegram.org",On=class{type="telegram";config;credential;status="stopped";stopping=!1;pollOffset=0;pollTimer=null;backoffMs=1e3;typingIntervals=new Map;pollAbort=null;inboundHandlers=new Set;statusHandlers=new Set;agentSwitchHandlers=new Set;allowedAgentsResolver;constructor(t,e){if(e.type!=="telegram")throw new Error(`TelegramAdapter requires a telegram credential, got ${e.type}`);this.config=t,this.credential=e}async start(){this.stopping=!1;try{let e=(await this.tgApi("getUpdates",{offset:-1,limit:1})).result?.[0];e&&(this.pollOffset=e.update_id+1)}catch{}try{await this.tgApi("setMyCommands",{commands:[{command:"agents",description:"List available agents"}]})}catch{}this.setStatus("connected"),this.poll()}async stop(){this.stopping=!0,this.pollTimer&&(window.clearTimeout(this.pollTimer),this.pollTimer=null),this.pollAbort?.abort(),this.pollAbort=null;for(let[,t]of this.typingIntervals)window.clearInterval(t);this.typingIntervals.clear(),this.setStatus("stopped")}getStatus(){return this.status}async send(t,e){let s=To(t);if(!s)return;let n=_o(t),a=Ao(e,4096);for(let r of a)await this.tgApi("sendMessage",{chat_id:s,text:r,parse_mode:"Markdown",...n?{message_thread_id:Number(n)}:{}})}async setTyping(t,e){let s=To(t);if(!s)return;let n=_o(t),a={chat_id:s,action:"typing"};if(n&&(a.message_thread_id=Number(n)),e){let r=this.typingIntervals.get(t);r&&window.clearInterval(r);try{await this.tgApi("sendChatAction",a)}catch(c){console.warn("Agent Fleet: Telegram sendChatAction failed",c)}let o=window.setInterval(()=>{this.tgApi("sendChatAction",a).catch(()=>{})},4500);this.typingIntervals.set(t,o)}else{let r=this.typingIntervals.get(t);r&&(window.clearInterval(r),this.typingIntervals.delete(t))}}async setThreadTitle(t,e){}async broadcast(t){let e=this.config.allowedUsers[0];if(e)try{let s=Ao(t,4096);for(let n of s)await this.tgApi("sendMessage",{chat_id:e,text:n,parse_mode:"Markdown"})}catch(s){console.error(`Agent Fleet: Telegram broadcast failed on ${this.config.name}`,s)}}onInbound(t){return this.inboundHandlers.add(t),()=>this.inboundHandlers.delete(t)}onStatusChange(t){return this.statusHandlers.add(t),()=>this.statusHandlers.delete(t)}setAllowedAgentsResolver(t){this.allowedAgentsResolver=t}pickerAgents(){return this.allowedAgentsResolver?this.allowedAgentsResolver():this.config.allowedAgents}onAgentSwitch(t){return this.agentSwitchHandlers.add(t),()=>this.agentSwitchHandlers.delete(t)}poll(){this.stopping||(async()=>{try{this.pollAbort=new AbortController;let t=await this.tgApi("getUpdates",{offset:this.pollOffset,timeout:30,allowed_updates:["message","callback_query"]},this.pollAbort.signal);if(t.ok&&t.result)for(let e of t.result){if(this.pollOffset=e.update_id+1,e.callback_query){this.handleCallbackQuery(e.callback_query);continue}e.message&&this.routeMessage(e.message)}this.backoffMs=1e3,this.status!=="connected"&&this.setStatus("connected")}catch(t){if(t instanceof DOMException&&t.name==="AbortError")return;if(console.warn(`Agent Fleet: Telegram poll failed on ${this.config.name}`,t),this.status!=="error"&&this.status!=="needs-auth"){let e=t instanceof Error?t.message:String(t);this.setStatus(e.includes("401")||e.includes("Unauthorized")?"needs-auth":"error")}await new Promise(e=>window.setTimeout(e,this.backoffMs)),this.backoffMs=Math.min(3e4,this.backoffMs*2)}this.stopping||(this.pollTimer=window.setTimeout(()=>this.poll(),100))})()}routeMessage(t){let e=t.photo&&t.photo.length>0,s=t.document&&this.isImageMime(t.document.mime_type),n=!!t.text;if(!t.from||!n&&!e&&!s)return;let a=t.text??t.caption??"";if(a==="/agents"||a.startsWith("/agents@")){this.handleAgentsCommand(t);return}if(a.startsWith("/"))return;let r=String(t.from.id),o=String(t.chat.id),c=t.message_thread_id?String(t.message_thread_id):void 0,l=c?`tg:${o}:topic:${c}`:`tg:${o}`;this.buildAndEmitMessage(t,a,l,r,o,c)}async buildAndEmitMessage(t,e,s,n,a,r){let o=[];try{if(t.photo&&t.photo.length>0){let l=t.photo[t.photo.length-1],h=await this.downloadFile(l.file_id,`photo_${t.message_id}.jpg`,"image/jpeg");h&&o.push(h)}if(t.document&&this.isImageMime(t.document.mime_type)){let l=t.document.file_name??`doc_${t.message_id}`,h=t.document.mime_type??"image/jpeg",d=await this.downloadFile(t.document.file_id,l,h);d&&o.push(d)}}catch(l){console.warn("Agent Fleet: Telegram image download failed",l)}let c={conversationId:s,externalUserId:n,text:e,timestamp:new Date(t.date*1e3).toISOString(),meta:{telegram_chat_id:a,telegram_message_id:t.message_id,telegram_thread_id:r,chat_type:t.chat.type},...o.length>0?{images:o}:{}};for(let l of this.inboundHandlers)try{l(c)}catch(h){console.error("Agent Fleet: Telegram inbound handler threw",h)}}async downloadFile(t,e,s){let a=(await this.tgApi("getFile",{file_id:t})).result?.file_path;if(!a)return null;let r=`${Co}/file/bot${this.credential.botToken}/${a}`;return{data:(await(0,ja.requestUrl)({url:r,method:"GET"})).arrayBuffer,filename:e,mimeType:s}}isImageMime(t){return t?t==="image/jpeg"||t==="image/png"||t==="image/gif"||t==="image/webp"||t==="image/bmp"||t==="image/tiff":!1}async handleAgentsCommand(t){let e=String(t.chat.id),s=t.message_thread_id,n=this.pickerAgents();if(n.length===0){await this.tgApi("sendMessage",{chat_id:e,text:"No agents available. Add existing, enabled agents to `allowed_agents` in the channel file.",...s?{message_thread_id:s}:{}});return}let a=n.map(r=>[{text:r===this.config.defaultAgent?`${r} \u2713`:r,callback_data:`switch:${r}`}]);await this.tgApi("sendMessage",{chat_id:e,text:"*Select an agent to chat with:*",parse_mode:"Markdown",reply_markup:{inline_keyboard:a},...s?{message_thread_id:s}:{}})}async handleCallbackQuery(t){let e=t.data;if(!e?.startsWith("switch:")){await this.tgApi("answerCallbackQuery",{callback_query_id:t.id});return}let s=e.slice(7),n=String(t.from.id),a=String(t.message?.chat?.id??t.from.id),r=t.message?.message_thread_id?String(t.message.message_thread_id):void 0,o=r?`tg:${a}:topic:${r}`:`tg:${a}`;for(let c of this.agentSwitchHandlers)try{c(o,s,n)}catch(l){console.error("Agent Fleet: Telegram agent switch handler threw",l)}if(await this.tgApi("answerCallbackQuery",{callback_query_id:t.id,text:`Switched to ${s}`}),t.message){let l=this.pickerAgents().map(h=>[{text:h===s?`${h} \u2713`:h,callback_data:`switch:${h}`}]);try{await this.tgApi("editMessageText",{chat_id:a,message_id:t.message.message_id,text:`*Active agent: ${s}*`,parse_mode:"Markdown",reply_markup:{inline_keyboard:l}})}catch{}}}async tgApi(t,e,s){if(s?.aborted)throw new DOMException("Aborted","AbortError");let n=`${Co}/bot${this.credential.botToken}/${t}`,a=(0,ja.requestUrl)({url:n,method:"POST",contentType:"application/json",body:JSON.stringify(e),throw:!1}),r;if(s?r=await Promise.race([a,new Promise((o,c)=>{s.addEventListener("abort",()=>c(new DOMException("Aborted","AbortError")),{once:!0})})]):r=await a,r.status===401||r.status===403)throw new Error(`Telegram ${t} ${r.status} Unauthorized`);if(r.status===429){let c=r.json?.parameters?.retry_after??1;return await new Promise(l=>window.setTimeout(l,c*1e3)),this.tgApi(t,e)}if(r.status<200||r.status>=300)throw new Error(`Telegram ${t} HTTP ${r.status}: ${r.text}`);return r.json}setStatus(t){if(this.status!==t){this.status=t;for(let e of this.statusHandlers)try{e(t)}catch{}}}};function To(i){return i.startsWith("tg:")?i.split(":")[1]??null:null}function _o(i){let t=i.split(":");if(t[2]==="topic"&&t[3])return t[3]}function Ao(i,t){if(i.length<=t)return[i];let e=[],s=i;for(;s.length>t;){let n=s.lastIndexOf(` +`}async getOrCreateSession(t,e,s){let n=`${t.name}:${e}:${s}`,a=this.sessions.get(n);if(a)return a.session;let r=this.deps.getRepository(),o=r.getAgentByName(s);if(!o)throw new Error(`Channel ${t.name} bound to missing agent ${s}`);let c=new Vt(o,this.deps.getSettings(),r,this.deps.vault,{channelName:t.name,conversationId:`${e}:${s}`,channelContext:t.channelContext||void 0,mcpAuth:this.deps.getMcpAuth?.()});this.deps.recordUsage&&c.setUsageRecorder(this.deps.recordUsage);try{await c.loadPersistedState()}catch{}return this.sessions.set(n,{session:c,channelName:t.name,conversationId:e,sessionKey:n}),c}resolveAllowedAgents(t){let e=new Set(this.deps.getRepository().getSnapshot().agents.filter(n=>n.enabled).map(n=>n.name));return(t.allowedAgents.length>0?t.allowedAgents:[...e]).filter(n=>e.has(n))}async persistBindings(t){let e=`${t}:`,s={};for(let[o,c]of this.threadBindings)o.startsWith(e)&&(s[o.slice(e.length)]=c);let n=this.deps.getSettings(),a=(0,ut.normalizePath)(`${n.fleetFolder}/channels/${t}/bindings.json`),r=JSON.stringify(s,null,2);try{let o=this.deps.vault.getAbstractFileByPath(a);if(o instanceof ut.TFile)await this.deps.vault.modify(o,r);else{let c=a.slice(0,a.lastIndexOf("/"));if(!this.deps.vault.getAbstractFileByPath(c))try{await this.deps.vault.createFolder(c)}catch{}await this.deps.vault.create(a,r)}}catch(o){console.warn(`Agent Fleet: failed to persist thread bindings for ${t}`,o)}}async loadBindings(t){let e=this.deps.getSettings(),s=(0,ut.normalizePath)(`${e.fleetFolder}/channels/${t}/bindings.json`);try{let n=this.deps.vault.getAbstractFileByPath(s);if(!(n instanceof ut.TFile))return;let a=await this.deps.vault.cachedRead(n),r=JSON.parse(a);for(let[o,c]of Object.entries(r))typeof c=="string"&&this.threadBindings.set(`${t}:${o}`,c)}catch{}}getThreadAgent(t,e){return this.threadBindings.get(`${t}:${e}`)}enforceHardCap(){let t=Math.max(1,this.deps.getSettings().maxConcurrentChannelSessions),e=Array.from(this.sessions.values()).filter(n=>n.session.isProcessAlive&&!n.session.isStreaming);if(e.length<=t)return;e.sort((n,a)=>n.session.lastActiveAt-a.session.lastActiveAt);let s=e.length-t;for(let n=0;n{n=o}),r=(this.conversationLockGen.get(t)??0)+1;this.conversationLockGen.set(t,r),this.conversationLocks.set(t,s.then(()=>a));try{await s,await e()}finally{n(),this.conversationLockGen.get(t)===r&&(this.conversationLocks.delete(t),this.conversationLockGen.delete(t))}}ensureMetrics(t){let e=this.metrics.get(t);return e||(e={messagesReceived:0,messagesSent:0,lastMessageAt:null},this.metrics.set(t,e)),e}getMetrics(t){return{...this.ensureMetrics(t)}}getChannelStatus(t){let e=this.adapters.get(t);return e?e.getStatus():"disabled"}getConnectedCount(){let t=0;for(let e of this.adapters.values())e.getStatus()==="connected"&&(t+=1);return t}getSessionCount(t){let e=0,s=`${t}:`;for(let n of this.sessions.keys())n.startsWith(s)&&(e+=1);return e}onStatusChange(t){return this.statusListeners.add(t),()=>this.statusListeners.delete(t)}notifyStatusListeners(){for(let t of this.statusListeners)try{t()}catch{}}};function vc(i){if(i.length===0)return"";let t=new Map;for(let s of i)t.set(s.name,(t.get(s.name)??0)+1);let e=[];for(let[s,n]of t)e.push(n>1?`${s}\xD7${n}`:s);return`Used ${i.length} tool${i.length===1?"":"s"}: ${e.join(", ")}`}var bn=class{credentials=new Map;secretStore;persistCallback;setSecretStore(t){this.secretStore=t}loadCredentials(t){if(this.credentials.clear(),this.secretStore?.available){let e=this.secretStore.listByPrefix(Gt);for(let s of e){let n=Gt+"-",a=s.startsWith(n)?s.slice(n.length):s,r=this.secretStore.getJson(Gt,a);if(r){let o=r._ref??a,c={...r};delete c._ref,this.credentials.set(o,c)}}}if(this.credentials.size===0&&t){for(let[e,s]of Object.entries(t))this.credentials.set(e,s);this.secretStore?.available&&this.credentials.size>0&&this.persistToSecretStore()}}onChanged(t){this.persistCallback=t}get(t){return this.credentials.get(t)}set(t,e){this.credentials.set(t,e),this.persist()}delete(t){this.credentials.delete(t),this.secretStore?.delete(Gt,t),this.persist()}list(){return Array.from(this.credentials.entries()).map(([t,e])=>({ref:t,entry:e}))}toRecord(){let t={};for(let[e,s]of this.credentials.entries())t[e]=s;return t}persist(){this.persistToSecretStore(),this.persistCallback&&this.persistCallback(this.toRecord())}persistToSecretStore(){if(this.secretStore?.available)for(let[t,e]of this.credentials)this.secretStore.setJson(Gt,t,{...e,_ref:t})}};function xr(){return{status:"disconnected",scope:"user",tools:[],toolDetails:[]}}function wc(i,t,e){if(e)return"stdio";let s=(i??"").toLowerCase();return s==="sse"?"sse":s==="http"||s==="streamable-http"||s==="streamable_http"?"http":t?.endsWith("/sse")?"sse":"http"}function Sr(i){let t=[],e={},s;try{s=JSON.parse(i)}catch{return{servers:t,tokens:e}}let n=s.mcpServers;if(!n||typeof n!="object")return{servers:t,tokens:e};for(let[a,r]of Object.entries(n)){if(!r||typeof r!="object")continue;let o=r,c=typeof o.command=="string"?o.command:void 0,l=typeof o.url=="string"?o.url:void 0;if(!c&&!l)continue;let h=wc(typeof o.type=="string"?o.type:void 0,l,!!c),d={name:a,type:h,enabled:!0,source:"imported",...xr()};if(h==="stdio")d.command=c,Array.isArray(o.args)&&(d.args=o.args.filter(u=>typeof u=="string")),o.env&&typeof o.env=="object"&&(d.env=kr(o.env));else{d.url=l;let u=o.headers&&typeof o.headers=="object"?kr(o.headers):{},p=u.Authorization??u.authorization;p?.startsWith("Bearer ")?(e[a]=p.slice(7),delete u.Authorization,delete u.authorization,d.auth="oauth"):d.auth="none",Object.keys(u).length>0&&(d.headers=u)}t.push(d)}return{servers:t,tokens:e}}function Cr(i){let t=new Map,e=null;for(let n of i.split(/\r?\n/)){let a=n.trim();if(!a||a.startsWith("#"))continue;let r=a.match(/^\[(.+)\]$/);if(r){let u=r[1].trim().match(/^mcp_servers\.(.+)$/);if(!u){e=null;continue}let p=u[1],m=null;p.endsWith(".env")?(m="env",p=p.slice(0,-4)):p.endsWith(".oauth")&&(m="oauth",p=p.slice(0,-6));let f=kn(p);if(!f){e=null;continue}t.has(f)||t.set(f,{name:f,enabled:!0}),e={name:f,sub:m};continue}if(!e)continue;let o=a.match(/^([^=]+?)\s*=\s*(.+)$/);if(!o)continue;let c=kn(o[1].trim()),l=o[2].trim(),h=t.get(e.name);if(e.sub==="env")h.env??={},h.env[c]=Yt(l);else if(e.sub==="oauth")c==="client_id"&&(h.oauthClientId=Yt(l));else switch(c){case"command":h.command=Yt(l);break;case"args":h.args=bc(l);break;case"url":h.url=Yt(l);break;case"bearer_token_env_var":h.bearerEnvVar=Yt(l);break;case"oauth_resource":h.oauthResource=Yt(l);break;case"enabled":h.enabled=l==="true";break;default:break}}let s=[];for(let n of t.values()){if(!n.command&&!n.url)continue;let a=n.command?"stdio":n.url?.endsWith("/sse")?"sse":"http",r={name:n.name,type:a,enabled:n.enabled,source:"imported",...xr()};a==="stdio"?(r.command=n.command,n.args&&n.args.length>0&&(r.args=n.args),n.env&&Object.keys(n.env).length>0&&(r.env=n.env)):(r.url=n.url,n.oauthClientId||n.oauthResource?(r.auth="oauth",r.oauth={clientId:n.oauthClientId,resource:n.oauthResource}):n.bearerEnvVar?(r.auth="bearer",r.envSecretKeys=[n.bearerEnvVar]):r.auth="none"),s.push(r)}return{servers:s,tokens:{}}}function Tr(...i){let t=new Map,e={};for(let s of i){for(let n of s.servers){let a=n.name.trim().toLowerCase();t.has(a)||t.set(a,n)}Object.assign(e,s.tokens)}return{servers:[...t.values()],tokens:e}}function kn(i){let t=i.trim();return t.startsWith('"')&&t.endsWith('"')||t.startsWith("'")&&t.endsWith("'")?t.slice(1,-1):t}function Yt(i){let t=i.trim();if(t.startsWith('"')||t.startsWith("'")){let e=t[0],s=t.indexOf(e,1);if(s>0)return t.slice(1,s)}return kn(t.replace(/\s+#.*$/,""))}function bc(i){let t=i.trim();try{let s=JSON.parse(t);if(Array.isArray(s))return s.filter(n=>typeof n=="string")}catch{}return t.replace(/^\[/,"").replace(/\]$/,"").split(",").map(s=>kn(s.trim())).filter(Boolean)}function kr(i){let t={};for(let[e,s]of Object.entries(i))typeof s=="string"&&(t[e]=s);return t}var jd=Ye(ko(),1),Wd=Ye(Rn(),1),Hd=Ye(Xt(),1),qd=Ye(La(),1),zd=Ye(Na(),1),Gd=Ye(Ha(),1),Ro=Ye(Ln(),1),Vd=Ye(Po(),1);var ns=Ro.default;var Do=require("obsidian");var Yd="https://slack.com/api";function Kd(i){let t=i.team??"unknown",e=i.channel??"unknown",s=i.thread_ts??i.ts??"unknown";return`slack:${t}:${e}:thread:${s}`}function Jd(i){let t=i.split(":");return t.length>=3&&t[0]==="slack"?t[2]??null:null}function Xd(i){let t=i.split(":");if(t[3]==="thread"&&t[4])return t[4]}var On=class{type="slack";config;credential;ws=null;status="stopped";stopping=!1;backoffMs=1e3;reconnectTimer=null;inboundHandlers=new Set;statusHandlers=new Set;agentSwitchHandlers=new Set;allowedAgentsResolver;sendQueues=new Map;threadContext=new Map;constructor(t,e){if(e.type!=="slack")throw new Error(`SlackAdapter requires a slack credential, got ${e.type}`);this.config=t,this.credential=e}async start(){this.stopping=!1,await this.connect()}async stop(){if(this.stopping=!0,this.reconnectTimer&&(window.clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.ws){try{this.ws.close()}catch{}this.ws=null}this.threadContext.clear(),this.sendQueues.clear(),this.setStatus("stopped")}getStatus(){return this.status}async send(t,e){let s=Jd(t);if(!s){console.warn(`Agent Fleet: could not extract channel id from ${t}`);return}let n=Xd(t),a=Ts(e);await this.enqueueSend(s,async()=>{await this.slackApi("chat.postMessage",{channel:s,text:a,...n?{thread_ts:n}:{}})})}async sendToTarget(t,e){if(!t)return;let s=Ts(e);await this.enqueueSend(t,async()=>{await this.slackApi("chat.postMessage",{channel:t,text:s})})}async broadcast(t){let e=this.config.allowedUsers[0];if(!e){console.warn(`Agent Fleet: broadcast on ${this.config.name} skipped \u2014 no allowed users configured`);return}try{let n=(await this.slackApi("conversations.open",{users:e})).channel?.id;if(!n){console.warn(`Agent Fleet: broadcast \u2014 conversations.open returned no channel for user ${e}`);return}let a=Ts(t);await this.slackApi("chat.postMessage",{channel:n,text:a})}catch(s){console.error(`Agent Fleet: broadcast failed on ${this.config.name}`,s)}}async setThreadTitle(t,e){let s=this.threadContext.get(t);if(s)try{await this.slackApi("assistant.threads.setTitle",{channel_id:s.channelId,thread_ts:s.threadTs,title:e})}catch(n){console.warn(`Agent Fleet: assistant.threads.setTitle failed on ${this.config.name}`,n)}}async setTyping(t,e){let s=this.threadContext.get(t);if(s)try{await this.slackApi("assistant.threads.setStatus",{channel_id:s.channelId,thread_ts:s.threadTs,status:e?"is thinking...":""})}catch(n){console.warn(`Agent Fleet: assistant.threads.setStatus (${e?"on":"off"}) failed on ${this.config.name}`,n)}}onInbound(t){return this.inboundHandlers.add(t),()=>this.inboundHandlers.delete(t)}onStatusChange(t){return this.statusHandlers.add(t),()=>this.statusHandlers.delete(t)}setAllowedAgentsResolver(t){this.allowedAgentsResolver=t}onAgentSwitch(t){return this.agentSwitchHandlers.add(t),()=>this.agentSwitchHandlers.delete(t)}async connect(){if(this.stopping)return;this.setStatus(this.ws?"reconnecting":"connecting");let t;try{let e=await this.slackApi("apps.connections.open",{},{useAppToken:!0});if(!e.ok||!e.url)throw new Error(e.error??"apps.connections.open returned no URL");t=e.url}catch(e){console.error(`Agent Fleet: Slack apps.connections.open failed for channel ${this.config.name}`,e),this.setStatus("needs-auth"),this.scheduleReconnect();return}try{let e=new ns(t);e.on("open",()=>{}),e.on("message",a=>{this.handleSocketData(a)}),e.on("error",a=>{console.warn(`Agent Fleet: Slack WebSocket error on ${this.config.name}`,a),this.setStatus("error")}),e.on("close",()=>{this.ws=null,this.stopping||this.scheduleReconnect()}),this.ws=e;let s=window.setTimeout(()=>{if(this.status==="connecting"||this.status==="reconnecting"){console.warn(`Agent Fleet: Slack WebSocket connect timeout on ${this.config.name}`);try{e.close()}catch{}}},3e4);e.on("close",()=>window.clearTimeout(s));let n=this.onStatusChange(a=>{a==="connected"&&(window.clearTimeout(s),n())})}catch(e){console.error("Agent Fleet: Slack WebSocket open failed",e),this.setStatus("error"),this.scheduleReconnect();return}}handleSocketData(t){let e;try{e=JSON.parse(t.toString())}catch{return}if(e.type==="hello"){this.backoffMs=1e3,this.setStatus("connected");return}if(e.type==="disconnect"){try{this.ws?.close()}catch{}return}if(e.type==="events_api"&&e.envelope_id){this.ackEnvelope(e.envelope_id),this.routeEventPayload(e.payload);return}if(e.type==="slash_commands"&&e.envelope_id){this.ackEnvelope(e.envelope_id),this.handleSlashCommand(e.payload);return}if(e.type==="interactive"&&e.envelope_id){this.ackEnvelope(e.envelope_id),this.handleInteraction(e.payload);return}e.envelope_id&&this.ackEnvelope(e.envelope_id)}async handleSlashCommand(t){if(!t)return;let e=t.command,s=t.channel_id,n=t.user_id;if(!(!e||!s||!n)){if(e==="/agents"){let a=this.allowedAgentsResolver?this.allowedAgentsResolver():this.config.allowedAgents;if(a.length===0){await this.slackApi("chat.postEphemeral",{channel:s,user:n,text:"No agents available. Add existing, enabled agents to `allowed_agents` in the channel file."});return}let r=a.map(c=>({type:"button",text:{type:"plain_text",text:c===this.config.defaultAgent?`${c} \u2713`:c,emoji:!0},action_id:`switch_agent_${c}`,value:c})),o=[{type:"section",text:{type:"mrkdwn",text:"*Select an agent to chat with:*"}}];for(let c=0;c\s*/,"").trim(),!n.text))return;let o=Kd(n),c=n.thread_ts??n.ts;if(n.channel&&c&&(this.threadContext.set(o,{channelId:n.channel,threadTs:c}),this.threadContext.size>500)){let d=this.threadContext.keys().next();d.done||this.threadContext.delete(d.value)}let l={conversationId:o,externalUserId:n.user,text:n.text,timestamp:new Date().toISOString(),meta:{slack_channel:n.channel,slack_ts:n.ts,thread_ts:n.thread_ts}};for(let h of this.inboundHandlers)try{h(l)}catch(d){console.error("Agent Fleet: Slack inbound handler threw",d)}}scheduleReconnect(){if(this.stopping||this.reconnectTimer)return;let t=this.backoffMs;this.backoffMs=Math.min(3e4,this.backoffMs*2),console.warn(`Agent Fleet: Slack channel ${this.config.name} scheduling reconnect in ${t}ms`),this.reconnectTimer=window.setTimeout(()=>{this.reconnectTimer=null,!this.stopping&&this.connect()},t)}setStatus(t){if(this.status!==t){this.status=t;for(let e of this.statusHandlers)try{e(t)}catch{}}}async slackApi(t,e,s={}){let n=s.useAppToken?this.credential.appToken:this.credential.botToken,a=`${Yd}/${t}`,r=await(0,Do.requestUrl)({url:a,method:"POST",contentType:"application/json; charset=utf-8",headers:{Authorization:`Bearer ${n}`},body:JSON.stringify(e),throw:!1});if(r.status===429){let c=Number(r.headers["retry-after"]??"1");return await new Promise(l=>window.setTimeout(l,Math.max(1e3,c*1e3))),this.slackApi(t,e,s)}if(r.status<200||r.status>=300)throw new Error(`Slack ${t} HTTP ${r.status}`);let o=r.json;if(o.ok===!1)throw new Error(`Slack ${t} error: ${o.error??"unknown"}`);return o}async enqueueSend(t,e){let n=(this.sendQueues.get(t)??Promise.resolve()).then(async()=>{try{await e()}finally{await new Promise(r=>window.setTimeout(r,1e3))}}),a=n.catch(r=>{console.warn(`Agent Fleet: Slack send queue error for ${t}`,r)});this.sendQueues.set(t,a),await n,this.sendQueues.get(t)===a&&this.sendQueues.delete(t)}};var Ga=require("obsidian"),Io="https://api.telegram.org",Nn=class{type="telegram";config;credential;status="stopped";stopping=!1;pollOffset=0;pollTimer=null;backoffMs=1e3;typingIntervals=new Map;pollAbort=null;inboundHandlers=new Set;statusHandlers=new Set;agentSwitchHandlers=new Set;allowedAgentsResolver;constructor(t,e){if(e.type!=="telegram")throw new Error(`TelegramAdapter requires a telegram credential, got ${e.type}`);this.config=t,this.credential=e}async start(){this.stopping=!1;try{let e=(await this.tgApi("getUpdates",{offset:-1,limit:1})).result?.[0];e&&(this.pollOffset=e.update_id+1)}catch{}try{await this.tgApi("setMyCommands",{commands:[{command:"agents",description:"List available agents"}]})}catch{}this.setStatus("connected"),this.poll()}async stop(){this.stopping=!0,this.pollTimer&&(window.clearTimeout(this.pollTimer),this.pollTimer=null),this.pollAbort?.abort(),this.pollAbort=null;for(let[,t]of this.typingIntervals)window.clearInterval(t);this.typingIntervals.clear(),this.setStatus("stopped")}getStatus(){return this.status}async send(t,e){let s=Mo(t);if(!s)return;let n=Lo(t),a=za(e,4096);for(let r of a)await this.tgApi("sendMessage",{chat_id:s,text:r,parse_mode:"Markdown",...n?{message_thread_id:Number(n)}:{}})}async sendToTarget(t,e){if(!t)return;let s=za(e,4096);for(let n of s)await this.tgApi("sendMessage",{chat_id:t,text:n,parse_mode:"Markdown"})}async setTyping(t,e){let s=Mo(t);if(!s)return;let n=Lo(t),a={chat_id:s,action:"typing"};if(n&&(a.message_thread_id=Number(n)),e){let r=this.typingIntervals.get(t);r&&window.clearInterval(r);try{await this.tgApi("sendChatAction",a)}catch(c){console.warn("Agent Fleet: Telegram sendChatAction failed",c)}let o=window.setInterval(()=>{this.tgApi("sendChatAction",a).catch(()=>{})},4500);this.typingIntervals.set(t,o)}else{let r=this.typingIntervals.get(t);r&&(window.clearInterval(r),this.typingIntervals.delete(t))}}async setThreadTitle(t,e){}async broadcast(t){let e=this.config.allowedUsers[0];if(e)try{let s=za(t,4096);for(let n of s)await this.tgApi("sendMessage",{chat_id:e,text:n,parse_mode:"Markdown"})}catch(s){console.error(`Agent Fleet: Telegram broadcast failed on ${this.config.name}`,s)}}onInbound(t){return this.inboundHandlers.add(t),()=>this.inboundHandlers.delete(t)}onStatusChange(t){return this.statusHandlers.add(t),()=>this.statusHandlers.delete(t)}setAllowedAgentsResolver(t){this.allowedAgentsResolver=t}pickerAgents(){return this.allowedAgentsResolver?this.allowedAgentsResolver():this.config.allowedAgents}onAgentSwitch(t){return this.agentSwitchHandlers.add(t),()=>this.agentSwitchHandlers.delete(t)}poll(){this.stopping||(async()=>{try{this.pollAbort=new AbortController;let t=await this.tgApi("getUpdates",{offset:this.pollOffset,timeout:30,allowed_updates:["message","callback_query"]},this.pollAbort.signal);if(t.ok&&t.result)for(let e of t.result){if(this.pollOffset=e.update_id+1,e.callback_query){this.handleCallbackQuery(e.callback_query);continue}e.message&&this.routeMessage(e.message)}this.backoffMs=1e3,this.status!=="connected"&&this.setStatus("connected")}catch(t){if(t instanceof DOMException&&t.name==="AbortError")return;if(console.warn(`Agent Fleet: Telegram poll failed on ${this.config.name}`,t),this.status!=="error"&&this.status!=="needs-auth"){let e=t instanceof Error?t.message:String(t);this.setStatus(e.includes("401")||e.includes("Unauthorized")?"needs-auth":"error")}await new Promise(e=>window.setTimeout(e,this.backoffMs)),this.backoffMs=Math.min(3e4,this.backoffMs*2)}this.stopping||(this.pollTimer=window.setTimeout(()=>this.poll(),100))})()}routeMessage(t){let e=t.photo&&t.photo.length>0,s=t.document&&this.isImageMime(t.document.mime_type),n=!!t.text;if(!t.from||!n&&!e&&!s)return;let a=t.text??t.caption??"";if(a==="/agents"||a.startsWith("/agents@")){this.handleAgentsCommand(t);return}if(a.startsWith("/"))return;let r=String(t.from.id),o=String(t.chat.id),c=t.message_thread_id?String(t.message_thread_id):void 0,l=c?`tg:${o}:topic:${c}`:`tg:${o}`;this.buildAndEmitMessage(t,a,l,r,o,c)}async buildAndEmitMessage(t,e,s,n,a,r){let o=[];try{if(t.photo&&t.photo.length>0){let l=t.photo[t.photo.length-1],h=await this.downloadFile(l.file_id,`photo_${t.message_id}.jpg`,"image/jpeg");h&&o.push(h)}if(t.document&&this.isImageMime(t.document.mime_type)){let l=t.document.file_name??`doc_${t.message_id}`,h=t.document.mime_type??"image/jpeg",d=await this.downloadFile(t.document.file_id,l,h);d&&o.push(d)}}catch(l){console.warn("Agent Fleet: Telegram image download failed",l)}let c={conversationId:s,externalUserId:n,text:e,timestamp:new Date(t.date*1e3).toISOString(),meta:{telegram_chat_id:a,telegram_message_id:t.message_id,telegram_thread_id:r,chat_type:t.chat.type},...o.length>0?{images:o}:{}};for(let l of this.inboundHandlers)try{l(c)}catch(h){console.error("Agent Fleet: Telegram inbound handler threw",h)}}async downloadFile(t,e,s){let a=(await this.tgApi("getFile",{file_id:t})).result?.file_path;if(!a)return null;let r=`${Io}/file/bot${this.credential.botToken}/${a}`;return{data:(await(0,Ga.requestUrl)({url:r,method:"GET"})).arrayBuffer,filename:e,mimeType:s}}isImageMime(t){return t?t==="image/jpeg"||t==="image/png"||t==="image/gif"||t==="image/webp"||t==="image/bmp"||t==="image/tiff":!1}async handleAgentsCommand(t){let e=String(t.chat.id),s=t.message_thread_id,n=this.pickerAgents();if(n.length===0){await this.tgApi("sendMessage",{chat_id:e,text:"No agents available. Add existing, enabled agents to `allowed_agents` in the channel file.",...s?{message_thread_id:s}:{}});return}let a=n.map(r=>[{text:r===this.config.defaultAgent?`${r} \u2713`:r,callback_data:`switch:${r}`}]);await this.tgApi("sendMessage",{chat_id:e,text:"*Select an agent to chat with:*",parse_mode:"Markdown",reply_markup:{inline_keyboard:a},...s?{message_thread_id:s}:{}})}async handleCallbackQuery(t){let e=t.data;if(!e?.startsWith("switch:")){await this.tgApi("answerCallbackQuery",{callback_query_id:t.id});return}let s=e.slice(7),n=String(t.from.id),a=String(t.message?.chat?.id??t.from.id),r=t.message?.message_thread_id?String(t.message.message_thread_id):void 0,o=r?`tg:${a}:topic:${r}`:`tg:${a}`;for(let c of this.agentSwitchHandlers)try{c(o,s,n)}catch(l){console.error("Agent Fleet: Telegram agent switch handler threw",l)}if(await this.tgApi("answerCallbackQuery",{callback_query_id:t.id,text:`Switched to ${s}`}),t.message){let l=this.pickerAgents().map(h=>[{text:h===s?`${h} \u2713`:h,callback_data:`switch:${h}`}]);try{await this.tgApi("editMessageText",{chat_id:a,message_id:t.message.message_id,text:`*Active agent: ${s}*`,parse_mode:"Markdown",reply_markup:{inline_keyboard:l}})}catch{}}}async tgApi(t,e,s){if(s?.aborted)throw new DOMException("Aborted","AbortError");let n=`${Io}/bot${this.credential.botToken}/${t}`,a=(0,Ga.requestUrl)({url:n,method:"POST",contentType:"application/json",body:JSON.stringify(e),throw:!1}),r;if(s?r=await Promise.race([a,new Promise((o,c)=>{s.addEventListener("abort",()=>c(new DOMException("Aborted","AbortError")),{once:!0})})]):r=await a,r.status===401||r.status===403)throw new Error(`Telegram ${t} ${r.status} Unauthorized`);if(r.status===429){let c=r.json?.parameters?.retry_after??1;return await new Promise(l=>window.setTimeout(l,c*1e3)),this.tgApi(t,e)}if(r.status<200||r.status>=300)throw new Error(`Telegram ${t} HTTP ${r.status}: ${r.text}`);return r.json}setStatus(t){if(this.status!==t){this.status=t;for(let e of this.statusHandlers)try{e(t)}catch{}}}};function Mo(i){return i.startsWith("tg:")?i.split(":")[1]??null:null}function Lo(i){let t=i.split(":");if(t[2]==="topic"&&t[3])return t[3]}function za(i,t){if(i.length<=t)return[i];let e=[],s=i;for(;s.length>t;){let n=s.lastIndexOf(` `,t);ns.agents.length},{icon:"columns-3",label:"Tasks Board",page:"kanban"},{icon:"scroll-text",label:"Run History",page:"runs"},{icon:"shield-check",label:"Approvals",page:"approvals",badge:()=>n.pending},{icon:"puzzle",label:"Skills",page:"skills",badge:()=>s.skills.length},{icon:"plug",label:"MCP Servers",page:"mcp",badge:()=>this.plugin.repository.getMcpServers().length},{icon:"radio",label:"Channels",page:"channels",badge:()=>this.plugin.channelManager?.getConnectedCount()??s.channels.length}];for(let l of r){let h=a.createDiv({cls:"af-sidebar-nav-item"}),d=h.createSpan({cls:"af-sidebar-nav-icon"});(0,Rs.setIcon)(d,l.icon),h.createSpan({cls:"af-sidebar-nav-label",text:l.label});let u=l.badge?.();u!==void 0&&u>0&&h.createSpan({cls:"af-sidebar-badge",text:String(u)}),h.setAttribute("role","button"),h.setAttribute("tabindex","0"),h.onclick=()=>void this.plugin.navigateDashboard(l.page),h.onkeydown=p=>{(p.key==="Enter"||p.key===" ")&&(p.preventDefault(),this.plugin.navigateDashboard(l.page))}}let o=e.createDiv({cls:"af-sidebar-section"});o.createDiv({cls:"af-sidebar-section-header",text:"AGENTS"}),s.agents.length===0&&o.createDiv({cls:"af-sidebar-empty",text:"No agents configured"});for(let l of s.agents){let h=this.plugin.runtime.getAgentState(l.name),d=o.createDiv({cls:"af-sidebar-agent-item"});d.createSpan({cls:`af-sidebar-agent-dot ${this.healthToClass(h.status)}`}),d.createSpan({cls:"af-sidebar-agent-name",text:l.name}),d.setAttribute("role","button"),d.setAttribute("tabindex","0"),d.onclick=()=>void this.plugin.navigateDashboard("agent-detail",l.name),d.onkeydown=u=>{(u.key==="Enter"||u.key===" ")&&(u.preventDefault(),this.plugin.navigateDashboard("agent-detail",l.name))}}let c=e.createDiv({cls:"af-sidebar-section"});c.createDiv({cls:"af-sidebar-section-header",text:"QUICK ACTIONS"}),this.renderQuickAction(c,"plus","New Agent",()=>void this.plugin.createAgentTemplate()),this.renderQuickAction(c,"plus","New Task",()=>void this.plugin.openCreateTask()),this.renderQuickAction(c,"plus","New Skill",()=>void this.plugin.createSkillTemplate())}renderQuickAction(e,s,n,a){let r=e.createDiv({cls:"af-sidebar-action-item"}),o=r.createSpan({cls:"af-sidebar-action-icon"});(0,Rs.setIcon)(o,s),r.createSpan({text:n}),r.setAttribute("role","button"),r.setAttribute("tabindex","0"),r.onclick=a,r.onkeydown=c=>{(c.key==="Enter"||c.key===" ")&&(c.preventDefault(),a())}}healthToClass(e){switch(e){case"running":return"running";case"error":return"error";case"pending":return"pending";case"disabled":return"disabled";default:return"idle"}}};var w=require("obsidian");var ss=require("obsidian"),Nd=["bot","brain","shield-check","search","file-text","rocket","wand","sparkles","zap","target","compass","eye","code","terminal","database","globe","mail","message-circle","book","pen-tool","palette","music","camera","chart-bar","clipboard","cpu","server","cloud","lock","key","bell","calendar","clock","heart","star","flag","bookmark"],Bn=class extends ss.Modal{constructor(e,s,n){super(e);this.onSelect=n;this.selectedIcon=s}searchQuery="";selectedIcon;allIcons=[];gridContainer;onOpen(){this.allIcons=(0,ss.getIconIds)().sort();let{contentEl:e}=this;e.empty(),e.addClass("af-icon-picker-modal");let s=e.createEl("input",{cls:"af-icon-picker-search",attr:{type:"text",placeholder:"Search icons...",value:this.searchQuery}});this.gridContainer=e.createDiv({cls:"af-icon-picker-scroll"}),this.renderGrid(),s.addEventListener("input",()=>{this.searchQuery=s.value,this.renderGrid()}),window.setTimeout(()=>s.focus(),0)}renderGrid(){let e=this.gridContainer;e.empty();let s=this.searchQuery.toLowerCase().trim();if(!s)this.renderSection(e,"Popular",Nd),this.renderSection(e,"All Icons",this.allIcons);else{let n=this.allIcons.filter(r=>r.includes(s)),a=n.length===0?"No results":`${n.length} result${n.length!==1?"s":""}`;this.renderSection(e,a,n)}}renderSection(e,s,n){let a=e.createDiv({cls:"af-icon-picker-section"});a.createDiv({cls:"af-icon-picker-section-label",text:s});let r=a.createDiv({cls:"af-icon-picker-grid"});for(let o of n)this.renderIconItem(r,o)}renderIconItem(e,s){let n=e.createDiv({cls:`af-icon-picker-item${this.selectedIcon===s?" selected":""}`});n.setAttribute("title",s),n.setAttribute("aria-label",s),(0,ss.setIcon)(n,s),n.addEventListener("click",()=>{this.selectedIcon=s,this.onSelect(s),this.close()})}onClose(){this.contentEl.empty()}};var Eo=require("obsidian");function E(i,t,e){let s=i.createSpan({cls:e??"af-icon"});return(0,Eo.setIcon)(s,t),s}function Je(i,t={}){let e=activeDocument.createElementNS("http://www.w3.org/2000/svg",i);for(let[s,n]of Object.entries(t))e.setAttribute(s,n);return e}function Bd(i){try{return new Date(i+"T12:00:00").toLocaleDateString(void 0,{month:"short",day:"numeric"})}catch{return i.slice(5)}}function Po(i,t){let e=t.length||1,s=1e3,n=6,a=4,r=4,o=s-a-r-n*(e-1),c=Math.floor(o/e),l=140,h=36,d=18,u=d+l+h,p=Math.max(1,...t.map(f=>f.success+f.failure+f.cancelled)),m=Je("svg",{viewBox:`0 0 ${s} ${u}`,width:"100%",height:String(u),class:"af-chart-bar"});for(let f=0;f<=4;f++){let g=d+l-f/4*l;m.appendChild(Je("line",{x1:String(a),y1:String(g),x2:String(s-r),y2:String(g),stroke:"var(--af-text-faint)","stroke-opacity":"0.15","stroke-width":"1"}))}for(let f=0;f0&&m.appendChild(Je("rect",{x:String(v),y:String(d+l-y),width:String(c),height:String(Math.max(y,2)),fill:"var(--af-green)",opacity:"0.85"})),g.cancelled>0&&m.appendChild(Je("rect",{x:String(v),y:String(d+l-y-S),width:String(c),height:String(Math.max(S,2)),fill:"var(--af-yellow)",opacity:"0.85"})),g.failure>0&&m.appendChild(Je("rect",{x:String(v),y:String(d+l-k),width:String(c),height:String(Math.max(T,2)),fill:"var(--af-red)",opacity:"0.85"})),b===0&&m.appendChild(Je("rect",{x:String(v),y:String(d+l-3),width:String(c),height:"3",rx:"1.5",fill:"var(--af-text-faint)",opacity:"0.2"})),b>0){let D=Je("text",{x:String(v+c/2),y:String(d+l-k-5),"text-anchor":"middle","font-size":"10","font-weight":"600",fill:"var(--af-text-secondary)"});D.textContent=String(b),m.appendChild(D)}let _=Je("text",{x:String(v+c/2),y:String(d+l+16),"text-anchor":"middle","font-size":"10",fill:"var(--af-text-muted)"});_.textContent=Bd(g.date),m.appendChild(_)}i.appendChild(m)}function Ro(i,t,e){let c=2*Math.PI*46,l=e>0?t/e:0,h=c*l,d=c-h,u=Je("svg",{viewBox:"0 0 130 130",width:String(130),height:String(130),class:"af-chart-donut"});u.appendChild(Je("circle",{cx:String(65),cy:String(65),r:String(46),fill:"none",stroke:e>0?"var(--af-red)":"var(--af-text-faint)","stroke-width":String(12),opacity:"0.2"})),l>0&&u.appendChild(Je("circle",{cx:String(65),cy:String(65),r:String(46),fill:"none",stroke:"var(--af-green)","stroke-width":String(12),"stroke-dasharray":`${h} ${d}`,"stroke-dashoffset":String(c*.25),"stroke-linecap":"round"}));let p=Je("text",{x:String(65),y:String(61),"text-anchor":"middle","dominant-baseline":"middle","font-size":"24","font-weight":"700",fill:"var(--af-text-primary)"});p.textContent=`${Math.round(l*100)}%`,u.appendChild(p);let m=Je("text",{x:String(65),y:String(83),"text-anchor":"middle","font-size":"10",fill:"var(--af-text-muted)"});m.textContent=`${t}/${e} runs`,u.appendChild(m),i.appendChild(u)}function Do(i,t){i.draggable=!0,i.addEventListener("dragstart",e=>{e.dataTransfer?.setData("text/plain",t),i.addClass("af-dragging")}),i.addEventListener("dragend",()=>{i.removeClass("af-dragging")})}function Io(i,t){i.addEventListener("dragover",e=>{e.preventDefault(),i.addClass("af-drag-over")}),i.addEventListener("dragleave",e=>{let s=e.relatedTarget;(!s||!i.contains(s))&&i.removeClass("af-drag-over")}),i.addEventListener("drop",e=>{e.preventDefault();let s=e.dataTransfer?.getData("text/plain");i.removeClass("af-drag-over"),s&&t(s)})}function Mo(i){let t=/^##\s+Lint\s+(\d{4}-\d{2}-\d{2})\s*$/gm,e,s=-1,n="";for(;(e=t.exec(i))!==null;)e.index>s&&(s=e.index,n=e[1]??"");if(s<0)return null;let a=i.slice(s),r=a.search(/\n##\s+(?!\s*#)/),o=r<0?a:a.slice(0,r);return{date:n,summary:Un(o,"Summary"),autoApplied:Un(o,"Auto-applied"),needsReview:Un(o,"Needs review"),refreshChained:Un(o,"Refresh chained")}}function Un(i,t){let e=t.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),n=new RegExp(`^###\\s+${e}\\s*$`,"m").exec(i);if(!n)return[];let a=n.index+n[0].length,r=i.slice(a),o=r.search(/\n###\s+/),l=(o<0?r:r.slice(0,o)).split(/\r?\n/).map(d=>d.trimEnd()),h=[];for(let d of l){let u=d.replace(/^\s+/,"");u.startsWith("- ")?h.push(u.slice(2).trim()):h.length>0&&(d.startsWith(" ")||d.startsWith(" "))&&(h[h.length-1]+=" "+u)}return h}var Lo={dashboard:"Dashboard",agents:"Agents",kanban:"Tasks Board",runs:"Run History",skills:"Skills Library",approvals:"Approvals",mcp:"MCP Servers",channels:"Channels","wiki-keepers":"Wiki Keepers","agent-detail":"Agent Details","task-detail":"Task Details","create-agent":"Create Agent","create-task":"Create Task","create-skill":"Create Skill","edit-agent":"Edit Agent","edit-task":"Edit Task","edit-skill":"Edit Skill","create-channel":"Create Channel","edit-channel":"Edit Channel","add-mcp-server":"Add MCP Server"},Ud={dashboard:"layout-dashboard",agents:"bot",kanban:"columns-3",runs:"scroll-text",skills:"puzzle",approvals:"shield-check",mcp:"plug",channels:"radio","wiki-keepers":"library","agent-detail":"bot","task-detail":"circle-dot","create-agent":"plus","create-task":"plus","create-skill":"plus","edit-agent":"edit","edit-task":"edit","edit-skill":"edit","create-channel":"plus","edit-channel":"edit","add-mcp-server":"plus"},$d=["dashboard","agents","kanban","runs","approvals","skills","wiki-keepers","mcp","channels"],Fo=[["claude-code","Claude Code",!1],["codex","Codex",!1],["process","Process (coming soon)",!0],["http","HTTP (coming soon)",!0]],No=[["bypassPermissions","Bypass Permissions","Auto-approve everything except deny list"],["dontAsk","Don\u2019t Ask","Auto-approve all tool calls"],["acceptEdits","Accept Edits","Auto-approve file edits, block bash unless allowed"],["plan","Plan","Read-only mode, no writes or commands"],["default","Default","Ask for each tool call"]],Bo=[["bypassPermissions","Bypass (no sandbox)","No sandbox, auto-approve everything"],["workspace-write","Workspace Write","Sandboxed: writes only inside the working dir"],["read-only","Read Only","Sandboxed: no writes or side-effect commands"]];function Ds(i){let t=i.trim().toLowerCase();return t==="codex"||t==="openai-codex"}function $n(i){return Ds(i)?Bo:No}function jn(i,t){if(Ds(t))switch(i){case"acceptEdits":case"default":return"workspace-write";case"plan":return"read-only";case"dontAsk":return"bypassPermissions";default:return Bo.some(([e])=>e===i)?i:"bypassPermissions"}switch(i){case"workspace-write":return"acceptEdits";case"read-only":return"plan";case"danger-full-access":return"bypassPermissions";default:return No.some(([e])=>e===i)?i:"bypassPermissions"}}var Is=class extends w.ItemView{constructor(e,s){super(e);this.plugin=s}currentPage="dashboard";detailContext;agentDetailTab="overview";streamingUnsubscribes=[];channelStatusUnsubscribe;authenticatingServers=new Set;getViewType(){return Ct}getDisplayText(){return"Agent Fleet"}getIcon(){return"bot"}async onOpen(){this.plugin.subscribeView(this),this.channelStatusUnsubscribe=this.plugin.channelManager?.onStatusChange(()=>{this.currentPage==="channels"&&this.render()}),await this.render()}async onClose(){this.cleanupStreaming(),this.channelStatusUnsubscribe?.(),this.channelStatusUnsubscribe=void 0,this.plugin.unsubscribeView(this)}navigateTo(e,s){this.currentPage=e,this.detailContext=s,e!=="agent-detail"&&(this.agentDetailTab="overview"),this.render()}async render(){this.cleanupStreaming();let e=this.contentEl;e.empty(),e.addClass("af-root");let n=e.createDiv({cls:"af-app"}).createDiv({cls:"af-main-content"});this.renderTopBar(n),this.renderTabBar(n);let a=n.createDiv({cls:"af-page"});switch(this.currentPage){case"dashboard":this.renderDashboardPage(a);break;case"agents":this.renderAgentsPage(a);break;case"kanban":this.renderKanbanPage(a);break;case"runs":this.renderRunsPage(a);break;case"skills":this.renderSkillsPage(a);break;case"approvals":this.renderApprovalsPage(a);break;case"mcp":this.renderMcpPage(a);break;case"channels":this.renderChannelsPage(a);break;case"wiki-keepers":this.renderWikiKeepersPage(a);break;case"agent-detail":this.renderAgentDetailPage(a);break;case"task-detail":this.renderTaskDetailPage(a);break;case"create-agent":this.renderCreateAgentPage(a);break;case"create-task":this.renderCreateTaskPage(a);break;case"create-skill":this.renderCreateSkillPage(a);break;case"edit-agent":this.renderEditAgentPage(a);break;case"edit-task":this.renderEditTaskPage(a);break;case"edit-skill":this.renderEditSkillPage(a);break;case"create-channel":this.renderCreateChannelPage(a);break;case"edit-channel":this.renderEditChannelPage(a);break;case"add-mcp-server":this.renderAddMcpServerPage(a);break}}navigate(e,s){this.navigateTo(e,s)}cleanupStreaming(){for(let e of this.streamingUnsubscribes)e();this.streamingUnsubscribes=[]}renderTopBar(e){let s=e.createDiv({cls:"af-top-bar"}),n=s.createDiv({cls:"af-top-bar-title"});E(n,"bot","af-top-bar-icon"),n.createSpan({text:"Agent Fleet"});let a=s.createDiv({cls:"af-breadcrumb"}),r=a.createSpan({cls:"af-breadcrumb-sep"});(0,w.setIcon)(r,"chevron-right");let o=(m,f,g)=>{let v=a.createSpan({cls:f?"af-breadcrumb-link":"",text:m});f&&(v.onclick=()=>this.navigate(f,g))},c=()=>{let m=a.createSpan({cls:"af-breadcrumb-sep"});(0,w.setIcon)(m,"chevron-right")};switch(this.currentPage){case"agent-detail":o("Agents","agents"),c(),o(this.detailContext??"Agent");break;case"task-detail":o("Tasks Board","kanban"),c(),o(this.detailContext??"Task");break;case"edit-agent":o("Agents","agents"),c(),o(this.detailContext??"Agent","agent-detail",this.detailContext),c(),o("Edit");break;case"edit-task":o("Tasks Board","kanban"),c(),o(this.detailContext??"Task","task-detail",this.detailContext),c(),o("Edit");break;case"create-agent":o("Agents","agents"),c(),o("New Agent");break;case"create-task":o("Tasks Board","kanban"),c(),o("New Task");break;case"create-skill":o("Skills Library","skills"),c(),o("New Skill");break;case"edit-skill":o("Skills Library","skills"),c(),o(this.detailContext??"Skill"),c(),o("Edit");break;case"create-channel":o("Channels","channels"),c(),o("New Channel");break;case"edit-channel":o("Channels","channels"),c(),o(this.detailContext??"Channel"),c(),o("Edit");break;case"add-mcp-server":o("MCP Servers","mcp"),c(),o("Add Server");break;default:o(Lo[this.currentPage])}s.createDiv({cls:"af-top-bar-spacer"});let l=s.createDiv({cls:"af-search-wrap"});E(l,"search","af-search-icon");let h=l.createEl("input",{cls:"af-search-input",attr:{type:"text",placeholder:"Search agents, tasks, runs..."}});h.addEventListener("input",()=>{this.handleSearch(h.value,l)}),h.addEventListener("blur",()=>{window.setTimeout(()=>l.querySelector(".af-search-results")?.remove(),200)});let d=this.plugin.runtime.getFleetStatus(),u=s.createDiv({cls:"af-status-pills"});if(d.running>0){let m=u.createSpan({cls:"af-pill yellow"});m.createSpan({cls:"af-dot pulse"}),m.appendText(` ${d.running} Running`)}if(d.pending>0){let m=u.createSpan({cls:"af-pill blue"});m.createSpan({cls:"af-dot"}),m.appendText(` ${d.pending} Pending`)}let p=u.createSpan({cls:"af-pill green"});p.createSpan({cls:"af-dot"}),p.appendText(` ${d.completedToday} Today`)}renderTabBar(e){let s=e.createDiv({cls:"af-tab-bar"}),n=this.plugin.runtime.getSnapshot(),a=this.plugin.runtime.getFleetStatus();for(let r of $d){let o=this.currentPage===r||r==="agents"&&(this.currentPage==="agent-detail"||this.currentPage==="edit-agent"||this.currentPage==="create-agent")||r==="kanban"&&(this.currentPage==="task-detail"||this.currentPage==="edit-task")||r==="skills"&&(this.currentPage==="edit-skill"||this.currentPage==="create-skill")||r==="mcp"&&this.currentPage==="add-mcp-server",c=s.createEl("button",{cls:`af-tab-item${o?" active":""}`}),l=c.createSpan({cls:"af-tab-icon"});if((0,w.setIcon)(l,Ud[r]),c.appendText(r==="dashboard"?"Overview":Lo[r]),r==="agents")c.createSpan({cls:"af-badge",text:String(n.agents.length)});else if(r==="skills")c.createSpan({cls:"af-badge",text:String(n.skills.length)});else if(r==="mcp"){let h=this.plugin.repository.getMcpServers().length;c.createSpan({cls:"af-badge",text:String(h)})}else r==="approvals"&&a.pending>0&&c.createSpan({cls:"af-badge af-badge-warn",text:String(a.pending)});c.onclick=()=>this.navigate(r)}}renderDashboardPage(e){let s=e.createDiv({cls:"af-dashboard"}),n=this.plugin.runtime.getSnapshot(),a=this.plugin.runtime.getRecentRuns(),r=this.plugin.runtime.getFleetStatus(),o=a.filter(y=>(y.approvals??[]).some(S=>S.status==="pending"));for(let y of o)for(let S of y.approvals??[])S.status==="pending"&&this.renderApprovalBanner(s,y,S.tool);let c=s.createDiv({cls:"af-dash-grid"}),l=n.agents.filter(y=>y.enabled).length,h=n.agents.length;this.renderStatCard(c,"Active Agents",`${l}`,`/ ${h}`,"bot",`${l} of ${h} enabled`);let d=this.toLocalDateStr(new Date),u=a.filter(y=>this.runToLocalDate(y.started)===d),p=u.filter(y=>y.status==="success").length,m=u.filter(y=>y.status==="failure"||y.status==="timeout").length;this.renderStatCard(c,"Runs Today",String(u.length),"","activity",`${p} passed \xB7 ${m} failed \xB7 ${r.running} running`);let f=u.reduce((y,S)=>y+(S.tokensUsed??0),0),g=u.reduce((y,S)=>y+(S.costUsd??0),0),v=g>0?` \xB7 $${g.toFixed(2)}`:"";this.renderStatCard(c,"Tokens Used",Wa(f),"","zap",`today${v}`);let b=n.tasks.filter(y=>y.enabled&&y.schedule);this.renderStatCard(c,"Scheduled Tasks",String(b.length),"","clock",b.length>0?`Next: ${this.getNextTaskLabel(b)}`:"No scheduled tasks"),this.renderChartsRow(s,a,this.plugin.runtime.getChartRuns()),this.renderStreamingCards(s);let k=s.createDiv({cls:"af-dash-split"});this.renderActivityTimeline(k,a),this.renderFleetStatusPanel(k,n)}renderChartsRow(e,s,n){let a=e.createDiv({cls:"af-charts-row"}),r=a.createDiv({cls:"af-section-card af-chart-section"}),c=r.createDiv({cls:"af-section-header"}).createDiv({cls:"af-section-title"});E(c,"activity"),c.appendText(" Run Activity (14d)");let l=r.createDiv({cls:"af-chart-body"}),h=this.buildChartData(n,14);h.some(v=>v.success+v.failure+v.cancelled>0)?Po(l,h):this.renderEmptyState(l,"activity","No run data","Run agents to see activity");let d=a.createDiv({cls:"af-section-card af-chart-section"}),p=d.createDiv({cls:"af-section-header"}).createDiv({cls:"af-section-title"});E(p,"target"),p.appendText(" Success Rate");let m=d.createDiv({cls:"af-chart-body af-chart-body-center"}),f=s.length,g=s.filter(v=>v.status==="success").length;f>0?Ro(m,g,f):this.renderEmptyState(m,"target","No data","")}toLocalDateStr(e){let s=n=>String(n).padStart(2,"0");return`${e.getFullYear()}-${s(e.getMonth()+1)}-${s(e.getDate())}`}runToLocalDate(e){return this.toLocalDateStr(new Date(e))}buildChartData(e,s){let n=[],a=new Date;for(let r=s-1;r>=0;r--){let o=new Date(a);o.setDate(o.getDate()-r);let c=this.toLocalDateStr(o),l=e.filter(h=>this.runToLocalDate(h.started)===c);n.push({date:c,success:l.filter(h=>h.status==="success").length,failure:l.filter(h=>h.status==="failure"||h.status==="timeout").length,cancelled:l.filter(h=>h.status==="cancelled").length})}return n}renderStreamingCards(e){let n=this.plugin.runtime.getSnapshot().agents.filter(c=>this.plugin.runtime.getAgentState(c.name).status==="running");if(n.length===0)return;let a=e.createDiv({cls:"af-streaming-section"}),o=a.createDiv({cls:"af-section-header"}).createDiv({cls:"af-section-title"});E(o,"terminal"),o.appendText(" Active Agents");for(let c of n)this.renderStreamingCard(a,c.name)}renderStreamingCard(e,s){let n=e.createDiv({cls:"af-streaming-card"}),a=this.plugin.runtime.getAgentState(s),r=this.plugin.runtime.getSnapshot(),o=a.currentTaskId?r.tasks.find(m=>m.taskId===a.currentTaskId):void 0,c=o?` \u2192 ${o.taskId}`:a.status==="running"?" \u2192 Heartbeat":"",l=n.createDiv({cls:"af-streaming-card-header"});l.createSpan({cls:"af-dot pulse",attr:{style:"background: var(--af-yellow)"}}),l.createSpan({cls:"af-streaming-card-agent",text:` ${s}`}),c&&l.createSpan({cls:"af-streaming-card-task",text:c});let h=n.createDiv({cls:"af-streaming-output"}),d=this.plugin.runtime.getRunOutputBuffer(s),u=ge(d).slice(-4);h.setText(u.join(` -`));let p=this.plugin.runtime.onRunOutput(s,()=>{let m=this.plugin.runtime.getRunOutputBuffer(s),f=ge(m).slice(-4);h.setText(f.join(` -`)),h.scrollTop=h.scrollHeight});this.streamingUnsubscribes.push(p)}renderApprovalBanner(e,s,n){let a=e.createDiv({cls:"af-approval-banner"}),r=a.createDiv({cls:"af-approval-icon"});(0,w.setIcon)(r,"shield-check");let o=a.createDiv({cls:"af-approval-text"});o.createDiv({cls:"af-approval-title",text:`${s.agent} wants to run: ${n}`}),o.createDiv({cls:"af-approval-desc",text:`Approval required for tool: ${n}`});let c=a.createDiv({cls:"af-approval-actions"}),l=c.createEl("button",{cls:"af-btn-approve",text:"Approve"});l.onclick=()=>void this.plugin.runtime.resolveApproval(s,n,"approved").then(()=>this.render());let h=c.createEl("button",{cls:"af-btn-reject",text:"Reject"});h.onclick=()=>void this.plugin.runtime.resolveApproval(s,n,"rejected").then(()=>this.render())}renderStatCard(e,s,n,a,r,o){let c=e.createDiv({cls:"af-stat-card"}),l=c.createDiv({cls:"af-stat-label"});E(l,r,"af-stat-icon"),l.appendText(` ${s}`);let h=c.createDiv({cls:"af-stat-value"});h.appendText(n),a&&h.createSpan({cls:"af-stat-value-suffix",text:a}),c.createDiv({cls:"af-stat-sub",text:o})}renderActivityTimeline(e,s){let n=e.createDiv({cls:"af-section-card"}),r=n.createDiv({cls:"af-section-header"}).createDiv({cls:"af-section-title"});E(r,"inbox"),r.appendText(" Recent Activity");let o=n.createDiv({cls:"af-timeline"}),c=s.slice(0,8);if(c.length===0){this.renderEmptyState(o,"inbox","No runs yet","Run an agent to see activity here");return}for(let l of c)this.renderTimelineItem(o,l)}renderTimelineItem(e,s){let n=e.createDiv({cls:"af-timeline-item"}),a=this.statusToTimelineClass(s.status),r=n.createDiv({cls:`af-tl-icon ${a}`});(0,w.setIcon)(r,this.statusToIconName(s.status));let o=n.createDiv({cls:"af-tl-body"}),c=o.createDiv({cls:"af-tl-title"});c.createSpan({cls:"af-agent-tag",text:s.agent}),c.appendText(` ${s.task}`),o.createDiv({cls:"af-tl-desc",text:$t(s.output,100)});let l=o.createDiv({cls:"af-tl-meta"}),h=l.createSpan();if(E(h,"clock","af-meta-icon"),h.appendText(` ${this.formatStarted(s.started)} \xB7 ${this.formatDuration(s.durationSeconds)}`),s.tokensUsed){let d=l.createSpan();E(d,"zap","af-meta-icon"),d.appendText(` ${s.tokensUsed.toLocaleString()} tokens`)}n.onclick=()=>this.openSlideover(s)}renderFleetStatusPanel(e,s){let n=e.createDiv({cls:"af-section-card"}),a=n.createDiv({cls:"af-section-header"}),r=a.createDiv({cls:"af-section-title"});E(r,"bot"),r.appendText(" Fleet Status");let c=a.createDiv({cls:"af-section-actions"}).createEl("button",{cls:"af-btn-sm primary"});E(c,"plus","af-btn-icon"),c.appendText(" New Agent"),c.onclick=()=>void this.plugin.createAgentTemplate();let l=n.createDiv({cls:"af-agent-mini-list"});if(s.agents.length===0){this.renderEmptyState(l,"bot","No agents yet","Create your first agent to get started");return}for(let d of s.agents)this.renderAgentMini(l,d,s.tasks);let h=s.agents.filter(d=>d.enabled);if(h.length>0){let d=n.createDiv({cls:"af-quick-run"}),u=d.createDiv({cls:"af-quick-run-label"});E(u,"zap","af-meta-icon"),u.appendText(" Quick Run");let p=d.createDiv({cls:"af-quick-run-row"}),m=p.createEl("select",{cls:"af-select"});for(let g of h)m.createEl("option",{text:g.name,attr:{value:g.name}});let f=p.createEl("button",{cls:"af-btn-sm primary"});E(f,"play","af-btn-icon"),f.appendText(" Run"),f.onclick=()=>void this.plugin.runAgentPrompt(m.value)}}renderAgentMini(e,s,n){let a=this.plugin.runtime.getAgentState(s.name),r=n.filter(u=>u.agent===s.name),o=this.healthToClass(a.status),c=e.createDiv({cls:"af-agent-mini"}),l=c.createDiv({cls:`af-agent-avatar ${o}`});s.avatar?.trim()?this.renderAgentAvatar(l,s):l.setText(this.getInitials(s.name));let h=c.createDiv({cls:"af-agent-info"});h.createDiv({cls:"af-agent-name",text:s.name});let d="";if(a.status==="running")d=`Running now \xB7 ${r.length} task${r.length!==1?"s":""}`;else if(!s.enabled)d=`Disabled \xB7 ${r.length} task${r.length!==1?"s":""} paused`;else{let u=r.map(p=>p.nextRun).filter(Boolean).sort()[0];d=u?`Next: ${this.formatNextRun(u)} \xB7 ${r.length} task${r.length!==1?"s":""}`:`${r.length} task${r.length!==1?"s":""}`}h.createDiv({cls:"af-agent-desc",text:d}),c.createDiv({cls:`af-agent-status-dot ${o}`}),c.onclick=()=>this.navigate("agent-detail",s.name)}renderAgentsPage(e){let s=e.createDiv({cls:"af-agents-page"}),n=this.plugin.runtime.getSnapshot(),a=s.createDiv({cls:"af-agents-toolbar"});a.createDiv({cls:"af-page-title",text:"Agents"}),a.createDiv({cls:"af-toolbar-spacer"});let r=a.createEl("button",{cls:"af-btn-sm primary"});E(r,"plus","af-btn-icon"),r.appendText(" New Agent"),r.onclick=()=>void this.plugin.createAgentTemplate();let o=s.createDiv({cls:"af-agents-grid"});if(n.agents.length===0){this.renderEmptyState(o,"bot","No agents configured","Create your first agent to get started");return}for(let c of n.agents)this.renderAgentCard(o,c,n)}renderAgentCard(e,s,n){let a=this.plugin.runtime.getAgentState(s.name),r=this.plugin.runtime.getRecentRuns().filter(D=>D.agent===s.name),o=e.createDiv({cls:`af-agent-card${s.enabled?"":" disabled"}`}),c=o.createDiv({cls:"af-agent-card-header"}),l=s.enabled?this.healthToClass(a.status):"disabled",h=c.createDiv({cls:`af-agent-card-avatar ${l}`});this.renderAgentAvatar(h,s);let d=c.createDiv({cls:"af-agent-card-titleblock"}),u=d.createDiv({cls:"af-agent-card-name"});if(u.appendText(s.name),s.heartbeatEnabled&&s.heartbeatSchedule){let D=u.createSpan({cls:"af-heartbeat-indicator"});(0,w.setIcon)(D,"heart-pulse"),D.title=`Heartbeat: ${s.heartbeatSchedule}`}d.createDiv({cls:"af-agent-card-desc",text:s.description??"No description"});let p=c.createDiv({cls:`af-agent-card-toggle${s.enabled?" on":""}`});p.onclick=D=>{D.stopPropagation(),this.plugin.toggleAgent(s.name,!s.enabled)};let m=o.createDiv({cls:"af-agent-card-stats"}),f=r.length,g=r.filter(D=>D.status==="success").length,v=f>0?Math.round(g/f*100):0,b=f>0?Math.round(r.reduce((D,M)=>D+M.durationSeconds,0)/f):0,k=r.reduce((D,M)=>D+(M.tokensUsed??0),0);if(this.renderAgentStat(m,String(f),"Runs"),this.renderAgentStat(m,`${v}%`,"Success"),this.renderAgentStat(m,`${b}s`,"Avg Time"),this.renderAgentStat(m,Wa(k),"Tokens"),s.skills.length>0){let D=o.createDiv({cls:"af-agent-card-skills"});for(let M of s.skills)D.createSpan({cls:"af-skill-tag",text:M})}let y=o.createDiv({cls:"af-agent-card-footer"}),S=[`Model: ${s.model}`];s.approvalRequired.length>0&&S.push(`Approval: ${s.approvalRequired.join(", ")}`),s.memory&&S.push("Memory: on"),s.enabled||S.unshift("Disabled"),y.createSpan({cls:"af-agent-card-meta",text:S.join(" \xB7 ")});let T=y.createDiv({cls:"af-agent-card-actions"});if(!s.enabled){let D=T.createEl("button",{cls:"af-btn-sm",text:"Enable"});D.onclick=M=>{M.stopPropagation(),this.plugin.toggleAgent(s.name,!0)}}let _=T.createEl("button",{cls:"af-btn-sm"});if(E(_,"edit","af-btn-icon"),_.appendText(" Edit"),_.onclick=D=>{D.stopPropagation(),this.navigate("edit-agent",s.name)},s.enabled){let D=T.createEl("button",{cls:"af-btn-sm primary"});E(D,"play","af-btn-icon"),D.appendText(" Run"),D.onclick=M=>{M.stopPropagation(),this.plugin.runAgentPrompt(s.name)}}o.onclick=()=>this.navigate("agent-detail",s.name)}renderAgentStat(e,s,n){let a=e.createDiv({cls:"af-agent-stat"});a.createSpan({cls:"af-agent-stat-value",text:s}),a.createSpan({cls:"af-agent-stat-label",text:n})}renderAgentDetailPage(e){let s=e.createDiv({cls:"af-agent-detail-page"}),n=this.detailContext;if(!n){this.renderEmptyState(s,"bot","No agent selected","Select an agent from the list");return}let a=this.plugin.runtime.getSnapshot().agents.find(k=>k.name===n);if(!a){this.renderEmptyState(s,"bot","Agent not found",`Agent "${n}" was not found`);return}let r=this.plugin.runtime.getAgentState(a.name),o=this.plugin.runtime.getRecentRuns().filter(k=>k.agent===a.name),c=s.createDiv({cls:"af-detail-header"}),l=c.createDiv({cls:"af-detail-header-left"}),h=l.createDiv({cls:`af-agent-card-avatar ${this.healthToClass(r.status)}`});this.renderAgentAvatar(h,a);let d=l.createDiv();d.createDiv({cls:"af-detail-header-name",text:a.name}),d.createDiv({cls:"af-detail-header-desc",text:a.description??"No description"});let u=c.createDiv({cls:"af-detail-header-actions"}),p=u.createEl("button",{cls:"af-btn-sm primary"});if(E(p,"message-circle","af-btn-icon"),p.appendText(" Chat"),p.onclick=()=>this.openChatSlideover(a),a.enabled){let k=u.createEl("button",{cls:"af-btn-sm"});E(k,"play","af-btn-icon"),k.appendText(" Run Now"),k.onclick=()=>void this.plugin.runAgentPrompt(a.name);let y=u.createEl("button",{cls:"af-btn-sm"});E(y,"pause","af-btn-icon"),y.appendText(" Disable"),y.onclick=()=>void this.plugin.toggleAgent(a.name,!1)}else{let k=u.createEl("button",{cls:"af-btn-sm"});E(k,"play","af-btn-icon"),k.appendText(" Enable"),k.onclick=()=>void this.plugin.toggleAgent(a.name,!0)}let m=u.createEl("button",{cls:"af-btn-sm"});E(m,"edit","af-btn-icon"),m.appendText(" Edit"),m.onclick=()=>this.navigate("edit-agent",a.name);let f=u.createEl("button",{cls:"af-btn-sm danger"});E(f,"trash-2","af-btn-icon"),f.appendText(" Delete"),f.onclick=()=>void this.plugin.deleteAgent(a.name);let g=s.createDiv({cls:"af-detail-tabs"}),v=[{id:"overview",label:"Overview",icon:"layout-dashboard"},{id:"config",label:"Config",icon:"settings"},{id:"runs",label:"Runs",icon:"scroll-text"},{id:"memory",label:"Memory",icon:"file-text"}];for(let k of v){let y=g.createEl("button",{cls:`af-detail-tab${this.agentDetailTab===k.id?" active":""}`});E(y,k.icon,"af-tab-icon"),y.appendText(` ${k.label}`),y.onclick=()=>{this.agentDetailTab=k.id,this.render()}}let b=s.createDiv({cls:"af-detail-tab-content"});switch(this.agentDetailTab){case"overview":this.renderAgentOverviewTab(b,a,o);break;case"config":this.renderAgentConfigTab(b,a);break;case"runs":this.renderAgentRunsTab(b,o);break;case"memory":this.renderAgentMemoryTab(b,a);break}}renderAgentOverviewTab(e,s,n){let a=e.createDiv({cls:"af-dash-grid"}),r=n.length,o=n.filter(k=>k.status==="success").length,c=r>0?Math.round(o/r*100):0,l=r>0?Math.round(n.reduce((k,y)=>k+y.durationSeconds,0)/r):0,h=n.reduce((k,y)=>k+(y.tokensUsed??0),0),d=n.reduce((k,y)=>k+(y.costUsd??0),0),u=d>0?` \xB7 $${d.toFixed(2)}`:"";if(this.renderStatCard(a,"Total Runs",String(r),"","activity","all time"),this.renderStatCard(a,"Success Rate",`${c}%`,"","check-circle-2",`${o}/${r}`),this.renderStatCard(a,"Avg Time",`${l}s`,"","clock","per run"),this.renderStatCard(a,"Total Tokens",Wa(h),"","zap",`all time${u}`),s.isFolder&&(s.heartbeatBody.trim()||s.heartbeatEnabled)){let k=e.createDiv({cls:"af-section-card"}),y=k.createDiv({cls:"af-section-header"}),S=y.createDiv({cls:"af-section-title"});E(S,"heart-pulse"),S.appendText(" Heartbeat");let _=y.createDiv({cls:"af-detail-header-actions"}).createDiv({cls:`af-agent-card-toggle${s.heartbeatEnabled?" on":""}`});_.onclick=async()=>{let C=_.hasClass("on");await this.plugin.repository.updateHeartbeat(s.name,{enabled:!C}),await this.plugin.refreshFromVault(),new w.Notice(`Heartbeat ${C?"paused":"enabled"} for ${s.name}`)};let D=k.createDiv({cls:"af-config-form"});this.renderConfigRow(D,"Schedule",jd(s.heartbeatSchedule));let M=this.plugin.runtime.getNextHeartbeat(s.name);M&&s.heartbeatEnabled&&this.renderConfigRow(D,"Next run",this.timeUntil(M)),s.heartbeatChannel&&this.renderConfigRow(D,"Channel",s.heartbeatChannel)}if(s.skills.length>0){let k=e.createDiv({cls:"af-section-card"}),S=k.createDiv({cls:"af-section-header"}).createDiv({cls:"af-section-title"});E(S,"puzzle"),S.appendText(" Skills");let T=k.createDiv({cls:"af-detail-skills-list"});for(let _ of s.skills)T.createSpan({cls:"af-skill-tag",text:_})}let p=s.mcpServers??[];if(p.length>0){let k=e.createDiv({cls:"af-section-card"}),S=k.createDiv({cls:"af-section-header"}).createDiv({cls:"af-section-title"});E(S,"plug"),S.appendText(" MCP Servers");let T=k.createDiv({cls:"af-mcp-overview-list"}),_=this.plugin.repository.getMcpServers();for(let D of p){let M=_.find(L=>L.name===D),C=T.createDiv({cls:"af-mcp-overview-row"}),I=C.createSpan({cls:`af-mcp-status-dot ${M?M.enabled?M.status:"disabled":"disconnected"}`});I.title=M?M.enabled?M.status:"disabled":"unknown",C.createSpan({cls:"af-mcp-overview-name",text:D});let P=M?.toolDetails.length??M?.tools.length??0;P>0?C.createSpan({cls:"af-mcp-overview-tools",text:`${P} tools`}):M&&!M.enabled?C.createSpan({cls:"af-mcp-overview-tools af-muted",text:"disabled"}):M?.status==="needs-auth"&&C.createSpan({cls:"af-mcp-overview-tools af-muted",text:"needs auth"})}}if(s.permissionRules.allow.length>0||s.permissionRules.deny.length>0||s.permissionMode&&s.permissionMode!=="default"){let k=e.createDiv({cls:"af-section-card"}),S=k.createDiv({cls:"af-section-header"}).createDiv({cls:"af-section-title"});E(S,"shield-check"),S.appendText(" Permissions");let T=k.createDiv({cls:"af-config-form"});this.renderConfigRow(T,"Mode",s.permissionMode||"default"),s.permissionRules.allow.length>0&&this.renderConfigRow(T,"Allowed",s.permissionRules.allow.join(", ")),s.permissionRules.deny.length>0&&this.renderConfigRow(T,"Denied",s.permissionRules.deny.join(", "))}let f=e.createDiv({cls:"af-section-card"}),v=f.createDiv({cls:"af-section-header"}).createDiv({cls:"af-section-title"});E(v,"scroll-text"),v.appendText(" Recent Runs");let b=f.createDiv({cls:"af-timeline"});if(n.length===0)this.renderEmptyState(b,"scroll-text","No runs yet","");else for(let k of n.slice(0,5))this.renderTimelineItem(b,k)}renderAgentConfigTab(e,s){let n=e.createDiv({cls:"af-config-form"});this.renderConfigRow(n,"Name",s.name),this.renderConfigRow(n,"Description",s.description??""),this.renderConfigRow(n,"Model",s.model),this.renderConfigRow(n,"Timeout",`${s.timeout}s`),this.renderConfigRow(n,"Working Directory",s.cwd??"(vault root)"),this.renderConfigRow(n,"Permission Mode",s.permissionMode||"default"),this.renderConfigRow(n,"Approval Required",s.approvalRequired.join(", ")||"none"),s.permissionRules.allow.length>0&&this.renderConfigRow(n,"Allowed Commands",s.permissionRules.allow.join(", ")),s.permissionRules.deny.length>0&&this.renderConfigRow(n,"Blocked Commands",s.permissionRules.deny.join(", ")),this.renderConfigRow(n,"Memory",s.memory?"Enabled":"Disabled"),this.renderConfigRow(n,"Auto-compact",s.autoCompactThreshold&&s.autoCompactThreshold>0?`at ${s.autoCompactThreshold}% context`:"disabled"),s.wikiReferences&&s.wikiReferences.length>0&&this.renderConfigRow(n,"Wiki access",s.wikiReferences.map(c=>c.agent).join(", ")),this.renderConfigRow(n,"Tags",s.tags.join(", ")||"none");let a=n.createDiv({cls:"af-config-prompt-section"});if(a.createDiv({cls:"af-slideover-section-title",text:"SYSTEM PROMPT"}),a.createDiv({cls:"af-output-block",text:s.body||"(empty)"}),s.heartbeatBody.trim()){let c=n.createDiv({cls:"af-config-prompt-section"});c.createDiv({cls:"af-slideover-section-title",text:"HEARTBEAT INSTRUCTION"}),c.createDiv({cls:"af-output-block",text:s.heartbeatBody})}let o=n.createDiv({cls:"af-slideover-actions"}).createEl("button",{cls:"af-btn-sm primary"});E(o,"edit","af-btn-icon"),o.appendText(" Edit Agent"),o.onclick=()=>this.navigate("edit-agent",s.name)}renderConfigRow(e,s,n){let a=e.createDiv({cls:"af-detail-row"});a.createSpan({cls:"af-detail-label",text:s}),a.createSpan({cls:"af-detail-value af-mono",text:n})}renderAgentRunsTab(e,s){if(s.length===0){this.renderEmptyState(e,"scroll-text","No runs yet","Run this agent to see history");return}for(let n of s){let a=e.createDiv({cls:"af-run-list-item"}),r=a.createDiv({cls:`af-tl-icon ${this.statusToTimelineClass(n.status)}`});(0,w.setIcon)(r,this.statusToIconName(n.status));let o=a.createDiv({cls:"af-tl-body"}),c=o.createDiv({cls:"af-tl-title"});c.createSpan({text:n.task}),c.createSpan({cls:`af-status-badge ${this.statusToBadgeClass(n.status)}`,text:this.statusToBadgeText(n.status)});let l=o.createDiv({cls:"af-tl-meta"});l.createSpan({text:`${this.formatStarted(n.started)} \xB7 ${this.formatDuration(n.durationSeconds)}`}),n.tokensUsed&&l.createSpan({text:`${n.tokensUsed.toLocaleString()} tokens`}),o.createDiv({cls:"af-tl-desc",text:$t(n.output,120)}),a.onclick=()=>this.openSlideover(n)}}async renderAgentMemoryTab(e,s){if(!s.memory){this.renderEmptyState(e,"file-text","Memory disabled","Enable memory in agent config");return}let n=await this.plugin.repository.readWorkingMemory(s.name),a=e.createDiv({cls:"af-form-help"}),r=n?.tokenEstimate??0,o=s.reflection.enabled?`reflection on (${s.reflection.schedule})`:"reflection off";a.setText(`~${r} / ${s.memoryTokenBudget} tokens \xB7 ${o}`),!n||n.sections.length===0?this.renderEmptyState(e,"file-text","No memories yet","Agent will learn from runs"):e.createDiv({cls:"af-output-block"}).setText(Ge(n.sections));let c=e.createDiv({cls:"af-slideover-actions"}),l=c.createEl("button",{cls:"af-btn-sm"});E(l,"moon","af-btn-icon"),l.appendText(" Reflect now"),l.onclick=async()=>{l.disabled=!0,l.setText(" Reflecting\u2026");let d=await this.plugin.runtime.runReflectionNow(s.name);new w.Notice(`Agent Fleet: ${d.message}`),await this.renderAgentMemoryTab((e.empty(),e),s)};let h=c.createEl("button",{cls:"af-btn-sm"});E(h,"external-link","af-btn-icon"),h.appendText(" Open in Editor"),h.onclick=()=>void this.plugin.openPath(this.plugin.repository.getWorkingMemoryPath(s.name))}timeAgo(e){let s=Math.round((Date.now()-e.getTime())/1e3);if(s<60)return"just now";let n=Math.round(s/60);if(n<60)return`${n}m ago`;let a=Math.round(n/60);return a<24?`${a}h ago`:`${Math.round(a/24)}d ago`}timeUntil(e){let s=Math.round((e.getTime()-Date.now())/1e3);if(s<60)return"< 1m";let n=Math.round(s/60);if(n<60)return`in ${n}m`;let a=Math.round(n/60);return a<24?`in ${a}h`:`in ${Math.round(a/24)}d`}renderKanbanPage(e){let s=e.createDiv({cls:"af-kanban-page"}),n=this.plugin.runtime.getSnapshot(),a=this.plugin.runtime.getRecentRuns(),r=s.createDiv({cls:"af-kanban-toolbar"});r.createDiv({cls:"af-page-title",text:"Tasks Board"}),r.createDiv({cls:"af-toolbar-spacer"});let o=r.createEl("button",{cls:"af-btn-sm primary"});E(o,"plus","af-btn-icon"),o.appendText(" New Task"),o.onclick=()=>this.navigate("create-task");let c=s.createDiv({cls:"af-kanban-board"}),l=[],h=[],d=[],u=[],p=[],m=new Set;for(let b of n.agents){let k=this.plugin.runtime.getAgentState(b.name);k.status==="running"&&k.currentTaskId&&m.add(k.currentTaskId)}let f=this.toLocalDateStr(new Date);for(let b of a)b.status==="success"&&this.runToLocalDate(b.started)===f?u.push(b):(b.status==="failure"||b.status==="timeout"||b.status==="cancelled")&&this.runToLocalDate(b.started)===f&&p.push(b);let g=new Set(u.map(b=>b.task)),v=new Set(p.map(b=>b.task));for(let b of n.tasks){let k=g.has(b.taskId)||v.has(b.taskId)||b.lastRun&&this.runToLocalDate(b.lastRun)===f;if(m.has(b.taskId))d.push(b);else{if(k&&!b.schedule)continue;b.schedule&&b.enabled?h.push(b):l.push(b)}}this.renderKanbanColumn(c,"Backlog","inbox",l.length,b=>{for(let k of l)this.renderKanbanTaskCard(b,k,n,!0)},"backlog"),this.renderKanbanColumn(c,"Scheduled","clock",h.length,b=>{for(let k of h)this.renderKanbanTaskCard(b,k,n,!0)},"scheduled"),this.renderKanbanColumn(c,"Running","loader-2",d.length,b=>{for(let k of d)this.renderKanbanRunningCard(b,k)},"running",!1,"running"),this.renderKanbanColumn(c,"Done","check-circle-2",u.length,b=>{for(let k of u.slice(0,10))this.renderKanbanCompletedCard(b,k)},"completed"),this.renderKanbanColumn(c,"Failed","x-circle",p.length,b=>{for(let k of p)this.renderKanbanFailedCard(b,k)},"failed",!1,"failed")}renderKanbanColumn(e,s,n,a,r,o,c=!1,l){let h=e.createDiv({cls:`af-kanban-column${l?` af-kanban-${l}`:""}`});(o==="backlog"||o==="scheduled")&&Io(h,m=>this.handleTaskDrop(m,o));let u=h.createDiv({cls:"af-kanban-col-header"}).createDiv({cls:"af-kanban-col-title"});E(u,n),u.appendText(` ${s} `),u.createSpan({cls:"af-kanban-col-count",text:String(a)});let p=h.createDiv({cls:"af-kanban-col-body"});if(r(p),a===0&&p.createDiv({cls:"af-kanban-empty",text:"No items"}),c){let f=h.createDiv({cls:"af-kanban-col-add"}).createEl("button");E(f,"plus","af-btn-icon"),f.appendText(" Add task"),f.onclick=()=>{this.navigate("create-task")}}}handleTaskDrop(e,s){let n=this.plugin.runtime.getSnapshot().tasks.find(a=>a.taskId===e);if(n){if(s==="backlog")this.setTaskEnabled(n,!1).then(()=>{new w.Notice(`Task "${e}" moved to backlog (disabled)`)});else if(s==="scheduled"){if(!n.schedule&&!n.runAt){new w.Notice(`Task "${e}" needs a schedule. Open task details to set one.`),this.navigate("task-detail",e);return}this.setTaskEnabled(n,!0).then(()=>{new w.Notice(`Task "${e}" moved to scheduled (enabled)`)})}}}async setTaskEnabled(e,s){let n=this.plugin.app.vault.getAbstractFileByPath(e.filePath);if(!n||!(n instanceof w.TFile))return;let a=await this.plugin.app.vault.cachedRead(n),{frontmatter:r,body:o}=J(a);r.enabled=s,await this.plugin.app.vault.modify(n,W(r,o)),await this.plugin.refreshFromVault()}renderKanbanTaskCard(e,s,n,a){let r=e.createDiv({cls:`af-kanban-card af-priority-${s.priority}`});if(a){Do(r,s.taskId);let f=r.createDiv({cls:"af-kanban-card-grip"});(0,w.setIcon)(f,"grip-vertical")}let o=r.createDiv({cls:"af-kanban-card-header"});o.createDiv({cls:"af-kanban-card-title",text:s.taskId});let l=(n.agents.find(f=>f.name===s.agent)?.enabled??!1)&&s.enabled,h=o.createSpan({cls:`af-kanban-card-status ${l?"active":"inactive"}`});h.title=l?"Active":"Inactive";let d=r.createDiv({cls:"af-kanban-card-agent"}),u=d.createSpan({cls:"af-kanban-card-agent-icon"});(0,w.setIcon)(u,"bot"),d.createSpan({cls:"af-kanban-card-agent-name",text:s.agent});let m=r.createDiv({cls:"af-kanban-card-footer"}).createSpan({cls:"af-kanban-card-schedule"});l?s.schedule?(E(m,"refresh-cw","af-meta-icon"),m.appendText(` ${this.humanizeCron(s.schedule)}`)):m.appendText(s.runAt??"Manual"):(E(m,"pause","af-meta-icon"),m.appendText(" Paused")),r.onclick=()=>this.navigate("task-detail",s.taskId)}renderKanbanRunningCard(e,s){let n=e.createDiv({cls:"af-kanban-card af-kanban-card-running"});n.createDiv({cls:"af-kanban-card-title",text:s.taskId});let a=n.createDiv({cls:"af-kanban-card-agent"}),r=a.createSpan({cls:"af-kanban-card-agent-icon"});(0,w.setIcon)(r,"bot"),a.createSpan({cls:"af-kanban-card-agent-name",text:s.agent});let c=this.plugin.runtime.getSnapshot().agents.find(S=>S.name===s.agent)?.timeout??300,l=this.plugin.runtime.getAgentState(s.agent),h=l.runStarted?new Date(l.runStarted).getTime():Date.now(),p=n.createDiv({cls:"af-kanban-progress"}).createDiv({cls:"af-kanban-progress-track"}).createDiv({cls:"af-kanban-progress-bar af-kanban-progress-bar-real"}),m=(Date.now()-h)/1e3,f=Math.min(95,m/c*100);p.setCssStyles({width:`${f}%`});let g=n.createDiv({cls:"af-kanban-card-footer"}),v=g.createSpan({cls:"af-kanban-card-schedule"});E(v,"loader-2","af-meta-icon");let b=Math.round(m);v.appendText(` ${b}s / ${c}s`);let k=g.createEl("button",{cls:"af-kanban-stop-btn"});(0,w.setIcon)(k,"square"),k.title="Stop task",k.onclick=S=>{S.stopPropagation(),this.plugin.runtime.abortAgentRun(s.agent),new w.Notice(`Stopped task "${s.taskId}"`)};let y=window.setInterval(()=>{let S=(Date.now()-h)/1e3,T=Math.min(95,S/c*100);p.setCssStyles({width:`${T}%`});let _=Math.round(S);v.textContent="",(0,w.setIcon)(v,"loader-2"),v.appendText(` ${_}s / ${c}s`)},1e3);this.streamingUnsubscribes.push(()=>window.clearInterval(y))}renderKanbanCompletedCard(e,s){let n=e.createDiv({cls:"af-kanban-card"});n.createDiv({cls:"af-kanban-card-title",text:s.task});let a=n.createDiv({cls:"af-kanban-card-agent"}),r=a.createSpan({cls:"af-kanban-card-agent-icon"});(0,w.setIcon)(r,"bot"),a.createSpan({cls:"af-kanban-card-agent-name",text:s.agent});let c=n.createDiv({cls:"af-kanban-card-footer"}).createSpan({cls:"af-kanban-card-schedule"});E(c,"check-circle-2","af-meta-icon"),c.appendText(` ${this.formatStarted(s.started)} \xB7 ${this.formatDuration(s.durationSeconds)}`),n.onclick=()=>this.openSlideover(s)}renderKanbanFailedCard(e,s){let n=s.status==="cancelled",a=e.createDiv({cls:`af-kanban-card ${n?"af-kanban-card-cancelled":"af-kanban-card-failed"}`});a.createDiv({cls:"af-kanban-card-title",text:s.task});let r=a.createDiv({cls:"af-kanban-card-agent"}),o=r.createSpan({cls:"af-kanban-card-agent-icon"});(0,w.setIcon)(o,"bot"),r.createSpan({cls:"af-kanban-card-agent-name",text:s.agent});let c=n?`Stopped after ${s.durationSeconds}s`:s.status==="timeout"?`Timeout after ${s.durationSeconds}s`:$t(s.output,60),l=a.createDiv({cls:"af-kanban-card-error"});E(l,n?"square":"alert-triangle","af-meta-icon"),l.appendText(` ${c}`);let h=a.createDiv({cls:"af-kanban-card-footer"}),d=h.createSpan({cls:"af-kanban-card-schedule"});if(E(d,n?"square":"x-circle","af-meta-icon"),d.appendText(` ${this.formatStarted(s.started)}`),!n){let u=h.createEl("button",{cls:"af-btn-sm"});E(u,"refresh-cw","af-btn-icon"),u.appendText(" Retry"),u.onclick=p=>{p.stopPropagation(),this.plugin.runAgentPrompt(s.agent)}}a.onclick=()=>this.openSlideover(s)}renderRunsPage(e){let s=e.createDiv({cls:"af-runs-page"}),n=this.plugin.runtime.getRecentRuns(),a=s.createDiv({cls:"af-runs-toolbar"});a.createDiv({cls:"af-page-title",text:"Run History"}),a.createDiv({cls:"af-toolbar-spacer"});let r=s.createDiv({cls:"af-runs-table"});if(n.length===0){this.renderEmptyState(r,"scroll-text","No runs yet","Run an agent to see history here");return}let o=r.createEl("table"),l=o.createEl("thead").createEl("tr");for(let d of["Status","Agent","Task","Started","Duration","Tokens","Model"])l.createEl("th",{text:d});let h=o.createEl("tbody");for(let d of n.slice(0,50))this.renderRunRow(h,d)}renderRunRow(e,s){let n=e.createEl("tr"),r=n.createEl("td").createSpan({cls:`af-status-badge ${this.statusToBadgeClass(s.status)}`}),o=r.createSpan();(0,w.setIcon)(o,this.statusToIconName(s.status)),r.appendText(` ${this.statusToBadgeText(s.status)}`);let c=n.createEl("td",{cls:"af-agent-link"});c.setText(s.agent),c.onclick=l=>{l.stopPropagation(),this.navigate("agent-detail",s.agent)},n.createEl("td",{text:s.task}),n.createEl("td",{cls:"af-mono",text:this.formatStarted(s.started)}),n.createEl("td",{cls:"af-mono",text:this.formatDuration(s.durationSeconds)}),n.createEl("td",{cls:"af-mono",text:s.tokensUsed?s.tokensUsed.toLocaleString():"\u2014"}),n.createEl("td",{cls:"af-mono",text:s.model}),n.setCssStyles({cursor:"pointer"}),n.onclick=()=>this.openSlideover(s)}renderSkillsPage(e){let s=e.createDiv({cls:"af-skills-page"}),n=this.plugin.runtime.getSnapshot(),a=s.createDiv({cls:"af-agents-toolbar"});a.createDiv({cls:"af-page-title",text:"Skills Library"}),a.createDiv({cls:"af-toolbar-spacer"});let r=a.createEl("button",{cls:"af-btn-sm primary"});E(r,"plus","af-btn-icon"),r.appendText(" New Skill"),r.onclick=()=>void this.plugin.createSkillTemplate();let o=s.createDiv({cls:"af-skills-grid"});if(n.skills.length===0){this.renderEmptyState(o,"puzzle","No skills yet","Create skills to give agents specialized abilities");return}for(let c of n.skills)this.renderSkillCard(o,c,n.agents)}renderSkillCard(e,s,n){let a=e.createDiv({cls:"af-skill-card"}),r=a.createDiv({cls:"af-skill-card-header"}),o=r.createDiv({cls:"af-skill-card-icon"});(0,w.setIcon)(o,this.getSkillIcon(s.name));let c=r.createEl("button",{cls:"af-btn-sm af-btn-xs"});E(c,"edit","af-btn-icon"),c.onclick=h=>{h.stopPropagation(),this.navigate("edit-skill",s.name)},a.createDiv({cls:"af-skill-card-name",text:s.name}),a.createDiv({cls:"af-skill-card-desc",text:s.description??"No description"});let l=n.filter(h=>h.skills.includes(s.name));if(l.length>0){let h=a.createDiv({cls:"af-skill-card-agents"});for(let d of l)h.createSpan({cls:"af-skill-card-agent-tag",text:d.name})}}renderChannelsPage(e){let s=e.createDiv({cls:"af-agents-page"}),n=this.plugin.runtime.getSnapshot(),a=s.createDiv({cls:"af-agents-toolbar"});a.createDiv({cls:"af-page-title",text:"Channels"}),a.createDiv({cls:"af-toolbar-spacer"});let r=a.createEl("button",{cls:"af-btn-sm primary"});E(r,"plus","af-btn-icon"),r.appendText(" New Channel"),r.onclick=()=>this.navigate("create-channel");let o=s.createDiv({cls:"af-agents-grid"});if(n.channels.length===0){this.renderEmptyState(o,"radio","No channels configured","Connect an agent to Slack or another chat platform");return}for(let c of n.channels)this.renderChannelCard(o,c,n.validationIssues)}async renderWikiKeepersPage(e){let s=e.createDiv({cls:"af-agents-page"}),n=this.plugin.runtime.getSnapshot(),a=s.createDiv({cls:"af-agents-toolbar"});a.createDiv({cls:"af-page-title",text:"Wiki Keepers"}),a.createDiv({cls:"af-toolbar-spacer"});let r=n.agents.filter(c=>c.wikiKeeper!==void 0);if(r.length===0){this.renderEmptyState(s,"library","No Wiki Keepers yet","Open Settings \u2192 Agent Fleet \u2192 Wiki Keepers \u2192 + Add to create one.");return}let o=s.createDiv({cls:"af-wk-list"});o.setCssStyles({display:"flex"}),o.setCssStyles({flexDirection:"column"}),o.setCssStyles({gap:"16px"});for(let c of r)await this.renderWikiKeeperCard(o,c)}async renderWikiKeeperCard(e,s){let n=s.wikiKeeper,a=e.createDiv({cls:"af-card"});a.setCssStyles({padding:"16px"}),a.setCssStyles({border:"1px solid var(--background-modifier-border)"}),a.setCssStyles({borderRadius:"8px"});let r=a.createDiv();r.setCssStyles({display:"flex"}),r.setCssStyles({alignItems:"center"}),r.setCssStyles({gap:"12px"}),r.setCssStyles({marginBottom:"12px"});let o=r.createDiv();o.setCssStyles({flex:"1"}),o.createEl("strong",{text:s.name});let c=n.scopeRoot||"(whole vault)";o.createEl("div",{text:`Scope: ${c} \xB7 topics: ${n.topicsRoot}/ \xB7 log: ${n.logPath}`,cls:"af-form-hint"});let l=r.createEl("button",{cls:"af-btn-sm"});l.appendText("Open log"),l.onclick=()=>{let g=n.scopeRoot?`${n.scopeRoot}/${n.logPath}`:n.logPath,v=this.plugin.app.vault.getAbstractFileByPath(g);v instanceof w.TFile?this.plugin.app.workspace.getLeaf().openFile(v):new w.Notice(`Log file not found: ${g}`)};let h=n.scopeRoot?`${n.scopeRoot}/${n.logPath}`:n.logPath,d=this.plugin.app.vault.getAbstractFileByPath(h),u=null;if(d instanceof w.TFile)try{let g=await this.plugin.app.vault.cachedRead(d);u=Mo(g)}catch{u=null}if(!u){a.createDiv({cls:"af-form-hint"}).setText("No lint report yet. Run wiki-lint manually or wait for the weekly task to fire.");return}let p=a.createDiv();if(p.setCssStyles({display:"flex"}),p.setCssStyles({alignItems:"baseline"}),p.setCssStyles({gap:"12px"}),p.setCssStyles({marginBottom:"8px"}),p.createEl("strong",{text:`Lint ${u.date}`}),p.createSpan({cls:"af-form-hint",text:`${u.summary.length} summary lines \xB7 ${u.autoApplied.length} auto-applied \xB7 ${u.needsReview.length} needs review`}),u.summary.length>0){let g=a.createEl("details");g.createEl("summary",{text:"Summary"});let v=g.createEl("ul");v.setCssStyles({marginTop:"4px"});for(let b of u.summary)v.createEl("li",{text:b})}if(u.autoApplied.length>0){let g=a.createEl("details");g.createEl("summary",{text:`Auto-applied (${u.autoApplied.length})`});let v=g.createEl("ul");v.setCssStyles({marginTop:"4px"});for(let b of u.autoApplied)v.createEl("li",{text:b})}if(u.refreshChained.length>0){let g=a.createEl("details");g.createEl("summary",{text:`Refresh chained (${u.refreshChained.length})`});let v=g.createEl("ul");v.setCssStyles({marginTop:"4px"});for(let b of u.refreshChained)v.createEl("li",{text:b})}let m=a.createDiv();if(m.setCssStyles({marginTop:"12px"}),m.createEl("strong",{text:`Needs review (${u.needsReview.length})`}),u.needsReview.length===0){m.createDiv({cls:"af-form-hint",text:"All clear. Nothing requires manual review from this lint pass."});return}let f=m.createDiv();f.setCssStyles({display:"flex"}),f.setCssStyles({flexDirection:"column"}),f.setCssStyles({gap:"6px"}),f.setCssStyles({marginTop:"8px"});for(let g of u.needsReview){let v=f.createDiv();v.setCssStyles({display:"flex"}),v.setCssStyles({alignItems:"flex-start"}),v.setCssStyles({gap:"8px"}),v.setCssStyles({padding:"8px 10px"}),v.setCssStyles({background:"var(--background-secondary)"}),v.setCssStyles({borderRadius:"4px"}),v.setCssStyles({fontSize:"13px"});let b=v.createDiv();b.setCssStyles({flex:"1"}),b.setText(g);let k=v.createEl("button",{cls:"af-btn-sm",text:"Dismiss"});k.title="Hide this item from the dashboard until the next lint pass (does not modify log.md).",k.onclick=()=>{v.remove()}}}renderChannelCard(e,s,n){let a=this.plugin.channelManager?.getChannelStatus(s.name)??"disabled",r=Oo(a),o=s.enabled&&a!=="disabled"?"af-agent-card":"af-agent-card disabled",c=e.createDiv({cls:o});c.setCssStyles({cursor:"default"});let l=c.createDiv({cls:"af-agent-card-header"}),h=l.createDiv({cls:`af-agent-card-avatar ${r}`});(0,w.setIcon)(h,"radio");let d=l.createDiv({cls:"af-agent-card-titleblock"});d.createDiv({cls:"af-agent-card-name",text:s.name}),d.createDiv({cls:"af-agent-card-desc",text:`Default: ${s.defaultAgent}`});let u=l.createSpan({cls:`af-pill ${Wd(a)}`});if(u.createSpan({cls:"af-dot"}),u.appendText(` ${a}`),s.allowedAgents.length>0){let T=c.createDiv({cls:"af-agent-card-skills"});for(let _ of s.allowedAgents){let D=T.createSpan({cls:"af-skill-tag",text:_});_===s.defaultAgent&&D.setCssStyles({fontWeight:"700"})}}let p=c.createDiv({cls:"af-agent-card-stats"}),m=this.plugin.channelManager?.getSessionCount(s.name)??0,f=this.plugin.channelManager?.getMetrics(s.name),g=s.allowedAgents.length>0?String(s.allowedAgents.length):"all";this.renderAgentStat(p,g,"Agents"),this.renderAgentStat(p,String(m),"Sessions"),this.renderAgentStat(p,String(f?.messagesReceived??0),"In"),this.renderAgentStat(p,String(f?.messagesSent??0),"Out");let v=c.createDiv({cls:"af-agent-card-footer"}),b=[s.type];s.enabled||b.push("disabled"),s.allowedUsers.length>0?b.push(`${s.allowedUsers.length} user(s)`):b.push("allowlist empty"),v.createSpan({cls:"af-agent-card-meta",text:b.join(" \xB7 ")});let y=v.createDiv({cls:"af-agent-card-actions"}).createEl("button",{cls:"af-btn-sm"});E(y,"edit","af-btn-icon"),y.appendText(" Edit"),y.onclick=T=>{T.stopPropagation(),this.navigate("edit-channel",s.name)};let S=n.filter(T=>T.path===s.filePath);if(S.length>0){let T=c.createDiv({cls:"af-channel-issues"});for(let _ of S)T.createDiv({cls:"af-channel-issue-row",text:_.message})}}renderCreateChannelPage(e){let s=e.createDiv({cls:"af-create-agent-page"}),n=this.plugin.runtime.getSnapshot(),a=this.plugin.channelCredentials.list(),r=s.createDiv({cls:"af-detail-header"}),o=r.createDiv({cls:"af-detail-header-left"}),c=o.createDiv({cls:"af-agent-card-avatar idle"});(0,w.setIcon)(c,"plus");let l=o.createDiv();l.createDiv({cls:"af-detail-header-name",text:"Create New Channel"}),l.createDiv({cls:"af-detail-header-desc",text:"Connect an external chat transport to an agent"});let h=r.createDiv({cls:"af-detail-header-actions"}),d={name:"",type:"slack",defaultAgent:n.agents[0]?.name??"",allowedAgents:[],credentialRef:a[0]?.ref??"",allowedUsers:"",perUserSessions:!0,channelContext:"",enabled:!0,tags:"",body:"",transportJson:""},u=s.createDiv({cls:"af-create-form"}),p=u.createDiv({cls:"af-create-section"}),m=p.createDiv({cls:"af-create-section-header"}),f=m.createSpan({cls:"af-create-section-icon"});(0,w.setIcon)(f,"radio"),m.createSpan({text:"Channel Details"}),this.createFormField(p,"Name","my-slack","Unique identifier for this channel",$=>{d.name=$});let g=p.createDiv({cls:"af-form-row"});g.createDiv({cls:"af-form-label",text:"Type"});let v=g.createEl("select",{cls:"af-form-select"});v.createEl("option",{text:"slack",attr:{value:"slack"}}),v.createEl("option",{text:"telegram",attr:{value:"telegram"}}),v.addEventListener("change",()=>{d.type=v.value});let b=p.createDiv({cls:"af-form-row"}),k=b.createDiv({cls:"af-form-label"});k.setText("Credential"),this.addTooltip(k,"Configured in Settings \u2192 Agent Fleet \u2192 Channel Credentials");let y=b.createEl("select",{cls:"af-form-select"});a.length===0&&y.createEl("option",{text:"(no credentials configured)",attr:{value:""}});for(let $ of a)y.createEl("option",{text:`${$.ref} (${$.entry.type})`,attr:{value:$.ref}});y.addEventListener("change",()=>{d.credentialRef=y.value});let S=p.createDiv({cls:"af-form-row af-form-row-toggle"});S.createDiv({cls:"af-form-label",text:"Enabled"});let T=S.createDiv({cls:"af-agent-card-toggle on"});T.onclick=()=>{let $=T.hasClass("on");T.toggleClass("on",!$),d.enabled=!$};let _=u.createDiv({cls:"af-create-section"}),D=_.createDiv({cls:"af-create-section-header"}),M=D.createSpan({cls:"af-create-section-icon"});(0,w.setIcon)(M,"bot"),D.createSpan({text:"Agent Routing"});let C=_.createDiv({cls:"af-form-row"}),I=C.createDiv({cls:"af-form-label"});I.setText("Default agent"),this.addTooltip(I,"Used when no @agent-name prefix is given in a message");let P=C.createEl("select",{cls:"af-form-select"});for(let $ of n.agents)P.createEl("option",{text:$.name,attr:{value:$.name}});P.addEventListener("change",()=>{d.defaultAgent=P.value});let L=_.createDiv({cls:"af-form-row"}),F=L.createDiv({cls:"af-form-label"});F.setText("Allowed agents"),this.addTooltip(F,"Agents reachable via @prefix. Leave unchecked to allow all agents.");let z=L.createDiv({cls:"af-form-checkboxes"});for(let $ of n.agents){let Te=z.createEl("label",{cls:"af-form-checkbox-label"}),xe=Te.createEl("input",{attr:{type:"checkbox",value:$.name}});Te.appendText(` ${$.name}`),xe.addEventListener("change",()=>{xe.checked?d.allowedAgents.includes($.name)||d.allowedAgents.push($.name):d.allowedAgents=d.allowedAgents.filter(he=>he!==$.name)})}let U=_.createDiv({cls:"af-form-row af-form-row-toggle"}),Z=U.createDiv({cls:"af-form-label"});Z.setText("Per-user sessions"),this.addTooltip(Z,"Each external user gets their own isolated Claude session");let de=U.createDiv({cls:"af-agent-card-toggle on"});de.onclick=()=>{let $=de.hasClass("on");de.toggleClass("on",!$),d.perUserSessions=!$};let me=u.createDiv({cls:"af-create-section"}),ye=me.createDiv({cls:"af-create-section-header"}),X=ye.createSpan({cls:"af-create-section-icon"});(0,w.setIcon)(X,"shield-check"),ye.createSpan({text:"Access Control"});let j=me.createDiv({cls:"af-form-label"});j.setText("Allowed users"),this.addTooltip(j,"Slack user IDs (U...), one per line. Only listed users can reach the bot.");let K=me.createEl("textarea",{cls:"af-create-prompt-textarea",attr:{placeholder:`U0AQW6P37N1 -U0BXYZ12345`,rows:"4"}});K.addEventListener("input",()=>{d.allowedUsers=K.value});let ne=u.createDiv({cls:"af-create-section"}),G=ne.createDiv({cls:"af-create-section-header"}),ee=G.createSpan({cls:"af-create-section-icon"});(0,w.setIcon)(ee,"message-square");let Q=G.createSpan({text:"Channel Context"});this.addTooltip(Q,"Extra instructions appended to the agent's system prompt when reached through this channel");let ae=ne.createEl("textarea",{cls:"af-create-prompt-textarea",attr:{placeholder:"You are being contacted via Slack. Keep replies concise...",rows:"6"}});ae.addEventListener("input",()=>{d.channelContext=ae.value}),this.createFormField(p,"Tags","ops, internal","Comma-separated metadata",$=>{d.tags=$});let ve=u.createDiv({cls:"af-create-section"}),Re=ve.createDiv({cls:"af-create-section-header"}),Le=Re.createSpan({cls:"af-create-section-icon"});(0,w.setIcon)(Le,"settings");let Me=Re.createSpan({text:"Advanced"});this.addTooltip(Me,"Markdown body (shown in the channel detail page) and transport-specific overrides");let Fe=ve.createDiv({cls:"af-form-label"});Fe.setText("Body (markdown)"),this.addTooltip(Fe,"Free-form notes for this channel. Shown in the channel detail page; not sent to the agent.");let ke=ve.createEl("textarea",{cls:"af-create-prompt-textarea",attr:{placeholder:"Notes, runbook snippets, escalation contacts\u2026",rows:"4"}});ke.addEventListener("input",()=>{d.body=ke.value});let Oe=ve.createDiv({cls:"af-form-label"});Oe.setText("Transport config (JSON)"),this.addTooltip(Oe,"Optional JSON object for transport-specific overrides (e.g. Slack socket_mode, telegram webhook settings). Leave blank for defaults.");let Ue=ve.createEl("textarea",{cls:"af-create-prompt-textarea",attr:{placeholder:`{ +`,t)),n{for(let n of s)await this.discordApi("POST",`/channels/${t}/messages`,{content:n})})}async setTyping(t,e){let s=Bo(t);if(s)if(e){let n=this.typingIntervals.get(t);n&&window.clearInterval(n);try{await this.discordApi("POST",`/channels/${s}/typing`)}catch(r){console.warn("Agent Fleet: Discord typing trigger failed",r)}let a=window.setInterval(()=>{this.discordApi("POST",`/channels/${s}/typing`).catch(()=>{})},8e3);this.typingIntervals.set(t,a)}else{let n=this.typingIntervals.get(t);n&&(window.clearInterval(n),this.typingIntervals.delete(t))}}async broadcast(t){let e=this.config.allowedUsers[0];if(e)try{let n=(await this.discordApi("POST","/users/@me/channels",{recipient_id:e}))?.id;if(!n)return;let a=Uo(t,2e3);for(let r of a)await this.discordApi("POST",`/channels/${n}/messages`,{content:r})}catch(s){console.error(`Agent Fleet: Discord broadcast failed on ${this.config.name}`,s)}}onInbound(t){return this.inboundHandlers.add(t),()=>this.inboundHandlers.delete(t)}onStatusChange(t){return this.statusHandlers.add(t),()=>this.statusHandlers.delete(t)}onAgentSwitch(t){return this.agentSwitchHandlers.add(t),()=>this.agentSwitchHandlers.delete(t)}setAllowedAgentsResolver(t){this.allowedAgentsResolver=t}pickerAgents(){return this.allowedAgentsResolver?this.allowedAgentsResolver():this.config.allowedAgents}async connect(){if(this.stopping)return;this.setStatus(this.ws?"reconnecting":"connecting");let e=`${this.canResume&&this.resumeGatewayUrl?this.resumeGatewayUrl:Zd}${eh}`;try{let s=new ns(e);s.on("message",r=>{this.handleFrame(r)}),s.on("error",r=>{console.warn(`Agent Fleet: Discord WebSocket error on ${this.config.name}`,r)}),s.on("close",r=>{this.ws=null,this.clearHeartbeat(),this.handleClose(r)}),this.ws=s;let n=window.setTimeout(()=>{if(this.status==="connecting"||this.status==="reconnecting"){console.warn(`Agent Fleet: Discord WebSocket connect timeout on ${this.config.name}`);try{s.close()}catch{}}},3e4);s.on("close",()=>window.clearTimeout(n));let a=this.onStatusChange(r=>{r==="connected"&&(window.clearTimeout(n),a())})}catch(s){console.error("Agent Fleet: Discord WebSocket open failed",s),this.setStatus("error"),this.scheduleReconnect()}}handleClose(t){let e=!1;t===4014?(console.error(`Agent Fleet: Discord channel ${this.config.name} \u2014 disallowed intents (4014). Enable the Message Content intent in the Developer Portal \u2192 your app \u2192 Bot \u2192 Privileged Gateway Intents, then reload.`),this.canResume=!1,this.setStatus("needs-auth"),e=!0):t===4004||t===4013?(console.error(`Agent Fleet: Discord channel ${this.config.name} \u2014 gateway auth/intents error (${t}). Check the bot token credential, then reload.`),this.canResume=!1,this.setStatus("needs-auth"),e=!0):(t===4007||t===4009)&&(this.canResume=!1),!this.stopping&&!e&&this.scheduleReconnect()}handleFrame(t){let e;try{e=JSON.parse(t.toString())}catch{return}switch(typeof e.s=="number"&&(this.seq=e.s),e.op){case lh:{let s=e.d?.heartbeat_interval;this.startHeartbeat(s??41250),this.canResume&&this.sessionId&&this.seq!==null?this.sendGateway(ih,{token:this.credential.botToken,session_id:this.sessionId,seq:this.seq}):this.identify();return}case Va:{this.sendGateway(Va,this.seq);return}case ch:{this.heartbeatAcked=!0;return}case rh:{try{this.ws?.close()}catch{}return}case oh:{e.d===!0||(this.canResume=!1,this.sessionId=null),window.setTimeout(()=>{try{this.ws?.close()}catch{}},1e3+Math.floor(Math.random()*4e3));return}case nh:{this.handleDispatch(e.t??"",e.d);return}default:return}}handleDispatch(t,e){if(t==="READY"){let s=e;this.sessionId=s.session_id??null,this.resumeGatewayUrl=s.resume_gateway_url??null,this.selfUserId=s.user?.id??null,this.applicationId=s.application?.id??null,this.canResume=!0,this.backoffMs=1e3,this.setStatus("connected"),this.registerAgentsCommand();return}if(t==="RESUMED"){this.backoffMs=1e3,this.setStatus("connected");return}if(t==="MESSAGE_CREATE"){this.routeMessage(e);return}if(t==="INTERACTION_CREATE"){this.handleInteraction(e);return}}identify(){this.sendGateway(ah,{token:this.credential.botToken,intents:sh,properties:{os:"linux",browser:"agent-fleet",device:"agent-fleet"}})}sendGateway(t,e){if(!(!this.ws||this.ws.readyState!==ns.OPEN))try{this.ws.send(JSON.stringify({op:t,d:e}))}catch(s){console.warn("Agent Fleet: Discord gateway send failed",s)}}startHeartbeat(t){this.clearHeartbeat(),this.heartbeatAcked=!0;let e=()=>{if(!this.heartbeatAcked){console.warn(`Agent Fleet: Discord heartbeat not ACKed on ${this.config.name} \u2014 reconnecting`);try{this.ws?.close()}catch{}return}this.heartbeatAcked=!1,this.sendGateway(Va,this.seq)};this.heartbeatInitialTimer=window.setTimeout(()=>{e(),this.heartbeatTimer=window.setInterval(e,t)},Math.floor(t*Math.random()))}clearHeartbeat(){this.heartbeatInitialTimer&&(window.clearTimeout(this.heartbeatInitialTimer),this.heartbeatInitialTimer=null),this.heartbeatTimer&&(window.clearInterval(this.heartbeatTimer),this.heartbeatTimer=null)}scheduleReconnect(){if(this.stopping||this.reconnectTimer)return;let t=this.backoffMs;this.backoffMs=Math.min(3e4,this.backoffMs*2),console.warn(`Agent Fleet: Discord channel ${this.config.name} scheduling reconnect in ${t}ms`),this.reconnectTimer=window.setTimeout(()=>{this.reconnectTimer=null,!this.stopping&&this.connect()},t)}routeMessage(t){if(!t.author||t.author.bot||this.selfUserId&&t.author.id===this.selfUserId||!t.channel_id)return;let e=(t.attachments??[]).filter(a=>typeof a.content_type=="string"&&a.content_type.startsWith("image/")),s=ph(t.content??"",this.selfUserId);if(!s&&e.length===0){this.warnedEmptyContent||(this.warnedEmptyContent=!0,console.warn(`Agent Fleet: Discord channel ${this.config.name} received a message with empty content. If this persists, enable the Message Content intent in the Developer Portal.`));return}let n=No(t.guild_id,t.channel_id);this.buildAndEmitMessage(t,s,n,e)}async buildAndEmitMessage(t,e,s,n){let a=[];for(let o of n)try{let c=await(0,Ya.requestUrl)({url:o.url,method:"GET"});a.push({data:c.arrayBuffer,filename:o.filename||`attachment_${t.id}`,mimeType:o.content_type??"image/jpeg"})}catch(c){console.warn("Agent Fleet: Discord attachment download failed",c)}let r={conversationId:s,externalUserId:t.author.id,text:e,timestamp:t.timestamp??new Date().toISOString(),meta:{discord_guild_id:t.guild_id,discord_channel_id:t.channel_id,discord_message_id:t.id,is_dm:!t.guild_id},...a.length>0?{images:a}:{}};for(let o of this.inboundHandlers)try{o(r)}catch(c){console.error("Agent Fleet: Discord inbound handler threw",c)}}async registerAgentsCommand(){if(!(this.commandRegistered||!this.applicationId))try{await this.discordApi("PUT",`/applications/${this.applicationId}/commands`,[{name:"agents",description:"Switch the active agent",type:1}]),this.commandRegistered=!0}catch(t){console.warn(`Agent Fleet: Discord /agents command registration failed on ${this.config.name}`,t)}}async handleInteraction(t){if(t.type===dh){t.data?.name==="agents"&&await this.respondWithAgentPicker(t);return}if(t.type===hh){let e=t.data?.custom_id??"";if(!e.startsWith("switch:"))return;let s=e.slice(7),n=t.member?.user?.id??t.user?.id,a=t.channel_id;if(!n||!a)return;let r=No(t.guild_id,a);for(let o of this.agentSwitchHandlers)try{o(r,s,n)}catch(c){console.error("Agent Fleet: Discord agent switch handler threw",c)}await this.respondToInteraction(t,uh,{content:`Active agent: **${s}**`,components:this.buildAgentButtons(s)});return}}async respondWithAgentPicker(t){if(this.pickerAgents().length===0){await this.respondToInteraction(t,Fo,{content:"No agents available. Add existing, enabled agents to `allowed_agents` in the channel file.",flags:Oo});return}await this.respondToInteraction(t,Fo,{content:"Select an agent to chat with:",flags:Oo,components:this.buildAgentButtons(this.config.defaultAgent)})}buildAgentButtons(t){let e=this.pickerAgents().slice(0,25),s=[];for(let n=0;n({type:2,style:r===t?1:2,label:r===t?`${r} \u2713`:r,custom_id:`switch:${r}`}))})}return s}async respondToInteraction(t,e,s){try{await this.discordApi("POST",`/interactions/${t.id}/${t.token}/callback`,{type:e,data:s})}catch(n){console.warn("Agent Fleet: Discord interaction response failed",n)}}async discordApi(t,e,s){let n=await(0,Ya.requestUrl)({url:`${Qd}${e}`,method:t,contentType:"application/json",headers:{Authorization:`Bot ${this.credential.botToken}`,"User-Agent":th},...s!==void 0?{body:JSON.stringify(s)}:{},throw:!1});if(n.status===429){let a=n.json?.retry_after??Number(n.headers["retry-after"]??"1");return await new Promise(r=>window.setTimeout(r,Math.max(1e3,a*1e3))),this.discordApi(t,e,s)}if(n.status===401||n.status===403)throw new Error(`Discord ${t} ${e} ${n.status}: ${mh(n.text)}`);if(n.status<200||n.status>=300)throw new Error(`Discord ${t} ${e} HTTP ${n.status}: ${n.text}`);if(n.status!==204)return n.json}async enqueueSend(t,e){let n=(this.sendQueues.get(t)??Promise.resolve()).then(e),a=n.catch(()=>{});this.sendQueues.set(t,a);try{await n}finally{this.sendQueues.get(t)===a&&this.sendQueues.delete(t)}}setStatus(t){if(this.status!==t){this.status=t;for(let e of this.statusHandlers)try{e(t)}catch{}}}};function No(i,t){return i?`discord:${i}:${t}`:`discord:dm:${t}`}function Bo(i){let t=i.split(":");return t[0]!=="discord"?null:t[2]??null}function ph(i,t){let e=i.trimStart();if(t){let s=new RegExp(`^<@!?${t}>\\s*`);e=e.replace(s,"")}return e.trim()}function mh(i){let t=i??"";try{let e=JSON.parse(t);if(e&&(e.message!==void 0||e.code!==void 0))return`${e.message??"error"} (code ${e.code??"?"})`}catch{}return t||"no body"}function Uo(i,t){if(i.length<=t)return[i];let e=[],s=i;for(;s.length>t;){let n=s.lastIndexOf(` + +`,t);ns.agents.length},{icon:"columns-3",label:"Tasks Board",page:"kanban"},{icon:"scroll-text",label:"Run History",page:"runs"},{icon:"shield-check",label:"Approvals",page:"approvals",badge:()=>n.pending},{icon:"puzzle",label:"Skills",page:"skills",badge:()=>s.skills.length},{icon:"plug",label:"MCP Servers",page:"mcp",badge:()=>this.plugin.repository.getMcpServers().length},{icon:"radio",label:"Channels",page:"channels",badge:()=>this.plugin.channelManager?.getConnectedCount()??s.channels.length}];for(let l of r){let h=a.createDiv({cls:"af-sidebar-nav-item"}),d=h.createSpan({cls:"af-sidebar-nav-icon"});(0,Ms.setIcon)(d,l.icon),h.createSpan({cls:"af-sidebar-nav-label",text:l.label});let u=l.badge?.();u!==void 0&&u>0&&h.createSpan({cls:"af-sidebar-badge",text:String(u)}),h.setAttribute("role","button"),h.setAttribute("tabindex","0"),h.onclick=()=>void this.plugin.navigateDashboard(l.page),h.onkeydown=p=>{(p.key==="Enter"||p.key===" ")&&(p.preventDefault(),this.plugin.navigateDashboard(l.page))}}let o=e.createDiv({cls:"af-sidebar-section"});o.createDiv({cls:"af-sidebar-section-header",text:"AGENTS"}),s.agents.length===0&&o.createDiv({cls:"af-sidebar-empty",text:"No agents configured"});for(let l of s.agents){let h=this.plugin.runtime.getAgentState(l.name),d=o.createDiv({cls:"af-sidebar-agent-item"});d.createSpan({cls:`af-sidebar-agent-dot ${this.healthToClass(h.status)}`}),d.createSpan({cls:"af-sidebar-agent-name",text:l.name}),d.setAttribute("role","button"),d.setAttribute("tabindex","0"),d.onclick=()=>void this.plugin.navigateDashboard("agent-detail",l.name),d.onkeydown=u=>{(u.key==="Enter"||u.key===" ")&&(u.preventDefault(),this.plugin.navigateDashboard("agent-detail",l.name))}}let c=e.createDiv({cls:"af-sidebar-section"});c.createDiv({cls:"af-sidebar-section-header",text:"QUICK ACTIONS"}),this.renderQuickAction(c,"plus","New Agent",()=>void this.plugin.createAgentTemplate()),this.renderQuickAction(c,"plus","New Task",()=>void this.plugin.openCreateTask()),this.renderQuickAction(c,"plus","New Skill",()=>void this.plugin.createSkillTemplate())}renderQuickAction(e,s,n,a){let r=e.createDiv({cls:"af-sidebar-action-item"}),o=r.createSpan({cls:"af-sidebar-action-icon"});(0,Ms.setIcon)(o,s),r.createSpan({text:n}),r.setAttribute("role","button"),r.setAttribute("tabindex","0"),r.onclick=a,r.onkeydown=c=>{(c.key==="Enter"||c.key===" ")&&(c.preventDefault(),a())}}healthToClass(e){switch(e){case"running":return"running";case"error":return"error";case"pending":return"pending";case"disabled":return"disabled";default:return"idle"}}};var b=require("obsidian");var as=require("obsidian"),fh=["bot","brain","shield-check","search","file-text","rocket","wand","sparkles","zap","target","compass","eye","code","terminal","database","globe","mail","message-circle","book","pen-tool","palette","music","camera","chart-bar","clipboard","cpu","server","cloud","lock","key","bell","calendar","clock","heart","star","flag","bookmark"],$n=class extends as.Modal{constructor(e,s,n){super(e);this.onSelect=n;this.selectedIcon=s}searchQuery="";selectedIcon;allIcons=[];gridContainer;onOpen(){this.allIcons=(0,as.getIconIds)().sort();let{contentEl:e}=this;e.empty(),e.addClass("af-icon-picker-modal");let s=e.createEl("input",{cls:"af-icon-picker-search",attr:{type:"text",placeholder:"Search icons...",value:this.searchQuery}});this.gridContainer=e.createDiv({cls:"af-icon-picker-scroll"}),this.renderGrid(),s.addEventListener("input",()=>{this.searchQuery=s.value,this.renderGrid()}),window.setTimeout(()=>s.focus(),0)}renderGrid(){let e=this.gridContainer;e.empty();let s=this.searchQuery.toLowerCase().trim();if(!s)this.renderSection(e,"Popular",fh),this.renderSection(e,"All Icons",this.allIcons);else{let n=this.allIcons.filter(r=>r.includes(s)),a=n.length===0?"No results":`${n.length} result${n.length!==1?"s":""}`;this.renderSection(e,a,n)}}renderSection(e,s,n){let a=e.createDiv({cls:"af-icon-picker-section"});a.createDiv({cls:"af-icon-picker-section-label",text:s});let r=a.createDiv({cls:"af-icon-picker-grid"});for(let o of n)this.renderIconItem(r,o)}renderIconItem(e,s){let n=e.createDiv({cls:`af-icon-picker-item${this.selectedIcon===s?" selected":""}`});n.setAttribute("title",s),n.setAttribute("aria-label",s),(0,as.setIcon)(n,s),n.addEventListener("click",()=>{this.selectedIcon=s,this.onSelect(s),this.close()})}onClose(){this.contentEl.empty()}};var gh=[{match:/opus/i,rates:{input:15,output:75,cacheWrite:18.75,cacheRead:1.5}},{match:/sonnet/i,rates:{input:3,output:15,cacheWrite:3.75,cacheRead:.3}},{match:/haiku/i,rates:{input:1,output:5,cacheWrite:1.25,cacheRead:.1}},{match:/gpt-5|codex|o[0-9]/i,rates:{input:1.25,output:10,cacheWrite:1.25,cacheRead:.125}}],yh={input:3,output:15,cacheWrite:3.75,cacheRead:.3};function $o(i){for(let{match:t,rates:e}of gh)if(t.test(i))return e;return yh}function jo(i,t){let e=$o(i);return(t.inputTokens*e.input+t.outputTokens*e.output+t.cacheCreateTokens*e.cacheWrite+t.cacheReadTokens*e.cacheRead)/1e6}function Wo(i,t){let e=$o(i),s=.7*e.input+.3*e.output;return t*s/1e6}var Ho=require("obsidian");function R(i,t,e){let s=i.createSpan({cls:e??"af-icon"});return(0,Ho.setIcon)(s,t),s}function Ze(i,t={}){let e=activeDocument.createElementNS("http://www.w3.org/2000/svg",i);for(let[s,n]of Object.entries(t))e.setAttribute(s,n);return e}function vh(i){try{return new Date(i+"T12:00:00").toLocaleDateString(void 0,{month:"short",day:"numeric"})}catch{return i.slice(5)}}function qo(i,t){let e=t.length||1,s=1e3,n=6,a=4,r=4,o=s-a-r-n*(e-1),c=Math.floor(o/e),l=140,h=36,d=18,u=d+l+h,p=Math.max(1,...t.map(f=>f.success+f.failure+f.cancelled)),m=Ze("svg",{viewBox:`0 0 ${s} ${u}`,width:"100%",height:String(u),class:"af-chart-bar"});for(let f=0;f<=4;f++){let g=d+l-f/4*l;m.appendChild(Ze("line",{x1:String(a),y1:String(g),x2:String(s-r),y2:String(g),stroke:"var(--af-text-faint)","stroke-opacity":"0.15","stroke-width":"1"}))}for(let f=0;f0&&m.appendChild(Ze("rect",{x:String(y),y:String(d+l-w),width:String(c),height:String(Math.max(w,2)),fill:"var(--af-green)",opacity:"0.85"})),g.cancelled>0&&m.appendChild(Ze("rect",{x:String(y),y:String(d+l-w-S),width:String(c),height:String(Math.max(S,2)),fill:"var(--af-yellow)",opacity:"0.85"})),g.failure>0&&m.appendChild(Ze("rect",{x:String(y),y:String(d+l-k),width:String(c),height:String(Math.max(T,2)),fill:"var(--af-red)",opacity:"0.85"})),v===0&&m.appendChild(Ze("rect",{x:String(y),y:String(d+l-3),width:String(c),height:"3",rx:"1.5",fill:"var(--af-text-faint)",opacity:"0.2"})),v>0){let D=Ze("text",{x:String(y+c/2),y:String(d+l-k-5),"text-anchor":"middle","font-size":"10","font-weight":"600",fill:"var(--af-text-secondary)"});D.textContent=String(v),m.appendChild(D)}let _=Ze("text",{x:String(y+c/2),y:String(d+l+16),"text-anchor":"middle","font-size":"10",fill:"var(--af-text-muted)"});_.textContent=vh(g.date),m.appendChild(_)}i.appendChild(m)}function zo(i,t,e){let c=2*Math.PI*46,l=e>0?t/e:0,h=c*l,d=c-h,u=Ze("svg",{viewBox:"0 0 130 130",width:String(130),height:String(130),class:"af-chart-donut"});u.appendChild(Ze("circle",{cx:String(65),cy:String(65),r:String(46),fill:"none",stroke:e>0?"var(--af-red)":"var(--af-text-faint)","stroke-width":String(12),opacity:"0.2"})),l>0&&u.appendChild(Ze("circle",{cx:String(65),cy:String(65),r:String(46),fill:"none",stroke:"var(--af-green)","stroke-width":String(12),"stroke-dasharray":`${h} ${d}`,"stroke-dashoffset":String(c*.25),"stroke-linecap":"round"}));let p=Ze("text",{x:String(65),y:String(61),"text-anchor":"middle","dominant-baseline":"middle","font-size":"24","font-weight":"700",fill:"var(--af-text-primary)"});p.textContent=`${Math.round(l*100)}%`,u.appendChild(p);let m=Ze("text",{x:String(65),y:String(83),"text-anchor":"middle","font-size":"10",fill:"var(--af-text-muted)"});m.textContent=`${t}/${e} runs`,u.appendChild(m),i.appendChild(u)}function Go(i,t){i.draggable=!0,i.addEventListener("dragstart",e=>{e.dataTransfer?.setData("text/plain",t),i.addClass("af-dragging")}),i.addEventListener("dragend",()=>{i.removeClass("af-dragging")})}function Vo(i,t){i.addEventListener("dragover",e=>{e.preventDefault(),i.addClass("af-drag-over")}),i.addEventListener("dragleave",e=>{let s=e.relatedTarget;(!s||!i.contains(s))&&i.removeClass("af-drag-over")}),i.addEventListener("drop",e=>{e.preventDefault();let s=e.dataTransfer?.getData("text/plain");i.removeClass("af-drag-over"),s&&t(s)})}function Yo(i){let t=/^##\s+Lint\s+(\d{4}-\d{2}-\d{2})\s*$/gm,e,s=-1,n="";for(;(e=t.exec(i))!==null;)e.index>s&&(s=e.index,n=e[1]??"");if(s<0)return null;let a=i.slice(s),r=a.search(/\n##\s+(?!\s*#)/),o=r<0?a:a.slice(0,r);return{date:n,summary:jn(o,"Summary"),autoApplied:jn(o,"Auto-applied"),needsReview:jn(o,"Needs review"),refreshChained:jn(o,"Refresh chained")}}function jn(i,t){let e=t.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),n=new RegExp(`^###\\s+${e}\\s*$`,"m").exec(i);if(!n)return[];let a=n.index+n[0].length,r=i.slice(a),o=r.search(/\n###\s+/),l=(o<0?r:r.slice(0,o)).split(/\r?\n/).map(d=>d.trimEnd()),h=[];for(let d of l){let u=d.replace(/^\s+/,"");u.startsWith("- ")?h.push(u.slice(2).trim()):h.length>0&&(d.startsWith(" ")||d.startsWith(" "))&&(h[h.length-1]+=" "+u)}return h}var Ko={dashboard:"Dashboard",agents:"Agents",kanban:"Tasks Board",runs:"Run History",skills:"Skills Library",approvals:"Approvals",mcp:"MCP Servers",channels:"Channels","wiki-keepers":"Wiki Keepers","agent-detail":"Agent Details","task-detail":"Task Details","create-agent":"Create Agent","create-task":"Create Task","create-skill":"Create Skill","edit-agent":"Edit Agent","edit-task":"Edit Task","edit-skill":"Edit Skill","create-channel":"Create Channel","edit-channel":"Edit Channel","add-mcp-server":"Add MCP Server"},wh={dashboard:"layout-dashboard",agents:"bot",kanban:"columns-3",runs:"scroll-text",skills:"puzzle",approvals:"shield-check",mcp:"plug",channels:"radio","wiki-keepers":"library","agent-detail":"bot","task-detail":"circle-dot","create-agent":"plus","create-task":"plus","create-skill":"plus","edit-agent":"edit","edit-task":"edit","edit-skill":"edit","create-channel":"plus","edit-channel":"edit","add-mcp-server":"plus"},bh=["dashboard","agents","kanban","runs","approvals","skills","wiki-keepers","mcp","channels"],Jo=[["claude-code","Claude Code",!1],["codex","Codex",!1],["process","Process (coming soon)",!0],["http","HTTP (coming soon)",!0]],Qo=[["bypassPermissions","Bypass Permissions","Auto-approve everything except deny list"],["dontAsk","Don\u2019t Ask","Auto-approve all tool calls"],["acceptEdits","Accept Edits","Auto-approve file edits, block bash unless allowed"],["plan","Plan","Read-only mode, no writes or commands"],["default","Default","Ask for each tool call"]],Zo=[["bypassPermissions","Bypass (no sandbox)","No sandbox, auto-approve everything"],["workspace-write","Workspace Write","Sandboxed: writes only inside the working dir"],["read-only","Read Only","Sandboxed: no writes or side-effect commands"]];function Ls(i){let t=i.trim().toLowerCase();return t==="codex"||t==="openai-codex"}function Wn(i){return Ls(i)?Zo:Qo}function Hn(i,t){if(Ls(t))switch(i){case"acceptEdits":case"default":return"workspace-write";case"plan":return"read-only";case"dontAsk":return"bypassPermissions";default:return Zo.some(([e])=>e===i)?i:"bypassPermissions"}switch(i){case"workspace-write":return"acceptEdits";case"read-only":return"plan";case"danger-full-access":return"bypassPermissions";default:return Qo.some(([e])=>e===i)?i:"bypassPermissions"}}var Fs=class extends b.ItemView{constructor(e,s){super(e);this.plugin=s}currentPage="dashboard";detailContext;agentDetailTab="overview";streamingUnsubscribes=[];channelStatusUnsubscribe;authenticatingServers=new Set;getViewType(){return _t}getDisplayText(){return"Agent Fleet"}getIcon(){return"bot"}async onOpen(){this.plugin.subscribeView(this),this.channelStatusUnsubscribe=this.plugin.channelManager?.onStatusChange(()=>{this.currentPage==="channels"&&this.render()}),await this.render()}async onClose(){this.cleanupStreaming(),this.channelStatusUnsubscribe?.(),this.channelStatusUnsubscribe=void 0,this.plugin.unsubscribeView(this)}navigateTo(e,s){this.currentPage=e,this.detailContext=s,e!=="agent-detail"&&(this.agentDetailTab="overview"),this.render()}async render(){this.cleanupStreaming();let e=this.contentEl;e.empty(),e.addClass("af-root");let n=e.createDiv({cls:"af-app"}).createDiv({cls:"af-main-content"});this.renderTopBar(n),this.renderTabBar(n);let a=n.createDiv({cls:"af-page"});switch(this.currentPage){case"dashboard":this.renderDashboardPage(a);break;case"agents":this.renderAgentsPage(a);break;case"kanban":this.renderKanbanPage(a);break;case"runs":this.renderRunsPage(a);break;case"skills":this.renderSkillsPage(a);break;case"approvals":this.renderApprovalsPage(a);break;case"mcp":this.renderMcpPage(a);break;case"channels":this.renderChannelsPage(a);break;case"wiki-keepers":this.renderWikiKeepersPage(a);break;case"agent-detail":this.renderAgentDetailPage(a);break;case"task-detail":this.renderTaskDetailPage(a);break;case"create-agent":this.renderCreateAgentPage(a);break;case"create-task":this.renderCreateTaskPage(a);break;case"create-skill":this.renderCreateSkillPage(a);break;case"edit-agent":this.renderEditAgentPage(a);break;case"edit-task":this.renderEditTaskPage(a);break;case"edit-skill":this.renderEditSkillPage(a);break;case"create-channel":this.renderCreateChannelPage(a);break;case"edit-channel":this.renderEditChannelPage(a);break;case"add-mcp-server":this.renderAddMcpServerPage(a);break}}navigate(e,s){this.navigateTo(e,s)}cleanupStreaming(){for(let e of this.streamingUnsubscribes)e();this.streamingUnsubscribes=[]}renderTopBar(e){let s=e.createDiv({cls:"af-top-bar"}),n=s.createDiv({cls:"af-top-bar-title"});R(n,"bot","af-top-bar-icon"),n.createSpan({text:"Agent Fleet"});let a=s.createDiv({cls:"af-breadcrumb"}),r=a.createSpan({cls:"af-breadcrumb-sep"});(0,b.setIcon)(r,"chevron-right");let o=(m,f,g)=>{let y=a.createSpan({cls:f?"af-breadcrumb-link":"",text:m});f&&(y.onclick=()=>this.navigate(f,g))},c=()=>{let m=a.createSpan({cls:"af-breadcrumb-sep"});(0,b.setIcon)(m,"chevron-right")};switch(this.currentPage){case"agent-detail":o("Agents","agents"),c(),o(this.detailContext??"Agent");break;case"task-detail":o("Tasks Board","kanban"),c(),o(this.detailContext??"Task");break;case"edit-agent":o("Agents","agents"),c(),o(this.detailContext??"Agent","agent-detail",this.detailContext),c(),o("Edit");break;case"edit-task":o("Tasks Board","kanban"),c(),o(this.detailContext??"Task","task-detail",this.detailContext),c(),o("Edit");break;case"create-agent":o("Agents","agents"),c(),o("New Agent");break;case"create-task":o("Tasks Board","kanban"),c(),o("New Task");break;case"create-skill":o("Skills Library","skills"),c(),o("New Skill");break;case"edit-skill":o("Skills Library","skills"),c(),o(this.detailContext??"Skill"),c(),o("Edit");break;case"create-channel":o("Channels","channels"),c(),o("New Channel");break;case"edit-channel":o("Channels","channels"),c(),o(this.detailContext??"Channel"),c(),o("Edit");break;case"add-mcp-server":o("MCP Servers","mcp"),c(),o("Add Server");break;default:o(Ko[this.currentPage])}s.createDiv({cls:"af-top-bar-spacer"});let l=s.createDiv({cls:"af-search-wrap"});R(l,"search","af-search-icon");let h=l.createEl("input",{cls:"af-search-input",attr:{type:"text",placeholder:"Search agents, tasks, runs..."}});h.addEventListener("input",()=>{this.handleSearch(h.value,l)}),h.addEventListener("blur",()=>{window.setTimeout(()=>l.querySelector(".af-search-results")?.remove(),200)});let d=this.plugin.runtime.getFleetStatus(),u=s.createDiv({cls:"af-status-pills"});if(d.running>0){let m=u.createSpan({cls:"af-pill yellow"});m.createSpan({cls:"af-dot pulse"}),m.appendText(` ${d.running} Running`)}if(d.pending>0){let m=u.createSpan({cls:"af-pill blue"});m.createSpan({cls:"af-dot"}),m.appendText(` ${d.pending} Pending`)}let p=u.createSpan({cls:"af-pill green"});p.createSpan({cls:"af-dot"}),p.appendText(` ${d.completedToday} Today`)}renderTabBar(e){let s=e.createDiv({cls:"af-tab-bar"}),n=this.plugin.runtime.getSnapshot(),a=this.plugin.runtime.getFleetStatus();for(let r of bh){let o=this.currentPage===r||r==="agents"&&(this.currentPage==="agent-detail"||this.currentPage==="edit-agent"||this.currentPage==="create-agent")||r==="kanban"&&(this.currentPage==="task-detail"||this.currentPage==="edit-task")||r==="skills"&&(this.currentPage==="edit-skill"||this.currentPage==="create-skill")||r==="mcp"&&this.currentPage==="add-mcp-server",c=s.createEl("button",{cls:`af-tab-item${o?" active":""}`}),l=c.createSpan({cls:"af-tab-icon"});if((0,b.setIcon)(l,wh[r]),c.appendText(r==="dashboard"?"Overview":Ko[r]),r==="agents")c.createSpan({cls:"af-badge",text:String(n.agents.length)});else if(r==="skills")c.createSpan({cls:"af-badge",text:String(n.skills.length)});else if(r==="mcp"){let h=this.plugin.repository.getMcpServers().length;c.createSpan({cls:"af-badge",text:String(h)})}else r==="approvals"&&a.pending>0&&c.createSpan({cls:"af-badge af-badge-warn",text:String(a.pending)});c.onclick=()=>this.navigate(r)}}renderDashboardPage(e){let s=e.createDiv({cls:"af-dashboard"}),n=this.plugin.runtime.getSnapshot(),a=this.plugin.runtime.getRecentRuns(),r=this.plugin.runtime.getFleetStatus(),o=a.filter(S=>(S.approvals??[]).some(T=>T.status==="pending"));for(let S of o)for(let T of S.approvals??[])T.status==="pending"&&this.renderApprovalBanner(s,S,T.tool);let c=s.createDiv({cls:"af-dash-grid"}),l=n.agents.filter(S=>S.enabled).length,h=n.agents.length;this.renderStatCard(c,"Active Agents",`${l}`,`/ ${h}`,"bot",`${l} of ${h} enabled`);let d=this.toLocalDateStr(new Date),u=a.filter(S=>this.runToLocalDate(S.started)===d),p=u.filter(S=>S.status==="success").length,m=u.filter(S=>S.status==="failure"||S.status==="timeout").length;this.renderStatCard(c,"Runs Today",String(u.length),"","activity",`${p} passed \xB7 ${m} failed \xB7 ${r.running} running`);let f=this.plugin.runtime.getUsageRecords().filter(S=>this.runToLocalDate(S.ts)===d),{tokens:g,cost:y}=this.combinedTotals(u,f),v=y>0?` \xB7 $${y.toFixed(2)}`:"";this.renderStatCard(c,"Tokens Used",Ka(g),"","zap",`today${v}`);let k=n.tasks.filter(S=>S.enabled&&S.schedule);this.renderStatCard(c,"Scheduled Tasks",String(k.length),"","clock",k.length>0?`Next: ${this.getNextTaskLabel(k)}`:"No scheduled tasks"),this.renderChartsRow(s,a,this.plugin.runtime.getChartRuns()),this.renderStreamingCards(s);let w=s.createDiv({cls:"af-dash-split"});this.renderActivityTimeline(w,a),this.renderFleetStatusPanel(w,n)}renderChartsRow(e,s,n){let a=e.createDiv({cls:"af-charts-row"}),r=a.createDiv({cls:"af-section-card af-chart-section"}),c=r.createDiv({cls:"af-section-header"}).createDiv({cls:"af-section-title"});R(c,"activity"),c.appendText(" Run Activity (14d)");let l=r.createDiv({cls:"af-chart-body"}),h=this.buildChartData(n,14);h.some(y=>y.success+y.failure+y.cancelled>0)?qo(l,h):this.renderEmptyState(l,"activity","No run data","Run agents to see activity");let d=a.createDiv({cls:"af-section-card af-chart-section"}),p=d.createDiv({cls:"af-section-header"}).createDiv({cls:"af-section-title"});R(p,"target"),p.appendText(" Success Rate");let m=d.createDiv({cls:"af-chart-body af-chart-body-center"}),f=s.length,g=s.filter(y=>y.status==="success").length;f>0?zo(m,g,f):this.renderEmptyState(m,"target","No data","")}toLocalDateStr(e){let s=n=>String(n).padStart(2,"0");return`${e.getFullYear()}-${s(e.getMonth()+1)}-${s(e.getDate())}`}runToLocalDate(e){return this.toLocalDateStr(new Date(e))}runCost(e){return e.costUsd??Wo(e.model,e.tokensUsed??0)}usageCost(e){return e.costUsd??jo(e.model,{inputTokens:e.inputTokens,outputTokens:e.outputTokens,cacheReadTokens:e.cacheReadTokens,cacheCreateTokens:e.cacheCreateTokens})}combinedTotals(e,s){let n=e.reduce((r,o)=>r+(o.tokensUsed??0),0)+s.reduce((r,o)=>r+o.totalTokens,0),a=e.reduce((r,o)=>r+this.runCost(o),0)+s.reduce((r,o)=>r+this.usageCost(o),0);return{tokens:n,cost:a}}buildChartData(e,s){let n=[],a=new Date;for(let r=s-1;r>=0;r--){let o=new Date(a);o.setDate(o.getDate()-r);let c=this.toLocalDateStr(o),l=e.filter(h=>this.runToLocalDate(h.started)===c);n.push({date:c,success:l.filter(h=>h.status==="success").length,failure:l.filter(h=>h.status==="failure"||h.status==="timeout").length,cancelled:l.filter(h=>h.status==="cancelled").length})}return n}renderStreamingCards(e){let n=this.plugin.runtime.getSnapshot().agents.filter(c=>this.plugin.runtime.getAgentState(c.name).status==="running");if(n.length===0)return;let a=e.createDiv({cls:"af-streaming-section"}),o=a.createDiv({cls:"af-section-header"}).createDiv({cls:"af-section-title"});R(o,"terminal"),o.appendText(" Active Agents");for(let c of n)this.renderStreamingCard(a,c.name)}renderStreamingCard(e,s){let n=e.createDiv({cls:"af-streaming-card"}),a=this.plugin.runtime.getAgentState(s),r=this.plugin.runtime.getSnapshot(),o=a.currentTaskId?r.tasks.find(m=>m.taskId===a.currentTaskId):void 0,c=o?` \u2192 ${o.taskId}`:a.currentTaskId?.startsWith("reflection-")?" \u2192 Reflection":a.currentTaskId?.startsWith("heartbeat-")||a.status==="running"?" \u2192 Heartbeat":"",l=n.createDiv({cls:"af-streaming-card-header"});l.createSpan({cls:"af-dot pulse",attr:{style:"background: var(--af-yellow)"}}),l.createSpan({cls:"af-streaming-card-agent",text:` ${s}`}),c&&l.createSpan({cls:"af-streaming-card-task",text:c});let h=n.createDiv({cls:"af-streaming-output"}),d=a.currentTaskId?.startsWith("reflection-")?"Consolidating memory\u2026":"Working\u2026",u=m=>{let f=ye(m).filter(g=>g.trim().length>0).slice(-4);h.setText(f.length>0?f.join(` +`):d)};u(this.plugin.runtime.getRunOutputBuffer(s));let p=this.plugin.runtime.onRunOutput(s,()=>{u(this.plugin.runtime.getRunOutputBuffer(s)),h.scrollTop=h.scrollHeight});this.streamingUnsubscribes.push(p)}renderApprovalBanner(e,s,n){let a=e.createDiv({cls:"af-approval-banner"}),r=a.createDiv({cls:"af-approval-icon"});(0,b.setIcon)(r,"shield-check");let o=a.createDiv({cls:"af-approval-text"});o.createDiv({cls:"af-approval-title",text:`${s.agent} wants to run: ${n}`}),o.createDiv({cls:"af-approval-desc",text:`Approval required for tool: ${n}`});let c=a.createDiv({cls:"af-approval-actions"}),l=c.createEl("button",{cls:"af-btn-approve",text:"Approve"});l.onclick=()=>void this.plugin.runtime.resolveApproval(s,n,"approved").then(()=>this.render());let h=c.createEl("button",{cls:"af-btn-reject",text:"Reject"});h.onclick=()=>void this.plugin.runtime.resolveApproval(s,n,"rejected").then(()=>this.render())}renderStatCard(e,s,n,a,r,o){let c=e.createDiv({cls:"af-stat-card"}),l=c.createDiv({cls:"af-stat-label"});R(l,r,"af-stat-icon"),l.appendText(` ${s}`);let h=c.createDiv({cls:"af-stat-value"});h.appendText(n),a&&h.createSpan({cls:"af-stat-value-suffix",text:a}),c.createDiv({cls:"af-stat-sub",text:o})}renderActivityTimeline(e,s){let n=e.createDiv({cls:"af-section-card"}),r=n.createDiv({cls:"af-section-header"}).createDiv({cls:"af-section-title"});R(r,"inbox"),r.appendText(" Recent Activity");let o=n.createDiv({cls:"af-timeline"}),c=s.slice(0,8);if(c.length===0){this.renderEmptyState(o,"inbox","No runs yet","Run an agent to see activity here");return}for(let l of c)this.renderTimelineItem(o,l)}renderTimelineItem(e,s){let n=e.createDiv({cls:"af-timeline-item"}),a=this.statusToTimelineClass(s.status),r=n.createDiv({cls:`af-tl-icon ${a}`});(0,b.setIcon)(r,this.statusToIconName(s.status));let o=n.createDiv({cls:"af-tl-body"}),c=o.createDiv({cls:"af-tl-title"});c.createSpan({cls:"af-agent-tag",text:s.agent}),c.appendText(` ${s.task}`),o.createDiv({cls:"af-tl-desc",text:jt(s.output,100)});let l=o.createDiv({cls:"af-tl-meta"}),h=l.createSpan();if(R(h,"clock","af-meta-icon"),h.appendText(` ${this.formatStarted(s.started)} \xB7 ${this.formatDuration(s.durationSeconds)}`),s.tokensUsed){let d=l.createSpan();R(d,"zap","af-meta-icon"),d.appendText(` ${s.tokensUsed.toLocaleString()} tokens`)}n.onclick=()=>this.openSlideover(s)}renderFleetStatusPanel(e,s){let n=e.createDiv({cls:"af-section-card"}),a=n.createDiv({cls:"af-section-header"}),r=a.createDiv({cls:"af-section-title"});R(r,"bot"),r.appendText(" Fleet Status");let c=a.createDiv({cls:"af-section-actions"}).createEl("button",{cls:"af-btn-sm primary"});R(c,"plus","af-btn-icon"),c.appendText(" New Agent"),c.onclick=()=>void this.plugin.createAgentTemplate();let l=n.createDiv({cls:"af-agent-mini-list"});if(s.agents.length===0){this.renderEmptyState(l,"bot","No agents yet","Create your first agent to get started");return}for(let d of s.agents)this.renderAgentMini(l,d,s.tasks);let h=s.agents.filter(d=>d.enabled);if(h.length>0){let d=n.createDiv({cls:"af-quick-run"}),u=d.createDiv({cls:"af-quick-run-label"});R(u,"zap","af-meta-icon"),u.appendText(" Quick Run");let p=d.createDiv({cls:"af-quick-run-row"}),m=p.createEl("select",{cls:"af-select"});for(let g of h)m.createEl("option",{text:g.name,attr:{value:g.name}});let f=p.createEl("button",{cls:"af-btn-sm primary"});R(f,"play","af-btn-icon"),f.appendText(" Run"),f.onclick=()=>void this.plugin.runAgentPrompt(m.value)}}renderAgentMini(e,s,n){let a=this.plugin.runtime.getAgentState(s.name),r=n.filter(u=>u.agent===s.name),o=this.healthToClass(a.status),c=e.createDiv({cls:"af-agent-mini"}),l=c.createDiv({cls:`af-agent-avatar ${o}`});s.avatar?.trim()?this.renderAgentAvatar(l,s):l.setText(this.getInitials(s.name));let h=c.createDiv({cls:"af-agent-info"});h.createDiv({cls:"af-agent-name",text:s.name});let d="";if(a.status==="running")d=`Running now \xB7 ${r.length} task${r.length!==1?"s":""}`;else if(!s.enabled)d=`Disabled \xB7 ${r.length} task${r.length!==1?"s":""} paused`;else{let u=r.map(p=>p.nextRun).filter(Boolean).sort()[0];d=u?`Next: ${this.formatNextRun(u)} \xB7 ${r.length} task${r.length!==1?"s":""}`:`${r.length} task${r.length!==1?"s":""}`}h.createDiv({cls:"af-agent-desc",text:d}),c.createDiv({cls:`af-agent-status-dot ${o}`}),c.onclick=()=>this.navigate("agent-detail",s.name)}renderAgentsPage(e){let s=e.createDiv({cls:"af-agents-page"}),n=this.plugin.runtime.getSnapshot(),a=s.createDiv({cls:"af-agents-toolbar"});a.createDiv({cls:"af-page-title",text:"Agents"}),a.createDiv({cls:"af-toolbar-spacer"});let r=a.createEl("button",{cls:"af-btn-sm primary"});R(r,"plus","af-btn-icon"),r.appendText(" New Agent"),r.onclick=()=>void this.plugin.createAgentTemplate();let o=s.createDiv({cls:"af-agents-grid"});if(n.agents.length===0){this.renderEmptyState(o,"bot","No agents configured","Create your first agent to get started");return}for(let c of n.agents)this.renderAgentCard(o,c,n)}renderAgentCard(e,s,n){let a=this.plugin.runtime.getAgentState(s.name),r=this.plugin.runtime.getRecentRuns().filter(D=>D.agent===s.name),o=e.createDiv({cls:`af-agent-card${s.enabled?"":" disabled"}`}),c=o.createDiv({cls:"af-agent-card-header"}),l=s.enabled?this.healthToClass(a.status):"disabled",h=c.createDiv({cls:`af-agent-card-avatar ${l}`});this.renderAgentAvatar(h,s);let d=c.createDiv({cls:"af-agent-card-titleblock"}),u=d.createDiv({cls:"af-agent-card-name"});if(u.appendText(s.name),s.heartbeatEnabled&&s.heartbeatSchedule){let D=u.createSpan({cls:"af-heartbeat-indicator"});(0,b.setIcon)(D,"heart-pulse"),D.title=`Heartbeat: ${s.heartbeatSchedule}`}d.createDiv({cls:"af-agent-card-desc",text:s.description??"No description"});let p=c.createDiv({cls:`af-agent-card-toggle${s.enabled?" on":""}`});p.onclick=D=>{D.stopPropagation(),this.plugin.toggleAgent(s.name,!s.enabled)};let m=o.createDiv({cls:"af-agent-card-stats"}),f=r.length,g=r.filter(D=>D.status==="success").length,y=f>0?Math.round(g/f*100):0,v=f>0?Math.round(r.reduce((D,O)=>D+O.durationSeconds,0)/f):0,k=r.reduce((D,O)=>D+(O.tokensUsed??0),0);if(this.renderAgentStat(m,String(f),"Runs"),this.renderAgentStat(m,`${y}%`,"Success"),this.renderAgentStat(m,`${v}s`,"Avg Time"),this.renderAgentStat(m,Ka(k),"Tokens"),s.skills.length>0){let D=o.createDiv({cls:"af-agent-card-skills"});for(let O of s.skills)D.createSpan({cls:"af-skill-tag",text:O})}let w=o.createDiv({cls:"af-agent-card-footer"}),S=[`Model: ${s.model}`];s.approvalRequired.length>0&&S.push(`Approval: ${s.approvalRequired.join(", ")}`),s.memory&&S.push("Memory: on"),s.enabled||S.unshift("Disabled"),w.createSpan({cls:"af-agent-card-meta",text:S.join(" \xB7 ")});let T=w.createDiv({cls:"af-agent-card-actions"});if(!s.enabled){let D=T.createEl("button",{cls:"af-btn-sm",text:"Enable"});D.onclick=O=>{O.stopPropagation(),this.plugin.toggleAgent(s.name,!0)}}let _=T.createEl("button",{cls:"af-btn-sm"});if(R(_,"edit","af-btn-icon"),_.appendText(" Edit"),_.onclick=D=>{D.stopPropagation(),this.navigate("edit-agent",s.name)},s.enabled){let D=T.createEl("button",{cls:"af-btn-sm primary"});R(D,"play","af-btn-icon"),D.appendText(" Run"),D.onclick=O=>{O.stopPropagation(),this.plugin.runAgentPrompt(s.name)}}o.onclick=()=>this.navigate("agent-detail",s.name)}renderAgentStat(e,s,n){let a=e.createDiv({cls:"af-agent-stat"});a.createSpan({cls:"af-agent-stat-value",text:s}),a.createSpan({cls:"af-agent-stat-label",text:n})}renderAgentDetailPage(e){let s=e.createDiv({cls:"af-agent-detail-page"}),n=this.detailContext;if(!n){this.renderEmptyState(s,"bot","No agent selected","Select an agent from the list");return}let a=this.plugin.runtime.getSnapshot().agents.find(k=>k.name===n);if(!a){this.renderEmptyState(s,"bot","Agent not found",`Agent "${n}" was not found`);return}let r=this.plugin.runtime.getAgentState(a.name),o=this.plugin.runtime.getRecentRuns().filter(k=>k.agent===a.name),c=s.createDiv({cls:"af-detail-header"}),l=c.createDiv({cls:"af-detail-header-left"}),h=l.createDiv({cls:`af-agent-card-avatar ${this.healthToClass(r.status)}`});this.renderAgentAvatar(h,a);let d=l.createDiv();d.createDiv({cls:"af-detail-header-name",text:a.name}),d.createDiv({cls:"af-detail-header-desc",text:a.description??"No description"});let u=c.createDiv({cls:"af-detail-header-actions"}),p=u.createEl("button",{cls:"af-btn-sm primary"});if(R(p,"message-circle","af-btn-icon"),p.appendText(" Chat"),p.onclick=()=>this.openChatSlideover(a),a.enabled){let k=u.createEl("button",{cls:"af-btn-sm"});R(k,"play","af-btn-icon"),k.appendText(" Run Now"),k.onclick=()=>void this.plugin.runAgentPrompt(a.name);let w=u.createEl("button",{cls:"af-btn-sm"});R(w,"pause","af-btn-icon"),w.appendText(" Disable"),w.onclick=()=>void this.plugin.toggleAgent(a.name,!1)}else{let k=u.createEl("button",{cls:"af-btn-sm"});R(k,"play","af-btn-icon"),k.appendText(" Enable"),k.onclick=()=>void this.plugin.toggleAgent(a.name,!0)}let m=u.createEl("button",{cls:"af-btn-sm"});R(m,"edit","af-btn-icon"),m.appendText(" Edit"),m.onclick=()=>this.navigate("edit-agent",a.name);let f=u.createEl("button",{cls:"af-btn-sm danger"});R(f,"trash-2","af-btn-icon"),f.appendText(" Delete"),f.onclick=()=>void this.plugin.deleteAgent(a.name);let g=s.createDiv({cls:"af-detail-tabs"}),y=[{id:"overview",label:"Overview",icon:"layout-dashboard"},{id:"config",label:"Config",icon:"settings"},{id:"runs",label:"Runs",icon:"scroll-text"},{id:"memory",label:"Memory",icon:"file-text"}];for(let k of y){let w=g.createEl("button",{cls:`af-detail-tab${this.agentDetailTab===k.id?" active":""}`});R(w,k.icon,"af-tab-icon"),w.appendText(` ${k.label}`),w.onclick=()=>{this.agentDetailTab=k.id,this.render()}}let v=s.createDiv({cls:"af-detail-tab-content"});switch(this.agentDetailTab){case"overview":this.renderAgentOverviewTab(v,a,o);break;case"config":this.renderAgentConfigTab(v,a);break;case"runs":this.renderAgentRunsTab(v,o);break;case"memory":this.renderAgentMemoryTab(v,a);break}}renderAgentOverviewTab(e,s,n){let a=e.createDiv({cls:"af-dash-grid"}),r=n.length,o=n.filter(w=>w.status==="success").length,c=r>0?Math.round(o/r*100):0,l=r>0?Math.round(n.reduce((w,S)=>w+S.durationSeconds,0)/r):0,h=this.plugin.runtime.getUsageRecords().filter(w=>w.agent===s.name),{tokens:d,cost:u}=this.combinedTotals(n,h),p=u>0?` \xB7 $${u.toFixed(2)}`:"";if(this.renderStatCard(a,"Total Runs",String(r),"","activity","all time"),this.renderStatCard(a,"Success Rate",`${c}%`,"","check-circle-2",`${o}/${r}`),this.renderStatCard(a,"Avg Time",`${l}s`,"","clock","per run"),this.renderStatCard(a,"Total Tokens",Ka(d),"","zap",`all time${p}`),s.isFolder&&(s.heartbeatBody.trim()||s.heartbeatEnabled)){let w=e.createDiv({cls:"af-section-card"}),S=w.createDiv({cls:"af-section-header"}),T=S.createDiv({cls:"af-section-title"});R(T,"heart-pulse"),T.appendText(" Heartbeat");let D=S.createDiv({cls:"af-detail-header-actions"}).createDiv({cls:`af-agent-card-toggle${s.heartbeatEnabled?" on":""}`});D.onclick=async()=>{let E=D.hasClass("on");await this.plugin.repository.updateHeartbeat(s.name,{enabled:!E}),await this.plugin.refreshFromVault(),new b.Notice(`Heartbeat ${E?"paused":"enabled"} for ${s.name}`)};let O=w.createDiv({cls:"af-config-form"});this.renderConfigRow(O,"Schedule",kh(s.heartbeatSchedule));let C=this.plugin.runtime.getNextHeartbeat(s.name);C&&s.heartbeatEnabled&&this.renderConfigRow(O,"Next run",this.timeUntil(C)),s.heartbeatChannel&&this.renderConfigRow(O,"Channel",s.heartbeatChannel)}if(s.skills.length>0){let w=e.createDiv({cls:"af-section-card"}),T=w.createDiv({cls:"af-section-header"}).createDiv({cls:"af-section-title"});R(T,"puzzle"),T.appendText(" Skills");let _=w.createDiv({cls:"af-detail-skills-list"});for(let D of s.skills)_.createSpan({cls:"af-skill-tag",text:D})}let m=s.mcpServers??[];if(m.length>0){let w=e.createDiv({cls:"af-section-card"}),T=w.createDiv({cls:"af-section-header"}).createDiv({cls:"af-section-title"});R(T,"plug"),T.appendText(" MCP Servers");let _=w.createDiv({cls:"af-mcp-overview-list"}),D=this.plugin.repository.getMcpServers();for(let O of m){let C=D.find(M=>M.name===O),E=_.createDiv({cls:"af-mcp-overview-row"}),P=E.createSpan({cls:`af-mcp-status-dot ${C?C.enabled?C.status:"disabled":"disconnected"}`});P.title=C?C.enabled?C.status:"disabled":"unknown",E.createSpan({cls:"af-mcp-overview-name",text:O});let N=C?.toolDetails.length??C?.tools.length??0;N>0?E.createSpan({cls:"af-mcp-overview-tools",text:`${N} tools`}):C&&!C.enabled?E.createSpan({cls:"af-mcp-overview-tools af-muted",text:"disabled"}):C?.status==="needs-auth"&&E.createSpan({cls:"af-mcp-overview-tools af-muted",text:"needs auth"})}}if(s.permissionRules.allow.length>0||s.permissionRules.deny.length>0||s.permissionMode&&s.permissionMode!=="default"){let w=e.createDiv({cls:"af-section-card"}),T=w.createDiv({cls:"af-section-header"}).createDiv({cls:"af-section-title"});R(T,"shield-check"),T.appendText(" Permissions");let _=w.createDiv({cls:"af-config-form"});this.renderConfigRow(_,"Mode",s.permissionMode||"default"),s.permissionRules.allow.length>0&&this.renderConfigRow(_,"Allowed",s.permissionRules.allow.join(", ")),s.permissionRules.deny.length>0&&this.renderConfigRow(_,"Denied",s.permissionRules.deny.join(", "))}let g=e.createDiv({cls:"af-section-card"}),v=g.createDiv({cls:"af-section-header"}).createDiv({cls:"af-section-title"});R(v,"scroll-text"),v.appendText(" Recent Runs");let k=g.createDiv({cls:"af-timeline"});if(n.length===0)this.renderEmptyState(k,"scroll-text","No runs yet","");else for(let w of n.slice(0,5))this.renderTimelineItem(k,w)}renderAgentConfigTab(e,s){let n=e.createDiv({cls:"af-config-form"});this.renderConfigRow(n,"Name",s.name),this.renderConfigRow(n,"Description",s.description??""),this.renderConfigRow(n,"Model",s.model),this.renderConfigRow(n,"Timeout",`${s.timeout}s`),this.renderConfigRow(n,"Working Directory",s.cwd??"(vault root)"),this.renderConfigRow(n,"Permission Mode",s.permissionMode||"default"),this.renderConfigRow(n,"Approval Required",s.approvalRequired.join(", ")||"none"),s.permissionRules.allow.length>0&&this.renderConfigRow(n,"Allowed Commands",s.permissionRules.allow.join(", ")),s.permissionRules.deny.length>0&&this.renderConfigRow(n,"Blocked Commands",s.permissionRules.deny.join(", ")),this.renderConfigRow(n,"Memory",s.memory?"Enabled":"Disabled"),this.renderConfigRow(n,"Auto-compact",s.autoCompactThreshold&&s.autoCompactThreshold>0?`at ${s.autoCompactThreshold}% context`:"disabled"),s.wikiReferences&&s.wikiReferences.length>0&&this.renderConfigRow(n,"Wiki access",s.wikiReferences.map(c=>c.agent).join(", ")),this.renderConfigRow(n,"Tags",s.tags.join(", ")||"none");let a=n.createDiv({cls:"af-config-prompt-section"});if(a.createDiv({cls:"af-slideover-section-title",text:"SYSTEM PROMPT"}),a.createDiv({cls:"af-output-block",text:s.body||"(empty)"}),s.heartbeatBody.trim()){let c=n.createDiv({cls:"af-config-prompt-section"});c.createDiv({cls:"af-slideover-section-title",text:"HEARTBEAT INSTRUCTION"}),c.createDiv({cls:"af-output-block",text:s.heartbeatBody})}let o=n.createDiv({cls:"af-slideover-actions"}).createEl("button",{cls:"af-btn-sm primary"});R(o,"edit","af-btn-icon"),o.appendText(" Edit Agent"),o.onclick=()=>this.navigate("edit-agent",s.name)}renderConfigRow(e,s,n){let a=e.createDiv({cls:"af-detail-row"});a.createSpan({cls:"af-detail-label",text:s}),a.createSpan({cls:"af-detail-value af-mono",text:n})}renderAgentRunsTab(e,s){if(s.length===0){this.renderEmptyState(e,"scroll-text","No runs yet","Run this agent to see history");return}for(let n of s){let a=e.createDiv({cls:"af-run-list-item"}),r=a.createDiv({cls:`af-tl-icon ${this.statusToTimelineClass(n.status)}`});(0,b.setIcon)(r,this.statusToIconName(n.status));let o=a.createDiv({cls:"af-tl-body"}),c=o.createDiv({cls:"af-tl-title"});c.createSpan({text:n.task}),c.createSpan({cls:`af-status-badge ${this.statusToBadgeClass(n.status)}`,text:this.statusToBadgeText(n.status)});let l=o.createDiv({cls:"af-tl-meta"});l.createSpan({text:`${this.formatStarted(n.started)} \xB7 ${this.formatDuration(n.durationSeconds)}`}),n.tokensUsed&&l.createSpan({text:`${n.tokensUsed.toLocaleString()} tokens`}),o.createDiv({cls:"af-tl-desc",text:jt(n.output,120)}),a.onclick=()=>this.openSlideover(n)}}async renderAgentMemoryTab(e,s){if(!s.memory){this.renderEmptyState(e,"file-text","Memory disabled","Enable memory in agent config");return}let n=await this.plugin.repository.readWorkingMemory(s.name),a=e.createDiv({cls:"af-form-help"}),r=n?.tokenEstimate??0,o=s.reflection.enabled?`reflection on (${s.reflection.schedule})`:"reflection off";a.setText(`~${r} / ${s.memoryTokenBudget} tokens \xB7 ${o}`),!n||n.sections.length===0?this.renderEmptyState(e,"file-text","No memories yet","Agent will learn from runs"):e.createDiv({cls:"af-output-block"}).setText(Ke(n.sections));let c=e.createDiv({cls:"af-slideover-actions"}),l=c.createEl("button",{cls:"af-btn-sm"});R(l,"moon","af-btn-icon"),l.appendText(" Reflect now"),l.onclick=async()=>{l.disabled=!0,l.setText(" Reflecting\u2026");let d=await this.plugin.runtime.runReflectionNow(s.name);new b.Notice(`Agent Fleet: ${d.message}`),await this.renderAgentMemoryTab((e.empty(),e),s)};let h=c.createEl("button",{cls:"af-btn-sm"});R(h,"external-link","af-btn-icon"),h.appendText(" Open in Editor"),h.onclick=()=>void this.plugin.openPath(this.plugin.repository.getWorkingMemoryPath(s.name))}timeAgo(e){let s=Math.round((Date.now()-e.getTime())/1e3);if(s<60)return"just now";let n=Math.round(s/60);if(n<60)return`${n}m ago`;let a=Math.round(n/60);return a<24?`${a}h ago`:`${Math.round(a/24)}d ago`}timeUntil(e){let s=Math.round((e.getTime()-Date.now())/1e3);if(s<60)return"< 1m";let n=Math.round(s/60);if(n<60)return`in ${n}m`;let a=Math.round(n/60);return a<24?`in ${a}h`:`in ${Math.round(a/24)}d`}renderKanbanPage(e){let s=e.createDiv({cls:"af-kanban-page"}),n=this.plugin.runtime.getSnapshot(),a=this.plugin.runtime.getRecentRuns(),r=s.createDiv({cls:"af-kanban-toolbar"});r.createDiv({cls:"af-page-title",text:"Tasks Board"}),r.createDiv({cls:"af-toolbar-spacer"});let o=r.createEl("button",{cls:"af-btn-sm primary"});R(o,"plus","af-btn-icon"),o.appendText(" New Task"),o.onclick=()=>this.navigate("create-task");let c=s.createDiv({cls:"af-kanban-board"}),l=[],h=[],d=[],u=[],p=[],m=new Set;for(let v of n.agents){let k=this.plugin.runtime.getAgentState(v.name);k.status==="running"&&k.currentTaskId&&m.add(k.currentTaskId)}let f=this.toLocalDateStr(new Date);for(let v of a)v.status==="success"&&this.runToLocalDate(v.started)===f?u.push(v):(v.status==="failure"||v.status==="timeout"||v.status==="cancelled")&&this.runToLocalDate(v.started)===f&&p.push(v);let g=new Set(u.map(v=>v.task)),y=new Set(p.map(v=>v.task));for(let v of n.tasks){let k=g.has(v.taskId)||y.has(v.taskId)||v.lastRun&&this.runToLocalDate(v.lastRun)===f;if(m.has(v.taskId))d.push(v);else{if(k&&!v.schedule)continue;v.schedule&&v.enabled?h.push(v):l.push(v)}}this.renderKanbanColumn(c,"Backlog","inbox",l.length,v=>{for(let k of l)this.renderKanbanTaskCard(v,k,n,!0)},"backlog"),this.renderKanbanColumn(c,"Scheduled","clock",h.length,v=>{for(let k of h)this.renderKanbanTaskCard(v,k,n,!0)},"scheduled"),this.renderKanbanColumn(c,"Running","loader-2",d.length,v=>{for(let k of d)this.renderKanbanRunningCard(v,k)},"running",!1,"running"),this.renderKanbanColumn(c,"Done","check-circle-2",u.length,v=>{for(let k of u.slice(0,10))this.renderKanbanCompletedCard(v,k)},"completed"),this.renderKanbanColumn(c,"Failed","x-circle",p.length,v=>{for(let k of p)this.renderKanbanFailedCard(v,k)},"failed",!1,"failed")}renderKanbanColumn(e,s,n,a,r,o,c=!1,l){let h=e.createDiv({cls:`af-kanban-column${l?` af-kanban-${l}`:""}`});(o==="backlog"||o==="scheduled")&&Vo(h,m=>this.handleTaskDrop(m,o));let u=h.createDiv({cls:"af-kanban-col-header"}).createDiv({cls:"af-kanban-col-title"});R(u,n),u.appendText(` ${s} `),u.createSpan({cls:"af-kanban-col-count",text:String(a)});let p=h.createDiv({cls:"af-kanban-col-body"});if(r(p),a===0&&p.createDiv({cls:"af-kanban-empty",text:"No items"}),c){let f=h.createDiv({cls:"af-kanban-col-add"}).createEl("button");R(f,"plus","af-btn-icon"),f.appendText(" Add task"),f.onclick=()=>{this.navigate("create-task")}}}handleTaskDrop(e,s){let n=this.plugin.runtime.getSnapshot().tasks.find(a=>a.taskId===e);if(n){if(s==="backlog")this.setTaskEnabled(n,!1).then(()=>{new b.Notice(`Task "${e}" moved to backlog (disabled)`)});else if(s==="scheduled"){if(!n.schedule&&!n.runAt){new b.Notice(`Task "${e}" needs a schedule. Open task details to set one.`),this.navigate("task-detail",e);return}this.setTaskEnabled(n,!0).then(()=>{new b.Notice(`Task "${e}" moved to scheduled (enabled)`)})}}}async setTaskEnabled(e,s){let n=this.plugin.app.vault.getAbstractFileByPath(e.filePath);if(!n||!(n instanceof b.TFile))return;let a=await this.plugin.app.vault.cachedRead(n),{frontmatter:r,body:o}=J(a);r.enabled=s,await this.plugin.app.vault.modify(n,H(r,o)),await this.plugin.refreshFromVault()}renderKanbanTaskCard(e,s,n,a){let r=e.createDiv({cls:`af-kanban-card af-priority-${s.priority}`});if(a){Go(r,s.taskId);let f=r.createDiv({cls:"af-kanban-card-grip"});(0,b.setIcon)(f,"grip-vertical")}let o=r.createDiv({cls:"af-kanban-card-header"});o.createDiv({cls:"af-kanban-card-title",text:s.taskId});let l=(n.agents.find(f=>f.name===s.agent)?.enabled??!1)&&s.enabled,h=o.createSpan({cls:`af-kanban-card-status ${l?"active":"inactive"}`});h.title=l?"Active":"Inactive";let d=r.createDiv({cls:"af-kanban-card-agent"}),u=d.createSpan({cls:"af-kanban-card-agent-icon"});(0,b.setIcon)(u,"bot"),d.createSpan({cls:"af-kanban-card-agent-name",text:s.agent});let m=r.createDiv({cls:"af-kanban-card-footer"}).createSpan({cls:"af-kanban-card-schedule"});l?s.schedule?(R(m,"refresh-cw","af-meta-icon"),m.appendText(` ${this.humanizeCron(s.schedule)}`)):m.appendText(s.runAt??"Manual"):(R(m,"pause","af-meta-icon"),m.appendText(" Paused")),r.onclick=()=>this.navigate("task-detail",s.taskId)}renderKanbanRunningCard(e,s){let n=e.createDiv({cls:"af-kanban-card af-kanban-card-running"});n.createDiv({cls:"af-kanban-card-title",text:s.taskId});let a=n.createDiv({cls:"af-kanban-card-agent"}),r=a.createSpan({cls:"af-kanban-card-agent-icon"});(0,b.setIcon)(r,"bot"),a.createSpan({cls:"af-kanban-card-agent-name",text:s.agent});let c=this.plugin.runtime.getSnapshot().agents.find(S=>S.name===s.agent)?.timeout??300,l=this.plugin.runtime.getAgentState(s.agent),h=l.runStarted?new Date(l.runStarted).getTime():Date.now(),p=n.createDiv({cls:"af-kanban-progress"}).createDiv({cls:"af-kanban-progress-track"}).createDiv({cls:"af-kanban-progress-bar af-kanban-progress-bar-real"}),m=(Date.now()-h)/1e3,f=Math.min(95,m/c*100);p.setCssStyles({width:`${f}%`});let g=n.createDiv({cls:"af-kanban-card-footer"}),y=g.createSpan({cls:"af-kanban-card-schedule"});R(y,"loader-2","af-meta-icon");let v=Math.round(m);y.appendText(` ${v}s / ${c}s`);let k=g.createEl("button",{cls:"af-kanban-stop-btn"});(0,b.setIcon)(k,"square"),k.title="Stop task",k.onclick=S=>{S.stopPropagation(),this.plugin.runtime.abortAgentRun(s.agent),new b.Notice(`Stopped task "${s.taskId}"`)};let w=window.setInterval(()=>{let S=(Date.now()-h)/1e3,T=Math.min(95,S/c*100);p.setCssStyles({width:`${T}%`});let _=Math.round(S);y.textContent="",(0,b.setIcon)(y,"loader-2"),y.appendText(` ${_}s / ${c}s`)},1e3);this.streamingUnsubscribes.push(()=>window.clearInterval(w))}renderKanbanCompletedCard(e,s){let n=e.createDiv({cls:"af-kanban-card"});n.createDiv({cls:"af-kanban-card-title",text:s.task});let a=n.createDiv({cls:"af-kanban-card-agent"}),r=a.createSpan({cls:"af-kanban-card-agent-icon"});(0,b.setIcon)(r,"bot"),a.createSpan({cls:"af-kanban-card-agent-name",text:s.agent});let c=n.createDiv({cls:"af-kanban-card-footer"}).createSpan({cls:"af-kanban-card-schedule"});R(c,"check-circle-2","af-meta-icon"),c.appendText(` ${this.formatStarted(s.started)} \xB7 ${this.formatDuration(s.durationSeconds)}`),n.onclick=()=>this.openSlideover(s)}renderKanbanFailedCard(e,s){let n=s.status==="cancelled",a=e.createDiv({cls:`af-kanban-card ${n?"af-kanban-card-cancelled":"af-kanban-card-failed"}`});a.createDiv({cls:"af-kanban-card-title",text:s.task});let r=a.createDiv({cls:"af-kanban-card-agent"}),o=r.createSpan({cls:"af-kanban-card-agent-icon"});(0,b.setIcon)(o,"bot"),r.createSpan({cls:"af-kanban-card-agent-name",text:s.agent});let c=n?`Stopped after ${s.durationSeconds}s`:s.status==="timeout"?`Timeout after ${s.durationSeconds}s`:jt(s.output,60),l=a.createDiv({cls:"af-kanban-card-error"});R(l,n?"square":"alert-triangle","af-meta-icon"),l.appendText(` ${c}`);let h=a.createDiv({cls:"af-kanban-card-footer"}),d=h.createSpan({cls:"af-kanban-card-schedule"});if(R(d,n?"square":"x-circle","af-meta-icon"),d.appendText(` ${this.formatStarted(s.started)}`),!n){let u=h.createEl("button",{cls:"af-btn-sm"});R(u,"refresh-cw","af-btn-icon"),u.appendText(" Retry"),u.onclick=p=>{p.stopPropagation(),this.plugin.runAgentPrompt(s.agent)}}a.onclick=()=>this.openSlideover(s)}renderRunsPage(e){let s=e.createDiv({cls:"af-runs-page"}),n=this.plugin.runtime.getRecentRuns(),a=s.createDiv({cls:"af-runs-toolbar"});a.createDiv({cls:"af-page-title",text:"Run History"}),a.createDiv({cls:"af-toolbar-spacer"});let r=s.createDiv({cls:"af-runs-table"});if(n.length===0){this.renderEmptyState(r,"scroll-text","No runs yet","Run an agent to see history here");return}let o=r.createEl("table"),l=o.createEl("thead").createEl("tr");for(let d of["Status","Agent","Task","Started","Duration","Tokens","Model"])l.createEl("th",{text:d});let h=o.createEl("tbody");for(let d of n.slice(0,50))this.renderRunRow(h,d)}renderRunRow(e,s){let n=e.createEl("tr"),r=n.createEl("td").createSpan({cls:`af-status-badge ${this.statusToBadgeClass(s.status)}`}),o=r.createSpan();(0,b.setIcon)(o,this.statusToIconName(s.status)),r.appendText(` ${this.statusToBadgeText(s.status)}`);let c=n.createEl("td",{cls:"af-agent-link"});c.setText(s.agent),c.onclick=l=>{l.stopPropagation(),this.navigate("agent-detail",s.agent)},n.createEl("td",{text:s.task}),n.createEl("td",{cls:"af-mono",text:this.formatStarted(s.started)}),n.createEl("td",{cls:"af-mono",text:this.formatDuration(s.durationSeconds)}),n.createEl("td",{cls:"af-mono",text:s.tokensUsed?s.tokensUsed.toLocaleString():"\u2014"}),n.createEl("td",{cls:"af-mono",text:s.model}),n.setCssStyles({cursor:"pointer"}),n.onclick=()=>this.openSlideover(s)}renderSkillsPage(e){let s=e.createDiv({cls:"af-skills-page"}),n=this.plugin.runtime.getSnapshot(),a=s.createDiv({cls:"af-agents-toolbar"});a.createDiv({cls:"af-page-title",text:"Skills Library"}),a.createDiv({cls:"af-toolbar-spacer"});let r=a.createEl("button",{cls:"af-btn-sm primary"});R(r,"plus","af-btn-icon"),r.appendText(" New Skill"),r.onclick=()=>void this.plugin.createSkillTemplate();let o=s.createDiv({cls:"af-skills-grid"});if(n.skills.length===0){this.renderEmptyState(o,"puzzle","No skills yet","Create skills to give agents specialized abilities");return}for(let c of n.skills)this.renderSkillCard(o,c,n.agents)}renderSkillCard(e,s,n){let a=e.createDiv({cls:"af-skill-card"}),r=a.createDiv({cls:"af-skill-card-header"}),o=r.createDiv({cls:"af-skill-card-icon"});(0,b.setIcon)(o,this.getSkillIcon(s.name));let c=r.createEl("button",{cls:"af-btn-sm af-btn-xs"});R(c,"edit","af-btn-icon"),c.onclick=h=>{h.stopPropagation(),this.navigate("edit-skill",s.name)},a.createDiv({cls:"af-skill-card-name",text:s.name}),a.createDiv({cls:"af-skill-card-desc",text:s.description??"No description"});let l=n.filter(h=>h.skills.includes(s.name));if(l.length>0){let h=a.createDiv({cls:"af-skill-card-agents"});for(let d of l)h.createSpan({cls:"af-skill-card-agent-tag",text:d.name})}}renderChannelsPage(e){let s=e.createDiv({cls:"af-agents-page"}),n=this.plugin.runtime.getSnapshot(),a=s.createDiv({cls:"af-agents-toolbar"});a.createDiv({cls:"af-page-title",text:"Channels"}),a.createDiv({cls:"af-toolbar-spacer"});let r=a.createEl("button",{cls:"af-btn-sm primary"});R(r,"plus","af-btn-icon"),r.appendText(" New Channel"),r.onclick=()=>this.navigate("create-channel");let o=s.createDiv({cls:"af-agents-grid"});if(n.channels.length===0){this.renderEmptyState(o,"radio","No channels configured","Connect an agent to Slack or another chat platform");return}for(let c of n.channels)this.renderChannelCard(o,c,n.validationIssues)}async renderWikiKeepersPage(e){let s=e.createDiv({cls:"af-agents-page"}),n=this.plugin.runtime.getSnapshot(),a=s.createDiv({cls:"af-agents-toolbar"});a.createDiv({cls:"af-page-title",text:"Wiki Keepers"}),a.createDiv({cls:"af-toolbar-spacer"});let r=n.agents.filter(c=>c.wikiKeeper!==void 0);if(r.length===0){this.renderEmptyState(s,"library","No Wiki Keepers yet","Open Settings \u2192 Agent Fleet \u2192 Wiki Keepers \u2192 + Add to create one.");return}let o=s.createDiv({cls:"af-wk-list"});o.setCssStyles({display:"flex"}),o.setCssStyles({flexDirection:"column"}),o.setCssStyles({gap:"16px"});for(let c of r)await this.renderWikiKeeperCard(o,c)}async renderWikiKeeperCard(e,s){let n=s.wikiKeeper,a=e.createDiv({cls:"af-card"});a.setCssStyles({padding:"16px"}),a.setCssStyles({border:"1px solid var(--background-modifier-border)"}),a.setCssStyles({borderRadius:"8px"});let r=a.createDiv();r.setCssStyles({display:"flex"}),r.setCssStyles({alignItems:"center"}),r.setCssStyles({gap:"12px"}),r.setCssStyles({marginBottom:"12px"});let o=r.createDiv();o.setCssStyles({flex:"1"}),o.createEl("strong",{text:s.name});let c=n.scopeRoot||"(whole vault)";o.createEl("div",{text:`Scope: ${c} \xB7 topics: ${n.topicsRoot}/ \xB7 log: ${n.logPath}`,cls:"af-form-hint"});let l=r.createEl("button",{cls:"af-btn-sm"});l.appendText("Open log"),l.onclick=()=>{let g=n.scopeRoot?`${n.scopeRoot}/${n.logPath}`:n.logPath,y=this.plugin.app.vault.getAbstractFileByPath(g);y instanceof b.TFile?this.plugin.app.workspace.getLeaf().openFile(y):new b.Notice(`Log file not found: ${g}`)};let h=n.scopeRoot?`${n.scopeRoot}/${n.logPath}`:n.logPath,d=this.plugin.app.vault.getAbstractFileByPath(h),u=null;if(d instanceof b.TFile)try{let g=await this.plugin.app.vault.cachedRead(d);u=Yo(g)}catch{u=null}if(!u){a.createDiv({cls:"af-form-hint"}).setText("No lint report yet. Run wiki-lint manually or wait for the weekly task to fire.");return}let p=a.createDiv();if(p.setCssStyles({display:"flex"}),p.setCssStyles({alignItems:"baseline"}),p.setCssStyles({gap:"12px"}),p.setCssStyles({marginBottom:"8px"}),p.createEl("strong",{text:`Lint ${u.date}`}),p.createSpan({cls:"af-form-hint",text:`${u.summary.length} summary lines \xB7 ${u.autoApplied.length} auto-applied \xB7 ${u.needsReview.length} needs review`}),u.summary.length>0){let g=a.createEl("details");g.createEl("summary",{text:"Summary"});let y=g.createEl("ul");y.setCssStyles({marginTop:"4px"});for(let v of u.summary)y.createEl("li",{text:v})}if(u.autoApplied.length>0){let g=a.createEl("details");g.createEl("summary",{text:`Auto-applied (${u.autoApplied.length})`});let y=g.createEl("ul");y.setCssStyles({marginTop:"4px"});for(let v of u.autoApplied)y.createEl("li",{text:v})}if(u.refreshChained.length>0){let g=a.createEl("details");g.createEl("summary",{text:`Refresh chained (${u.refreshChained.length})`});let y=g.createEl("ul");y.setCssStyles({marginTop:"4px"});for(let v of u.refreshChained)y.createEl("li",{text:v})}let m=a.createDiv();if(m.setCssStyles({marginTop:"12px"}),m.createEl("strong",{text:`Needs review (${u.needsReview.length})`}),u.needsReview.length===0){m.createDiv({cls:"af-form-hint",text:"All clear. Nothing requires manual review from this lint pass."});return}let f=m.createDiv();f.setCssStyles({display:"flex"}),f.setCssStyles({flexDirection:"column"}),f.setCssStyles({gap:"6px"}),f.setCssStyles({marginTop:"8px"});for(let g of u.needsReview){let y=f.createDiv();y.setCssStyles({display:"flex"}),y.setCssStyles({alignItems:"flex-start"}),y.setCssStyles({gap:"8px"}),y.setCssStyles({padding:"8px 10px"}),y.setCssStyles({background:"var(--background-secondary)"}),y.setCssStyles({borderRadius:"4px"}),y.setCssStyles({fontSize:"13px"});let v=y.createDiv();v.setCssStyles({flex:"1"}),v.setText(g);let k=y.createEl("button",{cls:"af-btn-sm",text:"Dismiss"});k.title="Hide this item from the dashboard until the next lint pass (does not modify log.md).",k.onclick=()=>{y.remove()}}}renderChannelCard(e,s,n){let a=this.plugin.channelManager?.getChannelStatus(s.name)??"disabled",r=Xo(a),o=s.enabled&&a!=="disabled"?"af-agent-card":"af-agent-card disabled",c=e.createDiv({cls:o});c.setCssStyles({cursor:"default"});let l=c.createDiv({cls:"af-agent-card-header"}),h=l.createDiv({cls:`af-agent-card-avatar ${r}`});(0,b.setIcon)(h,"radio");let d=l.createDiv({cls:"af-agent-card-titleblock"});d.createDiv({cls:"af-agent-card-name",text:s.name}),d.createDiv({cls:"af-agent-card-desc",text:`Default: ${s.defaultAgent}`});let u=l.createSpan({cls:`af-pill ${xh(a)}`});if(u.createSpan({cls:"af-dot"}),u.appendText(` ${a}`),s.allowedAgents.length>0){let T=c.createDiv({cls:"af-agent-card-skills"});for(let _ of s.allowedAgents){let D=T.createSpan({cls:"af-skill-tag",text:_});_===s.defaultAgent&&D.setCssStyles({fontWeight:"700"})}}let p=c.createDiv({cls:"af-agent-card-stats"}),m=this.plugin.channelManager?.getSessionCount(s.name)??0,f=this.plugin.channelManager?.getMetrics(s.name),g=s.allowedAgents.length>0?String(s.allowedAgents.length):"all";this.renderAgentStat(p,g,"Agents"),this.renderAgentStat(p,String(m),"Sessions"),this.renderAgentStat(p,String(f?.messagesReceived??0),"In"),this.renderAgentStat(p,String(f?.messagesSent??0),"Out");let y=c.createDiv({cls:"af-agent-card-footer"}),v=[s.type];s.enabled||v.push("disabled"),s.allowedUsers.length>0?v.push(`${s.allowedUsers.length} user(s)`):v.push("allowlist empty"),y.createSpan({cls:"af-agent-card-meta",text:v.join(" \xB7 ")});let w=y.createDiv({cls:"af-agent-card-actions"}).createEl("button",{cls:"af-btn-sm"});R(w,"edit","af-btn-icon"),w.appendText(" Edit"),w.onclick=T=>{T.stopPropagation(),this.navigate("edit-channel",s.name)};let S=n.filter(T=>T.path===s.filePath);if(S.length>0){let T=c.createDiv({cls:"af-channel-issues"});for(let _ of S)T.createDiv({cls:"af-channel-issue-row",text:_.message})}}renderCreateChannelPage(e){let s=e.createDiv({cls:"af-create-agent-page"}),n=this.plugin.runtime.getSnapshot(),a=this.plugin.channelCredentials.list(),r=s.createDiv({cls:"af-detail-header"}),o=r.createDiv({cls:"af-detail-header-left"}),c=o.createDiv({cls:"af-agent-card-avatar idle"});(0,b.setIcon)(c,"plus");let l=o.createDiv();l.createDiv({cls:"af-detail-header-name",text:"Create New Channel"}),l.createDiv({cls:"af-detail-header-desc",text:"Connect an external chat transport to an agent"});let h=r.createDiv({cls:"af-detail-header-actions"}),d={name:"",type:"slack",defaultAgent:n.agents[0]?.name??"",allowedAgents:[],credentialRef:a[0]?.ref??"",allowedUsers:"",perUserSessions:!0,channelContext:"",enabled:!0,tags:"",body:"",transportJson:""},u=s.createDiv({cls:"af-create-form"}),p=u.createDiv({cls:"af-create-section"}),m=p.createDiv({cls:"af-create-section-header"}),f=m.createSpan({cls:"af-create-section-icon"});(0,b.setIcon)(f,"radio"),m.createSpan({text:"Channel Details"}),this.createFormField(p,"Name","my-slack","Unique identifier for this channel",j=>{d.name=j});let g=p.createDiv({cls:"af-form-row"});g.createDiv({cls:"af-form-label",text:"Type"});let y=g.createEl("select",{cls:"af-form-select"});y.createEl("option",{text:"slack",attr:{value:"slack"}}),y.createEl("option",{text:"telegram",attr:{value:"telegram"}}),y.createEl("option",{text:"discord",attr:{value:"discord"}}),y.addEventListener("change",()=>{d.type=y.value});let v=p.createDiv({cls:"af-form-row"}),k=v.createDiv({cls:"af-form-label"});k.setText("Credential"),this.addTooltip(k,"Configured in Settings \u2192 Agent Fleet \u2192 Channel Credentials");let w=v.createEl("select",{cls:"af-form-select"});a.length===0&&w.createEl("option",{text:"(no credentials configured)",attr:{value:""}});for(let j of a)w.createEl("option",{text:`${j.ref} (${j.entry.type})`,attr:{value:j.ref}});w.addEventListener("change",()=>{d.credentialRef=w.value});let S=p.createDiv({cls:"af-form-row af-form-row-toggle"});S.createDiv({cls:"af-form-label",text:"Enabled"});let T=S.createDiv({cls:"af-agent-card-toggle on"});T.onclick=()=>{let j=T.hasClass("on");T.toggleClass("on",!j),d.enabled=!j};let _=u.createDiv({cls:"af-create-section"}),D=_.createDiv({cls:"af-create-section-header"}),O=D.createSpan({cls:"af-create-section-icon"});(0,b.setIcon)(O,"bot"),D.createSpan({text:"Agent Routing"});let C=_.createDiv({cls:"af-form-row"}),E=C.createDiv({cls:"af-form-label"});E.setText("Default agent"),this.addTooltip(E,"Used when no @agent-name prefix is given in a message");let P=C.createEl("select",{cls:"af-form-select"});for(let j of n.agents)P.createEl("option",{text:j.name,attr:{value:j.name}});P.addEventListener("change",()=>{d.defaultAgent=P.value});let N=_.createDiv({cls:"af-form-row"}),M=N.createDiv({cls:"af-form-label"});M.setText("Allowed agents"),this.addTooltip(M,"Agents reachable via @prefix. Leave unchecked to allow all agents.");let U=N.createDiv({cls:"af-form-checkboxes"});for(let j of n.agents){let Ce=U.createEl("label",{cls:"af-form-checkbox-label"}),xe=Ce.createEl("input",{attr:{type:"checkbox",value:j.name}});Ce.appendText(` ${j.name}`),xe.addEventListener("change",()=>{xe.checked?d.allowedAgents.includes(j.name)||d.allowedAgents.push(j.name):d.allowedAgents=d.allowedAgents.filter(he=>he!==j.name)})}let $=_.createDiv({cls:"af-form-row af-form-row-toggle"}),Z=$.createDiv({cls:"af-form-label"});Z.setText("Per-user sessions"),this.addTooltip(Z,"Each external user gets their own isolated Claude session");let de=$.createDiv({cls:"af-agent-card-toggle on"});de.onclick=()=>{let j=de.hasClass("on");de.toggleClass("on",!j),d.perUserSessions=!j};let me=u.createDiv({cls:"af-create-section"}),ge=me.createDiv({cls:"af-create-section-header"}),X=ge.createSpan({cls:"af-create-section-icon"});(0,b.setIcon)(X,"shield-check"),ge.createSpan({text:"Access Control"});let W=me.createDiv({cls:"af-form-label"});W.setText("Allowed users"),this.addTooltip(W,"User IDs, one per line \u2014 Slack (U...), Telegram (numeric), or Discord (numeric snowflakes). Only listed users can reach the bot.");let K=me.createEl("textarea",{cls:"af-create-prompt-textarea",attr:{placeholder:`U0AQW6P37N1 +U0BXYZ12345`,rows:"4"}});K.addEventListener("input",()=>{d.allowedUsers=K.value});let ne=u.createDiv({cls:"af-create-section"}),G=ne.createDiv({cls:"af-create-section-header"}),ee=G.createSpan({cls:"af-create-section-icon"});(0,b.setIcon)(ee,"message-square");let Q=G.createSpan({text:"Channel Context"});this.addTooltip(Q,"Extra instructions appended to the agent's system prompt when reached through this channel");let ae=ne.createEl("textarea",{cls:"af-create-prompt-textarea",attr:{placeholder:"You are being contacted via Slack. Keep replies concise...",rows:"6"}});ae.addEventListener("input",()=>{d.channelContext=ae.value}),this.createFormField(p,"Tags","ops, internal","Comma-separated metadata",j=>{d.tags=j});let ve=u.createDiv({cls:"af-create-section"}),Pe=ve.createDiv({cls:"af-create-section-header"}),Ie=Pe.createSpan({cls:"af-create-section-icon"});(0,b.setIcon)(Ie,"settings");let Me=Pe.createSpan({text:"Advanced"});this.addTooltip(Me,"Markdown body (shown in the channel detail page) and transport-specific overrides");let Fe=ve.createDiv({cls:"af-form-label"});Fe.setText("Body (markdown)"),this.addTooltip(Fe,"Free-form notes for this channel. Shown in the channel detail page; not sent to the agent.");let ke=ve.createEl("textarea",{cls:"af-create-prompt-textarea",attr:{placeholder:"Notes, runbook snippets, escalation contacts\u2026",rows:"4"}});ke.addEventListener("input",()=>{d.body=ke.value});let Oe=ve.createDiv({cls:"af-form-label"});Oe.setText("Transport config (JSON)"),this.addTooltip(Oe,"Optional JSON object for transport-specific overrides (e.g. Slack socket_mode, telegram webhook settings). Leave blank for defaults.");let Ue=ve.createEl("textarea",{cls:"af-create-prompt-textarea",attr:{placeholder:`{ "socket_mode": true -}`,rows:"4"}});Ue.addEventListener("input",()=>{d.transportJson=Ue.value});let Ne=s.createDiv({cls:"af-create-footer"}),H=Ne.createEl("button",{cls:"af-btn-sm",text:"Cancel"});H.onclick=()=>this.navigate("channels");let ie=Ne.createEl("button",{cls:"af-btn-sm primary af-create-submit"});E(ie,"plus","af-btn-icon"),ie.appendText(" Create Channel"),ie.onclick=async()=>{let $=d.name.trim();if(!$){new w.Notice("Channel name is required.");return}if(!d.credentialRef){new w.Notice("Select a credential.");return}let Te;if(d.transportJson.trim())try{let re=JSON.parse(d.transportJson);if(re&&typeof re=="object"&&!Array.isArray(re))Te=re;else{new w.Notice("Transport config must be a JSON object.");return}}catch(re){new w.Notice(`Transport JSON is invalid: ${re instanceof Error?re.message:String(re)}`);return}let xe=re=>re.split(/[\n,]+/).map(R=>R.trim()).filter(Boolean),he=re=>re.split(",").map(R=>R.trim()).filter(Boolean),$e={name:oe($),type:d.type,default_agent:d.defaultAgent,allowed_agents:d.allowedAgents.length>0?d.allowedAgents:void 0,enabled:d.enabled,credential_ref:d.credentialRef,allowed_users:xe(d.allowedUsers),per_user_sessions:d.perUserSessions,channel_context:d.channelContext.trim()||void 0,tags:he(d.tags).length>0?he(d.tags):void 0,transport:Te};try{let re=oe($),R=await this.plugin.repository.getAvailablePath(this.plugin.repository.getSubfolder("channels"),re);await this.plugin.app.vault.create(R,W($e,d.body.trim())),new w.Notice(`Channel "${re}" created.`),await this.plugin.refreshFromVault(),this.navigate("edit-channel",re)}catch(re){let R=re instanceof Error?re.message:String(re);new w.Notice(`Failed to create channel: ${R}`)}}}renderEditChannelPage(e){let s=e.createDiv({cls:"af-create-agent-page"}),n=this.detailContext;if(!n){this.renderEmptyState(s,"radio","No channel selected","");return}let a=this.plugin.runtime.getSnapshot().channels.find(R=>R.name===n);if(!a){this.renderEmptyState(s,"radio","Channel not found",`Channel "${n}" was not found`);return}let r=this.plugin.runtime.getSnapshot(),o=this.plugin.channelCredentials.list(),c=this.plugin.channelManager?.getChannelStatus(a.name)??"disabled",l=s.createDiv({cls:"af-detail-header"}),h=l.createDiv({cls:"af-detail-header-left"}),d=h.createDiv({cls:`af-agent-card-avatar ${Oo(c)}`});(0,w.setIcon)(d,"radio");let u=h.createDiv();u.createDiv({cls:"af-detail-header-name",text:`Edit Channel: ${a.name}`}),u.createDiv({cls:"af-detail-header-desc",text:`Status: ${c}`});let p=l.createDiv({cls:"af-detail-header-actions"}),m={type:a.type,defaultAgent:a.defaultAgent,allowedAgents:[...a.allowedAgents],credentialRef:a.credentialRef,allowedUsers:a.allowedUsers.join(` -`),perUserSessions:a.perUserSessions,channelContext:a.channelContext,enabled:a.enabled,tags:a.tags.join(", "),body:a.body,transportJson:Object.keys(a.transport).length>0?JSON.stringify(a.transport,null,2):""},f=s.createDiv({cls:"af-create-form"}),g=f.createDiv({cls:"af-create-section"}),v=g.createDiv({cls:"af-create-section-header"}),b=v.createSpan({cls:"af-create-section-icon"});(0,w.setIcon)(b,"radio"),v.createSpan({text:"Channel Details"});let k=g.createDiv({cls:"af-form-row"});k.createDiv({cls:"af-form-label",text:"Name"}),k.createEl("input",{cls:"af-form-input",attr:{type:"text",value:a.name,disabled:"true"}}).setCssStyles({opacity:"0.6"});let S=g.createDiv({cls:"af-form-row"});S.createDiv({cls:"af-form-label",text:"Type"});let T=S.createEl("select",{cls:"af-form-select"});for(let R of["slack","telegram"]){let se=T.createEl("option",{text:R,attr:{value:R}});R===a.type&&(se.selected=!0)}T.addEventListener("change",()=>{m.type=T.value});let _=g.createDiv({cls:"af-form-row"}),D=_.createDiv({cls:"af-form-label"});D.setText("Credential"),this.addTooltip(D,"Configured in Settings \u2192 Agent Fleet \u2192 Channel Credentials");let M=_.createEl("select",{cls:"af-form-select"});o.length===0&&M.createEl("option",{text:"(no credentials configured)",attr:{value:""}});for(let R of o){let se=M.createEl("option",{text:`${R.ref} (${R.entry.type})`,attr:{value:R.ref}});R.ref===a.credentialRef&&(se.selected=!0)}M.addEventListener("change",()=>{m.credentialRef=M.value});let C=g.createDiv({cls:"af-form-row af-form-row-toggle"});C.createDiv({cls:"af-form-label",text:"Enabled"});let I=C.createDiv({cls:`af-agent-card-toggle${a.enabled?" on":""}`});I.onclick=()=>{let R=I.hasClass("on");I.toggleClass("on",!R),m.enabled=!R};let P=f.createDiv({cls:"af-create-section"}),L=P.createDiv({cls:"af-create-section-header"}),F=L.createSpan({cls:"af-create-section-icon"});(0,w.setIcon)(F,"bot"),L.createSpan({text:"Agent Routing"});let z=P.createDiv({cls:"af-form-row"}),U=z.createDiv({cls:"af-form-label"});U.setText("Default agent"),this.addTooltip(U,"Used when no @agent-name prefix is given in a message");let Z=z.createEl("select",{cls:"af-form-select"});for(let R of r.agents){let se=Z.createEl("option",{text:R.name,attr:{value:R.name}});R.name===a.defaultAgent&&(se.selected=!0)}Z.addEventListener("change",()=>{m.defaultAgent=Z.value});let de=P.createDiv({cls:"af-form-row"}),me=de.createDiv({cls:"af-form-label"});me.setText("Allowed agents"),this.addTooltip(me,"Agents reachable via @prefix. Leave unchecked to allow all agents.");let ye=de.createDiv({cls:"af-form-checkboxes"});for(let R of r.agents){let se=ye.createEl("label",{cls:"af-form-checkbox-label"}),ce=se.createEl("input",{attr:{type:"checkbox",value:R.name}});a.allowedAgents.includes(R.name)&&(ce.checked=!0),se.appendText(` ${R.name}`),ce.addEventListener("change",()=>{ce.checked?m.allowedAgents.includes(R.name)||m.allowedAgents.push(R.name):m.allowedAgents=m.allowedAgents.filter(te=>te!==R.name)})}let X=P.createDiv({cls:"af-form-row af-form-row-toggle"}),j=X.createDiv({cls:"af-form-label"});j.setText("Per-user sessions"),this.addTooltip(j,"Each external user gets their own isolated Claude session");let K=X.createDiv({cls:`af-agent-card-toggle${a.perUserSessions?" on":""}`});K.onclick=()=>{let R=K.hasClass("on");K.toggleClass("on",!R),m.perUserSessions=!R};let ne=f.createDiv({cls:"af-create-section"}),G=ne.createDiv({cls:"af-create-section-header"}),ee=G.createSpan({cls:"af-create-section-icon"});(0,w.setIcon)(ee,"shield-check"),G.createSpan({text:"Access Control"});let Q=ne.createDiv({cls:"af-form-label"});Q.setText("Allowed users"),this.addTooltip(Q,"Slack user IDs (U...), one per line. Only listed users can reach the bot.");let ae=ne.createEl("textarea",{cls:"af-create-prompt-textarea",attr:{placeholder:`U0AQW6P37N1 +}`,rows:"4"}});Ue.addEventListener("input",()=>{d.transportJson=Ue.value});let Ne=s.createDiv({cls:"af-create-footer"}),q=Ne.createEl("button",{cls:"af-btn-sm",text:"Cancel"});q.onclick=()=>this.navigate("channels");let ie=Ne.createEl("button",{cls:"af-btn-sm primary af-create-submit"});R(ie,"plus","af-btn-icon"),ie.appendText(" Create Channel"),ie.onclick=async()=>{let j=d.name.trim();if(!j){new b.Notice("Channel name is required.");return}if(!d.credentialRef){new b.Notice("Select a credential.");return}let Ce;if(d.transportJson.trim())try{let re=JSON.parse(d.transportJson);if(re&&typeof re=="object"&&!Array.isArray(re))Ce=re;else{new b.Notice("Transport config must be a JSON object.");return}}catch(re){new b.Notice(`Transport JSON is invalid: ${re instanceof Error?re.message:String(re)}`);return}let xe=re=>re.split(/[\n,]+/).map(I=>I.trim()).filter(Boolean),he=re=>re.split(",").map(I=>I.trim()).filter(Boolean),$e={name:oe(j),type:d.type,default_agent:d.defaultAgent,allowed_agents:d.allowedAgents.length>0?d.allowedAgents:void 0,enabled:d.enabled,credential_ref:d.credentialRef,allowed_users:xe(d.allowedUsers),per_user_sessions:d.perUserSessions,channel_context:d.channelContext.trim()||void 0,tags:he(d.tags).length>0?he(d.tags):void 0,transport:Ce};try{let re=oe(j),I=await this.plugin.repository.getAvailablePath(this.plugin.repository.getSubfolder("channels"),re);await this.plugin.app.vault.create(I,H($e,d.body.trim())),new b.Notice(`Channel "${re}" created.`),await this.plugin.refreshFromVault(),this.navigate("edit-channel",re)}catch(re){let I=re instanceof Error?re.message:String(re);new b.Notice(`Failed to create channel: ${I}`)}}}renderEditChannelPage(e){let s=e.createDiv({cls:"af-create-agent-page"}),n=this.detailContext;if(!n){this.renderEmptyState(s,"radio","No channel selected","");return}let a=this.plugin.runtime.getSnapshot().channels.find(I=>I.name===n);if(!a){this.renderEmptyState(s,"radio","Channel not found",`Channel "${n}" was not found`);return}let r=this.plugin.runtime.getSnapshot(),o=this.plugin.channelCredentials.list(),c=this.plugin.channelManager?.getChannelStatus(a.name)??"disabled",l=s.createDiv({cls:"af-detail-header"}),h=l.createDiv({cls:"af-detail-header-left"}),d=h.createDiv({cls:`af-agent-card-avatar ${Xo(c)}`});(0,b.setIcon)(d,"radio");let u=h.createDiv();u.createDiv({cls:"af-detail-header-name",text:`Edit Channel: ${a.name}`}),u.createDiv({cls:"af-detail-header-desc",text:`Status: ${c}`});let p=l.createDiv({cls:"af-detail-header-actions"}),m={type:a.type,defaultAgent:a.defaultAgent,allowedAgents:[...a.allowedAgents],credentialRef:a.credentialRef,allowedUsers:a.allowedUsers.join(` +`),perUserSessions:a.perUserSessions,channelContext:a.channelContext,enabled:a.enabled,tags:a.tags.join(", "),body:a.body,transportJson:Object.keys(a.transport).length>0?JSON.stringify(a.transport,null,2):""},f=s.createDiv({cls:"af-create-form"}),g=f.createDiv({cls:"af-create-section"}),y=g.createDiv({cls:"af-create-section-header"}),v=y.createSpan({cls:"af-create-section-icon"});(0,b.setIcon)(v,"radio"),y.createSpan({text:"Channel Details"});let k=g.createDiv({cls:"af-form-row"});k.createDiv({cls:"af-form-label",text:"Name"}),k.createEl("input",{cls:"af-form-input",attr:{type:"text",value:a.name,disabled:"true"}}).setCssStyles({opacity:"0.6"});let S=g.createDiv({cls:"af-form-row"});S.createDiv({cls:"af-form-label",text:"Type"});let T=S.createEl("select",{cls:"af-form-select"});for(let I of["slack","telegram","discord"]){let se=T.createEl("option",{text:I,attr:{value:I}});I===a.type&&(se.selected=!0)}T.addEventListener("change",()=>{m.type=T.value});let _=g.createDiv({cls:"af-form-row"}),D=_.createDiv({cls:"af-form-label"});D.setText("Credential"),this.addTooltip(D,"Configured in Settings \u2192 Agent Fleet \u2192 Channel Credentials");let O=_.createEl("select",{cls:"af-form-select"});o.length===0&&O.createEl("option",{text:"(no credentials configured)",attr:{value:""}});for(let I of o){let se=O.createEl("option",{text:`${I.ref} (${I.entry.type})`,attr:{value:I.ref}});I.ref===a.credentialRef&&(se.selected=!0)}O.addEventListener("change",()=>{m.credentialRef=O.value});let C=g.createDiv({cls:"af-form-row af-form-row-toggle"});C.createDiv({cls:"af-form-label",text:"Enabled"});let E=C.createDiv({cls:`af-agent-card-toggle${a.enabled?" on":""}`});E.onclick=()=>{let I=E.hasClass("on");E.toggleClass("on",!I),m.enabled=!I};let P=f.createDiv({cls:"af-create-section"}),N=P.createDiv({cls:"af-create-section-header"}),M=N.createSpan({cls:"af-create-section-icon"});(0,b.setIcon)(M,"bot"),N.createSpan({text:"Agent Routing"});let U=P.createDiv({cls:"af-form-row"}),$=U.createDiv({cls:"af-form-label"});$.setText("Default agent"),this.addTooltip($,"Used when no @agent-name prefix is given in a message");let Z=U.createEl("select",{cls:"af-form-select"});for(let I of r.agents){let se=Z.createEl("option",{text:I.name,attr:{value:I.name}});I.name===a.defaultAgent&&(se.selected=!0)}Z.addEventListener("change",()=>{m.defaultAgent=Z.value});let de=P.createDiv({cls:"af-form-row"}),me=de.createDiv({cls:"af-form-label"});me.setText("Allowed agents"),this.addTooltip(me,"Agents reachable via @prefix. Leave unchecked to allow all agents.");let ge=de.createDiv({cls:"af-form-checkboxes"});for(let I of r.agents){let se=ge.createEl("label",{cls:"af-form-checkbox-label"}),ce=se.createEl("input",{attr:{type:"checkbox",value:I.name}});a.allowedAgents.includes(I.name)&&(ce.checked=!0),se.appendText(` ${I.name}`),ce.addEventListener("change",()=>{ce.checked?m.allowedAgents.includes(I.name)||m.allowedAgents.push(I.name):m.allowedAgents=m.allowedAgents.filter(te=>te!==I.name)})}let X=P.createDiv({cls:"af-form-row af-form-row-toggle"}),W=X.createDiv({cls:"af-form-label"});W.setText("Per-user sessions"),this.addTooltip(W,"Each external user gets their own isolated Claude session");let K=X.createDiv({cls:`af-agent-card-toggle${a.perUserSessions?" on":""}`});K.onclick=()=>{let I=K.hasClass("on");K.toggleClass("on",!I),m.perUserSessions=!I};let ne=f.createDiv({cls:"af-create-section"}),G=ne.createDiv({cls:"af-create-section-header"}),ee=G.createSpan({cls:"af-create-section-icon"});(0,b.setIcon)(ee,"shield-check"),G.createSpan({text:"Access Control"});let Q=ne.createDiv({cls:"af-form-label"});Q.setText("Allowed users"),this.addTooltip(Q,"Slack user IDs (U...), one per line. Only listed users can reach the bot.");let ae=ne.createEl("textarea",{cls:"af-create-prompt-textarea",attr:{placeholder:`U0AQW6P37N1 U0BXYZ12345`,rows:"4"}});ae.value=a.allowedUsers.join(` -`),ae.addEventListener("input",()=>{m.allowedUsers=ae.value});let ve=f.createDiv({cls:"af-create-section"}),Re=ve.createDiv({cls:"af-create-section-header"}),Le=Re.createSpan({cls:"af-create-section-icon"});(0,w.setIcon)(Le,"message-square");let Me=Re.createSpan({text:"Channel Context"});this.addTooltip(Me,"Extra instructions appended to the agent's system prompt when reached through this channel");let Fe=ve.createEl("textarea",{cls:"af-create-prompt-textarea",attr:{placeholder:"You are being contacted via Slack. Keep replies concise...",rows:"6"}});Fe.value=a.channelContext,Fe.addEventListener("input",()=>{m.channelContext=Fe.value}),this.createFormField(g,"Tags","ops, internal","Comma-separated metadata",R=>{m.tags=R},a.tags.join(", "));let ke=f.createDiv({cls:"af-create-section"}),Oe=ke.createDiv({cls:"af-create-section-header"}),Ue=Oe.createSpan({cls:"af-create-section-icon"});(0,w.setIcon)(Ue,"settings");let Ne=Oe.createSpan({text:"Advanced"});this.addTooltip(Ne,"Markdown body (shown in the channel detail page) and transport-specific overrides"),ke.createDiv({cls:"af-form-label"}).setText("Body (markdown)");let ie=ke.createEl("textarea",{cls:"af-create-prompt-textarea",attr:{placeholder:"Notes, runbook snippets, escalation contacts\u2026",rows:"4"}});ie.value=a.body,ie.addEventListener("input",()=>{m.body=ie.value});let $=ke.createDiv({cls:"af-form-label"});$.setText("Transport config (JSON)"),this.addTooltip($,"Optional JSON object for transport-specific overrides (e.g. Slack socket_mode). Leave blank for defaults.");let Te=ke.createEl("textarea",{cls:"af-create-prompt-textarea",attr:{placeholder:`{ +`),ae.addEventListener("input",()=>{m.allowedUsers=ae.value});let ve=f.createDiv({cls:"af-create-section"}),Pe=ve.createDiv({cls:"af-create-section-header"}),Ie=Pe.createSpan({cls:"af-create-section-icon"});(0,b.setIcon)(Ie,"message-square");let Me=Pe.createSpan({text:"Channel Context"});this.addTooltip(Me,"Extra instructions appended to the agent's system prompt when reached through this channel");let Fe=ve.createEl("textarea",{cls:"af-create-prompt-textarea",attr:{placeholder:"You are being contacted via Slack. Keep replies concise...",rows:"6"}});Fe.value=a.channelContext,Fe.addEventListener("input",()=>{m.channelContext=Fe.value}),this.createFormField(g,"Tags","ops, internal","Comma-separated metadata",I=>{m.tags=I},a.tags.join(", "));let ke=f.createDiv({cls:"af-create-section"}),Oe=ke.createDiv({cls:"af-create-section-header"}),Ue=Oe.createSpan({cls:"af-create-section-icon"});(0,b.setIcon)(Ue,"settings");let Ne=Oe.createSpan({text:"Advanced"});this.addTooltip(Ne,"Markdown body (shown in the channel detail page) and transport-specific overrides"),ke.createDiv({cls:"af-form-label"}).setText("Body (markdown)");let ie=ke.createEl("textarea",{cls:"af-create-prompt-textarea",attr:{placeholder:"Notes, runbook snippets, escalation contacts\u2026",rows:"4"}});ie.value=a.body,ie.addEventListener("input",()=>{m.body=ie.value});let j=ke.createDiv({cls:"af-form-label"});j.setText("Transport config (JSON)"),this.addTooltip(j,"Optional JSON object for transport-specific overrides (e.g. Slack socket_mode). Leave blank for defaults.");let Ce=ke.createEl("textarea",{cls:"af-create-prompt-textarea",attr:{placeholder:`{ "socket_mode": true -}`,rows:"4"}});Te.value=m.transportJson,Te.addEventListener("input",()=>{m.transportJson=Te.value});let xe=s.createDiv({cls:"af-create-footer"}),he=xe.createEl("button",{cls:"af-btn-sm danger"});E(he,"trash-2","af-btn-icon"),he.appendText(" Delete"),he.onclick=async()=>{await this.plugin.repository.deleteChannel(a.name),new w.Notice(`Channel "${a.name}" deleted.`),await new Promise(R=>window.setTimeout(R,200)),await this.plugin.refreshFromVault(),this.navigate("channels")},xe.createDiv({cls:"af-toolbar-spacer"});let $e=xe.createEl("button",{cls:"af-btn-sm",text:"Cancel"});$e.onclick=()=>this.navigate("channels");let re=xe.createEl("button",{cls:"af-btn-sm primary af-create-submit"});E(re,"check","af-btn-icon"),re.appendText(" Save Changes"),re.onclick=async()=>{let R=te=>te.split(/[\n,]+/).map(Ze=>Ze.trim()).filter(Boolean),se=te=>te.split(",").map(Ze=>Ze.trim()).filter(Boolean),ce;if(m.transportJson.trim())try{let te=JSON.parse(m.transportJson);if(te&&typeof te=="object"&&!Array.isArray(te))ce=te;else{new w.Notice("Transport config must be a JSON object.");return}}catch(te){new w.Notice(`Transport JSON is invalid: ${te instanceof Error?te.message:String(te)}`);return}else ce={};try{await this.plugin.repository.updateChannel(a.name,{type:m.type,default_agent:m.defaultAgent,allowed_agents:m.allowedAgents.length>0?m.allowedAgents:[],enabled:m.enabled,credential_ref:m.credentialRef,allowed_users:R(m.allowedUsers),per_user_sessions:m.perUserSessions,channel_context:m.channelContext.trim(),tags:se(m.tags),body:m.body.trim(),transport:ce}),new w.Notice(`Channel "${a.name}" updated.`),await this.plugin.refreshFromVault(),this.navigate("channels")}catch(te){let Ze=te instanceof Error?te.message:String(te);new w.Notice(`Failed to update channel: ${Ze}`)}}}renderApprovalsPage(e){let s=e.createDiv({cls:"af-approvals-page"}),n=this.plugin.runtime.getRecentRuns(),a=s.createDiv({cls:"af-agents-toolbar"});a.createDiv({cls:"af-page-title",text:"Approvals"}),a.createDiv({cls:"af-toolbar-spacer"});let r=n.filter(c=>(c.approvals??[]).some(l=>l.status==="pending"));if(r.length>0){let c=s.createDiv({cls:"af-section-card"}),h=c.createDiv({cls:"af-section-header"}).createDiv({cls:"af-section-title"});E(h,"alert-triangle"),h.appendText(` Pending (${r.length})`);let d=c.createDiv({cls:"af-approvals-list"});for(let u of r)this.renderApprovalItem(d,u,!0)}else{let c=s.createDiv({cls:"af-section-card"});this.renderEmptyState(c,"shield-check","No pending approvals","All clear!")}let o=n.filter(c=>(c.approvals??[]).some(l=>l.status!=="pending"));if(o.length>0){let c=s.createDiv({cls:"af-section-card"}),h=c.createDiv({cls:"af-section-header"}).createDiv({cls:"af-section-title"});E(h,"check-circle-2"),h.appendText(" History");let d=c.createDiv({cls:"af-approvals-list"});for(let u of o.slice(0,20))this.renderApprovalItem(d,u,!1)}}renderApprovalItem(e,s,n){for(let a of s.approvals??[]){if(n&&a.status!=="pending"||!n&&a.status==="pending")continue;let r=e.createDiv({cls:"af-approval-item"}),o=r.createDiv({cls:"af-approval-item-icon"});a.status==="pending"?((0,w.setIcon)(o,"shield-check"),o.addClass("pending")):a.status==="approved"?((0,w.setIcon)(o,"check-circle-2"),o.addClass("approved")):((0,w.setIcon)(o,"x-circle"),o.addClass("rejected"));let c=r.createDiv({cls:"af-approval-item-body"});if(c.createDiv({cls:"af-approval-item-title",text:`${s.agent} \u2192 ${a.tool}`}),c.createDiv({cls:"af-approval-item-meta",text:`Task: ${s.task} \xB7 ${a.command??"no command"} \xB7 ${this.formatStarted(s.started)}`}),a.reason&&c.createDiv({cls:"af-approval-item-reason",text:`Reason: ${a.reason}`}),n&&a.status==="pending"){let l=r.createDiv({cls:"af-approval-item-actions"}),h=l.createEl("button",{cls:"af-btn-approve"});E(h,"check-circle-2","af-btn-icon"),h.appendText(" Approve"),h.onclick=()=>void this.plugin.runtime.resolveApproval(s,a.tool,"approved").then(()=>this.render());let d=l.createEl("button",{cls:"af-btn-reject"});E(d,"x-circle","af-btn-icon"),d.appendText(" Reject"),d.onclick=()=>void this.plugin.runtime.resolveApproval(s,a.tool,"rejected").then(()=>this.render())}}}renderTaskDetailPage(e){let s=e.createDiv({cls:"af-task-detail-page"}),n=this.detailContext;if(!n){this.renderEmptyState(s,"circle-dot","No task selected","");return}let a=this.plugin.runtime.getSnapshot().tasks.find(P=>P.taskId===n);if(!a){this.renderEmptyState(s,"circle-dot","Task not found",`Task "${n}" was not found`);return}let r=this.plugin.runtime.getSnapshot(),o=this.plugin.runtime.getRecentRuns().filter(P=>P.task===n),c=r.agents.find(P=>P.name===a.agent),l=s.createDiv({cls:"af-detail-header"}),h=l.createDiv({cls:"af-detail-header-left"}),d=h.createDiv({cls:"af-agent-card-avatar idle"});(0,w.setIcon)(d,"circle-dot");let u=h.createDiv();u.createDiv({cls:"af-detail-header-name",text:a.taskId}),u.createDiv({cls:"af-detail-header-desc",text:`Agent: ${a.agent}`});let p=l.createDiv({cls:"af-detail-header-actions"}),m=p.createEl("button",{cls:"af-btn-sm"});E(m,"edit","af-btn-icon"),m.appendText(" Edit"),m.onclick=()=>this.navigate("edit-task",a.taskId);let f=p.createEl("button",{cls:"af-btn-sm primary"});E(f,"play","af-btn-icon"),f.appendText(" Run Now"),f.onclick=()=>void this.plugin.runtime.runTaskNow(a);let g=s.createDiv({cls:"af-section-card"}),b=g.createDiv({cls:"af-section-header"}).createDiv({cls:"af-section-title"});E(b,"file-text"),b.appendText(" Details");let k=g.createDiv({cls:"af-config-form"});this.renderConfigRow(k,"Agent",a.agent),this.renderConfigRow(k,"Priority",a.priority.charAt(0).toUpperCase()+a.priority.slice(1)),this.renderConfigRow(k,"Status",a.enabled?"Enabled":"Disabled");let y=a.schedule?this.humanizeCron(a.schedule):a.runAt??"Manual (run on demand)";this.renderConfigRow(k,"Schedule",y),a.schedule&&this.renderConfigRow(k,"Catch up if missed",a.catchUp?"Yes":"No"),this.renderConfigRow(k,"Created",a.created),this.renderConfigRow(k,"Runs",String(a.runCount)),a.lastRun&&this.renderConfigRow(k,"Last Run",this.formatStarted(a.lastRun));let S=s.createDiv({cls:"af-section-card"}),_=S.createDiv({cls:"af-section-header"}).createDiv({cls:"af-section-title"});E(_,"message-square"),_.appendText(" Instructions"),S.createDiv({cls:"af-output-block",text:a.body||"(empty)"});let D=s.createDiv({cls:"af-section-card"}),C=D.createDiv({cls:"af-section-header"}).createDiv({cls:"af-section-title"});E(C,"scroll-text"),C.appendText(" Recent Runs");let I=D.createDiv({cls:"af-timeline"});if(o.length===0)this.renderEmptyState(I,"scroll-text","No runs yet","");else for(let P of o.slice(0,10))this.renderTimelineItem(I,P)}handleSearch(e,s){if(s.querySelector(".af-search-results")?.remove(),e.length<2)return;let n=e.toLowerCase(),a=this.plugin.runtime.getSnapshot(),r=this.plugin.runtime.getRecentRuns(),o=[];for(let l of a.agents)(l.name.toLowerCase().includes(n)||(l.description?.toLowerCase().includes(n)??!1))&&o.push({label:`Agent: ${l.name}`,icon:"bot",action:()=>this.navigate("agent-detail",l.name)});for(let l of a.tasks)(l.taskId.toLowerCase().includes(n)||l.body.toLowerCase().includes(n))&&o.push({label:`Task: ${l.taskId}`,icon:"circle-dot",action:()=>this.navigate("task-detail",l.taskId)});for(let l of a.skills)l.name.toLowerCase().includes(n)&&o.push({label:`Skill: ${l.name}`,icon:"puzzle",action:()=>this.navigate("edit-skill",l.name)});for(let l of r.slice(0,20))l.output.toLowerCase().includes(n)&&o.push({label:`Run: ${l.agent} / ${l.task}`,icon:"scroll-text",action:()=>this.openSlideover(l)});if(o.length===0)return;let c=s.createDiv({cls:"af-search-results"});for(let l of o.slice(0,10)){let h=c.createDiv({cls:"af-search-result-item"});E(h,l.icon,"af-search-result-icon"),h.createSpan({text:l.label}),h.onclick=()=>{c.remove(),l.action()}}}openChatSlideover(e){this.plugin.openChatView(e.name)}openSlideover(e){this.contentEl.querySelector(".af-slideover-overlay")?.remove();let s=this.contentEl.createDiv({cls:"af-slideover-overlay"}),n=s.createDiv({cls:"af-slideover"}),a=n.createDiv({cls:"af-slideover-header"});a.createDiv({cls:"af-slideover-title",text:"Run Details"});let r=a.createEl("button",{cls:"clickable-icon"});(0,w.setIcon)(r,"cross"),r.onclick=()=>s.remove();let o=n.createDiv({cls:"af-slideover-body"}),c=o.createDiv({cls:"af-slideover-section"});c.createDiv({cls:"af-slideover-section-title",text:"METADATA"}),this.renderDetailRow(c,"Run ID",e.runId.slice(0,8)),this.renderDetailRow(c,"Agent",e.agent),this.renderDetailRow(c,"Task",e.task);let l=c.createDiv({cls:"af-detail-row"});l.createSpan({cls:"af-detail-label",text:"Status"});let d=l.createSpan({cls:"af-detail-value"}).createSpan({cls:`af-status-badge ${this.statusToBadgeClass(e.status)}`}),u=d.createSpan();(0,w.setIcon)(u,this.statusToIconName(e.status)),d.appendText(` ${this.statusToBadgeText(e.status)}`),this.renderDetailRow(c,"Started",e.started),this.renderDetailRow(c,"Duration",this.formatDuration(e.durationSeconds)),this.renderDetailRow(c,"Tokens",e.tokensUsed?e.tokensUsed.toLocaleString():"\u2014");{let y={task:"from task override",agent:"from agent",settings:"from settings default","cli-default":"CLI default"},S=e.modelSource?` (${y[e.modelSource]??e.modelSource})`:"",T=e.concreteModel&&e.concreteModel!==e.model?` \u2192 ${e.concreteModel}`:"";this.renderDetailRow(c,"Model",`${e.model}${T}${S}`)}let p=5e4,m=y=>y.length>p?y.slice(0,p)+` +}`,rows:"4"}});Ce.value=m.transportJson,Ce.addEventListener("input",()=>{m.transportJson=Ce.value});let xe=s.createDiv({cls:"af-create-footer"}),he=xe.createEl("button",{cls:"af-btn-sm danger"});R(he,"trash-2","af-btn-icon"),he.appendText(" Delete"),he.onclick=async()=>{await this.plugin.repository.deleteChannel(a.name),new b.Notice(`Channel "${a.name}" deleted.`),await new Promise(I=>window.setTimeout(I,200)),await this.plugin.refreshFromVault(),this.navigate("channels")},xe.createDiv({cls:"af-toolbar-spacer"});let $e=xe.createEl("button",{cls:"af-btn-sm",text:"Cancel"});$e.onclick=()=>this.navigate("channels");let re=xe.createEl("button",{cls:"af-btn-sm primary af-create-submit"});R(re,"check","af-btn-icon"),re.appendText(" Save Changes"),re.onclick=async()=>{let I=te=>te.split(/[\n,]+/).map(st=>st.trim()).filter(Boolean),se=te=>te.split(",").map(st=>st.trim()).filter(Boolean),ce;if(m.transportJson.trim())try{let te=JSON.parse(m.transportJson);if(te&&typeof te=="object"&&!Array.isArray(te))ce=te;else{new b.Notice("Transport config must be a JSON object.");return}}catch(te){new b.Notice(`Transport JSON is invalid: ${te instanceof Error?te.message:String(te)}`);return}else ce={};try{await this.plugin.repository.updateChannel(a.name,{type:m.type,default_agent:m.defaultAgent,allowed_agents:m.allowedAgents.length>0?m.allowedAgents:[],enabled:m.enabled,credential_ref:m.credentialRef,allowed_users:I(m.allowedUsers),per_user_sessions:m.perUserSessions,channel_context:m.channelContext.trim(),tags:se(m.tags),body:m.body.trim(),transport:ce}),new b.Notice(`Channel "${a.name}" updated.`),await this.plugin.refreshFromVault(),this.navigate("channels")}catch(te){let st=te instanceof Error?te.message:String(te);new b.Notice(`Failed to update channel: ${st}`)}}}renderApprovalsPage(e){let s=e.createDiv({cls:"af-approvals-page"}),n=this.plugin.runtime.getRecentRuns(),a=s.createDiv({cls:"af-agents-toolbar"});a.createDiv({cls:"af-page-title",text:"Approvals"}),a.createDiv({cls:"af-toolbar-spacer"});let r=n.filter(c=>(c.approvals??[]).some(l=>l.status==="pending"));if(r.length>0){let c=s.createDiv({cls:"af-section-card"}),h=c.createDiv({cls:"af-section-header"}).createDiv({cls:"af-section-title"});R(h,"alert-triangle"),h.appendText(` Pending (${r.length})`);let d=c.createDiv({cls:"af-approvals-list"});for(let u of r)this.renderApprovalItem(d,u,!0)}else{let c=s.createDiv({cls:"af-section-card"});this.renderEmptyState(c,"shield-check","No pending approvals","All clear!")}let o=n.filter(c=>(c.approvals??[]).some(l=>l.status!=="pending"));if(o.length>0){let c=s.createDiv({cls:"af-section-card"}),h=c.createDiv({cls:"af-section-header"}).createDiv({cls:"af-section-title"});R(h,"check-circle-2"),h.appendText(" History");let d=c.createDiv({cls:"af-approvals-list"});for(let u of o.slice(0,20))this.renderApprovalItem(d,u,!1)}}renderApprovalItem(e,s,n){for(let a of s.approvals??[]){if(n&&a.status!=="pending"||!n&&a.status==="pending")continue;let r=e.createDiv({cls:"af-approval-item"}),o=r.createDiv({cls:"af-approval-item-icon"});a.status==="pending"?((0,b.setIcon)(o,"shield-check"),o.addClass("pending")):a.status==="approved"?((0,b.setIcon)(o,"check-circle-2"),o.addClass("approved")):((0,b.setIcon)(o,"x-circle"),o.addClass("rejected"));let c=r.createDiv({cls:"af-approval-item-body"});if(c.createDiv({cls:"af-approval-item-title",text:`${s.agent} \u2192 ${a.tool}`}),c.createDiv({cls:"af-approval-item-meta",text:`Task: ${s.task} \xB7 ${a.command??"no command"} \xB7 ${this.formatStarted(s.started)}`}),a.reason&&c.createDiv({cls:"af-approval-item-reason",text:`Reason: ${a.reason}`}),n&&a.status==="pending"){let l=r.createDiv({cls:"af-approval-item-actions"}),h=l.createEl("button",{cls:"af-btn-approve"});R(h,"check-circle-2","af-btn-icon"),h.appendText(" Approve"),h.onclick=()=>void this.plugin.runtime.resolveApproval(s,a.tool,"approved").then(()=>this.render());let d=l.createEl("button",{cls:"af-btn-reject"});R(d,"x-circle","af-btn-icon"),d.appendText(" Reject"),d.onclick=()=>void this.plugin.runtime.resolveApproval(s,a.tool,"rejected").then(()=>this.render())}}}renderTaskDetailPage(e){let s=e.createDiv({cls:"af-task-detail-page"}),n=this.detailContext;if(!n){this.renderEmptyState(s,"circle-dot","No task selected","");return}let a=this.plugin.runtime.getSnapshot().tasks.find(P=>P.taskId===n);if(!a){this.renderEmptyState(s,"circle-dot","Task not found",`Task "${n}" was not found`);return}let r=this.plugin.runtime.getSnapshot(),o=this.plugin.runtime.getRecentRuns().filter(P=>P.task===n),c=r.agents.find(P=>P.name===a.agent),l=s.createDiv({cls:"af-detail-header"}),h=l.createDiv({cls:"af-detail-header-left"}),d=h.createDiv({cls:"af-agent-card-avatar idle"});(0,b.setIcon)(d,"circle-dot");let u=h.createDiv();u.createDiv({cls:"af-detail-header-name",text:a.taskId}),u.createDiv({cls:"af-detail-header-desc",text:`Agent: ${a.agent}`});let p=l.createDiv({cls:"af-detail-header-actions"}),m=p.createEl("button",{cls:"af-btn-sm"});R(m,"edit","af-btn-icon"),m.appendText(" Edit"),m.onclick=()=>this.navigate("edit-task",a.taskId);let f=p.createEl("button",{cls:"af-btn-sm primary"});R(f,"play","af-btn-icon"),f.appendText(" Run Now"),f.onclick=()=>void this.plugin.runtime.runTaskNow(a);let g=s.createDiv({cls:"af-section-card"}),v=g.createDiv({cls:"af-section-header"}).createDiv({cls:"af-section-title"});R(v,"file-text"),v.appendText(" Details");let k=g.createDiv({cls:"af-config-form"});this.renderConfigRow(k,"Agent",a.agent),this.renderConfigRow(k,"Priority",a.priority.charAt(0).toUpperCase()+a.priority.slice(1)),this.renderConfigRow(k,"Status",a.enabled?"Enabled":"Disabled");let w=a.schedule?this.humanizeCron(a.schedule):a.runAt??"Manual (run on demand)";this.renderConfigRow(k,"Schedule",w),a.schedule&&this.renderConfigRow(k,"Catch up if missed",a.catchUp?"Yes":"No"),this.renderConfigRow(k,"Created",a.created),this.renderConfigRow(k,"Runs",String(a.runCount)),a.lastRun&&this.renderConfigRow(k,"Last Run",this.formatStarted(a.lastRun));let S=s.createDiv({cls:"af-section-card"}),_=S.createDiv({cls:"af-section-header"}).createDiv({cls:"af-section-title"});R(_,"message-square"),_.appendText(" Instructions"),S.createDiv({cls:"af-output-block",text:a.body||"(empty)"});let D=s.createDiv({cls:"af-section-card"}),C=D.createDiv({cls:"af-section-header"}).createDiv({cls:"af-section-title"});R(C,"scroll-text"),C.appendText(" Recent Runs");let E=D.createDiv({cls:"af-timeline"});if(o.length===0)this.renderEmptyState(E,"scroll-text","No runs yet","");else for(let P of o.slice(0,10))this.renderTimelineItem(E,P)}handleSearch(e,s){if(s.querySelector(".af-search-results")?.remove(),e.length<2)return;let n=e.toLowerCase(),a=this.plugin.runtime.getSnapshot(),r=this.plugin.runtime.getRecentRuns(),o=[];for(let l of a.agents)(l.name.toLowerCase().includes(n)||(l.description?.toLowerCase().includes(n)??!1))&&o.push({label:`Agent: ${l.name}`,icon:"bot",action:()=>this.navigate("agent-detail",l.name)});for(let l of a.tasks)(l.taskId.toLowerCase().includes(n)||l.body.toLowerCase().includes(n))&&o.push({label:`Task: ${l.taskId}`,icon:"circle-dot",action:()=>this.navigate("task-detail",l.taskId)});for(let l of a.skills)l.name.toLowerCase().includes(n)&&o.push({label:`Skill: ${l.name}`,icon:"puzzle",action:()=>this.navigate("edit-skill",l.name)});for(let l of r.slice(0,20))l.output.toLowerCase().includes(n)&&o.push({label:`Run: ${l.agent} / ${l.task}`,icon:"scroll-text",action:()=>this.openSlideover(l)});if(o.length===0)return;let c=s.createDiv({cls:"af-search-results"});for(let l of o.slice(0,10)){let h=c.createDiv({cls:"af-search-result-item"});R(h,l.icon,"af-search-result-icon"),h.createSpan({text:l.label}),h.onclick=()=>{c.remove(),l.action()}}}openChatSlideover(e){this.plugin.openChatView(e.name)}openSlideover(e){this.contentEl.querySelector(".af-slideover-overlay")?.remove();let s=this.contentEl.createDiv({cls:"af-slideover-overlay"}),n=s.createDiv({cls:"af-slideover"}),a=n.createDiv({cls:"af-slideover-header"});a.createDiv({cls:"af-slideover-title",text:"Run Details"});let r=a.createEl("button",{cls:"clickable-icon"});(0,b.setIcon)(r,"cross"),r.onclick=()=>s.remove();let o=n.createDiv({cls:"af-slideover-body"}),c=o.createDiv({cls:"af-slideover-section"});c.createDiv({cls:"af-slideover-section-title",text:"METADATA"}),this.renderDetailRow(c,"Run ID",e.runId.slice(0,8)),this.renderDetailRow(c,"Agent",e.agent),this.renderDetailRow(c,"Task",e.task);let l=c.createDiv({cls:"af-detail-row"});l.createSpan({cls:"af-detail-label",text:"Status"});let d=l.createSpan({cls:"af-detail-value"}).createSpan({cls:`af-status-badge ${this.statusToBadgeClass(e.status)}`}),u=d.createSpan();(0,b.setIcon)(u,this.statusToIconName(e.status)),d.appendText(` ${this.statusToBadgeText(e.status)}`),this.renderDetailRow(c,"Started",e.started),this.renderDetailRow(c,"Duration",this.formatDuration(e.durationSeconds)),this.renderDetailRow(c,"Tokens",e.tokensUsed?e.tokensUsed.toLocaleString():"\u2014");{let w={task:"from task override",agent:"from agent",settings:"from settings default","cli-default":"CLI default"},S=e.modelSource?` (${w[e.modelSource]??e.modelSource})`:"",T=e.concreteModel&&e.concreteModel!==e.model?` \u2192 ${e.concreteModel}`:"";this.renderDetailRow(c,"Model",`${e.model}${T}${S}`)}let p=5e4,m=w=>w.length>p?w.slice(0,p)+` --- -*Truncated (${(y.length/1024).toFixed(0)} KB total). Open the run note for full content.*`:y,f=!!(e.finalResult&&e.finalResult.trim()),g=e.output?.trim()??"",v=f&&g.length>0&&g!==e.finalResult.trim();if(f){let y=o.createDiv({cls:"af-slideover-section"});y.createDiv({cls:"af-slideover-section-title",text:"OUTPUT"});let S=y.createDiv({cls:"af-output-block af-compact-md"});if(w.MarkdownRenderer.render(this.app,m(e.finalResult),S,"",this),v){let T=y.createEl("details",{cls:"af-run-transcript"}),_=T.createEl("summary");(0,w.setIcon)(_.createSpan({cls:"af-run-transcript-icon"}),"file-text"),_.createSpan({text:"Show full transcript"}),_.createSpan({cls:"af-run-transcript-meta"}).setText(`${(g.length/1024).toFixed(1)} KB`);let M=T.createDiv({cls:"af-output-block af-compact-md af-run-transcript-body"});w.MarkdownRenderer.render(this.app,m(g),M,"",this)}}else if(g){let y=o.createDiv({cls:"af-slideover-section"});y.createDiv({cls:"af-slideover-section-title",text:"OUTPUT"});let S=y.createDiv({cls:"af-output-block af-compact-md"});w.MarkdownRenderer.render(this.app,m(g),S,"",this)}if(e.toolsUsed.length>0){let y=o.createDiv({cls:"af-slideover-section"});y.createDiv({cls:"af-slideover-section-title",text:"TOOLS USED"}),y.createDiv({cls:"af-output-block",text:e.toolsUsed.join(` -`)})}let b=o.createDiv({cls:"af-slideover-actions"});if(e.filePath){let y=b.createEl("button",{cls:"af-btn-sm"});E(y,"external-link","af-btn-icon"),y.appendText(" Open Run Note"),y.onclick=()=>void this.plugin.openPath(e.filePath)}let k=b.createEl("button",{cls:"af-btn-sm primary"});E(k,"refresh-cw","af-btn-icon"),k.appendText(" Re-run Task"),k.onclick=()=>void this.plugin.runAgentPrompt(e.agent),s.onclick=y=>{y.target===s&&s.remove()}}renderDetailRow(e,s,n){let a=e.createDiv({cls:"af-detail-row"});a.createSpan({cls:"af-detail-label",text:s}),a.createSpan({cls:"af-detail-value af-mono",text:n})}renderEmptyState(e,s,n,a){let r=e.createDiv({cls:"af-empty-state"}),o=r.createDiv({cls:"af-empty-icon"});(0,w.setIcon)(o,s),r.createDiv({cls:"af-empty-label",text:n}),a&&r.createDiv({cls:"af-empty-sublabel",text:a})}healthToClass(e){switch(e){case"running":return"running";case"error":return"error";case"pending":return"pending";case"disabled":return"disabled";default:return"idle"}}statusToTimelineClass(e){switch(e){case"success":return"success";case"failure":case"timeout":return"error";case"cancelled":return"warning";case"pending_approval":return"pending";default:return"running"}}statusToIconName(e){switch(e){case"success":return"check-circle-2";case"failure":return"x-circle";case"timeout":return"clock";case"pending_approval":return"shield-check";case"cancelled":return"square";default:return"loader-2"}}statusToBadgeClass(e){switch(e){case"success":return"success";case"failure":return"failure";case"timeout":return"timeout";case"pending_approval":return"pending";case"cancelled":return"cancelled";default:return"running"}}statusToBadgeText(e){switch(e){case"success":return"Success";case"failure":return"Failed";case"timeout":return"Timeout";case"pending_approval":return"Pending";case"cancelled":return"Cancelled";case"interrupted":return"Interrupted";default:return e}}formatDuration(e){if(e<60)return`${e}s`;let s=Math.floor(e/60),n=e%60;return n>0?`${s}m ${n}s`:`${s}m`}formatStarted(e){try{let s=new Date(e),n=new Date;if(s.toDateString()===n.toDateString())return s.toLocaleTimeString([],{hour:"2-digit",minute:"2-digit"});let a=new Date(n);return a.setDate(a.getDate()-1),s.toDateString()===a.toDateString()?`Yesterday ${s.toLocaleTimeString([],{hour:"2-digit",minute:"2-digit"})}`:s.toLocaleDateString([],{month:"short",day:"numeric"})+` ${s.toLocaleTimeString([],{hour:"2-digit",minute:"2-digit"})}`}catch{return e}}formatNextRun(e){try{let s=new Date(e),n=new Date,a=s.getTime()-n.getTime();if(a<0)return"overdue";let r=Math.round(a/6e4);if(r<60)return`${r}m`;let o=Math.round(r/60);return o<24?`${o}h`:s.toLocaleDateString([],{month:"short",day:"numeric"})}catch{return e}}getNextTaskLabel(e){let n=e.map(r=>r.nextRun).filter(Boolean).sort()[0];return n?`${e.find(r=>r.nextRun===n)?.agent??"unknown"} in ${this.formatNextRun(n)}`:"none"}getInitials(e){return e.split("-").map(s=>s[0]?.toUpperCase()??"").slice(0,2).join("")}renderAgentAvatar(e,s){let n=s.avatar?.trim();if(!n){(0,w.setIcon)(e,"bot");return}/^[a-z][a-z0-9-]*$/.test(n)?(0,w.setIcon)(e,n):e.setText(n)}getSkillIcon(e){return e.includes("git")?"settings":e.includes("summarize")||e.includes("log")?"activity":e.includes("review")||e.includes("check")?"check-circle-2":e.includes("vault")||e.includes("note")?"file-text":"puzzle"}renderInlineSchedule(e,s){let n=this.parseCronComponents(s.schedule),a=e.createDiv({cls:"af-form-row"});a.createDiv({cls:"af-form-label",text:"Frequency"});let r=a.createEl("select",{cls:"af-form-select"}),o=[["every_5m","Every 5 minutes"],["every_15m","Every 15 minutes"],["every_30m","Every 30 minutes"],["every_hour","Every hour"],["every_2h","Every 2 hours"],["daily","Daily"],["weekdays","Weekdays"],["weekly","Weekly"],["monthly","Monthly"]];for(let[y,S]of o){let T=r.createEl("option",{text:S,attr:{value:y}});y===n.freq&&(T.selected=!0)}let c=e.createDiv({cls:"af-form-row af-schedule-time-row"});c.createDiv({cls:"af-form-label",text:"Time"});let l=c.createDiv({cls:"af-schedule-time-selects"}),h=l.createEl("select",{cls:"af-form-select af-form-select-sm"});for(let y=0;y<24;y++){let S=y>=12?"PM":"AM",T=y===0?12:y>12?y-12:y,_=h.createEl("option",{text:`${T} ${S}`,attr:{value:String(y)}});y===n.hour&&(_.selected=!0)}l.createSpan({cls:"af-schedule-colon",text:":"});let d=l.createEl("select",{cls:"af-form-select af-form-select-sm"});for(let y=0;y<60;y+=5){let S=d.createEl("option",{text:String(y).padStart(2,"0"),attr:{value:String(y)}});y===n.minute&&(S.selected=!0)}let u=e.createDiv({cls:"af-form-row af-schedule-day-row"});u.createDiv({cls:"af-form-label",text:"Day"});let p=u.createDiv({cls:"af-schedule-day-buttons"}),m=["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],f=new Set(n.days);for(let y=0;y<7;y++){let S=p.createEl("button",{cls:`af-schedule-day-btn${f.has(y)?" active":""}`,text:m[y]});S.onclick=()=>{f.has(y)?f.delete(y):f.add(y),S.toggleClass("active",f.has(y)),k()}}let g=e.createDiv({cls:"af-form-row af-schedule-dom-row"});g.createDiv({cls:"af-form-label",text:"Day of month"});let v=g.createEl("select",{cls:"af-form-select af-form-select-sm"});for(let y=1;y<=28;y++){let S=v.createEl("option",{text:String(y),attr:{value:String(y)}});y===n.dayOfMonth&&(S.selected=!0)}let b=()=>{let y=r.value,S=["daily","weekdays","weekly","monthly"].includes(y),T=y==="weekly",_=y==="monthly";c.setCssStyles({display:S?"":"none"}),u.setCssStyles({display:T?"":"none"}),g.setCssStyles({display:_?"":"none"})},k=()=>{let y=r.value,S=h.value,T=d.value,_="";switch(y){case"every_5m":_="*/5 * * * *";break;case"every_15m":_="*/15 * * * *";break;case"every_30m":_="*/30 * * * *";break;case"every_hour":_="0 * * * *";break;case"every_2h":_="0 */2 * * *";break;case"daily":_=`${T} ${S} * * *`;break;case"weekdays":_=`${T} ${S} * * 1-5`;break;case"weekly":{let D=Array.from(f).sort().join(",")||"1";_=`${T} ${S} * * ${D}`;break}case"monthly":_=`${T} ${S} ${v.value} * *`;break}s.schedule=_,s.type="recurring"};r.addEventListener("change",()=>{b(),k()}),h.addEventListener("change",k),d.addEventListener("change",k),v.addEventListener("change",k),b()}renderHeartbeatSchedule(e,s){let n=this.parseCronComponents(s.heartbeatSchedule),a=e.createDiv({cls:"af-form-row"});a.createDiv({cls:"af-form-label",text:"Frequency"});let r=a.createEl("select",{cls:"af-form-select"}),o=[["every_5m","Every 5 minutes"],["every_15m","Every 15 minutes"],["every_30m","Every 30 minutes"],["every_hour","Every hour"],["every_2h","Every 2 hours"],["every_4h","Every 4 hours"],["every_6h","Every 6 hours"],["every_12h","Every 12 hours"],["daily","Once a day"]],c="every_hour",l={"*/5 * * * *":"every_5m","*/15 * * * *":"every_15m","*/30 * * * *":"every_30m","0 * * * *":"every_hour","0 */2 * * *":"every_2h","0 */4 * * *":"every_4h","0 */6 * * *":"every_6h","0 */12 * * *":"every_12h"};l[s.heartbeatSchedule]?c=l[s.heartbeatSchedule]:(n.freq==="daily"||n.freq==="weekdays")&&(c="daily");for(let[g,v]of o){let b=r.createEl("option",{text:v,attr:{value:g}});g===c&&(b.selected=!0)}let h=e.createDiv({cls:"af-form-row af-schedule-time-row"});h.createDiv({cls:"af-form-label",text:"Time"});let d=h.createDiv({cls:"af-schedule-time-selects"}),u=d.createEl("select",{cls:"af-form-select af-form-select-sm"});for(let g=0;g<24;g++){let v=g>=12?"PM":"AM",b=g===0?12:g>12?g-12:g,k=u.createEl("option",{text:`${b} ${v}`,attr:{value:String(g)}});g===n.hour&&(k.selected=!0)}d.createSpan({cls:"af-schedule-colon",text:":"});let p=d.createEl("select",{cls:"af-form-select af-form-select-sm"});for(let g=0;g<60;g+=5){let v=p.createEl("option",{text:String(g).padStart(2,"0"),attr:{value:String(g)}});g===n.minute&&(v.selected=!0)}let m=()=>{h.setCssStyles({display:r.value==="daily"?"":"none"})},f=()=>{let g=r.value,v=u.value,b=p.value;switch(g){case"every_5m":s.heartbeatSchedule="*/5 * * * *";break;case"every_15m":s.heartbeatSchedule="*/15 * * * *";break;case"every_30m":s.heartbeatSchedule="*/30 * * * *";break;case"every_hour":s.heartbeatSchedule="0 * * * *";break;case"every_2h":s.heartbeatSchedule="0 */2 * * *";break;case"every_4h":s.heartbeatSchedule="0 */4 * * *";break;case"every_6h":s.heartbeatSchedule="0 */6 * * *";break;case"every_12h":s.heartbeatSchedule="0 */12 * * *";break;case"daily":s.heartbeatSchedule=`${b} ${v} * * *`;break}};r.addEventListener("change",()=>{m(),f()}),u.addEventListener("change",f),p.addEventListener("change",f),m()}parseCronComponents(e){let s={freq:"daily",hour:9,minute:0,days:[1],dayOfMonth:1};if(!e?.trim())return s;let n={"*/5 * * * *":"every_5m","*/15 * * * *":"every_15m","*/30 * * * *":"every_30m","0 * * * *":"every_hour","0 */2 * * *":"every_2h"};if(n[e])return{...s,freq:n[e]};let a=e.trim().split(/\s+/);if(a.length!==5)return s;let[r,o,c,,l]=a,h=parseInt(o??"9",10),d=parseInt(r??"0",10);if(c==="*"&&l==="*")return{...s,freq:"daily",hour:h,minute:d};if(c==="*"&&l==="1-5")return{...s,freq:"weekdays",hour:h,minute:d};if(c==="*"&&l!=="*"){let u=(l??"1").split(",").map(p=>parseInt(p,10));return{...s,freq:"weekly",hour:h,minute:d,days:u}}return l==="*"&&c!=="*"?{...s,freq:"monthly",hour:h,minute:d,dayOfMonth:parseInt(c??"1",10)}:{...s,hour:h,minute:d}}humanizeCron(e){let s={"*/5 * * * *":"Every 5 minutes","*/10 * * * *":"Every 10 minutes","*/15 * * * *":"Every 15 minutes","*/30 * * * *":"Every 30 minutes","0 * * * *":"Every hour","0 */2 * * *":"Every 2 hours"};if(s[e])return s[e];let n=e.toLowerCase().trim();if(n.startsWith("every ")||n.startsWith("daily ")||n==="daily")return e;if(n.startsWith("hourly"))return"Every hour";if(n.startsWith("weekdays")||n.startsWith("weekly")||n.startsWith("monthly"))return e;let a=e.trim().split(/\s+/);if(a.length!==5)return e;let[r,o,c,,l]=a,h=(p,m)=>{let f=parseInt(p??"0",10),g=parseInt(m??"0",10),v=f>=12?"PM":"AM",b=f===0?12:f>12?f-12:f;return g===0?`${b} ${v}`:`${b}:${String(g).padStart(2,"0")} ${v}`},d=["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],u=p=>p==="1-5"?"weekdays":p==="0,6"?"weekends":p.split(",").map(f=>parseInt(f,10)).map(f=>d[f]??f).join(", ");return o==="*"&&c==="*"&&l==="*"?r==="*"?"Every minute":`Every hour at :${String(r).padStart(2,"0")}`:c==="*"&&l==="*"&&o!=="*"?`Daily at ${h(o??"0",r??"0")}`:c==="*"&&l==="1-5"&&o!=="*"?`Weekdays at ${h(o??"0",r??"0")}`:c==="*"&&l!=="*"&&o!=="*"?`${u(l??"1")} at ${h(o??"0",r??"0")}`:l==="*"&&c!=="*"&&o!=="*"?`Monthly on the ${c} at ${h(o??"0",r??"0")}`:e}getTagClass(e){return e==="monitoring"?"monitoring":e==="devops"?"devops":e==="sample"?"sample":"default"}renderCreateAgentPage(e){let s=e.createDiv({cls:"af-create-agent-page"}),n=s.createDiv({cls:"af-detail-header"}),a=n.createDiv({cls:"af-detail-header-left"}),r=a.createDiv({cls:"af-agent-card-avatar idle"});(0,w.setIcon)(r,"plus");let o=a.createDiv();o.createDiv({cls:"af-detail-header-name",text:"Create New Agent"}),o.createDiv({cls:"af-detail-header-desc",text:"Configure a new agent for your fleet"});let c=n.createDiv({cls:"af-detail-header-actions"}),l={name:"",description:"",avatar:"",tags:"",systemPrompt:"",model:"default",adapter:"claude-code",cwd:"",timeout:300,permissionMode:"bypassPermissions",effort:"",selectedSkills:new Set,selectedMcpServers:new Set,skillsBody:"",contextBody:"",approvalRequired:"",memory:!0,enabled:!0,allowedCommands:"",blockedCommands:"",heartbeatEnabled:!1,heartbeatSchedule:"0 */6 * * *",heartbeatBody:"",heartbeatNotify:!0,heartbeatChannel:"",autoCompactThreshold:85,wikiReferences:[]},h={none:{label:"None",prompt:""},coding:{label:"Coding Agent",prompt:`You are a coding agent. Review code, write tests, fix bugs, and implement features. +*Truncated (${(w.length/1024).toFixed(0)} KB total). Open the run note for full content.*`:w,f=!!(e.finalResult&&e.finalResult.trim()),g=e.output?.trim()??"",y=f&&g.length>0&&g!==e.finalResult.trim();if(f){let w=o.createDiv({cls:"af-slideover-section"});w.createDiv({cls:"af-slideover-section-title",text:"OUTPUT"});let S=w.createDiv({cls:"af-output-block af-compact-md"});if(b.MarkdownRenderer.render(this.app,m(e.finalResult),S,"",this),y){let T=w.createEl("details",{cls:"af-run-transcript"}),_=T.createEl("summary");(0,b.setIcon)(_.createSpan({cls:"af-run-transcript-icon"}),"file-text"),_.createSpan({text:"Show full transcript"}),_.createSpan({cls:"af-run-transcript-meta"}).setText(`${(g.length/1024).toFixed(1)} KB`);let O=T.createDiv({cls:"af-output-block af-compact-md af-run-transcript-body"});b.MarkdownRenderer.render(this.app,m(g),O,"",this)}}else if(g){let w=o.createDiv({cls:"af-slideover-section"});w.createDiv({cls:"af-slideover-section-title",text:"OUTPUT"});let S=w.createDiv({cls:"af-output-block af-compact-md"});b.MarkdownRenderer.render(this.app,m(g),S,"",this)}if(e.toolsUsed.length>0){let w=o.createDiv({cls:"af-slideover-section"});w.createDiv({cls:"af-slideover-section-title",text:"TOOLS USED"}),w.createDiv({cls:"af-output-block",text:e.toolsUsed.join(` +`)})}let v=o.createDiv({cls:"af-slideover-actions"});if(e.filePath){let w=v.createEl("button",{cls:"af-btn-sm"});R(w,"external-link","af-btn-icon"),w.appendText(" Open Run Note"),w.onclick=()=>void this.plugin.openPath(e.filePath)}let k=v.createEl("button",{cls:"af-btn-sm primary"});R(k,"refresh-cw","af-btn-icon"),k.appendText(" Re-run Task"),k.onclick=()=>void this.plugin.runAgentPrompt(e.agent),s.onclick=w=>{w.target===s&&s.remove()}}renderDetailRow(e,s,n){let a=e.createDiv({cls:"af-detail-row"});a.createSpan({cls:"af-detail-label",text:s}),a.createSpan({cls:"af-detail-value af-mono",text:n})}renderEmptyState(e,s,n,a){let r=e.createDiv({cls:"af-empty-state"}),o=r.createDiv({cls:"af-empty-icon"});(0,b.setIcon)(o,s),r.createDiv({cls:"af-empty-label",text:n}),a&&r.createDiv({cls:"af-empty-sublabel",text:a})}healthToClass(e){switch(e){case"running":return"running";case"error":return"error";case"pending":return"pending";case"disabled":return"disabled";default:return"idle"}}statusToTimelineClass(e){switch(e){case"success":return"success";case"failure":case"timeout":return"error";case"cancelled":return"warning";case"pending_approval":return"pending";default:return"running"}}statusToIconName(e){switch(e){case"success":return"check-circle-2";case"failure":return"x-circle";case"timeout":return"clock";case"pending_approval":return"shield-check";case"cancelled":return"square";default:return"loader-2"}}statusToBadgeClass(e){switch(e){case"success":return"success";case"failure":return"failure";case"timeout":return"timeout";case"pending_approval":return"pending";case"cancelled":return"cancelled";default:return"running"}}statusToBadgeText(e){switch(e){case"success":return"Success";case"failure":return"Failed";case"timeout":return"Timeout";case"pending_approval":return"Pending";case"cancelled":return"Cancelled";case"interrupted":return"Interrupted";default:return e}}formatDuration(e){if(e<60)return`${e}s`;let s=Math.floor(e/60),n=e%60;return n>0?`${s}m ${n}s`:`${s}m`}formatStarted(e){try{let s=new Date(e),n=new Date;if(s.toDateString()===n.toDateString())return s.toLocaleTimeString([],{hour:"2-digit",minute:"2-digit"});let a=new Date(n);return a.setDate(a.getDate()-1),s.toDateString()===a.toDateString()?`Yesterday ${s.toLocaleTimeString([],{hour:"2-digit",minute:"2-digit"})}`:s.toLocaleDateString([],{month:"short",day:"numeric"})+` ${s.toLocaleTimeString([],{hour:"2-digit",minute:"2-digit"})}`}catch{return e}}formatNextRun(e){try{let s=new Date(e),n=new Date,a=s.getTime()-n.getTime();if(a<0)return"overdue";let r=Math.round(a/6e4);if(r<60)return`${r}m`;let o=Math.round(r/60);return o<24?`${o}h`:s.toLocaleDateString([],{month:"short",day:"numeric"})}catch{return e}}getNextTaskLabel(e){let n=e.map(r=>r.nextRun).filter(Boolean).sort()[0];return n?`${e.find(r=>r.nextRun===n)?.agent??"unknown"} in ${this.formatNextRun(n)}`:"none"}getInitials(e){return e.split("-").map(s=>s[0]?.toUpperCase()??"").slice(0,2).join("")}renderAgentAvatar(e,s){let n=s.avatar?.trim();if(!n){(0,b.setIcon)(e,"bot");return}/^[a-z][a-z0-9-]*$/.test(n)?(0,b.setIcon)(e,n):e.setText(n)}getSkillIcon(e){return e.includes("git")?"settings":e.includes("summarize")||e.includes("log")?"activity":e.includes("review")||e.includes("check")?"check-circle-2":e.includes("vault")||e.includes("note")?"file-text":"puzzle"}renderInlineSchedule(e,s){let n=this.parseCronComponents(s.schedule),a=e.createDiv({cls:"af-form-row"});a.createDiv({cls:"af-form-label",text:"Frequency"});let r=a.createEl("select",{cls:"af-form-select"}),o=[["every_5m","Every 5 minutes"],["every_15m","Every 15 minutes"],["every_30m","Every 30 minutes"],["every_hour","Every hour"],["every_2h","Every 2 hours"],["daily","Daily"],["weekdays","Weekdays"],["weekly","Weekly"],["monthly","Monthly"]];for(let[w,S]of o){let T=r.createEl("option",{text:S,attr:{value:w}});w===n.freq&&(T.selected=!0)}let c=e.createDiv({cls:"af-form-row af-schedule-time-row"});c.createDiv({cls:"af-form-label",text:"Time"});let l=c.createDiv({cls:"af-schedule-time-selects"}),h=l.createEl("select",{cls:"af-form-select af-form-select-sm"});for(let w=0;w<24;w++){let S=w>=12?"PM":"AM",T=w===0?12:w>12?w-12:w,_=h.createEl("option",{text:`${T} ${S}`,attr:{value:String(w)}});w===n.hour&&(_.selected=!0)}l.createSpan({cls:"af-schedule-colon",text:":"});let d=l.createEl("select",{cls:"af-form-select af-form-select-sm"});for(let w=0;w<60;w+=5){let S=d.createEl("option",{text:String(w).padStart(2,"0"),attr:{value:String(w)}});w===n.minute&&(S.selected=!0)}let u=e.createDiv({cls:"af-form-row af-schedule-day-row"});u.createDiv({cls:"af-form-label",text:"Day"});let p=u.createDiv({cls:"af-schedule-day-buttons"}),m=["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],f=new Set(n.days);for(let w=0;w<7;w++){let S=p.createEl("button",{cls:`af-schedule-day-btn${f.has(w)?" active":""}`,text:m[w]});S.onclick=()=>{f.has(w)?f.delete(w):f.add(w),S.toggleClass("active",f.has(w)),k()}}let g=e.createDiv({cls:"af-form-row af-schedule-dom-row"});g.createDiv({cls:"af-form-label",text:"Day of month"});let y=g.createEl("select",{cls:"af-form-select af-form-select-sm"});for(let w=1;w<=28;w++){let S=y.createEl("option",{text:String(w),attr:{value:String(w)}});w===n.dayOfMonth&&(S.selected=!0)}let v=()=>{let w=r.value,S=["daily","weekdays","weekly","monthly"].includes(w),T=w==="weekly",_=w==="monthly";c.setCssStyles({display:S?"":"none"}),u.setCssStyles({display:T?"":"none"}),g.setCssStyles({display:_?"":"none"})},k=()=>{let w=r.value,S=h.value,T=d.value,_="";switch(w){case"every_5m":_="*/5 * * * *";break;case"every_15m":_="*/15 * * * *";break;case"every_30m":_="*/30 * * * *";break;case"every_hour":_="0 * * * *";break;case"every_2h":_="0 */2 * * *";break;case"daily":_=`${T} ${S} * * *`;break;case"weekdays":_=`${T} ${S} * * 1-5`;break;case"weekly":{let D=Array.from(f).sort().join(",")||"1";_=`${T} ${S} * * ${D}`;break}case"monthly":_=`${T} ${S} ${y.value} * *`;break}s.schedule=_,s.type="recurring"};r.addEventListener("change",()=>{v(),k()}),h.addEventListener("change",k),d.addEventListener("change",k),y.addEventListener("change",k),v()}renderHeartbeatSchedule(e,s){let n=this.parseCronComponents(s.heartbeatSchedule),a=e.createDiv({cls:"af-form-row"});a.createDiv({cls:"af-form-label",text:"Frequency"});let r=a.createEl("select",{cls:"af-form-select"}),o=[["every_5m","Every 5 minutes"],["every_15m","Every 15 minutes"],["every_30m","Every 30 minutes"],["every_hour","Every hour"],["every_2h","Every 2 hours"],["every_4h","Every 4 hours"],["every_6h","Every 6 hours"],["every_12h","Every 12 hours"],["daily","Once a day"]],c="every_hour",l={"*/5 * * * *":"every_5m","*/15 * * * *":"every_15m","*/30 * * * *":"every_30m","0 * * * *":"every_hour","0 */2 * * *":"every_2h","0 */4 * * *":"every_4h","0 */6 * * *":"every_6h","0 */12 * * *":"every_12h"};l[s.heartbeatSchedule]?c=l[s.heartbeatSchedule]:(n.freq==="daily"||n.freq==="weekdays")&&(c="daily");for(let[g,y]of o){let v=r.createEl("option",{text:y,attr:{value:g}});g===c&&(v.selected=!0)}let h=e.createDiv({cls:"af-form-row af-schedule-time-row"});h.createDiv({cls:"af-form-label",text:"Time"});let d=h.createDiv({cls:"af-schedule-time-selects"}),u=d.createEl("select",{cls:"af-form-select af-form-select-sm"});for(let g=0;g<24;g++){let y=g>=12?"PM":"AM",v=g===0?12:g>12?g-12:g,k=u.createEl("option",{text:`${v} ${y}`,attr:{value:String(g)}});g===n.hour&&(k.selected=!0)}d.createSpan({cls:"af-schedule-colon",text:":"});let p=d.createEl("select",{cls:"af-form-select af-form-select-sm"});for(let g=0;g<60;g+=5){let y=p.createEl("option",{text:String(g).padStart(2,"0"),attr:{value:String(g)}});g===n.minute&&(y.selected=!0)}let m=()=>{h.setCssStyles({display:r.value==="daily"?"":"none"})},f=()=>{let g=r.value,y=u.value,v=p.value;switch(g){case"every_5m":s.heartbeatSchedule="*/5 * * * *";break;case"every_15m":s.heartbeatSchedule="*/15 * * * *";break;case"every_30m":s.heartbeatSchedule="*/30 * * * *";break;case"every_hour":s.heartbeatSchedule="0 * * * *";break;case"every_2h":s.heartbeatSchedule="0 */2 * * *";break;case"every_4h":s.heartbeatSchedule="0 */4 * * *";break;case"every_6h":s.heartbeatSchedule="0 */6 * * *";break;case"every_12h":s.heartbeatSchedule="0 */12 * * *";break;case"daily":s.heartbeatSchedule=`${v} ${y} * * *`;break}};r.addEventListener("change",()=>{m(),f()}),u.addEventListener("change",f),p.addEventListener("change",f),m()}parseCronComponents(e){let s={freq:"daily",hour:9,minute:0,days:[1],dayOfMonth:1};if(!e?.trim())return s;let n={"*/5 * * * *":"every_5m","*/15 * * * *":"every_15m","*/30 * * * *":"every_30m","0 * * * *":"every_hour","0 */2 * * *":"every_2h"};if(n[e])return{...s,freq:n[e]};let a=e.trim().split(/\s+/);if(a.length!==5)return s;let[r,o,c,,l]=a,h=parseInt(o??"9",10),d=parseInt(r??"0",10);if(c==="*"&&l==="*")return{...s,freq:"daily",hour:h,minute:d};if(c==="*"&&l==="1-5")return{...s,freq:"weekdays",hour:h,minute:d};if(c==="*"&&l!=="*"){let u=(l??"1").split(",").map(p=>parseInt(p,10));return{...s,freq:"weekly",hour:h,minute:d,days:u}}return l==="*"&&c!=="*"?{...s,freq:"monthly",hour:h,minute:d,dayOfMonth:parseInt(c??"1",10)}:{...s,hour:h,minute:d}}humanizeCron(e){let s={"*/5 * * * *":"Every 5 minutes","*/10 * * * *":"Every 10 minutes","*/15 * * * *":"Every 15 minutes","*/30 * * * *":"Every 30 minutes","0 * * * *":"Every hour","0 */2 * * *":"Every 2 hours"};if(s[e])return s[e];let n=e.toLowerCase().trim();if(n.startsWith("every ")||n.startsWith("daily ")||n==="daily")return e;if(n.startsWith("hourly"))return"Every hour";if(n.startsWith("weekdays")||n.startsWith("weekly")||n.startsWith("monthly"))return e;let a=e.trim().split(/\s+/);if(a.length!==5)return e;let[r,o,c,,l]=a,h=(p,m)=>{let f=parseInt(p??"0",10),g=parseInt(m??"0",10),y=f>=12?"PM":"AM",v=f===0?12:f>12?f-12:f;return g===0?`${v} ${y}`:`${v}:${String(g).padStart(2,"0")} ${y}`},d=["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],u=p=>p==="1-5"?"weekdays":p==="0,6"?"weekends":p.split(",").map(f=>parseInt(f,10)).map(f=>d[f]??f).join(", ");return o==="*"&&c==="*"&&l==="*"?r==="*"?"Every minute":`Every hour at :${String(r).padStart(2,"0")}`:c==="*"&&l==="*"&&o!=="*"?`Daily at ${h(o??"0",r??"0")}`:c==="*"&&l==="1-5"&&o!=="*"?`Weekdays at ${h(o??"0",r??"0")}`:c==="*"&&l!=="*"&&o!=="*"?`${u(l??"1")} at ${h(o??"0",r??"0")}`:l==="*"&&c!=="*"&&o!=="*"?`Monthly on the ${c} at ${h(o??"0",r??"0")}`:e}getTagClass(e){return e==="monitoring"?"monitoring":e==="devops"?"devops":e==="sample"?"sample":"default"}renderCreateAgentPage(e){let s=e.createDiv({cls:"af-create-agent-page"}),n=s.createDiv({cls:"af-detail-header"}),a=n.createDiv({cls:"af-detail-header-left"}),r=a.createDiv({cls:"af-agent-card-avatar idle"});(0,b.setIcon)(r,"plus");let o=a.createDiv();o.createDiv({cls:"af-detail-header-name",text:"Create New Agent"}),o.createDiv({cls:"af-detail-header-desc",text:"Configure a new agent for your fleet"});let c=n.createDiv({cls:"af-detail-header-actions"}),l={name:"",description:"",avatar:"",tags:"",systemPrompt:"",model:"default",adapter:"claude-code",cwd:"",timeout:300,permissionMode:"bypassPermissions",effort:"",selectedSkills:new Set,selectedMcpServers:new Set,skillsBody:"",contextBody:"",approvalRequired:"",memory:!0,enabled:!0,allowedCommands:"",blockedCommands:"",heartbeatEnabled:!1,heartbeatSchedule:"0 */6 * * *",heartbeatBody:"",heartbeatNotify:!0,heartbeatChannel:"",heartbeatChannelTarget:"",autoCompactThreshold:85,wikiReferences:[]},h={none:{label:"None",prompt:""},coding:{label:"Coding Agent",prompt:`You are a coding agent. Review code, write tests, fix bugs, and implement features. Follow existing code conventions. Write clean, well-tested code. If something is unclear, ask for clarification instead of guessing.`},monitor:{label:"Monitor",prompt:`You are a monitoring agent. Check system status, alert on failures, and report on health metrics. Be concise and factual. Highlight anomalies clearly. @@ -12024,13 +12056,13 @@ Include timestamps and relevant context in all reports.`},briefing:{label:"Brief Prioritize recent and important changes. Keep summaries concise. End with explicit next actions if they exist.`},reviewer:{label:"Code Reviewer",prompt:`You are a code review agent. Analyze pull requests, suggest improvements, and flag potential issues. Focus on correctness, security, and maintainability. -Be specific \u2014 reference file names and line numbers.`}},d=s.createDiv({cls:"af-create-form"}),u=d.createDiv({cls:"af-create-section"}),p=u.createDiv({cls:"af-create-section-header"}),m=p.createSpan({cls:"af-create-section-icon"});(0,w.setIcon)(m,"user"),p.createSpan({text:"Identity"}),this.createFormField(u,"Name","deploy-watcher","Unique identifier (will be slugified)",B=>{l.name=B}),this.createFormField(u,"Description","Monitors deployments and reports status","",B=>{l.description=B});let f=u.createDiv({cls:"af-form-row"});f.createDiv({cls:"af-form-label",text:"Avatar"});let g=f.createEl("input",{cls:"af-form-input af-form-input-sm",attr:{type:"text",placeholder:"\u{1F6E1}\uFE0F"}});g.addEventListener("input",()=>{l.avatar=g.value}),this.createFormField(u,"Tags","devops, monitoring","Comma-separated",B=>{l.tags=B});let v=u.createDiv({cls:"af-form-row af-form-row-toggle"});v.createDiv({cls:"af-form-label",text:"Enabled"});let b=v.createDiv({cls:"af-agent-card-toggle on"});b.onclick=()=>{let B=b.hasClass("on");b.toggleClass("on",!B),l.enabled=!B};let k=d.createDiv({cls:"af-create-section"}),y=k.createDiv({cls:"af-create-section-header"}),S=y.createSpan({cls:"af-create-section-icon"});(0,w.setIcon)(S,"message-square"),y.createSpan({text:"System Prompt"});let T=k.createDiv({cls:"af-form-row"});T.createDiv({cls:"af-form-label",text:"Template"});let _=T.createEl("select",{cls:"af-form-select"});for(let[B,{label:V}]of Object.entries(h))_.createEl("option",{text:V,attr:{value:B}});let D=k.createEl("textarea",{cls:"af-create-prompt-textarea",attr:{placeholder:"You are a deployment monitoring agent...",rows:"10"}});D.addEventListener("input",()=>{l.systemPrompt=D.value}),_.addEventListener("change",()=>{let B=h[_.value];B&&_.value!=="none"&&(l.systemPrompt=B.prompt,D.value=B.prompt)});let M=d.createDiv({cls:"af-create-section"}),C=M.createDiv({cls:"af-create-section-header"}),I=C.createSpan({cls:"af-create-section-icon"});(0,w.setIcon)(I,"settings"),C.createSpan({text:"Runtime Config"});let P=M.createDiv({cls:"af-create-config-grid"}),L=P.createDiv({cls:"af-form-row"});L.createDiv({cls:"af-form-label",text:"Adapter"});let F=L.createEl("select",{cls:"af-form-select"});for(let[B,V,Pe]of Fo){let we=F.createEl("option",{text:V,attr:{value:B,...Pe?{disabled:"true"}:{}}});B==="claude-code"&&(we.selected=!0)}let z=P.createDiv({cls:"af-form-row"}),U=z.createDiv({cls:"af-form-label",text:"Model"});this.addTooltip(U,`Aliases (opus/sonnet/haiku/opusplan) work on any backend. Choose Custom\u2026 for a pinned ID or Bedrock/Vertex/Foundry. Blank = use Settings default (${this.plugin.settings.defaultModel||"CLI default"}).`);let Z=z.createDiv({cls:"af-form-field-wrap"}),de=()=>{At(Z,{value:l.model,adapter:l.adapter,onChange:B=>{l.model=B}})};de();let me=()=>{};F.addEventListener("change",()=>{l.adapter=F.value,(Ds(l.adapter)?Js:Xs).some(V=>V.value===l.model.trim())&&(l.model=""),de(),l.permissionMode=jn(l.permissionMode,l.adapter),me()});let ye=P.createDiv({cls:"af-form-row"});ye.createDiv({cls:"af-form-label",text:"Working Dir"});let X=ye.createEl("input",{cls:"af-form-input",attr:{type:"text",placeholder:"Leave empty for vault root"}});X.addEventListener("input",()=>{l.cwd=X.value});let j=P.createDiv({cls:"af-form-row"});j.createDiv({cls:"af-form-label",text:"Timeout (sec)"});let K=j.createEl("input",{cls:"af-form-input af-form-input-sm",attr:{type:"number",value:"300"}});K.addEventListener("input",()=>{let B=parseInt(K.value,10);!isNaN(B)&&B>0&&(l.timeout=B)});let ne=P.createDiv({cls:"af-form-row"});ne.createDiv({cls:"af-form-label",text:"Permission Mode"});let G=ne.createEl("select",{cls:"af-form-select"}),ee=P.createDiv({cls:"af-form-hint",text:""});me=()=>{l.permissionMode=jn(l.permissionMode,l.adapter);let B=$n(l.adapter);G.empty();for(let[V,Pe]of B){let we=G.createEl("option",{text:Pe,attr:{value:V}});V===l.permissionMode&&(we.selected=!0)}ee.textContent=B.find(([V])=>V===G.value)?.[2]??""},me(),G.addEventListener("change",()=>{l.permissionMode=G.value,ee.textContent=$n(l.adapter).find(([B])=>B===G.value)?.[2]??""});let Q=P.createDiv({cls:"af-form-row"});Q.createDiv({cls:"af-form-label",text:"Effort Level"});let ae=Q.createEl("select",{cls:"af-form-select"});for(let[B,V]of[["","Default"],["low","Low"],["medium","Medium"],["high","High"],["max","Max"]])ae.createEl("option",{text:V,attr:{value:B}});ae.addEventListener("change",()=>{l.effort=ae.value}),P.createDiv({cls:"af-form-hint",text:"Controls reasoning depth \u2014 low is fast, max is most thorough"});let ve=P.createDiv({cls:"af-form-row"}),Re=ve.createDiv({cls:"af-form-label",text:"Auto-compact at"});this.addTooltip(Re,"Percent of context window at which the chat auto-invokes /compact before the next message. 85% is a good default. Set 0 to disable.");let Le=ve.createEl("input",{cls:"af-form-input af-form-input-sm",attr:{type:"number",min:"0",max:"100",value:String(l.autoCompactThreshold)}});Le.addEventListener("input",()=>{let B=parseInt(Le.value,10);!isNaN(B)&&B>=0&&B<=100&&(l.autoCompactThreshold=B)}),P.createDiv({cls:"af-form-hint",text:"0 disables auto-compact"});{let B=this.plugin.runtime.getSnapshot().agents.filter(V=>V.wikiKeeper!==void 0);if(B.length>0){let V=P.createDiv({cls:"af-form-row af-form-row-toggle"}),Pe=V.createDiv({cls:"af-form-label",text:"Wiki access"});this.addTooltip(Pe,"Lets this agent read + cite from the selected Wiki Keeper scopes (requires the wiki-query skill).");let we=V.createDiv({cls:"af-form-field-wrap"});for(let Ee of B){let et=we.createEl("label",{cls:"af-form-checkbox-row"}),We=et.createEl("input",{attr:{type:"checkbox"}});et.createSpan({text:` ${Ee.name}`,cls:"af-form-checkbox-label"}),We.addEventListener("change",()=>{We.checked?l.wikiReferences.includes(Ee.name)||l.wikiReferences.push(Ee.name):l.wikiReferences=l.wikiReferences.filter(St=>St!==Ee.name)})}}}{let B=d.createDiv({cls:"af-create-section"}),V=B.createDiv({cls:"af-create-section-header"}),Pe=V.createSpan({cls:"af-create-section-icon"});(0,w.setIcon)(Pe,"heart-pulse");let we=V.createSpan({text:"Heartbeat"});this.addTooltip(we,"Autonomous periodic run \u2014 what the agent does when no one is asking");let Ee=B.createDiv({cls:"af-form-row af-form-row-toggle"});Ee.createDiv({cls:"af-form-label",text:"Enabled"});let et=Ee.createDiv({cls:"af-agent-card-toggle"}),We=B.createDiv();We.setCssStyles({display:"none"}),et.onclick=()=>{let Se=et.hasClass("on");et.toggleClass("on",!Se),l.heartbeatEnabled=!Se,We.setCssStyles({display:Se?"none":""})},this.renderHeartbeatSchedule(We,l);let St=We.createDiv({cls:"af-form-row af-form-row-toggle"}),Ot=St.createDiv({cls:"af-form-label"});Ot.setText("Notify"),this.addTooltip(Ot,"Show an Obsidian notice when the heartbeat completes");let vt=St.createDiv({cls:"af-agent-card-toggle on"});vt.onclick=()=>{let Se=vt.hasClass("on");vt.toggleClass("on",!Se),l.heartbeatNotify=!Se};let ls=this.plugin.runtime.getSnapshot(),Ls=We.createDiv({cls:"af-form-row"}),Nt=Ls.createDiv({cls:"af-form-label"});Nt.setText("Post to channel"),this.addTooltip(Nt,"Heartbeat results are posted to this Slack channel when the run completes");let O=Ls.createEl("select",{cls:"af-form-select"});O.createEl("option",{text:"(none)",attr:{value:""}});for(let Se of ls.channels)O.createEl("option",{text:Se.name,attr:{value:Se.name}});O.addEventListener("change",()=>{l.heartbeatChannel=O.value});let Y=We.createDiv({cls:"af-form-label"});Y.setCssStyles({width:"auto"}),Y.setCssStyles({marginTop:"12px"}),Y.setText("Instruction"),this.addTooltip(Y,'What the agent does on each heartbeat. Also used by the "Run Now" button.');let _e=We.createEl("textarea",{cls:"af-create-prompt-textarea",attr:{placeholder:"Check status, scan for issues, report findings...",rows:"8"}});_e.addEventListener("input",()=>{l.heartbeatBody=_e.value})}let Me=d.createDiv({cls:"af-create-section"}),Fe=Me.createDiv({cls:"af-create-section-header"}),ke=Fe.createSpan({cls:"af-create-section-icon"});(0,w.setIcon)(ke,"puzzle"),Fe.createSpan({text:"Skills"});let Oe=this.plugin.runtime.getSnapshot();if(Oe.skills.length>0){Me.createDiv({cls:"af-form-sublabel",text:"Shared Skills"});let B=Me.createDiv({cls:"af-create-skills-grid"});for(let V of Oe.skills){let Pe=B.createDiv({cls:"af-create-skill-item"}),we=Pe.createEl("input",{cls:"af-form-toggle",attr:{type:"checkbox"}});we.addEventListener("change",()=>{we.checked?l.selectedSkills.add(V.name):l.selectedSkills.delete(V.name)});let Ee=Pe.createDiv({cls:"af-create-skill-label"});Ee.createSpan({cls:"af-create-skill-name",text:V.name}),V.description&&Ee.createSpan({cls:"af-create-skill-desc",text:` \u2014 ${V.description}`})}}let Ue=Me.createDiv({cls:"af-form-sublabel"});Ue.setText("Agent-specific skills"),this.addTooltip(Ue,"Custom skills/instructions only for this agent, not shared with others");let Ne=Me.createEl("textarea",{cls:"af-create-textarea",attr:{placeholder:"Custom skills/instructions for this agent...",rows:"4"}});Ne.addEventListener("input",()=>{l.skillsBody=Ne.value});{let B=d.createDiv({cls:"af-create-section"}),V=B.createDiv({cls:"af-create-section-header"}),Pe=V.createSpan({cls:"af-create-section-icon"});(0,w.setIcon)(Pe,"plug");let we=V.createSpan({text:"MCP Servers"});this.addTooltip(we,"Grant agent access to MCP servers"),this.renderAgentMcpPicker(B,l.selectedMcpServers)}let H=d.createDiv({cls:"af-create-section"}),ie=H.createDiv({cls:"af-create-section-header"}),$=ie.createSpan({cls:"af-create-section-icon"});(0,w.setIcon)($,"file-text");let Te=ie.createSpan({text:"Context"});this.addTooltip(Te,"Project-specific context included in every run");let xe=H.createEl("textarea",{cls:"af-create-textarea",attr:{placeholder:"Background info, repo structure, conventions...",rows:"4"}});xe.addEventListener("input",()=>{l.contextBody=xe.value});let he=d.createDiv({cls:"af-create-section"}),$e=he.createDiv({cls:"af-create-section-header"}),re=$e.createSpan({cls:"af-create-section-icon"});(0,w.setIcon)(re,"shield-check"),$e.createSpan({text:"Permissions"}),this.createFormField(he,"Approval required","git_push, file_delete","Comma-separated tool names",B=>{l.approvalRequired=B});let R=he.createDiv({cls:"af-form-row"});R.createDiv({cls:"af-form-label",text:"Allowed Commands"});let se=R.createEl("textarea",{cls:"af-create-textarea",attr:{placeholder:`Bash(curl *) +Be specific \u2014 reference file names and line numbers.`}},d=s.createDiv({cls:"af-create-form"}),u=d.createDiv({cls:"af-create-section"}),p=u.createDiv({cls:"af-create-section-header"}),m=p.createSpan({cls:"af-create-section-icon"});(0,b.setIcon)(m,"user"),p.createSpan({text:"Identity"}),this.createFormField(u,"Name","deploy-watcher","Unique identifier (will be slugified)",B=>{l.name=B}),this.createFormField(u,"Description","Monitors deployments and reports status","",B=>{l.description=B});let f=u.createDiv({cls:"af-form-row"});f.createDiv({cls:"af-form-label",text:"Avatar"});let g=f.createEl("input",{cls:"af-form-input af-form-input-sm",attr:{type:"text",placeholder:"\u{1F6E1}\uFE0F"}});g.addEventListener("input",()=>{l.avatar=g.value}),this.createFormField(u,"Tags","devops, monitoring","Comma-separated",B=>{l.tags=B});let y=u.createDiv({cls:"af-form-row af-form-row-toggle"});y.createDiv({cls:"af-form-label",text:"Enabled"});let v=y.createDiv({cls:"af-agent-card-toggle on"});v.onclick=()=>{let B=v.hasClass("on");v.toggleClass("on",!B),l.enabled=!B};let k=d.createDiv({cls:"af-create-section"}),w=k.createDiv({cls:"af-create-section-header"}),S=w.createSpan({cls:"af-create-section-icon"});(0,b.setIcon)(S,"message-square"),w.createSpan({text:"System Prompt"});let T=k.createDiv({cls:"af-form-row"});T.createDiv({cls:"af-form-label",text:"Template"});let _=T.createEl("select",{cls:"af-form-select"});for(let[B,{label:V}]of Object.entries(h))_.createEl("option",{text:V,attr:{value:B}});let D=k.createEl("textarea",{cls:"af-create-prompt-textarea",attr:{placeholder:"You are a deployment monitoring agent...",rows:"10"}});D.addEventListener("input",()=>{l.systemPrompt=D.value}),_.addEventListener("change",()=>{let B=h[_.value];B&&_.value!=="none"&&(l.systemPrompt=B.prompt,D.value=B.prompt)});let O=d.createDiv({cls:"af-create-section"}),C=O.createDiv({cls:"af-create-section-header"}),E=C.createSpan({cls:"af-create-section-icon"});(0,b.setIcon)(E,"settings"),C.createSpan({text:"Runtime Config"});let P=O.createDiv({cls:"af-create-config-grid"}),N=P.createDiv({cls:"af-form-row"});N.createDiv({cls:"af-form-label",text:"Adapter"});let M=N.createEl("select",{cls:"af-form-select"});for(let[B,V,Ee]of Jo){let we=M.createEl("option",{text:V,attr:{value:B,...Ee?{disabled:"true"}:{}}});B==="claude-code"&&(we.selected=!0)}let U=P.createDiv({cls:"af-form-row"}),$=U.createDiv({cls:"af-form-label",text:"Model"});this.addTooltip($,`Aliases (opus/sonnet/haiku/opusplan) work on any backend. Choose Custom\u2026 for a pinned ID or Bedrock/Vertex/Foundry. Blank = use Settings default (${this.plugin.settings.defaultModel||"CLI default"}).`);let Z=U.createDiv({cls:"af-form-field-wrap"}),de=()=>{Pt(Z,{value:l.model,adapter:l.adapter,onChange:B=>{l.model=B}})};de();let me=()=>{};M.addEventListener("change",()=>{l.adapter=M.value,(Ls(l.adapter)?Qs:Zs).some(V=>V.value===l.model.trim())&&(l.model=""),de(),l.permissionMode=Hn(l.permissionMode,l.adapter),me()});let ge=P.createDiv({cls:"af-form-row"});ge.createDiv({cls:"af-form-label",text:"Working Dir"});let X=ge.createEl("input",{cls:"af-form-input",attr:{type:"text",placeholder:"Leave empty for vault root"}});X.addEventListener("input",()=>{l.cwd=X.value});let W=P.createDiv({cls:"af-form-row"});W.createDiv({cls:"af-form-label",text:"Timeout (sec)"});let K=W.createEl("input",{cls:"af-form-input af-form-input-sm",attr:{type:"number",value:"300"}});K.addEventListener("input",()=>{let B=parseInt(K.value,10);!isNaN(B)&&B>0&&(l.timeout=B)});let ne=P.createDiv({cls:"af-form-row"});ne.createDiv({cls:"af-form-label",text:"Permission Mode"});let G=ne.createEl("select",{cls:"af-form-select"}),ee=P.createDiv({cls:"af-form-hint",text:""});me=()=>{l.permissionMode=Hn(l.permissionMode,l.adapter);let B=Wn(l.adapter);G.empty();for(let[V,Ee]of B){let we=G.createEl("option",{text:Ee,attr:{value:V}});V===l.permissionMode&&(we.selected=!0)}ee.textContent=B.find(([V])=>V===G.value)?.[2]??""},me(),G.addEventListener("change",()=>{l.permissionMode=G.value,ee.textContent=Wn(l.adapter).find(([B])=>B===G.value)?.[2]??""});let Q=P.createDiv({cls:"af-form-row"});Q.createDiv({cls:"af-form-label",text:"Effort Level"});let ae=Q.createEl("select",{cls:"af-form-select"});for(let[B,V]of[["","Default"],["low","Low"],["medium","Medium"],["high","High"],["max","Max"]])ae.createEl("option",{text:V,attr:{value:B}});ae.addEventListener("change",()=>{l.effort=ae.value}),P.createDiv({cls:"af-form-hint",text:"Controls reasoning depth \u2014 low is fast, max is most thorough"});let ve=P.createDiv({cls:"af-form-row"}),Pe=ve.createDiv({cls:"af-form-label",text:"Auto-compact at"});this.addTooltip(Pe,"Percent of context window at which the chat auto-invokes /compact before the next message. 85% is a good default. Set 0 to disable.");let Ie=ve.createEl("input",{cls:"af-form-input af-form-input-sm",attr:{type:"number",min:"0",max:"100",value:String(l.autoCompactThreshold)}});Ie.addEventListener("input",()=>{let B=parseInt(Ie.value,10);!isNaN(B)&&B>=0&&B<=100&&(l.autoCompactThreshold=B)}),P.createDiv({cls:"af-form-hint",text:"0 disables auto-compact"});{let B=this.plugin.runtime.getSnapshot().agents.filter(V=>V.wikiKeeper!==void 0);if(B.length>0){let V=P.createDiv({cls:"af-form-row af-form-row-toggle"}),Ee=V.createDiv({cls:"af-form-label",text:"Wiki access"});this.addTooltip(Ee,"Lets this agent read + cite from the selected Wiki Keeper scopes (requires the wiki-query skill).");let we=V.createDiv({cls:"af-form-field-wrap"});for(let Ae of B){let nt=we.createEl("label",{cls:"af-form-checkbox-row"}),je=nt.createEl("input",{attr:{type:"checkbox"}});nt.createSpan({text:` ${Ae.name}`,cls:"af-form-checkbox-label"}),je.addEventListener("change",()=>{je.checked?l.wikiReferences.includes(Ae.name)||l.wikiReferences.push(Ae.name):l.wikiReferences=l.wikiReferences.filter(Tt=>Tt!==Ae.name)})}}}{let B=d.createDiv({cls:"af-create-section"}),V=B.createDiv({cls:"af-create-section-header"}),Ee=V.createSpan({cls:"af-create-section-icon"});(0,b.setIcon)(Ee,"heart-pulse");let we=V.createSpan({text:"Heartbeat"});this.addTooltip(we,"Autonomous periodic run \u2014 what the agent does when no one is asking");let Ae=B.createDiv({cls:"af-form-row af-form-row-toggle"});Ae.createDiv({cls:"af-form-label",text:"Enabled"});let nt=Ae.createDiv({cls:"af-agent-card-toggle"}),je=B.createDiv();je.setCssStyles({display:"none"}),nt.onclick=()=>{let ze=nt.hasClass("on");nt.toggleClass("on",!ze),l.heartbeatEnabled=!ze,je.setCssStyles({display:ze?"none":""})},this.renderHeartbeatSchedule(je,l);let Tt=je.createDiv({cls:"af-form-row af-form-row-toggle"}),Bt=Tt.createDiv({cls:"af-form-label"});Bt.setText("Notify"),this.addTooltip(Bt,"Show an Obsidian notice when the heartbeat completes");let bt=Tt.createDiv({cls:"af-agent-card-toggle on"});bt.onclick=()=>{let ze=bt.hasClass("on");bt.toggleClass("on",!ze),l.heartbeatNotify=!ze};let ds=this.plugin.runtime.getSnapshot(),Ns=je.createDiv({cls:"af-form-row"}),Ut=Ns.createDiv({cls:"af-form-label"});Ut.setText("Post to channel"),this.addTooltip(Ut,"Heartbeat results are posted to this Slack channel when the run completes");let L=Ns.createEl("select",{cls:"af-form-select"});L.createEl("option",{text:"(none)",attr:{value:""}});for(let ze of ds.channels)L.createEl("option",{text:ze.name,attr:{value:ze.name}});L.addEventListener("change",()=>{l.heartbeatChannel=L.value,qe()});let Y=je.createDiv({cls:"af-form-row"}),Te=Y.createDiv({cls:"af-form-label"});Te.setText("Target ID"),this.addTooltip(Te,"Specific channel id to post to (Discord/Slack channel id, Telegram chat id). Empty = DM the channel\u2019s first allowed user.");let Le=Y.createEl("input",{cls:"af-form-input",attr:{type:"text",placeholder:"Channel/chat id \u2014 empty = DM"}});Le.value=l.heartbeatChannelTarget,Le.addEventListener("input",()=>{l.heartbeatChannelTarget=Le.value.trim()});let qe=()=>{Y.setCssStyles({display:l.heartbeatChannel?"":"none"})};qe();let at=je.createDiv({cls:"af-form-label"});at.setCssStyles({width:"auto"}),at.setCssStyles({marginTop:"12px"}),at.setText("Instruction"),this.addTooltip(at,'What the agent does on each heartbeat. Also used by the "Run Now" button.');let We=je.createEl("textarea",{cls:"af-create-prompt-textarea",attr:{placeholder:"Check status, scan for issues, report findings...",rows:"8"}});We.addEventListener("input",()=>{l.heartbeatBody=We.value})}let Me=d.createDiv({cls:"af-create-section"}),Fe=Me.createDiv({cls:"af-create-section-header"}),ke=Fe.createSpan({cls:"af-create-section-icon"});(0,b.setIcon)(ke,"puzzle"),Fe.createSpan({text:"Skills"});let Oe=this.plugin.runtime.getSnapshot();if(Oe.skills.length>0){Me.createDiv({cls:"af-form-sublabel",text:"Shared Skills"});let B=Me.createDiv({cls:"af-create-skills-grid"});for(let V of Oe.skills){let Ee=B.createDiv({cls:"af-create-skill-item"}),we=Ee.createEl("input",{cls:"af-form-toggle",attr:{type:"checkbox"}});we.addEventListener("change",()=>{we.checked?l.selectedSkills.add(V.name):l.selectedSkills.delete(V.name)});let Ae=Ee.createDiv({cls:"af-create-skill-label"});Ae.createSpan({cls:"af-create-skill-name",text:V.name}),V.description&&Ae.createSpan({cls:"af-create-skill-desc",text:` \u2014 ${V.description}`})}}let Ue=Me.createDiv({cls:"af-form-sublabel"});Ue.setText("Agent-specific skills"),this.addTooltip(Ue,"Custom skills/instructions only for this agent, not shared with others");let Ne=Me.createEl("textarea",{cls:"af-create-textarea",attr:{placeholder:"Custom skills/instructions for this agent...",rows:"4"}});Ne.addEventListener("input",()=>{l.skillsBody=Ne.value});{let B=d.createDiv({cls:"af-create-section"}),V=B.createDiv({cls:"af-create-section-header"}),Ee=V.createSpan({cls:"af-create-section-icon"});(0,b.setIcon)(Ee,"plug");let we=V.createSpan({text:"MCP Servers"});this.addTooltip(we,"Grant agent access to MCP servers"),this.renderAgentMcpPicker(B,l.selectedMcpServers)}let q=d.createDiv({cls:"af-create-section"}),ie=q.createDiv({cls:"af-create-section-header"}),j=ie.createSpan({cls:"af-create-section-icon"});(0,b.setIcon)(j,"file-text");let Ce=ie.createSpan({text:"Context"});this.addTooltip(Ce,"Project-specific context included in every run");let xe=q.createEl("textarea",{cls:"af-create-textarea",attr:{placeholder:"Background info, repo structure, conventions...",rows:"4"}});xe.addEventListener("input",()=>{l.contextBody=xe.value});let he=d.createDiv({cls:"af-create-section"}),$e=he.createDiv({cls:"af-create-section-header"}),re=$e.createSpan({cls:"af-create-section-icon"});(0,b.setIcon)(re,"shield-check"),$e.createSpan({text:"Permissions"}),this.createFormField(he,"Approval required","git_push, file_delete","Comma-separated tool names",B=>{l.approvalRequired=B});let I=he.createDiv({cls:"af-form-row"});I.createDiv({cls:"af-form-label",text:"Allowed Commands"});let se=I.createEl("textarea",{cls:"af-create-textarea",attr:{placeholder:`Bash(curl *) Bash(python3 *) Read Edit Write`,rows:"4"}});se.addEventListener("input",()=>{l.allowedCommands=se.value});let ce=he.createDiv({cls:"af-form-row"});ce.createDiv({cls:"af-form-label",text:"Blocked Commands"});let te=ce.createEl("textarea",{cls:"af-create-textarea",attr:{placeholder:`Bash(git push *) Bash(rm -rf *) -Bash(sudo *)`,rows:"4"}});te.addEventListener("input",()=>{l.blockedCommands=te.value}),he.createDiv({cls:"af-form-hint",text:"On Codex agents these become execpolicy command rules \u2014 only Bash(cmd args *) prefixes are enforced; tool-name rules (Read/Write) and mid-pattern wildcards are ignored, and file/network access is governed by Permission Mode (the sandbox)."});let Ze=he.createDiv({cls:"af-form-row"});Ze.createDiv({cls:"af-form-label",text:"Memory enabled"});let is=Ze.createDiv({cls:"af-agent-card-toggle on"});is.onclick=()=>{let B=is.hasClass("on");is.toggleClass("on",!B),l.memory=!B};let rs=s.createDiv({cls:"af-create-footer"}),os=rs.createEl("button",{cls:"af-btn-sm",text:"Cancel"});os.onclick=()=>this.navigate("agents");let Ft=rs.createEl("button",{cls:"af-btn-sm primary af-create-submit"});E(Ft,"plus","af-btn-icon"),Ft.appendText(" Create Agent"),Ft.onclick=async()=>{let B=l.name.trim();if(!B){new w.Notice("Agent name is required.");return}let V=oe(B);if(this.plugin.repository.getAgentByName(V)){new w.Notice(`Agent "${V}" already exists.`);return}let Pe=we=>we.split(",").map(Ee=>Ee.trim()).filter(Boolean);try{let we=Ee=>ge(Ee).map(et=>et.trim()).filter(Boolean);await this.plugin.repository.createAgentFolder({name:V,description:l.description.trim(),avatar:l.avatar.trim(),tags:Pe(l.tags),systemPrompt:l.systemPrompt.trim(),model:l.model.trim()||"default",adapter:l.adapter,cwd:l.cwd.trim(),timeout:l.timeout,permissionMode:l.permissionMode,effort:l.effort||void 0,approvalRequired:Pe(l.approvalRequired),memory:l.memory,memoryMaxEntries:100,skills:Array.from(l.selectedSkills),mcpServers:Array.from(l.selectedMcpServers),skillsBody:l.skillsBody.trim(),contextBody:l.contextBody.trim(),enabled:l.enabled,permissionRules:{allow:we(l.allowedCommands),deny:we(l.blockedCommands)},autoCompactThreshold:l.autoCompactThreshold,wikiReferences:l.wikiReferences}),l.heartbeatEnabled&&l.heartbeatBody.trim()&&await this.plugin.repository.updateHeartbeat(V,{enabled:l.heartbeatEnabled,schedule:l.heartbeatSchedule.trim(),notify:l.heartbeatNotify,channel:l.heartbeatChannel,body:l.heartbeatBody.trim()}),new w.Notice(`Agent "${V}" created.`),await this.plugin.refreshFromVault(),this.navigate("agent-detail",V)}catch(we){let Ee=we instanceof Error?we.message:String(we);new w.Notice(`Failed to create agent: ${Ee}`)}}}renderCreateSkillPage(e){let s=e.createDiv({cls:"af-create-agent-page"}),n=s.createDiv({cls:"af-detail-header"}),a=n.createDiv({cls:"af-detail-header-left"}),r=a.createDiv({cls:"af-agent-card-avatar idle"});(0,w.setIcon)(r,"plus");let o=a.createDiv();o.createDiv({cls:"af-detail-header-name",text:"Create New Skill"}),o.createDiv({cls:"af-detail-header-desc",text:"Define a reusable skill for your agents"});let c=n.createDiv({cls:"af-detail-header-actions"}),l={name:"",description:"",tags:"",body:"",toolsBody:"",referencesBody:"",examplesBody:""},h={none:{label:"None",prompt:""},cli:{label:"CLI Tool Wrapper",prompt:`You are using the {{tool}} CLI. All operations go through the wrapper script. +Bash(sudo *)`,rows:"4"}});te.addEventListener("input",()=>{l.blockedCommands=te.value}),he.createDiv({cls:"af-form-hint",text:"On Codex agents these become execpolicy command rules \u2014 only Bash(cmd args *) prefixes are enforced; tool-name rules (Read/Write) and mid-pattern wildcards are ignored, and file/network access is governed by Permission Mode (the sandbox)."});let st=he.createDiv({cls:"af-form-row"});st.createDiv({cls:"af-form-label",text:"Memory enabled"});let os=st.createDiv({cls:"af-agent-card-toggle on"});os.onclick=()=>{let B=os.hasClass("on");os.toggleClass("on",!B),l.memory=!B};let ls=s.createDiv({cls:"af-create-footer"}),cs=ls.createEl("button",{cls:"af-btn-sm",text:"Cancel"});cs.onclick=()=>this.navigate("agents");let Nt=ls.createEl("button",{cls:"af-btn-sm primary af-create-submit"});R(Nt,"plus","af-btn-icon"),Nt.appendText(" Create Agent"),Nt.onclick=async()=>{let B=l.name.trim();if(!B){new b.Notice("Agent name is required.");return}let V=oe(B);if(this.plugin.repository.getAgentByName(V)){new b.Notice(`Agent "${V}" already exists.`);return}let Ee=we=>we.split(",").map(Ae=>Ae.trim()).filter(Boolean);try{let we=Ae=>ye(Ae).map(nt=>nt.trim()).filter(Boolean);await this.plugin.repository.createAgentFolder({name:V,description:l.description.trim(),avatar:l.avatar.trim(),tags:Ee(l.tags),systemPrompt:l.systemPrompt.trim(),model:l.model.trim()||"default",adapter:l.adapter,cwd:l.cwd.trim(),timeout:l.timeout,permissionMode:l.permissionMode,effort:l.effort||void 0,approvalRequired:Ee(l.approvalRequired),memory:l.memory,memoryMaxEntries:100,skills:Array.from(l.selectedSkills),mcpServers:Array.from(l.selectedMcpServers),skillsBody:l.skillsBody.trim(),contextBody:l.contextBody.trim(),enabled:l.enabled,permissionRules:{allow:we(l.allowedCommands),deny:we(l.blockedCommands)},autoCompactThreshold:l.autoCompactThreshold,wikiReferences:l.wikiReferences}),l.heartbeatEnabled&&l.heartbeatBody.trim()&&await this.plugin.repository.updateHeartbeat(V,{enabled:l.heartbeatEnabled,schedule:l.heartbeatSchedule.trim(),notify:l.heartbeatNotify,channel:l.heartbeatChannel,channelTarget:l.heartbeatChannel?l.heartbeatChannelTarget:"",body:l.heartbeatBody.trim()}),new b.Notice(`Agent "${V}" created.`),await this.plugin.refreshFromVault(),this.navigate("agent-detail",V)}catch(we){let Ae=we instanceof Error?we.message:String(we);new b.Notice(`Failed to create agent: ${Ae}`)}}}renderCreateSkillPage(e){let s=e.createDiv({cls:"af-create-agent-page"}),n=s.createDiv({cls:"af-detail-header"}),a=n.createDiv({cls:"af-detail-header-left"}),r=a.createDiv({cls:"af-agent-card-avatar idle"});(0,b.setIcon)(r,"plus");let o=a.createDiv();o.createDiv({cls:"af-detail-header-name",text:"Create New Skill"}),o.createDiv({cls:"af-detail-header-desc",text:"Define a reusable skill for your agents"});let c=n.createDiv({cls:"af-detail-header-actions"}),l={name:"",description:"",tags:"",body:"",toolsBody:"",referencesBody:"",examplesBody:""},h={none:{label:"None",prompt:""},cli:{label:"CLI Tool Wrapper",prompt:`You are using the {{tool}} CLI. All operations go through the wrapper script. Requirements: - Ensure required environment variables are set @@ -12067,34 +12099,34 @@ Key behaviors: - Use tables and charts where appropriate - Always state the time range and filters applied - Flag anomalies and outliers explicitly -- End with actionable insights, not just observations`}},d=s.createDiv({cls:"af-create-form"}),u=d.createDiv({cls:"af-create-section"}),p=u.createDiv({cls:"af-create-section-header"}),m=p.createSpan({cls:"af-create-section-icon"});(0,w.setIcon)(m,"puzzle"),p.createSpan({text:"Identity"}),this.createFormField(u,"Name","todoist","Unique identifier (will be slugified)",K=>{l.name=K}),this.createFormField(u,"Description","Manage tasks and projects via CLI","",K=>{l.description=K}),this.createFormField(u,"Tags","productivity, tasks","Comma-separated",K=>{l.tags=K});let f=d.createDiv({cls:"af-create-section"}),g=f.createDiv({cls:"af-create-section-header"}),v=g.createSpan({cls:"af-create-section-icon"});(0,w.setIcon)(v,"file-text"),g.createSpan({text:"Core Instructions"});let b=f.createDiv({cls:"af-form-row"});b.createDiv({cls:"af-form-label",text:"Template"});let k=b.createEl("select",{cls:"af-form-select"});for(let[K,{label:ne}]of Object.entries(h))k.createEl("option",{text:ne,attr:{value:K}});let y=f.createEl("textarea",{cls:"af-create-prompt-textarea",attr:{placeholder:"Skill instructions \u2014 what does this skill do and how should agents use it?",rows:"10"}});y.addEventListener("input",()=>{l.body=y.value}),k.addEventListener("change",()=>{let K=h[k.value];K&&k.value!=="none"&&(l.body=K.prompt,y.value=K.prompt)});let S=d.createDiv({cls:"af-create-section"}),T=S.createDiv({cls:"af-create-section-header"}),_=T.createSpan({cls:"af-create-section-icon"});(0,w.setIcon)(_,"wrench");let D=T.createSpan({text:"Tools"});this.addTooltip(D,"CLI commands, API endpoints, and tool definitions available to agents using this skill");let M=S.createEl("textarea",{cls:"af-create-prompt-textarea",attr:{placeholder:`## Commands +- End with actionable insights, not just observations`}},d=s.createDiv({cls:"af-create-form"}),u=d.createDiv({cls:"af-create-section"}),p=u.createDiv({cls:"af-create-section-header"}),m=p.createSpan({cls:"af-create-section-icon"});(0,b.setIcon)(m,"puzzle"),p.createSpan({text:"Identity"}),this.createFormField(u,"Name","todoist","Unique identifier (will be slugified)",K=>{l.name=K}),this.createFormField(u,"Description","Manage tasks and projects via CLI","",K=>{l.description=K}),this.createFormField(u,"Tags","productivity, tasks","Comma-separated",K=>{l.tags=K});let f=d.createDiv({cls:"af-create-section"}),g=f.createDiv({cls:"af-create-section-header"}),y=g.createSpan({cls:"af-create-section-icon"});(0,b.setIcon)(y,"file-text"),g.createSpan({text:"Core Instructions"});let v=f.createDiv({cls:"af-form-row"});v.createDiv({cls:"af-form-label",text:"Template"});let k=v.createEl("select",{cls:"af-form-select"});for(let[K,{label:ne}]of Object.entries(h))k.createEl("option",{text:ne,attr:{value:K}});let w=f.createEl("textarea",{cls:"af-create-prompt-textarea",attr:{placeholder:"Skill instructions \u2014 what does this skill do and how should agents use it?",rows:"10"}});w.addEventListener("input",()=>{l.body=w.value}),k.addEventListener("change",()=>{let K=h[k.value];K&&k.value!=="none"&&(l.body=K.prompt,w.value=K.prompt)});let S=d.createDiv({cls:"af-create-section"}),T=S.createDiv({cls:"af-create-section-header"}),_=T.createSpan({cls:"af-create-section-icon"});(0,b.setIcon)(_,"wrench");let D=T.createSpan({text:"Tools"});this.addTooltip(D,"CLI commands, API endpoints, and tool definitions available to agents using this skill");let O=S.createEl("textarea",{cls:"af-create-prompt-textarea",attr:{placeholder:`## Commands ### list Usage: tool list [--filter ] -...`,rows:"8"}});M.addEventListener("input",()=>{l.toolsBody=M.value});let C=d.createDiv({cls:"af-create-section"}),I=C.createDiv({cls:"af-create-section-header"}),P=I.createSpan({cls:"af-create-section-icon"});(0,w.setIcon)(P,"book-open");let L=I.createSpan({text:"References"});this.addTooltip(L,"Background docs, conventions, cheat sheets");let F=C.createEl("textarea",{cls:"af-create-prompt-textarea",attr:{placeholder:"API docs, filter syntax, conventions...",rows:"6"}});F.addEventListener("input",()=>{l.referencesBody=F.value});let z=d.createDiv({cls:"af-create-section"}),U=z.createDiv({cls:"af-create-section-header"}),Z=U.createSpan({cls:"af-create-section-icon"});(0,w.setIcon)(Z,"message-circle");let de=U.createSpan({text:"Examples"});this.addTooltip(de,"Example prompts and ideal outputs showing how to use this skill");let me=z.createEl("textarea",{cls:"af-create-prompt-textarea",attr:{placeholder:`## Example: List all tasks +...`,rows:"8"}});O.addEventListener("input",()=>{l.toolsBody=O.value});let C=d.createDiv({cls:"af-create-section"}),E=C.createDiv({cls:"af-create-section-header"}),P=E.createSpan({cls:"af-create-section-icon"});(0,b.setIcon)(P,"book-open");let N=E.createSpan({text:"References"});this.addTooltip(N,"Background docs, conventions, cheat sheets");let M=C.createEl("textarea",{cls:"af-create-prompt-textarea",attr:{placeholder:"API docs, filter syntax, conventions...",rows:"6"}});M.addEventListener("input",()=>{l.referencesBody=M.value});let U=d.createDiv({cls:"af-create-section"}),$=U.createDiv({cls:"af-create-section-header"}),Z=$.createSpan({cls:"af-create-section-icon"});(0,b.setIcon)(Z,"message-circle");let de=$.createSpan({text:"Examples"});this.addTooltip(de,"Example prompts and ideal outputs showing how to use this skill");let me=U.createEl("textarea",{cls:"af-create-prompt-textarea",attr:{placeholder:`## Example: List all tasks User: Show me my tasks for today -Agent: ...`,rows:"6"}});me.addEventListener("input",()=>{l.examplesBody=me.value});let ye=s.createDiv({cls:"af-create-footer"}),X=ye.createEl("button",{cls:"af-btn-sm",text:"Cancel"});X.onclick=()=>this.navigate("skills");let j=ye.createEl("button",{cls:"af-btn-sm primary af-create-submit"});E(j,"plus","af-btn-icon"),j.appendText(" Create Skill"),j.onclick=async()=>{let K=l.name.trim();if(!K){new w.Notice("Skill name is required.");return}let ne=oe(K);if(this.plugin.repository.getSkillByName(ne)){new w.Notice(`Skill "${ne}" already exists.`);return}let G=ee=>ee.split(",").map(Q=>Q.trim()).filter(Boolean);try{await this.plugin.repository.createSkillFolder({name:ne,description:l.description.trim(),tags:G(l.tags),body:l.body.trim(),toolsBody:l.toolsBody.trim(),referencesBody:l.referencesBody.trim(),examplesBody:l.examplesBody.trim()}),new w.Notice(`Skill "${ne}" created.`),await this.plugin.refreshFromVault(),this.navigate("skills")}catch(ee){let Q=ee instanceof Error?ee.message:String(ee);new w.Notice(`Failed to create skill: ${Q}`)}}}renderEditAgentPage(e){let s=e.createDiv({cls:"af-create-agent-page"}),n=this.detailContext;if(!n){this.renderEmptyState(s,"bot","No agent selected","");return}let a=this.plugin.runtime.getSnapshot().agents.find(O=>O.name===n);if(!a){this.renderEmptyState(s,"bot","Agent not found",`Agent "${n}" was not found`);return}let r=s.createDiv({cls:"af-detail-header"}),o=r.createDiv({cls:"af-detail-header-left"}),c=o.createDiv({cls:"af-agent-card-avatar idle"});(0,w.setIcon)(c,"edit");let l=o.createDiv();l.createDiv({cls:"af-detail-header-name",text:`Edit Agent: ${a.name}`}),l.createDiv({cls:"af-detail-header-desc",text:"Modify agent configuration"});let h=r.createDiv({cls:"af-detail-header-actions"}),d={name:a.name,description:a.description??"",avatar:a.avatar,tags:a.tags.join(", "),systemPrompt:a.body,model:a.model,adapter:a.adapter,cwd:a.cwd??"",timeout:a.timeout,permissionMode:a.permissionMode,effort:a.effort??"",selectedSkills:new Set(a.skills),selectedMcpServers:new Set(a.mcpServers??[]),skillsBody:a.skillsBody,contextBody:a.contextBody,approvalRequired:a.approvalRequired.join(", "),memory:a.memory,memoryTokenBudget:a.memoryTokenBudget,reflectionEnabled:a.reflection.enabled,reflectionProposeSkills:a.reflection.proposeSkills,enabled:a.enabled,allowedCommands:a.permissionRules.allow.join(` +Agent: ...`,rows:"6"}});me.addEventListener("input",()=>{l.examplesBody=me.value});let ge=s.createDiv({cls:"af-create-footer"}),X=ge.createEl("button",{cls:"af-btn-sm",text:"Cancel"});X.onclick=()=>this.navigate("skills");let W=ge.createEl("button",{cls:"af-btn-sm primary af-create-submit"});R(W,"plus","af-btn-icon"),W.appendText(" Create Skill"),W.onclick=async()=>{let K=l.name.trim();if(!K){new b.Notice("Skill name is required.");return}let ne=oe(K);if(this.plugin.repository.getSkillByName(ne)){new b.Notice(`Skill "${ne}" already exists.`);return}let G=ee=>ee.split(",").map(Q=>Q.trim()).filter(Boolean);try{await this.plugin.repository.createSkillFolder({name:ne,description:l.description.trim(),tags:G(l.tags),body:l.body.trim(),toolsBody:l.toolsBody.trim(),referencesBody:l.referencesBody.trim(),examplesBody:l.examplesBody.trim()}),new b.Notice(`Skill "${ne}" created.`),await this.plugin.refreshFromVault(),this.navigate("skills")}catch(ee){let Q=ee instanceof Error?ee.message:String(ee);new b.Notice(`Failed to create skill: ${Q}`)}}}renderEditAgentPage(e){let s=e.createDiv({cls:"af-create-agent-page"}),n=this.detailContext;if(!n){this.renderEmptyState(s,"bot","No agent selected","");return}let a=this.plugin.runtime.getSnapshot().agents.find(L=>L.name===n);if(!a){this.renderEmptyState(s,"bot","Agent not found",`Agent "${n}" was not found`);return}let r=s.createDiv({cls:"af-detail-header"}),o=r.createDiv({cls:"af-detail-header-left"}),c=o.createDiv({cls:"af-agent-card-avatar idle"});(0,b.setIcon)(c,"edit");let l=o.createDiv();l.createDiv({cls:"af-detail-header-name",text:`Edit Agent: ${a.name}`}),l.createDiv({cls:"af-detail-header-desc",text:"Modify agent configuration"});let h=r.createDiv({cls:"af-detail-header-actions"}),d={name:a.name,description:a.description??"",avatar:a.avatar,tags:a.tags.join(", "),systemPrompt:a.body,model:a.model,adapter:a.adapter,cwd:a.cwd??"",timeout:a.timeout,permissionMode:a.permissionMode,effort:a.effort??"",selectedSkills:new Set(a.skills),selectedMcpServers:new Set(a.mcpServers??[]),skillsBody:a.skillsBody,contextBody:a.contextBody,approvalRequired:a.approvalRequired.join(", "),memory:a.memory,memoryTokenBudget:a.memoryTokenBudget,reflectionEnabled:a.reflection.enabled,reflectionProposeSkills:a.reflection.proposeSkills,enabled:a.enabled,allowedCommands:a.permissionRules.allow.join(` `),blockedCommands:a.permissionRules.deny.join(` -`),heartbeatEnabled:a.heartbeatEnabled,heartbeatSchedule:a.heartbeatSchedule,heartbeatBody:a.heartbeatBody,heartbeatNotify:a.heartbeatNotify,heartbeatChannel:a.heartbeatChannel,autoCompactThreshold:a.autoCompactThreshold??85,wikiReferences:(a.wikiReferences??[]).map(O=>O.agent)},u=s.createDiv({cls:"af-create-form"}),p=u.createDiv({cls:"af-create-section"}),m=p.createDiv({cls:"af-create-section-header"}),f=m.createSpan({cls:"af-create-section-icon"});(0,w.setIcon)(f,"user"),m.createSpan({text:"Identity"});let g=p.createDiv({cls:"af-form-row"});g.createDiv({cls:"af-form-label",text:"Name"}),g.createEl("input",{cls:"af-form-input",attr:{type:"text",value:a.name,disabled:"true"}}).setCssStyles({opacity:"0.6"}),this.createFormField(p,"Description","Monitors deployments and reports status","",O=>{d.description=O},a.description??"");let b=p.createDiv({cls:"af-form-row"});b.createDiv({cls:"af-form-label",text:"Avatar"});let k=b.createEl("button",{cls:"af-avatar-picker-btn"}),y=k.createDiv({cls:"af-avatar-picker-preview"});this.renderAgentAvatar(y,{...a,avatar:d.avatar??a.avatar}),k.createSpan({cls:"af-avatar-picker-label",text:d.avatar||a.avatar||"Pick icon\u2026"}),k.addEventListener("click",()=>{new Bn(this.app,d.avatar??a.avatar,O=>{d.avatar=O,y.empty(),(0,w.setIcon)(y,O),k.querySelector(".af-avatar-picker-label")?.setText(O)}).open()}),this.createFormField(p,"Tags","devops, monitoring","Comma-separated",O=>{d.tags=O},a.tags.join(", "));let S=p.createDiv({cls:"af-form-row"});S.createDiv({cls:"af-form-label",text:"Enabled"});let T=S.createDiv({cls:`af-agent-card-toggle${a.enabled?" on":""}`});T.onclick=()=>{let O=T.hasClass("on");T.toggleClass("on",!O),d.enabled=!O};let _=u.createDiv({cls:"af-create-section"}),D=_.createDiv({cls:"af-create-section-header"}),M=D.createSpan({cls:"af-create-section-icon"});(0,w.setIcon)(M,"message-square"),D.createSpan({text:"System Prompt"});let C=_.createEl("textarea",{cls:"af-create-prompt-textarea",attr:{placeholder:"System prompt...",rows:"10"}});C.value=a.body,C.addEventListener("input",()=>{d.systemPrompt=C.value});let I=u.createDiv({cls:"af-create-section"}),P=I.createDiv({cls:"af-create-section-header"}),L=P.createSpan({cls:"af-create-section-icon"});(0,w.setIcon)(L,"settings"),P.createSpan({text:"Runtime Config"});let F=I.createDiv({cls:"af-create-config-grid"}),z=F.createDiv({cls:"af-form-row"});z.createDiv({cls:"af-form-label",text:"Adapter"});let U=z.createEl("select",{cls:"af-form-select"});for(let[O,Y,_e]of Fo){let Se=U.createEl("option",{text:Y,attr:{value:O,..._e?{disabled:"true"}:{}}});(O===a.adapter||Ds(a.adapter)&&O==="codex")&&(Se.selected=!0)}let Z=F.createDiv({cls:"af-form-row"}),de=Z.createDiv({cls:"af-form-label",text:"Model"});this.addTooltip(de,`Aliases (opus/sonnet/haiku/opusplan) work on any backend. Choose Custom\u2026 for a pinned ID or Bedrock/Vertex/Foundry. Blank = use Settings default (${this.plugin.settings.defaultModel||"CLI default"}).`);let me=Z.createDiv({cls:"af-form-field-wrap"}),ye=()=>{At(me,{value:d.model,adapter:d.adapter,onChange:O=>{d.model=O}})};ye();let X=()=>{};U.addEventListener("change",()=>{d.adapter=U.value,(Ds(d.adapter)?Js:Xs).some(Y=>Y.value===d.model.trim())&&(d.model=""),ye(),d.permissionMode=jn(d.permissionMode,d.adapter),X()});let j=F.createDiv({cls:"af-form-row"});j.createDiv({cls:"af-form-label",text:"Working Dir"});let K=j.createEl("input",{cls:"af-form-input",attr:{type:"text",placeholder:"Leave empty for vault root",value:a.cwd??""}});K.addEventListener("input",()=>{d.cwd=K.value});let ne=F.createDiv({cls:"af-form-row"});ne.createDiv({cls:"af-form-label",text:"Timeout (sec)"});let G=ne.createEl("input",{cls:"af-form-input af-form-input-sm",attr:{type:"number",value:String(a.timeout)}});G.addEventListener("input",()=>{let O=parseInt(G.value,10);!isNaN(O)&&O>0&&(d.timeout=O)});let ee=F.createDiv({cls:"af-form-row"});ee.createDiv({cls:"af-form-label",text:"Permission Mode"});let Q=ee.createEl("select",{cls:"af-form-select"}),ae=F.createDiv({cls:"af-form-hint",text:""});X=()=>{d.permissionMode=jn(d.permissionMode,d.adapter);let O=$n(d.adapter);Q.empty();for(let[Y,_e]of O){let Se=Q.createEl("option",{text:_e,attr:{value:Y}});Y===d.permissionMode&&(Se.selected=!0)}ae.textContent=O.find(([Y])=>Y===Q.value)?.[2]??""},X(),Q.addEventListener("change",()=>{d.permissionMode=Q.value,ae.textContent=$n(d.adapter).find(([O])=>O===Q.value)?.[2]??""});let ve=F.createDiv({cls:"af-form-row"});ve.createDiv({cls:"af-form-label",text:"Effort Level"});let Re=ve.createEl("select",{cls:"af-form-select"});for(let[O,Y]of[["","Default"],["low","Low"],["medium","Medium"],["high","High"],["max","Max"]]){let _e=Re.createEl("option",{text:Y,attr:{value:O}});O===(a.effort??"")&&(_e.selected=!0)}Re.addEventListener("change",()=>{d.effort=Re.value}),F.createDiv({cls:"af-form-hint",text:"Controls reasoning depth \u2014 low is fast, max is most thorough"});let Le=F.createDiv({cls:"af-form-row"}),Me=Le.createDiv({cls:"af-form-label",text:"Auto-compact at"});this.addTooltip(Me,"Percent of context window at which the chat auto-invokes /compact before the next message. 85% is a good default. Set 0 to disable.");let Fe=Le.createEl("input",{cls:"af-form-input af-form-input-sm",attr:{type:"number",min:"0",max:"100",value:String(d.autoCompactThreshold)}});Fe.addEventListener("input",()=>{let O=parseInt(Fe.value,10);!isNaN(O)&&O>=0&&O<=100&&(d.autoCompactThreshold=O)}),F.createDiv({cls:"af-form-hint",text:"0 disables auto-compact"});{let O=this.plugin.runtime.getSnapshot().agents.filter(Y=>Y.wikiKeeper!==void 0);if(O.length>0){let Y=F.createDiv({cls:"af-form-row af-form-row-toggle"}),_e=Y.createDiv({cls:"af-form-label",text:"Wiki access"});this.addTooltip(_e,"Lets this agent read + cite from the selected Wiki Keeper scopes (requires the wiki-query skill).");let Se=Y.createDiv({cls:"af-form-field-wrap"});for(let tt of O){let Bt=Se.createEl("label",{cls:"af-form-checkbox-row"}),st=Bt.createEl("input",{attr:{type:"checkbox"}});d.wikiReferences.includes(tt.name)&&(st.checked=!0),Bt.createSpan({text:` ${tt.name}`,cls:"af-form-checkbox-label"}),st.addEventListener("change",()=>{st.checked?d.wikiReferences.includes(tt.name)||d.wikiReferences.push(tt.name):d.wikiReferences=d.wikiReferences.filter(Fs=>Fs!==tt.name)})}}}if(a.isFolder){let O=u.createDiv({cls:"af-create-section"}),Y=O.createDiv({cls:"af-create-section-header"}),_e=Y.createSpan({cls:"af-create-section-icon"});(0,w.setIcon)(_e,"heart-pulse");let Se=Y.createSpan({text:"Heartbeat"});this.addTooltip(Se,"Autonomous periodic run \u2014 what the agent does when no one is asking");let tt=O.createDiv({cls:"af-form-row af-form-row-toggle"});tt.createDiv({cls:"af-form-label",text:"Enabled"});let Bt=tt.createDiv({cls:`af-agent-card-toggle${d.heartbeatEnabled?" on":""}`}),st=O.createDiv();st.setCssStyles({display:d.heartbeatEnabled?"":"none"}),Bt.onclick=()=>{let ot=Bt.hasClass("on");Bt.toggleClass("on",!ot),d.heartbeatEnabled=!ot,st.setCssStyles({display:ot?"none":""})},this.renderHeartbeatSchedule(st,d);let Fs=st.createDiv({cls:"af-form-row af-form-row-toggle"}),Ga=Fs.createDiv({cls:"af-form-label"});Ga.setText("Notify"),this.addTooltip(Ga,"Show an Obsidian notice when the heartbeat completes");let qn=Fs.createDiv({cls:`af-agent-card-toggle${d.heartbeatNotify?" on":""}`});qn.onclick=()=>{let ot=qn.hasClass("on");qn.toggleClass("on",!ot),d.heartbeatNotify=!ot};let jo=this.plugin.runtime.getSnapshot(),Va=st.createDiv({cls:"af-form-row"}),Ya=Va.createDiv({cls:"af-form-label"});Ya.setText("Post to channel"),this.addTooltip(Ya,"Heartbeat results are posted to this Slack channel when the run completes");let Os=Va.createEl("select",{cls:"af-form-select"});Os.createEl("option",{text:"(none)",attr:{value:""}});for(let ot of jo.channels){let Wo=Os.createEl("option",{text:ot.name,attr:{value:ot.name}});ot.name===d.heartbeatChannel&&(Wo.selected=!0)}Os.addEventListener("change",()=>{d.heartbeatChannel=Os.value});let Ns=st.createDiv({cls:"af-form-label"});Ns.setCssStyles({width:"auto"}),Ns.setCssStyles({marginTop:"12px"}),Ns.setText("Instruction"),this.addTooltip(Ns,'What the agent does on each heartbeat. Also used by the "Run Now" button.');let zn=st.createEl("textarea",{cls:"af-create-prompt-textarea",attr:{placeholder:"Check status, scan for issues, report findings...",rows:"8"}});zn.value=d.heartbeatBody,zn.addEventListener("input",()=>{d.heartbeatBody=zn.value})}let ke=u.createDiv({cls:"af-create-section"}),Oe=ke.createDiv({cls:"af-create-section-header"}),Ue=Oe.createSpan({cls:"af-create-section-icon"});(0,w.setIcon)(Ue,"puzzle"),Oe.createSpan({text:"Skills"});let Ne=this.plugin.runtime.getSnapshot();if(Ne.skills.length>0){ke.createDiv({cls:"af-form-sublabel",text:"Shared Skills"});let O=ke.createDiv({cls:"af-create-skills-grid"});for(let Y of Ne.skills){let _e=O.createDiv({cls:"af-create-skill-item"}),Se=_e.createEl("input",{cls:"af-form-toggle",attr:{type:"checkbox"}});Se.checked=d.selectedSkills.has(Y.name),Se.addEventListener("change",()=>{Se.checked?d.selectedSkills.add(Y.name):d.selectedSkills.delete(Y.name)});let tt=_e.createDiv({cls:"af-create-skill-label"});tt.createSpan({cls:"af-create-skill-name",text:Y.name}),Y.description&&tt.createSpan({cls:"af-create-skill-desc",text:` \u2014 ${Y.description}`})}}let H=ke.createDiv({cls:"af-form-sublabel"});H.setText("Agent-specific skills"),this.addTooltip(H,"Custom skills/instructions only for this agent, not shared with others");let ie=ke.createEl("textarea",{cls:"af-create-textarea",attr:{placeholder:"Custom skills/instructions for this agent...",rows:"4"}});ie.value=a.skillsBody,ie.addEventListener("input",()=>{d.skillsBody=ie.value});let $=u.createDiv({cls:"af-create-section"}),Te=$.createDiv({cls:"af-create-section-header"}),xe=Te.createSpan({cls:"af-create-section-icon"});(0,w.setIcon)(xe,"plug");let he=Te.createSpan({text:"MCP Servers"});this.addTooltip(he,"Grant agent access to MCP servers"),this.renderAgentMcpPicker($,d.selectedMcpServers);let $e=u.createDiv({cls:"af-create-section"}),re=$e.createDiv({cls:"af-create-section-header"}),R=re.createSpan({cls:"af-create-section-icon"});(0,w.setIcon)(R,"file-text");let se=re.createSpan({text:"Context"});this.addTooltip(se,"Project-specific context included in every run");let ce=$e.createEl("textarea",{cls:"af-create-textarea",attr:{placeholder:"Background info, repo structure, conventions...",rows:"4"}});ce.value=a.contextBody,ce.addEventListener("input",()=>{d.contextBody=ce.value});let te=u.createDiv({cls:"af-create-section"}),Ze=te.createDiv({cls:"af-create-section-header"}),is=Ze.createSpan({cls:"af-create-section-icon"});(0,w.setIcon)(is,"shield-check"),Ze.createSpan({text:"Permissions"}),this.createFormField(te,"Approval required","git_push, file_delete","Comma-separated tool names",O=>{d.approvalRequired=O},a.approvalRequired.join(", "));let rs=te.createDiv({cls:"af-form-row"});rs.createDiv({cls:"af-form-label",text:"Allowed Commands"});let os=rs.createEl("textarea",{cls:"af-create-textarea",attr:{placeholder:`Bash(curl *) +`),heartbeatEnabled:a.heartbeatEnabled,heartbeatSchedule:a.heartbeatSchedule,heartbeatBody:a.heartbeatBody,heartbeatNotify:a.heartbeatNotify,heartbeatChannel:a.heartbeatChannel,heartbeatChannelTarget:a.heartbeatChannelTarget,autoCompactThreshold:a.autoCompactThreshold??85,wikiReferences:(a.wikiReferences??[]).map(L=>L.agent)},u=s.createDiv({cls:"af-create-form"}),p=u.createDiv({cls:"af-create-section"}),m=p.createDiv({cls:"af-create-section-header"}),f=m.createSpan({cls:"af-create-section-icon"});(0,b.setIcon)(f,"user"),m.createSpan({text:"Identity"});let g=p.createDiv({cls:"af-form-row"});g.createDiv({cls:"af-form-label",text:"Name"}),g.createEl("input",{cls:"af-form-input",attr:{type:"text",value:a.name,disabled:"true"}}).setCssStyles({opacity:"0.6"}),this.createFormField(p,"Description","Monitors deployments and reports status","",L=>{d.description=L},a.description??"");let v=p.createDiv({cls:"af-form-row"});v.createDiv({cls:"af-form-label",text:"Avatar"});let k=v.createEl("button",{cls:"af-avatar-picker-btn"}),w=k.createDiv({cls:"af-avatar-picker-preview"});this.renderAgentAvatar(w,{...a,avatar:d.avatar??a.avatar}),k.createSpan({cls:"af-avatar-picker-label",text:d.avatar||a.avatar||"Pick icon\u2026"}),k.addEventListener("click",()=>{new $n(this.app,d.avatar??a.avatar,L=>{d.avatar=L,w.empty(),(0,b.setIcon)(w,L),k.querySelector(".af-avatar-picker-label")?.setText(L)}).open()}),this.createFormField(p,"Tags","devops, monitoring","Comma-separated",L=>{d.tags=L},a.tags.join(", "));let S=p.createDiv({cls:"af-form-row"});S.createDiv({cls:"af-form-label",text:"Enabled"});let T=S.createDiv({cls:`af-agent-card-toggle${a.enabled?" on":""}`});T.onclick=()=>{let L=T.hasClass("on");T.toggleClass("on",!L),d.enabled=!L};let _=u.createDiv({cls:"af-create-section"}),D=_.createDiv({cls:"af-create-section-header"}),O=D.createSpan({cls:"af-create-section-icon"});(0,b.setIcon)(O,"message-square"),D.createSpan({text:"System Prompt"});let C=_.createEl("textarea",{cls:"af-create-prompt-textarea",attr:{placeholder:"System prompt...",rows:"10"}});C.value=a.body,C.addEventListener("input",()=>{d.systemPrompt=C.value});let E=u.createDiv({cls:"af-create-section"}),P=E.createDiv({cls:"af-create-section-header"}),N=P.createSpan({cls:"af-create-section-icon"});(0,b.setIcon)(N,"settings"),P.createSpan({text:"Runtime Config"});let M=E.createDiv({cls:"af-create-config-grid"}),U=M.createDiv({cls:"af-form-row"});U.createDiv({cls:"af-form-label",text:"Adapter"});let $=U.createEl("select",{cls:"af-form-select"});for(let[L,Y,Te]of Jo){let Le=$.createEl("option",{text:Y,attr:{value:L,...Te?{disabled:"true"}:{}}});(L===a.adapter||Ls(a.adapter)&&L==="codex")&&(Le.selected=!0)}let Z=M.createDiv({cls:"af-form-row"}),de=Z.createDiv({cls:"af-form-label",text:"Model"});this.addTooltip(de,`Aliases (opus/sonnet/haiku/opusplan) work on any backend. Choose Custom\u2026 for a pinned ID or Bedrock/Vertex/Foundry. Blank = use Settings default (${this.plugin.settings.defaultModel||"CLI default"}).`);let me=Z.createDiv({cls:"af-form-field-wrap"}),ge=()=>{Pt(me,{value:d.model,adapter:d.adapter,onChange:L=>{d.model=L}})};ge();let X=()=>{};$.addEventListener("change",()=>{d.adapter=$.value,(Ls(d.adapter)?Qs:Zs).some(Y=>Y.value===d.model.trim())&&(d.model=""),ge(),d.permissionMode=Hn(d.permissionMode,d.adapter),X()});let W=M.createDiv({cls:"af-form-row"});W.createDiv({cls:"af-form-label",text:"Working Dir"});let K=W.createEl("input",{cls:"af-form-input",attr:{type:"text",placeholder:"Leave empty for vault root",value:a.cwd??""}});K.addEventListener("input",()=>{d.cwd=K.value});let ne=M.createDiv({cls:"af-form-row"});ne.createDiv({cls:"af-form-label",text:"Timeout (sec)"});let G=ne.createEl("input",{cls:"af-form-input af-form-input-sm",attr:{type:"number",value:String(a.timeout)}});G.addEventListener("input",()=>{let L=parseInt(G.value,10);!isNaN(L)&&L>0&&(d.timeout=L)});let ee=M.createDiv({cls:"af-form-row"});ee.createDiv({cls:"af-form-label",text:"Permission Mode"});let Q=ee.createEl("select",{cls:"af-form-select"}),ae=M.createDiv({cls:"af-form-hint",text:""});X=()=>{d.permissionMode=Hn(d.permissionMode,d.adapter);let L=Wn(d.adapter);Q.empty();for(let[Y,Te]of L){let Le=Q.createEl("option",{text:Te,attr:{value:Y}});Y===d.permissionMode&&(Le.selected=!0)}ae.textContent=L.find(([Y])=>Y===Q.value)?.[2]??""},X(),Q.addEventListener("change",()=>{d.permissionMode=Q.value,ae.textContent=Wn(d.adapter).find(([L])=>L===Q.value)?.[2]??""});let ve=M.createDiv({cls:"af-form-row"});ve.createDiv({cls:"af-form-label",text:"Effort Level"});let Pe=ve.createEl("select",{cls:"af-form-select"});for(let[L,Y]of[["","Default"],["low","Low"],["medium","Medium"],["high","High"],["max","Max"]]){let Te=Pe.createEl("option",{text:Y,attr:{value:L}});L===(a.effort??"")&&(Te.selected=!0)}Pe.addEventListener("change",()=>{d.effort=Pe.value}),M.createDiv({cls:"af-form-hint",text:"Controls reasoning depth \u2014 low is fast, max is most thorough"});let Ie=M.createDiv({cls:"af-form-row"}),Me=Ie.createDiv({cls:"af-form-label",text:"Auto-compact at"});this.addTooltip(Me,"Percent of context window at which the chat auto-invokes /compact before the next message. 85% is a good default. Set 0 to disable.");let Fe=Ie.createEl("input",{cls:"af-form-input af-form-input-sm",attr:{type:"number",min:"0",max:"100",value:String(d.autoCompactThreshold)}});Fe.addEventListener("input",()=>{let L=parseInt(Fe.value,10);!isNaN(L)&&L>=0&&L<=100&&(d.autoCompactThreshold=L)}),M.createDiv({cls:"af-form-hint",text:"0 disables auto-compact"});{let L=this.plugin.runtime.getSnapshot().agents.filter(Y=>Y.wikiKeeper!==void 0);if(L.length>0){let Y=M.createDiv({cls:"af-form-row af-form-row-toggle"}),Te=Y.createDiv({cls:"af-form-label",text:"Wiki access"});this.addTooltip(Te,"Lets this agent read + cite from the selected Wiki Keeper scopes (requires the wiki-query skill).");let Le=Y.createDiv({cls:"af-form-field-wrap"});for(let qe of L){let at=Le.createEl("label",{cls:"af-form-checkbox-row"}),We=at.createEl("input",{attr:{type:"checkbox"}});d.wikiReferences.includes(qe.name)&&(We.checked=!0),at.createSpan({text:` ${qe.name}`,cls:"af-form-checkbox-label"}),We.addEventListener("change",()=>{We.checked?d.wikiReferences.includes(qe.name)||d.wikiReferences.push(qe.name):d.wikiReferences=d.wikiReferences.filter(ze=>ze!==qe.name)})}}}if(a.isFolder){let L=u.createDiv({cls:"af-create-section"}),Y=L.createDiv({cls:"af-create-section-header"}),Te=Y.createSpan({cls:"af-create-section-icon"});(0,b.setIcon)(Te,"heart-pulse");let Le=Y.createSpan({text:"Heartbeat"});this.addTooltip(Le,"Autonomous periodic run \u2014 what the agent does when no one is asking");let qe=L.createDiv({cls:"af-form-row af-form-row-toggle"});qe.createDiv({cls:"af-form-label",text:"Enabled"});let at=qe.createDiv({cls:`af-agent-card-toggle${d.heartbeatEnabled?" on":""}`}),We=L.createDiv();We.setCssStyles({display:d.heartbeatEnabled?"":"none"}),at.onclick=()=>{let ct=at.hasClass("on");at.toggleClass("on",!ct),d.heartbeatEnabled=!ct,We.setCssStyles({display:ct?"none":""})},this.renderHeartbeatSchedule(We,d);let ze=We.createDiv({cls:"af-form-row af-form-row-toggle"}),Za=ze.createDiv({cls:"af-form-label"});Za.setText("Notify"),this.addTooltip(Za,"Show an Obsidian notice when the heartbeat completes");let Gn=ze.createDiv({cls:`af-agent-card-toggle${d.heartbeatNotify?" on":""}`});Gn.onclick=()=>{let ct=Gn.hasClass("on");Gn.toggleClass("on",!ct),d.heartbeatNotify=!ct};let sl=this.plugin.runtime.getSnapshot(),ei=We.createDiv({cls:"af-form-row"}),ti=ei.createDiv({cls:"af-form-label"});ti.setText("Post to channel"),this.addTooltip(ti,"Heartbeat results are posted to this Slack channel when the run completes");let Bs=ei.createEl("select",{cls:"af-form-select"});Bs.createEl("option",{text:"(none)",attr:{value:""}});for(let ct of sl.channels){let nl=Bs.createEl("option",{text:ct.name,attr:{value:ct.name}});ct.name===d.heartbeatChannel&&(nl.selected=!0)}Bs.addEventListener("change",()=>{d.heartbeatChannel=Bs.value,ni()});let Vn=We.createDiv({cls:"af-form-row"}),si=Vn.createDiv({cls:"af-form-label"});si.setText("Target ID"),this.addTooltip(si,"Specific channel id to post to (Discord/Slack channel id, Telegram chat id). Empty = DM the channel\u2019s first allowed user.");let Yn=Vn.createEl("input",{cls:"af-form-input",attr:{type:"text",placeholder:"Channel/chat id \u2014 empty = DM"}});Yn.value=d.heartbeatChannelTarget,Yn.addEventListener("input",()=>{d.heartbeatChannelTarget=Yn.value.trim()});let ni=()=>{Vn.setCssStyles({display:d.heartbeatChannel?"":"none"})};ni();let Us=We.createDiv({cls:"af-form-label"});Us.setCssStyles({width:"auto"}),Us.setCssStyles({marginTop:"12px"}),Us.setText("Instruction"),this.addTooltip(Us,'What the agent does on each heartbeat. Also used by the "Run Now" button.');let Kn=We.createEl("textarea",{cls:"af-create-prompt-textarea",attr:{placeholder:"Check status, scan for issues, report findings...",rows:"8"}});Kn.value=d.heartbeatBody,Kn.addEventListener("input",()=>{d.heartbeatBody=Kn.value})}let ke=u.createDiv({cls:"af-create-section"}),Oe=ke.createDiv({cls:"af-create-section-header"}),Ue=Oe.createSpan({cls:"af-create-section-icon"});(0,b.setIcon)(Ue,"puzzle"),Oe.createSpan({text:"Skills"});let Ne=this.plugin.runtime.getSnapshot();if(Ne.skills.length>0){ke.createDiv({cls:"af-form-sublabel",text:"Shared Skills"});let L=ke.createDiv({cls:"af-create-skills-grid"});for(let Y of Ne.skills){let Te=L.createDiv({cls:"af-create-skill-item"}),Le=Te.createEl("input",{cls:"af-form-toggle",attr:{type:"checkbox"}});Le.checked=d.selectedSkills.has(Y.name),Le.addEventListener("change",()=>{Le.checked?d.selectedSkills.add(Y.name):d.selectedSkills.delete(Y.name)});let qe=Te.createDiv({cls:"af-create-skill-label"});qe.createSpan({cls:"af-create-skill-name",text:Y.name}),Y.description&&qe.createSpan({cls:"af-create-skill-desc",text:` \u2014 ${Y.description}`})}}let q=ke.createDiv({cls:"af-form-sublabel"});q.setText("Agent-specific skills"),this.addTooltip(q,"Custom skills/instructions only for this agent, not shared with others");let ie=ke.createEl("textarea",{cls:"af-create-textarea",attr:{placeholder:"Custom skills/instructions for this agent...",rows:"4"}});ie.value=a.skillsBody,ie.addEventListener("input",()=>{d.skillsBody=ie.value});let j=u.createDiv({cls:"af-create-section"}),Ce=j.createDiv({cls:"af-create-section-header"}),xe=Ce.createSpan({cls:"af-create-section-icon"});(0,b.setIcon)(xe,"plug");let he=Ce.createSpan({text:"MCP Servers"});this.addTooltip(he,"Grant agent access to MCP servers"),this.renderAgentMcpPicker(j,d.selectedMcpServers);let $e=u.createDiv({cls:"af-create-section"}),re=$e.createDiv({cls:"af-create-section-header"}),I=re.createSpan({cls:"af-create-section-icon"});(0,b.setIcon)(I,"file-text");let se=re.createSpan({text:"Context"});this.addTooltip(se,"Project-specific context included in every run");let ce=$e.createEl("textarea",{cls:"af-create-textarea",attr:{placeholder:"Background info, repo structure, conventions...",rows:"4"}});ce.value=a.contextBody,ce.addEventListener("input",()=>{d.contextBody=ce.value});let te=u.createDiv({cls:"af-create-section"}),st=te.createDiv({cls:"af-create-section-header"}),os=st.createSpan({cls:"af-create-section-icon"});(0,b.setIcon)(os,"shield-check"),st.createSpan({text:"Permissions"}),this.createFormField(te,"Approval required","git_push, file_delete","Comma-separated tool names",L=>{d.approvalRequired=L},a.approvalRequired.join(", "));let ls=te.createDiv({cls:"af-form-row"});ls.createDiv({cls:"af-form-label",text:"Allowed Commands"});let cs=ls.createEl("textarea",{cls:"af-create-textarea",attr:{placeholder:`Bash(curl *) Bash(python3 *) Read Edit -Write`,rows:"4"}});os.value=a.permissionRules.allow.join(` -`),os.addEventListener("input",()=>{d.allowedCommands=os.value});let Ft=te.createDiv({cls:"af-form-row"});Ft.createDiv({cls:"af-form-label",text:"Blocked Commands"});let B=Ft.createEl("textarea",{cls:"af-create-textarea",attr:{placeholder:`Bash(git push *) +Write`,rows:"4"}});cs.value=a.permissionRules.allow.join(` +`),cs.addEventListener("input",()=>{d.allowedCommands=cs.value});let Nt=te.createDiv({cls:"af-form-row"});Nt.createDiv({cls:"af-form-label",text:"Blocked Commands"});let B=Nt.createEl("textarea",{cls:"af-create-textarea",attr:{placeholder:`Bash(git push *) Bash(rm -rf *) Bash(sudo *)`,rows:"4"}});B.value=a.permissionRules.deny.join(` -`),B.addEventListener("input",()=>{d.blockedCommands=B.value}),te.createDiv({cls:"af-form-hint",text:"On Codex agents these become execpolicy command rules \u2014 only Bash(cmd args *) prefixes are enforced; tool-name rules (Read/Write) and mid-pattern wildcards are ignored, and file/network access is governed by Permission Mode (the sandbox)."});let V=te.createDiv({cls:"af-form-row"});V.createDiv({cls:"af-form-label",text:"Memory enabled"});let Pe=V.createDiv({cls:`af-agent-card-toggle${a.memory?" on":""}`});Pe.onclick=()=>{let O=Pe.hasClass("on");Pe.toggleClass("on",!O),d.memory=!O};let we=te.createDiv({cls:"af-form-row"});we.createDiv({cls:"af-form-label",text:"Memory token budget"});let Ee=we.createEl("input",{cls:"af-create-input",attr:{type:"number",min:"200",step:"100"}});Ee.value=String(d.memoryTokenBudget),Ee.addEventListener("input",()=>{let O=parseInt(Ee.value,10);Number.isFinite(O)&&(d.memoryTokenBudget=O)});let et=te.createDiv({cls:"af-form-row"});et.createDiv({cls:"af-form-label",text:"Nightly reflection"});let We=et.createDiv({cls:`af-agent-card-toggle${a.reflection.enabled?" on":""}`});We.onclick=()=>{let O=We.hasClass("on");We.toggleClass("on",!O),d.reflectionEnabled=!O};let St=te.createDiv({cls:"af-form-row"});St.createDiv({cls:"af-form-label",text:"Propose skills from memory"});let Ot=St.createDiv({cls:`af-agent-card-toggle${a.reflection.proposeSkills?" on":""}`});Ot.onclick=()=>{let O=Ot.hasClass("on");Ot.toggleClass("on",!O),d.reflectionProposeSkills=!O};let vt=s.createDiv({cls:"af-create-footer"}),ls=vt.createEl("button",{cls:"af-btn-sm danger"});E(ls,"trash-2","af-btn-icon"),ls.appendText(" Delete"),ls.onclick=()=>void this.plugin.deleteAgent(a.name),vt.createDiv({cls:"af-toolbar-spacer"});let Ls=vt.createEl("button",{cls:"af-btn-sm",text:"Cancel"});Ls.onclick=()=>this.navigate("agent-detail",a.name);let Nt=vt.createEl("button",{cls:"af-btn-sm primary af-create-submit"});E(Nt,"check","af-btn-icon"),Nt.appendText(" Save Changes"),Nt.onclick=async()=>{let O=Y=>Y.split(",").map(_e=>_e.trim()).filter(Boolean);try{let Y=_e=>ge(_e).map(Se=>Se.trim()).filter(Boolean);await this.plugin.repository.updateAgent(a.name,{description:d.description.trim(),avatar:d.avatar.trim(),tags:O(d.tags),systemPrompt:d.systemPrompt.trim(),model:d.model.trim()||"default",adapter:d.adapter,cwd:d.cwd.trim(),timeout:d.timeout,permissionMode:d.permissionMode,effort:d.effort||void 0,approvalRequired:O(d.approvalRequired),memory:d.memory,memoryTokenBudget:d.memoryTokenBudget,reflectionEnabled:d.reflectionEnabled,reflectionProposeSkills:d.reflectionProposeSkills,skills:Array.from(d.selectedSkills),mcpServers:Array.from(d.selectedMcpServers),skillsBody:d.skillsBody.trim(),contextBody:d.contextBody.trim(),enabled:d.enabled,permissionRules:{allow:Y(d.allowedCommands),deny:Y(d.blockedCommands)},autoCompactThreshold:d.autoCompactThreshold,wikiReferences:d.wikiReferences}),a.isFolder&&await this.plugin.repository.updateHeartbeat(a.name,{enabled:d.heartbeatEnabled,schedule:d.heartbeatSchedule.trim(),notify:d.heartbeatNotify,channel:d.heartbeatChannel,body:d.heartbeatBody.trim()}),new w.Notice(`Agent "${a.name}" updated.`),await this.plugin.refreshFromVault(),this.navigate("agent-detail",a.name)}catch(Y){let _e=Y instanceof Error?Y.message:String(Y);new w.Notice(`Failed to update agent: ${_e}`)}}}renderCreateTaskPage(e){let s=e.createDiv({cls:"af-create-agent-page"}),n=this.plugin.runtime.getSnapshot(),a=s.createDiv({cls:"af-detail-header"}),r=a.createDiv({cls:"af-detail-header-left"}),o=r.createDiv({cls:"af-agent-card-avatar idle"});(0,w.setIcon)(o,"plus");let c=r.createDiv();c.createDiv({cls:"af-detail-header-name",text:"Create New Task"}),c.createDiv({cls:"af-detail-header-desc",text:"Configure a new task for your fleet"});let l=a.createDiv({cls:"af-detail-header-actions"}),h={title:"",agent:n.agents[0]?.name??"",priority:"medium",tags:"",body:"",scheduleEnabled:!1,scheduleMode:"recurring",schedule:"0 9 * * *",runAt:"",type:"immediate",enabled:!0,catchUp:!0,effort:"",model:""},d=s.createDiv({cls:"af-create-form"}),u=d.createDiv({cls:"af-create-section"}),p=u.createDiv({cls:"af-create-section-header"}),m=p.createSpan({cls:"af-create-section-icon"});(0,w.setIcon)(m,"file-text"),p.createSpan({text:"Task Details"}),this.createFormField(u,"Title","Daily status report","Used as the task identifier",H=>{h.title=H});let f=u.createDiv({cls:"af-form-row"});f.createDiv({cls:"af-form-label",text:"Agent"});let g=f.createEl("select",{cls:"af-form-select"});for(let H of n.agents)g.createEl("option",{text:H.name,attr:{value:H.name}});g.addEventListener("change",()=>{h.agent=g.value});let v=u.createDiv({cls:"af-form-row"});v.createDiv({cls:"af-form-label",text:"Priority"});let b=v.createEl("select",{cls:"af-form-select"}),k=[["low","Low"],["medium","Medium"],["high","High"],["critical","Critical"]];for(let[H,ie]of k){let $=b.createEl("option",{text:ie,attr:{value:H}});H==="medium"&&($.selected=!0)}b.addEventListener("change",()=>{h.priority=b.value}),this.createFormField(u,"Tags","monitoring, devops","Comma-separated",H=>{h.tags=H});let y=d.createDiv({cls:"af-create-section"}),S=y.createDiv({cls:"af-create-section-header"}),T=S.createSpan({cls:"af-create-section-icon"});(0,w.setIcon)(T,"message-square"),S.createSpan({text:"Instructions"});let _=y.createEl("textarea",{cls:"af-create-prompt-textarea",attr:{placeholder:"Describe what the agent should do...",rows:"10"}});_.addEventListener("input",()=>{h.body=_.value});let D=d.createDiv({cls:"af-create-section"}),M=D.createDiv({cls:"af-create-section-header"}),C=M.createSpan({cls:"af-create-section-icon"});(0,w.setIcon)(C,"clock"),M.createSpan({text:"Schedule"});let I=D.createDiv({cls:"af-form-row af-form-row-toggle"});I.createDiv({cls:"af-form-label",text:"Enable schedule"});let P=I.createDiv({cls:"af-agent-card-toggle"}),L=D.createDiv({cls:"af-schedule-body"});L.setCssStyles({display:"none"}),P.onclick=()=>{let H=P.hasClass("on");P.toggleClass("on",!H),h.scheduleEnabled=!H,L.setCssStyles({display:H?"none":""}),H?h.type="immediate":h.type=h.scheduleMode==="once"?"once":"recurring"};let F=L.createDiv({cls:"af-form-row"});F.createDiv({cls:"af-form-label",text:"Mode"});let z=F.createEl("select",{cls:"af-form-select"});for(let[H,ie]of[["recurring","Recurring"],["once","One-time"]])z.createEl("option",{text:ie,attr:{value:H}});let U=L.createDiv(),Z=L.createDiv();Z.setCssStyles({display:"none"}),this.renderInlineSchedule(U,h);let de=Z.createDiv({cls:"af-form-row"});de.createDiv({cls:"af-form-label",text:"Run at"});let me=de.createEl("input",{cls:"af-form-input",attr:{type:"datetime-local",value:this.toDatetimeLocal(new Date(Date.now()+36e5))}});h.runAt=new Date(me.value).toISOString(),me.addEventListener("input",()=>{h.runAt=me.value?new Date(me.value).toISOString():""}),z.addEventListener("change",()=>{h.scheduleMode=z.value,U.setCssStyles({display:h.scheduleMode==="recurring"?"":"none"}),Z.setCssStyles({display:h.scheduleMode==="once"?"":"none"}),h.scheduleEnabled&&(h.type=h.scheduleMode==="once"?"once":"recurring")});let ye=L.createDiv({cls:"af-form-row af-form-row-toggle"});ye.createDiv({cls:"af-form-label",text:"Enabled"});let X=ye.createDiv({cls:"af-agent-card-toggle on"});X.onclick=()=>{let H=X.hasClass("on");X.toggleClass("on",!H),h.enabled=!H};let j=L.createDiv({cls:"af-form-row af-form-row-toggle"});j.createDiv({cls:"af-form-label"}).setText("Catch up if missed");let ne=j.createDiv({cls:`af-agent-card-toggle${h.catchUp?" on":""}`});ne.onclick=()=>{let H=ne.hasClass("on");ne.toggleClass("on",!H),h.catchUp=!H};let G=d.createDiv({cls:"af-create-section"}),ee=G.createDiv({cls:"af-create-section-header"}),Q=ee.createSpan({cls:"af-create-section-icon"});(0,w.setIcon)(Q,"gauge"),ee.createSpan({text:"Execution"});let ae=G.createDiv({cls:"af-form-row"}),ve=ae.createDiv({cls:"af-form-label",text:"Model"}),Re=ae.createDiv({cls:"af-form-field-wrap"}),Le=H=>{Re.empty();let ie=n.agents.find($=>$.name===H);At(Re,{value:h.model,adapter:ie?.adapter,onChange:$=>{h.model=$},allowInherit:!0,inheritPlaceholder:ie?`Inherit from ${ie.name}${ie.model?` (${ie.model})`:""}`:"Inherit from agent"})};Le(h.agent),g.addEventListener("change",()=>Le(g.value)),this.addTooltip(ve,"Override the agent\u2019s model for this task only. Useful for routing simple runs to haiku while the agent stays on opus for heavier work.");let Me=G.createDiv({cls:"af-form-row"}),Fe=Me.createDiv({cls:"af-form-label",text:"Effort"}),ke=Me.createEl("select",{cls:"af-form-select"});for(let[H,ie]of[["","Agent Default"],["low","Low"],["medium","Medium"],["high","High"],["max","Max"]]){let $=ke.createEl("option",{text:ie,attr:{value:H}});H===h.effort&&($.selected=!0)}ke.addEventListener("change",()=>{h.effort=ke.value}),this.addTooltip(Fe,"Overrides the agent\u2019s effort level for this task. Higher effort = more thinking tokens spent.");let Oe=s.createDiv({cls:"af-create-footer"}),Ue=Oe.createEl("button",{cls:"af-btn-sm",text:"Cancel"});Ue.onclick=()=>this.navigate("kanban");let Ne=Oe.createEl("button",{cls:"af-btn-sm primary af-create-submit"});E(Ne,"plus","af-btn-icon"),Ne.appendText(" Create Task"),Ne.onclick=async()=>{let H=h.title.trim();if(!H){new w.Notice("Task title is required.");return}let ie=oe(H),$=he=>he.split(",").map($e=>$e.trim()).filter(Boolean),Te=h.scheduleEnabled?h.scheduleMode==="once"?"once":"recurring":"immediate",xe={task_id:ie,agent:h.agent,type:Te,priority:h.priority,enabled:h.enabled,created:this.toLocalISO(new Date),run_count:0,catch_up:h.catchUp,effort:h.effort||void 0,model:h.model||void 0,tags:$(h.tags)};if(Te==="recurring")xe.schedule=h.schedule.trim()||"0 9 * * *";else if(Te==="once"){if(!h.runAt){new w.Notice("Pick a date/time for the one-time run.");return}xe.run_at=h.runAt}try{let he=await this.plugin.repository.getAvailablePath(this.plugin.repository.getSubfolder("tasks"),ie);await this.plugin.app.vault.create(he,W(xe,h.body.trim()||"Describe the task here.")),new w.Notice(`Task "${ie}" created.`),await this.plugin.refreshFromVault(),this.navigate("task-detail",ie)}catch(he){let $e=he instanceof Error?he.message:String(he);new w.Notice(`Failed to create task: ${$e}`)}}}toLocalISO(e){let s=n=>String(n).padStart(2,"0");return`${e.getFullYear()}-${s(e.getMonth()+1)}-${s(e.getDate())}T${s(e.getHours())}:${s(e.getMinutes())}:${s(e.getSeconds())}`}toDatetimeLocal(e){let s=n=>String(n).padStart(2,"0");return`${e.getFullYear()}-${s(e.getMonth()+1)}-${s(e.getDate())}T${s(e.getHours())}:${s(e.getMinutes())}`}renderEditTaskPage(e){let s=e.createDiv({cls:"af-create-agent-page"}),n=this.detailContext;if(!n){this.renderEmptyState(s,"circle-dot","No task selected","");return}let a=this.plugin.runtime.getSnapshot().tasks.find(R=>R.taskId===n);if(!a){this.renderEmptyState(s,"circle-dot","Task not found",`Task "${n}" was not found`);return}let r=this.plugin.runtime.getSnapshot(),o=s.createDiv({cls:"af-detail-header"}),c=o.createDiv({cls:"af-detail-header-left"}),l=c.createDiv({cls:"af-agent-card-avatar idle"});(0,w.setIcon)(l,"edit");let h=c.createDiv();h.createDiv({cls:"af-detail-header-name",text:`Edit Task: ${a.taskId}`}),h.createDiv({cls:"af-detail-header-desc",text:"Modify task configuration"});let d=o.createDiv({cls:"af-detail-header-actions"}),u=!!(a.schedule||a.runAt),p={agent:a.agent,type:a.type,priority:a.priority,schedule:a.schedule??"0 9 * * *",runAt:a.runAt??"",scheduleEnabled:u,scheduleMode:a.type==="once"?"once":"recurring",enabled:a.enabled,catchUp:a.catchUp,effort:a.effort??"",model:a.model??"",tags:a.tags.join(", "),body:a.body},m=s.createDiv({cls:"af-create-form"}),f=m.createDiv({cls:"af-create-section"}),g=f.createDiv({cls:"af-create-section-header"}),v=g.createSpan({cls:"af-create-section-icon"});(0,w.setIcon)(v,"file-text"),g.createSpan({text:"Task Details"});let b=f.createDiv({cls:"af-form-row"});b.createDiv({cls:"af-form-label",text:"Title"}),b.createEl("input",{cls:"af-form-input",attr:{type:"text",value:a.taskId,disabled:"true"}}).setCssStyles({opacity:"0.6"});let y=f.createDiv({cls:"af-form-row"});y.createDiv({cls:"af-form-label",text:"Agent"});let S=y.createEl("select",{cls:"af-form-select"});for(let R of r.agents){let se=S.createEl("option",{text:R.name,attr:{value:R.name}});R.name===a.agent&&(se.selected=!0)}S.addEventListener("change",()=>{p.agent=S.value});let T=f.createDiv({cls:"af-form-row"});T.createDiv({cls:"af-form-label",text:"Priority"});let _=T.createEl("select",{cls:"af-form-select"}),D=[["low","Low"],["medium","Medium"],["high","High"],["critical","Critical"]];for(let[R,se]of D){let ce=_.createEl("option",{text:se,attr:{value:R}});R===a.priority&&(ce.selected=!0)}_.addEventListener("change",()=>{p.priority=_.value}),this.createFormField(f,"Tags","monitoring, critical","Comma-separated",R=>{p.tags=R},a.tags.join(", "));let M=m.createDiv({cls:"af-create-section"}),C=M.createDiv({cls:"af-create-section-header"}),I=C.createSpan({cls:"af-create-section-icon"});(0,w.setIcon)(I,"message-square"),C.createSpan({text:"Instructions"});let P=M.createEl("textarea",{cls:"af-create-prompt-textarea",attr:{placeholder:"Describe what the agent should do...",rows:"10"}});P.value=a.body,P.addEventListener("input",()=>{p.body=P.value});let L=m.createDiv({cls:"af-create-section"}),F=L.createDiv({cls:"af-create-section-header"}),z=F.createSpan({cls:"af-create-section-icon"});(0,w.setIcon)(z,"clock"),F.createSpan({text:"Schedule"});let U=L.createDiv({cls:"af-form-row af-form-row-toggle"});U.createDiv({cls:"af-form-label",text:"Enable schedule"});let Z=U.createDiv({cls:`af-agent-card-toggle${u?" on":""}`}),de=L.createDiv({cls:"af-schedule-body"});de.setCssStyles({display:u?"":"none"}),Z.onclick=()=>{let R=Z.hasClass("on");Z.toggleClass("on",!R),p.scheduleEnabled=!R,de.setCssStyles({display:R?"none":""}),R?p.type="immediate":p.type=p.scheduleMode==="once"?"once":"recurring"};let me=de.createDiv({cls:"af-form-row"});me.createDiv({cls:"af-form-label",text:"Mode"});let ye=me.createEl("select",{cls:"af-form-select"});for(let[R,se]of[["recurring","Recurring"],["once","One-time"]]){let ce=ye.createEl("option",{text:se,attr:{value:R}});R===p.scheduleMode&&(ce.selected=!0)}let X=de.createDiv(),j=de.createDiv();X.setCssStyles({display:p.scheduleMode==="recurring"?"":"none"}),j.setCssStyles({display:p.scheduleMode==="once"?"":"none"}),this.renderInlineSchedule(X,p);let K=j.createDiv({cls:"af-form-row"});K.createDiv({cls:"af-form-label",text:"Run at"});let ne=p.runAt?this.toDatetimeLocal(new Date(p.runAt)):this.toDatetimeLocal(new Date(Date.now()+36e5)),G=K.createEl("input",{cls:"af-form-input",attr:{type:"datetime-local",value:ne}});p.runAt||(p.runAt=new Date(G.value).toISOString()),G.addEventListener("input",()=>{p.runAt=G.value?new Date(G.value).toISOString():""}),ye.addEventListener("change",()=>{p.scheduleMode=ye.value,X.setCssStyles({display:p.scheduleMode==="recurring"?"":"none"}),j.setCssStyles({display:p.scheduleMode==="once"?"":"none"}),p.scheduleEnabled&&(p.type=p.scheduleMode==="once"?"once":"recurring")});let ee=de.createDiv({cls:"af-form-row af-form-row-toggle"});ee.createDiv({cls:"af-form-label",text:"Enabled"});let Q=ee.createDiv({cls:`af-agent-card-toggle${a.enabled?" on":""}`});Q.onclick=()=>{let R=Q.hasClass("on");Q.toggleClass("on",!R),p.enabled=!R};let ae=de.createDiv({cls:"af-form-row af-form-row-toggle"});ae.createDiv({cls:"af-form-label"}).setText("Catch up if missed");let Re=ae.createDiv({cls:`af-agent-card-toggle${p.catchUp?" on":""}`});Re.onclick=()=>{let R=Re.hasClass("on");Re.toggleClass("on",!R),p.catchUp=!R};let Le=m.createDiv({cls:"af-create-section"}),Me=Le.createDiv({cls:"af-create-section-header"}),Fe=Me.createSpan({cls:"af-create-section-icon"});(0,w.setIcon)(Fe,"gauge"),Me.createSpan({text:"Execution"});let ke=Le.createDiv({cls:"af-form-row"}),Oe=ke.createDiv({cls:"af-form-label",text:"Effort"}),Ue=ke.createEl("select",{cls:"af-form-select"}),Ne=[["","Agent Default"],["low","Low"],["medium","Medium"],["high","High"],["max","Max"]];for(let[R,se]of Ne){let ce=Ue.createEl("option",{text:se,attr:{value:R}});R===p.effort&&(ce.selected=!0)}Ue.addEventListener("change",()=>{p.effort=Ue.value}),this.addTooltip(Oe,"Overrides the agent\u2019s effort level for this task. Higher effort = more thinking tokens spent.");let H=Le.createDiv({cls:"af-form-row"}),ie=H.createDiv({cls:"af-form-label",text:"Model"}),$=H.createDiv({cls:"af-form-field-wrap"}),Te=R=>{$.empty();let se=r.agents.find(ce=>ce.name===R);At($,{value:p.model,adapter:se?.adapter,onChange:ce=>{p.model=ce},allowInherit:!0,inheritPlaceholder:se?`Inherit from ${se.name}${se.model?` (${se.model})`:""}`:"Inherit from agent"})};Te(p.agent),S.addEventListener("change",()=>Te(S.value)),this.addTooltip(ie,"Override the agent\u2019s model for this task only. Useful for routing simple runs to haiku while the agent stays on opus for heavier work.");let xe=s.createDiv({cls:"af-create-footer"}),he=xe.createEl("button",{cls:"af-btn-sm danger"});E(he,"trash-2","af-btn-icon"),he.appendText(" Delete"),he.onclick=async()=>{await this.plugin.repository.deleteTask(a.taskId),new w.Notice(`Task "${a.taskId}" deleted.`),await new Promise(R=>window.setTimeout(R,200)),await this.plugin.refreshFromVault(),this.navigate("kanban")},xe.createDiv({cls:"af-toolbar-spacer"});let $e=xe.createEl("button",{cls:"af-btn-sm",text:"Cancel"});$e.onclick=()=>this.navigate("task-detail",a.taskId);let re=xe.createEl("button",{cls:"af-btn-sm primary af-create-submit"});E(re,"check","af-btn-icon"),re.appendText(" Save Changes"),re.onclick=async()=>{let R=ce=>ce.split(",").map(te=>te.trim()).filter(Boolean),se=p.scheduleEnabled?p.scheduleMode==="once"?"once":"recurring":"immediate";if(se==="once"&&!p.runAt){new w.Notice("Pick a date/time for the one-time run.");return}try{await this.plugin.repository.updateTask(a.taskId,{agent:p.agent,type:se,priority:p.priority,schedule:se==="recurring"?p.schedule.trim():"",runAt:se==="once"?p.runAt:"",enabled:p.enabled,catch_up:p.catchUp,effort:p.effort||void 0,model:p.model||"",tags:R(p.tags),body:p.body.trim()}),new w.Notice(`Task "${a.taskId}" updated.`),await this.plugin.refreshFromVault(),this.navigate("task-detail",a.taskId)}catch(ce){let te=ce instanceof Error?ce.message:String(ce);new w.Notice(`Failed to update task: ${te}`)}}}renderEditSkillPage(e){let s=e.createDiv({cls:"af-create-agent-page"}),n=this.detailContext;if(!n){this.renderEmptyState(s,"puzzle","No skill selected","");return}let a=this.plugin.runtime.getSnapshot().skills.find(G=>G.name===n);if(!a){this.renderEmptyState(s,"puzzle","Skill not found",`Skill "${n}" was not found`);return}let r=s.createDiv({cls:"af-detail-header"}),o=r.createDiv({cls:"af-detail-header-left"}),c=o.createDiv({cls:"af-agent-card-avatar idle"});(0,w.setIcon)(c,"edit");let l=o.createDiv();l.createDiv({cls:"af-detail-header-name",text:`Edit Skill: ${a.name}`}),l.createDiv({cls:"af-detail-header-desc",text:"Modify skill definition"});let h=r.createDiv({cls:"af-detail-header-actions"}),d={description:a.description??"",tags:a.tags.join(", "),body:a.body,toolsBody:a.toolsBody,referencesBody:a.referencesBody,examplesBody:a.examplesBody},u=s.createDiv({cls:"af-create-form"}),p=u.createDiv({cls:"af-create-section"}),m=p.createDiv({cls:"af-create-section-header"}),f=m.createSpan({cls:"af-create-section-icon"});(0,w.setIcon)(f,"puzzle"),m.createSpan({text:"Identity"});let g=p.createDiv({cls:"af-form-row"});g.createDiv({cls:"af-form-label",text:"Name"}),g.createEl("input",{cls:"af-form-input",attr:{type:"text",value:a.name,disabled:"true"}}).setCssStyles({opacity:"0.6"}),this.createFormField(p,"Description","Manage tasks and projects via CLI","",G=>{d.description=G},a.description??""),this.createFormField(p,"Tags","productivity, tasks","Comma-separated",G=>{d.tags=G},a.tags.join(", "));let b=u.createDiv({cls:"af-create-section"}),k=b.createDiv({cls:"af-create-section-header"}),y=k.createSpan({cls:"af-create-section-icon"});(0,w.setIcon)(y,"file-text"),k.createSpan({text:"Core Instructions"});let S=b.createEl("textarea",{cls:"af-create-prompt-textarea",attr:{placeholder:"Skill instructions...",rows:"10"}});S.value=a.body,S.addEventListener("input",()=>{d.body=S.value});let T=u.createDiv({cls:"af-create-section"}),_=T.createDiv({cls:"af-create-section-header"}),D=_.createSpan({cls:"af-create-section-icon"});(0,w.setIcon)(D,"wrench");let M=_.createSpan({text:"Tools"});this.addTooltip(M,"CLI commands, API endpoints, and tool definitions available to agents using this skill");let C=T.createEl("textarea",{cls:"af-create-prompt-textarea",attr:{placeholder:`## Commands +`),B.addEventListener("input",()=>{d.blockedCommands=B.value}),te.createDiv({cls:"af-form-hint",text:"On Codex agents these become execpolicy command rules \u2014 only Bash(cmd args *) prefixes are enforced; tool-name rules (Read/Write) and mid-pattern wildcards are ignored, and file/network access is governed by Permission Mode (the sandbox)."});let V=te.createDiv({cls:"af-form-row"});V.createDiv({cls:"af-form-label",text:"Memory enabled"});let Ee=V.createDiv({cls:`af-agent-card-toggle${a.memory?" on":""}`});Ee.onclick=()=>{let L=Ee.hasClass("on");Ee.toggleClass("on",!L),d.memory=!L};let we=te.createDiv({cls:"af-form-row"});we.createDiv({cls:"af-form-label",text:"Memory token budget"});let Ae=we.createEl("input",{cls:"af-create-input",attr:{type:"number",min:"200",step:"100"}});Ae.value=String(d.memoryTokenBudget),Ae.addEventListener("input",()=>{let L=parseInt(Ae.value,10);Number.isFinite(L)&&(d.memoryTokenBudget=L)});let nt=te.createDiv({cls:"af-form-row"});nt.createDiv({cls:"af-form-label",text:"Nightly reflection"});let je=nt.createDiv({cls:`af-agent-card-toggle${a.reflection.enabled?" on":""}`});je.onclick=()=>{let L=je.hasClass("on");je.toggleClass("on",!L),d.reflectionEnabled=!L};let Tt=te.createDiv({cls:"af-form-row"});Tt.createDiv({cls:"af-form-label",text:"Propose skills from memory"});let Bt=Tt.createDiv({cls:`af-agent-card-toggle${a.reflection.proposeSkills?" on":""}`});Bt.onclick=()=>{let L=Bt.hasClass("on");Bt.toggleClass("on",!L),d.reflectionProposeSkills=!L};let bt=s.createDiv({cls:"af-create-footer"}),ds=bt.createEl("button",{cls:"af-btn-sm danger"});R(ds,"trash-2","af-btn-icon"),ds.appendText(" Delete"),ds.onclick=()=>void this.plugin.deleteAgent(a.name),bt.createDiv({cls:"af-toolbar-spacer"});let Ns=bt.createEl("button",{cls:"af-btn-sm",text:"Cancel"});Ns.onclick=()=>this.navigate("agent-detail",a.name);let Ut=bt.createEl("button",{cls:"af-btn-sm primary af-create-submit"});R(Ut,"check","af-btn-icon"),Ut.appendText(" Save Changes"),Ut.onclick=async()=>{let L=Y=>Y.split(",").map(Te=>Te.trim()).filter(Boolean);try{let Y=Te=>ye(Te).map(Le=>Le.trim()).filter(Boolean);await this.plugin.repository.updateAgent(a.name,{description:d.description.trim(),avatar:d.avatar.trim(),tags:L(d.tags),systemPrompt:d.systemPrompt.trim(),model:d.model.trim()||"default",adapter:d.adapter,cwd:d.cwd.trim(),timeout:d.timeout,permissionMode:d.permissionMode,effort:d.effort||void 0,approvalRequired:L(d.approvalRequired),memory:d.memory,memoryTokenBudget:d.memoryTokenBudget,reflectionEnabled:d.reflectionEnabled,reflectionProposeSkills:d.reflectionProposeSkills,skills:Array.from(d.selectedSkills),mcpServers:Array.from(d.selectedMcpServers),skillsBody:d.skillsBody.trim(),contextBody:d.contextBody.trim(),enabled:d.enabled,permissionRules:{allow:Y(d.allowedCommands),deny:Y(d.blockedCommands)},autoCompactThreshold:d.autoCompactThreshold,wikiReferences:d.wikiReferences}),a.isFolder&&await this.plugin.repository.updateHeartbeat(a.name,{enabled:d.heartbeatEnabled,schedule:d.heartbeatSchedule.trim(),notify:d.heartbeatNotify,channel:d.heartbeatChannel,channelTarget:d.heartbeatChannel?d.heartbeatChannelTarget:"",body:d.heartbeatBody.trim()}),new b.Notice(`Agent "${a.name}" updated.`),await this.plugin.refreshFromVault(),this.navigate("agent-detail",a.name)}catch(Y){let Te=Y instanceof Error?Y.message:String(Y);new b.Notice(`Failed to update agent: ${Te}`)}}}renderTaskChannelDelivery(e,s,n){let a=e.createDiv({cls:"af-form-row"}),r=a.createDiv({cls:"af-form-label",text:"Channel"}),o=a.createEl("select",{cls:"af-form-select"});o.createEl("option",{text:"\u2014 none (run log only) \u2014",attr:{value:""}});for(let u of s){let p=o.createEl("option",{text:u.name,attr:{value:u.name}});u.name===n.channel&&(p.selected=!0)}this.addTooltip(r,"Post this task\u2019s full output to a channel when it finishes. Leave as none to only write the run log (the default batched behavior).");let c=e.createDiv({cls:"af-form-row"}),l=c.createDiv({cls:"af-form-label",text:"Target ID"}),h=c.createEl("input",{cls:"af-form-input",attr:{type:"text",placeholder:"Discord/Slack channel ID or Telegram chat ID \u2014 empty = DM you"}});h.value=n.channelTarget,h.addEventListener("input",()=>{n.channelTarget=h.value.trim()}),this.addTooltip(l,"Where in the channel to post. For Discord, enable Developer Mode and right-click the channel \u2192 Copy Channel ID. Leave empty to DM the first allowed user instead.");let d=()=>{c.setCssStyles({display:n.channel?"":"none"})};d(),o.addEventListener("change",()=>{n.channel=o.value,d()})}renderCreateTaskPage(e){let s=e.createDiv({cls:"af-create-agent-page"}),n=this.plugin.runtime.getSnapshot(),a=s.createDiv({cls:"af-detail-header"}),r=a.createDiv({cls:"af-detail-header-left"}),o=r.createDiv({cls:"af-agent-card-avatar idle"});(0,b.setIcon)(o,"plus");let c=r.createDiv();c.createDiv({cls:"af-detail-header-name",text:"Create New Task"}),c.createDiv({cls:"af-detail-header-desc",text:"Configure a new task for your fleet"});let l=a.createDiv({cls:"af-detail-header-actions"}),h={title:"",agent:n.agents[0]?.name??"",priority:"medium",tags:"",body:"",scheduleEnabled:!1,scheduleMode:"recurring",schedule:"0 9 * * *",runAt:"",type:"immediate",enabled:!0,catchUp:!0,effort:"",model:"",channel:"",channelTarget:""},d=s.createDiv({cls:"af-create-form"}),u=d.createDiv({cls:"af-create-section"}),p=u.createDiv({cls:"af-create-section-header"}),m=p.createSpan({cls:"af-create-section-icon"});(0,b.setIcon)(m,"file-text"),p.createSpan({text:"Task Details"}),this.createFormField(u,"Title","Daily status report","Used as the task identifier",q=>{h.title=q});let f=u.createDiv({cls:"af-form-row"});f.createDiv({cls:"af-form-label",text:"Agent"});let g=f.createEl("select",{cls:"af-form-select"});for(let q of n.agents)g.createEl("option",{text:q.name,attr:{value:q.name}});g.addEventListener("change",()=>{h.agent=g.value});let y=u.createDiv({cls:"af-form-row"});y.createDiv({cls:"af-form-label",text:"Priority"});let v=y.createEl("select",{cls:"af-form-select"}),k=[["low","Low"],["medium","Medium"],["high","High"],["critical","Critical"]];for(let[q,ie]of k){let j=v.createEl("option",{text:ie,attr:{value:q}});q==="medium"&&(j.selected=!0)}v.addEventListener("change",()=>{h.priority=v.value}),this.createFormField(u,"Tags","monitoring, devops","Comma-separated",q=>{h.tags=q});let w=d.createDiv({cls:"af-create-section"}),S=w.createDiv({cls:"af-create-section-header"}),T=S.createSpan({cls:"af-create-section-icon"});(0,b.setIcon)(T,"message-square"),S.createSpan({text:"Instructions"});let _=w.createEl("textarea",{cls:"af-create-prompt-textarea",attr:{placeholder:"Describe what the agent should do...",rows:"10"}});_.addEventListener("input",()=>{h.body=_.value});let D=d.createDiv({cls:"af-create-section"}),O=D.createDiv({cls:"af-create-section-header"}),C=O.createSpan({cls:"af-create-section-icon"});(0,b.setIcon)(C,"clock"),O.createSpan({text:"Schedule"});let E=D.createDiv({cls:"af-form-row af-form-row-toggle"});E.createDiv({cls:"af-form-label",text:"Enable schedule"});let P=E.createDiv({cls:"af-agent-card-toggle"}),N=D.createDiv({cls:"af-schedule-body"});N.setCssStyles({display:"none"}),P.onclick=()=>{let q=P.hasClass("on");P.toggleClass("on",!q),h.scheduleEnabled=!q,N.setCssStyles({display:q?"none":""}),q?h.type="immediate":h.type=h.scheduleMode==="once"?"once":"recurring"};let M=N.createDiv({cls:"af-form-row"});M.createDiv({cls:"af-form-label",text:"Mode"});let U=M.createEl("select",{cls:"af-form-select"});for(let[q,ie]of[["recurring","Recurring"],["once","One-time"]])U.createEl("option",{text:ie,attr:{value:q}});let $=N.createDiv(),Z=N.createDiv();Z.setCssStyles({display:"none"}),this.renderInlineSchedule($,h);let de=Z.createDiv({cls:"af-form-row"});de.createDiv({cls:"af-form-label",text:"Run at"});let me=de.createEl("input",{cls:"af-form-input",attr:{type:"datetime-local",value:this.toDatetimeLocal(new Date(Date.now()+36e5))}});h.runAt=new Date(me.value).toISOString(),me.addEventListener("input",()=>{h.runAt=me.value?new Date(me.value).toISOString():""}),U.addEventListener("change",()=>{h.scheduleMode=U.value,$.setCssStyles({display:h.scheduleMode==="recurring"?"":"none"}),Z.setCssStyles({display:h.scheduleMode==="once"?"":"none"}),h.scheduleEnabled&&(h.type=h.scheduleMode==="once"?"once":"recurring")});let ge=N.createDiv({cls:"af-form-row af-form-row-toggle"});ge.createDiv({cls:"af-form-label",text:"Enabled"});let X=ge.createDiv({cls:"af-agent-card-toggle on"});X.onclick=()=>{let q=X.hasClass("on");X.toggleClass("on",!q),h.enabled=!q};let W=N.createDiv({cls:"af-form-row af-form-row-toggle"});W.createDiv({cls:"af-form-label"}).setText("Catch up if missed");let ne=W.createDiv({cls:`af-agent-card-toggle${h.catchUp?" on":""}`});ne.onclick=()=>{let q=ne.hasClass("on");ne.toggleClass("on",!q),h.catchUp=!q};let G=d.createDiv({cls:"af-create-section"}),ee=G.createDiv({cls:"af-create-section-header"}),Q=ee.createSpan({cls:"af-create-section-icon"});(0,b.setIcon)(Q,"gauge"),ee.createSpan({text:"Execution"});let ae=G.createDiv({cls:"af-form-row"}),ve=ae.createDiv({cls:"af-form-label",text:"Model"}),Pe=ae.createDiv({cls:"af-form-field-wrap"}),Ie=q=>{Pe.empty();let ie=n.agents.find(j=>j.name===q);Pt(Pe,{value:h.model,adapter:ie?.adapter,onChange:j=>{h.model=j},allowInherit:!0,inheritPlaceholder:ie?`Inherit from ${ie.name}${ie.model?` (${ie.model})`:""}`:"Inherit from agent"})};Ie(h.agent),g.addEventListener("change",()=>Ie(g.value)),this.addTooltip(ve,"Override the agent\u2019s model for this task only. Useful for routing simple runs to haiku while the agent stays on opus for heavier work.");let Me=G.createDiv({cls:"af-form-row"}),Fe=Me.createDiv({cls:"af-form-label",text:"Effort"}),ke=Me.createEl("select",{cls:"af-form-select"});for(let[q,ie]of[["","Agent Default"],["low","Low"],["medium","Medium"],["high","High"],["max","Max"]]){let j=ke.createEl("option",{text:ie,attr:{value:q}});q===h.effort&&(j.selected=!0)}ke.addEventListener("change",()=>{h.effort=ke.value}),this.renderTaskChannelDelivery(G,n.channels,h),this.addTooltip(Fe,"Overrides the agent\u2019s effort level for this task. Higher effort = more thinking tokens spent.");let Oe=s.createDiv({cls:"af-create-footer"}),Ue=Oe.createEl("button",{cls:"af-btn-sm",text:"Cancel"});Ue.onclick=()=>this.navigate("kanban");let Ne=Oe.createEl("button",{cls:"af-btn-sm primary af-create-submit"});R(Ne,"plus","af-btn-icon"),Ne.appendText(" Create Task"),Ne.onclick=async()=>{let q=h.title.trim();if(!q){new b.Notice("Task title is required.");return}let ie=oe(q),j=he=>he.split(",").map($e=>$e.trim()).filter(Boolean),Ce=h.scheduleEnabled?h.scheduleMode==="once"?"once":"recurring":"immediate",xe={task_id:ie,agent:h.agent,type:Ce,priority:h.priority,enabled:h.enabled,created:this.toLocalISO(new Date),run_count:0,catch_up:h.catchUp,effort:h.effort||void 0,model:h.model||void 0,channel:h.channel||void 0,channel_target:h.channel&&h.channelTarget?h.channelTarget:void 0,tags:j(h.tags)};if(Ce==="recurring")xe.schedule=h.schedule.trim()||"0 9 * * *";else if(Ce==="once"){if(!h.runAt){new b.Notice("Pick a date/time for the one-time run.");return}xe.run_at=h.runAt}try{let he=await this.plugin.repository.getAvailablePath(this.plugin.repository.getSubfolder("tasks"),ie);await this.plugin.app.vault.create(he,H(xe,h.body.trim()||"Describe the task here.")),new b.Notice(`Task "${ie}" created.`),await this.plugin.refreshFromVault(),this.navigate("task-detail",ie)}catch(he){let $e=he instanceof Error?he.message:String(he);new b.Notice(`Failed to create task: ${$e}`)}}}toLocalISO(e){let s=n=>String(n).padStart(2,"0");return`${e.getFullYear()}-${s(e.getMonth()+1)}-${s(e.getDate())}T${s(e.getHours())}:${s(e.getMinutes())}:${s(e.getSeconds())}`}toDatetimeLocal(e){let s=n=>String(n).padStart(2,"0");return`${e.getFullYear()}-${s(e.getMonth()+1)}-${s(e.getDate())}T${s(e.getHours())}:${s(e.getMinutes())}`}renderEditTaskPage(e){let s=e.createDiv({cls:"af-create-agent-page"}),n=this.detailContext;if(!n){this.renderEmptyState(s,"circle-dot","No task selected","");return}let a=this.plugin.runtime.getSnapshot().tasks.find(I=>I.taskId===n);if(!a){this.renderEmptyState(s,"circle-dot","Task not found",`Task "${n}" was not found`);return}let r=this.plugin.runtime.getSnapshot(),o=s.createDiv({cls:"af-detail-header"}),c=o.createDiv({cls:"af-detail-header-left"}),l=c.createDiv({cls:"af-agent-card-avatar idle"});(0,b.setIcon)(l,"edit");let h=c.createDiv();h.createDiv({cls:"af-detail-header-name",text:`Edit Task: ${a.taskId}`}),h.createDiv({cls:"af-detail-header-desc",text:"Modify task configuration"});let d=o.createDiv({cls:"af-detail-header-actions"}),u=!!(a.schedule||a.runAt),p={agent:a.agent,type:a.type,priority:a.priority,schedule:a.schedule??"0 9 * * *",runAt:a.runAt??"",scheduleEnabled:u,scheduleMode:a.type==="once"?"once":"recurring",enabled:a.enabled,catchUp:a.catchUp,effort:a.effort??"",model:a.model??"",channel:a.channel??"",channelTarget:a.channelTarget??"",tags:a.tags.join(", "),body:a.body},m=s.createDiv({cls:"af-create-form"}),f=m.createDiv({cls:"af-create-section"}),g=f.createDiv({cls:"af-create-section-header"}),y=g.createSpan({cls:"af-create-section-icon"});(0,b.setIcon)(y,"file-text"),g.createSpan({text:"Task Details"});let v=f.createDiv({cls:"af-form-row"});v.createDiv({cls:"af-form-label",text:"Title"}),v.createEl("input",{cls:"af-form-input",attr:{type:"text",value:a.taskId,disabled:"true"}}).setCssStyles({opacity:"0.6"});let w=f.createDiv({cls:"af-form-row"});w.createDiv({cls:"af-form-label",text:"Agent"});let S=w.createEl("select",{cls:"af-form-select"});for(let I of r.agents){let se=S.createEl("option",{text:I.name,attr:{value:I.name}});I.name===a.agent&&(se.selected=!0)}S.addEventListener("change",()=>{p.agent=S.value});let T=f.createDiv({cls:"af-form-row"});T.createDiv({cls:"af-form-label",text:"Priority"});let _=T.createEl("select",{cls:"af-form-select"}),D=[["low","Low"],["medium","Medium"],["high","High"],["critical","Critical"]];for(let[I,se]of D){let ce=_.createEl("option",{text:se,attr:{value:I}});I===a.priority&&(ce.selected=!0)}_.addEventListener("change",()=>{p.priority=_.value}),this.createFormField(f,"Tags","monitoring, critical","Comma-separated",I=>{p.tags=I},a.tags.join(", "));let O=m.createDiv({cls:"af-create-section"}),C=O.createDiv({cls:"af-create-section-header"}),E=C.createSpan({cls:"af-create-section-icon"});(0,b.setIcon)(E,"message-square"),C.createSpan({text:"Instructions"});let P=O.createEl("textarea",{cls:"af-create-prompt-textarea",attr:{placeholder:"Describe what the agent should do...",rows:"10"}});P.value=a.body,P.addEventListener("input",()=>{p.body=P.value});let N=m.createDiv({cls:"af-create-section"}),M=N.createDiv({cls:"af-create-section-header"}),U=M.createSpan({cls:"af-create-section-icon"});(0,b.setIcon)(U,"clock"),M.createSpan({text:"Schedule"});let $=N.createDiv({cls:"af-form-row af-form-row-toggle"});$.createDiv({cls:"af-form-label",text:"Enable schedule"});let Z=$.createDiv({cls:`af-agent-card-toggle${u?" on":""}`}),de=N.createDiv({cls:"af-schedule-body"});de.setCssStyles({display:u?"":"none"}),Z.onclick=()=>{let I=Z.hasClass("on");Z.toggleClass("on",!I),p.scheduleEnabled=!I,de.setCssStyles({display:I?"none":""}),I?p.type="immediate":p.type=p.scheduleMode==="once"?"once":"recurring"};let me=de.createDiv({cls:"af-form-row"});me.createDiv({cls:"af-form-label",text:"Mode"});let ge=me.createEl("select",{cls:"af-form-select"});for(let[I,se]of[["recurring","Recurring"],["once","One-time"]]){let ce=ge.createEl("option",{text:se,attr:{value:I}});I===p.scheduleMode&&(ce.selected=!0)}let X=de.createDiv(),W=de.createDiv();X.setCssStyles({display:p.scheduleMode==="recurring"?"":"none"}),W.setCssStyles({display:p.scheduleMode==="once"?"":"none"}),this.renderInlineSchedule(X,p);let K=W.createDiv({cls:"af-form-row"});K.createDiv({cls:"af-form-label",text:"Run at"});let ne=p.runAt?this.toDatetimeLocal(new Date(p.runAt)):this.toDatetimeLocal(new Date(Date.now()+36e5)),G=K.createEl("input",{cls:"af-form-input",attr:{type:"datetime-local",value:ne}});p.runAt||(p.runAt=new Date(G.value).toISOString()),G.addEventListener("input",()=>{p.runAt=G.value?new Date(G.value).toISOString():""}),ge.addEventListener("change",()=>{p.scheduleMode=ge.value,X.setCssStyles({display:p.scheduleMode==="recurring"?"":"none"}),W.setCssStyles({display:p.scheduleMode==="once"?"":"none"}),p.scheduleEnabled&&(p.type=p.scheduleMode==="once"?"once":"recurring")});let ee=de.createDiv({cls:"af-form-row af-form-row-toggle"});ee.createDiv({cls:"af-form-label",text:"Enabled"});let Q=ee.createDiv({cls:`af-agent-card-toggle${a.enabled?" on":""}`});Q.onclick=()=>{let I=Q.hasClass("on");Q.toggleClass("on",!I),p.enabled=!I};let ae=de.createDiv({cls:"af-form-row af-form-row-toggle"});ae.createDiv({cls:"af-form-label"}).setText("Catch up if missed");let Pe=ae.createDiv({cls:`af-agent-card-toggle${p.catchUp?" on":""}`});Pe.onclick=()=>{let I=Pe.hasClass("on");Pe.toggleClass("on",!I),p.catchUp=!I};let Ie=m.createDiv({cls:"af-create-section"}),Me=Ie.createDiv({cls:"af-create-section-header"}),Fe=Me.createSpan({cls:"af-create-section-icon"});(0,b.setIcon)(Fe,"gauge"),Me.createSpan({text:"Execution"});let ke=Ie.createDiv({cls:"af-form-row"}),Oe=ke.createDiv({cls:"af-form-label",text:"Effort"}),Ue=ke.createEl("select",{cls:"af-form-select"}),Ne=[["","Agent Default"],["low","Low"],["medium","Medium"],["high","High"],["max","Max"]];for(let[I,se]of Ne){let ce=Ue.createEl("option",{text:se,attr:{value:I}});I===p.effort&&(ce.selected=!0)}Ue.addEventListener("change",()=>{p.effort=Ue.value}),this.addTooltip(Oe,"Overrides the agent\u2019s effort level for this task. Higher effort = more thinking tokens spent.");let q=Ie.createDiv({cls:"af-form-row"}),ie=q.createDiv({cls:"af-form-label",text:"Model"}),j=q.createDiv({cls:"af-form-field-wrap"}),Ce=I=>{j.empty();let se=r.agents.find(ce=>ce.name===I);Pt(j,{value:p.model,adapter:se?.adapter,onChange:ce=>{p.model=ce},allowInherit:!0,inheritPlaceholder:se?`Inherit from ${se.name}${se.model?` (${se.model})`:""}`:"Inherit from agent"})};Ce(p.agent),S.addEventListener("change",()=>Ce(S.value)),this.renderTaskChannelDelivery(Ie,r.channels,p),this.addTooltip(ie,"Override the agent\u2019s model for this task only. Useful for routing simple runs to haiku while the agent stays on opus for heavier work.");let xe=s.createDiv({cls:"af-create-footer"}),he=xe.createEl("button",{cls:"af-btn-sm danger"});R(he,"trash-2","af-btn-icon"),he.appendText(" Delete"),he.onclick=async()=>{await this.plugin.repository.deleteTask(a.taskId),new b.Notice(`Task "${a.taskId}" deleted.`),await new Promise(I=>window.setTimeout(I,200)),await this.plugin.refreshFromVault(),this.navigate("kanban")},xe.createDiv({cls:"af-toolbar-spacer"});let $e=xe.createEl("button",{cls:"af-btn-sm",text:"Cancel"});$e.onclick=()=>this.navigate("task-detail",a.taskId);let re=xe.createEl("button",{cls:"af-btn-sm primary af-create-submit"});R(re,"check","af-btn-icon"),re.appendText(" Save Changes"),re.onclick=async()=>{let I=ce=>ce.split(",").map(te=>te.trim()).filter(Boolean),se=p.scheduleEnabled?p.scheduleMode==="once"?"once":"recurring":"immediate";if(se==="once"&&!p.runAt){new b.Notice("Pick a date/time for the one-time run.");return}try{await this.plugin.repository.updateTask(a.taskId,{agent:p.agent,type:se,priority:p.priority,schedule:se==="recurring"?p.schedule.trim():"",runAt:se==="once"?p.runAt:"",enabled:p.enabled,catch_up:p.catchUp,effort:p.effort||void 0,model:p.model||"",channel:p.channel||"",channelTarget:p.channel&&p.channelTarget?p.channelTarget:"",tags:I(p.tags),body:p.body.trim()}),new b.Notice(`Task "${a.taskId}" updated.`),await this.plugin.refreshFromVault(),this.navigate("task-detail",a.taskId)}catch(ce){let te=ce instanceof Error?ce.message:String(ce);new b.Notice(`Failed to update task: ${te}`)}}}renderEditSkillPage(e){let s=e.createDiv({cls:"af-create-agent-page"}),n=this.detailContext;if(!n){this.renderEmptyState(s,"puzzle","No skill selected","");return}let a=this.plugin.runtime.getSnapshot().skills.find(G=>G.name===n);if(!a){this.renderEmptyState(s,"puzzle","Skill not found",`Skill "${n}" was not found`);return}let r=s.createDiv({cls:"af-detail-header"}),o=r.createDiv({cls:"af-detail-header-left"}),c=o.createDiv({cls:"af-agent-card-avatar idle"});(0,b.setIcon)(c,"edit");let l=o.createDiv();l.createDiv({cls:"af-detail-header-name",text:`Edit Skill: ${a.name}`}),l.createDiv({cls:"af-detail-header-desc",text:"Modify skill definition"});let h=r.createDiv({cls:"af-detail-header-actions"}),d={description:a.description??"",tags:a.tags.join(", "),body:a.body,toolsBody:a.toolsBody,referencesBody:a.referencesBody,examplesBody:a.examplesBody},u=s.createDiv({cls:"af-create-form"}),p=u.createDiv({cls:"af-create-section"}),m=p.createDiv({cls:"af-create-section-header"}),f=m.createSpan({cls:"af-create-section-icon"});(0,b.setIcon)(f,"puzzle"),m.createSpan({text:"Identity"});let g=p.createDiv({cls:"af-form-row"});g.createDiv({cls:"af-form-label",text:"Name"}),g.createEl("input",{cls:"af-form-input",attr:{type:"text",value:a.name,disabled:"true"}}).setCssStyles({opacity:"0.6"}),this.createFormField(p,"Description","Manage tasks and projects via CLI","",G=>{d.description=G},a.description??""),this.createFormField(p,"Tags","productivity, tasks","Comma-separated",G=>{d.tags=G},a.tags.join(", "));let v=u.createDiv({cls:"af-create-section"}),k=v.createDiv({cls:"af-create-section-header"}),w=k.createSpan({cls:"af-create-section-icon"});(0,b.setIcon)(w,"file-text"),k.createSpan({text:"Core Instructions"});let S=v.createEl("textarea",{cls:"af-create-prompt-textarea",attr:{placeholder:"Skill instructions...",rows:"10"}});S.value=a.body,S.addEventListener("input",()=>{d.body=S.value});let T=u.createDiv({cls:"af-create-section"}),_=T.createDiv({cls:"af-create-section-header"}),D=_.createSpan({cls:"af-create-section-icon"});(0,b.setIcon)(D,"wrench");let O=_.createSpan({text:"Tools"});this.addTooltip(O,"CLI commands, API endpoints, and tool definitions available to agents using this skill");let C=T.createEl("textarea",{cls:"af-create-prompt-textarea",attr:{placeholder:`## Commands ### list -...`,rows:"8"}});C.value=a.toolsBody,C.addEventListener("input",()=>{d.toolsBody=C.value});let I=u.createDiv({cls:"af-create-section"}),P=I.createDiv({cls:"af-create-section-header"}),L=P.createSpan({cls:"af-create-section-icon"});(0,w.setIcon)(L,"book-open");let F=P.createSpan({text:"References"});this.addTooltip(F,"Background docs, conventions, cheat sheets");let z=I.createEl("textarea",{cls:"af-create-prompt-textarea",attr:{placeholder:"API docs, filter syntax, conventions...",rows:"6"}});z.value=a.referencesBody,z.addEventListener("input",()=>{d.referencesBody=z.value});let U=u.createDiv({cls:"af-create-section"}),Z=U.createDiv({cls:"af-create-section-header"}),de=Z.createSpan({cls:"af-create-section-icon"});(0,w.setIcon)(de,"message-circle");let me=Z.createSpan({text:"Examples"});this.addTooltip(me,"Example prompts and ideal outputs showing how to use this skill");let ye=U.createEl("textarea",{cls:"af-create-prompt-textarea",attr:{placeholder:`## Example: List all tasks -...`,rows:"6"}});ye.value=a.examplesBody,ye.addEventListener("input",()=>{d.examplesBody=ye.value});let X=s.createDiv({cls:"af-create-footer"}),j=X.createEl("button",{cls:"af-btn-sm danger"});E(j,"trash-2","af-btn-icon"),j.appendText(" Delete"),j.onclick=async()=>{await this.plugin.repository.deleteSkill(a.name),new w.Notice(`Skill "${a.name}" deleted.`),await new Promise(G=>window.setTimeout(G,200)),await this.plugin.refreshFromVault(),this.navigate("skills")},X.createDiv({cls:"af-toolbar-spacer"});let K=X.createEl("button",{cls:"af-btn-sm",text:"Cancel"});K.onclick=()=>this.navigate("skills");let ne=X.createEl("button",{cls:"af-btn-sm primary af-create-submit"});E(ne,"check","af-btn-icon"),ne.appendText(" Save Changes"),ne.onclick=async()=>{let G=ee=>ee.split(",").map(Q=>Q.trim()).filter(Boolean);try{await this.plugin.repository.updateSkill(a.name,{description:d.description.trim(),tags:G(d.tags),body:d.body.trim(),toolsBody:d.toolsBody.trim(),referencesBody:d.referencesBody.trim(),examplesBody:d.examplesBody.trim()}),new w.Notice(`Skill "${a.name}" updated.`),await this.plugin.refreshFromVault(),this.navigate("skills")}catch(ee){let Q=ee instanceof Error?ee.message:String(ee);new w.Notice(`Failed to update skill: ${Q}`)}}}renderAgentMcpPicker(e,s){e.createDiv({cls:"af-form-hint",text:"Servers from the MCP Servers tab. Checked servers are available to this agent on any adapter (Claude or Codex). Leave all unchecked to grant every enabled server."});let n=this.plugin.repository.getMcpServers();if(n.length===0){let r=e.createDiv({cls:"af-form-hint"});r.appendText("No MCP servers registered yet. ");let o=r.createEl("a",{cls:"af-link",text:"Add one in the MCP Servers tab."});o.onclick=c=>{c.preventDefault(),this.navigate("mcp")};return}let a=e.createDiv({cls:"af-create-skills-grid"});for(let r of n){let o=a.createDiv({cls:"af-mcp-agent-item"}),c=o.createEl("input",{cls:"af-form-toggle",attr:{type:"checkbox"}});c.checked=s.has(r.name),c.addEventListener("change",()=>{c.checked?s.add(r.name):s.delete(r.name)});let h=o.createDiv({cls:"af-mcp-agent-label"}).createDiv({cls:"af-mcp-agent-name-row"}),d=h.createSpan({cls:`af-mcp-status-dot ${r.enabled?"idle":"disabled"}`});d.title=r.enabled?"enabled":"disabled",h.createSpan({cls:"af-create-skill-name",text:r.name}),h.createSpan({cls:"af-mcp-agent-tool-count",text:r.type}),r.enabled||h.createSpan({cls:"af-mcp-agent-tool-count af-muted",text:"disabled"})}}renderMcpPage(e){let s=e.createDiv({cls:"af-agents-page"}),n=s.createDiv({cls:"af-agents-toolbar"});n.createDiv({cls:"af-page-title",text:"MCP Servers"}),n.createDiv({cls:"af-toolbar-spacer"});let a=n.createEl("button",{cls:"af-btn-sm primary"});E(a,"plus","af-btn-icon"),a.appendText(" Add Server"),a.onclick=()=>this.navigate("add-mcp-server");let r=this.plugin.repository.getMcpServers();if(r.length===0){this.renderEmptyState(s,"plug","No MCP servers registered","Click 'Add Server' above to register one.");return}let o=s.createDiv({cls:"af-agents-grid"});for(let c of r)this.renderMcpCard(o,c)}mcpProbeCache=new Map;mcpHasToken(e){return this.plugin.mcpAuth.hasToken(e.name)}mcpNeedsAuth(e){return e.type!=="stdio"&&(e.auth==="oauth"||e.auth==="bearer")&&!this.mcpHasToken(e)}renderMcpCard(e,s){let n=e.createDiv({cls:`af-mcp-card${s.enabled?"":" af-mcp-card-disabled"}`}),a=this.mcpNeedsAuth(s),r=n.createDiv({cls:"af-agent-card-header"}),o=s.enabled?a?"pending":"idle":"disabled",c=r.createDiv({cls:`af-agent-card-avatar ${o}`});(0,w.setIcon)(c,"plug");let l=r.createDiv({cls:"af-agent-card-titleblock"});l.createDiv({cls:"af-agent-card-name",text:s.name});let h=l.createDiv({cls:"af-agent-card-desc af-mcp-meta"});h.createSpan({cls:"af-mcp-type-badge",text:s.type}),s.source==="imported"&&h.createSpan({cls:"af-badge",text:"imported"});let d=r.createDiv({cls:`af-agent-card-toggle${s.enabled?" on":""}`});d.onclick=g=>{g.stopPropagation(),this.plugin.repository.setMcpServerEnabled(s.name,!s.enabled).then(async()=>{await this.plugin.refreshFromVault(),this.render()})};let u=n.createDiv({cls:`af-mcp-status-badge ${s.enabled?a?"needs-auth":"connected":"disabled"}`}),p=u.createSpan();if(s.enabled?a?((0,w.setIcon)(p,"alert-circle"),u.createSpan({text:" Needs auth"})):((0,w.setIcon)(p,"check-circle"),u.createSpan({text:s.type==="stdio"?" Enabled":" Authenticated"})):((0,w.setIcon)(p,"pause"),u.createSpan({text:" Disabled"})),s.description){let g=this.truncateDescription(s.description,120);n.createDiv({cls:"af-mcp-description",text:g})}let m=s.url??s.command??"";m&&n.createDiv({cls:"af-mcp-command",text:$t(m,60)});let f=this.mcpProbeCache.get(s.name);if(f&&f.length>0){let g=n.createDiv({cls:"af-mcp-tool-footer"}),v=g.createDiv({cls:"af-mcp-tool-count"}),b=v.createSpan();(0,w.setIcon)(b,"wrench"),v.createSpan({text:` ${f.length} tools`});let k=g.createDiv({cls:"af-mcp-tool-chips"});for(let y of f.slice(0,4))k.createSpan({cls:"af-mcp-tool-chip",text:y.name});f.length>4&&k.createSpan({cls:"af-mcp-tool-chip af-mcp-tool-chip-more",text:`+${f.length-4}`})}if(this.authenticatingServers.has(s.name)){let v=n.createDiv({cls:"af-mcp-auth-row"}).createEl("button",{cls:"af-btn-sm primary",attr:{disabled:"true"}}),b=v.createSpan({cls:"af-spin"});(0,w.setIcon)(b,"loader-2"),v.appendText(" Authenticating\u2026")}else if(s.enabled&&a&&s.auth==="oauth"){let v=n.createDiv({cls:"af-mcp-auth-row"}).createEl("button",{cls:"af-btn-sm primary"}),b=v.createSpan();(0,w.setIcon)(b,"key"),v.appendText(" Authenticate"),v.onclick=k=>{k.stopPropagation(),this.authenticateMcpServer(s)}}n.onclick=()=>this.openMcpDetailSlideover(s)}async authenticateMcpServer(e){if(!e.url){new w.Notice("No URL found for this server \u2014 can't authenticate.");return}this.authenticatingServers.add(e.name),this.render(),new w.Notice(`Authenticating ${e.name}\u2026 Complete authorization in your browser.`,1e4);try{let s=e.type==="sse"?"sse":"http";await this.plugin.mcpManager.authenticateServer(e.name,e.url,s),new w.Notice(`${e.name} authenticated successfully!`)}catch(s){let n=s instanceof Error?s.message:String(s);new w.Notice(`Authentication failed: ${n}`,8e3)}finally{this.authenticatingServers.delete(e.name),this.render()}}async probeMcpServer(e){try{let s=await this.plugin.mcpManager.probeServer(e);this.mcpProbeCache.set(e.name,s),s.length===0&&new w.Notice(`No tools discovered for ${e.name}.`)}catch(s){let n=s instanceof Error?s.message:String(s);new w.Notice(`Probe failed: ${n}`)}}truncateDescription(e,s){let n=ge(e)[0]??e,a=n.split(/(?<=[.!?])\s/)[0]??n,r=a.lengths.remove(),s.onclick=v=>{v.target===s&&s.remove()};let o=n.createDiv({cls:"af-slideover-body"});if(e.description){let v=o.createDiv({cls:"af-slideover-section"});v.createDiv({cls:"af-slideover-section-title",text:"DESCRIPTION"}),v.createDiv({cls:"af-mcp-detail-description",text:e.description})}let c=o.createDiv({cls:"af-slideover-section"});c.createDiv({cls:"af-slideover-section-title",text:"SERVER INFO"}),this.renderDetailRow(c,"Name",e.name),this.renderDetailRow(c,"Transport",e.type),this.renderDetailRow(c,"Enabled",e.enabled?"yes":"no"),e.type!=="stdio"&&(this.renderDetailRow(c,"Auth",e.auth??"none"),this.renderDetailRow(c,"Authenticated",this.mcpHasToken(e)?"yes":"no")),e.source&&this.renderDetailRow(c,"Source",e.source),e.url&&this.renderDetailRow(c,"URL",e.url),e.command&&this.renderDetailRow(c,"Command",e.command),e.args&&e.args.length>0&&this.renderDetailRow(c,"Args",e.args.join(" "));let l=this.mcpProbeCache.get(e.name)??[],h=o.createDiv({cls:"af-slideover-section"});h.createDiv({cls:"af-slideover-section-title"}).setText(`TOOLS (${l.length})`);let u=h.createEl("button",{cls:"af-btn-sm"}),p=u.createSpan();if((0,w.setIcon)(p,"wrench"),u.appendText(" Probe tools"),u.onclick=async()=>{u.disabled=!0,u.setText(" Probing\u2026"),await this.probeMcpServer(e),s.remove(),this.openMcpDetailSlideover(e)},l.length>0)for(let v of l){let b=h.createDiv({cls:"af-mcp-tool-detail"}),k=b.createDiv({cls:"af-mcp-tool-detail-header"}),y=k.createSpan({cls:"af-mcp-tool-detail-name"}),S=y.createSpan();if((0,w.setIcon)(S,"wrench"),y.createSpan({text:` ${v.name}`}),v.inputSchema){let T=v.inputSchema.required??[];T.length>0&&k.createSpan({cls:"af-mcp-tool-param-count",text:`${T.length} param${T.length!==1?"s":""}`})}if(v.description){let T=ge(v.description).filter(M=>M.trim()),_=T.slice(0,2).join(" ").trim();if(T.length>2){let M=b.createEl("details",{cls:"af-mcp-tool-detail-desc"});M.createEl("summary",{text:this.truncateDescription(_,200)}),M.createDiv({cls:"af-mcp-tool-detail-full",text:v.description})}else b.createDiv({cls:"af-mcp-tool-detail-desc",text:_})}if(v.inputSchema){let T=v.inputSchema.properties,_=new Set(v.inputSchema.required??[]);if(T&&Object.keys(T).length>0){let D=b.createDiv({cls:"af-mcp-tool-params"});for(let[M,C]of Object.entries(T)){let I=D.createDiv({cls:"af-mcp-tool-param"});I.createSpan({cls:"af-mcp-tool-param-name",text:M}),C.type&&I.createSpan({cls:"af-mcp-tool-param-type",text:C.type}),_.has(M)&&I.createSpan({cls:"af-mcp-tool-param-required",text:"required"}),C.description&&I.createSpan({cls:"af-mcp-tool-param-desc",text:$t(C.description,80)})}}}}else h.createDiv({cls:"af-form-hint",text:'Click "Probe tools" to discover the tools this server exposes.'});let m=o.createDiv({cls:"af-slideover-section"});if(m.createDiv({cls:"af-slideover-section-title",text:"ACTIONS"}),e.enabled&&e.url&&e.auth==="oauth"&&!this.mcpHasToken(e)){let v=m.createEl("button",{cls:"af-btn-sm primary"}),b=v.createSpan();(0,w.setIcon)(b,"key"),v.appendText(" Authenticate"),v.onclick=()=>{s.remove(),this.authenticateMcpServer(e)}}let f=m.createEl("button",{cls:"af-btn-sm danger"}),g=f.createSpan();(0,w.setIcon)(g,"trash-2"),f.appendText(" Remove Server"),f.onclick=async()=>{try{await this.plugin.repository.deleteMcpServer(e.name),this.plugin.mcpAuth.removeToken(e.name),this.mcpProbeCache.delete(e.name),new w.Notice(`Server "${e.name}" removed.`),s.remove(),await this.plugin.refreshFromVault(),this.render()}catch(v){let b=v instanceof Error?v.message:String(v);new w.Notice(`Failed to remove server: ${b}`)}}}renderAddMcpServerPage(e){let s=e.createDiv({cls:"af-create-agent-page"}),n=s.createDiv({cls:"af-detail-header"}),a=n.createDiv({cls:"af-detail-header-left"}),r=a.createDiv({cls:"af-agent-card-avatar idle"});(0,w.setIcon)(r,"plus");let o=a.createDiv();o.createDiv({cls:"af-detail-header-name",text:"Add MCP Server"}),o.createDiv({cls:"af-detail-header-desc",text:"Register a new MCP server for agents to use"});let c=n.createDiv({cls:"af-detail-header-actions"}),l={name:"",transport:"stdio",description:"",command:"",args:"",envVars:"",url:"",headers:"",auth:"none",bearerToken:""},h=s.createDiv({cls:"af-create-form"}),d=h.createDiv({cls:"af-create-section"}),u=d.createDiv({cls:"af-create-section-header"}),p=u.createSpan({cls:"af-create-section-icon"});(0,w.setIcon)(p,"plug"),u.createSpan({text:"Server Details"}),this.createFormField(d,"Name","my-server","Unique name for this MCP server",j=>{l.name=j});let m=d.createDiv({cls:"af-form-row"}),f=m.createDiv({cls:"af-form-label"});f.setText("Transport"),this.addTooltip(f,"stdio: local process, http/sse: remote server");let g=m.createEl("select",{cls:"af-form-select"});g.createEl("option",{text:"stdio",attr:{value:"stdio"}}),g.createEl("option",{text:"http",attr:{value:"http"}}),g.createEl("option",{text:"sse",attr:{value:"sse"}}),this.createFormField(d,"Description","What this server does (optional)","Shown on the server card",j=>{l.description=j});let v=h.createDiv({cls:"af-create-section"}),b=v.createDiv({cls:"af-create-section-header"}),k=b.createSpan({cls:"af-create-section-icon"});(0,w.setIcon)(k,"terminal"),b.createSpan({text:"Process Configuration"}),this.createFormField(v,"Command","npx @anthropic-ai/mcp-server-memory","The command to run",j=>{l.command=j}),this.createFormField(v,"Arguments","--port 3000","Space-separated arguments (optional)",j=>{l.args=j});let y=v.createDiv({cls:"af-form-label"});y.setText("Environment variables"),this.addTooltip(y,"One KEY=VALUE per line");let S=v.createEl("textarea",{cls:"af-create-prompt-textarea",attr:{placeholder:`API_KEY=sk-... -DEBUG=true`,rows:"3"}});S.addEventListener("input",()=>{l.envVars=S.value});let T=h.createDiv({cls:"af-create-section"}),_=T.createDiv({cls:"af-create-section-header"}),D=_.createSpan({cls:"af-create-section-icon"});(0,w.setIcon)(D,"globe"),_.createSpan({text:"Remote Server Configuration"}),this.createFormField(T,"URL","https://mcp.example.com/sse","Server endpoint URL",j=>{l.url=j});let M=T.createDiv({cls:"af-form-label"});M.setText("Custom headers"),this.addTooltip(M,"One Header: Value per line (optional, non-secret)");let C=T.createEl("textarea",{cls:"af-create-prompt-textarea",attr:{placeholder:"X-Custom-Header: value",rows:"2"}});C.addEventListener("input",()=>{l.headers=C.value});let I=T.createDiv({cls:"af-form-row"}),P=I.createDiv({cls:"af-form-label"});P.setText("Authentication"),this.addTooltip(P,"none, a static bearer token, or OAuth (authenticate after saving)");let L=I.createEl("select",{cls:"af-form-select"});L.createEl("option",{text:"None",attr:{value:"none"}}),L.createEl("option",{text:"Bearer token",attr:{value:"bearer"}}),L.createEl("option",{text:"OAuth",attr:{value:"oauth"}});let F=T.createDiv({cls:"af-form-row"}),z=F.createDiv({cls:"af-form-label"});z.setText("Bearer token"),this.addTooltip(z,"Stored securely in the OS keychain, never written to the vault");let U=F.createEl("input",{cls:"af-form-input",attr:{type:"password",placeholder:"sk-\u2026"}});U.addEventListener("input",()=>{l.bearerToken=U.value});let Z=()=>{F.setCssStyles({display:l.auth==="bearer"?"":"none"})};L.addEventListener("change",()=>{l.auth=L.value,Z()}),Z();let de=()=>{v.setCssStyles({display:l.transport==="stdio"?"":"none"}),T.setCssStyles({display:l.transport!=="stdio"?"":"none"})};g.addEventListener("change",()=>{l.transport=g.value,de()}),de();let me=s.createDiv({cls:"af-create-footer"}),ye=me.createEl("button",{cls:"af-btn-sm",text:"Cancel"});ye.onclick=()=>this.navigate("mcp");let X=me.createEl("button",{cls:"af-btn-sm primary af-create-submit"});E(X,"plus","af-btn-icon"),X.appendText(" Add Server"),X.onclick=async()=>{let j=l.name.trim();if(!j){new w.Notice("Server name is required.");return}if(l.transport==="stdio"){if(!l.command.trim()){new w.Notice("Command is required for stdio servers.");return}}else if(!l.url.trim()){new w.Notice("URL is required for HTTP/SSE servers.");return}let K={};if(l.envVars.trim())for(let Q of ge(l.envVars)){let ae=Q.trim();if(!ae)continue;let ve=ae.indexOf("=");if(ve<=0){new w.Notice(`Invalid env var: ${ae}`);return}K[ae.slice(0,ve)]=ae.slice(ve+1)}let ne={};if(l.headers.trim())for(let Q of ge(l.headers)){let ae=Q.trim();if(!ae)continue;let ve=ae.indexOf(":");if(ve<=0){new w.Notice(`Invalid header: ${ae}`);return}ne[ae.slice(0,ve).trim()]=ae.slice(ve+1).trim()}let G=l.args.trim()?l.args.trim().split(/\s+/):void 0;if(this.plugin.repository.getMcpServerByName(j)){new w.Notice(`An MCP server named "${j}" already exists.`);return}if(l.transport!=="stdio"&&l.auth==="bearer"&&!l.bearerToken.trim()){new w.Notice("Enter a bearer token, or choose a different auth method.");return}X.disabled=!0,X.setText("Adding...");let ee={name:j,type:l.transport,enabled:!0,source:"manual",status:"disconnected",scope:"user",tools:[],toolDetails:[]};l.transport==="stdio"?(ee.command=l.command.trim(),G&&(ee.args=G),Object.keys(K).length>0&&(ee.env=K)):(ee.url=l.url.trim(),Object.keys(ne).length>0&&(ee.headers=ne),ee.auth=l.auth);try{await this.plugin.repository.saveMcpServer(ee,l.description.trim()),l.transport!=="stdio"&&l.auth==="bearer"&&l.bearerToken.trim()&&this.plugin.mcpAuth.storeStaticToken(j,l.bearerToken.trim()),new w.Notice(`Server "${j}" added.`),await this.plugin.refreshFromVault(),this.navigate("mcp")}catch(Q){let ae=Q instanceof Error?Q.message:String(Q);new w.Notice(`Failed to add server: ${ae}`),X.disabled=!1,X.setText(""),E(X,"plus","af-btn-icon"),X.appendText(" Add Server")}}}createFormField(e,s,n,a,r,o){let c=e.createDiv({cls:"af-form-row"}),l=c.createDiv({cls:"af-form-label"});l.setText(s),a&&this.addTooltip(l,a);let h=c.createEl("input",{cls:"af-form-input",attr:{type:"text",placeholder:n}});o!==void 0&&(h.value=o),h.addEventListener("input",()=>r(h.value))}addTooltip(e,s){let n=e.createSpan({cls:"af-form-tooltip"});(0,w.setIcon)(n,"info"),n.createSpan({cls:"af-tooltip-text",text:s})}};function Oo(i){switch(i){case"connected":return"idle";case"connecting":case"reconnecting":return"pending";case"needs-auth":case"error":return"error";case"stopped":case"disabled":default:return"disabled"}}function Wa(i){return i>=1e6?`${(i/1e6).toFixed(1)}M`:i>=1e4?`${Math.round(i/1e3)}K`:i>=1e3?`${(i/1e3).toFixed(1)}K`:String(i)}function jd(i){if(!i?.trim())return"not set";let t={"*/5 * * * *":"Every 5 minutes","*/10 * * * *":"Every 10 minutes","*/15 * * * *":"Every 15 minutes","*/30 * * * *":"Every 30 minutes","0 * * * *":"Every hour","0 */2 * * *":"Every 2 hours","0 */4 * * *":"Every 4 hours","0 */6 * * *":"Every 6 hours","0 */12 * * *":"Every 12 hours"};if(t[i])return t[i];let e=i.trim().split(/\s+/);if(e.length===5){let[s,n,a,,r]=e;if(a==="*"&&n&&s){let o=Number(n),c=Number(s);if(!isNaN(o)&&!isNaN(c)){let l=o>=12?"PM":"AM",d=`${o===0?12:o>12?o-12:o}:${String(c).padStart(2,"0")} ${l}`;return r==="*"?`Daily at ${d}`:r==="1-5"?`Weekdays at ${d}`:`${d} on days ${r}`}}}return i}function Wd(i){switch(i){case"connected":return"green";case"connecting":case"reconnecting":return"blue";case"needs-auth":case"error":return"red";case"stopped":case"disabled":default:return""}}var q=require("obsidian");function Wn(i,t){return`${i}::${t}`}function Uo(){return Math.random().toString(16).slice(2,10)}function $o(){return`New chat ${new Date().toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit"})}`}function Hd(i){let t=new Date(i).getTime();if(!Number.isFinite(t))return"";let e=Date.now()-t,s=60*1e3,n=60*s,a=24*n;return es.id===this.selectedConversationId)?.name??""}async onOpen(){this.plugin.subscribeView(this),this.buildShell(),await this.render(),this.observeContainerWidth()}async onClose(){for(let{session:e}of this.sessions.values())e.abort();this.sessions.clear(),this.statsUnsub?.(),this.statsUnsub=null,this.activityUnsub?.(),this.activityUnsub=null,this.resizeObserver?.disconnect(),this.resizeObserver=null,this.plugin.unsubscribeView(this)}observeContainerWidth(){typeof ResizeObserver>"u"||(this.resizeObserver=new ResizeObserver(e=>{if(!this.userToggledCollapse)for(let s of e){let a=s.contentRect.widththis.toggleConvoPanel(),this.agentSelect=this.headerEl.createEl("select",{cls:"af-chat-view-agent-select"});let n=this.headerEl.createEl("button",{cls:"af-btn-sm af-chat-view-new-btn"});E(n,"plus","af-btn-icon"),n.appendText(" New Chat"),n.onclick=()=>void this.handleNewChat();let a=s.createDiv({cls:"af-chat-view-body"});this.convoPanelEl=a.createDiv({cls:"af-chat-convo-panel"}),this.convoPanelCollapsed&&this.convoPanelEl.addClass("collapsed");let r=a.createDiv({cls:"af-chat-main"});this.agentSelect.onchange=()=>{let h=this.agentSelect.value;h&&(this.selectedConversationId="",this.textarea.disabled=!1,this.textarea.placeholder="Message the agent\u2026 (Ctrl+Enter to send)",this.switchToAgent(h))},this.messagesEl=r.createDiv({cls:"af-chat-messages"}),this.messagesInner=this.messagesEl.createDiv({cls:"af-chat-messages-inner"});let o=r.createDiv({cls:"af-chat-input-area"});this.pillsRow=o.createDiv({cls:"af-chat-pills-row"}),this.pillsRow.setCssStyles({display:"none"});let c=o.createDiv({cls:"af-chat-input-row"});this.attachStopBtn=c.createEl("button",{cls:"af-chat-attach-btn"}),E(this.attachStopBtn,"plus","af-btn-icon"),this.attachStopBtn.title="Attach active document",this.attachStopBtn.onclick=()=>{this.isInStopMode?this.handleStop():this.attachActiveDocument()},this.textarea=c.createEl("textarea",{cls:"af-chat-input",attr:{placeholder:"Message the agent\u2026 (Ctrl+Enter to send)",rows:"1"}}),this.sendBtn=c.createEl("button",{cls:"af-chat-send-btn"}),E(this.sendBtn,"arrow-up","af-btn-icon"),this.sendBtn.setCssStyles({display:"none"});let l=()=>{this.textarea.setCssStyles({height:"auto"});let h=Math.min(this.textarea.scrollHeight,160);this.textarea.setCssStyles({height:`${h}px`}),this.textarea.setCssStyles({overflowY:this.textarea.scrollHeight>160?"auto":"hidden"}),this.sendBtn.setCssStyles({display:this.textarea.value.trim()?"flex":"none"})};this.textarea.addEventListener("input",l),this.textarea.addEventListener("focus",()=>{let h=this.getCurrentSession();h&&this.setStatsSource(h.session)}),this.sendBtn.onclick=()=>void this.handleSend(),this.textarea.onkeydown=h=>{h.key==="Enter"&&(h.ctrlKey||h.metaKey)&&(h.preventDefault(),this.handleSend())},this.textarea.addEventListener("paste",h=>{let d=h.clipboardData?.items;if(d)for(let u=0;u{h.preventDefault(),h.stopPropagation(),o.addClass("af-chat-input-dragover")}),o.addEventListener("dragleave",()=>{o.removeClass("af-chat-input-dragover")}),o.addEventListener("drop",h=>{h.preventDefault(),h.stopPropagation(),o.removeClass("af-chat-input-dragover");let d=h.dataTransfer?.files;if(d)for(let u=0;uthis.renderStats(s)))}renderStats(e){if(this.statsEl.empty(),!e||!e.concreteModel){this.statsEl.createSpan({cls:"af-chat-stats-muted",text:"\xA0"});return}let s=this.statsEl.createSpan({cls:"af-chat-stats-line"});if(s.createSpan({cls:"af-chat-stats-model",text:e.concreteModel}),e.contextWindow&&e.contextTokensUsed){let n=Math.min(100,Math.round(e.contextTokensUsed/e.contextWindow*100)),a=10,r=Math.min(a,Math.max(0,Math.round(n/100*a))),o="\u2593".repeat(r)+"\u2591".repeat(a-r),c=s.createSpan({cls:`af-chat-stats-ctx${n>=80?" warn":""}`});c.createSpan({cls:"af-chat-stats-bar",text:o}),c.createSpan({cls:"af-chat-stats-pct",text:`${n}%`}),c.title=`Context: ${ns(e.contextTokensUsed)} / ${ns(e.contextWindow)} tokens (${n}%) +...`,rows:"8"}});C.value=a.toolsBody,C.addEventListener("input",()=>{d.toolsBody=C.value});let E=u.createDiv({cls:"af-create-section"}),P=E.createDiv({cls:"af-create-section-header"}),N=P.createSpan({cls:"af-create-section-icon"});(0,b.setIcon)(N,"book-open");let M=P.createSpan({text:"References"});this.addTooltip(M,"Background docs, conventions, cheat sheets");let U=E.createEl("textarea",{cls:"af-create-prompt-textarea",attr:{placeholder:"API docs, filter syntax, conventions...",rows:"6"}});U.value=a.referencesBody,U.addEventListener("input",()=>{d.referencesBody=U.value});let $=u.createDiv({cls:"af-create-section"}),Z=$.createDiv({cls:"af-create-section-header"}),de=Z.createSpan({cls:"af-create-section-icon"});(0,b.setIcon)(de,"message-circle");let me=Z.createSpan({text:"Examples"});this.addTooltip(me,"Example prompts and ideal outputs showing how to use this skill");let ge=$.createEl("textarea",{cls:"af-create-prompt-textarea",attr:{placeholder:`## Example: List all tasks +...`,rows:"6"}});ge.value=a.examplesBody,ge.addEventListener("input",()=>{d.examplesBody=ge.value});let X=s.createDiv({cls:"af-create-footer"}),W=X.createEl("button",{cls:"af-btn-sm danger"});R(W,"trash-2","af-btn-icon"),W.appendText(" Delete"),W.onclick=async()=>{await this.plugin.repository.deleteSkill(a.name),new b.Notice(`Skill "${a.name}" deleted.`),await new Promise(G=>window.setTimeout(G,200)),await this.plugin.refreshFromVault(),this.navigate("skills")},X.createDiv({cls:"af-toolbar-spacer"});let K=X.createEl("button",{cls:"af-btn-sm",text:"Cancel"});K.onclick=()=>this.navigate("skills");let ne=X.createEl("button",{cls:"af-btn-sm primary af-create-submit"});R(ne,"check","af-btn-icon"),ne.appendText(" Save Changes"),ne.onclick=async()=>{let G=ee=>ee.split(",").map(Q=>Q.trim()).filter(Boolean);try{await this.plugin.repository.updateSkill(a.name,{description:d.description.trim(),tags:G(d.tags),body:d.body.trim(),toolsBody:d.toolsBody.trim(),referencesBody:d.referencesBody.trim(),examplesBody:d.examplesBody.trim()}),new b.Notice(`Skill "${a.name}" updated.`),await this.plugin.refreshFromVault(),this.navigate("skills")}catch(ee){let Q=ee instanceof Error?ee.message:String(ee);new b.Notice(`Failed to update skill: ${Q}`)}}}renderAgentMcpPicker(e,s){e.createDiv({cls:"af-form-hint",text:"Servers from the MCP Servers tab. Checked servers are available to this agent on any adapter (Claude or Codex). Leave all unchecked to grant every enabled server."});let n=this.plugin.repository.getMcpServers();if(n.length===0){let r=e.createDiv({cls:"af-form-hint"});r.appendText("No MCP servers registered yet. ");let o=r.createEl("a",{cls:"af-link",text:"Add one in the MCP Servers tab."});o.onclick=c=>{c.preventDefault(),this.navigate("mcp")};return}let a=e.createDiv({cls:"af-create-skills-grid"});for(let r of n){let o=a.createDiv({cls:"af-mcp-agent-item"}),c=o.createEl("input",{cls:"af-form-toggle",attr:{type:"checkbox"}});c.checked=s.has(r.name),c.addEventListener("change",()=>{c.checked?s.add(r.name):s.delete(r.name)});let h=o.createDiv({cls:"af-mcp-agent-label"}).createDiv({cls:"af-mcp-agent-name-row"}),d=h.createSpan({cls:`af-mcp-status-dot ${r.enabled?"idle":"disabled"}`});d.title=r.enabled?"enabled":"disabled",h.createSpan({cls:"af-create-skill-name",text:r.name}),h.createSpan({cls:"af-mcp-agent-tool-count",text:r.type}),r.enabled||h.createSpan({cls:"af-mcp-agent-tool-count af-muted",text:"disabled"})}}renderMcpPage(e){let s=e.createDiv({cls:"af-agents-page"}),n=s.createDiv({cls:"af-agents-toolbar"});n.createDiv({cls:"af-page-title",text:"MCP Servers"}),n.createDiv({cls:"af-toolbar-spacer"});let a=n.createEl("button",{cls:"af-btn-sm primary"});R(a,"plus","af-btn-icon"),a.appendText(" Add Server"),a.onclick=()=>this.navigate("add-mcp-server");let r=this.plugin.repository.getMcpServers();if(r.length===0){this.renderEmptyState(s,"plug","No MCP servers registered","Click 'Add Server' above to register one.");return}let o=s.createDiv({cls:"af-agents-grid"});for(let c of r)this.renderMcpCard(o,c)}mcpProbeCache=new Map;mcpHasToken(e){return this.plugin.mcpAuth.hasToken(e.name)}mcpNeedsAuth(e){return e.type!=="stdio"&&(e.auth==="oauth"||e.auth==="bearer")&&!this.mcpHasToken(e)}renderMcpCard(e,s){let n=e.createDiv({cls:`af-mcp-card${s.enabled?"":" af-mcp-card-disabled"}`}),a=this.mcpNeedsAuth(s),r=n.createDiv({cls:"af-agent-card-header"}),o=s.enabled?a?"pending":"idle":"disabled",c=r.createDiv({cls:`af-agent-card-avatar ${o}`});(0,b.setIcon)(c,"plug");let l=r.createDiv({cls:"af-agent-card-titleblock"});l.createDiv({cls:"af-agent-card-name",text:s.name});let h=l.createDiv({cls:"af-agent-card-desc af-mcp-meta"});h.createSpan({cls:"af-mcp-type-badge",text:s.type}),s.source==="imported"&&h.createSpan({cls:"af-badge",text:"imported"});let d=r.createDiv({cls:`af-agent-card-toggle${s.enabled?" on":""}`});d.onclick=g=>{g.stopPropagation(),this.plugin.repository.setMcpServerEnabled(s.name,!s.enabled).then(async()=>{await this.plugin.refreshFromVault(),this.render()})};let u=n.createDiv({cls:`af-mcp-status-badge ${s.enabled?a?"needs-auth":"connected":"disabled"}`}),p=u.createSpan();if(s.enabled?a?((0,b.setIcon)(p,"alert-circle"),u.createSpan({text:" Needs auth"})):((0,b.setIcon)(p,"check-circle"),u.createSpan({text:s.type==="stdio"?" Enabled":" Authenticated"})):((0,b.setIcon)(p,"pause"),u.createSpan({text:" Disabled"})),s.description){let g=this.truncateDescription(s.description,120);n.createDiv({cls:"af-mcp-description",text:g})}let m=s.url??s.command??"";m&&n.createDiv({cls:"af-mcp-command",text:jt(m,60)});let f=this.mcpProbeCache.get(s.name);if(f&&f.length>0){let g=n.createDiv({cls:"af-mcp-tool-footer"}),y=g.createDiv({cls:"af-mcp-tool-count"}),v=y.createSpan();(0,b.setIcon)(v,"wrench"),y.createSpan({text:` ${f.length} tools`});let k=g.createDiv({cls:"af-mcp-tool-chips"});for(let w of f.slice(0,4))k.createSpan({cls:"af-mcp-tool-chip",text:w.name});f.length>4&&k.createSpan({cls:"af-mcp-tool-chip af-mcp-tool-chip-more",text:`+${f.length-4}`})}if(this.authenticatingServers.has(s.name)){let y=n.createDiv({cls:"af-mcp-auth-row"}).createEl("button",{cls:"af-btn-sm primary",attr:{disabled:"true"}}),v=y.createSpan({cls:"af-spin"});(0,b.setIcon)(v,"loader-2"),y.appendText(" Authenticating\u2026")}else if(s.enabled&&a&&s.auth==="oauth"){let y=n.createDiv({cls:"af-mcp-auth-row"}).createEl("button",{cls:"af-btn-sm primary"}),v=y.createSpan();(0,b.setIcon)(v,"key"),y.appendText(" Authenticate"),y.onclick=k=>{k.stopPropagation(),this.authenticateMcpServer(s)}}n.onclick=()=>this.openMcpDetailSlideover(s)}async authenticateMcpServer(e){if(!e.url){new b.Notice("No URL found for this server \u2014 can't authenticate.");return}this.authenticatingServers.add(e.name),this.render(),new b.Notice(`Authenticating ${e.name}\u2026 Complete authorization in your browser.`,1e4);try{let s=e.type==="sse"?"sse":"http";await this.plugin.mcpManager.authenticateServer(e.name,e.url,s),new b.Notice(`${e.name} authenticated successfully!`)}catch(s){let n=s instanceof Error?s.message:String(s);new b.Notice(`Authentication failed: ${n}`,8e3)}finally{this.authenticatingServers.delete(e.name),this.render()}}async probeMcpServer(e){try{let s=await this.plugin.mcpManager.probeServer(e);this.mcpProbeCache.set(e.name,s),s.length===0&&new b.Notice(`No tools discovered for ${e.name}.`)}catch(s){let n=s instanceof Error?s.message:String(s);new b.Notice(`Probe failed: ${n}`)}}truncateDescription(e,s){let n=ye(e)[0]??e,a=n.split(/(?<=[.!?])\s/)[0]??n,r=a.lengths.remove(),s.onclick=y=>{y.target===s&&s.remove()};let o=n.createDiv({cls:"af-slideover-body"});if(e.description){let y=o.createDiv({cls:"af-slideover-section"});y.createDiv({cls:"af-slideover-section-title",text:"DESCRIPTION"}),y.createDiv({cls:"af-mcp-detail-description",text:e.description})}let c=o.createDiv({cls:"af-slideover-section"});c.createDiv({cls:"af-slideover-section-title",text:"SERVER INFO"}),this.renderDetailRow(c,"Name",e.name),this.renderDetailRow(c,"Transport",e.type),this.renderDetailRow(c,"Enabled",e.enabled?"yes":"no"),e.type!=="stdio"&&(this.renderDetailRow(c,"Auth",e.auth??"none"),this.renderDetailRow(c,"Authenticated",this.mcpHasToken(e)?"yes":"no")),e.source&&this.renderDetailRow(c,"Source",e.source),e.url&&this.renderDetailRow(c,"URL",e.url),e.command&&this.renderDetailRow(c,"Command",e.command),e.args&&e.args.length>0&&this.renderDetailRow(c,"Args",e.args.join(" "));let l=this.mcpProbeCache.get(e.name)??[],h=o.createDiv({cls:"af-slideover-section"});h.createDiv({cls:"af-slideover-section-title"}).setText(`TOOLS (${l.length})`);let u=h.createEl("button",{cls:"af-btn-sm"}),p=u.createSpan();if((0,b.setIcon)(p,"wrench"),u.appendText(" Probe tools"),u.onclick=async()=>{u.disabled=!0,u.setText(" Probing\u2026"),await this.probeMcpServer(e),s.remove(),this.openMcpDetailSlideover(e)},l.length>0)for(let y of l){let v=h.createDiv({cls:"af-mcp-tool-detail"}),k=v.createDiv({cls:"af-mcp-tool-detail-header"}),w=k.createSpan({cls:"af-mcp-tool-detail-name"}),S=w.createSpan();if((0,b.setIcon)(S,"wrench"),w.createSpan({text:` ${y.name}`}),y.inputSchema){let T=y.inputSchema.required??[];T.length>0&&k.createSpan({cls:"af-mcp-tool-param-count",text:`${T.length} param${T.length!==1?"s":""}`})}if(y.description){let T=ye(y.description).filter(O=>O.trim()),_=T.slice(0,2).join(" ").trim();if(T.length>2){let O=v.createEl("details",{cls:"af-mcp-tool-detail-desc"});O.createEl("summary",{text:this.truncateDescription(_,200)}),O.createDiv({cls:"af-mcp-tool-detail-full",text:y.description})}else v.createDiv({cls:"af-mcp-tool-detail-desc",text:_})}if(y.inputSchema){let T=y.inputSchema.properties,_=new Set(y.inputSchema.required??[]);if(T&&Object.keys(T).length>0){let D=v.createDiv({cls:"af-mcp-tool-params"});for(let[O,C]of Object.entries(T)){let E=D.createDiv({cls:"af-mcp-tool-param"});E.createSpan({cls:"af-mcp-tool-param-name",text:O}),C.type&&E.createSpan({cls:"af-mcp-tool-param-type",text:C.type}),_.has(O)&&E.createSpan({cls:"af-mcp-tool-param-required",text:"required"}),C.description&&E.createSpan({cls:"af-mcp-tool-param-desc",text:jt(C.description,80)})}}}}else h.createDiv({cls:"af-form-hint",text:'Click "Probe tools" to discover the tools this server exposes.'});let m=o.createDiv({cls:"af-slideover-section"});if(m.createDiv({cls:"af-slideover-section-title",text:"ACTIONS"}),e.enabled&&e.url&&e.auth==="oauth"&&!this.mcpHasToken(e)){let y=m.createEl("button",{cls:"af-btn-sm primary"}),v=y.createSpan();(0,b.setIcon)(v,"key"),y.appendText(" Authenticate"),y.onclick=()=>{s.remove(),this.authenticateMcpServer(e)}}let f=m.createEl("button",{cls:"af-btn-sm danger"}),g=f.createSpan();(0,b.setIcon)(g,"trash-2"),f.appendText(" Remove Server"),f.onclick=async()=>{try{await this.plugin.repository.deleteMcpServer(e.name),this.plugin.mcpAuth.removeToken(e.name),this.mcpProbeCache.delete(e.name),new b.Notice(`Server "${e.name}" removed.`),s.remove(),await this.plugin.refreshFromVault(),this.render()}catch(y){let v=y instanceof Error?y.message:String(y);new b.Notice(`Failed to remove server: ${v}`)}}}renderAddMcpServerPage(e){let s=e.createDiv({cls:"af-create-agent-page"}),n=s.createDiv({cls:"af-detail-header"}),a=n.createDiv({cls:"af-detail-header-left"}),r=a.createDiv({cls:"af-agent-card-avatar idle"});(0,b.setIcon)(r,"plus");let o=a.createDiv();o.createDiv({cls:"af-detail-header-name",text:"Add MCP Server"}),o.createDiv({cls:"af-detail-header-desc",text:"Register a new MCP server for agents to use"});let c=n.createDiv({cls:"af-detail-header-actions"}),l={name:"",transport:"stdio",description:"",command:"",args:"",envVars:"",url:"",headers:"",auth:"none",bearerToken:""},h=s.createDiv({cls:"af-create-form"}),d=h.createDiv({cls:"af-create-section"}),u=d.createDiv({cls:"af-create-section-header"}),p=u.createSpan({cls:"af-create-section-icon"});(0,b.setIcon)(p,"plug"),u.createSpan({text:"Server Details"}),this.createFormField(d,"Name","my-server","Unique name for this MCP server",W=>{l.name=W});let m=d.createDiv({cls:"af-form-row"}),f=m.createDiv({cls:"af-form-label"});f.setText("Transport"),this.addTooltip(f,"stdio: local process, http/sse: remote server");let g=m.createEl("select",{cls:"af-form-select"});g.createEl("option",{text:"stdio",attr:{value:"stdio"}}),g.createEl("option",{text:"http",attr:{value:"http"}}),g.createEl("option",{text:"sse",attr:{value:"sse"}}),this.createFormField(d,"Description","What this server does (optional)","Shown on the server card",W=>{l.description=W});let y=h.createDiv({cls:"af-create-section"}),v=y.createDiv({cls:"af-create-section-header"}),k=v.createSpan({cls:"af-create-section-icon"});(0,b.setIcon)(k,"terminal"),v.createSpan({text:"Process Configuration"}),this.createFormField(y,"Command","npx @anthropic-ai/mcp-server-memory","The command to run",W=>{l.command=W}),this.createFormField(y,"Arguments","--port 3000","Space-separated arguments (optional)",W=>{l.args=W});let w=y.createDiv({cls:"af-form-label"});w.setText("Environment variables"),this.addTooltip(w,"One KEY=VALUE per line");let S=y.createEl("textarea",{cls:"af-create-prompt-textarea",attr:{placeholder:`API_KEY=sk-... +DEBUG=true`,rows:"3"}});S.addEventListener("input",()=>{l.envVars=S.value});let T=h.createDiv({cls:"af-create-section"}),_=T.createDiv({cls:"af-create-section-header"}),D=_.createSpan({cls:"af-create-section-icon"});(0,b.setIcon)(D,"globe"),_.createSpan({text:"Remote Server Configuration"}),this.createFormField(T,"URL","https://mcp.example.com/sse","Server endpoint URL",W=>{l.url=W});let O=T.createDiv({cls:"af-form-label"});O.setText("Custom headers"),this.addTooltip(O,"One Header: Value per line (optional, non-secret)");let C=T.createEl("textarea",{cls:"af-create-prompt-textarea",attr:{placeholder:"X-Custom-Header: value",rows:"2"}});C.addEventListener("input",()=>{l.headers=C.value});let E=T.createDiv({cls:"af-form-row"}),P=E.createDiv({cls:"af-form-label"});P.setText("Authentication"),this.addTooltip(P,"none, a static bearer token, or OAuth (authenticate after saving)");let N=E.createEl("select",{cls:"af-form-select"});N.createEl("option",{text:"None",attr:{value:"none"}}),N.createEl("option",{text:"Bearer token",attr:{value:"bearer"}}),N.createEl("option",{text:"OAuth",attr:{value:"oauth"}});let M=T.createDiv({cls:"af-form-row"}),U=M.createDiv({cls:"af-form-label"});U.setText("Bearer token"),this.addTooltip(U,"Stored securely in the OS keychain, never written to the vault");let $=M.createEl("input",{cls:"af-form-input",attr:{type:"password",placeholder:"sk-\u2026"}});$.addEventListener("input",()=>{l.bearerToken=$.value});let Z=()=>{M.setCssStyles({display:l.auth==="bearer"?"":"none"})};N.addEventListener("change",()=>{l.auth=N.value,Z()}),Z();let de=()=>{y.setCssStyles({display:l.transport==="stdio"?"":"none"}),T.setCssStyles({display:l.transport!=="stdio"?"":"none"})};g.addEventListener("change",()=>{l.transport=g.value,de()}),de();let me=s.createDiv({cls:"af-create-footer"}),ge=me.createEl("button",{cls:"af-btn-sm",text:"Cancel"});ge.onclick=()=>this.navigate("mcp");let X=me.createEl("button",{cls:"af-btn-sm primary af-create-submit"});R(X,"plus","af-btn-icon"),X.appendText(" Add Server"),X.onclick=async()=>{let W=l.name.trim();if(!W){new b.Notice("Server name is required.");return}if(l.transport==="stdio"){if(!l.command.trim()){new b.Notice("Command is required for stdio servers.");return}}else if(!l.url.trim()){new b.Notice("URL is required for HTTP/SSE servers.");return}let K={};if(l.envVars.trim())for(let Q of ye(l.envVars)){let ae=Q.trim();if(!ae)continue;let ve=ae.indexOf("=");if(ve<=0){new b.Notice(`Invalid env var: ${ae}`);return}K[ae.slice(0,ve)]=ae.slice(ve+1)}let ne={};if(l.headers.trim())for(let Q of ye(l.headers)){let ae=Q.trim();if(!ae)continue;let ve=ae.indexOf(":");if(ve<=0){new b.Notice(`Invalid header: ${ae}`);return}ne[ae.slice(0,ve).trim()]=ae.slice(ve+1).trim()}let G=l.args.trim()?l.args.trim().split(/\s+/):void 0;if(this.plugin.repository.getMcpServerByName(W)){new b.Notice(`An MCP server named "${W}" already exists.`);return}if(l.transport!=="stdio"&&l.auth==="bearer"&&!l.bearerToken.trim()){new b.Notice("Enter a bearer token, or choose a different auth method.");return}X.disabled=!0,X.setText("Adding...");let ee={name:W,type:l.transport,enabled:!0,source:"manual",status:"disconnected",scope:"user",tools:[],toolDetails:[]};l.transport==="stdio"?(ee.command=l.command.trim(),G&&(ee.args=G),Object.keys(K).length>0&&(ee.env=K)):(ee.url=l.url.trim(),Object.keys(ne).length>0&&(ee.headers=ne),ee.auth=l.auth);try{await this.plugin.repository.saveMcpServer(ee,l.description.trim()),l.transport!=="stdio"&&l.auth==="bearer"&&l.bearerToken.trim()&&this.plugin.mcpAuth.storeStaticToken(W,l.bearerToken.trim()),new b.Notice(`Server "${W}" added.`),await this.plugin.refreshFromVault(),this.navigate("mcp")}catch(Q){let ae=Q instanceof Error?Q.message:String(Q);new b.Notice(`Failed to add server: ${ae}`),X.disabled=!1,X.setText(""),R(X,"plus","af-btn-icon"),X.appendText(" Add Server")}}}createFormField(e,s,n,a,r,o){let c=e.createDiv({cls:"af-form-row"}),l=c.createDiv({cls:"af-form-label"});l.setText(s),a&&this.addTooltip(l,a);let h=c.createEl("input",{cls:"af-form-input",attr:{type:"text",placeholder:n}});o!==void 0&&(h.value=o),h.addEventListener("input",()=>r(h.value))}addTooltip(e,s){let n=e.createSpan({cls:"af-form-tooltip"});(0,b.setIcon)(n,"info"),n.createSpan({cls:"af-tooltip-text",text:s})}};function Xo(i){switch(i){case"connected":return"idle";case"connecting":case"reconnecting":return"pending";case"needs-auth":case"error":return"error";case"stopped":case"disabled":default:return"disabled"}}function Ka(i){return i>=1e6?`${(i/1e6).toFixed(1)}M`:i>=1e4?`${Math.round(i/1e3)}K`:i>=1e3?`${(i/1e3).toFixed(1)}K`:String(i)}function kh(i){if(!i?.trim())return"not set";let t={"*/5 * * * *":"Every 5 minutes","*/10 * * * *":"Every 10 minutes","*/15 * * * *":"Every 15 minutes","*/30 * * * *":"Every 30 minutes","0 * * * *":"Every hour","0 */2 * * *":"Every 2 hours","0 */4 * * *":"Every 4 hours","0 */6 * * *":"Every 6 hours","0 */12 * * *":"Every 12 hours"};if(t[i])return t[i];let e=i.trim().split(/\s+/);if(e.length===5){let[s,n,a,,r]=e;if(a==="*"&&n&&s){let o=Number(n),c=Number(s);if(!isNaN(o)&&!isNaN(c)){let l=o>=12?"PM":"AM",d=`${o===0?12:o>12?o-12:o}:${String(c).padStart(2,"0")} ${l}`;return r==="*"?`Daily at ${d}`:r==="1-5"?`Weekdays at ${d}`:`${d} on days ${r}`}}}return i}function xh(i){switch(i){case"connected":return"green";case"connecting":case"reconnecting":return"blue";case"needs-auth":case"error":return"red";case"stopped":case"disabled":default:return""}}var z=require("obsidian");function qn(i,t){return`${i}::${t}`}function el(){return Math.random().toString(16).slice(2,10)}function tl(){return`New chat ${new Date().toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit"})}`}function Sh(i){let t=new Date(i).getTime();if(!Number.isFinite(t))return"";let e=Date.now()-t,s=60*1e3,n=60*s,a=24*n;return es.id===this.selectedConversationId)?.name??""}async onOpen(){this.plugin.subscribeView(this),this.buildShell(),await this.render(),this.observeContainerWidth()}async onClose(){for(let{session:e}of this.sessions.values())e.abort();this.sessions.clear(),this.statsUnsub?.(),this.statsUnsub=null,this.activityUnsub?.(),this.activityUnsub=null,this.resizeObserver?.disconnect(),this.resizeObserver=null,this.plugin.unsubscribeView(this)}observeContainerWidth(){typeof ResizeObserver>"u"||(this.resizeObserver=new ResizeObserver(e=>{if(!this.userToggledCollapse)for(let s of e){let a=s.contentRect.widththis.toggleConvoPanel(),this.agentSelect=this.headerEl.createEl("select",{cls:"af-chat-view-agent-select"});let n=this.headerEl.createEl("button",{cls:"af-btn-sm af-chat-view-new-btn"});R(n,"plus","af-btn-icon"),n.appendText(" New Chat"),n.onclick=()=>void this.handleNewChat();let a=s.createDiv({cls:"af-chat-view-body"});this.convoPanelEl=a.createDiv({cls:"af-chat-convo-panel"}),this.convoPanelCollapsed&&this.convoPanelEl.addClass("collapsed");let r=a.createDiv({cls:"af-chat-main"});this.agentSelect.onchange=()=>{let h=this.agentSelect.value;h&&(this.selectedConversationId="",this.textarea.disabled=!1,this.textarea.placeholder="Message the agent\u2026 (Shift+Enter for newline)",this.switchToAgent(h))},this.messagesEl=r.createDiv({cls:"af-chat-messages"}),this.messagesInner=this.messagesEl.createDiv({cls:"af-chat-messages-inner"});let o=r.createDiv({cls:"af-chat-input-area"});this.pillsRow=o.createDiv({cls:"af-chat-pills-row"}),this.pillsRow.setCssStyles({display:"none"});let c=o.createDiv({cls:"af-chat-input-row"});this.attachStopBtn=c.createEl("button",{cls:"af-chat-attach-btn"}),R(this.attachStopBtn,"plus","af-btn-icon"),this.attachStopBtn.title="Attach active document",this.attachStopBtn.onclick=()=>{this.isInStopMode?this.handleStop():this.attachActiveDocument()},this.textarea=c.createEl("textarea",{cls:"af-chat-input",attr:{placeholder:"Message the agent\u2026 (Shift+Enter for newline)",rows:"1"}}),this.sendBtn=c.createEl("button",{cls:"af-chat-send-btn"}),R(this.sendBtn,"arrow-up","af-btn-icon"),this.sendBtn.setCssStyles({display:"none"});let l=()=>{this.textarea.setCssStyles({height:"auto"});let h=Math.min(this.textarea.scrollHeight,160);this.textarea.setCssStyles({height:`${h}px`}),this.textarea.setCssStyles({overflowY:this.textarea.scrollHeight>160?"auto":"hidden"}),this.sendBtn.setCssStyles({display:this.textarea.value.trim()?"flex":"none"})};this.textarea.addEventListener("input",l),this.textarea.addEventListener("focus",()=>{let h=this.getCurrentSession();h&&this.setStatsSource(h.session)}),this.sendBtn.onclick=()=>void this.handleSend(),this.textarea.onkeydown=h=>{h.key==="Enter"&&!h.shiftKey&&!h.isComposing&&(h.preventDefault(),this.handleSend())},this.textarea.addEventListener("paste",h=>{let d=h.clipboardData?.items;if(d)for(let u=0;u{h.preventDefault(),h.stopPropagation(),o.addClass("af-chat-input-dragover")}),o.addEventListener("dragleave",()=>{o.removeClass("af-chat-input-dragover")}),o.addEventListener("drop",h=>{h.preventDefault(),h.stopPropagation(),o.removeClass("af-chat-input-dragover");let d=h.dataTransfer?.files;if(d)for(let u=0;uthis.renderStats(s)))}renderStats(e){if(this.statsEl.empty(),!e||!e.concreteModel){this.statsEl.createSpan({cls:"af-chat-stats-muted",text:"\xA0"});return}let s=this.statsEl.createSpan({cls:"af-chat-stats-line"});if(s.createSpan({cls:"af-chat-stats-model",text:e.concreteModel}),e.contextWindow&&e.contextTokensUsed){let n=Math.min(100,Math.round(e.contextTokensUsed/e.contextWindow*100)),a=10,r=Math.min(a,Math.max(0,Math.round(n/100*a))),o="\u2593".repeat(r)+"\u2591".repeat(a-r),c=s.createSpan({cls:`af-chat-stats-ctx${n>=80?" warn":""}`});c.createSpan({cls:"af-chat-stats-bar",text:o}),c.createSpan({cls:"af-chat-stats-pct",text:`${n}%`}),c.title=`Context: ${is(e.contextTokensUsed)} / ${is(e.contextWindow)} tokens (${n}%) Includes the agent's system prompt, attached skills, tool schemas, memory, and prior turns. A fresh chat starts non-zero because all of that is loaded on turn 1. -To get 1M context on Opus, set the agent's model to claude-opus-4-7[1m] (or us.anthropic.claude-opus-4-7[1m] on Bedrock).`}if(e.lastCompact){let{preTokens:n,postTokens:a}=e.lastCompact,r=s.createSpan({cls:"af-chat-stats-compact"});r.setText(`compacted ${ns(n)} \u2192 ${ns(a)}`),r.title=`Conversation was summarized to free up context. ${ns(n)} tokens reduced to ${ns(a)}.`}}populateAgentDropdown(){let e=this.plugin.runtime.getSnapshot().agents,s=this.agentSelect.value;if(this.agentSelect.empty(),e.length===0){let a=this.agentSelect.createEl("option",{text:"No agents available",attr:{value:"",disabled:"true"}});a.selected=!0,this.textarea.disabled=!0,this.showEmptyState();return}if(!this.selectedAgentName){let a=this.agentSelect.createEl("option",{text:"Select agent\u2026",attr:{value:"",disabled:"true"}});a.selected=!0}for(let a of e){let r=a.avatar?.trim(),o=r&&!/^[a-z][a-z0-9-]*$/.test(r)?`${r} `:"";this.agentSelect.createEl("option",{text:`${o}${a.name}`,attr:{value:a.name}})}if(this.selectedAgentName&&e.some(a=>a.name===this.selectedAgentName))this.agentSelect.value=this.selectedAgentName,this.textarea.disabled=!1;else if(s&&e.some(a=>a.name===s))this.agentSelect.value=s,this.selectedAgentName=s,this.textarea.disabled=!1;else{this.selectedAgentName=null,this.leaf.updateHeader(),this.textarea.disabled=!0,this.textarea.placeholder="Select an agent to start chatting\u2026",this.showEmptyState();return}this.leaf.updateHeader(),this.textarea.placeholder="Message the agent\u2026 (Ctrl+Enter to send)";let n=this.messagesInner.querySelector(".af-chat-bubble")!==null;this.selectedAgentName&&!n&&this.switchToAgent(this.selectedAgentName,this.selectedConversationId||void 0)}showEmptyState(){this.messagesInner.empty();let e=this.messagesInner.createDiv({cls:"af-chat-view-empty"}),s=e.createDiv({cls:"af-chat-view-empty-icon"});this.plugin.runtime.getSnapshot().agents.length===0?((0,q.setIcon)(s,"bot"),e.createDiv({cls:"af-chat-view-empty-text",text:"No agents available"}),e.createDiv({cls:"af-chat-view-empty-hint",text:"Create an agent to start chatting"})):((0,q.setIcon)(s,"message-circle"),e.createDiv({cls:"af-chat-view-empty-text",text:"Select an agent to start"}),e.createDiv({cls:"af-chat-view-empty-hint",text:"Choose an agent from the dropdown above"}))}async switchToAgent(e,s){let a=this.plugin.runtime.getSnapshot().agents.find(h=>h.name===e);if(!a)return;await this.loadConversations(a);let r;if(s&&this.conversationsCache.some(h=>h.id===s))r=s;else if(this.conversationsCache.length>0)r=this.conversationsCache[0].id;else{r=Uo();try{await this.plugin.repository.createConversation(a,r,$o())}catch(h){new q.Notice(`Couldn't create conversation: ${h instanceof Error?h.message:String(h)}`);return}await this.loadConversations(a)}if(!s||s!==r){let h=this.plugin.app.workspace.getLeavesOfType(nt);for(let d of h)if(d.view!==this&&d.view instanceof i&&d.view.selectedAgentName===e&&d.view.selectedConversationId===r){this.plugin.app.workspace.revealLeaf(d);return}}this.selectedAgentName=e,this.selectedConversationId=r,this.leaf.updateHeader(),this.activityEl=null,this.streamingDot=null,this.messagesInner.empty(),this.threadExpanded.clear(),this.renderConvoPanel();let o=Wn(e,r),c=this.sessions.get(o);if(!c){let h=new Gt(a,this.plugin.settings,this.plugin.repository,this.app.vault,{inAppConversationId:r,mcpAuth:this.plugin.mcpAuth});c={session:h},this.sessions.set(o,c),await h.loadPersistedState()}this.setStatsSource(c.session);for(let h of c.session.messages)if(h.role==="user")this.addBubble("user",h.content,h.attachments);else{let d=this.addBubble("assistant");if(this.renderMarkdownBubble(d,h.content),d._setRawText?.(h.content),this.attachThreadAffordance(d,h.id,c.session),h.toolCalls&&h.toolCalls.length>0){let u=this.getOrCreateAffordancesRow(d);this.buildToolSummary(h.toolCalls,u)}}this.activityUnsub?.();let l=c.session;this.activityUnsub=l.onActivityChange(()=>{this.getCurrentSession()?.session===l&&this.renderIndicators(l)}),this.textarea.disabled=!1,this.textarea.focus()}getCurrentSession(){if(this.selectedAgentName)return this.sessions.get(Wn(this.selectedAgentName,this.selectedConversationId))}async loadConversations(e){this.conversationsCache=await this.plugin.repository.listConversations(e)}async refreshConversationsList(e){await this.loadConversations(e),this.renderConvoPanel(),this.leaf.updateHeader()}renderConvoPanel(){if(!this.convoPanelEl||(this.convoPanelEl.empty(),!this.selectedAgentName))return;this.convoPanelEl.createDiv({cls:"af-chat-convo-header",text:"Conversations"});let e=this.convoPanelEl.createDiv({cls:"af-chat-convo-new"}),s=e.createSpan({cls:"af-chat-convo-new-icon"});(0,q.setIcon)(s,"plus"),e.createSpan({cls:"af-chat-convo-new-label",text:"New chat"}),e.onclick=()=>void this.handleNewChat();let n=this.convoPanelEl.createDiv({cls:"af-chat-convo-list"});for(let a of this.conversationsCache)this.renderConvoRow(n,a)}renderConvoRow(e,s){let n=s.id===this.selectedConversationId,a=e.createDiv({cls:`af-chat-convo-item${n?" active":""}`}),r=a.createDiv({cls:"af-chat-convo-name",text:s.name});r.title=s.name,r.ondblclick=h=>{h.stopPropagation(),this.beginInlineRename(r,s)};let o=a.createDiv({cls:"af-chat-convo-meta"}),c=s.messageCount===1?"1 msg":`${s.messageCount} msgs`;o.appendText(`${c} \xB7 ${Hd(s.lastActive)}`);let l=a.createEl("button",{cls:"af-chat-convo-trash",attr:{title:"Delete conversation","aria-label":"Delete conversation"}});(0,q.setIcon)(l,"trash-2"),l.onclick=h=>{h.stopPropagation(),this.handleDeleteConversation(s)},a.onclick=()=>{s.id!==this.selectedConversationId&&this.handleConvoSelected(s.id)}}beginInlineRename(e,s){let n=s.name,a=activeDocument.createElement("input");a.type="text",a.value=n,a.className="af-chat-convo-name-input",e.replaceWith(a),a.focus(),a.select();let r=!1,o=l=>{let h=activeDocument.createElement("div");return h.className="af-chat-convo-name",h.textContent=l??n,h.title=l??n,h.ondblclick=d=>{d.stopPropagation();let u=this.conversationsCache.find(p=>p.id===s.id)??s;this.beginInlineRename(h,u)},a.replaceWith(h),h},c=async()=>{if(r)return;r=!0;let l=a.value.trim();if(!l||l===n){o(null);return}try{await this.saveConversationName(s.id,l)}catch(h){new q.Notice(`Couldn't rename: ${h instanceof Error?h.message:String(h)}`),o(null);return}this.renderConvoPanel(),this.leaf.updateHeader()};a.addEventListener("keydown",l=>{l.key==="Enter"?(l.preventDefault(),c()):l.key==="Escape"&&(l.preventDefault(),r=!0,o(null))}),a.addEventListener("blur",()=>{c()})}async saveConversationName(e,s){if(!this.selectedAgentName)return;let a=this.plugin.runtime.getSnapshot().agents.find(l=>l.name===this.selectedAgentName);if(!a)return;try{await this.plugin.repository.renameConversation(a,e,s)}catch{}let r=Wn(this.selectedAgentName,e),o=this.sessions.get(r);o&&await o.session.setConversationName(s);let c=this.conversationsCache.findIndex(l=>l.id===e);c>=0&&(this.conversationsCache[c]={...this.conversationsCache[c],name:s})}async handleConvoSelected(e){if(!this.selectedAgentName)return;let s=this.plugin.app.workspace.getLeavesOfType(nt);for(let n of s)if(n.view!==this&&n.view instanceof i&&n.view.selectedAgentName===this.selectedAgentName&&n.view.selectedConversationId===e){this.plugin.app.workspace.revealLeaf(n);return}await this.switchToAgent(this.selectedAgentName,e)}handleDeleteConversation(e){let s=this.selectedAgentName;if(!s)return;let a=this.plugin.runtime.getSnapshot().agents.find(r=>r.name===s);a&&new Wt(this.app,{title:`Delete conversation "${e.name}"?`,body:"This removes its message history. The agent and its other conversations are untouched.",confirmText:"Delete",danger:!0,onConfirm:async()=>{let r=Wn(s,e.id);this.sessions.get(r)?.session.dispose(),this.sessions.delete(r);try{await this.plugin.repository.deleteConversation(a,e.id)}catch(o){new q.Notice(`Couldn't delete: ${o instanceof Error?o.message:String(o)}`);return}if(await this.refreshConversationsList(a),e.id===this.selectedConversationId){let o=this.conversationsCache[0]?.id;await this.switchToAgent(s,o)}}}).open()}toggleConvoPanel(){this.convoPanelCollapsed=!this.convoPanelCollapsed,this.userToggledCollapse=!0,this.applyCollapsedClass(),this.leaf.updateHeader()}applyCollapsedClass(){this.convoPanelEl&&(this.convoPanelCollapsed?this.convoPanelEl.addClass("collapsed"):this.convoPanelEl.removeClass("collapsed")),this.collapseBtn&&this.collapseBtn.toggleClass("is-collapsed",this.convoPanelCollapsed)}renderMarkdownBubble(e,s){let n=e.querySelector(".af-chat-copy-btn"),a=n?.parentNode?.removeChild(n)??null;e.empty(),e.addClass("af-compact-md"),q.MarkdownRenderer.render(this.app,s,e,"",this).then(()=>{a&&e.appendChild(a),this.wireBubbleLinks(e),e.querySelectorAll("pre").forEach(r=>{r.querySelector(".copy-code-button")?.remove();let o=r.querySelector("code");if(!o)return;let c=activeDocument.createElement("button");c.className="af-code-copy-btn",c.setAttribute("aria-label","Copy code"),(0,q.setIcon)(c,"copy"),c.onclick=l=>{l.stopPropagation(),navigator.clipboard.writeText(o.textContent??"").then(()=>{c.addClass("copied"),(0,q.setIcon)(c,"check"),window.setTimeout(()=>{c.removeClass("copied"),(0,q.setIcon)(c,"copy")},1500)})},r.setCssStyles({position:"relative"}),r.appendChild(c)})})}wireBubbleLinks(e){e.addEventListener("click",s=>{let n=s.target.closest("a");if(!n)return;if(n.classList.contains("internal-link")){s.preventDefault(),s.stopPropagation();let r=n.getAttribute("data-href")||n.getAttribute("href")||n.textContent||"";if(!r)return;let o=s.shiftKey?"split":"tab";this.app.workspace.openLinkText(r,"",o);return}let a=n.getAttribute("href")||"";if(a.startsWith("obsidian://")){s.preventDefault(),s.stopPropagation(),window.location.href=a;return}if(n.classList.contains("external-link")||/^https?:\/\//.test(a)){s.preventDefault(),s.stopPropagation(),window.open(a,"_blank");return}})}addCopyBtn(e,s){let n=e.createEl("button",{cls:"af-chat-copy-btn",attr:{"aria-label":"Copy message"}});(0,q.setIcon)(n,"copy"),n.onclick=a=>{a.stopPropagation(),navigator.clipboard.writeText(s()).then(()=>{n.addClass("copied"),(0,q.setIcon)(n,"check"),window.setTimeout(()=>{n.removeClass("copied"),(0,q.setIcon)(n,"copy")},1500)})}}addBubble(e,s,n){if(e==="user"&&n&&n.length>0){let r=this.messagesInner.createDiv({cls:"af-chat-bubble-attachments"});for(let o of n){let c=r.createSpan({cls:"af-chat-pill af-chat-pill-inline"}),l=c.createSpan({cls:"af-chat-pill-icon"}),h=/\.(png|jpe?g|gif|webp|svg|bmp)$/i.test(o);(0,q.setIcon)(l,h?"image":"file-text"),c.createSpan({cls:"af-chat-pill-name",text:o})}}let a=this.messagesInner.createDiv({cls:`af-chat-bubble af-chat-bubble-${e}`});if(s&&(e==="assistant"?this.renderMarkdownBubble(a,s):a.setText(s)),e==="assistant"){let r=s??"";this.addCopyBtn(a,()=>r),a._setRawText=o=>{r=o}}return this.messagesEl.scrollTop=this.messagesEl.scrollHeight,a}getOrCreateAffordancesRow(e){let s=e.parentElement;if(!s)throw new Error("bubble has no parent");let n=e.nextElementSibling;if(n&&n.classList.contains("af-chat-affordances"))return n;let a=activeDocument.createElement("div");return a.className="af-chat-affordances",s.insertBefore(a,e.nextSibling),a}attachThreadAffordance(e,s,n){let a=e.parentElement;if(!a)return;let r=this.getOrCreateAffordancesRow(e);if(r.querySelector(".af-thread-badge"))return;let o=activeDocument.createElement("div");o.className="af-thread-badge",o.setAttribute("role","button"),o.setAttribute("tabindex","0"),r.appendChild(o),(0,q.setIcon)(o,"message-circle");let c=o.createSpan({cls:"af-thread-badge-label"}),l=activeDocument.createElement("div");l.className="af-thread-container",l.setCssStyles({display:"none"}),a.insertBefore(l,r.nextSibling);let h=()=>{let f=n.getThreadIndex()[s]?.messageCount??0;f<=0?c.setText("Thread"):c.setText(`${f} ${f===1?"reply":"replies"}`),f>0&&o.addClass("has-replies")};h();let d=!1,u=async()=>{if(this.threadExpanded.get(s)===!0){l.setCssStyles({display:"none"}),this.threadExpanded.set(s,!1),o.removeClass("expanded"),this.setStatsSource(n);return}if(this.threadExpanded.set(s,!0),l.setCssStyles({display:""}),o.addClass("expanded"),!d)try{let m=await n.openOrCreateThread(s);this.renderThreadContainer(l,m,n,h),d=!0}catch(m){l.setText(`Failed to open thread: ${m instanceof Error?m.message:String(m)}`)}};o.onclick=()=>void u(),o.onkeydown=p=>{(p.key==="Enter"||p.key===" ")&&(p.preventDefault(),u())}}renderThreadContainer(e,s,n,a){e.empty();let r=e.createDiv({cls:"af-thread-wrap"}),o=r.createDiv({cls:"af-thread-messages"}),c=null,l=null,h=C=>{C?(c||(c=o.createDiv({cls:"af-chat-activity"})),c.setText(`Working\u2026 (${C})`)):c&&(c.remove(),c=null)},d=C=>{if(C&&!l){l=o.createDiv({cls:"af-chat-streaming-dot"});for(let I=0;I<3;I++)l.createSpan()}else!C&&l&&(l.remove(),l=null)},u=(C,I,P)=>{if(C==="user"&&P&&P.length>0){let F=o.createDiv({cls:"af-chat-bubble-attachments"});for(let z of P){let U=F.createSpan({cls:"af-chat-pill af-chat-pill-inline"}),Z=U.createSpan({cls:"af-chat-pill-icon"});(0,q.setIcon)(Z,z.match(/\.(png|jpe?g|gif|webp|svg)$/i)?"image":"file-text"),U.createSpan({cls:"af-chat-pill-name",text:z})}}let L=o.createDiv({cls:`af-thread-bubble af-thread-bubble-${C}`});return C==="assistant"?this.renderMarkdownBubble(L,I):L.setText(I),L};for(let C of s.messages)u(C.role,C.content,C.attachments);let p=[],m=[],f=r.createDiv({cls:"af-thread-composer-wrap"}),g=f.createDiv({cls:"af-chat-pills-row af-thread-pills-row"});g.setCssStyles({display:"none"});let v=()=>{if(g.empty(),p.length===0&&m.length===0){g.setCssStyles({display:"none"});return}g.setCssStyles({display:"flex"});for(let C of p){let I=g.createDiv({cls:"af-chat-pill"}),P=I.createSpan({cls:"af-chat-pill-icon"});(0,q.setIcon)(P,"file-text"),I.createSpan({cls:"af-chat-pill-name",text:C.name});let L=I.createSpan({cls:"af-chat-pill-remove"});(0,q.setIcon)(L,"x"),L.onclick=F=>{F.stopPropagation();let z=p.findIndex(U=>U.path===C.path);z>=0&&p.splice(z,1),v()}}for(let C of m){let I=g.createDiv({cls:"af-chat-pill"}),P=I.createSpan({cls:"af-chat-pill-icon"});(0,q.setIcon)(P,"image"),I.createSpan({cls:"af-chat-pill-name",text:C.name});let L=I.createSpan({cls:"af-chat-pill-remove"});(0,q.setIcon)(L,"x"),L.onclick=F=>{F.stopPropagation();let z=m.findIndex(U=>U.path===C.path);z>=0&&m.splice(z,1),v()}}},b=()=>{let C=this.app.workspace.getActiveFile();if(!C){new q.Notice("No active document to attach");return}if(p.some(P=>P.path===C.path)){new q.Notice(`"${C.name}" is already attached`);return}if(!new Set(["md","txt","json","yaml","yml","toml","csv","xml","html","css","js","ts","py","sh","sql","env","cfg","ini","log"]).has(C.extension.toLowerCase())){new q.Notice(`Can't attach "${C.name}" \u2014 only text files are supported`);return}p.push(C),v()},k=async C=>{let I=C.type.split("/")[1]?.replace("jpeg","jpg")??"png",P=C.name&&C.name!=="image"?C.name:`pasted-${Date.now()}.${I}`;if(m.some(F=>F.name===P)){new q.Notice(`"${P}" is already attached`);return}let L=await this.saveImageBlobToVault(C);L&&(m.push(L),v())},y=f.createDiv({cls:"af-chat-input-row af-thread-composer"}),S=y.createEl("button",{cls:"af-chat-attach-btn"});E(S,"plus","af-btn-icon"),S.title="Attach active document",S.onclick=C=>{C.preventDefault(),b()};let T=y.createEl("textarea",{cls:"af-chat-input af-thread-input",attr:{placeholder:"Message in thread\u2026 (Ctrl+Enter to send)",rows:"1"}});T.addEventListener("paste",C=>{let I=C.clipboardData?.items;if(I)for(let P=0;P{C.preventDefault(),C.stopPropagation(),f.addClass("af-chat-input-dragover")}),f.addEventListener("dragleave",()=>{f.removeClass("af-chat-input-dragover")}),f.addEventListener("drop",C=>{C.preventDefault(),C.stopPropagation(),f.removeClass("af-chat-input-dragover");let I=C.dataTransfer?.files;if(I)for(let P=0;P{T.setCssStyles({height:"auto"}),T.setCssStyles({height:`${Math.min(T.scrollHeight,120)}px`}),_.setCssStyles({display:T.value.trim()?"flex":"none"})};T.addEventListener("input",D),T.addEventListener("focus",()=>this.setStatsSource(s));let M=async()=>{let C=T.value.trim();if(!C||s.isStreaming)return;let I=await this.buildAttachmentContextFor(p,m),P=[...p.map(U=>U.name),...m.map(U=>U.name)],L=I?`${I}${C}`:void 0;T.value="",D(),p.length=0,m.length=0,v(),u("user",C,P.length>0?P:void 0),d(!0);let F=null,z="";try{await s.sendMessage(C,U=>{if(U.type==="text"){F||(d(!1),h(),F=u("assistant",""),F.empty()),z+=U.content;let Z=F.querySelector(".af-chat-stream-text");Z||(Z=F.createDiv({cls:"af-chat-stream-text"})),Z.setText(z)}else U.type==="tool_use"?h(U.toolName):U.type==="result"&&(h(),d(!1),F&&this.renderMarkdownBubble(F,cs(z)))},L,P.length>0?P:void 0),a()}catch(U){d(!1),h();let Z=U instanceof Error?U.message:String(U);o.createDiv({cls:"af-thread-error",text:`Error: ${Z}`})}};_.onclick=()=>void M(),T.onkeydown=C=>{C.key==="Enter"&&(C.ctrlKey||C.metaKey)&&(C.preventDefault(),M())}}buildToolSummary(e,s){let a=(s??this.messagesInner).createDiv({cls:"af-chat-tool-summary"}),r=a.createEl("details"),o=r.createEl("summary"),c=new Map;for(let d of e){let u=c.get(d.name)??[];d.command&&u.push(d.command),c.set(d.name,u)}let l=o.createSpan({cls:"af-chat-tool-icon"});(0,q.setIcon)(l,"wrench"),o.appendText(` ${e.length} tool call${e.length!==1?"s":""}`);let h=r.createDiv({cls:"af-chat-tool-list"});for(let[d,u]of c){let p=u.length||(c.get(d)?.length??1),m=h.createDiv({cls:"af-chat-tool-item"}),f=p>1?`${d} (\xD7${p})`:d;m.createSpan({cls:"af-chat-tool-name",text:f}),u.length===1&&u[0]&&m.createSpan({cls:"af-chat-tool-cmd",text:u[0]})}return a}renderIndicators(e){let s=e.isStreaming,n=e.currentToolName,a=e.hasCurrentTurnText,r=!!this.messagesInner.querySelector(".af-chat-stream-text"),o=null;if(s&&n?o=`Working\u2026 (${n})`:s&&a&&!r&&(o="Replying\u2026"),o?(this.activityEl?(this.activityEl.parentElement!==this.messagesInner||this.activityEl.nextElementSibling!==null)&&this.messagesInner.appendChild(this.activityEl):this.activityEl=this.messagesInner.createDiv({cls:"af-chat-activity"}),this.activityEl.textContent!==o&&this.activityEl.setText(o)):this.activityEl&&(this.activityEl.remove(),this.activityEl=null),s&&!n&&!a)if(this.streamingDot)(this.streamingDot.parentElement!==this.messagesInner||this.streamingDot.nextElementSibling!==null)&&this.messagesInner.appendChild(this.streamingDot);else{this.streamingDot=this.messagesInner.createDiv({cls:"af-chat-streaming-dot"});for(let l=0;l<3;l++)this.streamingDot.createSpan()}else this.streamingDot&&(this.streamingDot.remove(),this.streamingDot=null);this.setAttachStopMode(s)}setAttachStopMode(e){e!==this.isInStopMode&&(this.isInStopMode=e,this.attachStopBtn.empty(),e?(E(this.attachStopBtn,"square","af-btn-icon"),this.attachStopBtn.title="Stop generation",this.attachStopBtn.addClass("af-chat-stop-mode")):(E(this.attachStopBtn,"plus","af-btn-icon"),this.attachStopBtn.title="Attach active document",this.attachStopBtn.removeClass("af-chat-stop-mode")))}handleStop(){let e=this.getCurrentSession();e&&(e.session.abort(),this.addBubble("error","Generation stopped"))}attachActiveDocument(){let e=this.app.workspace.getActiveFile();if(!e){new q.Notice("No active document to attach");return}if(this.attachedFiles.some(a=>a.path===e.path)){new q.Notice(`"${e.name}" is already attached`);return}let s=e.extension.toLowerCase();if(!new Set(["md","txt","json","yaml","yml","toml","csv","xml","html","css","js","ts","py","sh","sql","env","cfg","ini","log"]).has(s)){new q.Notice(`Can't attach "${e.name}" \u2014 only text files are supported`);return}this.attachedFiles.push(e),this.renderPills()}async saveImageBlobToVault(e){let s=e.type.split("/")[1]?.replace("jpeg","jpg")??"png",n=Date.now(),a=e.name&&e.name!=="image"?e.name:`pasted-${n}.${s}`,r=`${this.plugin.settings.fleetFolder}/chat-images`,o=`${r}/${n}-${a}`;try{this.app.vault.getAbstractFileByPath(r)||await this.app.vault.createFolder(r);let c=await e.arrayBuffer();await this.app.vault.createBinary(o,c);let h=this.app.vault.adapter.getBasePath?.()??"";return{name:a,path:`${h}/${o}`}}catch(c){let l=c instanceof Error?c.message:String(c);return new q.Notice(`Failed to save image: ${l}`),null}}async buildAttachmentContextFor(e,s){if(e.length===0&&s.length===0)return"";let n=[];for(let a of e)try{let r=await this.app.vault.cachedRead(a);n.push(`### ${a.name} +To get 1M context on Opus, set the agent's model to claude-opus-4-7[1m] (or us.anthropic.claude-opus-4-7[1m] on Bedrock).`}if(e.lastCompact){let{preTokens:n,postTokens:a}=e.lastCompact,r=s.createSpan({cls:"af-chat-stats-compact"});r.setText(`compacted ${is(n)} \u2192 ${is(a)}`),r.title=`Conversation was summarized to free up context. ${is(n)} tokens reduced to ${is(a)}.`}}populateAgentDropdown(){let e=this.plugin.runtime.getSnapshot().agents,s=this.agentSelect.value;if(this.agentSelect.empty(),e.length===0){let a=this.agentSelect.createEl("option",{text:"No agents available",attr:{value:"",disabled:"true"}});a.selected=!0,this.textarea.disabled=!0,this.showEmptyState();return}if(!this.selectedAgentName){let a=this.agentSelect.createEl("option",{text:"Select agent\u2026",attr:{value:"",disabled:"true"}});a.selected=!0}for(let a of e){let r=a.avatar?.trim(),o=r&&!/^[a-z][a-z0-9-]*$/.test(r)?`${r} `:"";this.agentSelect.createEl("option",{text:`${o}${a.name}`,attr:{value:a.name}})}if(this.selectedAgentName&&e.some(a=>a.name===this.selectedAgentName))this.agentSelect.value=this.selectedAgentName,this.textarea.disabled=!1;else if(s&&e.some(a=>a.name===s))this.agentSelect.value=s,this.selectedAgentName=s,this.textarea.disabled=!1;else{this.selectedAgentName=null,this.leaf.updateHeader(),this.textarea.disabled=!0,this.textarea.placeholder="Select an agent to start chatting\u2026",this.showEmptyState();return}this.leaf.updateHeader(),this.textarea.placeholder="Message the agent\u2026 (Shift+Enter for newline)";let n=this.messagesInner.querySelector(".af-chat-bubble")!==null;this.selectedAgentName&&!n&&this.switchToAgent(this.selectedAgentName,this.selectedConversationId||void 0)}showEmptyState(){this.messagesInner.empty();let e=this.messagesInner.createDiv({cls:"af-chat-view-empty"}),s=e.createDiv({cls:"af-chat-view-empty-icon"});this.plugin.runtime.getSnapshot().agents.length===0?((0,z.setIcon)(s,"bot"),e.createDiv({cls:"af-chat-view-empty-text",text:"No agents available"}),e.createDiv({cls:"af-chat-view-empty-hint",text:"Create an agent to start chatting"})):((0,z.setIcon)(s,"message-circle"),e.createDiv({cls:"af-chat-view-empty-text",text:"Select an agent to start"}),e.createDiv({cls:"af-chat-view-empty-hint",text:"Choose an agent from the dropdown above"}))}async switchToAgent(e,s){let a=this.plugin.runtime.getSnapshot().agents.find(h=>h.name===e);if(!a)return;await this.loadConversations(a);let r;if(s&&this.conversationsCache.some(h=>h.id===s))r=s;else if(this.conversationsCache.length>0)r=this.conversationsCache[0].id;else{r=el();try{await this.plugin.repository.createConversation(a,r,tl())}catch(h){new z.Notice(`Couldn't create conversation: ${h instanceof Error?h.message:String(h)}`);return}await this.loadConversations(a)}if(!s||s!==r){let h=this.plugin.app.workspace.getLeavesOfType(it);for(let d of h)if(d.view!==this&&d.view instanceof i&&d.view.selectedAgentName===e&&d.view.selectedConversationId===r){this.plugin.app.workspace.revealLeaf(d);return}}this.selectedAgentName=e,this.selectedConversationId=r,this.leaf.updateHeader(),this.activityEl=null,this.streamingDot=null,this.messagesInner.empty(),this.threadExpanded.clear(),this.renderConvoPanel();let o=qn(e,r),c=this.sessions.get(o);if(!c){let h=new Vt(a,this.plugin.settings,this.plugin.repository,this.app.vault,{inAppConversationId:r,mcpAuth:this.plugin.mcpAuth});h.setUsageRecorder(d=>this.plugin.runtime.recordUsage(d)),c={session:h},this.sessions.set(o,c),await h.loadPersistedState()}this.setStatsSource(c.session);for(let h of c.session.messages)if(h.role==="user")this.addBubble("user",h.content,h.attachments);else{let d=this.addBubble("assistant");if(this.renderMarkdownBubble(d,h.content),d._setRawText?.(h.content),this.attachThreadAffordance(d,h.id,c.session),h.toolCalls&&h.toolCalls.length>0){let u=this.getOrCreateAffordancesRow(d);this.buildToolSummary(h.toolCalls,u)}}this.activityUnsub?.();let l=c.session;this.activityUnsub=l.onActivityChange(()=>{this.getCurrentSession()?.session===l&&this.renderIndicators(l)}),this.textarea.disabled=!1,this.textarea.focus()}getCurrentSession(){if(this.selectedAgentName)return this.sessions.get(qn(this.selectedAgentName,this.selectedConversationId))}async loadConversations(e){this.conversationsCache=await this.plugin.repository.listConversations(e)}async refreshConversationsList(e){await this.loadConversations(e),this.renderConvoPanel(),this.leaf.updateHeader()}renderConvoPanel(){if(!this.convoPanelEl||(this.convoPanelEl.empty(),!this.selectedAgentName))return;this.convoPanelEl.createDiv({cls:"af-chat-convo-header",text:"Conversations"});let e=this.convoPanelEl.createDiv({cls:"af-chat-convo-new"}),s=e.createSpan({cls:"af-chat-convo-new-icon"});(0,z.setIcon)(s,"plus"),e.createSpan({cls:"af-chat-convo-new-label",text:"New chat"}),e.onclick=()=>void this.handleNewChat();let n=this.convoPanelEl.createDiv({cls:"af-chat-convo-list"});for(let a of this.conversationsCache)this.renderConvoRow(n,a)}renderConvoRow(e,s){let n=s.id===this.selectedConversationId,a=e.createDiv({cls:`af-chat-convo-item${n?" active":""}`}),r=a.createDiv({cls:"af-chat-convo-name",text:s.name});r.title=s.name,r.ondblclick=h=>{h.stopPropagation(),this.beginInlineRename(r,s)};let o=a.createDiv({cls:"af-chat-convo-meta"}),c=s.messageCount===1?"1 msg":`${s.messageCount} msgs`;o.appendText(`${c} \xB7 ${Sh(s.lastActive)}`);let l=a.createEl("button",{cls:"af-chat-convo-trash",attr:{title:"Delete conversation","aria-label":"Delete conversation"}});(0,z.setIcon)(l,"trash-2"),l.onclick=h=>{h.stopPropagation(),this.handleDeleteConversation(s)},a.onclick=()=>{s.id!==this.selectedConversationId&&this.handleConvoSelected(s.id)}}beginInlineRename(e,s){let n=s.name,a=activeDocument.createElement("input");a.type="text",a.value=n,a.className="af-chat-convo-name-input",e.replaceWith(a),a.focus(),a.select();let r=!1,o=l=>{let h=activeDocument.createElement("div");return h.className="af-chat-convo-name",h.textContent=l??n,h.title=l??n,h.ondblclick=d=>{d.stopPropagation();let u=this.conversationsCache.find(p=>p.id===s.id)??s;this.beginInlineRename(h,u)},a.replaceWith(h),h},c=async()=>{if(r)return;r=!0;let l=a.value.trim();if(!l||l===n){o(null);return}try{await this.saveConversationName(s.id,l)}catch(h){new z.Notice(`Couldn't rename: ${h instanceof Error?h.message:String(h)}`),o(null);return}this.renderConvoPanel(),this.leaf.updateHeader()};a.addEventListener("keydown",l=>{l.key==="Enter"?(l.preventDefault(),c()):l.key==="Escape"&&(l.preventDefault(),r=!0,o(null))}),a.addEventListener("blur",()=>{c()})}async saveConversationName(e,s){if(!this.selectedAgentName)return;let a=this.plugin.runtime.getSnapshot().agents.find(l=>l.name===this.selectedAgentName);if(!a)return;try{await this.plugin.repository.renameConversation(a,e,s)}catch{}let r=qn(this.selectedAgentName,e),o=this.sessions.get(r);o&&await o.session.setConversationName(s);let c=this.conversationsCache.findIndex(l=>l.id===e);c>=0&&(this.conversationsCache[c]={...this.conversationsCache[c],name:s})}async handleConvoSelected(e){if(!this.selectedAgentName)return;let s=this.plugin.app.workspace.getLeavesOfType(it);for(let n of s)if(n.view!==this&&n.view instanceof i&&n.view.selectedAgentName===this.selectedAgentName&&n.view.selectedConversationId===e){this.plugin.app.workspace.revealLeaf(n);return}await this.switchToAgent(this.selectedAgentName,e)}handleDeleteConversation(e){let s=this.selectedAgentName;if(!s)return;let a=this.plugin.runtime.getSnapshot().agents.find(r=>r.name===s);a&&new Ht(this.app,{title:`Delete conversation "${e.name}"?`,body:"This removes its message history. The agent and its other conversations are untouched.",confirmText:"Delete",danger:!0,onConfirm:async()=>{let r=qn(s,e.id);this.sessions.get(r)?.session.dispose(),this.sessions.delete(r);try{await this.plugin.repository.deleteConversation(a,e.id)}catch(o){new z.Notice(`Couldn't delete: ${o instanceof Error?o.message:String(o)}`);return}if(await this.refreshConversationsList(a),e.id===this.selectedConversationId){let o=this.conversationsCache[0]?.id;await this.switchToAgent(s,o)}}}).open()}toggleConvoPanel(){this.convoPanelCollapsed=!this.convoPanelCollapsed,this.userToggledCollapse=!0,this.applyCollapsedClass(),this.leaf.updateHeader()}applyCollapsedClass(){this.convoPanelEl&&(this.convoPanelCollapsed?this.convoPanelEl.addClass("collapsed"):this.convoPanelEl.removeClass("collapsed")),this.collapseBtn&&this.collapseBtn.toggleClass("is-collapsed",this.convoPanelCollapsed)}renderMarkdownBubble(e,s){let n=e.querySelector(".af-chat-copy-btn"),a=n?.parentNode?.removeChild(n)??null;e.empty(),e.addClass("af-compact-md"),z.MarkdownRenderer.render(this.app,s,e,"",this).then(()=>{a&&e.appendChild(a),this.wireBubbleLinks(e),e.querySelectorAll("pre").forEach(r=>{r.querySelector(".copy-code-button")?.remove();let o=r.querySelector("code");if(!o)return;let c=activeDocument.createElement("button");c.className="af-code-copy-btn",c.setAttribute("aria-label","Copy code"),(0,z.setIcon)(c,"copy"),c.onclick=l=>{l.stopPropagation(),navigator.clipboard.writeText(o.textContent??"").then(()=>{c.addClass("copied"),(0,z.setIcon)(c,"check"),window.setTimeout(()=>{c.removeClass("copied"),(0,z.setIcon)(c,"copy")},1500)})},r.setCssStyles({position:"relative"}),r.appendChild(c)})})}wireBubbleLinks(e){e.addEventListener("click",s=>{let n=s.target.closest("a");if(!n)return;if(n.classList.contains("internal-link")){s.preventDefault(),s.stopPropagation();let r=n.getAttribute("data-href")||n.getAttribute("href")||n.textContent||"";if(!r)return;let o=s.shiftKey?"split":"tab";this.app.workspace.openLinkText(r,"",o);return}let a=n.getAttribute("href")||"";if(a.startsWith("obsidian://")){s.preventDefault(),s.stopPropagation(),window.location.href=a;return}if(n.classList.contains("external-link")||/^https?:\/\//.test(a)){s.preventDefault(),s.stopPropagation(),window.open(a,"_blank");return}})}addCopyBtn(e,s){let n=e.createEl("button",{cls:"af-chat-copy-btn",attr:{"aria-label":"Copy message"}});(0,z.setIcon)(n,"copy"),n.onclick=a=>{a.stopPropagation(),navigator.clipboard.writeText(s()).then(()=>{n.addClass("copied"),(0,z.setIcon)(n,"check"),window.setTimeout(()=>{n.removeClass("copied"),(0,z.setIcon)(n,"copy")},1500)})}}addBubble(e,s,n){if(e==="user"&&n&&n.length>0){let r=this.messagesInner.createDiv({cls:"af-chat-bubble-attachments"});for(let o of n){let c=r.createSpan({cls:"af-chat-pill af-chat-pill-inline"}),l=c.createSpan({cls:"af-chat-pill-icon"}),h=/\.(png|jpe?g|gif|webp|svg|bmp)$/i.test(o);(0,z.setIcon)(l,h?"image":"file-text"),c.createSpan({cls:"af-chat-pill-name",text:o})}}let a=this.messagesInner.createDiv({cls:`af-chat-bubble af-chat-bubble-${e}`});if(s&&(e==="assistant"?this.renderMarkdownBubble(a,s):a.setText(s)),e==="assistant"){let r=s??"";this.addCopyBtn(a,()=>r),a._setRawText=o=>{r=o}}return this.messagesEl.scrollTop=this.messagesEl.scrollHeight,a}getOrCreateAffordancesRow(e){let s=e.parentElement;if(!s)throw new Error("bubble has no parent");let n=e.nextElementSibling;if(n&&n.classList.contains("af-chat-affordances"))return n;let a=activeDocument.createElement("div");return a.className="af-chat-affordances",s.insertBefore(a,e.nextSibling),a}attachThreadAffordance(e,s,n){let a=e.parentElement;if(!a)return;let r=this.getOrCreateAffordancesRow(e);if(r.querySelector(".af-thread-badge"))return;let o=activeDocument.createElement("div");o.className="af-thread-badge",o.setAttribute("role","button"),o.setAttribute("tabindex","0"),r.appendChild(o),(0,z.setIcon)(o,"message-circle");let c=o.createSpan({cls:"af-thread-badge-label"}),l=activeDocument.createElement("div");l.className="af-thread-container",l.setCssStyles({display:"none"}),a.insertBefore(l,r.nextSibling);let h=()=>{let f=n.getThreadIndex()[s]?.messageCount??0;f<=0?c.setText("Thread"):c.setText(`${f} ${f===1?"reply":"replies"}`),f>0&&o.addClass("has-replies")};h();let d=!1,u=async()=>{if(this.threadExpanded.get(s)===!0){l.setCssStyles({display:"none"}),this.threadExpanded.set(s,!1),o.removeClass("expanded"),this.setStatsSource(n);return}if(this.threadExpanded.set(s,!0),l.setCssStyles({display:""}),o.addClass("expanded"),!d)try{let m=await n.openOrCreateThread(s);this.renderThreadContainer(l,m,n,h),d=!0}catch(m){l.setText(`Failed to open thread: ${m instanceof Error?m.message:String(m)}`)}};o.onclick=()=>void u(),o.onkeydown=p=>{(p.key==="Enter"||p.key===" ")&&(p.preventDefault(),u())}}renderThreadContainer(e,s,n,a){e.empty();let r=e.createDiv({cls:"af-thread-wrap"}),o=r.createDiv({cls:"af-thread-messages"}),c=null,l=null,h=C=>{C?(c||(c=o.createDiv({cls:"af-chat-activity"})),c.setText(`Working\u2026 (${C})`)):c&&(c.remove(),c=null)},d=C=>{if(C&&!l){l=o.createDiv({cls:"af-chat-streaming-dot"});for(let E=0;E<3;E++)l.createSpan()}else!C&&l&&(l.remove(),l=null)},u=(C,E,P)=>{if(C==="user"&&P&&P.length>0){let M=o.createDiv({cls:"af-chat-bubble-attachments"});for(let U of P){let $=M.createSpan({cls:"af-chat-pill af-chat-pill-inline"}),Z=$.createSpan({cls:"af-chat-pill-icon"});(0,z.setIcon)(Z,U.match(/\.(png|jpe?g|gif|webp|svg)$/i)?"image":"file-text"),$.createSpan({cls:"af-chat-pill-name",text:U})}}let N=o.createDiv({cls:`af-thread-bubble af-thread-bubble-${C}`});return C==="assistant"?this.renderMarkdownBubble(N,E):N.setText(E),N};for(let C of s.messages)u(C.role,C.content,C.attachments);let p=[],m=[],f=r.createDiv({cls:"af-thread-composer-wrap"}),g=f.createDiv({cls:"af-chat-pills-row af-thread-pills-row"});g.setCssStyles({display:"none"});let y=()=>{if(g.empty(),p.length===0&&m.length===0){g.setCssStyles({display:"none"});return}g.setCssStyles({display:"flex"});for(let C of p){let E=g.createDiv({cls:"af-chat-pill"}),P=E.createSpan({cls:"af-chat-pill-icon"});(0,z.setIcon)(P,"file-text"),E.createSpan({cls:"af-chat-pill-name",text:C.name});let N=E.createSpan({cls:"af-chat-pill-remove"});(0,z.setIcon)(N,"x"),N.onclick=M=>{M.stopPropagation();let U=p.findIndex($=>$.path===C.path);U>=0&&p.splice(U,1),y()}}for(let C of m){let E=g.createDiv({cls:"af-chat-pill"}),P=E.createSpan({cls:"af-chat-pill-icon"});(0,z.setIcon)(P,"image"),E.createSpan({cls:"af-chat-pill-name",text:C.name});let N=E.createSpan({cls:"af-chat-pill-remove"});(0,z.setIcon)(N,"x"),N.onclick=M=>{M.stopPropagation();let U=m.findIndex($=>$.path===C.path);U>=0&&m.splice(U,1),y()}}},v=()=>{let C=this.app.workspace.getActiveFile();if(!C){new z.Notice("No active document to attach");return}if(p.some(P=>P.path===C.path)){new z.Notice(`"${C.name}" is already attached`);return}if(!new Set(["md","txt","json","yaml","yml","toml","csv","xml","html","css","js","ts","py","sh","sql","env","cfg","ini","log"]).has(C.extension.toLowerCase())){new z.Notice(`Can't attach "${C.name}" \u2014 only text files are supported`);return}p.push(C),y()},k=async C=>{let E=C.type.split("/")[1]?.replace("jpeg","jpg")??"png",P=C.name&&C.name!=="image"?C.name:`pasted-${Date.now()}.${E}`;if(m.some(M=>M.name===P)){new z.Notice(`"${P}" is already attached`);return}let N=await this.saveImageBlobToVault(C);N&&(m.push(N),y())},w=f.createDiv({cls:"af-chat-input-row af-thread-composer"}),S=w.createEl("button",{cls:"af-chat-attach-btn"});R(S,"plus","af-btn-icon"),S.title="Attach active document",S.onclick=C=>{C.preventDefault(),v()};let T=w.createEl("textarea",{cls:"af-chat-input af-thread-input",attr:{placeholder:"Message in thread\u2026 (Shift+Enter for newline)",rows:"1"}});T.addEventListener("paste",C=>{let E=C.clipboardData?.items;if(E)for(let P=0;P{C.preventDefault(),C.stopPropagation(),f.addClass("af-chat-input-dragover")}),f.addEventListener("dragleave",()=>{f.removeClass("af-chat-input-dragover")}),f.addEventListener("drop",C=>{C.preventDefault(),C.stopPropagation(),f.removeClass("af-chat-input-dragover");let E=C.dataTransfer?.files;if(E)for(let P=0;P{T.setCssStyles({height:"auto"}),T.setCssStyles({height:`${Math.min(T.scrollHeight,120)}px`}),_.setCssStyles({display:T.value.trim()?"flex":"none"})};T.addEventListener("input",D),T.addEventListener("focus",()=>this.setStatsSource(s));let O=async()=>{let C=T.value.trim();if(!C||s.isStreaming)return;let E=await this.buildAttachmentContextFor(p,m),P=[...p.map($=>$.name),...m.map($=>$.name)],N=E?`${E}${C}`:void 0;T.value="",D(),p.length=0,m.length=0,y(),u("user",C,P.length>0?P:void 0),d(!0);let M=null,U="";try{await s.sendMessage(C,$=>{if($.type==="text"){M||(d(!1),h(),M=u("assistant",""),M.empty()),U+=$.content;let Z=M.querySelector(".af-chat-stream-text");Z||(Z=M.createDiv({cls:"af-chat-stream-text"})),Z.setText(U)}else $.type==="tool_use"?h($.toolName):$.type==="result"&&(h(),d(!1),M&&this.renderMarkdownBubble(M,hs(U)))},N,P.length>0?P:void 0),a()}catch($){d(!1),h();let Z=$ instanceof Error?$.message:String($);o.createDiv({cls:"af-thread-error",text:`Error: ${Z}`})}};_.onclick=()=>void O(),T.onkeydown=C=>{C.key==="Enter"&&!C.shiftKey&&!C.isComposing&&(C.preventDefault(),O())}}buildToolSummary(e,s){let a=(s??this.messagesInner).createDiv({cls:"af-chat-tool-summary"}),r=a.createEl("details"),o=r.createEl("summary"),c=new Map;for(let d of e){let u=c.get(d.name)??[];d.command&&u.push(d.command),c.set(d.name,u)}let l=o.createSpan({cls:"af-chat-tool-icon"});(0,z.setIcon)(l,"wrench"),o.appendText(` ${e.length} tool call${e.length!==1?"s":""}`);let h=r.createDiv({cls:"af-chat-tool-list"});for(let[d,u]of c){let p=u.length||(c.get(d)?.length??1),m=h.createDiv({cls:"af-chat-tool-item"}),f=p>1?`${d} (\xD7${p})`:d;m.createSpan({cls:"af-chat-tool-name",text:f}),u.length===1&&u[0]&&m.createSpan({cls:"af-chat-tool-cmd",text:u[0]})}return a}renderIndicators(e){let s=e.isStreaming,n=e.currentToolName,a=e.hasCurrentTurnText,r=!!this.messagesInner.querySelector(".af-chat-stream-text"),o=null;if(s&&n?o=`Working\u2026 (${n})`:s&&a&&!r&&(o="Replying\u2026"),o?(this.activityEl?(this.activityEl.parentElement!==this.messagesInner||this.activityEl.nextElementSibling!==null)&&this.messagesInner.appendChild(this.activityEl):this.activityEl=this.messagesInner.createDiv({cls:"af-chat-activity"}),this.activityEl.textContent!==o&&this.activityEl.setText(o)):this.activityEl&&(this.activityEl.remove(),this.activityEl=null),s&&!n&&!a)if(this.streamingDot)(this.streamingDot.parentElement!==this.messagesInner||this.streamingDot.nextElementSibling!==null)&&this.messagesInner.appendChild(this.streamingDot);else{this.streamingDot=this.messagesInner.createDiv({cls:"af-chat-streaming-dot"});for(let l=0;l<3;l++)this.streamingDot.createSpan()}else this.streamingDot&&(this.streamingDot.remove(),this.streamingDot=null);this.setAttachStopMode(s)}setAttachStopMode(e){e!==this.isInStopMode&&(this.isInStopMode=e,this.attachStopBtn.empty(),e?(R(this.attachStopBtn,"square","af-btn-icon"),this.attachStopBtn.title="Stop generation",this.attachStopBtn.addClass("af-chat-stop-mode")):(R(this.attachStopBtn,"plus","af-btn-icon"),this.attachStopBtn.title="Attach active document",this.attachStopBtn.removeClass("af-chat-stop-mode")))}handleStop(){let e=this.getCurrentSession();e&&(e.session.abort(),this.addBubble("error","Generation stopped"))}attachActiveDocument(){let e=this.app.workspace.getActiveFile();if(!e){new z.Notice("No active document to attach");return}if(this.attachedFiles.some(a=>a.path===e.path)){new z.Notice(`"${e.name}" is already attached`);return}let s=e.extension.toLowerCase();if(!new Set(["md","txt","json","yaml","yml","toml","csv","xml","html","css","js","ts","py","sh","sql","env","cfg","ini","log"]).has(s)){new z.Notice(`Can't attach "${e.name}" \u2014 only text files are supported`);return}this.attachedFiles.push(e),this.renderPills()}async saveImageBlobToVault(e){let s=e.type.split("/")[1]?.replace("jpeg","jpg")??"png",n=Date.now(),a=e.name&&e.name!=="image"?e.name:`pasted-${n}.${s}`,r=`${this.plugin.settings.fleetFolder}/chat-images`,o=`${r}/${n}-${a}`;try{this.app.vault.getAbstractFileByPath(r)||await this.app.vault.createFolder(r);let c=await e.arrayBuffer();await this.app.vault.createBinary(o,c);let h=this.app.vault.adapter.getBasePath?.()??"";return{name:a,path:`${h}/${o}`}}catch(c){let l=c instanceof Error?c.message:String(c);return new z.Notice(`Failed to save image: ${l}`),null}}async buildAttachmentContextFor(e,s){if(e.length===0&&s.length===0)return"";let n=[];for(let a of e)try{let r=await this.app.vault.cachedRead(a);n.push(`### ${a.name} \`\`\` ${r} \`\`\``)}catch{n.push(`### ${a.name} @@ -12108,6 +12140,6 @@ ${n.join(` --- -`}async attachImageBlob(e){let s=e.type.split("/")[1]?.replace("jpeg","jpg")??"png",n=e.name&&e.name!=="image"?e.name:`pasted-${Date.now()}.${s}`;if(this.attachedImages.some(r=>r.name===n)){new q.Notice(`"${n}" is already attached`);return}let a=await this.saveImageBlobToVault(e);a&&(this.attachedImages.push(a),this.renderPills())}removeAttachment(e){this.attachedFiles=this.attachedFiles.filter(s=>s.path!==e),this.attachedImages=this.attachedImages.filter(s=>s.path!==e),this.renderPills()}renderPills(){if(this.pillsRow.empty(),this.attachedFiles.length+this.attachedImages.length===0){this.pillsRow.setCssStyles({display:"none"});return}this.pillsRow.setCssStyles({display:"flex"});for(let s of this.attachedFiles){let n=this.pillsRow.createDiv({cls:"af-chat-pill"}),a=n.createSpan({cls:"af-chat-pill-icon"});(0,q.setIcon)(a,"file-text"),n.createSpan({cls:"af-chat-pill-name",text:s.name});let r=n.createSpan({cls:"af-chat-pill-remove"});(0,q.setIcon)(r,"x"),r.onclick=o=>{o.stopPropagation(),this.removeAttachment(s.path)}}for(let s of this.attachedImages){let n=this.pillsRow.createDiv({cls:"af-chat-pill"}),a=n.createSpan({cls:"af-chat-pill-icon"});(0,q.setIcon)(a,"image"),n.createSpan({cls:"af-chat-pill-name",text:s.name});let r=n.createSpan({cls:"af-chat-pill-remove"});(0,q.setIcon)(r,"x"),r.onclick=o=>{o.stopPropagation(),this.removeAttachment(s.path)}}}buildAttachmentContext(){return this.buildAttachmentContextFor(this.attachedFiles,this.attachedImages)}async handleSend(){let e=this.getCurrentSession();if(!e)return;let s=this.textarea.value.trim();if(!s)return;let n=await this.buildAttachmentContext(),a=[...this.attachedFiles.map(u=>u.name),...this.attachedImages.map(u=>u.name)];this.textarea.value="",this.textarea.setCssStyles({height:"auto"}),this.sendBtn.setCssStyles({display:"none"}),this.attachedFiles=[],this.attachedImages=[],this.renderPills();let r=n?`${n}${s}`:void 0;if(this.addBubble("user",s,a.length>0?a:void 0),e.session.isStreaming){e.session.injectMessage(s,r,a.length>0?a:void 0);return}let o=null,c="",l=!1,h=e.session,d=()=>this.getCurrentSession()?.session===h;try{await e.session.sendMessage(s,u=>{if(!d()){o=null,l=!1,c="";return}if(u.type==="text"){(!l||!o||!o.isConnected)&&(o=this.addBubble("assistant"),l=!0),c+=u.content;let p=o.querySelector(".af-chat-stream-text");p||(p=o.createDiv({cls:"af-chat-stream-text"})),p.setText(c)}else if(u.type==="error"){let p=u.errorMessage?.trim()||"The agent's run ended with an error.";this.addBubble("error",`Error: ${p}`)}else if(u.type!=="compacted"){if(u.type==="result"){if(l&&o&&o.isConnected){let p=cs(c);this.renderMarkdownBubble(o,p),o._setRawText?.(p);let m=e.session.messages[e.session.messages.length-1];if(m&&m.role==="assistant"&&(this.attachThreadAffordance(o,m.id,e.session),u.toolCalls&&u.toolCalls.length>0)){let f=this.getOrCreateAffordancesRow(o);this.buildToolSummary(u.toolCalls,f)}}else u.toolCalls&&u.toolCalls.length>0&&this.buildToolSummary(u.toolCalls);c="",l=!1,o=null}}},r,a.length>0?a:void 0)}catch(u){let p=u instanceof Error?u.message:String(u);p!=="Aborted"&&this.addBubble("error",`Error: ${p}`)}}async handleNewChat(){if(!this.selectedAgentName)return;let s=this.plugin.runtime.getSnapshot().agents.find(a=>a.name===this.selectedAgentName);if(!s)return;let n=Uo();try{await this.plugin.repository.createConversation(s,n,$o())}catch(a){new q.Notice(`Couldn't create conversation: ${a instanceof Error?a.message:String(a)}`);return}await this.switchToAgent(this.selectedAgentName,n)}startFreshIntro(e){let s="",n=null,a=!1;e.sendMessage("Please introduce yourself and briefly describe your capabilities and what you can help with.",r=>{r.type==="text"&&(a||(n=this.addBubble("assistant"),a=!0),s+=r.content,n.setText(s))}).then(r=>{a&&n?(this.renderMarkdownBubble(n,s),n._setRawText?.(s)):r.text.trim()&&(n=this.addBubble("assistant"),this.renderMarkdownBubble(n,r.text),n._setRawText?.(r.text)),r.toolCalls.length>0&&this.buildToolSummary(r.toolCalls),this.textarea.focus()}).catch(r=>{let o=r instanceof Error?r.message:String(r);o!=="Aborted"&&this.addBubble("error",`Error: ${o}`)})}};function ns(i){return i>=1e3?`${(i/1e3).toFixed(i>=1e4?0:1)}k`:`${i}`}var Hn=class extends Ce.Plugin{settings={...at};repository;runtime;get mcpManager(){return this.runtime.mcpManager}mcpAuth=new mn;channelCredentials=new wn;channelManager;secretStore;statusBarEl;subscribedViews=new Set;vaultChangeTimer;suppressVaultEvents=!1;suppressTimer;runtimeUnsubscribe;async onload(){await this.loadSettings(),this.settings.claudeCliPath=await this.resolveClaudeCliPath(this.settings.claudeCliPath),this.repository=new hs(this.app,this.settings),this.repository.setChannelCredentialGetter(()=>this.channelCredentials.toRecord()),this.runtime=new ws(this.repository,this.settings,this.mcpAuth),this.registerView(Ct,n=>new Is(n,this)),this.registerView(Ut,n=>new Nn(n,this)),this.registerView(nt,n=>new as(n,this)),this.addSettingTab(new Qs(this)),await this.repository.ensureFleetStructure()&&await this.repository.ensureSamples();let e=await this.repository.updateDefaults(this.settings.defaultFileHashes??{});JSON.stringify(e)!==JSON.stringify(this.settings.defaultFileHashes??{})&&(this.settings.defaultFileHashes=e,await this.saveData(this.settings)),await this.runtime.initialize(),await this.verifyClaudeCli(!1),await this.maybeResolveCodexCliPath(),this.addRibbonIcon("bot","Agent Fleet Dashboard",()=>void this.activateDashboardView()),this.addRibbonIcon("message-circle","Agent Chat",()=>{let n=this.app.workspace.getLeavesOfType(nt);n.length>0?this.app.workspace.revealLeaf(n[0]):this.openChatView()}),this.addCommands(),this.registerVaultHandlers(),this.registerRuntimeListeners();let s=this.app.secretStorage;if(this.secretStore=new pn(s),this.channelCredentials.setSecretStore(this.secretStore),this.mcpAuth.setSecretStore(this.secretStore),!this.settings.secretsMigrated&&this.secretStore.available){this.channelCredentials.loadCredentials(this.settings.channelCredentials??{});for(let[n,a]of Object.entries(this.settings.mcpApiKeys??{}))typeof a=="string"&&a.trim()&&this.mcpAuth.storeStaticToken(n,a);this.settings.mcpTokens={},this.settings.mcpApiKeys={},this.settings.channelCredentials={},this.settings.secretsMigrated=!0,await this.saveData(this.settings)}else this.channelCredentials.loadCredentials(this.secretStore.available?void 0:this.settings.channelCredentials??{});this.secretStore.available||this.channelCredentials.onChanged(n=>{this.settings.channelCredentials=n,this.saveSettings()}),this.channelManager=new vn({getRepository:()=>this.repository,vault:this.app.vault,getSettings:()=>this.settings,getChannelCredentials:()=>this.channelCredentials.toRecord(),getMcpAuth:()=>this.mcpAuth,adapterFactory:(n,a)=>{if(n.type==="slack")return new Fn(n,a);if(n.type==="telegram")return new On(n,a);throw new Error(`Channel type \`${n.type}\` is not yet supported in this version.`)}});try{await this.channelManager.start(this.runtime.getSnapshot())}catch(n){console.error("Agent Fleet: channel manager failed to start",n),new Ce.Notice("Agent Fleet: channel manager failed to start \u2014 check console.")}this.runtime.onHeartbeatResult((n,a,r)=>{this.channelManager?.broadcastToChannel(a,`*Heartbeat \u2014 ${n}* +`}async attachImageBlob(e){let s=e.type.split("/")[1]?.replace("jpeg","jpg")??"png",n=e.name&&e.name!=="image"?e.name:`pasted-${Date.now()}.${s}`;if(this.attachedImages.some(r=>r.name===n)){new z.Notice(`"${n}" is already attached`);return}let a=await this.saveImageBlobToVault(e);a&&(this.attachedImages.push(a),this.renderPills())}removeAttachment(e){this.attachedFiles=this.attachedFiles.filter(s=>s.path!==e),this.attachedImages=this.attachedImages.filter(s=>s.path!==e),this.renderPills()}renderPills(){if(this.pillsRow.empty(),this.attachedFiles.length+this.attachedImages.length===0){this.pillsRow.setCssStyles({display:"none"});return}this.pillsRow.setCssStyles({display:"flex"});for(let s of this.attachedFiles){let n=this.pillsRow.createDiv({cls:"af-chat-pill"}),a=n.createSpan({cls:"af-chat-pill-icon"});(0,z.setIcon)(a,"file-text"),n.createSpan({cls:"af-chat-pill-name",text:s.name});let r=n.createSpan({cls:"af-chat-pill-remove"});(0,z.setIcon)(r,"x"),r.onclick=o=>{o.stopPropagation(),this.removeAttachment(s.path)}}for(let s of this.attachedImages){let n=this.pillsRow.createDiv({cls:"af-chat-pill"}),a=n.createSpan({cls:"af-chat-pill-icon"});(0,z.setIcon)(a,"image"),n.createSpan({cls:"af-chat-pill-name",text:s.name});let r=n.createSpan({cls:"af-chat-pill-remove"});(0,z.setIcon)(r,"x"),r.onclick=o=>{o.stopPropagation(),this.removeAttachment(s.path)}}}buildAttachmentContext(){return this.buildAttachmentContextFor(this.attachedFiles,this.attachedImages)}async handleSend(){let e=this.getCurrentSession();if(!e)return;let s=this.textarea.value.trim();if(!s)return;let n=await this.buildAttachmentContext(),a=[...this.attachedFiles.map(u=>u.name),...this.attachedImages.map(u=>u.name)];this.textarea.value="",this.textarea.setCssStyles({height:"auto"}),this.sendBtn.setCssStyles({display:"none"}),this.attachedFiles=[],this.attachedImages=[],this.renderPills();let r=n?`${n}${s}`:void 0;if(this.addBubble("user",s,a.length>0?a:void 0),e.session.isStreaming){e.session.injectMessage(s,r,a.length>0?a:void 0);return}let o=null,c="",l=!1,h=e.session,d=()=>this.getCurrentSession()?.session===h;try{await e.session.sendMessage(s,u=>{if(!d()){o=null,l=!1,c="";return}if(u.type==="text"){(!l||!o||!o.isConnected)&&(o=this.addBubble("assistant"),l=!0),c+=u.content;let p=o.querySelector(".af-chat-stream-text");p||(p=o.createDiv({cls:"af-chat-stream-text"})),p.setText(c)}else if(u.type==="error"){let p=u.errorMessage?.trim()||"The agent's run ended with an error.";this.addBubble("error",`Error: ${p}`)}else if(u.type!=="compacted"){if(u.type==="result"){if(l&&o&&o.isConnected){let p=hs(c);this.renderMarkdownBubble(o,p),o._setRawText?.(p);let m=e.session.messages[e.session.messages.length-1];if(m&&m.role==="assistant"&&(this.attachThreadAffordance(o,m.id,e.session),u.toolCalls&&u.toolCalls.length>0)){let f=this.getOrCreateAffordancesRow(o);this.buildToolSummary(u.toolCalls,f)}}else u.toolCalls&&u.toolCalls.length>0&&this.buildToolSummary(u.toolCalls);c="",l=!1,o=null}}},r,a.length>0?a:void 0)}catch(u){let p=u instanceof Error?u.message:String(u);p!=="Aborted"&&this.addBubble("error",`Error: ${p}`)}}async handleNewChat(){if(!this.selectedAgentName)return;let s=this.plugin.runtime.getSnapshot().agents.find(a=>a.name===this.selectedAgentName);if(!s)return;let n=el();try{await this.plugin.repository.createConversation(s,n,tl())}catch(a){new z.Notice(`Couldn't create conversation: ${a instanceof Error?a.message:String(a)}`);return}await this.switchToAgent(this.selectedAgentName,n)}startFreshIntro(e){let s="",n=null,a=!1;e.sendMessage("Please introduce yourself and briefly describe your capabilities and what you can help with.",r=>{r.type==="text"&&(a||(n=this.addBubble("assistant"),a=!0),s+=r.content,n.setText(s))}).then(r=>{a&&n?(this.renderMarkdownBubble(n,s),n._setRawText?.(s)):r.text.trim()&&(n=this.addBubble("assistant"),this.renderMarkdownBubble(n,r.text),n._setRawText?.(r.text)),r.toolCalls.length>0&&this.buildToolSummary(r.toolCalls),this.textarea.focus()}).catch(r=>{let o=r instanceof Error?r.message:String(r);o!=="Aborted"&&this.addBubble("error",`Error: ${o}`)})}};function is(i){return i>=1e3?`${(i/1e3).toFixed(i>=1e4?0:1)}k`:`${i}`}var zn=class extends Se.Plugin{settings={...rt};repository;runtime;get mcpManager(){return this.runtime.mcpManager}mcpAuth=new gn;channelCredentials=new bn;channelManager;secretStore;statusBarEl;subscribedViews=new Set;vaultChangeTimer;suppressVaultEvents=!1;suppressTimer;runtimeUnsubscribe;async onload(){await this.loadSettings(),this.settings.claudeCliPath=await this.resolveClaudeCliPath(this.settings.claudeCliPath),this.repository=new ps(this.app,this.settings),this.repository.setChannelCredentialGetter(()=>this.channelCredentials.toRecord()),this.runtime=new ks(this.repository,this.settings,this.mcpAuth),this.registerView(_t,n=>new Fs(n,this)),this.registerView($t,n=>new Un(n,this)),this.registerView(it,n=>new rs(n,this)),this.addSettingTab(new en(this)),await this.repository.ensureFleetStructure()&&await this.repository.ensureSamples();let e=await this.repository.updateDefaults(this.settings.defaultFileHashes??{});JSON.stringify(e)!==JSON.stringify(this.settings.defaultFileHashes??{})&&(this.settings.defaultFileHashes=e,await this.saveData(this.settings)),await this.runtime.initialize(),await this.verifyClaudeCli(!1),await this.maybeResolveCodexCliPath(),this.addRibbonIcon("bot","Agent Fleet Dashboard",()=>void this.activateDashboardView()),this.addRibbonIcon("message-circle","Agent Chat",()=>{let n=this.app.workspace.getLeavesOfType(it);n.length>0?this.app.workspace.revealLeaf(n[0]):this.openChatView()}),this.addCommands(),this.registerVaultHandlers(),this.registerRuntimeListeners();let s=this.app.secretStorage;if(this.secretStore=new fn(s),this.channelCredentials.setSecretStore(this.secretStore),this.mcpAuth.setSecretStore(this.secretStore),!this.settings.secretsMigrated&&this.secretStore.available){this.channelCredentials.loadCredentials(this.settings.channelCredentials??{});for(let[n,a]of Object.entries(this.settings.mcpApiKeys??{}))typeof a=="string"&&a.trim()&&this.mcpAuth.storeStaticToken(n,a);this.settings.mcpTokens={},this.settings.mcpApiKeys={},this.settings.channelCredentials={},this.settings.secretsMigrated=!0,await this.saveData(this.settings)}else this.channelCredentials.loadCredentials(this.secretStore.available?void 0:this.settings.channelCredentials??{});this.secretStore.available||this.channelCredentials.onChanged(n=>{this.settings.channelCredentials=n,this.saveSettings()}),this.channelManager=new wn({getRepository:()=>this.repository,vault:this.app.vault,getSettings:()=>this.settings,getChannelCredentials:()=>this.channelCredentials.toRecord(),getMcpAuth:()=>this.mcpAuth,recordUsage:n=>this.runtime.recordUsage(n),adapterFactory:(n,a)=>{if(n.type==="slack")return new On(n,a);if(n.type==="telegram")return new Nn(n,a);if(n.type==="discord")return new Bn(n,a);throw new Error(`Channel type \`${n.type}\` is not yet supported in this version.`)}});try{await this.channelManager.start(this.runtime.getSnapshot())}catch(n){console.error("Agent Fleet: channel manager failed to start",n),new Se.Notice("Agent Fleet: channel manager failed to start \u2014 check console.")}this.runtime.onChannelResult((n,a,r,o,c)=>{let h=`*${o==="heartbeat"?`Heartbeat \u2014 ${n}`:`${n} \u2014 ${o}`}* -${r}`).catch(o=>{console.warn(`Agent Fleet: heartbeat channel post failed for ${n}`,o)})}),this.refreshStatusBar(),this.importNativeMcpServers(),this.registerInterval(window.setInterval(()=>void this.mcpManager.refreshProbeTokens(),30*6e4)),new Ce.Notice("Agent Fleet loaded.")}async importNativeMcpServers(){if(this.settings.mcpImported)return;if(this.repository.getMcpServers().length>0){this.settings.mcpImported=!0,await this.saveData(this.settings);return}let t=n=>{try{return(0,Ms.existsSync)(n)?(0,Ms.readFileSync)(n,"utf-8"):null}catch{return null}},e=t((0,za.join)((0,qa.homedir)(),".claude.json")),s=t((0,za.join)((0,qa.homedir)(),".codex","config.toml"));try{let n=yr(e?fr(e):{servers:[],tokens:{}},s?gr(s):{servers:[],tokens:{}}),a=0;for(let r of n.servers)try{await this.repository.saveMcpServer(r,r.description??"");let o=n.tokens[r.name];o&&this.mcpAuth.storeStaticToken(r.name,o),a++}catch(o){console.warn(`Agent Fleet: failed to import MCP server "${r.name}":`,o)}this.settings.mcpImported=!0,await this.saveData(this.settings),a>0&&(await this.refreshFromVault(),new Ce.Notice(`Agent Fleet: imported ${a} MCP server${a===1?"":"s"}.`))}catch(n){console.error("Agent Fleet: MCP import failed",n)}}onunload(){this.runtimeUnsubscribe?.(),this.runtimeUnsubscribe=void 0,this.vaultChangeTimer&&(window.clearTimeout(this.vaultChangeTimer),this.vaultChangeTimer=void 0),this.suppressTimer&&(window.clearTimeout(this.suppressTimer),this.suppressTimer=void 0),this.runtime?.shutdown(),this.channelManager?.stop(),Hi()}async loadSettings(){this.settings={...at,...await this.loadData()}}async saveSettings(){this.settings.claudeCliPath=await this.resolveClaudeCliPath(this.settings.claudeCliPath),await this.maybeResolveCodexCliPath(),qi(),await this.saveData(this.settings),this.repository&&this.runtime&&(this.runtime.shutdown(),this.repository=new hs(this.app,this.settings),this.repository.setChannelCredentialGetter(()=>this.channelCredentials.toRecord()),this.runtime=new ws(this.repository,this.settings,this.mcpAuth),await this.repository.ensureFleetStructure(),await this.runtime.initialize(),this.registerRuntimeListeners(),this.notifyViews(),this.refreshStatusBar(),this.channelCredentials.loadCredentials(this.secretStore.available?void 0:this.settings.channelCredentials??{}),this.channelManager?.reconcile(this.runtime.getSnapshot()))}subscribeView(t){this.subscribedViews.add(t)}unsubscribeView(t){this.subscribedViews.delete(t)}async activateDashboardView(){let t=this.app.workspace.getLeavesOfType(Ct);if(t.length>0){this.app.workspace.revealLeaf(t[0]);return}await this.app.workspace.getLeaf(!0).setViewState({type:Ct,active:!0})}async navigateDashboard(t,e){await this.activateDashboardView();let n=this.app.workspace.getLeavesOfType(Ct)[0];if(n){let a=n.view;a instanceof Is&&a.navigateTo(t,e)}}async activateAgentsView(){let t=this.getLeafForView(Ut,"left");await t.setViewState({type:Ut,active:!0}),this.app.workspace.revealLeaf(t)}async openChatView(t){if(t){let s=this.app.workspace.getLeavesOfType(nt);for(let n of s)if(n.view instanceof as&&n.view.selectedAgentName===t){this.app.workspace.revealLeaf(n);return}}let e=this.app.workspace.getRightLeaf(!1)??this.app.workspace.getLeaf(!0);await e.setViewState({type:nt,active:!0,state:t?{agentName:t}:{}}),this.app.workspace.revealLeaf(e),t&&e.view instanceof as&&e.view.selectAgent(t)}async refreshFromVault(){this.suppressVaultEvents=!0;try{await this.runtime.refreshFromVault(),this.notifyViews(),this.refreshStatusBar(),this.channelManager?.reconcile(this.runtime.getSnapshot())}finally{this.suppressTimer&&window.clearTimeout(this.suppressTimer),this.suppressTimer=window.setTimeout(()=>{this.suppressTimer=void 0,this.suppressVaultEvents=!1},500)}}refreshStatusBar(){if(!this.settings.showStatusBar){this.statusBarEl?.detach(),this.statusBarEl=void 0;return}this.statusBarEl||(this.statusBarEl=this.addStatusBarItem(),this.statusBarEl.onclick=()=>void this.activateDashboardView());let t=this.runtime.getFleetStatus();this.statusBarEl.setText(`\u{1F916} ${t.running} running \xB7 ${t.pending} pending \xB7 ${t.completedToday} completed today`)}async verifyClaudeCli(t=!0){let e=await this.resolveClaudeCliPath(this.settings.claudeCliPath);return this.settings.claudeCliPath=e,await this.verifyCliBinary(e,"Claude",t)}async verifyCodexCli(t=!0){let e=await this.resolveCliPathFrom(Xn(this.settings.codexCliPath),this.settings.codexCliPath);return this.settings.codexCliPath=e,await this.verifyCliBinary(e,"Codex",t)}async verifyCliBinary(t,e,s){return await new Promise(n=>{let a=lt(t,["--version"]),r="";a.stderr.on("data",o=>{r+=o.toString()}),a.on("close",o=>{let c=o===0;c||console.error(`Agent Fleet: ${e} CLI verification failed`,r),s&&new Ce.Notice(c?`${e} CLI available.`:`${e} CLI verification failed \u2014 check ${e} CLI Path in settings.`),n(c)}),a.on("error",o=>{console.error(`Agent Fleet: ${e} CLI verification error`,o),s&&new Ce.Notice(`${e} CLI verification failed \u2014 check ${e} CLI Path in settings.`),n(!1)})})}async openPath(t){let e=this.app.vault.getAbstractFileByPath((0,Ce.normalizePath)(t));e instanceof Ce.TFile&&await this.app.workspace.getLeaf(!0).openFile(e)}async createAgentTemplate(){await this.navigateDashboard("create-agent")}async createSkillTemplate(){await this.navigateDashboard("create-skill")}async openCreateTask(){await this.navigateDashboard("create-task")}async runAgentPrompt(t){let e=this.repository.getAgentByName(t);if(!e){new Ce.Notice(`Unknown agent: ${t}`);return}await this.runtime.runAgentNow(e,"Run now and summarize the current state.")}async chatWithAgent(t){if(!this.repository.getAgentByName(t)){new Ce.Notice(`Unknown agent: ${t}`);return}await this.openChatView(t)}async deleteAgent(t){if(!this.repository.getAgentByName(t)){new Ce.Notice(`Unknown agent: ${t}`);return}let s=this.repository.getTasksForAgent(t),n=this.runtime.getRecentRuns().filter(o=>o.agent===t),a=this.repository.getMemoryPath(t),r=!!this.app.vault.getAbstractFileByPath(a);new Vs(this.app,{agentName:t,taskCount:s.length,runCount:n.length,hasMemory:r},async o=>{let c=await this.repository.deleteAgent(t,o);await new Promise(l=>window.setTimeout(l,200)),await this.refreshFromVault(),new Ce.Notice(`Deleted agent "${t}" (${c.trashedFiles.length} files moved to trash)`),await this.navigateDashboard("agents")}).open()}async toggleAgent(t,e){let s=this.repository.getAgentByName(t);if(!s)return;let n=this.app.vault.getAbstractFileByPath(s.filePath);if(!(n instanceof Ce.TFile))return;let a=await this.app.vault.cachedRead(n),{frontmatter:r,body:o}=J(a);r.enabled=e,await this.app.vault.modify(n,W(r,o)),await this.refreshFromVault()}addCommands(){this.addCommand({id:"open-dashboard",name:"Open dashboard",callback:()=>void this.activateDashboardView()}),this.addCommand({id:"open-agents-panel",name:"Open agents panel",callback:()=>void this.activateAgentsView()}),this.addCommand({id:"open-chat",name:"Open agent chat",callback:()=>{let t=this.app.workspace.getLeavesOfType(nt);t.length>0?this.app.workspace.revealLeaf(t[0]):this.openChatView()}}),this.addCommand({id:"new-chat-tab",name:"New chat tab",callback:()=>void this.openChatView()}),this.addCommand({id:"new-agent",name:"New agent",callback:()=>void this.createAgentTemplate()}),this.addCommand({id:"new-skill",name:"New skill",callback:()=>void this.createSkillTemplate()}),this.addCommand({id:"new-task",name:"New task",callback:()=>void this.openCreateTask()}),this.addCommand({id:"run-agent-now",name:"Run agent now",callback:()=>{let t=this.runtime.getSnapshot().agents[0];t?this.runAgentPrompt(t.name):new Ce.Notice("No agents configured.")}}),this.addCommand({id:"pause-all",name:"Pause all",callback:()=>{this.runtime.scheduler.pauseAll(),new Ce.Notice("Agent Fleet paused.")}}),this.addCommand({id:"resume-all",name:"Resume all",callback:()=>{this.runtime.scheduler.resumeAll(),new Ce.Notice("Agent Fleet resumed.")}}),this.addCommand({id:"view-fleet-status",name:"View status",callback:()=>{let t=this.runtime.getFleetStatus();new Ce.Notice(`${t.running} running \xB7 ${t.pending} pending \xB7 ${t.completedToday} completed today`)}})}debouncedVaultRefresh(){this.suppressVaultEvents||(this.vaultChangeTimer&&window.clearTimeout(this.vaultChangeTimer),this.vaultChangeTimer=window.setTimeout(()=>{this.suppressVaultEvents||this.refreshFromVault()},500))}registerVaultHandlers(){this.registerEvent(this.app.vault.on("create",t=>{t instanceof Ce.TFile&&t.path.startsWith(`${this.settings.fleetFolder}/`)&&this.debouncedVaultRefresh()})),this.registerEvent(this.app.vault.on("modify",t=>{t instanceof Ce.TFile&&t.path.startsWith(`${this.settings.fleetFolder}/`)&&this.debouncedVaultRefresh()})),this.registerEvent(this.app.vault.on("rename",t=>{t.path.startsWith(`${this.settings.fleetFolder}/`)&&this.debouncedVaultRefresh()})),this.registerEvent(this.app.vault.on("delete",t=>{t.path.startsWith(`${this.settings.fleetFolder}/`)&&this.debouncedVaultRefresh()}))}registerRuntimeListeners(){this.runtimeUnsubscribe?.(),this.runtimeUnsubscribe=this.runtime.subscribe(()=>{this.notifyViews(),this.refreshStatusBar()})}notifyViews(){for(let t of this.subscribedViews)t.render()}async resolveClaudeCliPath(t){return this.resolveCliPathFrom(gi(t),t)}async maybeResolveCodexCliPath(){this.runtime?.getSnapshot().agents.some(e=>wt(e.adapter)==="codex")&&(this.settings.codexCliPath=await this.resolveCliPathFrom(Xn(this.settings.codexCliPath),this.settings.codexCliPath))}async resolveCliPathFrom(t,e){for(let s of t)if(Qn(s)&&(0,Ms.existsSync)(s)||!Qn(s)&&await new Promise(a=>{let r=lt(s,["--version"]);r.on("close",o=>a(o===0)),r.on("error",()=>a(!1))}))return s;return e}getLeafForView(t,e){let s=this.app.workspace.getLeavesOfType(t)[0];return s||(e==="right"?this.app.workspace.getRightLeaf(!1)??this.app.workspace.getLeaf(!0):this.app.workspace.getLeftLeaf(!1)??this.app.workspace.getLeaf(!1))}}; +${r}`;(c?this.channelManager?.postToChannelTarget(a,c,h):this.channelManager?.broadcastToChannel(a,h))?.catch(u=>{console.warn(`Agent Fleet: channel post failed for ${n}`,u)})}),this.refreshStatusBar(),this.importNativeMcpServers(),this.registerInterval(window.setInterval(()=>void this.mcpManager.refreshProbeTokens(),30*6e4)),new Se.Notice("Agent Fleet loaded.")}async importNativeMcpServers(){if(this.settings.mcpImported)return;if(this.repository.getMcpServers().length>0){this.settings.mcpImported=!0,await this.saveData(this.settings);return}let t=n=>{try{return(0,Os.existsSync)(n)?(0,Os.readFileSync)(n,"utf-8"):null}catch{return null}},e=t((0,Qa.join)((0,Xa.homedir)(),".claude.json")),s=t((0,Qa.join)((0,Xa.homedir)(),".codex","config.toml"));try{let n=Tr(e?Sr(e):{servers:[],tokens:{}},s?Cr(s):{servers:[],tokens:{}}),a=0;for(let r of n.servers)try{await this.repository.saveMcpServer(r,r.description??"");let o=n.tokens[r.name];o&&this.mcpAuth.storeStaticToken(r.name,o),a++}catch(o){console.warn(`Agent Fleet: failed to import MCP server "${r.name}":`,o)}this.settings.mcpImported=!0,await this.saveData(this.settings),a>0&&(await this.refreshFromVault(),new Se.Notice(`Agent Fleet: imported ${a} MCP server${a===1?"":"s"}.`))}catch(n){console.error("Agent Fleet: MCP import failed",n)}}onunload(){this.runtimeUnsubscribe?.(),this.runtimeUnsubscribe=void 0,this.vaultChangeTimer&&(window.clearTimeout(this.vaultChangeTimer),this.vaultChangeTimer=void 0),this.suppressTimer&&(window.clearTimeout(this.suppressTimer),this.suppressTimer=void 0),this.runtime?.shutdown(),this.channelManager?.stop(),Qi()}async loadSettings(){this.settings={...rt,...await this.loadData()}}async saveSettings(){this.settings.claudeCliPath=await this.resolveClaudeCliPath(this.settings.claudeCliPath),await this.maybeResolveCodexCliPath(),Zi(),await this.saveData(this.settings),this.repository&&this.runtime&&(this.runtime.shutdown(),this.repository=new ps(this.app,this.settings),this.repository.setChannelCredentialGetter(()=>this.channelCredentials.toRecord()),this.runtime=new ks(this.repository,this.settings,this.mcpAuth),await this.repository.ensureFleetStructure(),await this.runtime.initialize(),this.registerRuntimeListeners(),this.notifyViews(),this.refreshStatusBar(),this.channelCredentials.loadCredentials(this.secretStore.available?void 0:this.settings.channelCredentials??{}),this.channelManager?.reconcile(this.runtime.getSnapshot()))}subscribeView(t){this.subscribedViews.add(t)}unsubscribeView(t){this.subscribedViews.delete(t)}async activateDashboardView(){let t=this.app.workspace.getLeavesOfType(_t);if(t.length>0){this.app.workspace.revealLeaf(t[0]);return}await this.app.workspace.getLeaf(!0).setViewState({type:_t,active:!0})}async navigateDashboard(t,e){await this.activateDashboardView();let n=this.app.workspace.getLeavesOfType(_t)[0];if(n){let a=n.view;a instanceof Fs&&a.navigateTo(t,e)}}async activateAgentsView(){let t=this.getLeafForView($t,"left");await t.setViewState({type:$t,active:!0}),this.app.workspace.revealLeaf(t)}async openChatView(t){if(t){let s=this.app.workspace.getLeavesOfType(it);for(let n of s)if(n.view instanceof rs&&n.view.selectedAgentName===t){this.app.workspace.revealLeaf(n);return}}let e=this.app.workspace.getRightLeaf(!1)??this.app.workspace.getLeaf(!0);await e.setViewState({type:it,active:!0,state:t?{agentName:t}:{}}),this.app.workspace.revealLeaf(e),t&&e.view instanceof rs&&e.view.selectAgent(t)}async refreshFromVault(){this.suppressVaultEvents=!0;try{await this.runtime.refreshFromVault(),this.notifyViews(),this.refreshStatusBar(),this.channelManager?.reconcile(this.runtime.getSnapshot())}finally{this.suppressTimer&&window.clearTimeout(this.suppressTimer),this.suppressTimer=window.setTimeout(()=>{this.suppressTimer=void 0,this.suppressVaultEvents=!1},500)}}refreshStatusBar(){if(!this.settings.showStatusBar){this.statusBarEl?.detach(),this.statusBarEl=void 0;return}this.statusBarEl||(this.statusBarEl=this.addStatusBarItem(),this.statusBarEl.onclick=()=>void this.activateDashboardView());let t=this.runtime.getFleetStatus();this.statusBarEl.setText(`\u{1F916} ${t.running} running \xB7 ${t.pending} pending \xB7 ${t.completedToday} completed today`)}async verifyClaudeCli(t=!0){let e=await this.resolveClaudeCliPath(this.settings.claudeCliPath);return this.settings.claudeCliPath=e,await this.verifyCliBinary(e,"Claude",t)}async verifyCodexCli(t=!0){let e=await this.resolveCliPathFrom(ta(this.settings.codexCliPath),this.settings.codexCliPath);return this.settings.codexCliPath=e,await this.verifyCliBinary(e,"Codex",t)}async verifyCliBinary(t,e,s){return await new Promise(n=>{let a=dt(t,["--version"]),r="";a.stderr.on("data",o=>{r+=o.toString()}),a.on("close",o=>{let c=o===0;c||console.error(`Agent Fleet: ${e} CLI verification failed`,r),s&&new Se.Notice(c?`${e} CLI available.`:`${e} CLI verification failed \u2014 check ${e} CLI Path in settings.`),n(c)}),a.on("error",o=>{console.error(`Agent Fleet: ${e} CLI verification error`,o),s&&new Se.Notice(`${e} CLI verification failed \u2014 check ${e} CLI Path in settings.`),n(!1)})})}async openPath(t){let e=this.app.vault.getAbstractFileByPath((0,Se.normalizePath)(t));e instanceof Se.TFile&&await this.app.workspace.getLeaf(!0).openFile(e)}async createAgentTemplate(){await this.navigateDashboard("create-agent")}async createSkillTemplate(){await this.navigateDashboard("create-skill")}async openCreateTask(){await this.navigateDashboard("create-task")}async runAgentPrompt(t){let e=this.repository.getAgentByName(t);if(!e){new Se.Notice(`Unknown agent: ${t}`);return}await this.runtime.runAgentNow(e,"Run now and summarize the current state.")}async chatWithAgent(t){if(!this.repository.getAgentByName(t)){new Se.Notice(`Unknown agent: ${t}`);return}await this.openChatView(t)}async deleteAgent(t){if(!this.repository.getAgentByName(t)){new Se.Notice(`Unknown agent: ${t}`);return}let s=this.repository.getTasksForAgent(t),n=this.runtime.getRecentRuns().filter(o=>o.agent===t),a=this.repository.getMemoryPath(t),r=!!this.app.vault.getAbstractFileByPath(a);new Ks(this.app,{agentName:t,taskCount:s.length,runCount:n.length,hasMemory:r},async o=>{let c=await this.repository.deleteAgent(t,o);await new Promise(l=>window.setTimeout(l,200)),await this.refreshFromVault(),new Se.Notice(`Deleted agent "${t}" (${c.trashedFiles.length} files moved to trash)`),await this.navigateDashboard("agents")}).open()}async toggleAgent(t,e){let s=this.repository.getAgentByName(t);if(!s)return;let n=this.app.vault.getAbstractFileByPath(s.filePath);if(!(n instanceof Se.TFile))return;let a=await this.app.vault.cachedRead(n),{frontmatter:r,body:o}=J(a);r.enabled=e,await this.app.vault.modify(n,H(r,o)),await this.refreshFromVault()}addCommands(){this.addCommand({id:"open-dashboard",name:"Open dashboard",callback:()=>void this.activateDashboardView()}),this.addCommand({id:"open-agents-panel",name:"Open agents panel",callback:()=>void this.activateAgentsView()}),this.addCommand({id:"open-chat",name:"Open agent chat",callback:()=>{let t=this.app.workspace.getLeavesOfType(it);t.length>0?this.app.workspace.revealLeaf(t[0]):this.openChatView()}}),this.addCommand({id:"new-chat-tab",name:"New chat tab",callback:()=>void this.openChatView()}),this.addCommand({id:"new-agent",name:"New agent",callback:()=>void this.createAgentTemplate()}),this.addCommand({id:"new-skill",name:"New skill",callback:()=>void this.createSkillTemplate()}),this.addCommand({id:"new-task",name:"New task",callback:()=>void this.openCreateTask()}),this.addCommand({id:"run-agent-now",name:"Run agent now",callback:()=>{let t=this.runtime.getSnapshot().agents[0];t?this.runAgentPrompt(t.name):new Se.Notice("No agents configured.")}}),this.addCommand({id:"pause-all",name:"Pause all",callback:()=>{this.runtime.scheduler.pauseAll(),new Se.Notice("Agent Fleet paused.")}}),this.addCommand({id:"resume-all",name:"Resume all",callback:()=>{this.runtime.scheduler.resumeAll(),new Se.Notice("Agent Fleet resumed.")}}),this.addCommand({id:"view-fleet-status",name:"View status",callback:()=>{let t=this.runtime.getFleetStatus();new Se.Notice(`${t.running} running \xB7 ${t.pending} pending \xB7 ${t.completedToday} completed today`)}})}debouncedVaultRefresh(){this.suppressVaultEvents||(this.vaultChangeTimer&&window.clearTimeout(this.vaultChangeTimer),this.vaultChangeTimer=window.setTimeout(()=>{this.suppressVaultEvents||this.refreshFromVault()},500))}registerVaultHandlers(){let t=e=>e.startsWith(`${this.settings.fleetFolder}/usage/`);this.registerEvent(this.app.vault.on("create",e=>{e instanceof Se.TFile&&e.path.startsWith(`${this.settings.fleetFolder}/`)&&!t(e.path)&&this.debouncedVaultRefresh()})),this.registerEvent(this.app.vault.on("modify",e=>{e instanceof Se.TFile&&e.path.startsWith(`${this.settings.fleetFolder}/`)&&!t(e.path)&&this.debouncedVaultRefresh()})),this.registerEvent(this.app.vault.on("rename",e=>{e.path.startsWith(`${this.settings.fleetFolder}/`)&&this.debouncedVaultRefresh()})),this.registerEvent(this.app.vault.on("delete",e=>{e.path.startsWith(`${this.settings.fleetFolder}/`)&&this.debouncedVaultRefresh()}))}registerRuntimeListeners(){this.runtimeUnsubscribe?.(),this.runtimeUnsubscribe=this.runtime.subscribe(()=>{this.notifyViews(),this.refreshStatusBar()})}notifyViews(){for(let t of this.subscribedViews)t.render()}async resolveClaudeCliPath(t){return this.resolveCliPathFrom(Ti(t),t)}async maybeResolveCodexCliPath(){this.runtime?.getSnapshot().agents.some(e=>kt(e.adapter)==="codex")&&(this.settings.codexCliPath=await this.resolveCliPathFrom(ta(this.settings.codexCliPath),this.settings.codexCliPath))}async resolveCliPathFrom(t,e){for(let s of t)if(sa(s)&&(0,Os.existsSync)(s)||!sa(s)&&await new Promise(a=>{let r=dt(s,["--version"]);r.on("close",o=>a(o===0)),r.on("error",()=>a(!1))}))return s;return e}getLeafForView(t,e){let s=this.app.workspace.getLeavesOfType(t)[0];return s||(e==="right"?this.app.workspace.getRightLeaf(!1)??this.app.workspace.getLeaf(!0):this.app.workspace.getLeftLeaf(!1)??this.app.workspace.getLeaf(!1))}}; diff --git a/manifest.json b/manifest.json index f1dbcc9..b5133ee 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "agent-fleet", "name": "Agent Fleet", - "version": "0.13.6", + "version": "0.14.0", "minAppVersion": "1.11.4", "description": "File-backed AI agents with task scheduling, channels, memory, and MCP — running on Claude Code or OpenAI Codex, all as plain markdown.", "author": "Denis Berekchiyan", diff --git a/package-lock.json b/package-lock.json index f6d5a76..db7dd48 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "obsidian-agent-fleet", - "version": "0.13.4", + "version": "0.13.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "obsidian-agent-fleet", - "version": "0.13.4", + "version": "0.13.6", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 8f52ac8..128f56e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "obsidian-agent-fleet", - "version": "0.13.6", - "description": "Obsidian plugin for file-backed AI agents, task scheduling, channels (Slack), heartbeat, and interactive chat.", + "version": "0.14.0", + "description": "Obsidian plugin for file-backed AI agents, task scheduling, channels (Slack, Telegram, Discord), heartbeat, and interactive chat.", "license": "MIT", "main": "plugin/main.js", "bin": { @@ -14,6 +14,7 @@ "plugin/styles.css", "README.md", "SLACK_SETUP.md", + "DISCORD_SETUP.md", "LICENSE", "screenshot.png" ], diff --git a/src/adapters/claudeCodeAdapter.test.ts b/src/adapters/claudeCodeAdapter.test.ts index f5950d8..17555a5 100644 --- a/src/adapters/claudeCodeAdapter.test.ts +++ b/src/adapters/claudeCodeAdapter.test.ts @@ -33,6 +33,7 @@ function makeAgent(overrides: Partial = {}): AgentConfig { heartbeatBody: "", heartbeatNotify: false, heartbeatChannel: "", + heartbeatChannelTarget: "", ...overrides, }; } diff --git a/src/adapters/codexAdapter.test.ts b/src/adapters/codexAdapter.test.ts index 7660a4b..07c5712 100644 --- a/src/adapters/codexAdapter.test.ts +++ b/src/adapters/codexAdapter.test.ts @@ -41,6 +41,7 @@ function makeAgent(overrides: Partial = {}): AgentConfig { heartbeatBody: "", heartbeatNotify: false, heartbeatChannel: "", + heartbeatChannelTarget: "", ...overrides, }; } diff --git a/src/adapters/codexPermissions.test.ts b/src/adapters/codexPermissions.test.ts index c0597f6..2d6d4bb 100644 --- a/src/adapters/codexPermissions.test.ts +++ b/src/adapters/codexPermissions.test.ts @@ -39,6 +39,7 @@ function makeAgent(overrides: Partial = {}): AgentConfig { heartbeatBody: "", heartbeatNotify: false, heartbeatChannel: "", + heartbeatChannelTarget: "", ...overrides, }; } diff --git a/src/constants.ts b/src/constants.ts index 367341a..84d46a9 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -27,7 +27,7 @@ export const DEFAULT_SETTINGS: FleetSettings = { defaultFileHashes: {}, }; -export const FLEET_SUBFOLDERS = ["agents", "skills", "tasks", "runs", "memory", "channels", "mcp"] as const; +export const FLEET_SUBFOLDERS = ["agents", "skills", "tasks", "runs", "memory", "channels", "mcp", "usage"] as const; // ─── Memory v2 defaults (see MEMORY_EVOLUTION_DESIGN.md) ─── /** Steady-state token budget for an agent's injected working memory. */ diff --git a/src/defaults.ts b/src/defaults.ts index 8315f18..d503e74 100644 --- a/src/defaults.ts +++ b/src/defaults.ts @@ -545,7 +545,7 @@ These are inherited by all agent processes. Never store tokens in vault files. | Defined in | HEARTBEAT.md in agent folder | _fleet/tasks/.md | | Prompt source | Heartbeat body | Task body | | "Run Now" button | Uses heartbeat instruction | Uses task prompt | -| Delivery | Optional Slack channel post | Run log only | +| Delivery | Run log + optional channel post (\`channel\` in HEARTBEAT.md) | Run log + optional channel post (\`channel\` in task frontmatter) | | Scope | One per agent | Many per agent | | Best for | Autonomous periodic monitoring | Specific scheduled work items | @@ -756,6 +756,9 @@ enabled: true schedule: "0 */6 * * *" # Cron expression — every 6 hours notify: true # Show Obsidian notice on completion channel: my-slack # Post results to this channel (optional) +channel_target: "" # Specific channel/conversation id within \`channel\` to post to + # (e.g. a Discord channel id). Empty = broadcast as a DM to the + # channel's first allowed user. Quote numeric ids to preserve precision. --- Check the following endpoints for availability and response time: @@ -771,6 +774,8 @@ a one-line "all clear". Use [REMEMBER] to track trends across heartbeats. - \`schedule\` — cron expression (same format as tasks) - \`notify\` — show Obsidian notice when heartbeat completes (default true) - \`channel\` — name of a configured channel to post results to (optional) +- \`channel_target\` — specific channel/conversation id within \`channel\` to post to (optional; + empty broadcasts a DM to the channel's first allowed user). Mirrors a task's \`channel_target\`. ## Creating a Channel @@ -845,6 +850,12 @@ model: "" # Override agent model for this task only. # Use aliases like "haiku" for cheap/simple tasks, # or leave empty to inherit agent's model. # Resolution order: task.model → agent.model → settings.defaultModel. +channel: "" # Post this task's output to a channel (e.g. "my-discord"). + # Empty = run log only. Lets scheduled tasks deliver to chat, + # not just the heartbeat. +channel_target: "" # Optional destination id within \`channel\`: a Discord/Slack + # channel id or Telegram chat id. Set = post directly to that + # channel; empty = DM the channel's first allowed user. tags: - monitoring --- @@ -853,6 +864,23 @@ Task prompt goes here. This is what the agent should do each run. Be specific and clear about expected output. \`\`\` +**Channel delivery (\`channel\` + \`channel_target\`).** Any task can post its output to a +configured channel by setting \`channel:\` to a channel name. Previously only an agent's +heartbeat could post to a channel; now every scheduled/manual task can too. The output +is the task run's **full** output text, prefixed with \`**\`. + +Two delivery modes: +- **\`channel_target\` empty** → broadcast (a DM to the channel's first allowed user) — + same path the heartbeat uses. +- **\`channel_target\` set** → post directly to that specific channel/conversation. The + target is a transport-native id: a Discord/Slack channel id or a Telegram chat id. + (Discord: enable Developer Mode → right-click the channel → Copy Channel ID. The bot + must have permission to post there.) + +Keep the task prompt's output concise/skimmable if it's going to chat. Heartbeats still +use \`channel\` in HEARTBEAT.md (broadcast only); tasks use \`channel\`/\`channel_target\` in +their own frontmatter. + ### Task Types - **recurring** — runs on a cron schedule. Requires \`schedule\` field. - **once** — runs at a specific time. Requires \`run_at\` field (ISO datetime). @@ -1787,7 +1815,7 @@ For smooth animation: \`\`\`javascript // Color utilities function hexToRgb(hex) { - const result = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex); + const result = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})\$/i.exec(hex); return result ? { r: parseInt(result[1], 16), g: parseInt(result[2], 16), @@ -2876,7 +2904,7 @@ Top-level fields (\`id\`, \`display_name\`, \`max_input_tokens\`, \`max_tokens\` \`\`\`bash curl https://api.anthropic.com/v1/models/claude-opus-4-6 \\ - -H "x-api-key: $ANTHROPIC_API_KEY" \\ + -H "x-api-key: \$ANTHROPIC_API_KEY" \\ -H "anthropic-version: 2023-06-01" \`\`\` @@ -3090,7 +3118,7 @@ The response \`usage\` object reports cache activity: If \`cache_read_input_tokens\` is zero across repeated requests with identical prefixes, a silent invalidator is at work — diff the rendered prompt bytes between two requests to find it. -Language-specific access: \`response.usage.cache_read_input_tokens\` (Python/TS/Ruby), \`$message->usage->cacheReadInputTokens\` (PHP), \`resp.Usage.CacheReadInputTokens\` (Go/C#), \`.usage().cacheReadInputTokens()\` (Java). +Language-specific access: \`response.usage.cache_read_input_tokens\` (Python/TS/Ruby), \`\$message->usage->cacheReadInputTokens\` (PHP), \`resp.Usage.CacheReadInputTokens\` (Go/C#), \`.usage().cacheReadInputTokens()\` (Java). --- @@ -3204,7 +3232,7 @@ The code execution tool lets Claude run code in a secure, sandboxed container. U - No internet access (fully sandboxed) - Python 3.11 with data science libraries pre-installed - Containers persist for 30 days and can be reused across requests -- Free when used with web search/web fetch tools; otherwise $0.05/hour after 1,550 free hours/month per organization +- Free when used with web search/web fetch tools; otherwise \$0.05/hour after 1,550 free hours/month per organization ### Tool Definition @@ -3363,7 +3391,7 @@ Two features are available: **Supported:** - Basic types: object, array, string, integer, number, boolean, null -- \`enum\`, \`const\`, \`anyOf\`, \`allOf\`, \`$ref\`/\`$def\` +- \`enum\`, \`const\`, \`anyOf\`, \`allOf\`, \`\$ref\`/\`\$def\` - String formats: \`date-time\`, \`time\`, \`date\`, \`duration\`, \`email\`, \`hostname\`, \`uri\`, \`ipv4\`, \`ipv6\`, \`uuid\` - \`additionalProperties: false\` (required for all objects) @@ -3544,11 +3572,11 @@ Everything goes through \`POST /v1/messages\`. Tools and output constraints are ## Current Models (cached: 2026-02-17) -| Model | Model ID | Context | Input $/1M | Output $/1M | +| Model | Model ID | Context | Input \$/1M | Output \$/1M | | ----------------- | ------------------- | -------------- | ---------- | ----------- | -| Claude Opus 4.6 | \`claude-opus-4-6\` | 200K (1M beta) | $5.00 | $25.00 | -| Claude Sonnet 4.6 | \`claude-sonnet-4-6\` | 200K (1M beta) | $3.00 | $15.00 | -| Claude Haiku 4.5 | \`claude-haiku-4-5\` | 200K | $1.00 | $5.00 | +| Claude Opus 4.6 | \`claude-opus-4-6\` | 200K (1M beta) | \$5.00 | \$25.00 | +| Claude Sonnet 4.6 | \`claude-sonnet-4-6\` | 200K (1M beta) | \$3.00 | \$15.00 | +| Claude Haiku 4.5 | \`claude-haiku-4-5\` | 200K | \$1.00 | \$5.00 | **ALWAYS use \`claude-opus-4-6\` unless the user explicitly names a different model.** This is non-negotiable. Do not use \`claude-sonnet-4-6\`, \`claude-sonnet-4-5\`, or any other model unless the user literally says "use sonnet" or "use haiku". Never downgrade for cost — that's the user's decision, not yours. @@ -5701,7 +5729,7 @@ server.registerResource( mimeType: "text/plain" }, async (uri: string) => { - const match = uri.match(/^file:\\/\\/documents\\/(.+)$/); + const match = uri.match(/^file:\\/\\/documents\\/(.+)\$/); const documentName = match[1]; const content = await loadDocument(documentName); return { contents: [{ uri, mimeType: "text/plain", text: content }] }; @@ -5853,7 +5881,7 @@ from pydantic import BaseModel, Field, field_validator, ConfigDict class CreateUserInput(BaseModel): model_config = ConfigDict(str_strip_whitespace=True, validate_assignment=True) name: str = Field(..., description="User's full name", min_length=1, max_length=100) - email: str = Field(..., description="User's email", pattern=r'^[\\w\\.-]+@[\\w\\.-]+\\.\\w+$') + email: str = Field(..., description="User's email", pattern=r'^[\\w\\.-]+@[\\w\\.-]+\\.\\w+\$') @field_validator('email') @classmethod @@ -6499,7 +6527,7 @@ Example evaluation file demonstrating the XML format for evaluation questions: \`\`\`xml - Calculate the compound interest on $10,000 invested at 5% annual interest rate, compounded monthly for 3 years. What is the final amount in dollars (rounded to 2 decimal places)? + Calculate the compound interest on \$10,000 invested at 5% annual interest rate, compounded monthly for 3 years. What is the final amount in dollars (rounded to 2 decimal places)? 11614.72 @@ -9289,7 +9317,7 @@ Put each with_skill version before its baseline counterpart. --skill-name "my-skill" \\ --benchmark /iteration-N/benchmark.json \\ > /dev/null 2>&1 & - VIEWER_PID=$! + VIEWER_PID=\$! \`\`\` For iteration 2+, also pass \`--previous-workspace /iteration-\`. @@ -9333,7 +9361,7 @@ Empty feedback means the user thought it was fine. Focus your improvements on th Kill the viewer server when you're done with it: \`\`\`bash -kill $VIEWER_PID 2>/dev/null +kill \$VIEWER_PID 2>/dev/null \`\`\` --- @@ -12232,8 +12260,8 @@ Unless otherwise stated by the user or existing template #### Required Format Rules - **Years**: Format as text strings (e.g., "2024" not "2,024") -- **Currency**: Use $#,##0 format; ALWAYS specify units in headers ("Revenue ($mm)") -- **Zeros**: Use number formatting to make all zeros "-", including percentages (e.g., "$#,##0;($#,##0);-") +- **Currency**: Use \$#,##0 format; ALWAYS specify units in headers ("Revenue (\$mm)") +- **Zeros**: Use number formatting to make all zeros "-", including percentages (e.g., "\$#,##0;(\$#,##0);-") - **Percentages**: Default to 0.0% format (one decimal) - **Multiples**: Format as 0.0x for valuation multiples (EV/EBITDA, P/E) - **Negative numbers**: Use parentheses (123) not minus -123 @@ -12243,7 +12271,7 @@ Unless otherwise stated by the user or existing template #### Assumptions Placement - Place ALL assumptions (growth rates, margins, multiples, etc.) in separate assumption cells - Use cell references instead of hardcoded values in formulas -- Example: Use =B5*(1+$B$6) instead of =B5*1.05 +- Example: Use =B5*(1+\$B\$6) instead of =B5*1.05 #### Formula Error Prevention - Verify all cell references are correct diff --git a/src/fleetRepository.ts b/src/fleetRepository.ts index 6406dbe..e68fc94 100644 --- a/src/fleetRepository.ts +++ b/src/fleetRepository.ts @@ -32,6 +32,7 @@ import type { WorkingMemory, SkillConfig, TaskConfig, + UsageRecord, ValidationIssue, } from "./types"; @@ -553,6 +554,7 @@ export class FleetRepository { let heartbeatBody = ""; let heartbeatNotify = true; let heartbeatChannel = ""; + let heartbeatChannelTarget = ""; const heartbeatPath = normalizePath(`${folderPath}/HEARTBEAT.md`); const heartbeatFile = this.vault.getAbstractFileByPath(heartbeatPath); if (heartbeatFile instanceof TFile) { @@ -562,6 +564,7 @@ export class FleetRepository { heartbeatSchedule = asString(parsed.frontmatter.schedule) ?? ""; heartbeatNotify = asBoolean(parsed.frontmatter.notify, true); heartbeatChannel = asString(parsed.frontmatter.channel) ?? ""; + heartbeatChannelTarget = asString(parsed.frontmatter.channel_target) ?? ""; heartbeatBody = parsed.body; } @@ -616,6 +619,7 @@ export class FleetRepository { heartbeatBody, heartbeatNotify, heartbeatChannel, + heartbeatChannelTarget, wikiKeeper: this.parseWikiKeeperConfig(configFm.wiki_keeper ?? agentFm.wiki_keeper), wikiReferences: this.parseWikiReferences(configFm.wiki_references ?? agentFm.wiki_references), }; @@ -1311,6 +1315,60 @@ export class FleetRepository { return parsed.sort((a, b) => b.started.localeCompare(a.started)); } + // ═══════════════════════════════════════════════════════ + // Usage ledger (chat/channel token+cost) — _fleet/usage/YYYY-MM-DD.jsonl + // ═══════════════════════════════════════════════════════ + + private usageLedgerPath(ts: string): string { + return normalizePath(`${this.getSubfolder("usage")}/${ts.slice(0, 10)}.jsonl`); + } + + /** Append one usage record to the day's JSONL ledger (one line per turn). + * Uses the raw adapter so it doesn't go through the markdown pipeline. */ + async appendUsage(record: UsageRecord): Promise { + await this.ensureFolder(this.getSubfolder("usage")); + const path = this.usageLedgerPath(record.ts); + const line = `${JSON.stringify(record)}\n`; + const adapter = this.vault.adapter; + if (await adapter.exists(path)) { + await adapter.append(path, line); + } else { + await adapter.write(path, line); + } + } + + /** Read all usage records on or after `sinceDate` (by ledger file date). */ + async readUsageSince(sinceDate: Date): Promise { + const dir = this.getSubfolder("usage"); + const adapter = this.vault.adapter; + if (!(await adapter.exists(dir))) return []; + const sinceStr = `${sinceDate.getFullYear()}-${String(sinceDate.getMonth() + 1).padStart(2, "0")}-${String(sinceDate.getDate()).padStart(2, "0")}`; + const out: UsageRecord[] = []; + const listing = await adapter.list(dir); + for (const filePath of listing.files) { + if (!filePath.endsWith(".jsonl")) continue; + const base = (filePath.split("/").pop() ?? "").replace(/\.jsonl$/, ""); + // Files are named YYYY-MM-DD; lexicographic >= matches calendar >=. + if (base < sinceStr) continue; + let content: string; + try { + content = await adapter.read(filePath); + } catch { + continue; + } + for (const raw of content.split("\n")) { + const trimmed = raw.trim(); + if (!trimmed) continue; + try { + out.push(JSON.parse(trimmed) as UsageRecord); + } catch { + // skip a corrupt line rather than failing the whole read + } + } + } + return out; + } + async readRunLog(file: TFile): Promise { const content = await this.vault.cachedRead(file); const { frontmatter, body } = parseMarkdownWithFrontmatter>(content); @@ -1776,6 +1834,8 @@ export class FleetRepository { catch_up?: boolean; effort?: string; model?: string; + channel?: string; + channelTarget?: string; tags?: string[]; body?: string; }): Promise { @@ -1796,6 +1856,8 @@ export class FleetRepository { if (updates.catch_up !== undefined) frontmatter.catch_up = updates.catch_up; if (updates.effort !== undefined) frontmatter.effort = updates.effort || undefined; if (updates.model !== undefined) frontmatter.model = updates.model || undefined; + if (updates.channel !== undefined) frontmatter.channel = updates.channel || undefined; + if (updates.channelTarget !== undefined) frontmatter.channel_target = updates.channelTarget || undefined; if (updates.tags !== undefined) frontmatter.tags = updates.tags; const newBody = updates.body !== undefined ? updates.body : body; await this.vault.modify(file, stringifyMarkdownWithFrontmatter(frontmatter, newBody)); @@ -2024,6 +2086,7 @@ export class FleetRepository { schedule?: string; notify?: boolean; channel?: string; + channelTarget?: string; body?: string; }): Promise { const agent = this.getAgentByName(agentName); @@ -2041,6 +2104,7 @@ export class FleetRepository { if (updates.schedule !== undefined) frontmatter.schedule = updates.schedule || undefined; if (updates.notify !== undefined) frontmatter.notify = updates.notify; if (updates.channel !== undefined) frontmatter.channel = updates.channel || undefined; + if (updates.channelTarget !== undefined) frontmatter.channel_target = updates.channelTarget || undefined; const newBody = updates.body !== undefined ? updates.body : body; await this.vault.modify(file, stringifyMarkdownWithFrontmatter(frontmatter, newBody)); } else { @@ -2051,6 +2115,7 @@ export class FleetRepository { if (updates.schedule) frontmatter.schedule = updates.schedule; if (updates.notify !== undefined) frontmatter.notify = updates.notify; if (updates.channel) frontmatter.channel = updates.channel; + if (updates.channelTarget) frontmatter.channel_target = updates.channelTarget; const body = updates.body ?? ""; await this.vault.create( heartbeatPath, @@ -2257,6 +2322,7 @@ export class FleetRepository { heartbeatBody: "", heartbeatNotify: true, heartbeatChannel: "", + heartbeatChannelTarget: "", }; } @@ -2319,6 +2385,8 @@ export class FleetRepository { catchUp: asBoolean(frontmatter.catch_up, this.settings.catchUpMissedTasks), effort: asString(frontmatter.effort), model: asString(frontmatter.model), + channel: asString(frontmatter.channel), + channelTarget: asString(frontmatter.channel_target), tags: asStringArray(frontmatter.tags), body, }; @@ -2334,7 +2402,7 @@ export class FleetRepository { } const rawType = asString(frontmatter.type); - const validTypes: readonly string[] = ["slack", "telegram"]; + const validTypes: readonly string[] = ["slack", "telegram", "discord"]; if (!rawType || !validTypes.includes(rawType)) { this.setIssue( path, diff --git a/src/main.ts b/src/main.ts index 1fb0d31..8f7b83b 100644 --- a/src/main.ts +++ b/src/main.ts @@ -20,6 +20,7 @@ import { SecretStore } from "./services/secretStore"; import { parseClaudeMcpServers, parseCodexMcpServers, mergeImports } from "./services/mcpImport"; import { SlackAdapter } from "./services/channels/slackAdapter"; import { TelegramAdapter } from "./services/channels/telegramAdapter"; +import { DiscordAdapter } from "./services/channels/discordAdapter"; import type { ChannelAdapter } from "./services/channels/adapter"; import type { ChannelConfig, ChannelCredentialEntry, FleetSettings } from "./types"; import { parseMarkdownWithFrontmatter, stringifyMarkdownWithFrontmatter } from "./utils/markdown"; @@ -154,6 +155,7 @@ export default class AgentFleetPlugin extends Plugin { getSettings: () => this.settings, getChannelCredentials: () => this.channelCredentials.toRecord(), getMcpAuth: () => this.mcpAuth, + recordUsage: (record) => this.runtime.recordUsage(record), adapterFactory: (config: ChannelConfig, credential: ChannelCredentialEntry): ChannelAdapter => { if (config.type === "slack") { return new SlackAdapter(config, credential); @@ -161,6 +163,9 @@ export default class AgentFleetPlugin extends Plugin { if (config.type === "telegram") { return new TelegramAdapter(config, credential); } + if (config.type === "discord") { + return new DiscordAdapter(config, credential); + } throw new Error(`Channel type \`${config.type}\` is not yet supported in this version.`); }, }); @@ -171,16 +176,19 @@ export default class AgentFleetPlugin extends Plugin { new Notice("Agent Fleet: channel manager failed to start — check console."); } - // Wire heartbeat results to Slack channels. When an agent's heartbeat - // completes and its heartbeatChannel is set, the runtime calls this handler - // which posts the output via the channel's broadcast method (opens a DM with - // the first allowed user and posts there). - this.runtime.onHeartbeatResult((agentName, channelName, output) => { - void this.channelManager?.broadcastToChannel( - channelName, - `*Heartbeat — ${agentName}*\n\n${output}`, - ).catch((err: unknown) => { - console.warn(`Agent Fleet: heartbeat channel post failed for ${agentName}`, err); + // Wire run results to channels. Fires when an agent's heartbeat completes + // (using heartbeatChannel) or when a scheduled/manual task sets a `channel` + // field. `source` is "heartbeat" or the task id, used only to label the post. + // When `target` is set the post goes to that specific channel id; otherwise it + // broadcasts (opens a DM with the first allowed user and posts there). + this.runtime.onChannelResult((agentName, channelName, output, source, target) => { + const label = source === "heartbeat" ? `Heartbeat — ${agentName}` : `${agentName} — ${source}`; + const text = `*${label}*\n\n${output}`; + const delivery = target + ? this.channelManager?.postToChannelTarget(channelName, target, text) + : this.channelManager?.broadcastToChannel(channelName, text); + void delivery?.catch((err: unknown) => { + console.warn(`Agent Fleet: channel post failed for ${agentName}`, err); }); }); @@ -630,16 +638,20 @@ export default class AgentFleetPlugin extends Plugin { } private registerVaultHandlers(): void { + // The usage ledger (`_fleet/usage/`) is appended on every chat/channel turn + // and holds no parsed entities — skip it so the hot path never triggers a + // full fleet reparse. + const isLedgerPath = (p: string) => p.startsWith(`${this.settings.fleetFolder}/usage/`); this.registerEvent( this.app.vault.on("create", (file) => { - if (file instanceof TFile && file.path.startsWith(`${this.settings.fleetFolder}/`)) { + if (file instanceof TFile && file.path.startsWith(`${this.settings.fleetFolder}/`) && !isLedgerPath(file.path)) { this.debouncedVaultRefresh(); } }), ); this.registerEvent( this.app.vault.on("modify", (file) => { - if (file instanceof TFile && file.path.startsWith(`${this.settings.fleetFolder}/`)) { + if (file instanceof TFile && file.path.startsWith(`${this.settings.fleetFolder}/`) && !isLedgerPath(file.path)) { this.debouncedVaultRefresh(); } }), diff --git a/src/services/channelManager.test.ts b/src/services/channelManager.test.ts index be8733f..3784947 100644 --- a/src/services/channelManager.test.ts +++ b/src/services/channelManager.test.ts @@ -46,6 +46,7 @@ function makeAgent(overrides: Partial = {}): AgentConfig { heartbeatBody: "", heartbeatNotify: true, heartbeatChannel: "", + heartbeatChannelTarget: "", ...overrides, }; } diff --git a/src/services/channelManager.ts b/src/services/channelManager.ts index 1069d7c..2b3683c 100644 --- a/src/services/channelManager.ts +++ b/src/services/channelManager.ts @@ -5,6 +5,7 @@ import type { ChannelStatus, FleetSettings, FleetSnapshot, + UsageRecord, } from "../types"; import type { FleetRepository } from "../fleetRepository"; import type { McpAuthManager } from "./mcpAuth"; @@ -43,6 +44,9 @@ export interface ChannelManagerDeps { getMcpAuth?: () => McpAuthManager; /** Factory that produces the transport-specific adapter. */ adapterFactory: ChannelAdapterFactory; + /** Sink for per-turn token/cost usage from channel sessions (the usage ledger). + * Late-bound to the current runtime so it survives runtime rebuilds. */ + recordUsage?: (record: UsageRecord) => void; /** Injectable clock, primarily for tests. */ now?: () => number; } @@ -629,6 +633,9 @@ export class ChannelManager { mcpAuth: this.deps.getMcpAuth?.(), }, ); + if (this.deps.recordUsage) { + session.setUsageRecorder(this.deps.recordUsage); + } try { await session.loadPersistedState(); @@ -786,6 +793,24 @@ export class ChannelManager { await adapter.broadcast(text); } + /** + * Post a message to an explicit destination id within a channel (a Discord/Slack + * channel id or Telegram chat id) — used for per-task delivery to a specific + * channel rather than the broadcast DM. + */ + async postToChannelTarget(channelName: string, target: string, text: string): Promise { + const adapter = this.adapters.get(channelName); + if (!adapter) { + console.warn(`Agent Fleet: postToChannelTarget — no adapter for channel ${channelName}`); + return; + } + if (!adapter.sendToTarget) { + console.warn(`Agent Fleet: postToChannelTarget — adapter ${channelName} does not support sendToTarget`); + return; + } + await adapter.sendToTarget(target, text); + } + // ═══════════════════════════════════════════════════════ // Conversation lock — per-key FIFO promise chain // ═══════════════════════════════════════════════════════ diff --git a/src/services/channels/adapter.ts b/src/services/channels/adapter.ts index 5d5786f..a08a2bc 100644 --- a/src/services/channels/adapter.ts +++ b/src/services/channels/adapter.ts @@ -87,6 +87,15 @@ export interface ChannelAdapter { */ broadcast?(text: string): Promise; + /** + * Post a message to an explicit transport-native destination id — a Discord or + * Slack channel id, or a Telegram chat id — independent of any inbound + * conversation. Used for per-task result delivery to a specific channel. + * Adapters should chunk + rate-limit the same way `send()` does. Optional; + * transports that don't support it can omit. + */ + sendToTarget?(target: string, text: string): Promise; + onInbound(handler: InboundHandler): () => void; onStatusChange(handler: StatusHandler): () => void; diff --git a/src/services/channels/discordAdapter.test.ts b/src/services/channels/discordAdapter.test.ts new file mode 100644 index 0000000..22e7734 --- /dev/null +++ b/src/services/channels/discordAdapter.test.ts @@ -0,0 +1,236 @@ +import { describe, expect, it, vi } from "vitest"; + +// The adapter's only runtime obsidian dependency is requestUrl — mock it so we can +// assert the outbound REST request shape (headers/url) without a network call. +const { requestUrlMock } = vi.hoisted(() => ({ requestUrlMock: vi.fn() })); +vi.mock("obsidian", () => ({ requestUrl: requestUrlMock })); + +import { + DiscordAdapter, + buildConversationId, + channelIdFromConversationId, + describeDiscordError, + stripLeadingMention, +} from "./discordAdapter"; +import type { ChannelConfig, ChannelCredentialEntry } from "../../types"; +import type { InboundMessage } from "./adapter"; + +function makeChannel(overrides: Partial = {}): ChannelConfig { + return { + filePath: "_fleet/channels/test-discord.md", + name: "test-discord", + type: "discord", + defaultAgent: "test-agent", + allowedAgents: [], + enabled: true, + credentialRef: "discord-creds", + allowedUsers: [], + perUserSessions: true, + channelContext: "", + transport: {}, + tags: [], + body: "", + ...overrides, + }; +} + +const credential: ChannelCredentialEntry = { type: "discord", botToken: "test-token" }; + +/** Typed view into the adapter's internals for testing the inbound mapping. */ +interface AdapterInternals { + selfUserId: string | null; + routeMessage(message: unknown): void; +} + +function internals(adapter: DiscordAdapter): AdapterInternals { + return adapter as unknown as AdapterInternals; +} + +const flush = () => new Promise((r) => setTimeout(r, 0)); + +describe("conversationId helpers", () => { + it("builds and parses guild conversation ids", () => { + const id = buildConversationId("guild1", "chan1"); + expect(id).toBe("discord:guild1:chan1"); + expect(channelIdFromConversationId(id)).toBe("chan1"); + }); + + it("builds and parses DM conversation ids", () => { + const id = buildConversationId(undefined, "dmchan"); + expect(id).toBe("discord:dm:dmchan"); + expect(channelIdFromConversationId(id)).toBe("dmchan"); + }); + + it("treats a thread as just another channel id", () => { + // A thread message arrives with channel_id = the thread's id. + const id = buildConversationId("guild1", "thread99"); + expect(channelIdFromConversationId(id)).toBe("thread99"); + }); + + it("returns null for non-discord conversation ids", () => { + expect(channelIdFromConversationId("tg:123")).toBeNull(); + expect(channelIdFromConversationId("slack:t:c:thread:1")).toBeNull(); + }); +}); + +describe("stripLeadingMention", () => { + it("strips a leading <@id> mention", () => { + expect(stripLeadingMention("<@123> hello there", "123")).toBe("hello there"); + }); + + it("strips a leading <@!id> nickname mention", () => { + expect(stripLeadingMention("<@!123> hi", "123")).toBe("hi"); + }); + + it("ignores mentions of other users", () => { + expect(stripLeadingMention("<@999> hi", "123")).toBe("<@999> hi"); + }); + + it("trims surrounding whitespace and handles no mention", () => { + expect(stripLeadingMention(" plain text ", "123")).toBe("plain text"); + expect(stripLeadingMention("plain", null)).toBe("plain"); + }); +}); + +describe("MESSAGE_CREATE → InboundMessage mapping", () => { + it("emits an InboundMessage for a normal guild message", async () => { + const adapter = new DiscordAdapter(makeChannel(), credential); + internals(adapter).selfUserId = "bot-self"; + const received: InboundMessage[] = []; + adapter.onInbound((m) => received.push(m)); + + internals(adapter).routeMessage({ + id: "msg1", + channel_id: "chan1", + guild_id: "guild1", + author: { id: "user1", bot: false }, + content: "<@bot-self> do the thing", + timestamp: "2026-06-19T00:00:00.000Z", + }); + await flush(); + + expect(received).toHaveLength(1); + const msg = received[0]!; + expect(msg.conversationId).toBe("discord:guild1:chan1"); + expect(msg.externalUserId).toBe("user1"); + expect(msg.text).toBe("do the thing"); + expect(msg.meta).toMatchObject({ + discord_channel_id: "chan1", + discord_guild_id: "guild1", + is_dm: false, + }); + }); + + it("marks DMs with is_dm and a discord:dm: conversation id", async () => { + const adapter = new DiscordAdapter(makeChannel(), credential); + internals(adapter).selfUserId = "bot-self"; + const received: InboundMessage[] = []; + adapter.onInbound((m) => received.push(m)); + + internals(adapter).routeMessage({ + id: "msg2", + channel_id: "dmchan", + author: { id: "user2", bot: false }, + content: "hi", + timestamp: "2026-06-19T00:00:00.000Z", + }); + await flush(); + + expect(received).toHaveLength(1); + expect(received[0]!.conversationId).toBe("discord:dm:dmchan"); + expect(received[0]!.meta).toMatchObject({ is_dm: true }); + }); + + it("ignores bot-authored and self-authored messages", async () => { + const adapter = new DiscordAdapter(makeChannel(), credential); + internals(adapter).selfUserId = "bot-self"; + const received: InboundMessage[] = []; + adapter.onInbound((m) => received.push(m)); + + internals(adapter).routeMessage({ + id: "m3", channel_id: "c", guild_id: "g", + author: { id: "other-bot", bot: true }, content: "beep", + }); + internals(adapter).routeMessage({ + id: "m4", channel_id: "c", guild_id: "g", + author: { id: "bot-self", bot: false }, content: "echo", + }); + await flush(); + + expect(received).toHaveLength(0); + }); + + it("ignores empty-content messages with no attachments", async () => { + const adapter = new DiscordAdapter(makeChannel(), credential); + internals(adapter).selfUserId = "bot-self"; + const received: InboundMessage[] = []; + adapter.onInbound((m) => received.push(m)); + + internals(adapter).routeMessage({ + id: "m5", channel_id: "c", guild_id: "g", + author: { id: "user1", bot: false }, content: "", + }); + await flush(); + + expect(received).toHaveLength(0); + }); + + it("rejects a non-discord credential", () => { + expect( + () => new DiscordAdapter(makeChannel(), { type: "telegram", botToken: "x" }), + ).toThrow(/discord credential/); + }); +}); + +describe("REST request shape", () => { + it("sends the required Discord User-Agent + bot auth on outbound calls", async () => { + // Regression guard: a missing User-Agent gets Cloudflare-blocked (403 code 40333) + // on every REST call while the Gateway still connects. + requestUrlMock.mockReset(); + requestUrlMock.mockResolvedValue({ status: 200, json: {}, text: "" }); + + const adapter = new DiscordAdapter(makeChannel(), credential); + await adapter.send("discord:dm:chan1", "hello"); + + expect(requestUrlMock).toHaveBeenCalledTimes(1); + const opts = requestUrlMock.mock.calls[0]![0] as { + url: string; + method: string; + headers: Record; + body?: string; + }; + expect(opts.url).toContain("/channels/chan1/messages"); + expect(opts.method).toBe("POST"); + expect(opts.headers["User-Agent"]).toMatch(/^DiscordBot \(/); + expect(opts.headers.Authorization).toBe("Bot test-token"); + expect(opts.body).toContain("hello"); + }); + + it("sendToTarget posts directly to the given channel id", async () => { + requestUrlMock.mockReset(); + requestUrlMock.mockResolvedValue({ status: 200, json: {}, text: "" }); + + const adapter = new DiscordAdapter(makeChannel(), credential); + await adapter.sendToTarget("987654321", "task result"); + + expect(requestUrlMock).toHaveBeenCalledTimes(1); + const opts = requestUrlMock.mock.calls[0]![0] as { url: string; body?: string }; + expect(opts.url).toContain("/channels/987654321/messages"); + expect(opts.body).toContain("task result"); + }); +}); + +describe("describeDiscordError", () => { + it("extracts Discord's message + code from a JSON error body", () => { + expect(describeDiscordError('{"message":"internal network error","code":40333}')) + .toBe("internal network error (code 40333)"); + expect(describeDiscordError('{"message":"Missing Access","code":50001}')) + .toBe("Missing Access (code 50001)"); + }); + + it("falls back to raw text for non-JSON bodies", () => { + expect(describeDiscordError("Bad Gateway")).toBe("Bad Gateway"); + expect(describeDiscordError("")).toBe("no body"); + expect(describeDiscordError(undefined)).toBe("no body"); + }); +}); diff --git a/src/services/channels/discordAdapter.ts b/src/services/channels/discordAdapter.ts new file mode 100644 index 0000000..c303086 --- /dev/null +++ b/src/services/channels/discordAdapter.ts @@ -0,0 +1,852 @@ +import WebSocket from "ws"; +import { requestUrl } from "obsidian"; +import type { + ChannelConfig, + ChannelCredentialEntry, + ChannelStatus, + ChannelType, +} from "../../types"; +import type { + ChannelAdapter, + InboundHandler, + InboundImage, + InboundMessage, + StatusHandler, +} from "./adapter"; + +/** + * Discord adapter using the Gateway (WebSocket) for inbound events and the REST + * API for everything outbound. Hand-rolled over `ws` — no discord.js — to stay + * consistent with the Slack/Telegram adapters and avoid a heavy dependency. + * + * Transport: outbound WebSocket + outbound HTTPS, so no NAT/firewall concerns. + * + * Security invariant: the allowlist check uses `author.id` from the + * Gateway-delivered MESSAGE_CREATE payload. The Gateway connection is + * authenticated by the bot token during IDENTIFY, so `author.id` is trustworthy. + * Never accept a sender id from anywhere else (e.g. message content). + * + * Note: the Message Content intent is privileged and must be enabled in the + * Discord Developer Portal, or message `content` arrives empty. A disallowed + * intent closes the Gateway with code 4014 — we surface that as `needs-auth` + * with a clear console hint. + */ + +interface DiscordCredential { + type: "discord"; + botToken: string; +} + +const DISCORD_API = "https://discord.com/api/v10"; +const DEFAULT_GATEWAY = "wss://gateway.discord.gg"; +const GATEWAY_QUERY = "?v=10&encoding=json"; + +// Discord's REST API REQUIRES a User-Agent in the form `DiscordBot ($url, $version)`. +// Without it, requests get Cloudflare-blocked with HTTP 403 `internal network error` +// (code 40333) — every REST call fails while the Gateway (no Cloudflare WAF) still +// connects. See https://docs.discord.food/topics/errors. +const DISCORD_USER_AGENT = "DiscordBot (https://github.com/denberek/obsidian-agent-fleet, 0.13.6)"; + +// Gateway intents: GUILD_MESSAGES (1<<9) | DIRECT_MESSAGES (1<<12) | MESSAGE_CONTENT (1<<15) +const INTENTS = (1 << 9) | (1 << 12) | (1 << 15); // 37376 + +// Gateway opcodes +const OP_DISPATCH = 0; +const OP_HEARTBEAT = 1; +const OP_IDENTIFY = 2; +const OP_RESUME = 6; +const OP_RECONNECT = 7; +const OP_INVALID_SESSION = 9; +const OP_HELLO = 10; +const OP_HEARTBEAT_ACK = 11; + +// Interaction types +const INTERACTION_APPLICATION_COMMAND = 2; +const INTERACTION_MESSAGE_COMPONENT = 3; + +// Interaction callback types +const CALLBACK_CHANNEL_MESSAGE = 4; +const CALLBACK_UPDATE_MESSAGE = 7; + +// Message flags +const FLAG_EPHEMERAL = 1 << 6; // 64 + +export class DiscordAdapter implements ChannelAdapter { + readonly type: ChannelType = "discord"; + public config: ChannelConfig; + + private readonly credential: DiscordCredential; + private ws: WebSocket | null = null; + private status: ChannelStatus = "stopped"; + private stopping = false; + private backoffMs = 1000; + private reconnectTimer: number | null = null; + + // Gateway session state (for RESUME). + private sessionId: string | null = null; + private resumeGatewayUrl: string | null = null; + private seq: number | null = null; + private canResume = false; + + // Heartbeat state. + private heartbeatTimer: number | null = null; + private heartbeatInitialTimer: number | null = null; + private heartbeatAcked = true; + + // Identity learned from the READY dispatch. + private selfUserId: string | null = null; + private applicationId: string | null = null; + private commandRegistered = false; + private warnedEmptyContent = false; + + private typingIntervals = new Map(); + /** Per-channel outbound queue so a chunked reply doesn't burst Discord's rate limit. */ + private readonly sendQueues = new Map>(); + + private readonly inboundHandlers = new Set(); + private readonly statusHandlers = new Set(); + private readonly agentSwitchHandlers = new Set<(conversationId: string, agentName: string, userId: string) => void>(); + /** Resolver for the validated (existing + enabled) agent list, set by the + * ChannelManager. Falls back to raw allowed_agents when unset. */ + private allowedAgentsResolver?: () => string[]; + + constructor(config: ChannelConfig, credential: ChannelCredentialEntry) { + if (credential.type !== "discord") { + throw new Error(`DiscordAdapter requires a discord credential, got ${credential.type}`); + } + this.config = config; + this.credential = credential; + } + + // ═══════════════════════════════════════════════════════ + // Lifecycle + // ═══════════════════════════════════════════════════════ + + async start(): Promise { + this.stopping = false; + this.backoffMs = 1000; + this.canResume = false; + await this.connect(); + } + + async stop(): Promise { + this.stopping = true; + if (this.reconnectTimer) { + window.clearTimeout(this.reconnectTimer); + this.reconnectTimer = null; + } + this.clearHeartbeat(); + for (const [, interval] of this.typingIntervals) { + window.clearInterval(interval); + } + this.typingIntervals.clear(); + try { + this.ws?.close(); + } catch { + // best-effort + } + this.ws = null; + this.setStatus("stopped"); + } + + getStatus(): ChannelStatus { + return this.status; + } + + // ═══════════════════════════════════════════════════════ + // Outbound + // ═══════════════════════════════════════════════════════ + + async send(conversationId: string, text: string): Promise { + const channelId = channelIdFromConversationId(conversationId); + if (!channelId) return; + await this.sendToTarget(channelId, text); + } + + /** Post directly to a Discord channel id (server channel, thread, or DM channel). */ + async sendToTarget(channelId: string, text: string): Promise { + if (!channelId) return; + // Discord's message content cap is 2000 chars. + const chunks = splitText(text, 2000); + await this.enqueueSend(channelId, async () => { + for (const chunk of chunks) { + await this.discordApi("POST", `/channels/${channelId}/messages`, { content: chunk }); + } + }); + } + + async setTyping(conversationId: string, on: boolean): Promise { + const channelId = channelIdFromConversationId(conversationId); + if (!channelId) return; + + if (on) { + // Clear any existing interval first to prevent a double-create leak. + const existing = this.typingIntervals.get(conversationId); + if (existing) window.clearInterval(existing); + // Discord typing lasts ~10s; send immediately and refresh every 8s. + try { + await this.discordApi("POST", `/channels/${channelId}/typing`); + } catch (err) { + console.warn("Agent Fleet: Discord typing trigger failed", err); + } + const interval = window.setInterval(() => { + void this.discordApi("POST", `/channels/${channelId}/typing`).catch(() => { /* best-effort */ }); + }, 8000); + this.typingIntervals.set(conversationId, interval); + } else { + const interval = this.typingIntervals.get(conversationId); + if (interval) { + window.clearInterval(interval); + this.typingIntervals.delete(conversationId); + } + } + } + + async broadcast(text: string): Promise { + // Post to the first allowed user via a DM channel. + const userId = this.config.allowedUsers[0]; + if (!userId) return; + try { + const dm = await this.discordApi<{ id?: string }>("POST", "/users/@me/channels", { + recipient_id: userId, + }); + const channelId = dm?.id; + if (!channelId) return; + const chunks = splitText(text, 2000); + for (const chunk of chunks) { + await this.discordApi("POST", `/channels/${channelId}/messages`, { content: chunk }); + } + } catch (err) { + console.error(`Agent Fleet: Discord broadcast failed on ${this.config.name}`, err); + } + } + + // ═══════════════════════════════════════════════════════ + // Event subscription + // ═══════════════════════════════════════════════════════ + + onInbound(handler: InboundHandler): () => void { + this.inboundHandlers.add(handler); + return () => this.inboundHandlers.delete(handler); + } + + onStatusChange(handler: StatusHandler): () => void { + this.statusHandlers.add(handler); + return () => this.statusHandlers.delete(handler); + } + + onAgentSwitch(handler: (conversationId: string, agentName: string, userId: string) => void): () => void { + this.agentSwitchHandlers.add(handler); + return () => this.agentSwitchHandlers.delete(handler); + } + + setAllowedAgentsResolver(resolve: () => string[]): void { + this.allowedAgentsResolver = resolve; + } + + /** The agents the picker should offer: validated list if available, else the + * raw configured allow-list. */ + private pickerAgents(): string[] { + if (this.allowedAgentsResolver) return this.allowedAgentsResolver(); + return this.config.allowedAgents; + } + + // ═══════════════════════════════════════════════════════ + // Gateway connection + // ═══════════════════════════════════════════════════════ + + private async connect(): Promise { + if (this.stopping) return; + this.setStatus(this.ws ? "reconnecting" : "connecting"); + + const base = this.canResume && this.resumeGatewayUrl ? this.resumeGatewayUrl : DEFAULT_GATEWAY; + const url = `${base}${GATEWAY_QUERY}`; + + try { + const ws = new WebSocket(url); + + // Attach handlers BEFORE assigning to this.ws to avoid a race where events + // fire between construction and handler attachment. + ws.on("message", (data: WebSocket.RawData) => { + this.handleFrame(data); + }); + + ws.on("error", (err: Error) => { + console.warn(`Agent Fleet: Discord WebSocket error on ${this.config.name}`, err); + }); + + ws.on("close", (code: number) => { + this.ws = null; + this.clearHeartbeat(); + this.handleClose(code); + }); + + this.ws = ws; + + // Connection timeout — if we don't get HELLO within 30s, tear down and retry. + const connectTimeout = window.setTimeout(() => { + if (this.status === "connecting" || this.status === "reconnecting") { + console.warn(`Agent Fleet: Discord WebSocket connect timeout on ${this.config.name}`); + try { ws.close(); } catch { /* best-effort */ } + } + }, 30_000); + ws.on("close", () => window.clearTimeout(connectTimeout)); + const statusUnsub = this.onStatusChange((status) => { + if (status === "connected") { + window.clearTimeout(connectTimeout); + statusUnsub(); + } + }); + } catch (err) { + console.error("Agent Fleet: Discord WebSocket open failed", err); + this.setStatus("error"); + this.scheduleReconnect(); + } + } + + private handleClose(code: number): void { + // Fatal codes won't recover by retrying — they need the user to fix the app + // config (bad token / disallowed intents). Auto-reconnecting every ≤30s with a + // known-bad IDENTIFY just wastes cycles and risks Discord rate-limiting the IP, + // so we surface needs-auth and STOP. Recovery happens when the channel restarts + // (Obsidian reload, or a connection-relevant config edit re-creates the adapter). + // 4004 authentication failed · 4013 invalid intents · 4014 disallowed intents + let fatal = false; + if (code === 4014) { + console.error( + `Agent Fleet: Discord channel ${this.config.name} — disallowed intents (4014). ` + + "Enable the Message Content intent in the Developer Portal → your app → Bot → Privileged Gateway Intents, then reload.", + ); + this.canResume = false; + this.setStatus("needs-auth"); + fatal = true; + } else if (code === 4004 || code === 4013) { + console.error( + `Agent Fleet: Discord channel ${this.config.name} — gateway auth/intents error (${code}). ` + + "Check the bot token credential, then reload.", + ); + this.canResume = false; + this.setStatus("needs-auth"); + fatal = true; + } else if (code === 4007 || code === 4009) { + // Invalid seq / session timed out — reconnect fresh (no resume). + this.canResume = false; + } + if (!this.stopping && !fatal) { + this.scheduleReconnect(); + } + } + + private handleFrame(data: WebSocket.RawData): void { + let frame: GatewayFrame; + try { + frame = JSON.parse(data.toString()) as GatewayFrame; + } catch { + return; + } + + if (typeof frame.s === "number") this.seq = frame.s; + + switch (frame.op) { + case OP_HELLO: { + const interval = (frame.d as { heartbeat_interval?: number } | undefined)?.heartbeat_interval; + this.startHeartbeat(interval ?? 41_250); + if (this.canResume && this.sessionId && this.seq !== null) { + this.sendGateway(OP_RESUME, { + token: this.credential.botToken, + session_id: this.sessionId, + seq: this.seq, + }); + } else { + this.identify(); + } + return; + } + case OP_HEARTBEAT: { + // Server requested an immediate heartbeat. + this.sendGateway(OP_HEARTBEAT, this.seq); + return; + } + case OP_HEARTBEAT_ACK: { + this.heartbeatAcked = true; + return; + } + case OP_RECONNECT: { + // Server asked us to reconnect — keep the session so we can RESUME. + try { this.ws?.close(); } catch { /* best-effort */ } + return; + } + case OP_INVALID_SESSION: { + // d is a boolean: whether the session is resumable. + const resumable = frame.d === true; + if (!resumable) { + this.canResume = false; + this.sessionId = null; + } + // Discord asks for a short random delay (1–5s) before re-identifying. + window.setTimeout(() => { + try { this.ws?.close(); } catch { /* best-effort */ } + }, 1000 + Math.floor(Math.random() * 4000)); + return; + } + case OP_DISPATCH: { + this.handleDispatch(frame.t ?? "", frame.d); + return; + } + default: + return; + } + } + + private handleDispatch(type: string, d: unknown): void { + if (type === "READY") { + const ready = d as { + session_id?: string; + resume_gateway_url?: string; + user?: { id?: string }; + application?: { id?: string }; + }; + this.sessionId = ready.session_id ?? null; + this.resumeGatewayUrl = ready.resume_gateway_url ?? null; + this.selfUserId = ready.user?.id ?? null; + this.applicationId = ready.application?.id ?? null; + this.canResume = true; + this.backoffMs = 1000; + this.setStatus("connected"); + void this.registerAgentsCommand(); + return; + } + + if (type === "RESUMED") { + this.backoffMs = 1000; + this.setStatus("connected"); + return; + } + + if (type === "MESSAGE_CREATE") { + this.routeMessage(d as DiscordMessage); + return; + } + + if (type === "INTERACTION_CREATE") { + void this.handleInteraction(d as DiscordInteraction); + return; + } + } + + private identify(): void { + this.sendGateway(OP_IDENTIFY, { + token: this.credential.botToken, + intents: INTENTS, + properties: { os: "linux", browser: "agent-fleet", device: "agent-fleet" }, + }); + } + + private sendGateway(op: number, d: unknown): void { + if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return; + try { + this.ws.send(JSON.stringify({ op, d })); + } catch (err) { + console.warn("Agent Fleet: Discord gateway send failed", err); + } + } + + private startHeartbeat(intervalMs: number): void { + this.clearHeartbeat(); + this.heartbeatAcked = true; + const beat = () => { + if (!this.heartbeatAcked) { + // Zombie connection — no ACK since the last beat. Reconnect (resume). + console.warn(`Agent Fleet: Discord heartbeat not ACKed on ${this.config.name} — reconnecting`); + try { this.ws?.close(); } catch { /* best-effort */ } + return; + } + this.heartbeatAcked = false; + this.sendGateway(OP_HEARTBEAT, this.seq); + }; + // First beat after interval * jitter (per Discord docs), then every interval. + this.heartbeatInitialTimer = window.setTimeout(() => { + beat(); + this.heartbeatTimer = window.setInterval(beat, intervalMs); + }, Math.floor(intervalMs * Math.random())); + } + + private clearHeartbeat(): void { + if (this.heartbeatInitialTimer) { + window.clearTimeout(this.heartbeatInitialTimer); + this.heartbeatInitialTimer = null; + } + if (this.heartbeatTimer) { + window.clearInterval(this.heartbeatTimer); + this.heartbeatTimer = null; + } + } + + private scheduleReconnect(): void { + if (this.stopping) return; + if (this.reconnectTimer) return; + const delay = this.backoffMs; + this.backoffMs = Math.min(30_000, this.backoffMs * 2); + console.warn(`Agent Fleet: Discord channel ${this.config.name} scheduling reconnect in ${delay}ms`); + this.reconnectTimer = window.setTimeout(() => { + this.reconnectTimer = null; + if (this.stopping) return; + void this.connect(); + }, delay); + } + + // ═══════════════════════════════════════════════════════ + // Inbound messages + // ═══════════════════════════════════════════════════════ + + private routeMessage(message: DiscordMessage): void { + if (!message.author || message.author.bot) return; + if (this.selfUserId && message.author.id === this.selfUserId) return; + if (!message.channel_id) return; + + const imageAttachments = (message.attachments ?? []).filter( + (a) => typeof a.content_type === "string" && a.content_type.startsWith("image/"), + ); + + let text = stripLeadingMention(message.content ?? "", this.selfUserId); + + // The agent prefix / routing happens in ChannelManager. Empty content with no + // attachments usually means the privileged Message Content intent is off. + if (!text && imageAttachments.length === 0) { + if (!this.warnedEmptyContent) { + this.warnedEmptyContent = true; + console.warn( + `Agent Fleet: Discord channel ${this.config.name} received a message with empty content. ` + + "If this persists, enable the Message Content intent in the Developer Portal.", + ); + } + return; + } + + const conversationId = buildConversationId(message.guild_id, message.channel_id); + void this.buildAndEmitMessage(message, text, conversationId, imageAttachments); + } + + private async buildAndEmitMessage( + message: DiscordMessage, + text: string, + conversationId: string, + imageAttachments: DiscordAttachment[], + ): Promise { + const images: InboundImage[] = []; + for (const attachment of imageAttachments) { + try { + const resp = await requestUrl({ url: attachment.url, method: "GET" }); + images.push({ + data: resp.arrayBuffer, + filename: attachment.filename || `attachment_${message.id}`, + mimeType: attachment.content_type ?? "image/jpeg", + }); + } catch (err) { + console.warn("Agent Fleet: Discord attachment download failed", err); + } + } + + const msg: InboundMessage = { + conversationId, + externalUserId: message.author!.id, + text, + timestamp: message.timestamp ?? new Date().toISOString(), + meta: { + discord_guild_id: message.guild_id, + discord_channel_id: message.channel_id, + discord_message_id: message.id, + is_dm: !message.guild_id, + }, + ...(images.length > 0 ? { images } : {}), + }; + + for (const handler of this.inboundHandlers) { + try { + handler(msg); + } catch (err) { + console.error("Agent Fleet: Discord inbound handler threw", err); + } + } + } + + // ═══════════════════════════════════════════════════════ + // /agents slash command + buttons + // ═══════════════════════════════════════════════════════ + + private async registerAgentsCommand(): Promise { + if (this.commandRegistered || !this.applicationId) return; + try { + await this.discordApi("PUT", `/applications/${this.applicationId}/commands`, [ + { name: "agents", description: "Switch the active agent", type: 1 }, + ]); + this.commandRegistered = true; + } catch (err) { + // Best-effort — global command registration can take up to ~1h to propagate. + console.warn(`Agent Fleet: Discord /agents command registration failed on ${this.config.name}`, err); + } + } + + private async handleInteraction(interaction: DiscordInteraction): Promise { + if (interaction.type === INTERACTION_APPLICATION_COMMAND) { + if (interaction.data?.name === "agents") { + await this.respondWithAgentPicker(interaction); + } + return; + } + + if (interaction.type === INTERACTION_MESSAGE_COMPONENT) { + const customId = interaction.data?.custom_id ?? ""; + if (!customId.startsWith("switch:")) return; + const agentName = customId.slice("switch:".length); + const userId = interaction.member?.user?.id ?? interaction.user?.id; + const channelId = interaction.channel_id; + if (!userId || !channelId) return; + const conversationId = buildConversationId(interaction.guild_id, channelId); + + // Notify ChannelManager of the binding change. + for (const handler of this.agentSwitchHandlers) { + try { + handler(conversationId, agentName, userId); + } catch (err) { + console.error("Agent Fleet: Discord agent switch handler threw", err); + } + } + + // Acknowledge by updating the original message to show the selection. + await this.respondToInteraction(interaction, CALLBACK_UPDATE_MESSAGE, { + content: `Active agent: **${agentName}**`, + components: this.buildAgentButtons(agentName), + }); + return; + } + } + + private async respondWithAgentPicker(interaction: DiscordInteraction): Promise { + const agents = this.pickerAgents(); + if (agents.length === 0) { + await this.respondToInteraction(interaction, CALLBACK_CHANNEL_MESSAGE, { + content: "No agents available. Add existing, enabled agents to `allowed_agents` in the channel file.", + flags: FLAG_EPHEMERAL, + }); + return; + } + await this.respondToInteraction(interaction, CALLBACK_CHANNEL_MESSAGE, { + content: "Select an agent to chat with:", + flags: FLAG_EPHEMERAL, + components: this.buildAgentButtons(this.config.defaultAgent), + }); + } + + /** Build action-row button components (≤5 buttons/row, ≤5 rows = 25 agents). */ + private buildAgentButtons(activeAgent: string): unknown[] { + const agents = this.pickerAgents().slice(0, 25); + const rows: unknown[] = []; + for (let i = 0; i < agents.length; i += 5) { + const slice = agents.slice(i, i + 5); + rows.push({ + type: 1, + components: slice.map((name) => ({ + type: 2, + // style 1 = primary (active), 2 = secondary + style: name === activeAgent ? 1 : 2, + label: name === activeAgent ? `${name} ✓` : name, + custom_id: `switch:${name}`, + })), + }); + } + return rows; + } + + private async respondToInteraction( + interaction: DiscordInteraction, + callbackType: number, + data: Record, + ): Promise { + try { + await this.discordApi( + "POST", + `/interactions/${interaction.id}/${interaction.token}/callback`, + { type: callbackType, data }, + ); + } catch (err) { + console.warn("Agent Fleet: Discord interaction response failed", err); + } + } + + // ═══════════════════════════════════════════════════════ + // Discord REST API + per-channel send queue + // ═══════════════════════════════════════════════════════ + + private async discordApi>( + method: string, + path: string, + body?: unknown, + ): Promise { + const res = await requestUrl({ + url: `${DISCORD_API}${path}`, + method, + contentType: "application/json", + headers: { + Authorization: `Bot ${this.credential.botToken}`, + "User-Agent": DISCORD_USER_AGENT, + }, + ...(body !== undefined ? { body: JSON.stringify(body) } : {}), + // Handle statuses ourselves so we can honor retry_after on 429. + throw: false, + }); + + if (res.status === 429) { + const retryAfter = (res.json as { retry_after?: number } | undefined)?.retry_after + ?? Number(res.headers["retry-after"] ?? "1"); + await new Promise((r) => window.setTimeout(r, Math.max(1000, retryAfter * 1000))); + return this.discordApi(method, path, body); + } + + if (res.status === 401 || res.status === 403) { + // Surface Discord's real reason (body carries `message` + numeric `code`, + // e.g. 50001 Missing Access, 50007 Cannot send messages to this user, + // 0 401:Unauthorized). Without it a 403 is unactionable. + throw new Error(`Discord ${method} ${path} ${res.status}: ${describeDiscordError(res.text)}`); + } + + if (res.status < 200 || res.status >= 300) { + throw new Error(`Discord ${method} ${path} HTTP ${res.status}: ${res.text}`); + } + + // 204 No Content (e.g. typing, interaction callbacks) has no JSON body. + if (res.status === 204) return undefined as T; + return res.json as T; + } + + /** Serialize sends per Discord channel id so a chunked reply stays under the + * per-channel rate limit. */ + private async enqueueSend(channel: string, fn: () => Promise): Promise { + const prev = this.sendQueues.get(channel) ?? Promise.resolve(); + const next = prev.then(fn); + // `wrapped` keeps the per-channel chain alive (never rejects) so a failed send + // doesn't poison the next one. But the CALLER must still see this send's failure + // — otherwise delivery errors are invisible (the dashboard counts them as sent + // and the channel status never reflects them). So await `next`, not `wrapped`. + const wrapped = next.catch(() => { /* keep chain unrejected for the next send */ }); + this.sendQueues.set(channel, wrapped); + try { + await next; + } finally { + if (this.sendQueues.get(channel) === wrapped) { + this.sendQueues.delete(channel); + } + } + } + + private setStatus(next: ChannelStatus): void { + if (this.status === next) return; + this.status = next; + for (const h of this.statusHandlers) { + try { h(next); } catch { /* best-effort */ } + } + } +} + +// ═══════════════════════════════════════════════════════ +// Discord types (minimal subset) +// ═══════════════════════════════════════════════════════ + +interface GatewayFrame { + op: number; + d?: unknown; + s?: number | null; + t?: string | null; +} + +interface DiscordAttachment { + id: string; + filename: string; + url: string; + content_type?: string; + size?: number; +} + +interface DiscordMessage { + id: string; + channel_id?: string; + guild_id?: string; + author?: { id: string; bot?: boolean; username?: string }; + content?: string; + timestamp?: string; + attachments?: DiscordAttachment[]; +} + +interface DiscordInteraction { + id: string; + token: string; + type: number; + channel_id?: string; + guild_id?: string; + member?: { user?: { id: string } }; + user?: { id: string }; + data?: { name?: string; custom_id?: string; component_type?: number }; + message?: { id: string }; +} + +// ═══════════════════════════════════════════════════════ +// Helpers +// ═══════════════════════════════════════════════════════ + +/** + * Conversation id shape: + * guild message / thread: `discord::` + * DM: `discord:dm:` + * A thread is just another channel_id, so per-thread isolation is automatic. + * The channel id is always the third segment in both forms. + */ +export function buildConversationId(guildId: string | undefined, channelId: string): string { + return guildId ? `discord:${guildId}:${channelId}` : `discord:dm:${channelId}`; +} + +export function channelIdFromConversationId(conversationId: string): string | null { + const parts = conversationId.split(":"); + if (parts[0] !== "discord") return null; + return parts[2] ?? null; +} + +/** Strip a leading bot mention (`<@id>` or `<@!id>`) so the agent sees clean text. */ +export function stripLeadingMention(content: string, selfUserId: string | null): string { + let text = content.trimStart(); + if (selfUserId) { + const mention = new RegExp(`^<@!?${selfUserId}>\\s*`); + text = text.replace(mention, ""); + } + return text.trim(); +} + +/** Extract Discord's human-readable error (`message` + `code`) from a response + * body, falling back to the raw text. Discord error bodies are JSON like + * `{"message":"Missing Access","code":50001}`. */ +export function describeDiscordError(rawBody: string | undefined): string { + const raw = rawBody ?? ""; + try { + const j = JSON.parse(raw) as { message?: string; code?: number }; + if (j && (j.message !== undefined || j.code !== undefined)) { + return `${j.message ?? "error"} (code ${j.code ?? "?"})`; + } + } catch { + // not JSON — fall through to raw + } + return raw || "no body"; +} + +function splitText(text: string, limit: number): string[] { + if (text.length <= limit) return [text]; + const chunks: string[] = []; + let remaining = text; + while (remaining.length > limit) { + let cutAt = remaining.lastIndexOf("\n\n", limit); + if (cutAt < limit / 2) cutAt = remaining.lastIndexOf("\n", limit); + if (cutAt < limit / 2) cutAt = limit; + chunks.push(remaining.slice(0, cutAt)); + remaining = remaining.slice(cutAt).replace(/^\n+/, ""); + } + if (remaining) chunks.push(remaining); + return chunks; +} diff --git a/src/services/channels/slackAdapter.ts b/src/services/channels/slackAdapter.ts index 1d21ccb..182e6bb 100644 --- a/src/services/channels/slackAdapter.ts +++ b/src/services/channels/slackAdapter.ts @@ -194,6 +194,15 @@ export class SlackAdapter implements ChannelAdapter { }); } + /** Post directly to a Slack channel id (the bot must be a member). */ + async sendToTarget(channelId: string, text: string): Promise { + if (!channelId) return; + const mrkdwn = markdownToMrkdwn(text); + await this.enqueueSend(channelId, async () => { + await this.slackApi("chat.postMessage", { channel: channelId, text: mrkdwn }); + }); + } + async broadcast(text: string): Promise { // Post to the first allowed user's DM. Open the DM channel first via // conversations.open, then post with chat.postMessage. diff --git a/src/services/channels/telegramAdapter.ts b/src/services/channels/telegramAdapter.ts index fb8aa4f..b85600b 100644 --- a/src/services/channels/telegramAdapter.ts +++ b/src/services/channels/telegramAdapter.ts @@ -135,6 +135,19 @@ export class TelegramAdapter implements ChannelAdapter { } } + /** Post directly to a Telegram chat id (user, group, or @channelusername). */ + async sendToTarget(chatId: string, text: string): Promise { + if (!chatId) return; + const chunks = splitText(text, 4096); + for (const chunk of chunks) { + await this.tgApi("sendMessage", { + chat_id: chatId, + text: chunk, + parse_mode: "Markdown", + }); + } + } + async setTyping(conversationId: string, on: boolean): Promise { const chatId = chatIdFromConversationId(conversationId); if (!chatId) return; diff --git a/src/services/chatSession.test.ts b/src/services/chatSession.test.ts index 3100c89..0b7d0bb 100644 --- a/src/services/chatSession.test.ts +++ b/src/services/chatSession.test.ts @@ -39,6 +39,7 @@ function makeAgent(overrides: Partial = {}): AgentConfig { heartbeatBody: "", heartbeatNotify: true, heartbeatChannel: "", + heartbeatChannelTarget: "", ...overrides, }; } diff --git a/src/services/chatSession.ts b/src/services/chatSession.ts index a9c73f6..306f05a 100644 --- a/src/services/chatSession.ts +++ b/src/services/chatSession.ts @@ -2,7 +2,7 @@ import { type ChildProcess } from "child_process"; import { createHash, randomUUID } from "crypto"; import { normalizePath, TFile } from "obsidian"; import type { Vault } from "obsidian"; -import type { AgentConfig, ChatMessage, FleetSettings } from "../types"; +import type { AgentConfig, ChatMessage, FleetSettings, UsageRecord } from "../types"; import type { FleetRepository } from "../fleetRepository"; import { slugify } from "../utils/markdown"; import { resolveModel, shouldPassModelFlag } from "./../utils/modelResolution"; @@ -320,6 +320,14 @@ export class ChatSession { // Stats (derived from stream-json events) private stats: ChatSessionStats = { costTotalUsd: 0, turnCount: 0 }; + /** Sink for per-turn token/cost records (the usage ledger). Set by the plugin. */ + private usageRecorder?: (record: UsageRecord) => void; + + /** Wire the per-turn usage ledger sink. Called once after construction by the + * plugin for both chat-panel and channel sessions. */ + setUsageRecorder(fn: (record: UsageRecord) => void): void { + this.usageRecorder = fn; + } private statsListeners = new Set<(s: ChatSessionStats) => void>(); /** Subscribe to stats changes. Fires once immediately with current state. */ @@ -1008,6 +1016,9 @@ export class ChatSession { } this.stats.turnCount += 1; dirty = true; + // Ledger this turn's tokens + cost so chat/channel usage counts toward the + // dashboard totals (runs are counted separately via their run logs). + this.recordTurnUsage(event as unknown as Record, costDelta); } // After digesting a result event, evaluate the auto-compact threshold. @@ -1021,6 +1032,33 @@ export class ChatSession { if (dirty) this.emitStats(); } + /** Emit one usage-ledger record for the just-completed turn. Token breakdown + * comes from the result event's aggregate `usage`; cost is the CLI's per-turn + * `total_cost_usd` when present (else estimated from tokens at read time). */ + private recordTurnUsage(event: Record, costUsd: number): void { + if (!this.usageRecorder) return; + const usage = event.usage as Record | undefined; + const num = (v: unknown) => (typeof v === "number" ? v : 0); + const inputTokens = num(usage?.input_tokens); + const outputTokens = num(usage?.output_tokens); + const cacheReadTokens = num(usage?.cache_read_input_tokens); + const cacheCreateTokens = num(usage?.cache_creation_input_tokens); + const totalTokens = inputTokens + outputTokens + cacheReadTokens + cacheCreateTokens; + if (totalTokens === 0 && costUsd === 0) return; + this.usageRecorder({ + ts: new Date().toISOString(), + agent: this.agent.name, + source: this.channelName ? "channel" : "chat", + model: this.stats.concreteModel ?? this.agent.model, + inputTokens, + outputTokens, + cacheReadTokens, + cacheCreateTokens, + totalTokens, + ...(costUsd > 0 ? { costUsd } : {}), + }); + } + /** If the agent has autoCompactThreshold set and the latest turn's * context fraction is above it, queue a /compact for the next user turn. * Rate-limited to once per 30s to avoid loops if the CLI reports odd usage. */ diff --git a/src/services/fleetRuntime.ts b/src/services/fleetRuntime.ts index 5bc54f7..fb82543 100644 --- a/src/services/fleetRuntime.ts +++ b/src/services/fleetRuntime.ts @@ -24,6 +24,7 @@ import type { SkillCandidate, SkillProposal, TaskConfig, + UsageRecord, } from "../types"; export class FleetRuntime { @@ -47,6 +48,9 @@ export class FleetRuntime { * for days that actually had activity. */ private chartRuns: RunLogData[] = []; private static readonly CHART_WINDOW_DAYS = 14; + /** Chat/channel usage records over the chart window. Feeds the comprehensive + * token/cost totals (runs are counted from run logs). */ + private recentUsage: UsageRecord[] = []; private statusChangeListeners = new Set<() => void>(); private runOutputListeners = new Map void>>(); private runOutputBuffers = new Map(); @@ -64,8 +68,11 @@ export class FleetRuntime { private heartbeatRegisteredAt = 0; /** Tracks agents with a heartbeat currently in-flight (prevents duplicate runs). */ private heartbeatsInFlight = new Set(); - /** Callback for heartbeat result delivery (e.g. Slack posting). Set by main.ts. */ - private heartbeatResultHandler?: (agentName: string, channel: string, output: string) => void; + /** Callback for run-result channel delivery (e.g. Slack/Discord posting). Set by + * main.ts. `source` is "heartbeat" for heartbeat runs or the task id otherwise, + * so the handler can label the post. `target` is an optional transport-native + * destination id (post to a specific channel) — empty means broadcast/DM. */ + private channelResultHandler?: (agentName: string, channel: string, output: string, source: string, target: string) => void; /** Single choke point for memory writes (per-agent locked). */ private readonly memoryWriter: MemoryWriter; @@ -105,11 +112,13 @@ export class FleetRuntime { } /** - * Register a callback that receives heartbeat results for delivery to external - * channels (e.g. Slack). Called from main.ts after the ChannelManager is ready. + * Register a callback that receives run results for delivery to external + * channels (e.g. Slack, Discord). Fires for heartbeat runs (using the agent's + * heartbeatChannel) and for any scheduled/manual task that sets a `channel` + * field. Called from main.ts after the ChannelManager is ready. */ - onHeartbeatResult(handler: (agentName: string, channel: string, output: string) => void): void { - this.heartbeatResultHandler = handler; + onChannelResult(handler: (agentName: string, channel: string, output: string, source: string, target: string) => void): void { + this.channelResultHandler = handler; } async refreshFromVault(): Promise { @@ -139,6 +148,22 @@ export class FleetRuntime { const since = new Date(); since.setDate(since.getDate() - (FleetRuntime.CHART_WINDOW_DAYS - 1)); this.chartRuns = await this.repository.listRunsSince(since); + this.recentUsage = await this.repository.readUsageSince(since); + } + + /** Chat/channel usage records over the chart window (for token/cost totals). */ + getUsageRecords(): UsageRecord[] { + return this.recentUsage; + } + + /** Append a chat/channel turn's usage to the ledger and reflect it live in the + * cached totals. Fire-and-forget from ChatSession via the plugin. */ + recordUsage(record: UsageRecord): void { + this.recentUsage.push(record); + void this.repository.appendUsage(record).catch((err) => { + console.warn("Agent Fleet: failed to append usage record", err); + }); + this.emitStatusChange(); } getAgentState(agentName: string): AgentRuntimeState { @@ -523,7 +548,18 @@ export class FleetRuntime { return { ok: false, message: "A reflection is already in progress." }; } this.reflectionsInFlight.add(agentName); - const nowIso = new Date().toISOString(); + const started = new Date().toISOString(); + const nowIso = started; + const reflectionTaskId = `reflection-${Date.now()}`; + // Surface the reflection as a running agent (overview "Active Agents" card + + // sidebar status) with live output, exactly like a task/heartbeat run. + this.runtimeState.set(agentName, { + status: "running", + currentTaskId: reflectionTaskId, + runStarted: started, + }); + this.runOutputBuffers.set(agentName, ""); + this.emitStatusChange(); try { // Guarantee the legacy→v2 migration (and its raw-archive seeding) has run // before we read/consolidate, so a first reflection can't lose pre-v2 @@ -541,7 +577,7 @@ export class FleetRuntime { const task: TaskConfig = { filePath: "", - taskId: `reflection-${Date.now()}`, + taskId: reflectionTaskId, agent: agent.name, type: "immediate", priority: "low", @@ -556,9 +592,17 @@ export class FleetRuntime { // Gate the spawn through the reflection semaphore, and suppress memory // capture for the reflection run itself (it consolidates memory; it must - // not emit new captures or carry the capture instruction). + // not emit new captures or carry the capture instruction). Stream output so + // the overview shows live progress. const result = await this.withReflectionSlot(() => - this.executor.execute(agent, task, prompt, undefined, { suppressMemoryCapture: true }), + this.executor.execute(agent, task, prompt, (chunk) => { + const current = this.runOutputBuffers.get(agentName) ?? ""; + this.runOutputBuffers.set(agentName, current + chunk); + const listeners = this.runOutputListeners.get(agentName); + if (listeners) { + for (const listener of listeners) listener(chunk); + } + }, { suppressMemoryCapture: true }), ); const parsed = parseReflectionOutput(result.outputText); @@ -572,20 +616,79 @@ export class FleetRuntime { await this.generateProposals(agent, merged, nowIso); } + const message = applied + ? "Reflection complete — working memory consolidated." + : "Reflection produced no memory block; working memory left unchanged."; + + // Record the reflection as a run so it shows in Recent Activity / run + // history with its full output — the user can confirm it ran and see what + // it consolidated. A clean CLI exit is "success" even if nothing changed; + // `finalResult` carries the consolidation outcome. + const runStatus: RunStatus = + result.exitCode === 0 || result.exitCode === null ? "success" : "failure"; + const run: RunLogData = { + runId: result.runId, + agent: agent.name, + task: reflectionTaskId, + status: runStatus, + started, + completed: new Date().toISOString(), + durationSeconds: result.durationSeconds, + tokensUsed: result.tokensUsed, + costUsd: result.costUsd, + model: result.resolvedModel || agent.reflection.model || agent.model, + modelSource: result.modelSource, + concreteModel: result.concreteModel, + exitCode: result.exitCode, + tags: Array.from(new Set([...agent.tags, "reflection"])), + prompt: result.prompt, + output: result.outputText, + toolsUsed: result.toolsUsed.map((tool) => `${tool.tool}${tool.command ? `: ${tool.command}` : ""}`), + finalResult: message, + stderr: result.stderr, + }; + const runPath = await this.repository.writeRunLog(run); await this.refreshRunCaches(); - this.emitStatusChange(); - return applied - ? { ok: true, message: "Reflection complete — working memory consolidated." } - : { - ok: false, - message: "Reflection produced no memory block; working memory left unchanged.", - }; + this.runtimeState.set(agentName, { + status: runStatus === "success" ? "idle" : "error", + currentRunId: result.runId, + lastRun: { ...run, filePath: runPath }, + }); + return { ok: applied, message }; } catch (error) { const msg = error instanceof Error ? error.message : String(error); console.warn(`Agent Fleet: reflection failed for "${agentName}":`, error); + // Record the failure as a run too, so it's visible in activity. + const run: RunLogData = { + runId: randomUUID(), + agent: agentName, + task: reflectionTaskId, + status: "failure", + started, + completed: new Date().toISOString(), + durationSeconds: Math.round((Date.now() - new Date(started).getTime()) / 1000), + model: agent.reflection.model || agent.model, + exitCode: 1, + tags: Array.from(new Set([...agent.tags, "reflection"])), + prompt: "", + output: `Reflection failed: ${msg}`, + toolsUsed: [], + }; + let lastRun: RunLogData = run; + try { + const runPath = await this.repository.writeRunLog(run); + await this.refreshRunCaches(); + lastRun = { ...run, filePath: runPath }; + } catch (writeErr) { + console.warn(`Agent Fleet: failed to write reflection run log for "${agentName}"`, writeErr); + } + this.runtimeState.set(agentName, { status: "error", lastRun }); return { ok: false, message: `Reflection failed: ${msg}` }; } finally { this.reflectionsInFlight.delete(agentName); + this.runOutputBuffers.delete(agentName); + this.runOutputListeners.delete(agentName); + this.emitStatusChange(); } } @@ -693,13 +796,29 @@ export class FleetRuntime { } } - // Post heartbeat results to Slack channel if configured + // Post run results to a channel if configured. Heartbeat runs use the + // agent's heartbeatChannel; regular scheduled/manual tasks use their own + // `channel` field. This lets any task — not just the heartbeat — deliver + // its output to Slack/Discord/Telegram. const isHeartbeatRun = task.tags.includes("heartbeat"); - if (isHeartbeatRun && !wasAborted && agent.heartbeatChannel && run.output.trim()) { + const deliveryChannel = isHeartbeatRun ? agent.heartbeatChannel : (task.channel ?? ""); + // Both heartbeats and tasks may post to a specific channel id via their + // target field (HEARTBEAT.md `channel_target` / task `channel_target`); + // an empty target falls back to broadcast (DM to the first allowed user). + const deliveryTarget = isHeartbeatRun + ? (agent.heartbeatChannelTarget ?? "") + : (task.channelTarget ?? ""); + if (deliveryChannel && !wasAborted && run.output.trim()) { try { - this.heartbeatResultHandler?.(agent.name, agent.heartbeatChannel, run.output); + this.channelResultHandler?.( + agent.name, + deliveryChannel, + run.output, + isHeartbeatRun ? "heartbeat" : task.taskId, + deliveryTarget, + ); } catch (err) { - console.warn(`Agent Fleet: heartbeat channel delivery failed for ${agent.name}`, err); + console.warn(`Agent Fleet: channel delivery failed for ${agent.name}`, err); } } diff --git a/src/settingsTab.ts b/src/settingsTab.ts index 6357e6d..d7eccbd 100644 --- a/src/settingsTab.ts +++ b/src/settingsTab.ts @@ -288,11 +288,13 @@ export class AgentFleetSettingTab extends PluginSettingTab { dropdown .addOption("slack", "Slack") .addOption("telegram", "Telegram") + .addOption("discord", "Discord") .setValue("slack") .onChange((value) => { state.type = value; slackFields.setCssStyles({ display: value === "slack" ? "" : "none" }); telegramFields.setCssStyles({ display: value === "telegram" ? "" : "none" }); + discordFields.setCssStyles({ display: value === "discord" ? "" : "none" }); }), ); @@ -330,6 +332,19 @@ export class AgentFleetSettingTab extends PluginSettingTab { }); }); + // Discord-specific fields + const discordFields = addForm.createDiv(); + discordFields.setCssStyles({ display: "none" }); + new Setting(discordFields) + .setName("Bot token") + .setDesc("From the Discord Developer Portal → your application → Bot → Reset Token. Enable the Message Content intent on the same page.") + .addText((text) => { + text.inputEl.type = "password"; + text.setPlaceholder("MTA...").onChange((value) => { + state.botToken = value.trim(); + }); + }); + new Setting(addForm).addButton((button) => button .setButtonText("Add credential") @@ -342,6 +357,8 @@ export class AgentFleetSettingTab extends PluginSettingTab { let entry: ChannelCredentialEntry; if (state.type === "telegram") { entry = { type: "telegram", botToken: state.botToken }; + } else if (state.type === "discord") { + entry = { type: "discord", botToken: state.botToken }; } else { if (!state.appToken) { new Notice("Slack requires both bot token and app-level token."); diff --git a/src/types.ts b/src/types.ts index 29f04ad..ab7be20 100644 --- a/src/types.ts +++ b/src/types.ts @@ -44,7 +44,7 @@ export interface FleetSettings { mcpImported?: boolean; } -export type ChannelType = "slack" | "telegram"; +export type ChannelType = "slack" | "telegram" | "discord"; export type ChannelStatus = | "connected" @@ -57,7 +57,8 @@ export type ChannelStatus = export type ChannelCredentialEntry = | { type: "slack"; botToken: string; appToken: string } - | { type: "telegram"; botToken: string }; + | { type: "telegram"; botToken: string } + | { type: "discord"; botToken: string }; export interface ChannelConfig { filePath: string; @@ -82,6 +83,29 @@ export interface ValidationIssue { message: string; } +/** + * One token-consuming event recorded to the usage ledger + * (`_fleet/usage/YYYY-MM-DD.jsonl`). Chat and channel turns append these so their + * token/cost are counted in the dashboard totals (runs/heartbeats/reflections + * already carry tokens+cost in their run logs). `costUsd` is the CLI-reported + * dollar cost when available; when absent it's estimated from tokens at read time. + */ +export interface UsageRecord { + /** ISO-8601 timestamp of the turn. */ + ts: string; + agent: string; + /** Origin of the usage — only chat/channel are ledgered; runs live in run logs. */ + source: "chat" | "channel"; + model: string; + inputTokens: number; + outputTokens: number; + cacheReadTokens: number; + cacheCreateTokens: number; + totalTokens: number; + /** CLI-reported cost for this turn; undefined → estimate from tokens. */ + costUsd?: number; +} + export interface SkillConfig { filePath: string; name: string; @@ -178,6 +202,10 @@ export interface AgentConfig { heartbeatNotify: boolean; /** Channel name to post heartbeat results to (e.g. "my-slack"). Empty = no channel post. */ heartbeatChannel: string; + /** Specific channel/conversation id to post heartbeat results to within + * heartbeatChannel (e.g. a Discord channel id). Empty = broadcast as a DM to + * the channel's first allowed user. Mirrors a task's channelTarget. */ + heartbeatChannelTarget: string; /** Wiki Keeper scope config. Present only on agents that manage a wiki * scope. See WIKI_KEEPER_DESIGN.md. */ wikiKeeper?: WikiKeeperConfig; @@ -206,6 +234,16 @@ export interface TaskConfig { effort?: string; /** Optional per-task model override. Empty/absent = inherit from agent. */ model?: string; + /** Channel name to post this task's output to (e.g. "my-discord"). Empty/absent + * = no channel post (run log only). Mirrors an agent's heartbeatChannel but is + * per-task, so scheduled tasks — not just the heartbeat — can deliver to a + * channel. */ + channel?: string; + /** Optional transport-native destination id within `channel` (Discord/Slack + * channel id, Telegram chat id). When set, the task posts directly to that + * channel/conversation; when empty, delivery falls back to the channel's + * broadcast (DM to the first allowed user). Ignored unless `channel` is set. */ + channelTarget?: string; tags: string[]; body: string; } diff --git a/src/utils/claudeSettings.test.ts b/src/utils/claudeSettings.test.ts index 63fc77e..7dd4c62 100644 --- a/src/utils/claudeSettings.test.ts +++ b/src/utils/claudeSettings.test.ts @@ -35,6 +35,7 @@ function makeAgent(overrides: Partial = {}): AgentConfig { heartbeatBody: "", heartbeatNotify: false, heartbeatChannel: "", + heartbeatChannelTarget: "", ...overrides, }; } diff --git a/src/utils/memoryFormat.test.ts b/src/utils/memoryFormat.test.ts index 47ae3a6..4d6d1d3 100644 --- a/src/utils/memoryFormat.test.ts +++ b/src/utils/memoryFormat.test.ts @@ -212,6 +212,18 @@ describe("memoryFormat", () => { expect(prefs?.entries.filter((e) => /post to #wiki/i.test(e.text))).toHaveLength(1); }); + it("carryForwardPinnedPreferences trusts the model and does not re-add a reworded pin", () => { + // The model UPDATED a pin (14 → 24) and emitted pins, so we trust its + // consolidation rather than re-adding the byte-different stale version. + let prev = emptyWorkingMemory("p", "A"); + prev = appendEntries(prev, [{ text: "warm-path priority: 14 shortlists", pinned: true }], "Preferences"); + const consolidated = parseSectionsFromBody("## Preferences\n- [pin] warm-path priority: 24 shortlists"); + const merged = carryForwardPinnedPreferences(prev, consolidated); + const prefs = merged.find((s) => s.name === "Preferences"); + expect(prefs?.entries).toHaveLength(1); + expect(prefs?.entries[0]?.text).toContain("24 shortlists"); + }); + it("redactRememberForDisplay strips complete blocks and holds partial/unclosed tags", () => { // Complete block removed. expect(redactRememberForDisplay("a [REMEMBER] x [/REMEMBER] b")).toBe("a b"); diff --git a/src/utils/memoryFormat.ts b/src/utils/memoryFormat.ts index b49ffb9..072bb94 100644 --- a/src/utils/memoryFormat.ts +++ b/src/utils/memoryFormat.ts @@ -393,19 +393,23 @@ export function carryForwardPinnedPreferences( prev: WorkingMemory | null, sections: MemorySection[], ): MemorySection[] { - // Rescue pinned entries from ANY prior section (not just Preferences) — a - // pin could have landed in Recent/Observations — so a reflection that omits - // it can never silently drop a pinned fact. + // Rescue pinned entries from ANY prior section (not just Preferences) — a pin + // could have landed in Recent/Observations. const pins = (prev?.sections ?? []) .flatMap((s) => s.entries) .filter((e) => e.pinned); if (pins.length === 0) return sections; - const present = new Set( - sections.flatMap((s) => s.entries).map((e) => e.text.trim().toLowerCase()), - ); - const missing = pins.filter((e) => !present.has(e.text.trim().toLowerCase())); - if (missing.length === 0) return sections; + // The reflection model is explicitly instructed to preserve AND update pinned + // facts, so a reworded/updated pin ("14 shortlists" → "24 shortlists") is the + // model doing its job — not a drop. An earlier version re-added every prev pin + // that wasn't byte-identical to the new output, which undid the model's + // consolidation and piled up near-duplicate pins on every reflection. Instead, + // trust the consolidated output whenever it contains ANY pinned entry, and only + // rescue the previous pins when the reflection produced NONE at all — the real + // signal that it dropped them rather than updating them. + const newHasPins = sections.some((s) => s.entries.some((e) => e.pinned)); + if (newHasPins) return sections; const out = sections.map((s) => ({ name: s.name, entries: [...s.entries] })); let prefs = out.find((s) => s.name === "Preferences"); @@ -413,7 +417,7 @@ export function carryForwardPinnedPreferences( prefs = { name: "Preferences", entries: [] }; out.unshift(prefs); } - prefs.entries.unshift(...missing); + prefs.entries.unshift(...pins); return out; } diff --git a/src/utils/pricing.test.ts b/src/utils/pricing.test.ts new file mode 100644 index 0000000..b744dbc --- /dev/null +++ b/src/utils/pricing.test.ts @@ -0,0 +1,37 @@ +import { describe, expect, it } from "vitest"; +import { estimateCostFromBreakdown, estimateCostFromTotal } from "./pricing"; + +describe("pricing", () => { + it("prices a Claude breakdown using the model's per-type rates", () => { + // opus: input $15, output $75, cacheRead $1.50 per 1M. + const cost = estimateCostFromBreakdown("claude-opus-4-7", { + inputTokens: 1_000_000, + outputTokens: 1_000_000, + cacheReadTokens: 1_000_000, + cacheCreateTokens: 0, + }); + expect(cost).toBeCloseTo(15 + 75 + 1.5, 5); + }); + + it("matches family by substring (sonnet/haiku)", () => { + const sonnet = estimateCostFromBreakdown("sonnet", { inputTokens: 1_000_000, outputTokens: 0, cacheReadTokens: 0, cacheCreateTokens: 0 }); + const haiku = estimateCostFromBreakdown("claude-haiku-4-5", { inputTokens: 1_000_000, outputTokens: 0, cacheReadTokens: 0, cacheCreateTokens: 0 }); + expect(sonnet).toBeCloseTo(3, 5); + expect(haiku).toBeCloseTo(1, 5); + }); + + it("falls back to a default rate for unknown models", () => { + const cost = estimateCostFromBreakdown("some-unknown-model", { inputTokens: 1_000_000, outputTokens: 0, cacheReadTokens: 0, cacheCreateTokens: 0 }); + expect(cost).toBeCloseTo(3, 5); // default = sonnet-ish input rate + }); + + it("estimates from a total-token count with a blended rate", () => { + // haiku blended = 0.7*1 + 0.3*5 = 2.2 per 1M. + expect(estimateCostFromTotal("haiku", 1_000_000)).toBeCloseTo(2.2, 5); + }); + + it("returns zero cost for zero tokens", () => { + expect(estimateCostFromBreakdown("opus", { inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheCreateTokens: 0 })).toBe(0); + expect(estimateCostFromTotal("opus", 0)).toBe(0); + }); +}); diff --git a/src/utils/pricing.ts b/src/utils/pricing.ts new file mode 100644 index 0000000..642db83 --- /dev/null +++ b/src/utils/pricing.ts @@ -0,0 +1,58 @@ +// Token → USD cost estimation, used as a fallback when the CLI doesn't report a +// dollar cost (Codex agents, or any turn missing `total_cost_usd`). Claude runs +// carry an exact CLI cost; this table only fills the gaps. Rates are USD per +// MILLION tokens and will drift as vendors change pricing — update as needed. + +interface ModelRates { + input: number; + output: number; + cacheWrite: number; + cacheRead: number; +} + +/** Per-family rates (USD / 1M tokens). Matched by substring against the model id. */ +const RATE_TABLE: Array<{ match: RegExp; rates: ModelRates }> = [ + { match: /opus/i, rates: { input: 15, output: 75, cacheWrite: 18.75, cacheRead: 1.5 } }, + { match: /sonnet/i, rates: { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.3 } }, + { match: /haiku/i, rates: { input: 1, output: 5, cacheWrite: 1.25, cacheRead: 0.1 } }, + // OpenAI Codex / GPT slugs — approximate; no cache-tier distinction. + { match: /gpt-5|codex|o[0-9]/i, rates: { input: 1.25, output: 10, cacheWrite: 1.25, cacheRead: 0.125 } }, +]; + +/** Fallback when the model id matches nothing (use mid-tier Sonnet-ish rates). */ +const DEFAULT_RATES: ModelRates = { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.3 }; + +function ratesFor(model: string): ModelRates { + for (const { match, rates } of RATE_TABLE) { + if (match.test(model)) return rates; + } + return DEFAULT_RATES; +} + +export interface TokenBreakdown { + inputTokens: number; + outputTokens: number; + cacheReadTokens: number; + cacheCreateTokens: number; +} + +/** Estimate USD cost from a full per-type token breakdown (most accurate). */ +export function estimateCostFromBreakdown(model: string, b: TokenBreakdown): number { + const r = ratesFor(model); + return ( + (b.inputTokens * r.input + + b.outputTokens * r.output + + b.cacheCreateTokens * r.cacheWrite + + b.cacheReadTokens * r.cacheRead) / + 1_000_000 + ); +} + +/** Estimate USD cost when only a single total-token count is known (e.g. a run + * log, which doesn't store the input/output split). Assumes a ~70/30 input/output + * blend — a rough best-effort, used only when the CLI reported no cost. */ +export function estimateCostFromTotal(model: string, totalTokens: number): number { + const r = ratesFor(model); + const blended = 0.7 * r.input + 0.3 * r.output; + return (totalTokens * blended) / 1_000_000; +} diff --git a/src/views/agentChatView.ts b/src/views/agentChatView.ts index 6446604..e03b78f 100644 --- a/src/views/agentChatView.ts +++ b/src/views/agentChatView.ts @@ -362,7 +362,7 @@ export class AgentChatView extends ItemView { // resolved (in switchToAgent), so we don't pre-check here. this.selectedConversationId = ""; this.textarea.disabled = false; - this.textarea.placeholder = "Message the agent\u2026 (Ctrl+Enter to send)"; + this.textarea.placeholder = "Message the agent\u2026 (Shift+Enter for newline)"; void this.switchToAgent(val); }; @@ -393,7 +393,7 @@ export class AgentChatView extends ItemView { this.textarea = inputRow.createEl("textarea", { cls: "af-chat-input", - attr: { placeholder: "Message the agent\u2026 (Ctrl+Enter to send)", rows: "1" }, + attr: { placeholder: "Message the agent\u2026 (Shift+Enter for newline)", rows: "1" }, }); this.sendBtn = inputRow.createEl("button", { cls: "af-chat-send-btn" }); createIcon(this.sendBtn, "arrow-up", "af-btn-icon"); @@ -415,7 +415,10 @@ export class AgentChatView extends ItemView { this.sendBtn.onclick = () => void this.handleSend(); this.textarea.onkeydown = (e: KeyboardEvent) => { - if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) { + // Enter sends; Shift+Enter inserts a newline (common chat convention). + // Skip while an IME composition is active so Enter confirms the candidate + // rather than sending a half-composed message. + if (e.key === "Enter" && !e.shiftKey && !e.isComposing) { e.preventDefault(); void this.handleSend(); } @@ -582,7 +585,7 @@ export class AgentChatView extends ItemView { } this.leaf.updateHeader(); - this.textarea.placeholder = "Message the agent\u2026 (Ctrl+Enter to send)"; + this.textarea.placeholder = "Message the agent\u2026 (Shift+Enter for newline)"; // Load the session if not already displayed. Check for actual chat bubbles // (not just the empty state divs) to decide whether switchToAgent is needed. @@ -690,6 +693,7 @@ export class AgentChatView extends ItemView { this.app.vault, { inAppConversationId: resolvedId, mcpAuth: this.plugin.mcpAuth }, ); + session.setUsageRecorder((r) => this.plugin.runtime.recordUsage(r)); managed = { session }; this.sessions.set(key, managed); await session.loadPersistedState(); @@ -1417,7 +1421,7 @@ export class AgentChatView extends ItemView { const input = composer.createEl("textarea", { cls: "af-chat-input af-thread-input", - attr: { placeholder: "Message in thread\u2026 (Ctrl+Enter to send)", rows: "1" }, + attr: { placeholder: "Message in thread\u2026 (Shift+Enter for newline)", rows: "1" }, }); // Paste image support @@ -1532,7 +1536,8 @@ export class AgentChatView extends ItemView { sendBtn.onclick = () => void submit(); input.onkeydown = (e: KeyboardEvent) => { - if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) { + // Enter sends; Shift+Enter inserts a newline. + if (e.key === "Enter" && !e.shiftKey && !e.isComposing) { e.preventDefault(); void submit(); } diff --git a/src/views/dashboardView.ts b/src/views/dashboardView.ts index afcb86d..ddf18c7 100644 --- a/src/views/dashboardView.ts +++ b/src/views/dashboardView.ts @@ -2,7 +2,8 @@ import { ItemView, MarkdownRenderer, Notice, TFile, WorkspaceLeaf, setIcon } fro import { IconPickerModal } from "../modals/iconPickerModal"; import { VIEW_TYPE_DASHBOARD } from "../constants"; import type AgentFleetPlugin from "../main"; -import type { AgentConfig, AgentHealth, ChannelConfig, McpServer, McpTool, RunLogData, SkillConfig, TaskConfig } from "../types"; +import type { AgentConfig, AgentHealth, ChannelConfig, McpServer, McpTool, RunLogData, SkillConfig, TaskConfig, UsageRecord } from "../types"; +import { estimateCostFromBreakdown, estimateCostFromTotal } from "../utils/pricing"; import { truncate, slugify, parseMarkdownWithFrontmatter, stringifyMarkdownWithFrontmatter } from "../utils/markdown"; import { splitLines } from "../utils/platform"; import { createIcon } from "../utils/icons"; @@ -499,8 +500,9 @@ export class FleetDashboardView extends ItemView { this.renderStatCard(grid, "Runs Today", String(todayRuns.length), "", "activity", `${passed} passed \u00B7 ${failed} failed \u00B7 ${status.running} running`); - const totalTokens = todayRuns.reduce((sum, r) => sum + (r.tokensUsed ?? 0), 0); - const totalCost = todayRuns.reduce((sum, r) => sum + (r.costUsd ?? 0), 0); + // Comprehensive: run logs (tasks/heartbeats/reflections) + chat/channel usage. + const todayUsage = this.plugin.runtime.getUsageRecords().filter((u) => this.runToLocalDate(u.ts) === todayStr); + const { tokens: totalTokens, cost: totalCost } = this.combinedTotals(todayRuns, todayUsage); const costSuffix = totalCost > 0 ? ` \u00B7 $${totalCost.toFixed(2)}` : ""; this.renderStatCard(grid, "Tokens Used", formatTokenCount(totalTokens), "", "zap", `today${costSuffix}`); @@ -567,6 +569,35 @@ export class FleetDashboardView extends ItemView { return this.toLocalDateStr(new Date(started)); } + /** Cost of a run log: the CLI-reported dollars, or an estimate from total + * tokens when the CLI reported none (e.g. Codex). */ + private runCost(r: RunLogData): number { + return r.costUsd ?? estimateCostFromTotal(r.model, r.tokensUsed ?? 0); + } + + /** Cost of a chat/channel usage record: CLI-reported, or estimated from the + * per-type token breakdown when absent. */ + private usageCost(u: UsageRecord): number { + return u.costUsd ?? estimateCostFromBreakdown(u.model, { + inputTokens: u.inputTokens, + outputTokens: u.outputTokens, + cacheReadTokens: u.cacheReadTokens, + cacheCreateTokens: u.cacheCreateTokens, + }); + } + + /** Comprehensive token + cost totals across run logs AND chat/channel usage + * records, with the pricing fallback applied per source. */ + private combinedTotals(runs: RunLogData[], usage: UsageRecord[]): { tokens: number; cost: number } { + const tokens = + runs.reduce((s, r) => s + (r.tokensUsed ?? 0), 0) + + usage.reduce((s, u) => s + u.totalTokens, 0); + const cost = + runs.reduce((s, r) => s + this.runCost(r), 0) + + usage.reduce((s, u) => s + this.usageCost(u), 0); + return { tokens, cost }; + } + private buildChartData(runs: RunLogData[], days: number): BarChartDay[] { const result: BarChartDay[] = []; const now = new Date(); @@ -616,7 +647,11 @@ export class FleetDashboardView extends ItemView { : undefined; const taskLabel = runningTask ? ` → ${runningTask.taskId}` - : state.status === "running" ? " → Heartbeat" : ""; + : state.currentTaskId?.startsWith("reflection-") + ? " → Reflection" + : state.currentTaskId?.startsWith("heartbeat-") + ? " → Heartbeat" + : state.status === "running" ? " → Heartbeat" : ""; const header = card.createDiv({ cls: "af-streaming-card-header" }); header.createSpan({ cls: "af-dot pulse", attr: { style: "background: var(--af-yellow)" } }); @@ -626,14 +661,20 @@ export class FleetDashboardView extends ItemView { } const output = card.createDiv({ cls: "af-streaming-output" }); - const buffer = this.plugin.runtime.getRunOutputBuffer(agentName); - const lines = splitLines(buffer).slice(-4); - output.setText(lines.join("\n")); + // Reflection reads a lot of context before emitting its memory block, so the + // buffer is empty for most of the run — show a placeholder instead of a blank + // card so it doesn't look stalled. + const placeholder = state.currentTaskId?.startsWith("reflection-") + ? "Consolidating memory…" + : "Working…"; + const renderBuffer = (buf: string) => { + const lines = splitLines(buf).filter((l) => l.trim().length > 0).slice(-4); + output.setText(lines.length > 0 ? lines.join("\n") : placeholder); + }; + renderBuffer(this.plugin.runtime.getRunOutputBuffer(agentName)); const unsub = this.plugin.runtime.onRunOutput(agentName, () => { - const updated = this.plugin.runtime.getRunOutputBuffer(agentName); - const updatedLines = splitLines(updated).slice(-4); - output.setText(updatedLines.join("\n")); + renderBuffer(this.plugin.runtime.getRunOutputBuffer(agentName)); output.scrollTop = output.scrollHeight; }); this.streamingUnsubscribes.push(unsub); @@ -1058,8 +1099,9 @@ export class FleetDashboardView extends ItemView { const successRuns = runs.filter((r) => r.status === "success").length; const successRate = totalRuns > 0 ? Math.round((successRuns / totalRuns) * 100) : 0; const avgTime = totalRuns > 0 ? Math.round(runs.reduce((s, r) => s + r.durationSeconds, 0) / totalRuns) : 0; - const totalTokens = runs.reduce((s, r) => s + (r.tokensUsed ?? 0), 0); - const totalCostAgent = runs.reduce((s, r) => s + (r.costUsd ?? 0), 0); + // Comprehensive: this agent's run logs + its chat/channel usage over the window. + const agentUsage = this.plugin.runtime.getUsageRecords().filter((u) => u.agent === agent.name); + const { tokens: totalTokens, cost: totalCostAgent } = this.combinedTotals(runs, agentUsage); const costSuffixAgent = totalCostAgent > 0 ? ` \u00B7 $${totalCostAgent.toFixed(2)}` : ""; this.renderStatCard(statsRow, "Total Runs", String(totalRuns), "", "activity", "all time"); @@ -2121,6 +2163,7 @@ export class FleetDashboardView extends ItemView { const typeSelect = typeRow.createEl("select", { cls: "af-form-select" }); typeSelect.createEl("option", { text: "slack", attr: { value: "slack" } }); typeSelect.createEl("option", { text: "telegram", attr: { value: "telegram" } }); + typeSelect.createEl("option", { text: "discord", attr: { value: "discord" } }); typeSelect.addEventListener("change", () => { state.type = typeSelect.value; }); // Credential dropdown @@ -2205,7 +2248,7 @@ export class FleetDashboardView extends ItemView { const usersLabel = accessSection.createDiv({ cls: "af-form-label" }); usersLabel.setText("Allowed users"); - this.addTooltip(usersLabel, "Slack user IDs (U...), one per line. Only listed users can reach the bot."); + this.addTooltip(usersLabel, "User IDs, one per line — Slack (U...), Telegram (numeric), or Discord (numeric snowflakes). Only listed users can reach the bot."); const usersTextarea = accessSection.createEl("textarea", { cls: "af-create-prompt-textarea", attr: { placeholder: "U0AQW6P37N1\nU0BXYZ12345", rows: "4" }, @@ -2389,7 +2432,7 @@ export class FleetDashboardView extends ItemView { const typeRow = detailsSection.createDiv({ cls: "af-form-row" }); typeRow.createDiv({ cls: "af-form-label", text: "Type" }); const typeSelect = typeRow.createEl("select", { cls: "af-form-select" }); - for (const t of ["slack", "telegram"] as const) { + for (const t of ["slack", "telegram", "discord"] as const) { const opt = typeSelect.createEl("option", { text: t, attr: { value: t } }); if (t === channel.type) opt.selected = true; } @@ -3573,6 +3616,7 @@ export class FleetDashboardView extends ItemView { heartbeatBody: "", heartbeatNotify: true, heartbeatChannel: "", + heartbeatChannelTarget: "", autoCompactThreshold: 85, wikiReferences: [] as string[], }; @@ -3838,7 +3882,17 @@ export class FleetDashboardView extends ItemView { for (const ch of createSnapshot.channels) { hbChannelSelect.createEl("option", { text: ch.name, attr: { value: ch.name } }); } - hbChannelSelect.addEventListener("change", () => { state.heartbeatChannel = hbChannelSelect.value; }); + hbChannelSelect.addEventListener("change", () => { state.heartbeatChannel = hbChannelSelect.value; syncHbTarget(); }); + + const hbTargetRow = hbBody.createDiv({ cls: "af-form-row" }); + const hbTargetLabel = hbTargetRow.createDiv({ cls: "af-form-label" }); + hbTargetLabel.setText("Target ID"); + this.addTooltip(hbTargetLabel, "Specific channel id to post to (Discord/Slack channel id, Telegram chat id). Empty = DM the channel’s first allowed user."); + const hbTargetInput = hbTargetRow.createEl("input", { cls: "af-form-input", attr: { type: "text", placeholder: "Channel/chat id — empty = DM" } }); + hbTargetInput.value = state.heartbeatChannelTarget; + hbTargetInput.addEventListener("input", () => { state.heartbeatChannelTarget = hbTargetInput.value.trim(); }); + const syncHbTarget = () => { hbTargetRow.setCssStyles({ display: state.heartbeatChannel ? "" : "none" }); }; + syncHbTarget(); const hbInstructionLabel = hbBody.createDiv({ cls: "af-form-label" }); hbInstructionLabel.setCssStyles({ width: "auto" }); @@ -4010,6 +4064,7 @@ export class FleetDashboardView extends ItemView { schedule: state.heartbeatSchedule.trim(), notify: state.heartbeatNotify, channel: state.heartbeatChannel, + channelTarget: state.heartbeatChannel ? state.heartbeatChannelTarget : "", body: state.heartbeatBody.trim(), }); } @@ -4238,6 +4293,7 @@ export class FleetDashboardView extends ItemView { heartbeatBody: agent.heartbeatBody, heartbeatNotify: agent.heartbeatNotify, heartbeatChannel: agent.heartbeatChannel, + heartbeatChannelTarget: agent.heartbeatChannelTarget, autoCompactThreshold: agent.autoCompactThreshold ?? 85, wikiReferences: (agent.wikiReferences ?? []).map((r) => r.agent), }; @@ -4495,7 +4551,17 @@ export class FleetDashboardView extends ItemView { const opt = hbChannelSelect.createEl("option", { text: ch.name, attr: { value: ch.name } }); if (ch.name === state.heartbeatChannel) opt.selected = true; } - hbChannelSelect.addEventListener("change", () => { state.heartbeatChannel = hbChannelSelect.value; }); + hbChannelSelect.addEventListener("change", () => { state.heartbeatChannel = hbChannelSelect.value; syncHbTarget(); }); + + const hbTargetRow = hbBody.createDiv({ cls: "af-form-row" }); + const hbTargetLabel = hbTargetRow.createDiv({ cls: "af-form-label" }); + hbTargetLabel.setText("Target ID"); + this.addTooltip(hbTargetLabel, "Specific channel id to post to (Discord/Slack channel id, Telegram chat id). Empty = DM the channel’s first allowed user."); + const hbTargetInput = hbTargetRow.createEl("input", { cls: "af-form-input", attr: { type: "text", placeholder: "Channel/chat id — empty = DM" } }); + hbTargetInput.value = state.heartbeatChannelTarget; + hbTargetInput.addEventListener("input", () => { state.heartbeatChannelTarget = hbTargetInput.value.trim(); }); + const syncHbTarget = () => { hbTargetRow.setCssStyles({ display: state.heartbeatChannel ? "" : "none" }); }; + syncHbTarget(); const hbInstructionLabel = hbBody.createDiv({ cls: "af-form-label" }); hbInstructionLabel.setCssStyles({ width: "auto" }); @@ -4703,6 +4769,7 @@ export class FleetDashboardView extends ItemView { schedule: state.heartbeatSchedule.trim(), notify: state.heartbeatNotify, channel: state.heartbeatChannel, + channelTarget: state.heartbeatChannel ? state.heartbeatChannelTarget : "", body: state.heartbeatBody.trim(), }); } @@ -4717,6 +4784,50 @@ export class FleetDashboardView extends ItemView { }; } + /** + * Render the optional "post results to a channel" controls shared by the + * create and edit task forms: a channel picker plus a transport-native target + * id (Discord/Slack channel id, Telegram chat id). Empty target = broadcast/DM. + */ + private renderTaskChannelDelivery( + section: HTMLElement, + channels: ChannelConfig[], + state: { channel: string; channelTarget: string }, + ): void { + const channelRow = section.createDiv({ cls: "af-form-row" }); + const channelLabel = channelRow.createDiv({ cls: "af-form-label", text: "Channel" }); + const channelSelect = channelRow.createEl("select", { cls: "af-form-select" }); + channelSelect.createEl("option", { text: "— none (run log only) —", attr: { value: "" } }); + for (const ch of channels) { + const opt = channelSelect.createEl("option", { text: ch.name, attr: { value: ch.name } }); + if (ch.name === state.channel) opt.selected = true; + } + this.addTooltip( + channelLabel, + "Post this task’s full output to a channel when it finishes. Leave as none to only write the run log (the default batched behavior).", + ); + + const targetRow = section.createDiv({ cls: "af-form-row" }); + const targetLabel = targetRow.createDiv({ cls: "af-form-label", text: "Target ID" }); + const targetInput = targetRow.createEl("input", { + cls: "af-form-input", + attr: { type: "text", placeholder: "Discord/Slack channel ID or Telegram chat ID — empty = DM you" }, + }); + targetInput.value = state.channelTarget; + targetInput.addEventListener("input", () => { state.channelTarget = targetInput.value.trim(); }); + this.addTooltip( + targetLabel, + "Where in the channel to post. For Discord, enable Developer Mode and right-click the channel → Copy Channel ID. Leave empty to DM the first allowed user instead.", + ); + + const syncVisibility = () => { targetRow.setCssStyles({ display: state.channel ? "" : "none" }); }; + syncVisibility(); + channelSelect.addEventListener("change", () => { + state.channel = channelSelect.value; + syncVisibility(); + }); + } + // ═══════════════════════════════════════════════════════ // Create Task Page // ═══════════════════════════════════════════════════════ @@ -4754,6 +4865,8 @@ export class FleetDashboardView extends ItemView { catchUp: true, effort: "", model: "", + channel: "", + channelTarget: "", }; const form = page.createDiv({ cls: "af-create-form" }); @@ -4926,6 +5039,8 @@ export class FleetDashboardView extends ItemView { if (val === state.effort) opt.selected = true; } taskEffortSelect.addEventListener("change", () => { state.effort = taskEffortSelect.value; }); + // Channel delivery (optional) — post this task's output to a channel on completion. + this.renderTaskChannelDelivery(execSection, snapshot.channels, state); this.addTooltip( taskEffortLabel, "Overrides the agent\u2019s effort level for this task. Higher effort = more thinking tokens spent.", @@ -4965,6 +5080,8 @@ export class FleetDashboardView extends ItemView { catch_up: state.catchUp, effort: state.effort || undefined, model: state.model || undefined, + channel: state.channel || undefined, + channel_target: state.channel && state.channelTarget ? state.channelTarget : undefined, tags: parseTags(state.tags), }; @@ -5055,6 +5172,8 @@ export class FleetDashboardView extends ItemView { catchUp: task.catchUp, effort: task.effort ?? "", model: task.model ?? "", + channel: task.channel ?? "", + channelTarget: task.channelTarget ?? "", tags: task.tags.join(", "), body: task.body, }; @@ -5243,6 +5362,8 @@ export class FleetDashboardView extends ItemView { }; renderTaskModelPicker(state.agent); agentSelect.addEventListener("change", () => renderTaskModelPicker(agentSelect.value)); + // Channel delivery (optional) — post this task's output to a channel on completion. + this.renderTaskChannelDelivery(execSection, snapshot.channels, state); this.addTooltip( modelLabelEdit, "Override the agent\u2019s model for this task only. Useful for routing simple runs to haiku while the agent stays on opus for heavier work.", @@ -5292,6 +5413,8 @@ export class FleetDashboardView extends ItemView { catch_up: state.catchUp, effort: state.effort || undefined, model: state.model || "", + channel: state.channel || "", + channelTarget: state.channel && state.channelTarget ? state.channelTarget : "", tags: parseTags(state.tags), body: state.body.trim(), }); diff --git a/versions.json b/versions.json index 784b3bb..4411a68 100644 --- a/versions.json +++ b/versions.json @@ -28,5 +28,6 @@ "0.13.3": "1.11.4", "0.13.4": "1.11.4", "0.13.5": "1.11.4", - "0.13.6": "1.11.4" + "0.13.6": "1.11.4", + "0.14.0": "1.11.4" } \ No newline at end of file