@agentdeploymentco/bishop 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -3
- package/dist/cli.js +12 -12
- package/dist/gmail/fake.js +6 -4
- package/dist/gmail/interface.js +2 -2
- package/dist/gmail/message.js +10 -9
- package/dist/gmail/presence.js +3 -3
- package/dist/harness/claude.js +5 -5
- package/dist/harness/codex.js +4 -4
- package/dist/interface/phrases.js +1 -1
- package/dist/outgoing.js +2 -0
- package/dist/schedule/presence.js +3 -3
- package/dist/schedule/runner.js +3 -3
- package/dist/slack/commands/run.js +3 -1
- package/dist/slack/destination.js +1 -1
- package/dist/slack/interface.js +2 -2
- package/dist/slack/manifest.js +1 -1
- package/dist/slack/presence.js +3 -3
- package/dist/slack/upload.js +1 -0
- package/dist/teams/interface.js +4 -2
- package/dist/thread-model.js +1 -0
- package/dist/tools/files.js +2 -2
- package/dist/tools/server.js +1 -1
- package/dist/turn.js +1 -1
- package/package.json +6 -1
package/README.md
CHANGED
|
@@ -405,6 +405,8 @@ Each thread is its own agent session with its own history. Two threads are two i
|
|
|
405
405
|
|
|
406
406
|
**Turn it down.** A thread showing every tool call is a thread that's hard to read when you already trust the agent. Send `` `! verbosity medium` `` to drop the tool card, or `` `! verbosity low` `` to leave only the answer. Send `` `! verbosity` `` to see where a thread stands, and `` `! verbosity default` `` to put it back. It applies to the turn that's running, so you can quiet one mid-flight. `slack.verbosity` in the config sets where every thread starts, and Slack always marks the thread as working whatever the level.
|
|
407
407
|
|
|
408
|
+
**Run one thread on another model.** Send `` `! model opus` `` to move a thread onto a different model, `` `! model terra high` `` to set the model and the effort together, or `` `! model --effort high` `` to change how hard it thinks and leave the model alone. Send `` `! model` `` to see where a thread stands, and `default` in either position to put it back: `` `! model default default` ``. Names go to the harness as you type them, in its own naming, so `opus` or `gpt-5.1-codex-max` means whatever it means there; Bishop only checks that it looks like a model name. A name the harness doesn't know does what a bad `--model` does, which differs by harness and is described under `agent.model` below: Codex fails the turn, Claude says so in prose about a model and silently ignores an effort it doesn't recognize. `` `! model` `` is how you see what a thread is being run with, which is the only sign of a typo in the silent case. Unlike verbosity this takes effect on the thread's next turn, not the one running, and it lasts for the life of the thread.
|
|
409
|
+
|
|
408
410
|
**Commands go to Bishop, not the agent.** Anything whose whole message is code opening with `! ` is a command: Bishop runs it, answers in the thread, and the agent is never told about either. `` `! help` `` lists them, and any of them takes `--help`. What follows the `!` has to look like a command name, so code and pasted output reach the agent as they always did.
|
|
409
411
|
|
|
410
412
|
**It doesn't answer every message in a busy thread.** In a thread where more than one other person is talking, the agent can leave a message alone when it wasn't for the agent, the way anyone else in the thread would. Slack still shows it thinking for a moment, the way somebody starts typing and then doesn't send. It always answers a message that @-mentions it, and always answers in a thread where you're the only other person talking, so a DM and a thread between you and the agent are unchanged.
|
|
@@ -729,7 +731,9 @@ Under Claude, Bishop refuses `Edit`, `Write`, and `NotebookEdit` in the shared d
|
|
|
729
731
|
|
|
730
732
|
**When the agent directory isn't a git repository**, there's nothing to isolate. Bishop warns at startup and says so once in each thread, then edits the directory directly.
|
|
731
733
|
|
|
732
|
-
###
|
|
734
|
+
### Files people share
|
|
735
|
+
|
|
736
|
+
Files go both ways: people attach them for the agent, and the agent sends them back. This half is the first.
|
|
733
737
|
|
|
734
738
|
Bishop downloads what people attach and saves it under `.bishop/files`, one directory per thread, collected with everything else that thread left behind. The agent reads it from disk, so any file type works.
|
|
735
739
|
|
|
@@ -745,7 +749,15 @@ A file shared earlier in the conversation is named to the agent rather than down
|
|
|
745
749
|
|
|
746
750
|
The default is 100. A file over the limit isn't refused: the agent is told its name and that it was too large, so it can say so. There is no restriction on type and won't be.
|
|
747
751
|
|
|
748
|
-
**Slack needs the `files:read`
|
|
752
|
+
**Slack needs the `files:read` and `files:write` scopes**, which are new. An app set up before these has to be reinstalled to grant them, and until then Bishop warns at startup about each one it is missing, the agent sees a note in place of each file, and a file it sends fails to upload. Run `bishop slack manifest` for the current manifest, or reinstall from App Settings. Gmail needs nothing new. **Teams needs `supportsFiles`** on the app's bot, without which it offers no attach button in a chat with the agent and only pasted images arrive. `bishop teams setup` sets it on a new app and tells you when an existing one is missing it, which needs the Teams CLI logged in.
|
|
753
|
+
|
|
754
|
+
### Files the agent sends
|
|
755
|
+
|
|
756
|
+
The other half. The agent can send a file back: an upload in the Slack thread, an attachment on the mail in email. It registers the file with a tool, which checks it and hands back a reference, and writes that reference into its reply where the file should appear. In Slack the file arrives on the message carrying the words around it, the way a person sends one. A file it registers and never places is still sent, after the answer, so nothing is silently dropped.
|
|
757
|
+
|
|
758
|
+
Teams cannot carry one yet. An agent that tries in a Teams conversation is told so and can say what it made instead, and a scheduled report bound for Teams arrives with a line naming the file it could not attach.
|
|
759
|
+
|
|
760
|
+
The limit is 25MB per answer in Slack and 4MB by email, across everything one answer sends. Neither is configurable, and the email one is lower because of how Bishop talks to Gmail rather than what Gmail accepts. Anything past it is dropped with a line in the log and the message still goes. A scheduled report can carry files too, and an agent that reports nothing sends nothing, attachment included.
|
|
749
761
|
|
|
750
762
|
### Credentials
|
|
751
763
|
|
|
@@ -765,7 +777,7 @@ Anything else in `.env` reaches the agent's environment too, which is how `GH_TO
|
|
|
765
777
|
|
|
766
778
|
## Running it for real
|
|
767
779
|
|
|
768
|
-
Bishop is one process that logs JSON to stdout and errors to stderr, so any process supervisor works.
|
|
780
|
+
Bishop is one process that logs JSON to stdout and errors to stderr, so any process supervisor works. Slack and Gmail need outbound access only, since one holds a socket open and the other polls. Teams is the exception: it POSTs activities to an endpoint rather than holding a socket open, so Bishop serves one when Teams credentials are present. That endpoint binds loopback like every other port Bishop opens, and a tunnel gives it a public hostname and a certificate without an inbound firewall rule.
|
|
769
781
|
|
|
770
782
|
```
|
|
771
783
|
$ BISHOP_LOG_LEVEL=info bishop
|
package/dist/cli.js
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{stat as
|
|
3
|
-
`),process.exit(1)}async function
|
|
2
|
+
import{stat as ie}from"node:fs/promises";import{join as ce,resolve as de}from"node:path";import{Command as pe}from"commander";import{agentSnapshots as me,fixedAgentDir as he}from"./agent-dir.js";import{looksRemote as K,parseRepoSpec as F}from"./agent-source.js";import{everyStore as z,storePath as q}from"./agent-store.js";import{attachmentStore as le}from"./attachments.js";import{HARNESSES as ue,loadConfig as O,nonEmptyFlag as j,parseHarnessName as fe,positiveIntFlag as we,resolveAgent as ge}from"./config.js";import{dbPath as Y,openDb as W}from"./db.js";import{describeMissing as ye,loadEnv as S,readCredentials as ke}from"./env.js";import{BishopError as v}from"./errors.js";import{fileStore as Se}from"./files.js";import{DEFAULT_MAX_AGE_DAYS as ve,makeCollector as be,sweep as Te}from"./gc.js";import{ghAuthLogin as Ie,ghCliIn as Ee,gitAuthenticatesToGithub as Ae,withTemporaryGhConfig as _e}from"./github/gh.js";import{AGENT_SCOPES as Re,checkGithubSetup as Ne}from"./github/setup.js";import{createGmail as Oe}from"./gmail/create.js";import{checkGmailSetup as Ge}from"./gmail/setup.js";import{createHarness as $e}from"./harness/create.js";import{sharedFileFetcher as xe}from"./interface/attachments.js";import{principalCheck as J}from"./interface/authorize.js";import{deliveryRouter as Le}from"./interface/delivery.js";import{conversationMatch as Ce,destinationResolver as He}from"./interface/destination.js";import{log as p,requestedLevel as Me}from"./log.js";import{addSchedule as Pe,listSchedules as De,removeSchedule as Be,setScheduleEnabled as Q}from"./schedule/cli.js";import{authorizedToRun as Ue,makeScheduler as Ke}from"./schedule/runner.js";import{scheduleStore as Fe}from"./schedule/store.js";import{threadQueue as ze}from"./sessions/queue.js";import{interfaceState as qe,retentionStore as V,sessionStore as je,threadHandover as Ye,threadMeta as We}from"./sessions/store.js";import{ensureGitignore as Je,openBrowser as Qe,resolveAppDetails as Ve,SetupError as X,setupSlack as Xe,setupSlackManually as Ze,withPrompt as Z,writeEnv as ee}from"./setup.js";import{createSlackInterface as et}from"./slack/interface.js";import{buildManifest as tt}from"./slack/manifest.js";import{createTeams as ot}from"./teams/create.js";import{administratorInstructions as nt,repointTeamsEndpoint as st,setupTeams as rt,setupTeamsManually as at}from"./teams/setup.js";import{threadModel as it}from"./thread-model.js";import{fileTools as ct}from"./tools/files.js";import{scheduleTools as dt}from"./tools/schedules.js";import{startToolServer as pt}from"./tools/server.js";import{makeTurnRunner as mt}from"./turn.js";import{version as ht}from"./version.js";import{hasGitIdentity as lt,isGitRepo as ut,sharedDirNotice as ft,WORKTREE_DIR as wt}from"./worktrees.js";function u(e){process.stderr.write(`${e}
|
|
3
|
+
`),process.exit(1)}async function gt(e){const t=de(e??".");let n;try{n=(await ie(t)).isDirectory()}catch{throw new v(`--agent directory doesn't exist: ${t}`)}if(!n)throw new v(`--agent is not a directory: ${t}`);return t}async function yt(e){const t=await ut(e),n={enabled:!0,isRepo:t};return t?(p.info({agentDir:e,worktrees:ce(e,wt)},"worktree mode is on"),await lt(e)||p.warn({agentDir:e},"git has no user.name or user.email here, so the agent cannot commit what it does in a worktree"),n):(p.warn({agentDir:e},"worktree mode is on but the agent directory is not a git repository; threads share it"),n)}function kt(e){if(e.kind==="options-replaced"){p.warn({harness:e.harness},`agent.harnesses.${e.harness} replaces agent.options, which is ignored for this run`);return}p.warn({harness:e.harness,configured:e.configured},`--harness overrides the configured harness, but agent.options is still written for that one; move it to agent.harnesses.${e.configured} and give ${e.harness} its own block`)}async function St(e=[],t={}){e.length>0&&u(`bishop: unknown command "${e[0]}"
|
|
4
4
|
|
|
5
|
-
Run \`bishop --help\` to see what's available.`);const n=process.cwd(),i=t.agent&&
|
|
5
|
+
Run \`bishop --help\` to see what's available.`);const n=process.cwd(),i=t.agent&&K(t.agent)?F(t.agent):void 0;t.autoUpdate&&!i&&u("--auto-update needs --agent to be a git URL. Bishop never updates a directory it was pointed at; that state is yours to manage."),S(n);const s=await O(n),a=ke();a.ok||u(ye(a));const r=ge(s,t);for(const o of r.warnings)kt(o);const m=W(Y(n)),c=je(m,r.harness);let d;const l=async o=>await d?.check(o)??{ok:!0},f=i?me({spec:i,store:q(n,i.slug),dirs:c,autoUpdate:!!t.autoUpdate,recheck:l}):void 0,w=f?await f.start():await gt(t.agent),oe=f?f.forTurn:he(w,c);p.info({bishopDir:n,agentDir:w,...w===n?{}:{separate:!0},...i?{source:i.cloneUrl,autoUpdate:!!t.autoUpdate}:{},app:s.slack?.app?.name},"bishop starting");const A=s.agent?.worktrees?await yt(w):void 0,b=$e(r.harness,{...r.options?{options:r.options}:{},...r.model?{model:r.model}:{},...r.effort?{effort:r.effort}:{},...A?{worktrees:A}:{},credentials:a.credentials}),_=await b.check(w);_.ok||u(b.describeStartupFailure(w,_.error));const N=_.model??r.model;p.info({harness:b.name,credentials:_.credentialSource??"unreported",model:N,...r.effort?{effort:r.effort}:{},...r.options?{agentOptions:Object.keys(r.options)}:{}},"harness ready"),d=b,p.info({sessions:c.count()},"opened session store");const $=be({retention:V(m),stores:()=>z(n),home:n,...s.gc?.maxAgeDays===void 0?{}:{maxAgeDays:s.gc.maxAgeDays}});$.start();let y;const ne=o=>y?J(y)(o):Ue(o),se=async(o,h)=>{if(!y)throw new v("no interface is running, so there is nowhere to send");return He(y,{optedInOnly:!0})(o,h)},re=(o,h)=>y?Ce(y)(o,h):!1,x=Se({home:n,...s.files?.maxMb===void 0?{}:{maxMb:s.files.maxMb}}),L=le(m),C=Fe(m),H=await pt({tools:[...dt({schedules:C,authorized:ne,resolve:se,sameConversation:re}),...ct({fetch:xe({interfaces:()=>y??[],store:L,files:x})})]}),M=We(m),P=it(M),ae={...N?{model:N}:{},...r.effort?{effort:r.effort}:{}},D=mt({harness:b,cwd:oe,sessions:c,model:P,tools:H,...A&&!A.isRepo?{sharedDirNotice:ft(w)}:{}}),B=ze(),T={threads:c,events:c,files:x,attachments:L,run:o=>B.run(o.thread.threadKey,()=>D.run(o)),stop:o=>D.stop(o)},g=[];if(a.credentials.slack&&g.push(await et({...T,credentials:a.credentials.slack,meta:M,model:P,configuredModel:ae,...s.slack?.verbosity?{verbosity:s.slack.verbosity}:{},...s.slack?.allow?{allow:s.slack.allow}:{},...s.slack?.maxBotTurns===void 0?{}:{maxBotTurns:s.slack.maxBotTurns}})),a.credentials.gmail)try{g.push(await Oe({...T,credentials:a.credentials.gmail,...s.gmail?{config:s.gmail}:{},state:qe(m)}))}catch(o){o instanceof v&&u(o.message);const h=a.credentials.gmail,R=h.kind==="oauth"?"the authorized Gmail account":`${h.user}`;u(`Couldn't reach the mailbox for ${R}:
|
|
6
6
|
${o instanceof Error?o.message:String(o)}
|
|
7
7
|
|
|
8
|
-
Run \`bishop gmail setup\` to check the credentials.`)}if(a.credentials.teams)try{g.push(await
|
|
8
|
+
Run \`bishop gmail setup\` to check the credentials.`)}if(a.credentials.teams)try{g.push(await ot({...T,credentials:a.credentials.teams,...s.teams?{config:s.teams}:{}}))}catch(o){o instanceof v&&u(o.message),u(`Couldn't start the Teams app for client ${a.credentials.teams.clientId}:
|
|
9
9
|
${o instanceof Error?o.message:String(o)}
|
|
10
10
|
|
|
11
|
-
Check the credentials in .env and the \`teams\` block in .bishop/config.json.`)}y=g;for(const o of g)await o.start();p.info({interfaces:g.map(o=>o.name)},"bishop is listening");const
|
|
11
|
+
Check the credentials in .env and the \`teams\` block in .bishop/config.json.`)}y=g;for(const o of g)await o.start();p.info({interfaces:g.map(o=>o.name)},"bishop is listening");const U=Ke({schedules:C,run:T.run,threads:c,cancel:T.stop,deliver:Le(g),authorized:J(g),attach:Ye(c)});U.start();for(const o of["SIGINT","SIGTERM"])process.once(o,()=>{p.info({signal:o,inFlight:B.size},"shutting down"),U.stop().catch(h=>p.error({err:h},"the scheduler failed to stop cleanly")).then(()=>Promise.allSettled(g.map(h=>h.stop()))).then(async h=>{const R=h.filter(I=>I.status==="rejected");for(const I of R)p.error({err:I.reason},"an interface failed to stop cleanly");await H.stop().catch(I=>{p.error({err:I},"the tool endpoint failed to stop cleanly")}),await $.stop(),m.close(),process.exit(R.length>0?1:0)})})}function vt(e){for(const t of e.snapshots)process.stdout.write(`removed snapshot ${t.commit} (unused for ${t.ageDays} days)
|
|
12
12
|
`);for(const t of e.worktrees){const n=t.branch?`, branch ${t.branch}`:"";process.stdout.write(`released ${t.path}${n}
|
|
13
13
|
`)}process.stdout.write(`snapshots: removed ${e.snapshots.length}, kept ${e.keptSnapshots}
|
|
14
14
|
worktrees: released ${e.worktrees.length}
|
|
15
15
|
files: removed ${e.files} download directories
|
|
16
16
|
rows: forgot ${e.threads} threads, ${e.events} events
|
|
17
|
-
`)}async function
|
|
17
|
+
`)}async function bt(e){const t=process.cwd();S(t),e.agent&&!K(e.agent)&&u("gc --agent takes the git URL Bishop cloned, not a path. It narrows the pass to the files Bishop owns for one agent, and it owns none for a directory agent. Run `bishop gc` with no --agent to collect what its threads left behind.");const n=e.agent?[q(t,F(e.agent).slug)]:await z(t),i=await O(t),s=e.maxAgeDays??i.gc?.maxAgeDays;Me()||(p.level="error");const a=W(Y(t));try{vt(await Te({retention:V(a),stores:n,home:t,...e.agent?{only:n}:{},...s===void 0?{}:{maxAgeDays:s}}))}finally{a.close()}}function Tt(){return!(process.env.SSH_CONNECTION||process.env.SSH_TTY||process.env.SSH_CLIENT||process.platform==="linux"&&!process.env.DISPLAY&&!process.env.WAYLAND_DISPLAY)}async function It(e={}){const t=process.cwd();S(t);const n=process.env.BISHOP_GMAIL_CLIENT_ID?.trim(),i=process.env.BISHOP_GMAIL_CLIENT_SECRET?.trim(),s=process.env.BISHOP_GMAIL_REFRESH_TOKEN?.trim(),a=process.env.BISHOP_GMAIL_USER?.trim(),r=process.env.BISHOP_GMAIL_SERVICE_ACCOUNT?.trim();n&&!i&&u("BISHOP_GMAIL_CLIENT_ID is set but BISHOP_GMAIL_CLIENT_SECRET is not.");const m=e.browser===!1||!Tt()?"paste":"browser",c=await Z(!0,async d=>Ge({...n&&i?{client:{clientId:n,clientSecret:i}}:{},...s?{refreshToken:s}:{},...a?{user:a}:{},...r?{serviceAccount:r}:{},mode:m,openBrowser:Qe,readPaste:async l=>(process.stdout.write(`Open this in a browser and approve it:
|
|
18
18
|
|
|
19
19
|
${l}
|
|
20
20
|
|
|
@@ -22,12 +22,12 @@ It will finish on a http://127.0.0.1 address that fails to load. That is expecte
|
|
|
22
22
|
Copy the whole address out of the browser and paste it here.
|
|
23
23
|
|
|
24
24
|
`),d("Redirect URL",""))}));if(process.stdout.write(`${c.report}
|
|
25
|
-
`),c.refreshToken){const d=await
|
|
25
|
+
`),c.refreshToken){const d=await ee(t,{BISHOP_GMAIL_REFRESH_TOKEN:c.refreshToken});process.stdout.write(`
|
|
26
26
|
Saved BISHOP_GMAIL_REFRESH_TOKEN to ${d}
|
|
27
|
-
`)}c.ok||process.exit(1)}async function
|
|
27
|
+
`)}c.ok||process.exit(1)}async function Et(e){const t=process.cwd(),n=process.env.GH_TOKEN?.trim();process.env.GH_TOKEN!==void 0&&!n&&delete process.env.GH_TOKEN,S(t);const i=process.env.GH_TOKEN?.trim(),s=!!i&&!n,a=e.prompt&&process.stdin.isTTY===!0,r=e.print?process.stderr:process.stdout,m=l=>r.write(l),c=l=>Ne({...i?{token:i,tokenIsSaved:s}:{},...a?{login:()=>Ie(Re.map(f=>f.scope),l),confirm:async f=>/^y/i.test(await Z(!0,w=>w(f,"y"),r))}:{},gitConfigured:Ae,gh:Ee(l),...e.temporary?{temporary:!0}:{}}),d=e.temporary&&!i?await _e(c):await c();if(m(`${d.report}
|
|
28
28
|
`),e.print)d.token&&process.stdout.write(`${JSON.stringify({GH_TOKEN:d.token})}
|
|
29
|
-
`);else if(d.token){if(d.source==="gh"){const f=await
|
|
29
|
+
`);else if(d.token){if(d.source==="gh"){const f=await ee(t,{GH_TOKEN:d.token});m(`
|
|
30
30
|
Saved GH_TOKEN to ${f}
|
|
31
|
-
`)}const l=await
|
|
32
|
-
`)}d.ok||process.exit(1)}async function
|
|
33
|
-
`)}const k=new
|
|
31
|
+
`)}const l=await Je(t,[".env"]);l.length>0&&m(`Added ${l.join(", ")} to .gitignore
|
|
32
|
+
`)}d.ok||process.exit(1)}async function At(e){S(process.cwd());const t={...e.name===void 0?{}:{name:e.name},...e.displayName===void 0?{}:{displayName:e.displayName},...e.description===void 0?{}:{description:e.description},prompt:e.prompt};e.manual?await Ze(t):await Xe(t)}async function _t(e){if(e.appId!==void 0&&!e.manual)throw new X("--app-id adopts an app somebody else created, so it needs --manual too.");if(e.endpoint!==void 0&&e.manual)throw new X("--endpoint sets the URL on an app Bishop is creating, so it cannot be used\nwith --manual. Point an adopted app with `bishop teams endpoint <url>`,\nor ask whoever created it to.");S(process.cwd());const t={...e.name===void 0?{}:{name:e.name},...e.displayName===void 0?{}:{displayName:e.displayName},...e.description===void 0?{}:{description:e.description},...e.endpoint===void 0?{}:{endpoint:e.endpoint},...e.appId===void 0?{}:{appId:e.appId},prompt:e.prompt};e.manual?await at(t):await rt(t)}async function Rt(e){const t=process.cwd(),n=await O(t),i=await Ve({name:e.name??n.slack?.app?.name,displayName:e.displayName??n.slack?.app?.displayName,description:e.description??n.slack?.app?.description,prompt:!1},t,async(s,a)=>a);process.stdout.write(`${JSON.stringify(tt(i),null,2)}
|
|
33
|
+
`)}const k=new pe().name("bishop").description("An agent gateway. Runs an agent and connects it to Slack and email.").version(ht,"-v, --version").enablePositionalOptions().option("--agent <path-or-url>","the agent's directory, or a git URL to clone; defaults to the current directory").option(`--harness <${ue.join("|")}>`,"which SDK runs the agent",fe).option("--model <model>","the model to run, in the harness's own naming",j("--model")).option("--effort <effort>","how hard the model should think, in the harness's own naming",j("--effort")).option("--auto-update","for a git-URL --agent, check for a new commit at the start of every new thread").allowExcessArguments(!0).action((e,t)=>St(t.args,e));k.command("gc").description("Collect the snapshots, worktrees, and rows old conversations left behind").option("--max-age-days <days>",`how long a quiet thread's remains are kept; defaults to gc.maxAgeDays, then ${ve}`,we("--max-age-days")).option("--agent <url>","only this agent's snapshots; defaults to every agent Bishop has cloned here").action(bt);const E=k.command("schedules").description("Prompts the agent runs on a clock, and where their answers go");E.command("list").description("Everything scheduled here, in your own time zone").action(()=>De(process.cwd())),E.command("add").description("Add a schedule").requiredOption("--name <name>","what a person sees in a list, and the subject of an email").requiredOption("--cron <expression>","five-field cron, or @daily and friends").requiredOption("--prompt <text>","what the agent is asked, including when to stay quiet").requiredOption("--to <destination>","#channel or @person for Slack, an address for Gmail").option("--tz <zone>","IANA zone the expression is read in; defaults to this machine").option("--interface <name>","which interface delivers, when --to is ambiguous").action(e=>Pe(process.cwd(),e)),E.command("rm <id>").description("Remove a schedule").action(e=>Be(process.cwd(),e)),E.command("disable <id>").description("Stop a schedule firing, keeping it").action(e=>Q(process.cwd(),e,!1)),E.command("enable <id>").description("Start a disabled schedule firing again, from now").action(e=>Q(process.cwd(),e,!0));const Nt=k.command("gmail").description("Gmail interface commands");Nt.command("setup").description("Authorize a Gmail mailbox, or check the one already configured").option("--no-browser","print a URL to open elsewhere instead of opening one here").action(It);const Ot=k.command("github").description("GitHub credentials for the agent");Ot.command("setup").description("Give the agent a GitHub token, or check the one it's using").option("--no-prompt","don't offer to run `gh auth login`").option("--print","write the credential to stdout as JSON instead of to .env").option("--temporary","log in without touching this machine's own gh login").action(Et);const te=k.command("slack").description("Slack interface commands");te.command("setup").description("Create and install a Slack app, and save its credentials here").option("--name <name>","app name; defaults to the current directory name").option("--display-name <name>","bot display name in Slack; defaults to --name").option("--description <text>","short description shown in Slack").option("--no-prompt","don't prompt for anything left off").option("--manual","adopt an app you created in Slack yourself, using its tokens").action(At),te.command("manifest").description("Print the app manifest, for creating an app in Slack by hand").option("--name <name>","app name; defaults to config or the directory name").option("--display-name <name>","bot display name; defaults to --name").option("--description <text>","short description shown in Slack").action(Rt);const G=k.command("teams").description("Microsoft Teams interface commands");G.command("setup").description("Create a Teams app and save its credentials here, or check the one configured").option("--name <name>","app name; defaults to the current directory name").option("--display-name <name>","app name shown in Teams; defaults to --name").option("--description <text>","short description shown in Teams").option("--endpoint <url>","the URL Teams POSTs to; can be set later").option("--app-id <id>","with --manual, the Teams app ID you were given").option("--no-prompt","don't prompt for anything left off").option("--manual","adopt an app somebody else created, using its credentials").action(_t),G.command("endpoint <url>").description("Point the Teams app at a different URL, changing nothing else").option("--app-id <id>","the Teams app to point; defaults to the one in config").action((e,t)=>st(e,t.appId===void 0?{}:{appId:t.appId})),G.command("instructions").description("Print what to send whoever creates the Teams app, when that is an administrator").option("--endpoint <url>","the URL Teams should POST to, if you already know it").action(e=>{process.stdout.write(nt(e.endpoint))});try{await k.parseAsync()}catch(e){throw e instanceof v&&u(e.message),e}
|
package/dist/gmail/fake.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import{INBOX as
|
|
1
|
+
import{INBOX as $}from"./api.js";function y(c){const r={};for(const o of c.replaceAll(/\r\n[ \t]+/g," ").split(`\r
|
|
2
|
+
`)){const n=o.indexOf(":");n>0&&(r[o.slice(0,n).toLowerCase()]=o.slice(n+1).trim())}return r}function b(c,r){return c["content-transfer-encoding"]==="base64"?Buffer.from(r.replaceAll(`\r
|
|
3
|
+
`,""),"base64").toString("utf8"):r}const x=/filename="([^"]*)"/;function w(c){const r=Buffer.from(c,"base64url").toString("utf8"),o=r.indexOf(`\r
|
|
2
4
|
\r
|
|
3
|
-
`),
|
|
4
|
-
|
|
5
|
-
|
|
5
|
+
`),n=y(r.slice(0,o)),u=r.slice(o+4),f=/boundary="([^"]+)"/.exec(n["content-type"]??"")?.[1];if(!f)return{text:b(n,u),headers:n,files:[]};const[t,...i]=u.split(`--${f}`).slice(1,-1).map(l=>{const h=l.indexOf(`\r
|
|
6
|
+
\r
|
|
7
|
+
`),p=y(l.slice(0,h).replace(/^\r\n/,""));return{headers:p,body:b(p,l.slice(h+4))}});return{text:(t?.body??"").replace(/\r\n$/,""),headers:n,files:i.map(l=>({name:x.exec(l.headers["content-disposition"]??"")?.[1]??"",mimeType:(l.headers["content-type"]??"").split(";")[0]?.trim()??"",contents:l.body.replace(/\r\n$/,"")}))}}function S(c="agent@acme.com",r={}){const o=new Map,n=new Map,u=new Map,f=[],t=[],i=[],l=new Map;let h=!1,p=1;function g(e,s){const a=n.get(e)??new Set;for(const d of s.removeLabelIds??[])a.delete(d);for(const d of s.addLabelIds??[])a.add(d);n.set(e,a)}return{calls:t,sent:f,deliver(e){const s=e.id??`m${p++}`,a=e.threadId??s,d=Object.entries(e.headers??{}).map(([m,I])=>({name:m,value:I}));return o.set(s,{id:s,threadId:a,internalDate:e.internalDate??String(Date.now()),labelIds:e.labelIds??[$],payload:e.payload??{mimeType:"text/plain",headers:d,body:{data:Buffer.from(e.body??"","utf8").toString("base64url")}}}),i.push(s),s},attach(e,s){l.set(e,s)},labelsOn(e){return[...n.get(e)??[]]},expireHistory(){h=!0},async profile(){return t.push("profile"),{emailAddress:c,historyId:String(i.length)}},async sendAs(){return t.push("sendAs"),[{email:c,...r.displayName?{displayName:r.displayName}:{},isPrimary:!0},...(r.alsoSendsAs??[]).map(e=>({email:e}))]},async history(e){if(t.push(`history:${e}`),h)return h=!1,{messageIds:[],expired:!0};const s=Number(e);return{messageIds:i.slice(s),historyId:String(i.length),expired:!1}},async search(e,s){return t.push(`search:${e}`),i.slice(-s).reverse()},async message(e){t.push(`message:${e}`);const s=o.get(e);if(!s)throw new Error(`no such message ${e}`);return s},async thread(e){return t.push(`thread:${e}`),i.map(s=>o.get(s)).filter(s=>s?.threadId===e)},async attachment(e,s){t.push(`attachment:${s}`);const a=l.get(s);if(a===void 0)throw new Error(`no such attachment ${s} on ${e}`);return Buffer.from(a)},async send(e){return t.push("send"),f.push({raw:e.raw,...e.threadId?{threadId:e.threadId}:{},...w(e.raw)}),{id:`sent-${f.length}`,threadId:e.threadId??`sent-thread-${f.length}`}},async modifyMessage(e,s){t.push(`modifyMessage:${e}`);const a=o.get(e);if(!a)return;const d=new Set(a.labelIds??[]);for(const m of s.removeLabelIds??[])d.delete(m);for(const m of s.addLabelIds??[])d.add(m);a.labelIds=[...d]},async modifyThread(e,s){t.push(`modifyThread:${e}`),g(e,s)},async labels(){return t.push("labels"),[...u.values()]},async createLabel(e){t.push(`createLabel:${e}`);const s={id:`label-${u.size+1}`,name:e};return u.set(s.id,s),s}}}export{S as fakeMailbox};
|
package/dist/gmail/interface.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{BishopError as C}from"../errors.js";import{log as n}from"../log.js";import{retry as B}from"../retry.js";import{GmailApiError as P,gmailSendRetryable as $,UNREAD as q}from"./api.js";import{looksLikeAddress as z}from"./authorize.js";import{resolveGmailDestination as H}from"./destination.js";import{mailEtiquetteNote as U}from"./etiquette.js";import{mailAttachment as R,mailFile as W,mailFileId as J,mailFilePayload as Q,parseMailFile as V}from"./files.js";import{continuesAThread as X,mailHistory as Z}from"./history.js";import{mailIdentity as ee}from"./identity.js";import{buildMessage as te,buildReply as ae,mailHeaders as re,parseMessage as ne}from"./message.js";import{gmailPresence as
|
|
2
|
-
`)},A="gmail:historyId",G="gmail:attachedAt",D="in:inbox newer_than:2d",S=50;function N(a){return`gmail:${a}`}async function
|
|
1
|
+
import{BishopError as C}from"../errors.js";import{log as n}from"../log.js";import{retry as B}from"../retry.js";import{GmailApiError as P,gmailSendRetryable as $,UNREAD as q}from"./api.js";import{looksLikeAddress as z}from"./authorize.js";import{resolveGmailDestination as H}from"./destination.js";import{mailEtiquetteNote as U}from"./etiquette.js";import{mailAttachment as R,mailFile as W,mailFileId as J,mailFilePayload as Q,parseMailFile as V}from"./files.js";import{continuesAThread as X,mailHistory as Z}from"./history.js";import{mailIdentity as ee}from"./identity.js";import{buildMessage as te,buildReply as ae,mailHeaders as re,parseMessage as ne,readAttachments as oe}from"./message.js";import{gmailPresence as ie}from"./presence.js";import{participants as se,routeMessage as le}from"./route.js";const de={name:"gmail",guidance:["This conversation is an email thread. Everything you write during a turn is collected and","sent as one plain-text reply to everyone on the thread, so give the whole answer in a","single message and never say you will follow up separately: there is no way for you to","send anything until someone writes to you again.","","Nobody is watching while you work. None of your tool calls are visible and nothing you say","arrives until the turn is over, so a turn that takes minutes costs nothing. Markdown is not","rendered; write plain prose, and use blank lines rather than formatting for structure.","","A <headers> tag above each message says who it was addressed to. Everyone on the To and Cc","lines receives your reply and there is no way to answer one of them privately. Being on Cc","usually means you are being kept informed rather than asked for something.","","A thread can be a conversation between other people that you were only copied on. Bishop",'marks a message you may leave alone with a <thread> tag carrying reply="optional": you were',"not the only person it was written to. Stay out of those unless you are sure somebody is","asking you to do something. To stay out, say <no-output> and nothing else, and Bishop sends","no mail at all. Every message without that tag is yours to answer.","","Sure means the sender named you, or asked for something you can act on. Work of yours coming","up in conversation is not, and neither is a question somebody else is better placed to","answer, and neither is having something you think worth adding. When you are sure, answer,","however many people are on the thread. A message replying to something you said is a","conversation you are already part of, so lean towards answering that one.","",'When the tag also carries addressed="no", your address is on neither line. You reached this',"through a group, a forwarding alias, or a blind copy, so nobody wrote to you and a single","address can stand for a thousand people. Answer only if the message asks you for something","by name.","","One address can be a thousand people wherever it appears. Judge how many you would be","interrupting by who an address reaches rather than by how many you can count, and treat one","you cannot place as a crowd."].join(`
|
|
2
|
+
`)},A="gmail:historyId",G="gmail:attachedAt",D="in:inbox newer_than:2d",S=50;function N(a){return`gmail:${a}`}async function he(a){try{return await a.sendAs()}catch(y){return n.warn({err:y},"the mailbox would not say who it is; the agent will not know its own name or any alias"),[]}}async function Se(a){const y=await a.api.profile(),h=ee(y.emailAddress,await he(a.api)),w=h.address,g={email:w,...h.name?{name:h.name}:{}},p={...de,self:{address:w,...h.name?{aka:[h.name]}:{}}},O=a.now??Date.now,b=a.state.get(G),f=b?Number(b):O();b||a.state.set(G,String(f));const x=new Set;let v,m,I=!1;const u=new Set;async function T(e,t){if(await a.labels.mark(e.threadId,"refused"),!(!t||x.has(e.threadId)))try{await a.api.send({raw:ae({from:g,to:[e.from],subject:e.subject,inReplyTo:e.messageId,references:e.references,body:t}),threadId:e.threadId}),x.add(e.threadId)}catch(s){n.warn({threadId:e.threadId,err:s},"could not send the refusal")}}function K(e,t){if(t.fetchable)return a.attachments.remember(e,p.name,{id:J(t),name:t.name,payload:Q(t)})}async function L(e,t){return t.attachments.length===0?[]:Promise.all(t.attachments.map(async(s,r)=>{const i=W(t.id,r,s),o=K(e.id,i),l=await a.files.save(e.dirKey,R(a.api,i,s.data));return o?{...l,ref:o}:l}))}function F(e,t,s,r){const i=[],o=se(t,h).map(d=>({email:d})),l=U(t,h);return{thread:e,isNewThread:r,origin:p,...l?{note:l}:{},...X(t)?{history:()=>Z({api:a.api,message:t,self:h,allow:a.allow,remember:d=>a.attachments.remember(e.id,p.name,d)})}:{},async messages(){const d=await L(e,t);return[{principal:{id:t.from.email,email:t.from.email,...t.from.name?{realName:t.from.name}:{}},text:s,headers:re(t),...d.length>0?{files:d}:{}}]},presence:()=>ie({api:a.api,labels:a.labels,self:g,message:t,to:o,threadKey:e.threadKey,notices:i,...l?{mayDecline:!0}:{}}),async notify(d){i.push(d)},async destination(){return{interface:"gmail",handle:t.from.email,label:t.from.email}}}}async function Y(e){const t=`gmail:${e}`;if(await a.events.seen(t,{record:!1}))return;let s;try{s=await a.api.message(e)}catch(c){if(c instanceof P&&c.status===404){await a.events.seen(t),n.warn({messageId:e},"a message vanished before it could be read; skipping it");return}throw c}await a.events.seen(t);const r=ne(s);if(r.receivedAt<f){n.warn({messageId:e,threadId:r.threadId,receivedAt:new Date(r.receivedAt).toISOString(),floor:new Date(f).toISOString()},"ignoring an email that predates this agent");return}const i=le(r,{self:h,allow:a.allow});if(i.action==="ignore"){n.debug({messageId:e,threadId:r.threadId,reason:i.reason},"ignoring an email");return}if(i.action==="refuse"){n.warn({messageId:e,threadId:r.threadId,from:r.from.email,reason:i.reason},"refusing an unauthorized email"),await T(r,i.note);return}const o=N(r.threadId),l=await a.threads.has(o),d=await a.threads.remember(o);a.markRead&&await a.api.modifyMessage(e,{removeLabelIds:[q]}).catch(c=>{n.debug({messageId:e,err:c},"could not mark the message read")}),n.info({thread:o,from:r.from.email,newThread:!l},"running a turn from email");const M=a.run(F(d,r,i.text,!l)).catch(c=>{n.error({thread:o,err:c},"a turn from email failed")}).finally(()=>{u.delete(M)});u.add(M)}async function _(){const e=a.state.get(A);if(!e){const o=await a.api.profile();a.state.set(A,o.historyId),n.info({historyId:o.historyId},"starting from the current state of the mailbox");return}const t=await a.api.history(e);let s=t.messageIds,r=t.historyId;if(t.expired){n.warn({cursor:e},"the Gmail history cursor is too old; falling back to a search of recent mail");const o=await a.api.profile();s=(await a.api.search(D,S)).reverse(),s.length===S&&n.warn({limit:S,query:D},"the resync search filled its limit, so older mail in the window was not looked at"),r=o.historyId}let i=!0;for(const o of s){if(I)return;try{await Y(o)}catch(l){i=!1,n.error({messageId:o,err:l},"could not handle an email")}}if(!i){n.warn({cursor:e},"holding the Gmail cursor so unhandled mail is offered again");return}r&&a.state.set(A,r)}async function k(){if(!(m||I)){m=(async()=>{await _()})();try{await m}catch(e){n.error({err:e},"a Gmail poll failed; retrying on the next interval")}finally{m=void 0}}}async function E(){await Promise.allSettled([...u])}async function j(e){const t=e.handle.trim().toLowerCase();if(!z(t))throw new C(`"${e.handle}" is not an email address, so nothing was sent`);if(!a.allow.allows(t))throw new C(`${t} is not on this agent's allow list, so nothing was sent to it`);const s=e.title?.trim()||"A message from your agent",r=await oe(e.files??[],(o,l)=>{n.error({to:t,file:o.name,err:l},"could not read a file to send with a report")}),i=await B(()=>a.api.send({raw:te({from:g,to:[{email:t}],subject:s,body:e.text,...r.length>0?{files:r}:{}})}),{what:"gmail messages.send",retryable:$});return n.info({to:t,chars:e.text.length,...r.length>0?{files:r.length}:{}},"delivered by email"),i.threadId?{threadKey:N(i.threadId)}:(n.warn({to:t},"Gmail named no thread for a sent message, so a reply to it starts fresh"),{})}return{name:"gmail",poll:k,settle:E,deliver:j,allows:e=>a.allow.allows(e),async resolveDestination(e,t){return H(a.allow,e,{...t?.optedInOnly?{optedInOnly:!0}:{}})},sameConversation:(e,t)=>e.trim().toLowerCase()===t.trim().toLowerCase(),attachment:e=>{const t=V(e);return t?R(a.api,t):void 0},async start(){n.info({address:w,everyMs:a.pollIntervalMs,allow:a.allow.open?"anyone":"a list",answeringMailAfter:new Date(f).toISOString()},"watching the Gmail inbox"),await k(),v=setInterval(()=>{k()},a.pollIntervalMs)},async stop(){I=!0,v&&clearInterval(v),await m?.catch(()=>{}),u.size>0&&(n.info({turns:u.size},"waiting for email turns still running"),await E())}}}export{de as GMAIL_ORIGIN,Se as createGmailInterface,N as threadKeyFor};
|
package/dist/gmail/message.js
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
function
|
|
2
|
-
`);if(o.trim())return o;const r=t.filter(n=>n.mimeType==="text/html").map(
|
|
3
|
-
`);return r.trim()?
|
|
1
|
+
import{randomBytes as g}from"node:crypto";import{readFile as x}from"node:fs/promises";function j(e){const t=new Map;for(const{name:o,value:r}of e?.headers??[]){const n=o.toLowerCase(),i=t.get(n);i?i.push(r):t.set(n,[r])}return t}function c(e,t){return e.get(t.toLowerCase())?.[0]??""}function l(e){const t=[];let o="",r=!1;for(const n of e){if(n==='"'&&(r=!r),n===","&&!r){t.push(o),o="";continue}o+=n}return t.push(o),t.map(C).filter(n=>!!n)}function C(e){const t=e.trim();if(!t)return;const o=/^(.*)<([^>]+)>\s*$/.exec(t);if(o){const r=b(o[1]?.trim().replace(/^"|"$/g,"").trim()??""),n=o[2]?.trim().toLowerCase()??"";return n?{...r?{name:r}:{},email:n}:void 0}if(t.includes("@"))return{email:t.toLowerCase()}}function b(e){return e.replaceAll(/=\?([^?]+)\?([BbQq])\?([^?]*)\?=/g,(t,o,r,n)=>{try{const i=r.toUpperCase()==="B"?Buffer.from(n,"base64"):Buffer.from(n.replaceAll("_"," ").replaceAll(/=([0-9A-Fa-f]{2})/g,(a,T)=>String.fromCharCode(Number.parseInt(T,16))),"binary");return new TextDecoder(o.toLowerCase()).decode(i)}catch{return t}})}function h(e){const t=e.body?.data;return t?Buffer.from(t,"base64url").toString("utf8"):""}function*s(e){if(e){yield e;for(const t of e.parts??[])yield*s(t)}}function B(e){const t=[...s(e)].filter(n=>!n.filename),o=t.filter(n=>n.mimeType==="text/plain").map(h).join(`
|
|
2
|
+
`);if(o.trim())return o;const r=t.filter(n=>n.mimeType==="text/html").map(h).join(`
|
|
3
|
+
`);return r.trim()?S(r):""}function S(e){return e.replaceAll(/<(script|style)[^>]*>[\s\S]*?<\/\1>/gi,"").replaceAll(/<br\s*\/?>/gi,`
|
|
4
4
|
`).replaceAll(/<\/(p|div|tr|li|h[1-6])>/gi,`
|
|
5
5
|
`).replaceAll(/<[^>]+>/g,"").replaceAll(" "," ").replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll(""",'"').replaceAll("'","'").replaceAll(/\n{3,}/g,`
|
|
6
6
|
|
|
7
|
-
`).trim()}const
|
|
8
|
-
`).trim()}function
|
|
9
|
-
`)}function
|
|
10
|
-
`);return
|
|
7
|
+
`).trim()}const I=[/^On .+ wrote:$/,/^-{2,}\s*Original Message\s*-{2,}$/i,/^_{10,}$/],w=/^(Sent|Date|To|Subject):\s/i;function R(e){const t=e.split(/\r?\n/),o=[];for(let r=0;r<t.length;r+=1){const n=(t[r]??"").trim();if(I.some(i=>i.test(n))||/^From:\s/i.test(n)&&t.slice(r+1,r+3).map(a=>a.trim()).some(a=>w.test(a))||n.startsWith(">")&&E(t,r))break;o.push(t[r]??"")}return o.join(`
|
|
8
|
+
`).trim()}function E(e,t){for(let o=t;o<e.length;o+=1){const r=(e[o]??"").trim();if(!(!r||r.startsWith(">")))return!1}return!0}function F(e){const t=j(e.payload),o=l(c(t,"from"))[0]??{email:""},r=[...s(e.payload)].filter(n=>!!n.filename).map(n=>({name:n.filename,...n.body?.attachmentId?{attachmentId:n.body.attachmentId}:{},...n.body?.data===void 0?{}:{data:n.body.data},...n.mimeType?{mimeType:n.mimeType}:{},...n.body?.size===void 0?{}:{size:n.body.size}}));return{id:e.id,threadId:e.threadId,receivedAt:Number(e.internalDate??0),labelIds:e.labelIds??[],from:o,to:l(c(t,"to")),cc:l(c(t,"cc")),subject:b(c(t,"subject")),messageId:c(t,"message-id"),references:c(t,"references").split(/\s+/).filter(Boolean),body:R(B(e.payload)),attachments:r,headers:t}}function O(e){const t=l(c(e.headers,"reply-to"));return[{name:"To",value:e.to.map(u).join(", ")},{name:"Cc",value:e.cc.map(u).join(", ")},{name:"Reply-To",value:t.map(u).join(", ")},{name:"Date",value:e.receivedAt?new Date(e.receivedAt).toISOString():""},{name:"Subject",value:e.subject}]}function f(e){return/^[\x20-\x7e]*$/.test(e)?e:`=?UTF-8?B?${Buffer.from(e,"utf8").toString("base64")}?=`}const $=/[()<>[\]:;@\\,."]/;function u(e){return e.name?$.test(e.name)?`"${e.name.replaceAll("\\","\\\\").replaceAll('"','\\"')}" <${e.email}>`:`${e.name} <${e.email}>`:e.email}function m(e){if(!e.name)return e.email;const t=f(e.name);return t!==e.name?`${t} <${e.email}>`:$.test(e.name)?`"${e.name.replaceAll("\\","\\\\").replaceAll('"','\\"')}" <${e.email}>`:`${e.name} <${e.email}>`}function M(e){const t=e.trim();return t?/^re:/i.test(t)?t:`Re: ${t}`:"Re: (no subject)"}const L=4*1024*1024;async function q(e,t){let o=L;const r=e.filter(i=>i.size<=o?(o-=i.size,!0):(t(i,new Error(`${i.size} bytes is more than this mail can carry`)),!1));return(await Promise.all(r.map(async i=>{try{return{name:i.name,mimeType:i.mimeType,bytes:await x(i.path)}}catch(a){t(i,a);return}}))).filter(i=>i!==void 0)}const p=78;function d(e){if(e.length<=p)return e;const[t="",...o]=e.split(" "),r=[];let n=t;for(const i of o){const a=r.length===0?p:p-1;if(n&&`${n} ${i}`.length>a){r.push(n),n=i;continue}n=n?`${n} ${i}`:i}return n&&r.push(n),r.join(`\r
|
|
9
|
+
`)}function U(e){return D({...e,subject:M(e.subject)})}function y(e){return(typeof e=="string"?Buffer.from(e,"utf8"):Buffer.from(e)).toString("base64").replaceAll(/(.{76})/g,`$1\r
|
|
10
|
+
`)}function A(e){const t=f(e);return t!==e?t:`"${e.replaceAll("\\","\\\\").replaceAll('"','\\"')}"`}function D(e){const t=[...e.references??[],e.inReplyTo].filter(Boolean),o=e.files??[],r=`bishop-${g(8).toString("hex")}`,n=[`From: ${m(e.from)}`,`To: ${e.to.map(m).join(", ")}`,...e.cc&&e.cc.length>0?[`Cc: ${e.cc.map(m).join(", ")}`]:[],`Subject: ${f(e.subject)}`,...e.inReplyTo?[`In-Reply-To: ${e.inReplyTo}`]:[],...t.length>0?[`References: ${t.join(" ")}`]:[],`Auto-Submitted: ${e.inReplyTo?"auto-replied":"auto-generated"}`,"MIME-Version: 1.0",...o.length>0?[`Content-Type: multipart/mixed; boundary="${r}"`]:['Content-Type: text/plain; charset="UTF-8"',"Content-Transfer-Encoding: base64"]],i=o.length>0?[`--${r}`,'Content-Type: text/plain; charset="UTF-8"',"Content-Transfer-Encoding: base64","",y(e.body),...o.flatMap(a=>[`--${r}`,d(`Content-Type: ${a.mimeType}; name=${A(a.name)}`),"Content-Transfer-Encoding: base64",d(`Content-Disposition: attachment; filename=${A(a.name)}`),"",y(a.bytes)]),`--${r}--`].join(`\r
|
|
11
|
+
`):y(e.body);return Buffer.from(`${n.map(d).join(`\r
|
|
11
12
|
`)}\r
|
|
12
13
|
\r
|
|
13
|
-
${
|
|
14
|
-
`,"utf8").toString("base64url")}export{
|
|
14
|
+
${i}\r
|
|
15
|
+
`,"utf8").toString("base64url")}export{L as MAX_ATTACHED_BYTES,D as buildMessage,U as buildReply,b as decodeWords,u as describeAddress,f as encodeHeaderValue,B as extractBody,d as foldHeader,m as formatAddress,c as header,O as mailHeaders,l as parseAddressList,F as parseMessage,q as readAttachments,M as replySubject,S as stripHtml,R as stripQuotedReply};
|
package/dist/gmail/presence.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import{saysNoOutput as
|
|
1
|
+
import{saysNoOutput as s}from"../harness/principal.js";import{log as o}from"../log.js";import{fileShelf as d}from"../outgoing.js";import{buildReply as u,MAX_ATTACHED_BYTES as y,readAttachments as w}from"./message.js";const b="The agent finished without saying anything. Nothing went wrong that it reported, but there is no answer to send.";function A(e){const l=[];let r,h="",m=!1;const n=d({budget:y}),g=e.labels.mark(e.message.threadId,"working");return{share:t=>n.offer(t),async handle(t){switch(t.type){case"text":if(s(t.text))return;l.push(n.strip(t.text));return;case"done":h=s(t.text)?"":n.strip(t.text),m=!!e.mayDecline&&s(t.text);return;case"error":r=t.message;return;default:return}},async finish(){if(await g,m&&!r){o.info({thread:e.threadKey},"the agent left this message alone"),await e.labels.mark(e.message.threadId,"quiet");return}const t=l.join(`
|
|
2
2
|
|
|
3
|
-
`).trim()||
|
|
3
|
+
`).trim()||h.trim(),f=[...e.notices,r?`Something went wrong: ${r}`:"",t||(r?"":b)].filter(Boolean).join(`
|
|
4
4
|
|
|
5
|
-
`);try{await e.api.send({raw:
|
|
5
|
+
`),i=await w(n.offered(),(a,c)=>{o.error({thread:e.threadKey,file:a.name,err:c},"could not read a file the agent sent, so it is not on the reply")});try{await e.api.send({raw:u({from:e.self,to:e.to,subject:e.message.subject,inReplyTo:e.message.messageId,references:e.message.references,body:f,...i.length>0?{files:i}:{}}),threadId:e.message.threadId}),o.info({thread:e.threadKey,to:e.to.map(a=>a.email),chars:f.length,...i.length>0?{files:i.length}:{}},"replied by email")}catch(a){o.error({thread:e.threadKey,err:a},"could not send the reply"),await e.labels.mark(e.message.threadId,"failed");return}await e.labels.mark(e.message.threadId,r?"failed":"done")}}}export{A as gmailPresence};
|
package/dist/harness/claude.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import{resolve as
|
|
2
|
-
`)[0]??"";return
|
|
3
|
-
`)}function
|
|
1
|
+
import{resolve as m}from"node:path";import{query as g}from"@anthropic-ai/claude-agent-sdk";import{log as d}from"../log.js";import{isolates as p,worktreePath as k}from"../worktrees.js";import{buildAgentOptions as _,CONTROLLED_KEYS as x,SETTINGS_ONLY_KEYS as v}from"./claude-options.js";import{ignoredKeys as w,shadowedKeys as E}from"./layers.js";import{buildPrompt as S,principalInstructions as T}from"./principal.js";function*C(e){switch(e.type){case"system":e.subtype==="init"&&(yield{type:"session",sessionId:e.session_id,...e.model?{model:e.model}:{}});return;case"assistant":for(const t of e.message.content)if(t.type==="text"&&t.text.trim())yield{type:"text",text:t.text};else if(t.type==="tool_use"){const n=K(t.input);yield{type:"tool",name:t.name,...n?{detail:n}:{}}}else t.type==="thinking"&&(yield{type:"thinking"});return;case"tool_use_summary":yield{type:"tool_summary",summary:e.summary};return;case"result":e.subtype==="success"?yield{type:"done",text:e.result,usage:{turns:e.num_turns,costUsd:e.total_cost_usd}}:yield{type:"error",kind:e.subtype,message:P(e.subtype)};return;default:return}}function K(e){if(!e||typeof e!="object")return;const t=e,n=t.file_path??t.notebook_path??t.path;if(typeof n=="string"&&n)return n.split("/").pop()??n;const i=t.url;if(typeof i=="string"&&i)try{return new URL(i).hostname}catch{return i.slice(0,40)}for(const u of["pattern","query","description"]){const l=t[u];if(typeof l=="string"&&l.trim()){const c=l.trim().split(`
|
|
2
|
+
`)[0]??"";return c.length>48?`${c.slice(0,45)}...`:c}}}function A(e){switch(e){case"none":return"existing claude login or cloud provider";case"/login managed key":return"key managed by claude login";default:return e}}function P(e){switch(e){case"error_max_turns":return"the agent hit its turn limit before finishing";case"error_max_budget_usd":return"the agent hit its cost limit before finishing";case"error_during_execution":return"the agent stopped on an error";default:return`the agent stopped: ${e}`}}const I=new Set(["Edit","Write","NotebookEdit"]);function N(e,t){return["This directory is shared with other conversations that may be running at the same time.",`Before you edit, create, or delete any file here, call EnterWorktree with name "${t}"`,"so your changes land in a git worktree of your own. Load the tool with ToolSearch first if","it isn't available. If a worktree by that name already exists, enter it by path instead:",`${k(e,t)}.`,"","Once you are in the worktree, work normally. Relative paths resolve inside it, and you","stay in it for every later message in this thread. Commit what you finish on the","worktree's branch and say which branch it is, since that's how a person finds your work.","Don't call ExitWorktree."].join(`
|
|
3
|
+
`)}function R(e){const t=m(e);return({toolName:n,cwd:i})=>{if(I.has(n)&&m(i)===t)return"This is the shared checkout, which other conversations are using. Call EnterWorktree with the name given in your instructions, then make this edit inside the worktree."}}function L(e,t,n,i){const u=p(e)?N(i,n):void 0;return[T(t),u].filter(l=>!!l?.trim()).join(`
|
|
4
4
|
|
|
5
|
-
`)}function
|
|
5
|
+
`)}function $(e,t){return{model:t?.model??e.model,effort:t?.effort??e.effort}}function D(e){return async t=>{const n=t,i=e({toolName:n.tool_name,cwd:n.cwd});return i?(d.info({tool:n.tool_name,cwd:n.cwd},"refusing an edit in the shared checkout"),{continue:!0,hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:i}}):{continue:!0}}}function F(e={}){const t=w(e.options,x);t.length>0&&d.warn({ignored:t},"these agent.options are set by Bishop and cannot be overridden; ignoring them");const n=r=>$(e,r),i=n(),u=E(e.options,i);u.length>0&&d.warn({shadowed:u},"agent.model and agent.effort override these agent.options");const l=w(e.options,v);l.length>0&&d.warn({misplaced:l},"the harness reads these from agent.options.settings only and ignores them here");const c=e.worktrees;if(typeof e.options?.settings=="string"){const r=["auto memory is left to that file and the agent's own settings"];p(c)&&r.push("new worktrees branch from origin/<default-branch> rather than HEAD"),d.warn({settings:e.options.settings},`agent.options.settings names a file, so Bishop applies no settings defaults: ${r.join("; ")}`)}const y=(r,s,o)=>{const f=p(c)?R(r.cwd):void 0,a=f?{PreToolUse:[{hooks:[D(f)]}]}:void 0;return _({...e.options?{configOptions:e.options}:{},general:n(o),...e.apiKey?{apiKey:e.apiKey}:{},...a?{hooks:a,worktrees:!0}:{},...s?{tools:s}:{},controlled:r})};return{name:"claude",describeStartupFailure(r,s){return`The Claude agent couldn't start in ${r}:
|
|
6
6
|
${s}
|
|
7
7
|
|
|
8
8
|
Bishop does not manage Anthropic credentials. The agent authenticates the
|
|
9
9
|
same way \`claude\` does in this directory, so make sure that works first:
|
|
10
10
|
ANTHROPIC_API_KEY in the environment or .env, or
|
|
11
|
-
an existing \`claude\` login on this machine.`},async check(r){const s=new AbortController;try{for await(const o of
|
|
11
|
+
an existing \`claude\` login on this machine.`},async check(r){const s=new AbortController;try{for await(const o of g({prompt:"ping",options:{...y({cwd:r,systemPrompt:void 0,abortController:s}),maxTurns:1,persistSession:!1}})){if(o.type==="system"&&o.subtype==="init")return s.abort(),{ok:!0,credentialSource:A(o.apiKeySource),...o.model?{model:o.model}:{}};if(o.type==="result")break}return{ok:!1,error:"the harness exited without starting a session"}}catch(o){return s.signal.aborted?{ok:!0}:{ok:!1,error:o instanceof Error?o.message:String(o)}}},async*runTurn(r){const s=new AbortController,o=()=>s.abort();r.signal.addEventListener("abort",o,{once:!0});let f=!1;try{const a=g({prompt:S(r),options:y({cwd:r.cwd,systemPrompt:{type:"preset",preset:"claude_code",append:L(c,r.origin,r.worktree,r.cwd)},abortController:s,...r.sessionId?{resume:r.sessionId}:{}},r.tools,r)});for await(const b of a)for(const h of C(b))(h.type==="done"||h.type==="error")&&(f=!0),yield h}catch(a){s.signal.aborted?d.debug({thread:r.threadKey},"claude turn aborted"):f?d.debug({err:a},"query() threw after reporting a result"):(d.error({err:a},"harness turn failed"),yield{type:"error",kind:"harness_failure",message:a instanceof Error?a.message:String(a)})}finally{r.signal.removeEventListener("abort",o)}}}}export{$ as chosenGeneral,F as claudeHarness,R as editGuard,L as systemPromptAppend,K as toolDetail,N as worktreeNote};
|
package/dist/harness/codex.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import{existsSync as
|
|
2
|
-
`)}function
|
|
1
|
+
import{existsSync as D}from"node:fs";import{homedir as K}from"node:os";import{join as _}from"node:path";import{Codex as A}from"@openai/codex-sdk";import{log as g}from"../log.js";import{SERVER_NAME as I}from"../tools/server.js";import{ensureWorktree as P,gitCommonDir as j,isolates as M}from"../worktrees.js";import{ignoredKeys as R,layerOptions as L,mergeEnv as N,shadowedKeys as X}from"./layers.js";import{buildPrompt as Y,principalInstructions as $}from"./principal.js";const U=10,W=60;function B(e,t){const o=e??void 0;return t?{...o,[I]:{url:t.url,bearer_token_env_var:t.tokenEnvVar,required:!0,default_tools_approval_mode:"auto",startup_timeout_sec:U,tool_timeout_sec:W}}:o}const F={approvalPolicy:"never",sandboxMode:"workspace-write",networkAccessEnabled:!0,skipGitRepoCheck:!0},x=["workingDirectory","developer_instructions"];function G(e){const t=e?.config,o=t&&typeof t=="object"&&"developer_instructions"in t?["config.developer_instructions"]:[];return[...R(e,x),...o]}const V=new Set(["apiKey","baseUrl","config","configOverrides","env","codexPathOverride"]);function H(e){switch(e.type){case"command_execution":return{type:"tool",name:"Bash"};case"file_change":{const t=e.changes.length;if(t>1)return{type:"tool",name:"Edit",detail:`${t} files`};const o=t===1?e.changes[0]?.path.split("/").pop():void 0;return{type:"tool",name:"Edit",...o?{detail:o}:{}}}case"mcp_tool_call":return{type:"tool",name:`mcp__${e.server}__${e.tool}`};case"web_search":return{type:"tool",name:"WebSearch",...e.query?{detail:e.query}:{}};case"todo_list":return{type:"tool",name:"TodoWrite"};case"reasoning":return{type:"thinking"};default:return}}const z=new Set(["command_execution","mcp_tool_call","web_search","reasoning"]);function J(e){const t=new Set;let o="";return i=>{switch(i.type){case"thread.started":return[{type:"session",sessionId:i.thread_id,...e?{model:e}:{}}];case"item.started":case"item.completed":{const s=i.item,l=z.has(s.type)?"item.started":"item.completed";if(i.type!==l||t.has(s.id))return[];if(t.add(s.id),s.type==="agent_message")return o=s.text,s.text.trim()?[{type:"text",text:s.text}]:[];if(s.type==="error")return g.warn({message:s.message},"codex reported a non-fatal error"),[];const u=H(s);return u?[u]:[]}case"turn.completed":return[{type:"done",text:o,usage:{inputTokens:i.usage.input_tokens,cachedInputTokens:i.usage.cached_input_tokens,outputTokens:i.usage.output_tokens}}];case"turn.failed":return[{type:"error",kind:"turn_failed",message:i.error.message}];case"error":return[{type:"error",kind:"stream_error",message:i.message}];default:return[]}}}function Q(e){return[`You are working in a git worktree of your own, on the branch "${e}", created for this`,"conversation. Other conversations are working in their own worktrees at the same time.","","Work normally. Commit what you finish on this branch and say which branch it is, since that's","how a person finds your work."].join(`
|
|
2
|
+
`)}function Z(e,t){const o=Array.isArray(e)?e:[],i=[...new Set([...o,...t??[]])];return i.length>0?{additionalDirectories:i}:{}}function k(e){return{model:e.model,modelReasoningEffort:e.effort}}function q(e){return Object.fromEntries(Object.entries(k(e)).filter(([,t])=>t!==void 0))}const ee={model:"model",modelReasoningEffort:"model_reasoning_effort"};function te(e,t){const o=e?.config,i=o&&typeof o=="object"?Object.entries(ee).filter(([s,l])=>t[s]!==void 0&&l in o).map(([,s])=>`config.${s}`):[];return[...X(e,t),...i]}function pe(e={}){const t=G(e.options);t.length>0&&g.warn({ignored:t},"these agent.options are set by Bishop and cannot be overridden; ignoring them");const o=k(e),i=te(e.options,o);i.length>0&&g.warn({shadowed:i},"agent.model and agent.effort override these agent.options");const s=L({defaults:F,...e.options?{configOptions:e.options}:{},general:o,controlledKeys:x}),l={},u={};for(const[r,n]of Object.entries(s))V.has(r)?l[r]=n:u[r]=n;const E=l.env;e.apiKey&&(l.apiKey=e.apiKey);const b=e.worktrees,w=(r,n,a)=>{const d=l.config??{},c=B(d.mcp_servers,a),m=N(E,a?{[a.tokenEnvVar]:a.token}:{}),f={...d,developer_instructions:[n&&$(n),r].filter(p=>!!p?.trim()).join(`
|
|
3
3
|
|
|
4
|
-
`)};return
|
|
4
|
+
`)};return c&&(f.mcp_servers=c),new A({...l,...m?{env:m}:{},config:f})};async function v(r){if(!M(b))return{cwd:r.cwd};const n=await P(r.cwd,r.worktree),a=await j(n);return a||g.warn({worktree:n},"couldn't resolve the repository's git directory, so the agent probably can't commit"),{cwd:n,note:Q(r.worktree),...a?{writable:[a]}:{}}}return{name:"codex",describeStartupFailure(r,n){return`The Codex agent couldn't start in ${r}:
|
|
5
5
|
${n}
|
|
6
6
|
|
|
7
7
|
Bishop does not manage OpenAI credentials. The agent authenticates the
|
|
8
8
|
same way \`codex\` does on this machine, so make sure that works first:
|
|
9
9
|
CODEX_API_KEY in the environment or .env, or
|
|
10
|
-
an existing \`codex login\` on this machine.`},async check(
|
|
10
|
+
an existing \`codex login\` on this machine.`},async check(r){const n=new AbortController,a=u.model;try{const c=w(void 0,void 0).startThread({...u,workingDirectory:r}),{events:m}=await c.runStreamed("ping",{signal:n.signal});for await(const f of m){if(f.type==="thread.started"){n.abort();const p=re(e.apiKey);return oe(p),{ok:!0,...p?{credentialSource:p}:{},...a?{model:a}:{}}}if(f.type==="error")return{ok:!1,error:f.message}}return{ok:!1,error:"the harness exited without starting a thread"}}catch(d){return n.signal.aborted?{ok:!0}:{ok:!1,error:d instanceof Error?d.message:String(d)}}},async*runTurn(r){const n=new AbortController,a=()=>n.abort();r.signal.addEventListener("abort",a,{once:!0});let d=!1;try{const{cwd:c,note:m,writable:f}=await v(r),p=w(m,r.origin,r.tools),h={...u,...q(r),workingDirectory:c,...Z(u.additionalDirectories,f)},C=r.sessionId?p.resumeThread(r.sessionId,h):p.startThread(h),S=J(h.model),{events:T}=await C.runStreamed(Y(r),{signal:n.signal});for await(const O of T)for(const y of S(O))(y.type==="done"||y.type==="error")&&(d=!0),yield y}catch(c){n.signal.aborted?g.debug({thread:r.threadKey},"codex turn aborted"):d?g.debug({err:c},"codex threw after reporting a result"):(g.error({err:c},"harness turn failed"),yield{type:"error",kind:"harness_failure",message:c instanceof Error?c.message:String(c)})}finally{r.signal.removeEventListener("abort",a)}}}}function re(e){if(e)return"CODEX_API_KEY";const t=process.env.CODEX_HOME??_(K(),".codex");if(D(_(t,"auth.json")))return"existing codex login"}function oe(e){e||g.warn("no CODEX_API_KEY and no codex login found, so the first turn will probably fail to authenticate; run `codex login`, or set CODEX_API_KEY")}export{x as CONTROLLED_CODEX_KEYS,F as DEFAULT_CODEX_OPTIONS,q as chosenCodexOptions,k as codexGeneralOptions,pe as codexHarness,J as codexTranslator,G as ignoredCodexKeys,B as mergeMcpServers,te as shadowedCodexKeys,Z as withWritable,Q as worktreeNote};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{FETCH_FILE_TOOL as i}from"../harness/principal.js";const
|
|
1
|
+
import{FETCH_FILE_TOOL as i}from"../harness/principal.js";import{SHARE_FILE_TOOL as a}from"../outgoing.js";const s={Read:e=>e?`Reading ${e}`:"Reading a file",Write:e=>e?`Writing ${e}`:"Writing a file",Edit:e=>e?`Editing ${e}`:"Editing a file",NotebookEdit:e=>e?`Editing ${e}`:"Editing a notebook",Bash:e=>e??"Running a command",BashOutput:()=>"Checking on a command",Glob:e=>e?`Looking for ${e}`:"Looking through files",Grep:e=>e?`Searching for ${e}`:"Searching the code",WebFetch:e=>e?`Reading ${e}`:"Fetching a page",WebSearch:e=>e?`Searching for ${e}`:"Searching the web",Task:e=>e?`Working on ${e}`:"Delegating a subtask",TodoWrite:()=>"Planning",ExitPlanMode:()=>"Finishing up a plan",AskUserQuestion:()=>"Considering options"},l={schedule_create:"Setting up a schedule",schedule_list:"Checking what is scheduled",schedule_delete:"Removing a schedule",[i]:"Fetching a file from this conversation",[a]:"Getting a file ready to send"};function c(e){if(!e.startsWith("mcp__"))return;const[,n,...t]=e.split("__");return n==="bishop"?l[t.join("__")]??"Sorting something out":n?`Calling ${n}`:"Calling a tool"}function u(e){const n=e.replaceAll(/[_-]+/g," ").replaceAll(/([a-z\d])([A-Z])/g,"$1 $2").trim();if(!n)return"Working";const t=n.toLowerCase();return t.charAt(0).toUpperCase()+t.slice(1)}const r=100;function g(e){const n=e.trim();return n.length>r?`${n.slice(0,r-3)}...`:n}function d(e){const n=e.summary?.trim();if(n)return g(n);const t=s[e.name];if(t)return t(e.detail);const o=c(e.name);return o||u(e.name)}function h(e){const n=Math.max(0,Math.round(e/1e3)),t=Math.floor(n/60),o=n%60;return t?`${t}m ${o}s`:`${o}s`}function p(e){return e===1?"Used 1 tool":`Used ${e} tools`}function $(e,n){return`${n?"Worked for":"Stopped after"} ${h(e)}`}export{h as elapsed,u as humanizeToolName,g as summaryPhrase,d as toolPhrase,p as toolTally,$ as turnTally};
|
package/dist/outgoing.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{randomBytes as b}from"node:crypto";import{stat as T}from"node:fs/promises";import{basename as k,isAbsolute as _}from"node:path";import{log as $}from"./log.js";const C="file_share",z=25*1024*1024,A=10;class m extends Error{constructor(o){super(o),this.name="ShareRefused"}}const j={png:"image/png",jpg:"image/jpeg",jpeg:"image/jpeg",gif:"image/gif",webp:"image/webp",svg:"image/svg+xml",pdf:"application/pdf",csv:"text/csv",tsv:"text/tab-separated-values",txt:"text/plain",log:"text/plain",md:"text/markdown",json:"application/json",yaml:"application/yaml",yml:"application/yaml",html:"text/html",xml:"application/xml",zip:"application/zip",docx:"application/vnd.openxmlformats-officedocument.wordprocessingml.document",xlsx:"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",pptx:"application/vnd.openxmlformats-officedocument.presentationml.presentation"};function M(t){const o=t.lastIndexOf("."),s=o>0?t.slice(o+1).toLowerCase():"";return j[s]??"application/octet-stream"}function u(t){return t>=1024*1024?`${Math.round(t/(1024*1024))}MB`:t>=1024?`${Math.round(t/1024)}KB`:`${t} bytes`}function R(t){return t.replaceAll(/[/\\]/g,"-").replaceAll(/[\u0000-\u001f\u007f]/g,"").trim().replace(/^\.+/,"").trim()||"file"}const I=/<\s*attachment\b([^>]*?)\/?\s*>/gi,B=/\bref\s*=\s*"([^"]*)"|\bref\s*=\s*'([^']*)'/i,L="attachment";function N(t={}){const o=t.budget??z,s=new Map,h=new Set,g=new Set;let d=0;function w(e,n){const a=[],r=[];let i="",c=0;for(const l of e.matchAll(I)){const v=l.index??0,S=e.slice(c,v);c=v+l[0].length;const E=B.exec(l[1]??""),p=(E?.[1]??E?.[2]??"").trim(),f=s.get(p);if(r.push(S),i+=S,!f){g.has(p)||(g.add(p),$.warn({ref:p||"(none)"},"the agent placed a file reference naming nothing"));continue}h.has(f.ref)||(n&&h.add(f.ref),a.push(i,f),i="")}return r.push(e.slice(c)),a.push(i+e.slice(c)),{parts:a.filter(l=>typeof l!="string"||l.trim()),text:r.join("").trim()}}let x=Promise.resolve();async function y(e){const n=e.path.trim();if(!_(n))throw new m(`${n} is not an absolute path. Bishop sends the file from where it sits on disk and has no working directory of yours to resolve a relative one against.`);if(s.size>=A)throw new m(`One answer can carry ${A} files, and that many are already on it.`);const a=await T(n).catch(()=>{});if(!a?.isFile())throw new m(`There is no file at ${n}.`);if(d+a.size>o)throw new m(`${u(a.size)} is more than this conversation can carry: the limit is ${u(o)} across everything one answer sends, and ${u(d)} of it is already spoken for.`);const r=R(e.name?.trim()||k(n)),i={ref:b(6).toString("base64url"),path:n,name:r,size:a.size,mimeType:M(r)};return d+=a.size,s.set(i.ref,i),i}return{offer(e){const n=x.then(()=>y(e),()=>y(e));return x=n.catch(()=>{}),n},offered:()=>[...s.values()],place:e=>w(e,!0),strip:e=>w(e,!1).text,unplaced:()=>[...s.values()].filter(e=>!h.has(e.ref))}}const D=["Sends a file from disk to the conversation you are in, as a real attachment: an upload in","Slack, an attachment on the mail in email.","","Takes an absolute path and hands back a ref. Place it to send it: write",`<${L} ref="the ref" /> in your reply where you want the file to appear, and`,"Bishop puts it there and removes the tag. Say what the file is around it, the way you would","if you were sending it yourself.","","It fails, with the reason, when there is no file at the path or it is too large to send."].join(`
|
|
2
|
+
`);export{A as MAX_SHARED_FILES,z as MAX_SHARE_BYTES,D as SHARE_FILE_DESCRIPTION,C as SHARE_FILE_TOOL,m as ShareRefused,N as fileShelf,M as guessType};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{fileShelf as i,MAX_SHARE_BYTES as f}from"../outgoing.js";import{isSilent as c}from"./prompt.js";function u(){const n=[],s=[];let o="",t;const r=i({budget:f});return{share:e=>r.offer(e),async handle(e){switch(e.type){case"text":n.push(e.text);return;case"done":o=e.text;return;case"error":t=e.message;return;default:return}},async finish(){},note(e){s.push(e)},outcome(){const e=o.trim()||n.join(`
|
|
2
2
|
|
|
3
|
-
`).trim();return{text:[...r
|
|
3
|
+
`).trim();return{text:[...s,r.strip(e)].filter(Boolean).join(`
|
|
4
4
|
|
|
5
|
-
`),...t?{failure:t}:{},silent:
|
|
5
|
+
`),files:r.offered(),...t?{failure:t}:{},silent:c(e)}}}}export{u as schedulePresence};
|
package/dist/schedule/runner.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import{log as a}from"../log.js";import{nextRun as z}from"./cron.js";import{schedulePresence as
|
|
1
|
+
import{log as a}from"../log.js";import{SHARE_FILE_TOOL as $}from"../outgoing.js";import{nextRun as z}from"./cron.js";import{schedulePresence as I}from"./presence.js";import{SCHEDULE_ORIGIN as R,scheduleNote as N}from"./prompt.js";const M=3e4,A=5,F=5e3;function O(r){return r.startsWith("cli:")}function B(r,l){return`schedule:${r}:${l}`}function K(r){const l=r.now??Date.now,T=r.tickMs??M,x=r.authorized??O,s=new Map,g=new Map,u=new Set;let f,S=!1;function w(e,t){try{return r.schedules.setNextRun(e.id,z(e.cron,e.tz,t)),!0}catch(i){const n=i instanceof Error?i.message:String(i);return a.error({schedule:e.id,cron:e.cron,tz:e.tz,err:i},"disabling a schedule whose cron expression cannot be read"),r.schedules.setEnabled(e.id,!1),r.schedules.recordFailure(e.id,t,n),y(e,`The schedule "${e.name}" is off: Bishop can no longer read when it should run. ${n}
|
|
2
2
|
|
|
3
|
-
Remove it, or add it again with a cron expression that works.`),!1}}async function
|
|
3
|
+
Remove it, or add it again with a cron expression that works.`),!1}}async function y(e,t){const i=r.deliver(e.destination,{text:t,title:e.name}).then(()=>{}).catch(n=>{a.error({schedule:e.id,to:e.destination.label,err:n},"could not say that a schedule failed")}).finally(()=>{u.delete(i)});u.add(i),await i}async function p(e,t,i){const n=r.schedules.recordFailure(e.id,t,i);if(a.error({schedule:e.id,name:e.name,failures:n,err:i},"a scheduled run failed"),n>=A){r.schedules.setEnabled(e.id,!1),a.error({schedule:e.id,name:e.name,failures:n},"disabling a schedule after too many failures in a row"),await y(e,`The schedule "${e.name}" has failed ${n} times in a row and is now off. The last failure was: ${i}
|
|
4
4
|
|
|
5
|
-
Nothing more will run until \`bishop schedules enable ${e.id}\`.`);return}n===1&&await
|
|
5
|
+
Nothing more will run until \`bishop schedules enable ${e.id}\`.`);return}n===1&&await y(e,`The schedule "${e.name}" failed: ${i}`)}async function k(e,t,i){const n=I();a.info({schedule:e.id,name:e.name},"a schedule is firing");let c,b;try{b=await r.threads.remember(i)}catch(o){await p(e,l(),o instanceof Error?o.message:String(o));return}const _={thread:b,isNewThread:!0,freshSession:!0,tools:[$],origin:R,note:N(e,t),async messages(){return[{principal:{id:`schedule:${e.id}`,displayName:e.name},text:e.prompt}]},presence:()=>n,async notify(o){n.note(o)},async destination(){return e.destination}};try{await r.run(_)}catch(o){c=o instanceof Error?o.message:String(o)}const d=n.outcome(),v=d.failure??c,h=l();if(v){await p(e,h,v);return}if(d.silent){r.schedules.recordSuccess(e.id,h),a.info({schedule:e.id,name:e.name},"a schedule had nothing to report");return}let m;try{m=await r.deliver(e.destination,{text:d.text,title:e.name,...d.files.length>0?{files:d.files}:{}})}catch(o){await p(e,h,o instanceof Error?o.message:String(o));return}if(m.threadKey&&r.attach)try{await r.attach(m.threadKey,i)}catch(o){a.error({schedule:e.id,thread:m.threadKey,err:o},"a report was delivered but nothing will answer a reply to it")}r.schedules.recordSuccess(e.id,h),a.info({schedule:e.id,name:e.name,to:e.destination.label},"a schedule reported")}async function E(){if(S)return;const e=l();for(const t of r.schedules.due(e)){if(!x(t.createdBy)){a.warn({schedule:t.id,createdBy:t.createdBy},"skipping a schedule whose creator can no longer be authorized"),w(t,e);continue}if(!w(t,e))continue;if(s.has(t.id)){a.warn({schedule:t.id,name:t.name},"skipping a firing while the previous one is still running");continue}const i=B(t.id,e);g.set(t.id,i);const n=k(t,e,i).catch(c=>{a.error({schedule:t.id,err:c},"a schedule firing threw")}).finally(()=>{s.delete(t.id),g.delete(t.id)});s.set(t.id,n)}}return{start(){const e=l(),t=r.schedules.due(e);for(const n of t)a.info({schedule:n.id,name:n.name,due:new Date(n.nextRunAt).toISOString()},"skipping a firing that came due while Bishop was not running"),w(n,e);const i=r.schedules.all().filter(n=>n.enabled);a.info({schedules:i.length,skipped:t.length},"watching the schedules"),f=setInterval(()=>{E().catch(n=>{a.error({err:n},"a scheduler pass failed; trying again on the next tick")})},T),f.unref()},tick:E,async settle(){await Promise.allSettled([...s.values(),...u])},async stop(){if(S=!0,f&&clearInterval(f),s.size>0){a.info({firings:s.size},"stopping scheduled runs still going");for(const n of s.keys()){const c=g.get(n);c&&r.cancel?.(c)}}const e=Promise.allSettled([...s.values(),...u]);let t;const i=new Promise(n=>{t=setTimeout(()=>{a.warn({firings:s.size},"giving up on scheduled runs that would not stop"),n()},F)});await Promise.race([e,i]),t&&clearTimeout(t)}}}export{A as MAX_CONSECUTIVE_FAILURES,O as authorizedToRun,B as firingThread,K as makeScheduler};
|
|
@@ -1 +1,3 @@
|
|
|
1
|
-
import{Command as
|
|
1
|
+
import{Command as L,CommanderError as N,InvalidArgumentError as g}from"commander";import{VERBOSITY as y}from"../../config.js";import{log as b}from"../../log.js";import{isHarnessName as S,MODEL_SETTINGS as O}from"../../thread-model.js";import{parseVerbosity as E}from"../verbosity.js";import{commandOutput as T,tokenize as j}from"./syntax.js";const a="default";function k(o){if(o.trim().toLowerCase()===a)return a;const e=E(o);if(!e)throw new g(`Use one of ${y.join(", ")}, or ${a}.`);return e}function l(o){const e=o.trim();if(!e)throw new g(`Name one, or ${a} to follow the configured one.`);if(!S(e))throw new g(`"${e}" is not a model name. Letters, digits, and . : / @ - _ only.`);return e}function I(o,e,t){return e?`${o} is ${e}, set in this thread`:t?`${o} is ${t}, the default`:`${o} is whatever the harness runs by default`}function C(o,e){const t=e.model.of(o);return[I("Model",t.model,e.configured.model),I("Effort",t.effort,e.configured.effort)]}function H(o,e,t){const u=[];let h;const f=new L;f.name("!").description("Bishop commands. These are handled here and never reach the agent.").exitOverride().configureOutput({writeOut:r=>u.push(r),writeErr:r=>u.push(r),outputError:(r,n)=>n(r)}),f.command("verbosity").description("How much of a turn this thread shows while it is running.").argument("[level]",`${y.join(", ")}, or ${a} to follow the configured level. Omit it to see the current one.`,k).action(r=>{if(!r){const c=t.verbosity.of(e.thread.id),d=c.fromThread?"":" (the configured default)";h=`Thread verbosity is ${c.level}${d}`;return}if(r===a){t.verbosity.clear(e.thread.id),h=`Thread verbosity follows the configured default, which is ${t.verbosity.of(e.thread.id).level}`;return}const n=E(r);n&&(t.verbosity.set(e.thread.id,n),h=`Thread verbosity set to ${n}`)}),f.command("model").description("Which model this thread runs, and how hard it thinks.").argument("[model]",`a model name in the harness's own naming, or ${a} to follow the configured one. Omit everything to see where the thread stands.`,l).argument("[effort]",`how hard it should think, in the harness's own naming, or ${a}.`,l).option("-m, --model <model>","the model, named as a flag rather than in order",l).option("-e, --effort <effort>","the effort, named as a flag rather than in order",l).action((r,n,c)=>{const d=e.thread.id,s={...c},w=O.filter(i=>s[i]===void 0),v=[r,n].filter(i=>i!==void 0);if(v.length>w.length){h="Name each half once: a model and an effort, in order or as flags.";return}if(v.forEach((i,m)=>{const p=w[m];p&&(s[p]=i)}),!s.model&&!s.effort){h=C(d,t).join(`
|
|
2
|
+
`);return}const $={};for(const i of O){const m=s[i];m&&(m.toLowerCase()===a?t.model.clear(d,i):$[i]=m)}t.model.set(d,$),b.info({thread:e.thread.threadKey,...s},"a thread changed the model it runs"),h=["Set. It takes effect on this thread's next turn.",...C(d,t)].join(`
|
|
3
|
+
`)});try{f.parse(j(o),{from:"user"})}catch(r){if(!(r instanceof N)){const n=r instanceof Error?r.message:String(r);return b.debug({thread:e.thread.threadKey,line:o,err:r},"a command failed"),T(n)}}return T(h??(u.join("").trim()||f.helpInformation()))}export{H as runCommand};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{BishopError as
|
|
1
|
+
import{BishopError as l}from"../errors.js";import{log as p}from"../log.js";import{retry as m}from"../retry.js";import{splitForSlack as b}from"./format.js";import{attachToSlack as S}from"./upload.js";function w(e){return e.threadTs?`${e.channel}:${e.threadTs}`:e.channel}function k(e){const[n,a]=e.split(":");if(!n)throw new l(`not a Slack destination: ${e}`);return{channel:n,...a?{threadTs:a}:{}}}function v(e){return/^[CG][A-Z0-9]{2,}$/.test(e)}function x(e){return/^D[A-Z0-9]{2,}$/.test(e)}function $(e){return/^[UW][A-Z0-9]{2,}$/.test(e)}const T=new Set(["ratelimited","rate_limited"]);function _(e){const n=e;if(!n)return!1;if(n.code==="slack_webapi_rate_limited_error")return{...n.retryAfter?{retryAfterMs:n.retryAfter*1e3}:{}};const a=n.data?.error;return!!(a&&T.has(a))}async function L(e,n){const{channel:a,threadTs:r}=k(n.handle),d=b(n.text),s=n.files??[],t=s.length>0?d.pop()??"":void 0;let i;for(const h of d){const c=r??i,f=await m(()=>e.chat.postMessage({channel:a,markdown_text:h,...c?{thread_ts:c}:{}}),{what:"chat.postMessage",retryable:_});i??=f.ts}const o=r??i;if(t!==void 0){const h={channel:a,...o?{threadTs:o}:{}};try{const c=await S(e,h,s,t);if(i??=c?.ts,!c&&t){const f=await m(()=>e.chat.postMessage({channel:a,markdown_text:t,...o?{thread_ts:o}:{}}),{what:"chat.postMessage",retryable:_});i??=f.ts}}catch(c){const f=s.map(u=>u.name).join(", ");p.error({channel:a,files:f,err:c},"could not send a file with a report");for(const u of[t,`_Could not upload ${f}._`]){if(!u)continue;const y=await e.chat.postMessage({channel:a,markdown_text:u,...o?{thread_ts:o}:{}}).catch(()=>{});i??=y?.ts}}}return p.info({channel:a,...o?{thread:o}:{},parts:d.length},"delivered to Slack"),r||!i?void 0:{channel:a,threadTs:i}}async function F(e,n){const{channel:a,threadTs:r}=k(n),d=r?" (thread)":"";try{const t=(await e.conversations.info({channel:a})).channel;if(t?.name)return`#${t.name}${d}`;if(t?.is_im&&t.user){const i=await e.users.info({user:t.user}),o=i.user?.profile?.display_name?.trim()||i.user?.name;if(o)return`@${o}`}}catch(s){p.debug({channel:a,err:s},"could not name a Slack destination")}return`${a}${d}`}async function A(e,n){const a=n.toLowerCase();for await(const r of e.paginate("conversations.list",{types:"public_channel,private_channel",exclude_archived:!0,limit:200})){const s=(r.channels??[]).find(t=>t.name?.toLowerCase()===a);if(s)return s}}async function C(e,n){const a=n.toLowerCase();if(a.includes("@"))try{const r=await e.users.lookupByEmail({email:n});if(r.user?.id)return r.user.id}catch(r){p.debug({email:n,err:r},"no Slack user with that address")}for await(const r of e.paginate("users.list",{limit:200})){const d=r.members??[];for(const s of d){if(s.deleted||!s.id)continue;if([s.name,s.profile?.display_name,s.profile?.real_name].some(i=>i?.toLowerCase()===a))return s.id}}}async function R(e,n,a={}){try{return await I(e,n,a)}catch(r){throw r instanceof l?r:M(r,n)}}function M(e,n){const a=e,r=a?.data?.error;return r==="missing_scope"?new l(`Slack won't let this app look up ${n}: it is missing the ${a?.data?.needed??"required"} scope. Bishop's manifest asks for it, so an app installed before this existed has to be reinstalled: run \`bishop slack setup\` again.`):new l(`Slack could not resolve ${n}: ${r??(e instanceof Error?e.message:String(e))}`)}async function I(e,n,a){const r=n.trim();if(!r)throw new l("a Slack destination needs a channel or a person");const[d,s]=r.split(":"),t=(d??"").replace(/^[#@]/,"");if(!t)throw new l(`not a Slack destination: ${n}`);const i=r.startsWith("@")||$(t);if(x(t))return{interface:"slack",handle:w({channel:t,...s?{threadTs:s}:{}}),label:t};if(v(t)){const c=(await e.conversations.info({channel:t})).channel;if(!c?.id)throw new l(`Slack has no conversation ${t}`);return g(c,t),{interface:"slack",handle:w({channel:c.id,...s?{threadTs:s}:{}}),label:c.name?`#${c.name}`:c.id}}if(i){const h=$(t)?t:await C(e,t);if(!h)throw new l(`no Slack user matches @${t}`);if(a.allows&&!a.allows(h))throw new l(`@${t} is not on this agent's allow list, so Bishop won't start sending them messages they never asked for. Add them to slack.allow in .bishop/config.json, or post to a channel they are in instead.`);const f=(await e.conversations.open({users:h})).channel?.id;if(!f)throw new l(`could not open a DM with @${t}`);return{interface:"slack",handle:w({channel:f,...s?{threadTs:s}:{}}),label:`@${t}`}}const o=await A(e,t);if(!o?.id)throw new l(`no Slack channel named #${t} that this app can see. A private channel has to have the app in it.`);return g(o,`#${t}`),{interface:"slack",handle:w({channel:o.id,...s?{threadTs:s}:{}}),label:`#${o.name??t}`}}function g(e,n){if(!(e.is_im||e.is_member!==!1))throw new l(`this app is not in ${n}. Invite it there first, or nothing sent to it will arrive.`)}export{L as deliverToSlack,F as describeSlackHandle,k as parseSlackHandle,R as resolveSlackDestination,w as slackHandle,_ as slackRetryable};
|
package/dist/slack/interface.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{etiquetteNote as C,threadParticipants as
|
|
2
|
-
`)},
|
|
1
|
+
import{etiquetteNote as C,threadParticipants as L}from"../interface/etiquette.js";import{log as p}from"../log.js";import{createSlackApp as O}from"./app.js";import{createAuthorizer as E}from"./authorizer.js";import{botTurnCap as H}from"./bots.js";import{runCommand as F}from"./commands/run.js";import{commandLine as K}from"./commands/syntax.js";import{deliverToSlack as _,describeSlackHandle as U,parseSlackHandle as b,resolveSlackDestination as z,slackHandle as R}from"./destination.js";import{describeSlackFile as W,parseSlackFile as j,slackAttachment as v,slackFilePayload as q}from"./files.js";import{registerHandlers as G}from"./handlers.js";import{landedMidThread as V,threadHistory as Y}from"./history.js";import{linkMentions as I,mentionableName as S,renderMentions as T}from"./mentions.js";import{createPresence as $}from"./presence.js";import{say as N}from"./say.js";import{threadKeyString as J}from"./thread.js";import{userDirectory as Q}from"./users.js";import{threadVerbosity as X}from"./verbosity.js";const Z={name:"slack",guidance:["This conversation is a Slack thread. Each thing you write is posted as its own message while","you work, and the tools you call show as a card between them, so someone is watching the","whole turn. Say what you are doing as you go, and keep it to a line or two. Your last message","is the answer to what was asked. Slack renders standard markdown, tables included, so write","normally and keep it short.","People and other agents are named to you as @name. Write @name to mention one and Bishop","makes it a real Slack mention; a name it cannot match to one account stays plain text, so","use the name you were given. @here, @channel, and @everyone are never mentions, whoever","writes them.","A thread can hold a conversation between other people that has nothing to do with you.",'Bishop marks a message you may leave alone with a <thread> tag carrying reply="optional":',"other people are talking here and this one did not name you, so answer it only if you have","something worth adding. To stay out of it, say <no-output> and nothing else, and Bishop","posts nothing at all. Every message without that tag is yours to answer."].join(`
|
|
2
|
+
`)},ee=3e3;async function ae(t,i,s=ee){const l=await te(t.botUserId,i,s),r=S(l)??t.username;if(!r)return;const m=[t.appName,l.realName].filter(h=>!!h?.trim()&&h?.toLowerCase()!==r.toLowerCase());return{address:`@${r}`,...m.length>0?{aka:[...new Set(m)]}:{}}}async function te(t,i,s){const l={userId:t};let r;const m=new Promise(h=>{r=setTimeout(()=>h(l),s)});try{return await Promise.race([i.lookup(t).catch(()=>l),m])}finally{clearTimeout(r)}}async function be(t){const{app:i,identity:s}=await O(t.credentials),l=await E(i.client,t.allow),r=Q(i.client,{self:s.botUserId}),m=H(t.maxBotTurns),h=L(),g=X(t.meta,t.verbosity),x={verbosity:g,model:t.model,configured:t.configuredModel??{}},c=await ae(s,r),y={...Z,...c?{self:c}:{}};c?p.info({address:c.address,aka:c.aka},"the agent's name in Slack"):p.warn("could not read the bot's own name; the agent will not know what it is called");const k=new Map,M=async({key:e,keyString:a,thread:o,text:w},d)=>{const n=K(w);if(!n)return!1;const f=F(n,{thread:o},x);try{await N(d.client,{channel:e.channelId,threadTs:e.threadTs},f),k.get(a)?.interrupted()}catch(u){p.warn({thread:a,err:u},"couldn't answer a command")}return!0};async function B(e){const a=await r.lookup(e.userId),o=S(a);return{id:e.userId,...o?{displayName:o}:{},...a.realName?{realName:a.realName}:{},...a.email?{email:a.email}:{},...e.isBot?{isBot:!0}:{}}}function P(e,a){const o=W(a);if(!o.unavailable)return t.attachments.remember(e,y.name,{id:o.id,name:o.name,payload:q(a)})}async function A(e,a){return a.files.length===0?[]:Promise.all(a.files.map(async o=>{const w=P(e.id,o),d=await t.files.save(e.dirKey,v(o,t.credentials.botToken));return w?{...d,ref:w}:d}))}function D(e,a){const o=T(e.messages[0]?.text??"",r),w=V(e),d=C({participants:e.participants,mentioned:e.messages.some(n=>n.mentionsAgent)});return{thread:e.thread,isNewThread:e.isNewThread,origin:y,...d?{note:d}:{},...w?{history:()=>Y({client:a.client,key:e.key,before:w,allows:n=>l.allows(n),answersBots:m.answersBots,self:{botUserId:s.botUserId,...s.botId?{botId:s.botId}:{},...c?{botName:c.address}:{}},users:r,remember:n=>t.attachments.remember(e.thread.id,y.name,n),saw:(n,f)=>h.saw(e.keyString,n,f)})}:{},async messages(){return Promise.all(e.messages.map(async(n,f)=>{const u=await A(e.thread,n);return{principal:await B(n),text:f===0?await o:await T(n.text,r),...u.length>0?{files:u}:{}}}))},presence:()=>{const n=$(e,a,{outgoing:f=>I(f,r),title:()=>o,verbosity:()=>g.of(e.thread.id).level,...d?{mayDecline:!0}:{},...t.taskIntervalMs===void 0?{}:{taskIntervalMs:t.taskIntervalMs}});return k.set(e.keyString,n),{...n,finish:async()=>{try{await n.finish()}finally{k.delete(e.keyString)}}}},async notify(n){await N(a.client,{channel:e.channelId,threadTs:e.threadTs},n)},async destination(){const n=R({channel:e.channelId,threadTs:e.threadTs});return{interface:"slack",handle:n,label:await U(i.client,n)}}}}return G(i,{identity:s,...c?{botName:c.address}:{},threads:t.threads,events:t.events,run:(e,a)=>t.run(D(e,a)),stop:t.stop,allows:e=>l.allows(e),bots:m,participants:h,command:M}),{name:"slack",async start(){await i.start(),s.scopes.includes("files:read")||p.warn("this Slack app has no files:read scope, so it cannot open files people share; reinstall it to grant one"),s.scopes.includes("files:write")||p.warn("this Slack app has no files:write scope, so it cannot send files back; reinstall it to grant one"),r.prime(),p.info("listening on Slack via Socket Mode")},async stop(){l.stop(),await i.stop()},allows:e=>l.allows(e),resolveDestination:(e,a)=>z(i.client,e,{...a?.optedInOnly?{allows:o=>l.allows(o)}:{}}),sameConversation:(e,a)=>b(e).channel===b(a).channel,deliver:async e=>{const a=await _(i.client,{...e,text:await I(e.text,r)});return a?{threadKey:J({teamId:s.teamId,channelId:a.channel,threadTs:a.threadTs})}:{}},attachment:e=>{const a=j(e);return a?v(a,t.credentials.botToken):void 0}}}export{Z as SLACK_ORIGIN,be as createSlackInterface,ae as slackIdentity};
|
package/dist/slack/manifest.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{displayNameOf as
|
|
1
|
+
import{displayNameOf as a}from"../config.js";const i=["app_mentions:read","chat:write","assistant:write","im:history","channels:history","groups:history","files:read","files:write","reactions:write","users:read","users:read.email","usergroups:read","channels:read","groups:read","im:write"],n=["app_mention","agent_session_stopped","app_home_opened","app_context_changed","message.im","message.channels","message.groups"];function r(t){const e=a(t),s=t.description?.trim();return{display_information:{name:e.slice(0,35),...s?{description:s.slice(0,140)}:{}},features:{bot_user:{display_name:e.slice(0,80),always_online:!0},app_home:{messages_tab_enabled:!0,messages_tab_read_only_enabled:!1,home_tab_enabled:!1},agent_view:{agent_description:(s||e).slice(0,300)}},oauth_config:{scopes:{bot:[...i]}},settings:{event_subscriptions:{bot_events:[...n]},interactivity:{is_enabled:!1},socket_mode_enabled:!0,org_deploy_enabled:!1,token_rotation_enabled:!1}}}export{n as BOT_EVENTS,i as BOT_SCOPES,r as buildManifest};
|
package/dist/slack/presence.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import{DEFAULT_VERBOSITY as
|
|
1
|
+
import{DEFAULT_VERBOSITY as H}from"../config.js";import{saysNoOutput as b}from"../harness/principal.js";import{summaryPhrase as N,toolPhrase as U,turnTally as V}from"../interface/phrases.js";import{showsProgress as Y,showsTasks as B}from"../interface/verbosity.js";import{log as y}from"../log.js";import{fileShelf as q}from"../outgoing.js";import{renameSession as z,setSessionStatus as O}from"./agent-session.js";import{splitForSlack as G}from"./format.js";import{say as j}from"./say.js";import{taskMessage as J}from"./tasks.js";import{attachToSlack as K}from"./upload.js";function Q(r){const l=[];for(const a of r){if(typeof a=="string"){l.push({text:a.trim(),files:[]});continue}const n=l.at(-1);n?n.files.push(a):l.push({text:"",files:[a]})}return l}const W=3e4;function lt(r,l,a={}){const n=l.client,u={channel:r.channelId,threadTs:r.threadTs},L=Date.now(),R=()=>a.verbosity?.()??H;let c,x,$="",i=!1,p="",A=!1,d,D=!1;const w=q();async function g(t,e){try{return await e()}catch(o){y.debug({thread:r.keyString,what:t,err:o},"presence call failed");return}}let S=C();function k(t){return S=S.then(()=>O(n,r,t)),S}async function C(){const t=r.messages[0];if(await O(n,r,"processing",t?.userId),!(!a.title||t?.ts!==r.threadTs))try{await z(n,r,await a.title())}catch(e){y.debug({thread:r.keyString,err:e},"could not read the session title")}}async function T(){const t=c;t&&(c=void 0,await g("task.close",()=>t.close()))}async function _(t){const e=await g("say",()=>j(n,u,t));return e?(x=e,i=!0,!0):(i=!1,!1)}async function E(t,e){const o=t?G(t):[],s=o.pop()??"";let f=!0;for(const h of o)await _(h)||(f=!1);const m=e.map(h=>h.name).join(", ");let I,F=!1;try{I=await K(n,u,e,s)}catch(h){F=!0,y.error({thread:r.keyString,files:m,err:h},"could not upload a file")}return I?(x=I,i=!!s,f):(s&&!await _(s)&&(f=!1),F&&await g("say",()=>j(n,u,`_Could not upload ${m}._`)),i=!1,f)}async function M(t){await T();const e=w.place(t);let o=!0;for(const s of Q(e.parts))(s.files.length?await E(s.text,s.files):await _(s.text))||(o=!1);o&&($=e.text)}const P=setInterval(()=>{k("processing")},a.heartbeatMs??W);return P.unref(),{interrupted(){i=!1},share:t=>w.offer(t),async handle(t){const e=R();switch(B(e)||await T(),t.type){case"text":{if(b(t.text)||!t.text.trim()||!Y(e))return;await M(a.outgoing?await a.outgoing(t.text):t.text);return}case"tool":if(!B(e))return;c||(c=J(n,u,a.taskIntervalMs),i=!1),c.note(U(t));return;case"tool_summary":if(!B(e))return;c?.retitle(N(t.summary));return;case"done":A=!0,p=b(t.text)?"":t.text,D=!!a.mayDecline&&b(t.text);return;case"error":d=t.message;return;default:return}},async finish(){if(clearInterval(P),await T(),D&&!d){y.info({thread:r.keyString},"the agent left this message alone"),await k("active");return}const t=V(Date.now()-L,A||!!d),e=p.trim()&&a.outgoing?await a.outgoing(p):p,o=d?`Something went wrong: ${d}`:w.strip(e)===$.trim()?"":e.trim(),s=x;!o&&s&&i&&await g("coda",()=>n.chat.update({channel:u.channel,ts:s.ts,markdown_text:`${s.text}
|
|
2
2
|
|
|
3
|
-
_${t}_`}))||await
|
|
3
|
+
_${t}_`}))||await M([o,`_${t}_`].filter(Boolean).join(`
|
|
4
4
|
|
|
5
|
-
`)),await
|
|
5
|
+
`));const m=w.unplaced();m.length>0&&await E("",m),await k("active")}}}export{lt as createPresence};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{readFile as d}from"node:fs/promises";import{log as r}from"../log.js";const f=5,u=250;async function h(t,a){for(let e=1;e<=f;e+=1){const s=await t.files.info({file:a}),i={...s.file?.shares?.public,...s.file?.shares?.private},l=Object.values(i).flat()[0]?.ts;if(l)return l;e<f&&await new Promise(o=>setTimeout(o,u))}}async function _(t,a,e,s){const i=await Promise.all(e.map(async n=>({file:await d(n.path),filename:n.name}))),l=await(a.threadTs?t.files.uploadV2({file_uploads:i,channel_id:a.channel,thread_ts:a.threadTs}):t.files.uploadV2({file_uploads:i,channel_id:a.channel}));r.info({channel:a.channel,files:e.map(n=>n.name)},"uploaded files the agent sent");const o=l.files?.[0]?.files?.[0]?.id,c=o?await h(t,o):void 0;if(!c){r.warn({channel:a.channel,files:e.map(n=>n.name)},"slack did not say which message the files landed on");return}if(!s)return{ts:c,text:""};try{await t.chat.update({channel:a.channel,ts:c,markdown_text:s})}catch(n){r.warn({channel:a.channel,err:n},"could not put the words on the message carrying the files");return}return{ts:c,text:s}}export{_ as attachToSlack};
|
package/dist/teams/interface.js
CHANGED
|
@@ -1,2 +1,4 @@
|
|
|
1
|
-
import{DEFAULT_VERBOSITY as
|
|
2
|
-
`)}function
|
|
1
|
+
import{DEFAULT_VERBOSITY as U}from"../config.js";import{BishopError as Y}from"../errors.js";import{etiquetteNote as $,threadParticipants as z}from"../interface/etiquette.js";import{splitMessage as G}from"../interface/format.js";import{showsProgress as R,showsTasks as H}from"../interface/verbosity.js";import{log as s}from"../log.js";import{describeTeamsFile as L,parseTeamsFile as W,teamsAttachment as N,teamsFilePayload as q}from"./files.js";import{teamsPresence as Q}from"./presence.js";import{addressesAgent as m,routeActivity as V}from"./route.js";import{baseConversation as X,conversationOf as O,sameConversation as J,threadKeyOf as Z}from"./thread.js";function tt(e){return[...nt(e),...et].join(`
|
|
2
|
+
`)}function nt(e){return R(e)?["This conversation is in Microsoft Teams. Each thing you write is posted as its own message","while you work, so somebody is watching the whole turn. Say what you are doing as you go,","and keep it to a line or two. Your last message is the answer to what was asked.","",H(e)?"Your tool calls show as one message that is rewritten as you work, and replaced by a count of them once you speak again.":"Your tool calls are not shown, so a stretch of them is a silence unless you say what you are doing.","","Nothing more can reach this conversation once the turn ends, so never say you will follow up","separately: finish what you were asked before you stop.",""]:["This conversation is in Microsoft Teams. Only the last thing you write reaches it, and","anything you say on the way there is dropped, so put the whole answer in the message you","finish with. Never say you will follow up separately: nothing more can reach this","conversation until someone writes to you again.","","Nobody is watching while you work. None of your tool calls are visible and nothing you say","arrives until the turn is over, so a turn that takes minutes costs nothing, and there is no","typing indicator to say you are still going.",""]}const et=["Teams renders a narrower markdown than Slack does. Bold, italics, links, inline code, fenced","code blocks and lists work. Tables do not, so write a list or plain prose where you would","otherwise reach for one.","","A file somebody attaches reaches you in a direct chat and in a channel, and so does an image","pasted into a message anywhere. One Bishop could not download arrives saying why instead of","going missing, so you can tell somebody what happened to it. A group chat is the one place a","file may not reach you at all: if somebody there asks about one you were given nothing for,","say it never arrived and ask them to send it in a direct chat.","","A conversation can hold people talking to each other and not to you. Bishop marks a message",'you may leave alone with a <thread> tag carrying reply="optional": other people are talking',"here and this one did not @mention you, so answer it only if you have something worth adding.","To stay out of it, say <no-output> and nothing else, and Bishop posts nothing at all. Every","message without that tag is yours to answer."],ot="You are not on this agent\u2019s allow list, so it can\u2019t answer you.",A="There was no text in that message for me to act on. Say what you need in words, and @mention me so I see it.",at=A+" If you attached a file, nothing about it reached me, so send it in a direct chat.",it=2e4;function pt(e){const h=new Set,w=new Map,p=z(),g=e.verbosity??U,S=tt(g);let u,v=!1;function I(){return{name:"teams",guidance:S,...u?{self:u}:{}}}async function _(t,n){const o=n.from.aadObjectId;if(!o)return;const i=w.get(o);if(i)return i;const a=X(t);try{const r=await e.api.member(a,n.from.id);return r&&w.set(o,r),r}catch(r){s.warn({err:r,conversation:a},"could not read the Teams roster");return}}async function d(t,n){let o;for(const i of G(n,it)){const a=await e.api.send(t,i);o=a?{id:a.id,text:i}:void 0}return o}function T(t,n){return[t,n?.userPrincipalName,n?.email].filter(o=>!!o).map(o=>o.trim().toLowerCase())}function x(t,n){const o=q(n);if(!o)return;const i=L(n);return e.attachments.remember(t,"teams",{id:i.id,name:i.name,payload:o})}async function B(t,n){return n.files.length===0?[]:Promise.all(n.files.map(async o=>{const i=x(t.id,o),a=await e.files.save(t.dirKey,N(o,e.api));return i?{...a,ref:i}:a}))}function F(t,n){return{id:t.from.aadObjectId??t.from.id,...t.from.name?{displayName:t.from.name}:{},...n?.name?{realName:n.name}:{},...n?.email?{email:n.email}:{}}}function j(t,n,o,i){const a=O(n),r=$({participants:p.count(t.threadKey),mentioned:m(n)});return{thread:t,isNewThread:i,origin:I(),...r?{note:r}:{},async messages(){const c=await B(t,n);return[{principal:o,text:n.text,...c.length>0?{files:c}:{}}]},presence:()=>Q({send:c=>d(a,c),post:c=>e.api.send(a,c),update:(c,y)=>e.api.update(a,c,y),threadKey:t.threadKey,verbosity:()=>g,...r?{mayDecline:!0}:{}}),async notify(c){await d(a,c)},async destination(){return{interface:"teams",handle:a,label:rt(n)}}}}async function K(t){if(v)return;if(await e.events.seen(`teams:${t.id}`)){s.debug({activity:t.id},"already answered this Teams activity");return}!u&&t.recipientName&&(u={address:`@${t.recipientName}`},s.info({address:u.address},"the agent is known by this name in Teams"));const n=Z(t),o=await e.threads.has(n),i=V(t,{threadKnown:o});if(i.action==="ignore"){s.debug({activity:t.id,thread:n,reason:i.reason},"leaving a Teams message alone");return}const a=t.from.aadObjectId;if(!a){s.warn({activity:t.id},"refusing a Teams message with no Entra object ID");return}p.saw(n,a,Date.now());const r=O(t),c=e.allow.open?void 0:await _(r,t);if(!e.allow.open&&!T(a,c).some(l=>e.allow.allows(l))){s.warn({activity:t.id,who:a,conversation:r},"refusing an unauthorized Teams message"),m(t)&&await d(r,ot).catch(l=>{s.debug({err:l},"could not tell somebody they are not on the allow list")});return}const y=new Set(t.files.map(l=>l.contentUrl)),k=(await e.api.sharedFiles(t)).filter(l=>!y.has(l.contentUrl)),f=k.length>0?{...t,files:[...t.files,...k]}:t;if(!f.text.trim()&&f.files.length===0){if(s.info({activity:t.id},"a Teams message had nothing to act on"),m(t)){const l=t.conversationType==="personal"?A:at;await d(r,l).catch(C=>{s.debug({err:C},"could not answer a message with nothing in it")})}return}const P=await e.threads.remember(n),D=F(f,c);s.info({thread:n,who:a,newThread:!o},"running a turn from Teams");const E=e.run(j(P,f,D,!o)).catch(l=>{s.error({thread:n,err:l},"a turn from Teams failed")}).finally(()=>{h.delete(E)});h.add(E)}async function b(){for(;h.size>0;)await Promise.allSettled([...h])}async function M(t){const n=(t.files??[]).map(o=>o.name).join(", ");return n&&s.warn({handle:t.handle,files:n},"a report carried files, which Teams cannot send"),await d(t.handle,n?`${t.text}
|
|
3
|
+
|
|
4
|
+
_Could not attach ${n}: Bishop cannot send files in Teams._`:t.text),{}}return{name:"teams",handle:K,settle:b,deliver:M,allows(t){return e.allow.open?!0:T(t,w.get(t)).some(n=>e.allow.allows(n))},sameConversation:J,async resolveDestination(){throw new Y("Bishop cannot yet name a Teams channel or chat from what you typed. Ask for a report in the conversation you want it in, which names itself.")},attachment:t=>{const n=W(t);return n?N(n,e.api):void 0},async start(){await e.listener.start(),s.info({allow:e.allow.open?"anyone in the tenant":"a list"},"listening for Teams activities")},async stop(){v=!0,await e.listener.stop(),h.size>0&&(s.info({turns:h.size},"waiting for Teams turns still running"),await b())}}}function rt(e){return e.conversationType==="personal"?`a chat with ${e.from.name??"someone"}`:e.conversationType==="groupChat"?"a Teams group chat":"a Teams channel thread"}export{pt as createTeamsInterface,tt as teamsGuidance};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const f={model:"agent.model",effort:"agent.effort"},c=/^[\w./:@-]+$/;function d(o){return c.test(o)}const s=["model","effort"];function i(o){return{of(t){const e={};for(const n of s){const r=o.get(t,f[n]);r&&(e[n]=r)}return e},set(t,e){for(const n of s){const r=e[n];r!==void 0&&o.set(t,f[n],r)}},clear(t,e){o.clear(t,f[e])}}}export{s as MODEL_SETTINGS,d as isHarnessName,i as threadModel};
|
package/dist/tools/files.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{z as
|
|
2
|
-
`),input:{ref:
|
|
1
|
+
import{z as i}from"zod";import{FETCH_FILE_TOOL as s,fileTag as f}from"../harness/principal.js";import{SHARE_FILE_DESCRIPTION as l,SHARE_FILE_TOOL as c,ShareRefused as m}from"../outgoing.js";import{defineTool as a,ToolRefusal as r}from"./types.js";function y(t){return[d(t),w()]}function d(t){return a({name:s,description:["Downloads a file somebody shared in this conversation and gives you the path to it.","","Takes the ref from an <attachment> tag. Ask for a file when you need to open it, rather","than for everything in the conversation. Asking for one already downloaded hands back","the same path without fetching it again, so ask rather than reusing a path from earlier","in the conversation. It fails, with the reason, when there is no file to be had."].join(`
|
|
2
|
+
`),input:{ref:i.string().trim().min(1).describe("The ref attribute of the file tag naming the file you want")},async run(n,o){const e=await t.fetch(o.thread,n.ref);if(!e)throw new r(`No file with the ref ${n.ref} was shared in this conversation. Refs come from the file tags Bishop writes, and one only works in the conversation it was written in.`);if(!e.path){const h=e.unavailable??"the download produced no file";throw new r(`Bishop could not fetch the file with ref ${n.ref}: ${h}.`+(e.url?` The original is at ${e.url}`:""))}return f({...e,ref:n.ref})}})}function w(){return a({name:c,description:l,input:{path:i.string().trim().min(1).describe("Absolute path to the file to send"),name:i.string().trim().min(1).optional().describe("What the recipient sees it called. Defaults to the filename on disk.")},async run(t,n){const o=n.share;if(!o)throw new r("There is nowhere to put a file in this conversation, so nothing was sent.");try{const e=await o({path:t.path,...t.name?{name:t.name}:{}});return`Registered ${e.name}. Write <attachment ref="${e.ref}" /> in your reply where the file should appear.`}catch(e){throw e instanceof m?new r(e.message):e}}})}export{y as fileTools,w as shareTool};
|
package/dist/tools/server.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{randomBytes as
|
|
1
|
+
import{randomBytes as T,randomUUID as b}from"node:crypto";import{createServer as k}from"node:http";import{McpServer as B}from"@modelcontextprotocol/sdk/server/mcp.js";import{StreamableHTTPServerTransport as O}from"@modelcontextprotocol/sdk/server/streamableHttp.js";import{isInitializeRequest as _}from"@modelcontextprotocol/sdk/types.js";import{log as h}from"../log.js";import{ToolRefusal as y}from"./types.js";const x="bishop",v="/mcp",R="BISHOP_TOOL_TOKEN",A=1048576;function N(i){const r=i.headers.authorization;return r&&/^Bearer\s+(.+)$/i.exec(r.trim())?.[1]?.trim()||void 0}function p(i,r,a){i.writeHead(r,{"content-type":"application/json"}),i.end(JSON.stringify({jsonrpc:"2.0",id:null,error:{code:-32001,message:a}}))}async function I(i){const r=[];let a=0;for await(const n of i){if(a+=n.length,a>A)throw new Error("request body is too large");r.push(n)}if(r.length!==0)return JSON.parse(Buffer.concat(r).toString("utf8"))}const P="Bishop's tools answer to a person, and this message came from another bot. Ask someone in the conversation to do it.";function z(i,r){const a=new B({name:x,version:"1"});for(const n of i)a.registerTool(n.name,{description:n.description,inputSchema:n.input},async w=>{try{if(r.principal.isBot)throw new y(P);return{content:[{type:"text",text:await n.run(w,r)}]}}catch(c){const f=c instanceof Error?c.message:String(c);return c instanceof y?h.info({tool:n.name,thread:r.thread.threadKey,reason:f},"a tool refused"):h.error({tool:n.name,thread:r.thread.threadKey,err:c},"a tool failed"),{content:[{type:"text",text:f}],isError:!0}}});return a}async function D(i){const r=i.host??"127.0.0.1",a=new Map,n=new Map;async function w(e,t){const o=new O({sessionIdGenerator:()=>b(),onsessioninitialized:s=>{if(a.get(e)!==t){o.close().catch(()=>{});return}n.set(s,{token:e,transport:o}),t.sessions.add(s)}});return o.onclose=()=>{const s=o.sessionId;s&&(n.delete(s),t.sessions.delete(s))},await z(t.tools,t.context).connect(o),o}const c=k((e,t)=>{(async()=>{try{if(!e.url?.startsWith(v))return p(t,404,"no such endpoint");const o=N(e),s=o?a.get(o):void 0;if(!s||!o)return p(t,401,"this call carries no valid turn credential");const l=e.method==="POST"?await I(e):void 0,d=e.headers["mcp-session-id"],m=typeof d=="string"?n.get(d):void 0;if(m){if(m.token!==o)return p(t,404,"no such session");await m.transport.handleRequest(e,t,l);return}if(typeof d=="string")return p(t,404,"no such session");if(!_(l))return p(t,400,"the first call on a connection must be initialize");await(await w(o,s)).handleRequest(e,t,l)}catch(o){h.error({err:o},"a tool call could not be handled"),t.headersSent?t.end():p(t,400,"that request could not be handled")}})()});await new Promise((e,t)=>{c.once("error",t),c.listen(0,r,()=>{c.removeListener("error",t),e()})});const f=c.address();if(!f||typeof f=="string")throw new Error("the tool endpoint started without a port");const g=`http://${r}:${f.port}${v}`;return h.info({url:g,tools:i.tools.map(e=>e.name)},"serving the agent its own tools"),{url:g,toolNames:i.tools.map(e=>e.name),grant(e,t){const o=T(32).toString("base64url"),s=t?.only,l=s?i.tools.filter(u=>s.includes(u.name)):i.tools;s&&l.length!==s.length&&h.error({asked:[...s],serving:l.map(u=>u.name)},"a turn asked for tools this endpoint does not serve");const d={context:e,tools:l,sessions:new Set};return a.set(o,d),{access:{url:g,tokenEnvVar:R,token:o},revoke:()=>{if(a.delete(o)){for(const u of d.sessions){const S=n.get(u);n.delete(u),S?.transport.close().catch(E=>{h.debug({err:E},"a tool connection did not close cleanly")})}d.sessions.clear()}}}},async stop(){for(const{transport:e}of n.values())await e.close().catch(()=>{});n.clear(),a.clear(),c.closeAllConnections(),await new Promise(e=>c.close(()=>e()))}}}export{v as PATH,x as SERVER_NAME,R as TOKEN_ENV_VAR,D as startToolServer};
|
package/dist/turn.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{log as
|
|
1
|
+
import{log as d}from"./log.js";import{worktreeName as u}from"./worktrees.js";function A(n){const h=new Map,y=new Set;async function w(e){const a=n.sharedDirNotice;if(!(!a||y.has(e.thread.threadKey))){y.add(e.thread.threadKey);try{await e.notify(a)}catch(o){d.debug({thread:e.thread.threadKey,err:o},"could not deliver the shared-directory notice")}}}function K(e,a,o){if(!n.tools||e.tools===!1)return;const i=Array.isArray(e.tools)?e.tools:void 0;if(i&&i.length===0)return;const r=o.share?.bind(o);return n.tools.grant({thread:e.thread,principal:a,origin:e.origin,destination:()=>e.destination(),...r?{share:r}:{}},...i?[{only:i}]:[])}async function p(e){await w(e);const a=new AbortController,o=e.presence();let i;try{h.set(e.thread.threadKey,a);const r=e.freshSession?void 0:await n.sessions.get(e.thread.id),c=await n.cwd(e.thread),[b,g]=await Promise.all([e.messages(),r?void 0:e.history?.().catch(t=>{d.warn({thread:e.thread.threadKey,err:t},"could not read the history")})]),[f,...m]=b;if(!f)throw new Error("a turn with no messages");const k=m.at(-1)?.principal??f.principal;if(a.signal.aborted){d.info({thread:e.thread.threadKey},"turn stopped before the agent started");return}i=K(e,k,o);const l=n.model?.of(e.thread.id)??{};let s;for await(const t of n.harness.runTurn({messages:[f,...m],cwd:c,threadKey:e.thread.threadKey,worktree:u(e.thread.dirKey),...r?{sessionId:r}:{},...l.model?{model:l.model}:{},...l.effort?{effort:l.effort}:{},...e.note?{note:e.note}:{},...g?.length?{history:g}:{},origin:e.origin,...i?{tools:i.access}:{},signal:a.signal}))t.type==="session"&&(t.model&&(s=t.model),t.sessionId!==r&&await n.sessions.set(e.thread.id,t.sessionId)),t.type==="text"&&d.debug({thread:e.thread.threadKey,chars:t.text.length},"agent said something"),t.type==="tool"&&d.debug({thread:e.thread.threadKey,tool:t.name,detail:t.summary??t.detail},"tool activity"),t.type==="done"&&d.info({thread:e.thread.threadKey,...s?{model:s}:{},...t.usage},"turn complete"),t.type==="error"&&d.error({thread:e.thread.threadKey,...s?{model:s}:{},kind:t.kind,err:t.message},"turn errored"),await o.handle(t)}catch(r){const c=r instanceof Error?r.message:String(r);throw d.error({thread:e.thread.threadKey,err:r},"turn failed before the agent answered"),await o.handle({type:"error",kind:"turn_failed",message:c}),r}finally{i?.revoke(),h.get(e.thread.threadKey)===a&&h.delete(e.thread.threadKey),await o.finish()}}return{run:p,stop(e){const a=h.get(e);return a?(a.abort(),!0):!1}}}export{A as makeTurnRunner};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentdeploymentco/bishop",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "An agent gateway. Connects an agent to Slack and email.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"type": "module",
|
|
@@ -22,6 +22,11 @@
|
|
|
22
22
|
"build:release": "rm -rf dist && tsc -p tsconfig.release.json && node scripts/minify.mjs && chmod +x dist/cli.js",
|
|
23
23
|
"prepublishOnly": "npm run build:release && node scripts/check-package.mjs",
|
|
24
24
|
"dev": "tsx src/cli.ts",
|
|
25
|
+
"dev:slack": "./scripts/dev.sh start",
|
|
26
|
+
"dev:slack:stop": "./scripts/dev.sh stop",
|
|
27
|
+
"dev:slack:restart": "./scripts/dev.sh restart",
|
|
28
|
+
"dev:slack:status": "./scripts/dev.sh status",
|
|
29
|
+
"dev:slack:logs": "./scripts/dev.sh logs",
|
|
25
30
|
"typecheck": "tsc --noEmit",
|
|
26
31
|
"test": "vitest run",
|
|
27
32
|
"test:watch": "vitest",
|