@openape/apes 1.24.0 → 1.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. package/dist/{auth-lock-BBSP72GH.js → auth-lock-4IRWI3ED.js} +1 -2
  2. package/dist/{auth-lock-BBSP72GH.js.map → auth-lock-4IRWI3ED.js.map} +1 -1
  3. package/dist/{chunk-QJJ7DG5C.js → chunk-4KPKANZT.js} +5 -3
  4. package/dist/{chunk-QJJ7DG5C.js.map → chunk-4KPKANZT.js.map} +1 -1
  5. package/dist/{chunk-FRCNYDTR.js → chunk-L2V3CW5B.js} +3 -1
  6. package/dist/{chunk-FRCNYDTR.js.map → chunk-L2V3CW5B.js.map} +1 -1
  7. package/dist/cli.js +1122 -1384
  8. package/dist/cli.js.map +1 -1
  9. package/dist/{config-DA2L3XOV.js → config-MOB5DJ6H.js} +1 -2
  10. package/dist/{http-JWS5LV2U.js → http-6OKWT52Z.js} +2 -3
  11. package/dist/index.js +2 -3
  12. package/dist/{orchestrator-2QS5KXFL.js → orchestrator-CIDV7OGM.js} +2 -3
  13. package/dist/{orchestrator-2QS5KXFL.js.map → orchestrator-CIDV7OGM.js.map} +1 -1
  14. package/dist/{server-K77SHBP2.js → server-V2V5YHK3.js} +3 -4
  15. package/dist/{server-K77SHBP2.js.map → server-V2V5YHK3.js.map} +1 -1
  16. package/dist/{ssh-key-6X3YZXSD.js → ssh-key-YBNNG5K5.js} +1 -2
  17. package/package.json +6 -5
  18. package/dist/chunk-7OCVIDC7.js +0 -12
  19. package/dist/chunk-WGF3SPIH.js +0 -3598
  20. package/dist/chunk-WGF3SPIH.js.map +0 -1
  21. package/dist/multipart-parser-FVZBRBXW.js +0 -182
  22. package/dist/multipart-parser-FVZBRBXW.js.map +0 -1
  23. package/dist/ssh-key-6X3YZXSD.js.map +0 -1
  24. /package/dist/{chunk-7OCVIDC7.js.map → config-MOB5DJ6H.js.map} +0 -0
  25. /package/dist/{config-DA2L3XOV.js.map → http-6OKWT52Z.js.map} +0 -0
  26. /package/dist/{http-JWS5LV2U.js.map → ssh-key-YBNNG5K5.js.map} +0 -0
@@ -2,7 +2,6 @@
2
2
  import {
3
3
  CONFIG_DIR
4
4
  } from "./chunk-OBF7IMQ2.js";
5
- import "./chunk-7OCVIDC7.js";
6
5
 
7
6
  // src/auth-lock.ts
8
7
  import { open, rm, stat } from "fs/promises";
@@ -39,4 +38,4 @@ export {
39
38
  acquireAuthLock,
40
39
  releaseAuthLock
41
40
  };
42
- //# sourceMappingURL=auth-lock-BBSP72GH.js.map
41
+ //# sourceMappingURL=auth-lock-4IRWI3ED.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/auth-lock.ts"],"sourcesContent":["import type { FileHandle } from 'node:fs/promises'\nimport { open, rm, stat } from 'node:fs/promises'\nimport { join } from 'node:path'\nimport { CONFIG_DIR } from './config'\n\nconst LOCK_FILE = join(CONFIG_DIR, 'auth.json.lock')\n\nexport interface AuthLock {\n handle: FileHandle\n}\n\n/**\n * Best-effort exclusive file lock to serialize concurrent token refreshes\n * between parallel apes / ape-shell invocations. Uses O_CREAT|O_EXCL which\n * is atomic on POSIX. Returns null on timeout so the caller can fall back\n * to \"just re-read auth.json\" (the assumption being that another process\n * successfully refreshed in the meantime).\n *\n * A stale lock older than 30s is considered abandoned (from a crashed\n * process) and is removed so the next acquire can proceed.\n */\nexport async function acquireAuthLock(\n opts: { timeoutMs?: number } = {},\n): Promise<AuthLock | null> {\n const deadline = Date.now() + (opts.timeoutMs ?? 5000)\n while (Date.now() < deadline) {\n try {\n const handle = await open(LOCK_FILE, 'wx')\n return { handle }\n }\n catch (err) {\n if ((err as NodeJS.ErrnoException).code !== 'EEXIST')\n throw err\n // Stale lock? If the file is older than 30s, remove it and retry.\n try {\n const s = await stat(LOCK_FILE)\n if (Date.now() - s.mtimeMs > 30_000)\n await rm(LOCK_FILE, { force: true })\n }\n catch {\n // File gone between stat and rm — next iteration will retry the open.\n }\n await new Promise(r => setTimeout(r, 100))\n }\n }\n return null\n}\n\nexport async function releaseAuthLock(lock: AuthLock): Promise<void> {\n try {\n await lock.handle.close()\n }\n finally {\n await rm(LOCK_FILE, { force: true })\n }\n}\n"],"mappings":";;;;;;;AACA,SAAS,MAAM,IAAI,YAAY;AAC/B,SAAS,YAAY;AAGrB,IAAM,YAAY,KAAK,YAAY,gBAAgB;AAgBnD,eAAsB,gBACpB,OAA+B,CAAC,GACN;AAC1B,QAAM,WAAW,KAAK,IAAI,KAAK,KAAK,aAAa;AACjD,SAAO,KAAK,IAAI,IAAI,UAAU;AAC5B,QAAI;AACF,YAAM,SAAS,MAAM,KAAK,WAAW,IAAI;AACzC,aAAO,EAAE,OAAO;AAAA,IAClB,SACO,KAAK;AACV,UAAK,IAA8B,SAAS;AAC1C,cAAM;AAER,UAAI;AACF,cAAM,IAAI,MAAM,KAAK,SAAS;AAC9B,YAAI,KAAK,IAAI,IAAI,EAAE,UAAU;AAC3B,gBAAM,GAAG,WAAW,EAAE,OAAO,KAAK,CAAC;AAAA,MACvC,QACM;AAAA,MAEN;AACA,YAAM,IAAI,QAAQ,OAAK,WAAW,GAAG,GAAG,CAAC;AAAA,IAC3C;AAAA,EACF;AACA,SAAO;AACT;AAEA,eAAsB,gBAAgB,MAA+B;AACnE,MAAI;AACF,UAAM,KAAK,OAAO,MAAM;AAAA,EAC1B,UACA;AACE,UAAM,GAAG,WAAW,EAAE,OAAO,KAAK,CAAC;AAAA,EACrC;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/auth-lock.ts"],"sourcesContent":["import type { FileHandle } from 'node:fs/promises'\nimport { open, rm, stat } from 'node:fs/promises'\nimport { join } from 'node:path'\nimport { CONFIG_DIR } from './config'\n\nconst LOCK_FILE = join(CONFIG_DIR, 'auth.json.lock')\n\nexport interface AuthLock {\n handle: FileHandle\n}\n\n/**\n * Best-effort exclusive file lock to serialize concurrent token refreshes\n * between parallel apes / ape-shell invocations. Uses O_CREAT|O_EXCL which\n * is atomic on POSIX. Returns null on timeout so the caller can fall back\n * to \"just re-read auth.json\" (the assumption being that another process\n * successfully refreshed in the meantime).\n *\n * A stale lock older than 30s is considered abandoned (from a crashed\n * process) and is removed so the next acquire can proceed.\n */\nexport async function acquireAuthLock(\n opts: { timeoutMs?: number } = {},\n): Promise<AuthLock | null> {\n const deadline = Date.now() + (opts.timeoutMs ?? 5000)\n while (Date.now() < deadline) {\n try {\n const handle = await open(LOCK_FILE, 'wx')\n return { handle }\n }\n catch (err) {\n if ((err as NodeJS.ErrnoException).code !== 'EEXIST')\n throw err\n // Stale lock? If the file is older than 30s, remove it and retry.\n try {\n const s = await stat(LOCK_FILE)\n if (Date.now() - s.mtimeMs > 30_000)\n await rm(LOCK_FILE, { force: true })\n }\n catch {\n // File gone between stat and rm — next iteration will retry the open.\n }\n await new Promise(r => setTimeout(r, 100))\n }\n }\n return null\n}\n\nexport async function releaseAuthLock(lock: AuthLock): Promise<void> {\n try {\n await lock.handle.close()\n }\n finally {\n await rm(LOCK_FILE, { force: true })\n }\n}\n"],"mappings":";;;;;;AACA,SAAS,MAAM,IAAI,YAAY;AAC/B,SAAS,YAAY;AAGrB,IAAM,YAAY,KAAK,YAAY,gBAAgB;AAgBnD,eAAsB,gBACpB,OAA+B,CAAC,GACN;AAC1B,QAAM,WAAW,KAAK,IAAI,KAAK,KAAK,aAAa;AACjD,SAAO,KAAK,IAAI,IAAI,UAAU;AAC5B,QAAI;AACF,YAAM,SAAS,MAAM,KAAK,WAAW,IAAI;AACzC,aAAO,EAAE,OAAO;AAAA,IAClB,SACO,KAAK;AACV,UAAK,IAA8B,SAAS;AAC1C,cAAM;AAER,UAAI;AACF,cAAM,IAAI,MAAM,KAAK,SAAS;AAC9B,YAAI,KAAK,IAAI,IAAI,EAAE,UAAU;AAC3B,gBAAM,GAAG,WAAW,EAAE,OAAO,KAAK,CAAC;AAAA,MACvC,QACM;AAAA,MAEN;AACA,YAAM,IAAI,QAAQ,OAAK,WAAW,GAAG,GAAG,CAAC;AAAA,IAC3C;AAAA,EACF;AACA,SAAO;AACT;AAEA,eAAsB,gBAAgB,MAA+B;AACnE,MAAI;AACF,UAAM,KAAK,OAAO,MAAM;AAAA,EAC1B,UACA;AACE,UAAM,GAAG,WAAW,EAAE,OAAO,KAAK,CAAC;AAAA,EACrC;AACF;","names":[]}
@@ -17,6 +17,8 @@ var ApiError = class extends Error {
17
17
  this.problemDetails = problemDetails;
18
18
  this.name = "ApiError";
19
19
  }
20
+ statusCode;
21
+ problemDetails;
20
22
  };
21
23
  var _discoveryCache = {};
22
24
  async function discoverEndpoints(idpUrl) {
@@ -63,7 +65,7 @@ async function refreshAgentToken() {
63
65
  const { readFileSync } = await import("fs");
64
66
  const { sign } = await import("crypto");
65
67
  const { homedir } = await import("os");
66
- const { loadEd25519PrivateKey } = await import("./ssh-key-6X3YZXSD.js");
68
+ const { loadEd25519PrivateKey } = await import("./ssh-key-YBNNG5K5.js");
67
69
  const resolved = keyPath.replace(/^~/, homedir());
68
70
  const keyContent = readFileSync(resolved, "utf-8");
69
71
  const privateKey = loadEd25519PrivateKey(keyContent);
@@ -104,7 +106,7 @@ async function refreshOAuthToken() {
104
106
  const auth = loadAuth();
105
107
  if (!auth?.refresh_token)
106
108
  return null;
107
- const { acquireAuthLock, releaseAuthLock } = await import("./auth-lock-BBSP72GH.js");
109
+ const { acquireAuthLock, releaseAuthLock } = await import("./auth-lock-4IRWI3ED.js");
108
110
  const lock = await acquireAuthLock({ timeoutMs: 5e3 });
109
111
  if (!lock) {
110
112
  return getAuthToken();
@@ -217,4 +219,4 @@ export {
217
219
  ensureFreshToken,
218
220
  apiFetch
219
221
  };
220
- //# sourceMappingURL=chunk-QJJ7DG5C.js.map
222
+ //# sourceMappingURL=chunk-4KPKANZT.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/http.ts"],"sourcesContent":["import consola from 'consola'\nimport { getAuthToken, getIdpUrl, loadAuth, loadConfig, saveAuth } from './config'\n\nconst debug = process.argv.includes('--debug')\n\nexport class ApiError extends Error {\n constructor(public statusCode: number, message: string, public problemDetails?: Record<string, unknown>) {\n super(message)\n this.name = 'ApiError'\n }\n}\n\n// OIDC Discovery cache (one-time per CLI invocation)\nconst _discoveryCache: Record<string, Record<string, unknown>> = {}\n\nexport async function discoverEndpoints(idpUrl: string): Promise<Record<string, unknown>> {\n if (_discoveryCache[idpUrl]) {\n return _discoveryCache[idpUrl]\n }\n\n try {\n const response = await fetch(`${idpUrl}/.well-known/openid-configuration`)\n if (response.ok) {\n const data = await response.json() as Record<string, unknown>\n _discoveryCache[idpUrl] = data\n return data\n }\n }\n catch {}\n\n // Return empty if discovery fails (graceful degradation)\n _discoveryCache[idpUrl] = {}\n return {}\n}\n\nexport async function getGrantsEndpoint(idpUrl: string): Promise<string> {\n const disco = await discoverEndpoints(idpUrl)\n return (disco.openape_grants_endpoint as string) || `${idpUrl}/api/grants`\n}\n\nexport async function getAgentChallengeEndpoint(idpUrl: string): Promise<string> {\n const disco = await discoverEndpoints(idpUrl)\n return (disco.ddisa_agent_challenge_endpoint as string) || `${idpUrl}/api/agent/challenge`\n}\n\nexport async function getAgentAuthenticateEndpoint(idpUrl: string): Promise<string> {\n const disco = await discoverEndpoints(idpUrl)\n return (disco.ddisa_agent_authenticate_endpoint as string) || `${idpUrl}/api/agent/authenticate`\n}\n\nexport async function getDelegationsEndpoint(idpUrl: string): Promise<string> {\n const disco = await discoverEndpoints(idpUrl)\n return (disco.openape_delegations_endpoint as string) || `${idpUrl}/api/delegations`\n}\n\n/**\n * Re-authenticate an agent using Ed25519 challenge-response.\n * Called automatically when the token is expired.\n */\nasync function refreshAgentToken(): Promise<string | null> {\n const auth = loadAuth()\n if (!auth)\n return null\n\n const config = loadConfig()\n const keyPath = config.agent?.key\n if (!keyPath)\n return null\n\n try {\n const { readFileSync } = await import('node:fs')\n const { sign } = await import('node:crypto')\n const { homedir } = await import('node:os')\n const { loadEd25519PrivateKey } = await import('./ssh-key.js')\n\n const resolved = keyPath.replace(/^~/, homedir())\n const keyContent = readFileSync(resolved, 'utf-8')\n const privateKey = loadEd25519PrivateKey(keyContent)\n\n const challengeUrl = await getAgentChallengeEndpoint(auth.idp)\n const challengeResp = await fetch(challengeUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ agent_id: auth.email }),\n })\n\n if (!challengeResp.ok)\n return null\n\n const { challenge } = await challengeResp.json() as { challenge: string }\n const { Buffer } = await import('node:buffer')\n const signature = sign(null, Buffer.from(challenge), privateKey).toString('base64')\n\n const authenticateUrl = await getAgentAuthenticateEndpoint(auth.idp)\n const authResp = await fetch(authenticateUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ agent_id: auth.email, challenge, signature }),\n })\n\n if (!authResp.ok)\n return null\n\n const { token, expires_in } = await authResp.json() as { token: string, expires_in: number }\n\n saveAuth({\n ...auth,\n access_token: token,\n expires_at: Math.floor(Date.now() / 1000) + (expires_in || 3600),\n })\n\n if (debug) {\n consola.debug('Token refreshed via Ed25519 challenge-response')\n }\n\n return token\n }\n catch {\n return null\n }\n}\n\n/**\n * Refresh an OAuth2 access token using a stored refresh_token. Used for\n * PKCE/browser login sessions where no Ed25519 agent key is configured.\n *\n * Serialized via a file lock so concurrent apes/ape-shell invocations don't\n * both consume the same rotating refresh token (which would revoke the\n * entire family server-side).\n */\nasync function refreshOAuthToken(): Promise<string | null> {\n const auth = loadAuth()\n if (!auth?.refresh_token)\n return null\n\n const { acquireAuthLock, releaseAuthLock } = await import('./auth-lock.js')\n const lock = await acquireAuthLock({ timeoutMs: 5000 })\n if (!lock) {\n // Another process is refreshing. It should have updated auth.json by now;\n // re-read and return whatever fresh token is there (may still be null).\n return getAuthToken()\n }\n\n try {\n // Re-read auth.json inside the lock — another holder may already have\n // refreshed while we were waiting, in which case we reuse the new token.\n const latest = loadAuth()\n if (latest?.expires_at && Date.now() / 1000 < latest.expires_at - 30)\n return latest.access_token\n\n const activeRefreshToken = latest?.refresh_token ?? auth.refresh_token\n if (!activeRefreshToken)\n return null\n\n const disco = await discoverEndpoints(auth.idp)\n const tokenEndpoint = (disco.token_endpoint as string) || `${auth.idp}/token`\n\n const body = new URLSearchParams({\n grant_type: 'refresh_token',\n refresh_token: activeRefreshToken,\n })\n\n const resp = await fetch(tokenEndpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: body.toString(),\n })\n\n if (!resp.ok) {\n // Family may have been revoked server-side — clear refresh_token to\n // prevent an infinite retry loop on every subsequent apes invocation.\n if (resp.status === 400 || resp.status === 401) {\n const base = latest ?? auth\n saveAuth({ ...base, refresh_token: undefined })\n }\n return null\n }\n\n const tokens = await resp.json() as {\n access_token: string\n refresh_token?: string\n expires_in?: number\n }\n\n const base = latest ?? auth\n saveAuth({\n ...base,\n access_token: tokens.access_token,\n refresh_token: tokens.refresh_token ?? base.refresh_token,\n expires_at: Math.floor(Date.now() / 1000) + (tokens.expires_in || 300),\n })\n\n if (debug)\n consola.debug('Token refreshed via OAuth refresh_token')\n\n return tokens.access_token\n }\n finally {\n await releaseAuthLock(lock)\n }\n}\n\n/**\n * Refresh the local IdP token if it has expired. Used by every code path\n * that needs an access token — `apiFetch` for IdP-bound HTTP calls, plus\n * any pure-introspection command (`apes whoami`, `apes config get …`)\n * that previously only read local auth state and surfaced a stale\n * \"expired\" without attempting renewal.\n *\n * Returns the fresh access token on success, or null when no refresh\n * path is available (no agent key AND no refresh_token, or both refresh\n * attempts failed). Callers decide what to do with null — `apiFetch`\n * throws \"Not authenticated\", `whoami` falls back to the on-disk state.\n */\nexport async function ensureFreshToken(): Promise<string | null> {\n const cached = getAuthToken()\n if (cached) return cached\n\n // Auto-refresh: priority (1) ed25519 agent key, (2) OAuth refresh_token.\n // Agent-key first because it is concurrency-safe — every challenge is\n // independent server-side, so parallel ape-shell spawns don't race.\n const agentToken = await refreshAgentToken()\n if (agentToken) return agentToken\n const oauthToken = await refreshOAuthToken()\n return oauthToken ?? null\n}\n\nexport async function apiFetch<T = unknown>(\n path: string,\n options: {\n method?: string\n body?: unknown\n idp?: string\n token?: string\n } = {},\n): Promise<T> {\n const token = options.token ?? await ensureFreshToken()\n\n if (!token) {\n throw new Error('Not authenticated (token expired). Run `apes login` first.')\n }\n\n let url: string\n if (path.startsWith('http')) {\n url = path\n }\n else {\n const idp = options.idp || getIdpUrl()\n if (!idp) {\n throw new Error('No IdP URL configured. Run `apes login` first or pass --idp.')\n }\n url = `${idp}${path}`\n }\n const method = options.method || 'GET'\n const headers: Record<string, string> = {\n 'Authorization': `Bearer ${token}`,\n 'Content-Type': 'application/json',\n }\n\n if (debug) {\n consola.debug(`${method} ${url}`)\n consola.debug(`Token: ${token.substring(0, 20)}...${token.substring(token.length - 10)}`)\n }\n\n const response = await fetch(url, {\n method,\n headers,\n body: options.body ? JSON.stringify(options.body) : undefined,\n })\n\n if (debug) {\n consola.debug(`Response: ${response.status} ${response.statusText}`)\n }\n\n if (!response.ok) {\n const contentType = response.headers.get('content-type') || ''\n\n // Parse RFC 7807 Problem Details\n if (contentType.includes('application/problem+json') || contentType.includes('application/json')) {\n try {\n const problem = await response.json() as Record<string, unknown>\n const message = (problem.detail as string) || (problem.title as string) || (problem.statusMessage as string) || (problem.message as string) || `${response.status} ${response.statusText}`\n throw new ApiError(response.status, message, problem)\n }\n catch (e) {\n if (e instanceof ApiError)\n throw e\n }\n }\n\n const text = await response.text()\n throw new ApiError(response.status, text || `${response.status} ${response.statusText}`)\n }\n\n return response.json() as Promise<T>\n}\n"],"mappings":"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","names":["base"]}
1
+ {"version":3,"sources":["../src/http.ts"],"sourcesContent":["import consola from 'consola'\nimport { getAuthToken, getIdpUrl, loadAuth, loadConfig, saveAuth } from './config'\n\nconst debug = process.argv.includes('--debug')\n\nexport class ApiError extends Error {\n constructor(public statusCode: number, message: string, public problemDetails?: Record<string, unknown>) {\n super(message)\n this.name = 'ApiError'\n }\n}\n\n// OIDC Discovery cache (one-time per CLI invocation)\nconst _discoveryCache: Record<string, Record<string, unknown>> = {}\n\nexport async function discoverEndpoints(idpUrl: string): Promise<Record<string, unknown>> {\n if (_discoveryCache[idpUrl]) {\n return _discoveryCache[idpUrl]\n }\n\n try {\n const response = await fetch(`${idpUrl}/.well-known/openid-configuration`)\n if (response.ok) {\n const data = await response.json() as Record<string, unknown>\n _discoveryCache[idpUrl] = data\n return data\n }\n }\n catch {}\n\n // Return empty if discovery fails (graceful degradation)\n _discoveryCache[idpUrl] = {}\n return {}\n}\n\nexport async function getGrantsEndpoint(idpUrl: string): Promise<string> {\n const disco = await discoverEndpoints(idpUrl)\n return (disco.openape_grants_endpoint as string) || `${idpUrl}/api/grants`\n}\n\nexport async function getAgentChallengeEndpoint(idpUrl: string): Promise<string> {\n const disco = await discoverEndpoints(idpUrl)\n return (disco.ddisa_agent_challenge_endpoint as string) || `${idpUrl}/api/agent/challenge`\n}\n\nexport async function getAgentAuthenticateEndpoint(idpUrl: string): Promise<string> {\n const disco = await discoverEndpoints(idpUrl)\n return (disco.ddisa_agent_authenticate_endpoint as string) || `${idpUrl}/api/agent/authenticate`\n}\n\nexport async function getDelegationsEndpoint(idpUrl: string): Promise<string> {\n const disco = await discoverEndpoints(idpUrl)\n return (disco.openape_delegations_endpoint as string) || `${idpUrl}/api/delegations`\n}\n\n/**\n * Re-authenticate an agent using Ed25519 challenge-response.\n * Called automatically when the token is expired.\n */\nasync function refreshAgentToken(): Promise<string | null> {\n const auth = loadAuth()\n if (!auth)\n return null\n\n const config = loadConfig()\n const keyPath = config.agent?.key\n if (!keyPath)\n return null\n\n try {\n const { readFileSync } = await import('node:fs')\n const { sign } = await import('node:crypto')\n const { homedir } = await import('node:os')\n const { loadEd25519PrivateKey } = await import('./ssh-key.js')\n\n const resolved = keyPath.replace(/^~/, homedir())\n const keyContent = readFileSync(resolved, 'utf-8')\n const privateKey = loadEd25519PrivateKey(keyContent)\n\n const challengeUrl = await getAgentChallengeEndpoint(auth.idp)\n const challengeResp = await fetch(challengeUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ agent_id: auth.email }),\n })\n\n if (!challengeResp.ok)\n return null\n\n const { challenge } = await challengeResp.json() as { challenge: string }\n const { Buffer } = await import('node:buffer')\n const signature = sign(null, Buffer.from(challenge), privateKey).toString('base64')\n\n const authenticateUrl = await getAgentAuthenticateEndpoint(auth.idp)\n const authResp = await fetch(authenticateUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ agent_id: auth.email, challenge, signature }),\n })\n\n if (!authResp.ok)\n return null\n\n const { token, expires_in } = await authResp.json() as { token: string, expires_in: number }\n\n saveAuth({\n ...auth,\n access_token: token,\n expires_at: Math.floor(Date.now() / 1000) + (expires_in || 3600),\n })\n\n if (debug) {\n consola.debug('Token refreshed via Ed25519 challenge-response')\n }\n\n return token\n }\n catch {\n return null\n }\n}\n\n/**\n * Refresh an OAuth2 access token using a stored refresh_token. Used for\n * PKCE/browser login sessions where no Ed25519 agent key is configured.\n *\n * Serialized via a file lock so concurrent apes/ape-shell invocations don't\n * both consume the same rotating refresh token (which would revoke the\n * entire family server-side).\n */\nasync function refreshOAuthToken(): Promise<string | null> {\n const auth = loadAuth()\n if (!auth?.refresh_token)\n return null\n\n const { acquireAuthLock, releaseAuthLock } = await import('./auth-lock.js')\n const lock = await acquireAuthLock({ timeoutMs: 5000 })\n if (!lock) {\n // Another process is refreshing. It should have updated auth.json by now;\n // re-read and return whatever fresh token is there (may still be null).\n return getAuthToken()\n }\n\n try {\n // Re-read auth.json inside the lock — another holder may already have\n // refreshed while we were waiting, in which case we reuse the new token.\n const latest = loadAuth()\n if (latest?.expires_at && Date.now() / 1000 < latest.expires_at - 30)\n return latest.access_token\n\n const activeRefreshToken = latest?.refresh_token ?? auth.refresh_token\n if (!activeRefreshToken)\n return null\n\n const disco = await discoverEndpoints(auth.idp)\n const tokenEndpoint = (disco.token_endpoint as string) || `${auth.idp}/token`\n\n const body = new URLSearchParams({\n grant_type: 'refresh_token',\n refresh_token: activeRefreshToken,\n })\n\n const resp = await fetch(tokenEndpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: body.toString(),\n })\n\n if (!resp.ok) {\n // Family may have been revoked server-side — clear refresh_token to\n // prevent an infinite retry loop on every subsequent apes invocation.\n if (resp.status === 400 || resp.status === 401) {\n const base = latest ?? auth\n saveAuth({ ...base, refresh_token: undefined })\n }\n return null\n }\n\n const tokens = await resp.json() as {\n access_token: string\n refresh_token?: string\n expires_in?: number\n }\n\n const base = latest ?? auth\n saveAuth({\n ...base,\n access_token: tokens.access_token,\n refresh_token: tokens.refresh_token ?? base.refresh_token,\n expires_at: Math.floor(Date.now() / 1000) + (tokens.expires_in || 300),\n })\n\n if (debug)\n consola.debug('Token refreshed via OAuth refresh_token')\n\n return tokens.access_token\n }\n finally {\n await releaseAuthLock(lock)\n }\n}\n\n/**\n * Refresh the local IdP token if it has expired. Used by every code path\n * that needs an access token — `apiFetch` for IdP-bound HTTP calls, plus\n * any pure-introspection command (`apes whoami`, `apes config get …`)\n * that previously only read local auth state and surfaced a stale\n * \"expired\" without attempting renewal.\n *\n * Returns the fresh access token on success, or null when no refresh\n * path is available (no agent key AND no refresh_token, or both refresh\n * attempts failed). Callers decide what to do with null — `apiFetch`\n * throws \"Not authenticated\", `whoami` falls back to the on-disk state.\n */\nexport async function ensureFreshToken(): Promise<string | null> {\n const cached = getAuthToken()\n if (cached) return cached\n\n // Auto-refresh: priority (1) ed25519 agent key, (2) OAuth refresh_token.\n // Agent-key first because it is concurrency-safe — every challenge is\n // independent server-side, so parallel ape-shell spawns don't race.\n const agentToken = await refreshAgentToken()\n if (agentToken) return agentToken\n const oauthToken = await refreshOAuthToken()\n return oauthToken ?? null\n}\n\nexport async function apiFetch<T = unknown>(\n path: string,\n options: {\n method?: string\n body?: unknown\n idp?: string\n token?: string\n } = {},\n): Promise<T> {\n const token = options.token ?? await ensureFreshToken()\n\n if (!token) {\n throw new Error('Not authenticated (token expired). Run `apes login` first.')\n }\n\n let url: string\n if (path.startsWith('http')) {\n url = path\n }\n else {\n const idp = options.idp || getIdpUrl()\n if (!idp) {\n throw new Error('No IdP URL configured. Run `apes login` first or pass --idp.')\n }\n url = `${idp}${path}`\n }\n const method = options.method || 'GET'\n const headers: Record<string, string> = {\n 'Authorization': `Bearer ${token}`,\n 'Content-Type': 'application/json',\n }\n\n if (debug) {\n consola.debug(`${method} ${url}`)\n consola.debug(`Token: ${token.substring(0, 20)}...${token.substring(token.length - 10)}`)\n }\n\n const response = await fetch(url, {\n method,\n headers,\n body: options.body ? JSON.stringify(options.body) : undefined,\n })\n\n if (debug) {\n consola.debug(`Response: ${response.status} ${response.statusText}`)\n }\n\n if (!response.ok) {\n const contentType = response.headers.get('content-type') || ''\n\n // Parse RFC 7807 Problem Details\n if (contentType.includes('application/problem+json') || contentType.includes('application/json')) {\n try {\n const problem = await response.json() as Record<string, unknown>\n const message = (problem.detail as string) || (problem.title as string) || (problem.statusMessage as string) || (problem.message as string) || `${response.status} ${response.statusText}`\n throw new ApiError(response.status, message, problem)\n }\n catch (e) {\n if (e instanceof ApiError)\n throw e\n }\n }\n\n const text = await response.text()\n throw new ApiError(response.status, text || `${response.status} ${response.statusText}`)\n }\n\n return response.json() as Promise<T>\n}\n"],"mappings":"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","names":["base"]}
@@ -7,6 +7,7 @@ var CliError = class extends Error {
7
7
  this.exitCode = exitCode;
8
8
  this.name = "CliError";
9
9
  }
10
+ exitCode;
10
11
  };
11
12
  var CliExit = class extends Error {
12
13
  constructor(exitCode = 0) {
@@ -14,6 +15,7 @@ var CliExit = class extends Error {
14
15
  this.exitCode = exitCode;
15
16
  this.name = "CliExit";
16
17
  }
18
+ exitCode;
17
19
  };
18
20
 
19
21
  // src/duration.ts
@@ -600,4 +602,4 @@ export {
600
602
  runLoop,
601
603
  RpcSessionMap
602
604
  };
603
- //# sourceMappingURL=chunk-FRCNYDTR.js.map
605
+ //# sourceMappingURL=chunk-L2V3CW5B.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/errors.ts","../src/duration.ts","../src/lib/agent-tools/bash.ts","../src/lib/agent-tools/file.ts","../src/lib/agent-tools/http.ts","../src/lib/agent-tools/mail.ts","../src/lib/agent-tools/tasks.ts","../src/lib/agent-tools/time.ts","../src/lib/agent-tools/index.ts","../src/lib/agent-runtime.ts"],"sourcesContent":["export class CliError extends Error {\n constructor(message: string, public exitCode: number = 1) {\n super(message)\n this.name = 'CliError'\n }\n}\n\nexport class CliExit extends Error {\n constructor(public exitCode: number = 0) {\n super('')\n this.name = 'CliExit'\n }\n}\n","/**\n * Parse a human-readable duration string into seconds.\n * Supported formats: 30s, 5m, 1h, 7d\n */\nexport function parseDuration(value: string): number {\n const match = value.match(/^(\\d+)\\s*([smhd])$/)\n if (!match) {\n throw new Error(`Invalid duration format: \"${value}\". Use e.g. 30m, 1h, 7d`)\n }\n const amount = Number.parseInt(match[1]!, 10)\n switch (match[2]) {\n case 's': return amount\n case 'm': return amount * 60\n case 'h': return amount * 3600\n case 'd': return amount * 86400\n default: throw new Error(`Unknown duration unit: ${match[2]}`)\n }\n}\n","import { spawn } from 'node:child_process'\nimport type { ToolDefinition } from './index'\n\nconst DEFAULT_TIMEOUT_MS = 5 * 60 * 1000\nconst MAX_STDIO_BYTES = 64 * 1024\n\n// Spawning `ape-shell -c <cmd>` (instead of building the inner\n// `apes run --shell -- bash -c …` ourselves) keeps the tool aligned\n// with what the human owner types interactively. ape-shell rewrites\n// to the same `apes run --shell` flow, so the grant cycle and the\n// shapes-adapter detection are identical to a terminal invocation.\n// APE_WAIT=1 forces the blocking path — the tool must return a\n// result, not exit-75 with grant-pending instructions.\nconst BIN = 'ape-shell'\n\nfunction capStdio(s: string): string {\n const buf = Buffer.from(s, 'utf8')\n if (buf.byteLength <= MAX_STDIO_BYTES) return s\n return `${buf.subarray(0, MAX_STDIO_BYTES).toString('utf8')}\\n[truncated to ${MAX_STDIO_BYTES} bytes]`\n}\n\nexport const bashTools: ToolDefinition[] = [\n {\n name: 'bash',\n description:\n 'Run a shell command on the agent host. Every invocation goes through the OpenApe DDISA grant cycle — auto-approved if the owner has a matching YOLO scope, otherwise the owner gets a push notification to approve. Runs as the agent\\'s macOS user, so file/network access is limited to what that user can see. Returns stdout, stderr, and exit code. For repeated command patterns ask the owner to set up a YOLO scope so approvals don\\'t pile up.',\n parameters: {\n type: 'object',\n properties: {\n cmd: {\n type: 'string',\n description: 'Shell command to run, e.g. `ls -la ~/Documents`, `git status`, `curl -fsSL https://example.com`. The whole string is passed to `bash -c`; quote internally as needed.',\n },\n timeout_ms: {\n type: 'number',\n description: 'Wall-clock cap for the whole approval-and-run cycle in milliseconds. Default 300000 (5 min). Approval waits count against this budget.',\n },\n },\n required: ['cmd'],\n },\n execute: async (args: unknown) => {\n const a = args as { cmd?: unknown, timeout_ms?: unknown }\n if (typeof a.cmd !== 'string' || a.cmd.trim() === '') {\n throw new Error('cmd must be a non-empty string')\n }\n const timeout = typeof a.timeout_ms === 'number' && a.timeout_ms > 0\n ? a.timeout_ms\n : DEFAULT_TIMEOUT_MS\n\n return await new Promise<unknown>((resolveResult) => {\n const child = spawn(BIN, ['-c', a.cmd as string], {\n env: { ...process.env, APE_WAIT: '1' },\n stdio: ['ignore', 'pipe', 'pipe'],\n })\n let stdout = ''\n let stderr = ''\n let timedOut = false\n let spawnError: Error | null = null\n\n child.stdout!.on('data', (chunk: Buffer) => { stdout += chunk.toString('utf8') })\n child.stderr!.on('data', (chunk: Buffer) => { stderr += chunk.toString('utf8') })\n child.on('error', (err) => { spawnError = err as Error })\n\n const timer = setTimeout(() => {\n timedOut = true\n child.kill('SIGTERM')\n // Force-kill if SIGTERM doesn't take in 5s — happens when the\n // child is wedged waiting on an upstream grant approval.\n setTimeout(() => {\n try { child.kill('SIGKILL') }\n catch { /* already dead */ }\n }, 5000)\n }, timeout)\n\n child.on('close', (code) => {\n clearTimeout(timer)\n if (spawnError) {\n resolveResult({\n error: spawnError.message,\n hint: `Could not exec '${BIN}'. The agent host needs @openape/apes installed globally so ape-shell is on PATH.`,\n })\n return\n }\n resolveResult({\n stdout: capStdio(stdout),\n stderr: capStdio(stderr),\n exit_code: code ?? -1,\n ...(timedOut ? { timed_out: true } : {}),\n })\n })\n })\n },\n },\n]\n","import { mkdirSync, readFileSync, writeFileSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { dirname, normalize, resolve } from 'node:path'\nimport type { ToolDefinition } from './index'\n\nconst MAX_BYTES = 1024 * 1024\n\n// All file ops are jailed inside the agent's $HOME. Path traversal\n// (`..`) is blocked by resolving the requested path against the home\n// dir and asserting the resolved path is still inside it.\nfunction jailPath(input: unknown): string {\n if (typeof input !== 'string' || input === '') {\n throw new Error('path must be a non-empty string')\n }\n const home = homedir()\n // Treat input as relative to home unless it starts with $HOME.\n const candidate = input.startsWith('~/')\n ? resolve(home, input.slice(2))\n : input.startsWith('/')\n ? normalize(input)\n : resolve(home, input)\n if (candidate !== home && !candidate.startsWith(`${home}/`)) {\n throw new Error(`path \"${input}\" resolves outside the agent's home`)\n }\n return candidate\n}\n\nexport const fileTools: ToolDefinition[] = [\n {\n name: 'file.read',\n description: 'Read a UTF-8 file from the agent\\'s home directory ($HOME). Capped at 1MB. Path traversal blocked.',\n parameters: {\n type: 'object',\n properties: {\n path: { type: 'string', description: 'Path relative to $HOME (or absolute under $HOME). `..` segments are rejected.' },\n },\n required: ['path'],\n },\n execute: async (args: unknown) => {\n const a = args as { path: string }\n const p = jailPath(a.path)\n const content = readFileSync(p, 'utf8')\n if (Buffer.byteLength(content, 'utf8') > MAX_BYTES) {\n return { path: p, truncated: true, content: content.slice(0, MAX_BYTES) }\n }\n return { path: p, truncated: false, content }\n },\n },\n {\n name: 'file.write',\n description: 'Write a UTF-8 file under the agent\\'s home directory. Creates parent dirs as needed. 1MB max.',\n parameters: {\n type: 'object',\n properties: {\n path: { type: 'string', description: 'Path relative to $HOME (or absolute under $HOME).' },\n content: { type: 'string', description: 'File body. Existing files are overwritten.' },\n },\n required: ['path', 'content'],\n },\n execute: async (args: unknown) => {\n const a = args as { path: string, content: string }\n if (typeof a.content !== 'string') throw new Error('content must be a string')\n if (Buffer.byteLength(a.content, 'utf8') > MAX_BYTES) {\n throw new Error(`content exceeds ${MAX_BYTES} byte cap`)\n }\n const p = jailPath(a.path)\n mkdirSync(dirname(p), { recursive: true })\n writeFileSync(p, a.content, { encoding: 'utf8' })\n return { path: p, bytes: Buffer.byteLength(a.content, 'utf8') }\n },\n },\n]\n\nexport const _internal = { jailPath }\n","import type { ToolDefinition } from './index'\n\nconst MAX_BYTES = 1024 * 1024\n// Headers we never let the model set: hop-by-hop ones, host (we\n// don't want to spoof another origin) and authorization (the model\n// would otherwise be a step away from \"fetch tasks.openape.ai with my\n// own JWT\" — that's a manual escalation path, not a tool the agent\n// gets out of the box).\nconst FORBIDDEN_HEADERS = new Set([\n 'host',\n 'authorization',\n 'cookie',\n 'connection',\n 'transfer-encoding',\n 'upgrade',\n 'proxy-authorization',\n])\n\nfunction sanitizeHeaders(input: unknown): Record<string, string> {\n if (!input || typeof input !== 'object') return {}\n const out: Record<string, string> = {}\n for (const [k, v] of Object.entries(input as Record<string, unknown>)) {\n if (typeof v !== 'string') continue\n if (FORBIDDEN_HEADERS.has(k.toLowerCase())) continue\n out[k] = v\n }\n return out\n}\n\nasync function readCappedBody(res: Response): Promise<string> {\n const buf = new Uint8Array(MAX_BYTES + 1)\n let written = 0\n const reader = res.body?.getReader()\n if (!reader) return await res.text()\n while (true) {\n const { value, done } = await reader.read()\n if (done) break\n if (written + value.byteLength > MAX_BYTES) {\n buf.set(value.subarray(0, MAX_BYTES - written), written)\n written = MAX_BYTES\n try { await reader.cancel() }\n catch { /* ignore */ }\n break\n }\n buf.set(value, written)\n written += value.byteLength\n }\n return new TextDecoder().decode(buf.subarray(0, written))\n}\n\nexport const httpTools: ToolDefinition[] = [\n {\n name: 'http.get',\n description: 'GET an HTTPS URL and return the response body (capped at 1MB). Useful for reading public APIs, RSS feeds, web pages.',\n parameters: {\n type: 'object',\n properties: {\n url: { type: 'string', description: 'Absolute HTTPS URL.' },\n headers: { type: 'object', description: 'Optional headers (Host, Authorization, Cookie are stripped).' },\n },\n required: ['url'],\n },\n execute: async (args: unknown) => {\n const a = args as { url: string, headers?: unknown }\n if (typeof a.url !== 'string' || !a.url.startsWith('http')) {\n throw new Error('url must be an http(s) URL')\n }\n const res = await fetch(a.url, { method: 'GET', headers: sanitizeHeaders(a.headers) })\n const body = await readCappedBody(res)\n return { status: res.status, headers: Object.fromEntries(res.headers), body }\n },\n },\n {\n name: 'http.post',\n description: 'POST JSON to an HTTPS URL and return the response body (capped at 1MB).',\n parameters: {\n type: 'object',\n properties: {\n url: { type: 'string', description: 'Absolute HTTPS URL.' },\n body: { description: 'JSON-serialisable payload.' },\n headers: { type: 'object', description: 'Optional headers (Host, Authorization, Cookie are stripped).' },\n },\n required: ['url', 'body'],\n },\n execute: async (args: unknown) => {\n const a = args as { url: string, body: unknown, headers?: unknown }\n if (typeof a.url !== 'string' || !a.url.startsWith('http')) {\n throw new Error('url must be an http(s) URL')\n }\n const res = await fetch(a.url, {\n method: 'POST',\n headers: { 'content-type': 'application/json', ...sanitizeHeaders(a.headers) },\n body: JSON.stringify(a.body),\n })\n const body = await readCappedBody(res)\n return { status: res.status, headers: Object.fromEntries(res.headers), body }\n },\n },\n]\n","import { execFileSync } from 'node:child_process'\nimport type { ToolDefinition } from './index'\n\n// Shell out to o365-cli for read-only mail access. Same auth model\n// as ape-tasks: the agent's macOS user has o365-cli installed +\n// authenticated, agent runs as that user.\n\nfunction o365(args: string[]): string {\n try {\n return execFileSync('o365-cli', args, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] })\n }\n catch (err) {\n const e = err as { stderr?: Buffer | string, code?: string, message?: string }\n if (e.code === 'ENOENT') {\n throw new Error('o365-cli is not installed on this agent host')\n }\n const stderr = typeof e.stderr === 'string' ? e.stderr : e.stderr?.toString('utf8')\n throw new Error(`o365-cli failed: ${stderr ?? e.message ?? err}`)\n }\n}\n\nexport const mailTools: ToolDefinition[] = [\n {\n name: 'mail.list',\n description: 'List recent inbox messages via o365-cli. Optional `unread_only` and `limit`.',\n parameters: {\n type: 'object',\n properties: {\n limit: { type: 'integer', minimum: 1, maximum: 100, default: 20 },\n unread_only: { type: 'boolean', default: false },\n },\n required: [],\n },\n execute: async (args: unknown) => {\n const a = (args as { limit?: number, unread_only?: boolean }) ?? {}\n const argv = ['mail', 'list', '--json', '--limit', String(a.limit ?? 20)]\n if (a.unread_only) argv.push('--unread')\n const out = o365(argv)\n try { return JSON.parse(out) }\n catch { return { raw: out } }\n },\n },\n {\n name: 'mail.search',\n description: 'Search the inbox via o365-cli using a free-form query string.',\n parameters: {\n type: 'object',\n properties: {\n q: { type: 'string' },\n limit: { type: 'integer', minimum: 1, maximum: 100, default: 20 },\n },\n required: ['q'],\n },\n execute: async (args: unknown) => {\n const a = args as { q: string, limit?: number }\n if (typeof a.q !== 'string' || a.q.length === 0) throw new Error('q is required')\n const argv = ['mail', 'search', a.q, '--json', '--limit', String(a.limit ?? 20)]\n const out = o365(argv)\n try { return JSON.parse(out) }\n catch { return { raw: out } }\n },\n },\n]\n","import { execFileSync } from 'node:child_process'\nimport type { ToolDefinition } from './index'\n\n// Shell out to the user's `ape-tasks` CLI. The agent's macOS user\n// has its own ~/.config/apes/auth.json, so the CLI talks to\n// tasks.openape.ai as the agent's owner via the agent JWT (which\n// carries the owner-domain). For v1 we don't require a separate\n// agent identity for tasks — the tasks CLI authenticates with the\n// same auth.json the runtime uses.\n\nfunction ape(args: string[]): string {\n try {\n return execFileSync('ape-tasks', args, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] })\n }\n catch (err) {\n const e = err as { stderr?: Buffer | string, message?: string }\n const stderr = typeof e.stderr === 'string' ? e.stderr : e.stderr?.toString('utf8')\n throw new Error(`ape-tasks failed: ${stderr ?? e.message ?? err}`)\n }\n}\n\nexport const tasksTools: ToolDefinition[] = [\n {\n name: 'tasks.list',\n description: 'List the owner\\'s open ape-tasks (the user\\'s personal task list at tasks.openape.ai).',\n parameters: {\n type: 'object',\n properties: {\n status: { type: 'string', enum: ['open', 'doing', 'done', 'archived'] },\n team_id: { type: 'string' },\n },\n required: [],\n },\n execute: async (args: unknown) => {\n const a = (args as { status?: string, team_id?: string }) ?? {}\n const argv = ['list', '--json']\n if (a.status) argv.push('--status', a.status)\n if (a.team_id) argv.push('--team', a.team_id)\n const out = ape(argv)\n try { return JSON.parse(out) }\n catch { return { raw: out } }\n },\n },\n {\n name: 'tasks.create',\n description: 'Create a new ape-task on the owner\\'s task list at tasks.openape.ai.',\n parameters: {\n type: 'object',\n properties: {\n title: { type: 'string' },\n notes: { type: 'string' },\n priority: { type: 'string', enum: ['low', 'med', 'high'] },\n due_at: { type: 'string', description: 'ISO date or +Nh/+Nd shorthand.' },\n },\n required: ['title'],\n },\n execute: async (args: unknown) => {\n const a = args as { title: string, notes?: string, priority?: string, due_at?: string }\n const argv = ['new', '--title', a.title, '--json']\n if (a.notes) argv.push('--notes', a.notes)\n if (a.priority) argv.push('--priority', a.priority)\n if (a.due_at) argv.push('--due', a.due_at)\n const out = ape(argv)\n try { return JSON.parse(out) }\n catch { return { raw: out } }\n },\n },\n]\n","import type { ToolDefinition } from './index'\n\nexport const timeTools: ToolDefinition[] = [\n {\n name: 'time.now',\n description: 'Returns the current UTC date and time as ISO 8601 plus epoch seconds. No inputs.',\n parameters: { type: 'object', properties: {}, required: [] },\n execute: async () => {\n const now = new Date()\n return {\n iso: now.toISOString(),\n epoch_seconds: Math.floor(now.getTime() / 1000),\n timezone_offset_minutes: -now.getTimezoneOffset(),\n }\n },\n },\n]\n","// Built-in tool registry shipped with the apes binary. Each tool is\n// (a) an OpenAI tool-spec object (used as-is in the LiteLLM call's\n// `tools[]` parameter) and (b) an `execute` function called when the\n// model emits a `tool_calls` entry. Adding a new tool means\n// implementing it here AND adding an entry to\n// apps/openape-troop/server/tool-catalog.json so the SP validates\n// task specs against the same allowlist.\n//\n// The registry is keyed by string name (`time.now`, `http.get`, …).\n// `taskTools(spec.tools)` resolves a task's tool list to the slice of\n// the registry it can use. Unknown names abort the run before any\n// LLM call so the model can't invent a tool we haven't shipped.\n\nimport { bashTools } from './bash'\nimport { fileTools } from './file'\nimport { httpTools } from './http'\nimport { mailTools } from './mail'\nimport { tasksTools } from './tasks'\nimport { timeTools } from './time'\n\nexport interface ToolDefinition {\n name: string\n description: string\n // OpenAI tool-spec shape: { type: 'object', properties: …, required: … }.\n // We don't constrain it further — the LLM's job to fill it in.\n parameters: Record<string, unknown>\n execute: (args: unknown) => Promise<unknown>\n}\n\nconst ALL_TOOLS: ToolDefinition[] = [\n ...timeTools,\n ...httpTools,\n ...fileTools,\n ...tasksTools,\n ...mailTools,\n ...bashTools,\n]\n\nexport const TOOLS: Record<string, ToolDefinition> = Object.fromEntries(\n ALL_TOOLS.map(t => [t.name, t]),\n)\n\n/**\n * Resolve a task spec's tool name list to ToolDefinitions. Throws on\n * unknown names — callers must surface that as a run-failure with a\n * clear \"unknown tool: foo\" final_message so the owner can see what\n * went wrong in the SP UI.\n */\nexport function taskTools(names: string[]): ToolDefinition[] {\n const out: ToolDefinition[] = []\n const missing: string[] = []\n for (const name of names) {\n const tool = TOOLS[name]\n if (!tool) missing.push(name)\n else out.push(tool)\n }\n if (missing.length > 0) {\n throw new Error(`unknown tool(s): ${missing.join(', ')}`)\n }\n return out\n}\n\n/**\n * Format the registry slice as the OpenAI `tools` param. Strips the\n * `execute` function — only the spec part goes to the LLM.\n *\n * Tool names get sent through `wireToolName()` because some upstreams\n * — notably ChatGPT's Responses API behind LiteLLM — enforce\n * `^[a-zA-Z0-9_-]+$` and reject our dotted catalog names like\n * `time.now`. Use `localToolName()` to map back when the model emits\n * a tool_call.\n */\nexport function asOpenAiTools(tools: ToolDefinition[]): { type: 'function', function: Omit<ToolDefinition, 'execute'> }[] {\n return tools.map(t => ({\n type: 'function' as const,\n function: { name: wireToolName(t.name), description: t.description, parameters: t.parameters },\n }))\n}\n\n/** Encode a local tool name (e.g. `time.now`) for the wire format. */\nexport function wireToolName(local: string): string {\n return local.replace(/\\./g, '_')\n}\n\n/**\n * Decode a tool name from the wire format back to its local form. The\n * encoding is `.` → `_`; we recover the original by looking for an\n * exact match in the catalog and only fall back to passing the name\n * through if no match is found (so unknown tool names still surface\n * as the obvious \"unknown tool\" error rather than silent rewrites).\n */\nexport function localToolName(wire: string): string {\n for (const t of Object.values(TOOLS)) {\n if (wireToolName(t.name) === wire) return t.name\n }\n return wire\n}\n","import { asOpenAiTools, localToolName, wireToolName } from './agent-tools'\nimport type { ToolDefinition } from './agent-tools'\n\n// Shared agent loop: send messages + tools to LiteLLM (OpenAI-\n// compatible chat-completions API), execute any tool_calls in the\n// response, append tool-result messages, loop until the model\n// returns a response with no tool_calls or we hit max_steps.\n//\n// Both `apes agents run` (cron) and `apes agents serve --rpc`\n// (chat-bridge subprocess) call into here so the LLM behaviour is\n// guaranteed identical between modes.\n\nexport interface ChatMessage {\n role: 'system' | 'user' | 'assistant' | 'tool'\n content: string | null\n // Assistant message tool calls\n tool_calls?: Array<{\n id: string\n type: 'function'\n function: { name: string, arguments: string }\n }>\n // Tool message metadata\n tool_call_id?: string\n name?: string\n}\n\nexport interface RuntimeConfig {\n apiBase: string // LITELLM_BASE_URL (e.g. \"http://127.0.0.1:4000/v1\")\n apiKey: string // LITELLM_API_KEY (or LITELLM_MASTER_KEY)\n model: string\n}\n\nexport interface TraceEntry {\n step: number\n type: 'assistant' | 'tool_call' | 'tool_result' | 'tool_error'\n // Stripped down for trace: don't carry full bodies\n preview: string\n tool?: string\n}\n\nexport interface RunResult {\n status: 'ok' | 'error'\n finalMessage: string | null\n stepCount: number\n trace: TraceEntry[]\n}\n\nexport interface RunStreamHandlers {\n onTextDelta?: (delta: string) => void\n onToolCall?: (call: { name: string, args: unknown }) => void\n onToolResult?: (result: { name: string, result: unknown }) => void\n onToolError?: (err: { name: string, error: string }) => void\n onDone?: (result: RunResult) => void\n}\n\nexport interface RunOptions {\n config: RuntimeConfig\n systemPrompt: string\n userMessage: string\n tools: ToolDefinition[]\n maxSteps: number\n // Pre-existing message history for continued sessions (RPC mode).\n // The system prompt is always prepended, even if `history` is\n // non-empty — the SP-stored prompt is canonical. Defaults to []\n // (a fresh single-user-turn conversation).\n history?: ChatMessage[]\n handlers?: RunStreamHandlers\n // Test seam — replace fetch (we always use the global fetch in\n // production). Tests pass a mock that returns canned responses.\n fetchImpl?: typeof fetch\n}\n\ninterface OpenAIChoice {\n message: ChatMessage\n finish_reason?: string\n}\ninterface OpenAIChatResponse {\n choices: OpenAIChoice[]\n}\n\nfunction previewJson(value: unknown, max = 500): string {\n let s: string\n try { s = JSON.stringify(value) }\n catch { s = String(value) }\n return s.length > max ? `${s.slice(0, max)}…` : s\n}\n\nexport async function runLoop(opts: RunOptions): Promise<RunResult> {\n const fetchFn = opts.fetchImpl ?? fetch\n const trace: TraceEntry[] = []\n const messages: ChatMessage[] = [\n { role: 'system', content: opts.systemPrompt },\n ...(opts.history ?? []),\n { role: 'user', content: opts.userMessage },\n ]\n const tools = asOpenAiTools(opts.tools)\n\n for (let step = 1; step <= opts.maxSteps; step++) {\n const res = await fetchFn(`${opts.config.apiBase}/chat/completions`, {\n method: 'POST',\n headers: {\n 'authorization': `Bearer ${opts.config.apiKey}`,\n 'content-type': 'application/json',\n },\n body: JSON.stringify({\n model: opts.config.model,\n messages,\n ...(tools.length > 0 ? { tools, tool_choice: 'auto' } : {}),\n }),\n })\n if (!res.ok) {\n const text = await res.text().catch(() => '')\n throw new Error(`LiteLLM ${res.status}: ${text.slice(0, 500)}`)\n }\n const data = await res.json() as OpenAIChatResponse\n const choice = data.choices?.[0]\n if (!choice) throw new Error('LiteLLM response had no choices')\n\n const assistant = choice.message\n messages.push(assistant)\n if (assistant.content) opts.handlers?.onTextDelta?.(assistant.content)\n\n trace.push({\n step,\n type: 'assistant',\n preview: previewJson({ content: assistant.content, tool_calls: assistant.tool_calls?.length ?? 0 }),\n })\n\n if (!assistant.tool_calls || assistant.tool_calls.length === 0) {\n const result: RunResult = {\n status: 'ok',\n finalMessage: assistant.content,\n stepCount: step,\n trace,\n }\n opts.handlers?.onDone?.(result)\n return result\n }\n\n // Execute each tool call; the model sees the result on the next turn.\n // Wire-format names (`time_now`) get decoded to local catalog names\n // (`time.now`) for lookup + handler events; we send back the same\n // wire name in the tool message so the next request validates.\n for (const call of assistant.tool_calls) {\n const wireName = call.function.name\n const localName = localToolName(wireName)\n const tool = opts.tools.find(t => t.name === localName)\n let parsedArgs: unknown\n try { parsedArgs = JSON.parse(call.function.arguments) }\n catch { parsedArgs = {} }\n opts.handlers?.onToolCall?.({ name: localName, args: parsedArgs })\n trace.push({ step, type: 'tool_call', tool: localName, preview: previewJson(parsedArgs) })\n\n let result: unknown\n let isError = false\n if (!tool) {\n result = `unknown tool: ${localName}`\n isError = true\n }\n else {\n try {\n result = await tool.execute(parsedArgs)\n }\n catch (err) {\n result = (err as Error)?.message ?? String(err)\n isError = true\n }\n }\n\n if (isError) {\n opts.handlers?.onToolError?.({ name: localName, error: String(result) })\n trace.push({ step, type: 'tool_error', tool: localName, preview: previewJson(result) })\n }\n else {\n opts.handlers?.onToolResult?.({ name: localName, result })\n trace.push({ step, type: 'tool_result', tool: localName, preview: previewJson(result) })\n }\n\n messages.push({\n role: 'tool',\n tool_call_id: call.id,\n name: wireToolName(localName),\n content: typeof result === 'string' ? result : JSON.stringify(result),\n })\n }\n }\n\n // Loop fell through max_steps without a no-tool-calls reply.\n const result: RunResult = {\n status: 'error',\n finalMessage: `max_steps (${opts.maxSteps}) reached without completion`,\n stepCount: opts.maxSteps,\n trace,\n }\n opts.handlers?.onDone?.(result)\n return result\n}\n\nexport interface RpcSession {\n messages: ChatMessage[]\n systemPrompt: string\n tools: ToolDefinition[]\n maxSteps: number\n lastTouched: number\n}\n\nconst RPC_SESSION_TTL_MS = 60 * 60 * 1000\n\nexport class RpcSessionMap {\n private sessions = new Map<string, RpcSession>()\n\n get(id: string): RpcSession | undefined {\n const s = this.sessions.get(id)\n if (s) s.lastTouched = Date.now()\n return s\n }\n\n put(id: string, s: RpcSession): void {\n s.lastTouched = Date.now()\n this.sessions.set(id, s)\n }\n\n evictStale(): void {\n const cutoff = Date.now() - RPC_SESSION_TTL_MS\n for (const [k, v] of this.sessions) {\n if (v.lastTouched < cutoff) this.sessions.delete(k)\n }\n }\n\n size(): number {\n return this.sessions.size\n }\n}\n"],"mappings":"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","names":["MAX_BYTES","execFileSync","result"]}
1
+ {"version":3,"sources":["../src/errors.ts","../src/duration.ts","../src/lib/agent-tools/bash.ts","../src/lib/agent-tools/file.ts","../src/lib/agent-tools/http.ts","../src/lib/agent-tools/mail.ts","../src/lib/agent-tools/tasks.ts","../src/lib/agent-tools/time.ts","../src/lib/agent-tools/index.ts","../src/lib/agent-runtime.ts"],"sourcesContent":["export class CliError extends Error {\n constructor(message: string, public exitCode: number = 1) {\n super(message)\n this.name = 'CliError'\n }\n}\n\nexport class CliExit extends Error {\n constructor(public exitCode: number = 0) {\n super('')\n this.name = 'CliExit'\n }\n}\n","/**\n * Parse a human-readable duration string into seconds.\n * Supported formats: 30s, 5m, 1h, 7d\n */\nexport function parseDuration(value: string): number {\n const match = value.match(/^(\\d+)\\s*([smhd])$/)\n if (!match) {\n throw new Error(`Invalid duration format: \"${value}\". Use e.g. 30m, 1h, 7d`)\n }\n const amount = Number.parseInt(match[1]!, 10)\n switch (match[2]) {\n case 's': return amount\n case 'm': return amount * 60\n case 'h': return amount * 3600\n case 'd': return amount * 86400\n default: throw new Error(`Unknown duration unit: ${match[2]}`)\n }\n}\n","import { spawn } from 'node:child_process'\nimport type { ToolDefinition } from './index'\n\nconst DEFAULT_TIMEOUT_MS = 5 * 60 * 1000\nconst MAX_STDIO_BYTES = 64 * 1024\n\n// Spawning `ape-shell -c <cmd>` (instead of building the inner\n// `apes run --shell -- bash -c …` ourselves) keeps the tool aligned\n// with what the human owner types interactively. ape-shell rewrites\n// to the same `apes run --shell` flow, so the grant cycle and the\n// shapes-adapter detection are identical to a terminal invocation.\n// APE_WAIT=1 forces the blocking path — the tool must return a\n// result, not exit-75 with grant-pending instructions.\nconst BIN = 'ape-shell'\n\nfunction capStdio(s: string): string {\n const buf = Buffer.from(s, 'utf8')\n if (buf.byteLength <= MAX_STDIO_BYTES) return s\n return `${buf.subarray(0, MAX_STDIO_BYTES).toString('utf8')}\\n[truncated to ${MAX_STDIO_BYTES} bytes]`\n}\n\nexport const bashTools: ToolDefinition[] = [\n {\n name: 'bash',\n description:\n 'Run a shell command on the agent host. Every invocation goes through the OpenApe DDISA grant cycle — auto-approved if the owner has a matching YOLO scope, otherwise the owner gets a push notification to approve. Runs as the agent\\'s macOS user, so file/network access is limited to what that user can see. Returns stdout, stderr, and exit code. For repeated command patterns ask the owner to set up a YOLO scope so approvals don\\'t pile up.',\n parameters: {\n type: 'object',\n properties: {\n cmd: {\n type: 'string',\n description: 'Shell command to run, e.g. `ls -la ~/Documents`, `git status`, `curl -fsSL https://example.com`. The whole string is passed to `bash -c`; quote internally as needed.',\n },\n timeout_ms: {\n type: 'number',\n description: 'Wall-clock cap for the whole approval-and-run cycle in milliseconds. Default 300000 (5 min). Approval waits count against this budget.',\n },\n },\n required: ['cmd'],\n },\n execute: async (args: unknown) => {\n const a = args as { cmd?: unknown, timeout_ms?: unknown }\n if (typeof a.cmd !== 'string' || a.cmd.trim() === '') {\n throw new Error('cmd must be a non-empty string')\n }\n const timeout = typeof a.timeout_ms === 'number' && a.timeout_ms > 0\n ? a.timeout_ms\n : DEFAULT_TIMEOUT_MS\n\n return await new Promise<unknown>((resolveResult) => {\n const child = spawn(BIN, ['-c', a.cmd as string], {\n env: { ...process.env, APE_WAIT: '1' },\n stdio: ['ignore', 'pipe', 'pipe'],\n })\n let stdout = ''\n let stderr = ''\n let timedOut = false\n let spawnError: Error | null = null\n\n child.stdout!.on('data', (chunk: Buffer) => { stdout += chunk.toString('utf8') })\n child.stderr!.on('data', (chunk: Buffer) => { stderr += chunk.toString('utf8') })\n child.on('error', (err) => { spawnError = err as Error })\n\n const timer = setTimeout(() => {\n timedOut = true\n child.kill('SIGTERM')\n // Force-kill if SIGTERM doesn't take in 5s — happens when the\n // child is wedged waiting on an upstream grant approval.\n setTimeout(() => {\n try { child.kill('SIGKILL') }\n catch { /* already dead */ }\n }, 5000)\n }, timeout)\n\n child.on('close', (code) => {\n clearTimeout(timer)\n if (spawnError) {\n resolveResult({\n error: spawnError.message,\n hint: `Could not exec '${BIN}'. The agent host needs @openape/apes installed globally so ape-shell is on PATH.`,\n })\n return\n }\n resolveResult({\n stdout: capStdio(stdout),\n stderr: capStdio(stderr),\n exit_code: code ?? -1,\n ...(timedOut ? { timed_out: true } : {}),\n })\n })\n })\n },\n },\n]\n","import { mkdirSync, readFileSync, writeFileSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { dirname, normalize, resolve } from 'node:path'\nimport type { ToolDefinition } from './index'\n\nconst MAX_BYTES = 1024 * 1024\n\n// All file ops are jailed inside the agent's $HOME. Path traversal\n// (`..`) is blocked by resolving the requested path against the home\n// dir and asserting the resolved path is still inside it.\nfunction jailPath(input: unknown): string {\n if (typeof input !== 'string' || input === '') {\n throw new Error('path must be a non-empty string')\n }\n const home = homedir()\n // Treat input as relative to home unless it starts with $HOME.\n const candidate = input.startsWith('~/')\n ? resolve(home, input.slice(2))\n : input.startsWith('/')\n ? normalize(input)\n : resolve(home, input)\n if (candidate !== home && !candidate.startsWith(`${home}/`)) {\n throw new Error(`path \"${input}\" resolves outside the agent's home`)\n }\n return candidate\n}\n\nexport const fileTools: ToolDefinition[] = [\n {\n name: 'file.read',\n description: 'Read a UTF-8 file from the agent\\'s home directory ($HOME). Capped at 1MB. Path traversal blocked.',\n parameters: {\n type: 'object',\n properties: {\n path: { type: 'string', description: 'Path relative to $HOME (or absolute under $HOME). `..` segments are rejected.' },\n },\n required: ['path'],\n },\n execute: async (args: unknown) => {\n const a = args as { path: string }\n const p = jailPath(a.path)\n const content = readFileSync(p, 'utf8')\n if (Buffer.byteLength(content, 'utf8') > MAX_BYTES) {\n return { path: p, truncated: true, content: content.slice(0, MAX_BYTES) }\n }\n return { path: p, truncated: false, content }\n },\n },\n {\n name: 'file.write',\n description: 'Write a UTF-8 file under the agent\\'s home directory. Creates parent dirs as needed. 1MB max.',\n parameters: {\n type: 'object',\n properties: {\n path: { type: 'string', description: 'Path relative to $HOME (or absolute under $HOME).' },\n content: { type: 'string', description: 'File body. Existing files are overwritten.' },\n },\n required: ['path', 'content'],\n },\n execute: async (args: unknown) => {\n const a = args as { path: string, content: string }\n if (typeof a.content !== 'string') throw new Error('content must be a string')\n if (Buffer.byteLength(a.content, 'utf8') > MAX_BYTES) {\n throw new Error(`content exceeds ${MAX_BYTES} byte cap`)\n }\n const p = jailPath(a.path)\n mkdirSync(dirname(p), { recursive: true })\n writeFileSync(p, a.content, { encoding: 'utf8' })\n return { path: p, bytes: Buffer.byteLength(a.content, 'utf8') }\n },\n },\n]\n\nexport const _internal = { jailPath }\n","import type { ToolDefinition } from './index'\n\nconst MAX_BYTES = 1024 * 1024\n// Headers we never let the model set: hop-by-hop ones, host (we\n// don't want to spoof another origin) and authorization (the model\n// would otherwise be a step away from \"fetch tasks.openape.ai with my\n// own JWT\" — that's a manual escalation path, not a tool the agent\n// gets out of the box).\nconst FORBIDDEN_HEADERS = new Set([\n 'host',\n 'authorization',\n 'cookie',\n 'connection',\n 'transfer-encoding',\n 'upgrade',\n 'proxy-authorization',\n])\n\nfunction sanitizeHeaders(input: unknown): Record<string, string> {\n if (!input || typeof input !== 'object') return {}\n const out: Record<string, string> = {}\n for (const [k, v] of Object.entries(input as Record<string, unknown>)) {\n if (typeof v !== 'string') continue\n if (FORBIDDEN_HEADERS.has(k.toLowerCase())) continue\n out[k] = v\n }\n return out\n}\n\nasync function readCappedBody(res: Response): Promise<string> {\n const buf = new Uint8Array(MAX_BYTES + 1)\n let written = 0\n const reader = res.body?.getReader()\n if (!reader) return await res.text()\n while (true) {\n const { value, done } = await reader.read()\n if (done) break\n if (written + value.byteLength > MAX_BYTES) {\n buf.set(value.subarray(0, MAX_BYTES - written), written)\n written = MAX_BYTES\n try { await reader.cancel() }\n catch { /* ignore */ }\n break\n }\n buf.set(value, written)\n written += value.byteLength\n }\n return new TextDecoder().decode(buf.subarray(0, written))\n}\n\nexport const httpTools: ToolDefinition[] = [\n {\n name: 'http.get',\n description: 'GET an HTTPS URL and return the response body (capped at 1MB). Useful for reading public APIs, RSS feeds, web pages.',\n parameters: {\n type: 'object',\n properties: {\n url: { type: 'string', description: 'Absolute HTTPS URL.' },\n headers: { type: 'object', description: 'Optional headers (Host, Authorization, Cookie are stripped).' },\n },\n required: ['url'],\n },\n execute: async (args: unknown) => {\n const a = args as { url: string, headers?: unknown }\n if (typeof a.url !== 'string' || !a.url.startsWith('http')) {\n throw new Error('url must be an http(s) URL')\n }\n const res = await fetch(a.url, { method: 'GET', headers: sanitizeHeaders(a.headers) })\n const body = await readCappedBody(res)\n return { status: res.status, headers: Object.fromEntries(res.headers), body }\n },\n },\n {\n name: 'http.post',\n description: 'POST JSON to an HTTPS URL and return the response body (capped at 1MB).',\n parameters: {\n type: 'object',\n properties: {\n url: { type: 'string', description: 'Absolute HTTPS URL.' },\n body: { description: 'JSON-serialisable payload.' },\n headers: { type: 'object', description: 'Optional headers (Host, Authorization, Cookie are stripped).' },\n },\n required: ['url', 'body'],\n },\n execute: async (args: unknown) => {\n const a = args as { url: string, body: unknown, headers?: unknown }\n if (typeof a.url !== 'string' || !a.url.startsWith('http')) {\n throw new Error('url must be an http(s) URL')\n }\n const res = await fetch(a.url, {\n method: 'POST',\n headers: { 'content-type': 'application/json', ...sanitizeHeaders(a.headers) },\n body: JSON.stringify(a.body),\n })\n const body = await readCappedBody(res)\n return { status: res.status, headers: Object.fromEntries(res.headers), body }\n },\n },\n]\n","import { execFileSync } from 'node:child_process'\nimport type { ToolDefinition } from './index'\n\n// Shell out to o365-cli for read-only mail access. Same auth model\n// as ape-tasks: the agent's macOS user has o365-cli installed +\n// authenticated, agent runs as that user.\n\nfunction o365(args: string[]): string {\n try {\n return execFileSync('o365-cli', args, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] })\n }\n catch (err) {\n const e = err as { stderr?: Buffer | string, code?: string, message?: string }\n if (e.code === 'ENOENT') {\n throw new Error('o365-cli is not installed on this agent host')\n }\n const stderr = typeof e.stderr === 'string' ? e.stderr : e.stderr?.toString('utf8')\n throw new Error(`o365-cli failed: ${stderr ?? e.message ?? err}`)\n }\n}\n\nexport const mailTools: ToolDefinition[] = [\n {\n name: 'mail.list',\n description: 'List recent inbox messages via o365-cli. Optional `unread_only` and `limit`.',\n parameters: {\n type: 'object',\n properties: {\n limit: { type: 'integer', minimum: 1, maximum: 100, default: 20 },\n unread_only: { type: 'boolean', default: false },\n },\n required: [],\n },\n execute: async (args: unknown) => {\n const a = (args as { limit?: number, unread_only?: boolean }) ?? {}\n const argv = ['mail', 'list', '--json', '--limit', String(a.limit ?? 20)]\n if (a.unread_only) argv.push('--unread')\n const out = o365(argv)\n try { return JSON.parse(out) }\n catch { return { raw: out } }\n },\n },\n {\n name: 'mail.search',\n description: 'Search the inbox via o365-cli using a free-form query string.',\n parameters: {\n type: 'object',\n properties: {\n q: { type: 'string' },\n limit: { type: 'integer', minimum: 1, maximum: 100, default: 20 },\n },\n required: ['q'],\n },\n execute: async (args: unknown) => {\n const a = args as { q: string, limit?: number }\n if (typeof a.q !== 'string' || a.q.length === 0) throw new Error('q is required')\n const argv = ['mail', 'search', a.q, '--json', '--limit', String(a.limit ?? 20)]\n const out = o365(argv)\n try { return JSON.parse(out) }\n catch { return { raw: out } }\n },\n },\n]\n","import { execFileSync } from 'node:child_process'\nimport type { ToolDefinition } from './index'\n\n// Shell out to the user's `ape-tasks` CLI. The agent's macOS user\n// has its own ~/.config/apes/auth.json, so the CLI talks to\n// tasks.openape.ai as the agent's owner via the agent JWT (which\n// carries the owner-domain). For v1 we don't require a separate\n// agent identity for tasks — the tasks CLI authenticates with the\n// same auth.json the runtime uses.\n\nfunction ape(args: string[]): string {\n try {\n return execFileSync('ape-tasks', args, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] })\n }\n catch (err) {\n const e = err as { stderr?: Buffer | string, message?: string }\n const stderr = typeof e.stderr === 'string' ? e.stderr : e.stderr?.toString('utf8')\n throw new Error(`ape-tasks failed: ${stderr ?? e.message ?? err}`)\n }\n}\n\nexport const tasksTools: ToolDefinition[] = [\n {\n name: 'tasks.list',\n description: 'List the owner\\'s open ape-tasks (the user\\'s personal task list at tasks.openape.ai).',\n parameters: {\n type: 'object',\n properties: {\n status: { type: 'string', enum: ['open', 'doing', 'done', 'archived'] },\n team_id: { type: 'string' },\n },\n required: [],\n },\n execute: async (args: unknown) => {\n const a = (args as { status?: string, team_id?: string }) ?? {}\n const argv = ['list', '--json']\n if (a.status) argv.push('--status', a.status)\n if (a.team_id) argv.push('--team', a.team_id)\n const out = ape(argv)\n try { return JSON.parse(out) }\n catch { return { raw: out } }\n },\n },\n {\n name: 'tasks.create',\n description: 'Create a new ape-task on the owner\\'s task list at tasks.openape.ai.',\n parameters: {\n type: 'object',\n properties: {\n title: { type: 'string' },\n notes: { type: 'string' },\n priority: { type: 'string', enum: ['low', 'med', 'high'] },\n due_at: { type: 'string', description: 'ISO date or +Nh/+Nd shorthand.' },\n },\n required: ['title'],\n },\n execute: async (args: unknown) => {\n const a = args as { title: string, notes?: string, priority?: string, due_at?: string }\n const argv = ['new', '--title', a.title, '--json']\n if (a.notes) argv.push('--notes', a.notes)\n if (a.priority) argv.push('--priority', a.priority)\n if (a.due_at) argv.push('--due', a.due_at)\n const out = ape(argv)\n try { return JSON.parse(out) }\n catch { return { raw: out } }\n },\n },\n]\n","import type { ToolDefinition } from './index'\n\nexport const timeTools: ToolDefinition[] = [\n {\n name: 'time.now',\n description: 'Returns the current UTC date and time as ISO 8601 plus epoch seconds. No inputs.',\n parameters: { type: 'object', properties: {}, required: [] },\n execute: async () => {\n const now = new Date()\n return {\n iso: now.toISOString(),\n epoch_seconds: Math.floor(now.getTime() / 1000),\n timezone_offset_minutes: -now.getTimezoneOffset(),\n }\n },\n },\n]\n","// Built-in tool registry shipped with the apes binary. Each tool is\n// (a) an OpenAI tool-spec object (used as-is in the LiteLLM call's\n// `tools[]` parameter) and (b) an `execute` function called when the\n// model emits a `tool_calls` entry. Adding a new tool means\n// implementing it here AND adding an entry to\n// apps/openape-troop/server/tool-catalog.json so the SP validates\n// task specs against the same allowlist.\n//\n// The registry is keyed by string name (`time.now`, `http.get`, …).\n// `taskTools(spec.tools)` resolves a task's tool list to the slice of\n// the registry it can use. Unknown names abort the run before any\n// LLM call so the model can't invent a tool we haven't shipped.\n\nimport { bashTools } from './bash'\nimport { fileTools } from './file'\nimport { httpTools } from './http'\nimport { mailTools } from './mail'\nimport { tasksTools } from './tasks'\nimport { timeTools } from './time'\n\nexport interface ToolDefinition {\n name: string\n description: string\n // OpenAI tool-spec shape: { type: 'object', properties: …, required: … }.\n // We don't constrain it further — the LLM's job to fill it in.\n parameters: Record<string, unknown>\n execute: (args: unknown) => Promise<unknown>\n}\n\nconst ALL_TOOLS: ToolDefinition[] = [\n ...timeTools,\n ...httpTools,\n ...fileTools,\n ...tasksTools,\n ...mailTools,\n ...bashTools,\n]\n\nexport const TOOLS: Record<string, ToolDefinition> = Object.fromEntries(\n ALL_TOOLS.map(t => [t.name, t]),\n)\n\n/**\n * Resolve a task spec's tool name list to ToolDefinitions. Throws on\n * unknown names — callers must surface that as a run-failure with a\n * clear \"unknown tool: foo\" final_message so the owner can see what\n * went wrong in the SP UI.\n */\nexport function taskTools(names: string[]): ToolDefinition[] {\n const out: ToolDefinition[] = []\n const missing: string[] = []\n for (const name of names) {\n const tool = TOOLS[name]\n if (!tool) missing.push(name)\n else out.push(tool)\n }\n if (missing.length > 0) {\n throw new Error(`unknown tool(s): ${missing.join(', ')}`)\n }\n return out\n}\n\n/**\n * Format the registry slice as the OpenAI `tools` param. Strips the\n * `execute` function — only the spec part goes to the LLM.\n *\n * Tool names get sent through `wireToolName()` because some upstreams\n * — notably ChatGPT's Responses API behind LiteLLM — enforce\n * `^[a-zA-Z0-9_-]+$` and reject our dotted catalog names like\n * `time.now`. Use `localToolName()` to map back when the model emits\n * a tool_call.\n */\nexport function asOpenAiTools(tools: ToolDefinition[]): { type: 'function', function: Omit<ToolDefinition, 'execute'> }[] {\n return tools.map(t => ({\n type: 'function' as const,\n function: { name: wireToolName(t.name), description: t.description, parameters: t.parameters },\n }))\n}\n\n/** Encode a local tool name (e.g. `time.now`) for the wire format. */\nexport function wireToolName(local: string): string {\n return local.replace(/\\./g, '_')\n}\n\n/**\n * Decode a tool name from the wire format back to its local form. The\n * encoding is `.` → `_`; we recover the original by looking for an\n * exact match in the catalog and only fall back to passing the name\n * through if no match is found (so unknown tool names still surface\n * as the obvious \"unknown tool\" error rather than silent rewrites).\n */\nexport function localToolName(wire: string): string {\n for (const t of Object.values(TOOLS)) {\n if (wireToolName(t.name) === wire) return t.name\n }\n return wire\n}\n","import { asOpenAiTools, localToolName, wireToolName } from './agent-tools'\nimport type { ToolDefinition } from './agent-tools'\n\n// Shared agent loop: send messages + tools to LiteLLM (OpenAI-\n// compatible chat-completions API), execute any tool_calls in the\n// response, append tool-result messages, loop until the model\n// returns a response with no tool_calls or we hit max_steps.\n//\n// Both `apes agents run` (cron) and `apes agents serve --rpc`\n// (chat-bridge subprocess) call into here so the LLM behaviour is\n// guaranteed identical between modes.\n\nexport interface ChatMessage {\n role: 'system' | 'user' | 'assistant' | 'tool'\n content: string | null\n // Assistant message tool calls\n tool_calls?: Array<{\n id: string\n type: 'function'\n function: { name: string, arguments: string }\n }>\n // Tool message metadata\n tool_call_id?: string\n name?: string\n}\n\nexport interface RuntimeConfig {\n apiBase: string // LITELLM_BASE_URL (e.g. \"http://127.0.0.1:4000/v1\")\n apiKey: string // LITELLM_API_KEY (or LITELLM_MASTER_KEY)\n model: string\n}\n\nexport interface TraceEntry {\n step: number\n type: 'assistant' | 'tool_call' | 'tool_result' | 'tool_error'\n // Stripped down for trace: don't carry full bodies\n preview: string\n tool?: string\n}\n\nexport interface RunResult {\n status: 'ok' | 'error'\n finalMessage: string | null\n stepCount: number\n trace: TraceEntry[]\n}\n\nexport interface RunStreamHandlers {\n onTextDelta?: (delta: string) => void\n onToolCall?: (call: { name: string, args: unknown }) => void\n onToolResult?: (result: { name: string, result: unknown }) => void\n onToolError?: (err: { name: string, error: string }) => void\n onDone?: (result: RunResult) => void\n}\n\nexport interface RunOptions {\n config: RuntimeConfig\n systemPrompt: string\n userMessage: string\n tools: ToolDefinition[]\n maxSteps: number\n // Pre-existing message history for continued sessions (RPC mode).\n // The system prompt is always prepended, even if `history` is\n // non-empty — the SP-stored prompt is canonical. Defaults to []\n // (a fresh single-user-turn conversation).\n history?: ChatMessage[]\n handlers?: RunStreamHandlers\n // Test seam — replace fetch (we always use the global fetch in\n // production). Tests pass a mock that returns canned responses.\n fetchImpl?: typeof fetch\n}\n\ninterface OpenAIChoice {\n message: ChatMessage\n finish_reason?: string\n}\ninterface OpenAIChatResponse {\n choices: OpenAIChoice[]\n}\n\nfunction previewJson(value: unknown, max = 500): string {\n let s: string\n try { s = JSON.stringify(value) }\n catch { s = String(value) }\n return s.length > max ? `${s.slice(0, max)}…` : s\n}\n\nexport async function runLoop(opts: RunOptions): Promise<RunResult> {\n const fetchFn = opts.fetchImpl ?? fetch\n const trace: TraceEntry[] = []\n const messages: ChatMessage[] = [\n { role: 'system', content: opts.systemPrompt },\n ...(opts.history ?? []),\n { role: 'user', content: opts.userMessage },\n ]\n const tools = asOpenAiTools(opts.tools)\n\n for (let step = 1; step <= opts.maxSteps; step++) {\n const res = await fetchFn(`${opts.config.apiBase}/chat/completions`, {\n method: 'POST',\n headers: {\n 'authorization': `Bearer ${opts.config.apiKey}`,\n 'content-type': 'application/json',\n },\n body: JSON.stringify({\n model: opts.config.model,\n messages,\n ...(tools.length > 0 ? { tools, tool_choice: 'auto' } : {}),\n }),\n })\n if (!res.ok) {\n const text = await res.text().catch(() => '')\n throw new Error(`LiteLLM ${res.status}: ${text.slice(0, 500)}`)\n }\n const data = await res.json() as OpenAIChatResponse\n const choice = data.choices?.[0]\n if (!choice) throw new Error('LiteLLM response had no choices')\n\n const assistant = choice.message\n messages.push(assistant)\n if (assistant.content) opts.handlers?.onTextDelta?.(assistant.content)\n\n trace.push({\n step,\n type: 'assistant',\n preview: previewJson({ content: assistant.content, tool_calls: assistant.tool_calls?.length ?? 0 }),\n })\n\n if (!assistant.tool_calls || assistant.tool_calls.length === 0) {\n const result: RunResult = {\n status: 'ok',\n finalMessage: assistant.content,\n stepCount: step,\n trace,\n }\n opts.handlers?.onDone?.(result)\n return result\n }\n\n // Execute each tool call; the model sees the result on the next turn.\n // Wire-format names (`time_now`) get decoded to local catalog names\n // (`time.now`) for lookup + handler events; we send back the same\n // wire name in the tool message so the next request validates.\n for (const call of assistant.tool_calls) {\n const wireName = call.function.name\n const localName = localToolName(wireName)\n const tool = opts.tools.find(t => t.name === localName)\n let parsedArgs: unknown\n try { parsedArgs = JSON.parse(call.function.arguments) }\n catch { parsedArgs = {} }\n opts.handlers?.onToolCall?.({ name: localName, args: parsedArgs })\n trace.push({ step, type: 'tool_call', tool: localName, preview: previewJson(parsedArgs) })\n\n let result: unknown\n let isError = false\n if (!tool) {\n result = `unknown tool: ${localName}`\n isError = true\n }\n else {\n try {\n result = await tool.execute(parsedArgs)\n }\n catch (err) {\n result = (err as Error)?.message ?? String(err)\n isError = true\n }\n }\n\n if (isError) {\n opts.handlers?.onToolError?.({ name: localName, error: String(result) })\n trace.push({ step, type: 'tool_error', tool: localName, preview: previewJson(result) })\n }\n else {\n opts.handlers?.onToolResult?.({ name: localName, result })\n trace.push({ step, type: 'tool_result', tool: localName, preview: previewJson(result) })\n }\n\n messages.push({\n role: 'tool',\n tool_call_id: call.id,\n name: wireToolName(localName),\n content: typeof result === 'string' ? result : JSON.stringify(result),\n })\n }\n }\n\n // Loop fell through max_steps without a no-tool-calls reply.\n const result: RunResult = {\n status: 'error',\n finalMessage: `max_steps (${opts.maxSteps}) reached without completion`,\n stepCount: opts.maxSteps,\n trace,\n }\n opts.handlers?.onDone?.(result)\n return result\n}\n\nexport interface RpcSession {\n messages: ChatMessage[]\n systemPrompt: string\n tools: ToolDefinition[]\n maxSteps: number\n lastTouched: number\n}\n\nconst RPC_SESSION_TTL_MS = 60 * 60 * 1000\n\nexport class RpcSessionMap {\n private sessions = new Map<string, RpcSession>()\n\n get(id: string): RpcSession | undefined {\n const s = this.sessions.get(id)\n if (s) s.lastTouched = Date.now()\n return s\n }\n\n put(id: string, s: RpcSession): void {\n s.lastTouched = Date.now()\n this.sessions.set(id, s)\n }\n\n evictStale(): void {\n const cutoff = Date.now() - RPC_SESSION_TTL_MS\n for (const [k, v] of this.sessions) {\n if (v.lastTouched < cutoff) this.sessions.delete(k)\n }\n }\n\n size(): number {\n return this.sessions.size\n }\n}\n"],"mappings":"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","names":["MAX_BYTES","execFileSync","result"]}