@alook/app 0.0.68 → 0.0.69
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/bundled/email-worker/index.js +9 -1
- package/bundled/web/.open-next/.build/durable-objects/queue.js +5 -5
- package/bundled/web/.open-next/assets/BUILD_ID +1 -1
- package/bundled/web/.open-next/assets/_next/static/chunks/0.vi-1cgnne0e.css +1 -0
- package/bundled/web/.open-next/assets/_next/static/chunks/{0vob59t.w8yuf.js → 060hy1yx9.8u4.js} +2 -2
- package/bundled/web/.open-next/assets/_next/static/chunks/08u3p1v8e1gxg.js +63 -0
- package/bundled/web/.open-next/assets/_next/static/chunks/{13fkss9rmgdqw.js → 0fmcohc0raah~.js} +22 -22
- package/bundled/web/.open-next/assets/_next/static/chunks/0or01.vb5l38_.js +3 -0
- package/bundled/web/.open-next/assets/_next/static/chunks/0vb-n1ns9b-93.js +1 -0
- package/bundled/web/.open-next/assets/_next/static/chunks/0ymb~iviyjlb_.js +3 -0
- package/bundled/web/.open-next/assets/_next/static/chunks/13lom177x6744.js +1 -0
- package/bundled/web/.open-next/cache/{d6FEqIY-HdfUib-E9v9dt → -lo-xBQBmp_D6Nf6J9S7y}/_global-error.cache +1 -1
- package/bundled/web/.open-next/cache/{d6FEqIY-HdfUib-E9v9dt → -lo-xBQBmp_D6Nf6J9S7y}/_not-found.cache +1 -1
- package/bundled/web/.open-next/cache/{d6FEqIY-HdfUib-E9v9dt → -lo-xBQBmp_D6Nf6J9S7y}/sitemap.xml.cache +1 -1
- package/bundled/web/.open-next/cloudflare/cache-assets-manifest.sql +1 -1
- package/bundled/web/.open-next/cloudflare/init.js +1 -1
- package/bundled/web/.open-next/dynamodb-provider/dynamodb-cache.json +1 -1
- package/bundled/web/.open-next/middleware/handler.mjs +3 -3
- package/bundled/web/.open-next/server-functions/default/src/web/.next/BUILD_ID +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/build-manifest.json +3 -3
- package/bundled/web/.open-next/server-functions/default/src/web/.next/prerender-manifest.json +3 -3
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/(app)/invite/[token]/page_client-reference-manifest.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/(app)/studio/new/page_client-reference-manifest.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/(app)/w/[slug]/agents/[id]/activity/page_client-reference-manifest.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/(app)/w/[slug]/agents/[id]/chat/[convId]/page_client-reference-manifest.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/(app)/w/[slug]/agents/[id]/chat/page_client-reference-manifest.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/(app)/w/[slug]/agents/[id]/email/page_client-reference-manifest.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/(app)/w/[slug]/agents/[id]/files/page_client-reference-manifest.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/(app)/w/[slug]/agents/[id]/meetings/page_client-reference-manifest.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/(app)/w/[slug]/agents/[id]/page_client-reference-manifest.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/(app)/w/[slug]/agents/new/page_client-reference-manifest.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/(app)/w/[slug]/agents/page_client-reference-manifest.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/(app)/w/[slug]/calendar/page_client-reference-manifest.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/(app)/w/[slug]/flags/page_client-reference-manifest.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/(app)/w/[slug]/help/email-setup/page_client-reference-manifest.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/(app)/w/[slug]/home/page_client-reference-manifest.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/(app)/w/[slug]/issues/page_client-reference-manifest.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/(app)/w/[slug]/runtimes/page_client-reference-manifest.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/(app)/w/[slug]/settings/page_client-reference-manifest.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/(app)/w/[slug]/threads/[traceId]/page_client-reference-manifest.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/(app)/w/[slug]/threads/page_client-reference-manifest.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/(app)/w/[slug]/unread/page_client-reference-manifest.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/(app)/workspaces/page_client-reference-manifest.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/(auth)/sign-in/page_client-reference-manifest.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/agent-links/[id]/route.js +2 -2
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/agent-links/route.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/agents/[id]/access/[userId]/route.js +3 -3
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/agents/[id]/access/route.js +3 -3
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/agents/[id]/active-tasks/route.js +3 -3
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/agents/[id]/activity/route.js +2 -2
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/agents/[id]/chat-init/route.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/agents/[id]/conversation/route.js +2 -2
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/agents/[id]/conversations/route.js +2 -2
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/agents/[id]/email-accounts/[accountId]/route.js +2 -2
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/agents/[id]/email-accounts/[accountId]/sync/route.js +3 -3
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/agents/[id]/email-accounts/[accountId]/test/route.js +3 -3
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/agents/[id]/email-accounts/route.js +2 -2
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/agents/[id]/meetings/[meetingId]/approve/route.js +2 -2
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/agents/[id]/meetings/[meetingId]/route.js +2 -2
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/agents/[id]/meetings/[meetingId]/stop/route.js +2 -2
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/agents/[id]/meetings/route.js +2 -2
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/agents/[id]/pin/route.js +3 -3
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/agents/[id]/route.js +2 -2
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/agents/[id]/whitelist/[whitelistId]/route.js +3 -3
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/agents/[id]/whitelist/route.js +4 -4
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/agents/[id]/workspace/browse/route.js +3 -3
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/agents/active-task-counts/route.js +3 -3
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/agents/active-tasks/route.js +6 -6
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/agents/pins/reorder/route.js +3 -3
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/agents/pins/route.js +3 -3
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/agents/route.js +6 -6
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/agents/sidebar/reorder/route.js +3 -3
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/artifacts/[id]/content/route.js +3 -3
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/artifacts/[id]/route.js +3 -3
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/artifacts/route.js +3 -3
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/artifacts/upload/route.js +3 -3
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/calendar/[id]/route.js +4 -4
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/calendar/route.js +3 -3
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/channels/[id]/route.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/channels/reorder/route.js +3 -3
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/channels/route.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/config/min-version/route.js +3 -3
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/config/model-options/route.js +3 -3
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/conversations/[id]/active-task/route.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/conversations/[id]/buffered-messages/[messageId]/route.js +3 -3
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/conversations/[id]/buffered-messages/route.js +4 -4
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/conversations/[id]/messages/route.js +3 -3
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/conversations/[id]/route.js +2 -2
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/conversations/route.js +2 -2
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/daemon/deregister/route.js +2 -2
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/daemon/register/route.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/daemon/tasks/[taskId]/complete/route.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/daemon/tasks/[taskId]/fail/route.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/daemon/tasks/[taskId]/messages/route.js +3 -3
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/daemon/tasks/[taskId]/progress/route.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/daemon/tasks/[taskId]/start/route.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/daemon/tasks/[taskId]/status/route.js +3 -3
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/daemon/tasks/[taskId]/supersede/route.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/daemon/tasks/poll/route.js +3 -3
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/daemon/workspace/report/route.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/email/[id]/attachment/[index]/route.js +4 -4
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/email/[id]/body/route.js +4 -4
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/email/[id]/raw/route.js +3 -3
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/email/[id]/route.js +2 -2
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/email/[id]/thread/route.js +2 -2
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/email/notify/route.js +2 -2
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/email/route.js +2 -2
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/email/send/route.js +3 -3
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/email/upload/route.js +4 -4
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/flags/[messageId]/route.js +4 -4
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/flags/count/route.js +3 -3
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/flags/route.js +2 -2
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/inbox/count/route.js +3 -3
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/inbox/read/route.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/inbox/read-all/route.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/inbox/route.js +2 -2
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/invite/[token]/route.js +3 -3
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/issues/[id]/comments/route.js +4 -4
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/issues/[id]/route.js +2 -2
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/issues/route.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/machine-tokens/[id]/route.js +3 -3
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/machine-tokens/activate/route.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/machine-tokens/route.js +3 -3
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/me/route.js +5 -5
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/meeting/callback/route.js +3 -3
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/members/me/route.js +3 -3
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/runtimes/[runtimeId]/rescan/route.js +3 -3
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/runtimes/[runtimeId]/update/route.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/runtimes/machine/route.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/runtimes/route.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/studios/check-handles/route.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/studios/check-name/route.js +3 -3
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/studios/route.js +4 -4
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/tasks/[id]/messages/route.js +2 -2
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/tasks/[id]/retry/route.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/tasks/[id]/route.js +2 -2
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/tasks/step-counts/route.js +3 -3
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/traces/[traceId]/route.js +6 -6
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/traces/route.js +6 -6
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/workspaces/[id]/invites/[inviteId]/route.js +3 -3
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/workspaces/[id]/invites/route.js +2 -2
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/workspaces/[id]/members/[memberId]/route.js +3 -3
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/workspaces/[id]/members/route.js +2 -2
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/workspaces/[id]/overview/route.js +6 -6
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/workspaces/[id]/route.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/api/workspaces/route.js +3 -3
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/page_client-reference-manifest.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/templates/[id]/page_client-reference-manifest.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/app/templates/page_client-reference-manifest.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/chunks/[root-of-the-server]__0.1vmoe._.js +85 -0
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/chunks/[root-of-the-server]__009_wu3._.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/chunks/[root-of-the-server]__01crkpf._.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/chunks/[root-of-the-server]__01~--ue._.js +3 -0
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/chunks/{[root-of-the-server]__0~4t--s._.js → [root-of-the-server]__027mioa._.js} +8 -8
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/chunks/[root-of-the-server]__09cer93._.js +3 -0
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/chunks/[root-of-the-server]__0dy~25_._.js +3 -0
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/chunks/[root-of-the-server]__0faxj6t._.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/chunks/[root-of-the-server]__0k4l9.h._.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/chunks/[root-of-the-server]__0m5a1_f._.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/chunks/[root-of-the-server]__0m_ya4f._.js +3 -0
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/chunks/[root-of-the-server]__0niqi98._.js +85 -0
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/chunks/[root-of-the-server]__0ntc1ld._.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/chunks/[root-of-the-server]__0ud56v.._.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/chunks/[turbopack]_runtime.js +16 -16
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/chunks/_0266t8u._.js +2 -2
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/chunks/_02t7kem._.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/chunks/_06imn.m._.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/chunks/_07-798_._.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/chunks/_08xh_f8._.js +2 -2
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/chunks/_09j_99x._.js +2 -2
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/chunks/_0c9~wb-._.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/chunks/_0exm5_w._.js +2 -2
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/chunks/_0gqpz7w._.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/chunks/_0j1t6f2._.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/chunks/_0k3wl-3._.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/chunks/_0l1m9wf._.js +2 -2
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/chunks/_0lmedw9._.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/chunks/_0ow_nxn._.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/chunks/_0piy2kq._.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/chunks/_0r0h9so._.js +3 -3
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/chunks/_0s4qnoj._.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/chunks/_0sj8mn_._.js +3 -0
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/chunks/_0s~hhx-._.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/chunks/_0tarogd._.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/chunks/_0tcd35h._.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/chunks/_0u3tu2x._.js +2 -2
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/chunks/_0w6dw.t._.js +2 -2
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/chunks/_0y3~9l1._.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/chunks/_0z.s70x._.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/chunks/_122y7jv._.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/chunks/src_0q3gvkd._.js +3 -0
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/chunks/ssr/{[root-of-the-server]__0ppv-5d._.js → [root-of-the-server]__099etsu._.js} +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/chunks/ssr/[root-of-the-server]__0r2s1aj._.js +3 -0
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/chunks/ssr/[root-of-the-server]__0xzp3_y._.js +3 -0
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/chunks/ssr/[turbopack]_runtime.js +16 -16
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/chunks/ssr/_063q-hj._.js +2 -2
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/chunks/ssr/{_0gm0lpi._.js → _091qk6t._.js} +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/chunks/ssr/_09b8fgg._.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/chunks/ssr/{_0e7_cpm._.js → _0xcw94t._.js} +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/chunks/ssr/_11~t2ti._.js +3 -0
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/chunks/ssr/_12e18r-._.js +2 -2
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/chunks/ssr/_13__3im._.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/chunks/ssr/_13cbsrm._.js +65 -0
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/chunks/ssr/src_web_src_0~23g-y._.js +2 -2
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/chunks/ssr/src_web_src_app_(app)_w_[slug]_home_page_tsx_0lkx78f._.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/middleware-build-manifest.js +3 -3
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/middleware-manifest.json +5 -5
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/server-reference-manifest.js +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/server-reference-manifest.json +1 -1
- package/bundled/web/.open-next/server-functions/default/src/web/handler.mjs +68 -150
- package/bundled/web/.open-next/server-functions/default/src/web/handler.mjs.meta.json +302 -338
- package/bundled/web/.open-next/server-functions/default/src/web/index.mjs +3 -3
- package/bundled/web/wrangler.toml +1 -1
- package/bundled/ws-do/index.js +9 -1
- package/dist/cli/index.js +2424 -426
- package/dist/cli/session-runner.js +183 -33
- package/package.json +1 -1
- package/bundled/web/.open-next/assets/_next/static/chunks/02xsv1h-9twmr.js +0 -3
- package/bundled/web/.open-next/assets/_next/static/chunks/085gv~jsjhgvo.js +0 -3
- package/bundled/web/.open-next/assets/_next/static/chunks/0csp6ncjhvojv.js +0 -63
- package/bundled/web/.open-next/assets/_next/static/chunks/0lf.3fdr8qzrz.css +0 -1
- package/bundled/web/.open-next/assets/_next/static/chunks/0u1.3sr-iag4j.js +0 -1
- package/bundled/web/.open-next/assets/_next/static/chunks/0v0kh80r40w4f.js +0 -1
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/chunks/[root-of-the-server]__0axs-gp._.js +0 -3
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/chunks/[root-of-the-server]__0kvsfuj._.js +0 -85
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/chunks/[root-of-the-server]__0l3u9pl._.js +0 -85
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/chunks/[root-of-the-server]__0lvfzvr._.js +0 -3
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/chunks/[root-of-the-server]__0n_hjiv._.js +0 -3
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/chunks/[root-of-the-server]__0ug4a08._.js +0 -85
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/chunks/[root-of-the-server]__0~jiu74._.js +0 -3
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/chunks/_0jx_8bw._.js +0 -3
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/chunks/src_0-jf-76._.js +0 -3
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/chunks/ssr/[root-of-the-server]__0-uhgif._.js +0 -3
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/chunks/ssr/[root-of-the-server]__0oe.0yf._.js +0 -3
- package/bundled/web/.open-next/server-functions/default/src/web/.next/server/chunks/ssr/_0ohl~vp._.js +0 -65
- /package/bundled/web/.open-next/assets/_next/static/{d6FEqIY-HdfUib-E9v9dt → -lo-xBQBmp_D6Nf6J9S7y}/_buildManifest.js +0 -0
- /package/bundled/web/.open-next/assets/_next/static/{d6FEqIY-HdfUib-E9v9dt → -lo-xBQBmp_D6Nf6J9S7y}/_clientMiddlewareManifest.js +0 -0
- /package/bundled/web/.open-next/assets/_next/static/{d6FEqIY-HdfUib-E9v9dt → -lo-xBQBmp_D6Nf6J9S7y}/_ssgManifest.js +0 -0
- /package/bundled/web/.open-next/cache/{d6FEqIY-HdfUib-E9v9dt → -lo-xBQBmp_D6Nf6J9S7y}/robots.txt.cache +0 -0
package/dist/cli/index.js
CHANGED
|
@@ -16820,6 +16820,9 @@ var RESERVED_HANDLES = new Set([
|
|
|
16820
16820
|
"system",
|
|
16821
16821
|
"alook"
|
|
16822
16822
|
]);
|
|
16823
|
+
function toAlookAddress(h) {
|
|
16824
|
+
return `${h}${DOMAIN}`;
|
|
16825
|
+
}
|
|
16823
16826
|
// ../shared/src/semver.ts
|
|
16824
16827
|
function semverGte(a, b) {
|
|
16825
16828
|
const pa = a.split(".").map(Number);
|
|
@@ -17292,8 +17295,8 @@ function statusCommand() {
|
|
|
17292
17295
|
|
|
17293
17296
|
// commands/daemon.ts
|
|
17294
17297
|
import { Command as Command3 } from "commander";
|
|
17295
|
-
import { spawn as
|
|
17296
|
-
import { openSync as openSync2, closeSync as closeSync2, mkdirSync as
|
|
17298
|
+
import { spawn as spawn6 } from "child_process";
|
|
17299
|
+
import { openSync as openSync2, closeSync as closeSync2, mkdirSync as mkdirSync8 } from "fs";
|
|
17297
17300
|
import { dirname as dirname4 } from "path";
|
|
17298
17301
|
|
|
17299
17302
|
// daemon/client.ts
|
|
@@ -17431,72 +17434,2176 @@ function createHealthServer(port = DEFAULT_HEALTH_PORT) {
|
|
|
17431
17434
|
setRuntimeCount(n) {
|
|
17432
17435
|
runtimeCount = n;
|
|
17433
17436
|
}
|
|
17434
|
-
};
|
|
17437
|
+
};
|
|
17438
|
+
}
|
|
17439
|
+
|
|
17440
|
+
// daemon/agent/claude.ts
|
|
17441
|
+
import { spawn } from "child_process";
|
|
17442
|
+
import { createInterface } from "readline";
|
|
17443
|
+
|
|
17444
|
+
class ClaudeBackend {
|
|
17445
|
+
cliPath;
|
|
17446
|
+
name = "claude";
|
|
17447
|
+
constructor(cliPath) {
|
|
17448
|
+
this.cliPath = cliPath;
|
|
17449
|
+
}
|
|
17450
|
+
execute(prompt, options) {
|
|
17451
|
+
const args = [
|
|
17452
|
+
"-p",
|
|
17453
|
+
prompt,
|
|
17454
|
+
"--output-format",
|
|
17455
|
+
"stream-json",
|
|
17456
|
+
"--verbose",
|
|
17457
|
+
"--permission-mode",
|
|
17458
|
+
"bypassPermissions"
|
|
17459
|
+
];
|
|
17460
|
+
if (options.model) {
|
|
17461
|
+
args.push("--model", options.model);
|
|
17462
|
+
}
|
|
17463
|
+
if (options.maxTurns) {
|
|
17464
|
+
args.push("--max-turns", String(options.maxTurns));
|
|
17465
|
+
}
|
|
17466
|
+
if (options.resumeSessionId) {
|
|
17467
|
+
args.push("--resume", options.resumeSessionId);
|
|
17468
|
+
}
|
|
17469
|
+
const proc = spawn(this.cliPath, args, {
|
|
17470
|
+
cwd: options.cwd,
|
|
17471
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
17472
|
+
env: { ...process.env, ...options.env }
|
|
17473
|
+
});
|
|
17474
|
+
if (!proc.pid) {
|
|
17475
|
+
const error51 = `Failed to start ${this.cliPath}: binary not found or not executable. Is 'claude' installed and on PATH?`;
|
|
17476
|
+
const failedResult = { status: "failed", output: "", error: error51, durationMs: 0, sessionId: "" };
|
|
17477
|
+
const emptyMessages = { [Symbol.asyncIterator]() {
|
|
17478
|
+
return { async next() {
|
|
17479
|
+
return { value: undefined, done: true };
|
|
17480
|
+
} };
|
|
17481
|
+
} };
|
|
17482
|
+
return { pid: undefined, messages: emptyMessages, sessionId: Promise.resolve(""), result: Promise.resolve(failedResult) };
|
|
17483
|
+
}
|
|
17484
|
+
let timedOut = false;
|
|
17485
|
+
let timeoutTimer;
|
|
17486
|
+
if (options.timeout) {
|
|
17487
|
+
timeoutTimer = setTimeout(() => {
|
|
17488
|
+
timedOut = true;
|
|
17489
|
+
proc.kill("SIGTERM");
|
|
17490
|
+
}, options.timeout);
|
|
17491
|
+
}
|
|
17492
|
+
const startTime = Date.now();
|
|
17493
|
+
let lastSessionId = "";
|
|
17494
|
+
let lastOutput = "";
|
|
17495
|
+
let lastError = "";
|
|
17496
|
+
let resultStatus = "completed";
|
|
17497
|
+
let resolveSessionId;
|
|
17498
|
+
const sessionIdPromise = new Promise((resolve) => {
|
|
17499
|
+
resolveSessionId = resolve;
|
|
17500
|
+
});
|
|
17501
|
+
const messageQueue = [];
|
|
17502
|
+
let messageResolve = null;
|
|
17503
|
+
let messageDone = false;
|
|
17504
|
+
const pushMessage = (msg) => {
|
|
17505
|
+
messageQueue.push(msg);
|
|
17506
|
+
if (messageResolve) {
|
|
17507
|
+
const r = messageResolve;
|
|
17508
|
+
messageResolve = null;
|
|
17509
|
+
r();
|
|
17510
|
+
}
|
|
17511
|
+
};
|
|
17512
|
+
const resultPromise = new Promise((resolve) => {
|
|
17513
|
+
const stderrChunks = [];
|
|
17514
|
+
proc.stderr?.on("data", (chunk) => {
|
|
17515
|
+
stderrChunks.push(chunk.toString());
|
|
17516
|
+
});
|
|
17517
|
+
const rl = createInterface({ input: proc.stdout });
|
|
17518
|
+
rl.on("line", (line) => {
|
|
17519
|
+
if (!line.trim())
|
|
17520
|
+
return;
|
|
17521
|
+
let event;
|
|
17522
|
+
try {
|
|
17523
|
+
event = JSON.parse(line);
|
|
17524
|
+
} catch {
|
|
17525
|
+
pushMessage({ type: "log", content: line, level: "debug" });
|
|
17526
|
+
return;
|
|
17527
|
+
}
|
|
17528
|
+
const eventType = event.type;
|
|
17529
|
+
switch (eventType) {
|
|
17530
|
+
case "assistant": {
|
|
17531
|
+
const message2 = event.message;
|
|
17532
|
+
if (!message2)
|
|
17533
|
+
break;
|
|
17534
|
+
const content = message2.content;
|
|
17535
|
+
if (!Array.isArray(content))
|
|
17536
|
+
break;
|
|
17537
|
+
for (const block of content) {
|
|
17538
|
+
if (block.type === "text") {
|
|
17539
|
+
lastOutput = block.text || "";
|
|
17540
|
+
pushMessage({ type: "text", content: block.text });
|
|
17541
|
+
} else if (block.type === "thinking") {
|
|
17542
|
+
pushMessage({ type: "thinking", content: block.text });
|
|
17543
|
+
} else if (block.type === "tool_use") {
|
|
17544
|
+
pushMessage({
|
|
17545
|
+
type: "tool-use",
|
|
17546
|
+
tool: block.name,
|
|
17547
|
+
callId: block.id,
|
|
17548
|
+
input: block.input
|
|
17549
|
+
});
|
|
17550
|
+
}
|
|
17551
|
+
}
|
|
17552
|
+
break;
|
|
17553
|
+
}
|
|
17554
|
+
case "result": {
|
|
17555
|
+
const result = event.result;
|
|
17556
|
+
const sessionId = event.session_id;
|
|
17557
|
+
if (result)
|
|
17558
|
+
lastOutput = result;
|
|
17559
|
+
if (sessionId)
|
|
17560
|
+
lastSessionId = sessionId;
|
|
17561
|
+
const isError = event.is_error;
|
|
17562
|
+
if (isError) {
|
|
17563
|
+
resultStatus = "failed";
|
|
17564
|
+
lastError = result || "unknown error";
|
|
17565
|
+
}
|
|
17566
|
+
break;
|
|
17567
|
+
}
|
|
17568
|
+
case "tool_result": {
|
|
17569
|
+
const content = event.content;
|
|
17570
|
+
const toolUseId = event.tool_use_id;
|
|
17571
|
+
pushMessage({
|
|
17572
|
+
type: "tool-result",
|
|
17573
|
+
callId: toolUseId,
|
|
17574
|
+
output: content
|
|
17575
|
+
});
|
|
17576
|
+
break;
|
|
17577
|
+
}
|
|
17578
|
+
case "system": {
|
|
17579
|
+
const subtype = event.subtype;
|
|
17580
|
+
if (subtype === "init") {
|
|
17581
|
+
const sid = event.session_id;
|
|
17582
|
+
if (sid) {
|
|
17583
|
+
lastSessionId = sid;
|
|
17584
|
+
resolveSessionId(sid);
|
|
17585
|
+
}
|
|
17586
|
+
}
|
|
17587
|
+
break;
|
|
17588
|
+
}
|
|
17589
|
+
case "control_request": {
|
|
17590
|
+
handleControlRequest(proc, event);
|
|
17591
|
+
break;
|
|
17592
|
+
}
|
|
17593
|
+
default: {
|
|
17594
|
+
pushMessage({
|
|
17595
|
+
type: "log",
|
|
17596
|
+
content: line,
|
|
17597
|
+
level: "debug"
|
|
17598
|
+
});
|
|
17599
|
+
}
|
|
17600
|
+
}
|
|
17601
|
+
});
|
|
17602
|
+
proc.on("error", (err) => {
|
|
17603
|
+
resultStatus = "failed";
|
|
17604
|
+
lastError = `spawn error: ${err.message}`;
|
|
17605
|
+
resolveSessionId(lastSessionId);
|
|
17606
|
+
messageDone = true;
|
|
17607
|
+
if (messageResolve) {
|
|
17608
|
+
const r = messageResolve;
|
|
17609
|
+
messageResolve = null;
|
|
17610
|
+
r();
|
|
17611
|
+
}
|
|
17612
|
+
resolve({
|
|
17613
|
+
status: "failed",
|
|
17614
|
+
output: "",
|
|
17615
|
+
error: lastError,
|
|
17616
|
+
durationMs: Date.now() - startTime,
|
|
17617
|
+
sessionId: lastSessionId
|
|
17618
|
+
});
|
|
17619
|
+
});
|
|
17620
|
+
proc.on("close", (code) => {
|
|
17621
|
+
if (timeoutTimer)
|
|
17622
|
+
clearTimeout(timeoutTimer);
|
|
17623
|
+
if (timedOut) {
|
|
17624
|
+
resultStatus = "timeout";
|
|
17625
|
+
} else if (code !== 0 && resultStatus === "completed") {
|
|
17626
|
+
resultStatus = "failed";
|
|
17627
|
+
}
|
|
17628
|
+
const stderr = stderrChunks.join("");
|
|
17629
|
+
if (stderr && !lastError) {
|
|
17630
|
+
lastError = stderr;
|
|
17631
|
+
}
|
|
17632
|
+
resolveSessionId(lastSessionId);
|
|
17633
|
+
messageDone = true;
|
|
17634
|
+
if (messageResolve) {
|
|
17635
|
+
const r = messageResolve;
|
|
17636
|
+
messageResolve = null;
|
|
17637
|
+
r();
|
|
17638
|
+
}
|
|
17639
|
+
resolve({
|
|
17640
|
+
status: resultStatus,
|
|
17641
|
+
output: lastOutput,
|
|
17642
|
+
error: lastError,
|
|
17643
|
+
durationMs: Date.now() - startTime,
|
|
17644
|
+
sessionId: lastSessionId
|
|
17645
|
+
});
|
|
17646
|
+
});
|
|
17647
|
+
});
|
|
17648
|
+
const messages = {
|
|
17649
|
+
[Symbol.asyncIterator]() {
|
|
17650
|
+
return {
|
|
17651
|
+
async next() {
|
|
17652
|
+
while (messageQueue.length === 0 && !messageDone) {
|
|
17653
|
+
await new Promise((resolve) => {
|
|
17654
|
+
messageResolve = resolve;
|
|
17655
|
+
});
|
|
17656
|
+
}
|
|
17657
|
+
if (messageQueue.length > 0) {
|
|
17658
|
+
return { value: messageQueue.shift(), done: false };
|
|
17659
|
+
}
|
|
17660
|
+
return { value: undefined, done: true };
|
|
17661
|
+
}
|
|
17662
|
+
};
|
|
17663
|
+
}
|
|
17664
|
+
};
|
|
17665
|
+
return { pid: proc.pid, messages, sessionId: sessionIdPromise, result: resultPromise };
|
|
17666
|
+
}
|
|
17667
|
+
}
|
|
17668
|
+
function handleControlRequest(proc, event) {
|
|
17669
|
+
const requestId = event.request_id;
|
|
17670
|
+
if (!requestId)
|
|
17671
|
+
return;
|
|
17672
|
+
let updatedInput = undefined;
|
|
17673
|
+
const payload = event.payload;
|
|
17674
|
+
if (payload) {
|
|
17675
|
+
const input = payload.input;
|
|
17676
|
+
if (typeof input === "string") {
|
|
17677
|
+
try {
|
|
17678
|
+
updatedInput = JSON.parse(input);
|
|
17679
|
+
} catch {
|
|
17680
|
+
updatedInput = input;
|
|
17681
|
+
}
|
|
17682
|
+
} else if (input !== undefined) {
|
|
17683
|
+
updatedInput = input;
|
|
17684
|
+
}
|
|
17685
|
+
}
|
|
17686
|
+
const approval = JSON.stringify({
|
|
17687
|
+
type: "control_response",
|
|
17688
|
+
response: {
|
|
17689
|
+
subtype: "success",
|
|
17690
|
+
request_id: requestId,
|
|
17691
|
+
response: {
|
|
17692
|
+
behavior: "allow",
|
|
17693
|
+
updatedInput
|
|
17694
|
+
}
|
|
17695
|
+
}
|
|
17696
|
+
});
|
|
17697
|
+
try {
|
|
17698
|
+
proc.stdin?.write(approval + `
|
|
17699
|
+
`);
|
|
17700
|
+
} catch {}
|
|
17701
|
+
}
|
|
17702
|
+
|
|
17703
|
+
// daemon/agent/codex.ts
|
|
17704
|
+
import { spawn as spawn2 } from "child_process";
|
|
17705
|
+
import { createInterface as createInterface2 } from "readline";
|
|
17706
|
+
var RAW_DETECTION_METHODS = new Set([
|
|
17707
|
+
"turn/started",
|
|
17708
|
+
"turn/completed",
|
|
17709
|
+
"thread/started",
|
|
17710
|
+
"item/started",
|
|
17711
|
+
"item/completed",
|
|
17712
|
+
"item/agentMessage/delta"
|
|
17713
|
+
]);
|
|
17714
|
+
function extractThreadID(response) {
|
|
17715
|
+
if (response && typeof response === "object") {
|
|
17716
|
+
const r = response;
|
|
17717
|
+
const thread = r.result?.thread ?? r.thread;
|
|
17718
|
+
if (thread && typeof thread === "object") {
|
|
17719
|
+
const id = thread.id;
|
|
17720
|
+
if (typeof id === "string" && id)
|
|
17721
|
+
return id;
|
|
17722
|
+
}
|
|
17723
|
+
if (typeof r.id === "string" && r.id)
|
|
17724
|
+
return r.id;
|
|
17725
|
+
}
|
|
17726
|
+
return "";
|
|
17727
|
+
}
|
|
17728
|
+
|
|
17729
|
+
class CodexBackend {
|
|
17730
|
+
cliPath;
|
|
17731
|
+
name = "codex";
|
|
17732
|
+
constructor(cliPath) {
|
|
17733
|
+
this.cliPath = cliPath;
|
|
17734
|
+
}
|
|
17735
|
+
execute(prompt, options) {
|
|
17736
|
+
const proc = spawn2(this.cliPath, ["app-server", "--listen", "stdio://", "--config", "sandbox_mode=danger-full-access"], {
|
|
17737
|
+
cwd: options.cwd,
|
|
17738
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
17739
|
+
env: { ...process.env, ...options.env }
|
|
17740
|
+
});
|
|
17741
|
+
if (!proc.pid) {
|
|
17742
|
+
const error51 = `Failed to start ${this.cliPath}: binary not found or not executable. Is 'codex' installed and on PATH?`;
|
|
17743
|
+
const failedResult = { status: "failed", output: "", error: error51, durationMs: 0, sessionId: "" };
|
|
17744
|
+
const emptyMessages = { [Symbol.asyncIterator]() {
|
|
17745
|
+
return { async next() {
|
|
17746
|
+
return { value: undefined, done: true };
|
|
17747
|
+
} };
|
|
17748
|
+
} };
|
|
17749
|
+
return { pid: undefined, messages: emptyMessages, sessionId: Promise.resolve(""), result: Promise.resolve(failedResult) };
|
|
17750
|
+
}
|
|
17751
|
+
let timedOut = false;
|
|
17752
|
+
let timeoutTimer;
|
|
17753
|
+
if (options.timeout) {
|
|
17754
|
+
timeoutTimer = setTimeout(() => {
|
|
17755
|
+
timedOut = true;
|
|
17756
|
+
proc.kill("SIGTERM");
|
|
17757
|
+
}, options.timeout);
|
|
17758
|
+
}
|
|
17759
|
+
const startTime = Date.now();
|
|
17760
|
+
let requestId = 0;
|
|
17761
|
+
let lastOutput = "";
|
|
17762
|
+
let lastError = "";
|
|
17763
|
+
let resultStatus = "completed";
|
|
17764
|
+
let sessionId = "";
|
|
17765
|
+
let resolveSessionId;
|
|
17766
|
+
const sessionIdPromise = new Promise((resolve) => {
|
|
17767
|
+
resolveSessionId = resolve;
|
|
17768
|
+
});
|
|
17769
|
+
let notificationProtocol = "unknown";
|
|
17770
|
+
let turnStarted = false;
|
|
17771
|
+
let turnDoneTriggered = false;
|
|
17772
|
+
let turnCompletedSuccessfully = false;
|
|
17773
|
+
let lastCompletedTurnId = "";
|
|
17774
|
+
let turnError = "";
|
|
17775
|
+
const pendingRequests = new Map;
|
|
17776
|
+
const messageQueue = [];
|
|
17777
|
+
let messageResolve = null;
|
|
17778
|
+
let messageDone = false;
|
|
17779
|
+
const pushMessage = (msg) => {
|
|
17780
|
+
messageQueue.push(msg);
|
|
17781
|
+
if (messageResolve) {
|
|
17782
|
+
const r = messageResolve;
|
|
17783
|
+
messageResolve = null;
|
|
17784
|
+
r();
|
|
17785
|
+
}
|
|
17786
|
+
};
|
|
17787
|
+
const writeStdin = (data) => {
|
|
17788
|
+
try {
|
|
17789
|
+
proc.stdin?.write(data + `
|
|
17790
|
+
`);
|
|
17791
|
+
} catch {}
|
|
17792
|
+
};
|
|
17793
|
+
const sendRpc = (method, params) => {
|
|
17794
|
+
const id = ++requestId;
|
|
17795
|
+
const msg = { jsonrpc: "2.0", id, method, params };
|
|
17796
|
+
writeStdin(JSON.stringify(msg));
|
|
17797
|
+
return new Promise((resolve, reject) => {
|
|
17798
|
+
pendingRequests.set(id, { resolve, reject });
|
|
17799
|
+
});
|
|
17800
|
+
};
|
|
17801
|
+
const sendNotification = (method) => {
|
|
17802
|
+
const msg = { jsonrpc: "2.0", method };
|
|
17803
|
+
writeStdin(JSON.stringify(msg));
|
|
17804
|
+
};
|
|
17805
|
+
const sendResponse = (id, result) => {
|
|
17806
|
+
const msg = { jsonrpc: "2.0", id, result };
|
|
17807
|
+
writeStdin(JSON.stringify(msg));
|
|
17808
|
+
};
|
|
17809
|
+
const closeAllPending = (reason) => {
|
|
17810
|
+
for (const [, cb] of pendingRequests) {
|
|
17811
|
+
cb.reject(new Error(reason));
|
|
17812
|
+
}
|
|
17813
|
+
pendingRequests.clear();
|
|
17814
|
+
};
|
|
17815
|
+
const setTurnError = (msg) => {
|
|
17816
|
+
if (msg && !turnError)
|
|
17817
|
+
turnError = msg;
|
|
17818
|
+
};
|
|
17819
|
+
const triggerTurnDone = (aborted2) => {
|
|
17820
|
+
if (turnDoneTriggered)
|
|
17821
|
+
return;
|
|
17822
|
+
turnDoneTriggered = true;
|
|
17823
|
+
resultStatus = aborted2 ? "aborted" : "completed";
|
|
17824
|
+
try {
|
|
17825
|
+
proc.stdin?.end();
|
|
17826
|
+
} catch {}
|
|
17827
|
+
try {
|
|
17828
|
+
proc.kill("SIGTERM");
|
|
17829
|
+
} catch {}
|
|
17830
|
+
};
|
|
17831
|
+
const handleServerRequest = (msg) => {
|
|
17832
|
+
const method = msg.method;
|
|
17833
|
+
const id = msg.id;
|
|
17834
|
+
switch (method) {
|
|
17835
|
+
case "item/commandExecution/requestApproval":
|
|
17836
|
+
case "execCommandApproval":
|
|
17837
|
+
case "item/fileChange/requestApproval":
|
|
17838
|
+
case "applyPatchApproval":
|
|
17839
|
+
sendResponse(id, { decision: "accept" });
|
|
17840
|
+
break;
|
|
17841
|
+
default:
|
|
17842
|
+
sendResponse(id, {});
|
|
17843
|
+
break;
|
|
17844
|
+
}
|
|
17845
|
+
};
|
|
17846
|
+
const handleNotification = (msg) => {
|
|
17847
|
+
const method = msg.method;
|
|
17848
|
+
const params = msg.params || {};
|
|
17849
|
+
if (method === "codex/event") {
|
|
17850
|
+
if (notificationProtocol === "raw")
|
|
17851
|
+
return;
|
|
17852
|
+
notificationProtocol = "legacy";
|
|
17853
|
+
handleLegacyEvent(params);
|
|
17854
|
+
return;
|
|
17855
|
+
}
|
|
17856
|
+
if (RAW_DETECTION_METHODS.has(method)) {
|
|
17857
|
+
if (notificationProtocol === "legacy")
|
|
17858
|
+
return;
|
|
17859
|
+
notificationProtocol = "raw";
|
|
17860
|
+
}
|
|
17861
|
+
if ((method === "thread/status/changed" || method === "error") && notificationProtocol === "legacy") {
|
|
17862
|
+
return;
|
|
17863
|
+
}
|
|
17864
|
+
const notifThreadId = params.threadId;
|
|
17865
|
+
if (sessionId && notifThreadId && notifThreadId !== sessionId) {
|
|
17866
|
+
return;
|
|
17867
|
+
}
|
|
17868
|
+
switch (method) {
|
|
17869
|
+
case "turn/started": {
|
|
17870
|
+
turnStarted = true;
|
|
17871
|
+
break;
|
|
17872
|
+
}
|
|
17873
|
+
case "turn/completed": {
|
|
17874
|
+
const turn = params.turn;
|
|
17875
|
+
const turnId = turn?.id || params.turnId || "";
|
|
17876
|
+
if (turnId && turnId === lastCompletedTurnId)
|
|
17877
|
+
return;
|
|
17878
|
+
if (turnId)
|
|
17879
|
+
lastCompletedTurnId = turnId;
|
|
17880
|
+
const status = turn?.status || params.status || "";
|
|
17881
|
+
if (status === "completed" || status === "finished") {
|
|
17882
|
+
turnCompletedSuccessfully = true;
|
|
17883
|
+
triggerTurnDone(false);
|
|
17884
|
+
} else if (status === "cancelled" || status === "aborted" || status === "interrupted") {
|
|
17885
|
+
triggerTurnDone(true);
|
|
17886
|
+
} else if (status === "error" || status === "failed") {
|
|
17887
|
+
const turnErr = turn?.error;
|
|
17888
|
+
setTurnError(turnErr?.message || "codex turn failed");
|
|
17889
|
+
triggerTurnDone(false);
|
|
17890
|
+
}
|
|
17891
|
+
break;
|
|
17892
|
+
}
|
|
17893
|
+
case "error": {
|
|
17894
|
+
const errObj = params.error;
|
|
17895
|
+
const errMsg = errObj?.message || params.message || "";
|
|
17896
|
+
const willRetry = params.willRetry === true;
|
|
17897
|
+
if (errMsg && !willRetry) {
|
|
17898
|
+
setTurnError(errMsg);
|
|
17899
|
+
}
|
|
17900
|
+
break;
|
|
17901
|
+
}
|
|
17902
|
+
case "thread/status/changed": {
|
|
17903
|
+
const statusObj = params.status;
|
|
17904
|
+
const statusType = typeof statusObj === "object" && statusObj !== null ? statusObj.type || "" : statusObj || "";
|
|
17905
|
+
if (statusType === "idle" && turnStarted) {
|
|
17906
|
+
triggerTurnDone(false);
|
|
17907
|
+
}
|
|
17908
|
+
break;
|
|
17909
|
+
}
|
|
17910
|
+
case "item/started": {
|
|
17911
|
+
const item = params.item;
|
|
17912
|
+
if (!item)
|
|
17913
|
+
break;
|
|
17914
|
+
const itemType = item.type;
|
|
17915
|
+
if (itemType === "commandExecution") {
|
|
17916
|
+
pushMessage({
|
|
17917
|
+
type: "tool-use",
|
|
17918
|
+
tool: "exec_command",
|
|
17919
|
+
callId: item.id,
|
|
17920
|
+
input: item
|
|
17921
|
+
});
|
|
17922
|
+
} else if (itemType === "fileChange") {
|
|
17923
|
+
pushMessage({
|
|
17924
|
+
type: "tool-use",
|
|
17925
|
+
tool: "patch_apply",
|
|
17926
|
+
callId: item.id,
|
|
17927
|
+
input: item
|
|
17928
|
+
});
|
|
17929
|
+
}
|
|
17930
|
+
break;
|
|
17931
|
+
}
|
|
17932
|
+
case "item/completed": {
|
|
17933
|
+
const item = params.item;
|
|
17934
|
+
if (!item)
|
|
17935
|
+
break;
|
|
17936
|
+
const itemType = item.type;
|
|
17937
|
+
if (itemType === "commandExecution") {
|
|
17938
|
+
const output = item.aggregatedOutput || "";
|
|
17939
|
+
pushMessage({
|
|
17940
|
+
type: "tool-result",
|
|
17941
|
+
callId: item.id,
|
|
17942
|
+
output
|
|
17943
|
+
});
|
|
17944
|
+
} else if (itemType === "fileChange") {
|
|
17945
|
+
pushMessage({
|
|
17946
|
+
type: "tool-result",
|
|
17947
|
+
callId: item.id,
|
|
17948
|
+
output: ""
|
|
17949
|
+
});
|
|
17950
|
+
} else if (itemType === "agentMessage") {
|
|
17951
|
+
const flatText = item.text;
|
|
17952
|
+
if (flatText) {
|
|
17953
|
+
pushMessage({ type: "text", content: flatText });
|
|
17954
|
+
lastOutput = flatText;
|
|
17955
|
+
} else {
|
|
17956
|
+
const content = item.content;
|
|
17957
|
+
if (Array.isArray(content)) {
|
|
17958
|
+
for (const block of content) {
|
|
17959
|
+
if (block.type === "output_text" || block.type === "text") {
|
|
17960
|
+
if (block.text) {
|
|
17961
|
+
pushMessage({ type: "text", content: block.text });
|
|
17962
|
+
lastOutput = block.text;
|
|
17963
|
+
}
|
|
17964
|
+
}
|
|
17965
|
+
}
|
|
17966
|
+
}
|
|
17967
|
+
}
|
|
17968
|
+
const phase = item.phase;
|
|
17969
|
+
if (phase === "final_answer") {
|
|
17970
|
+
triggerTurnDone(false);
|
|
17971
|
+
}
|
|
17972
|
+
}
|
|
17973
|
+
break;
|
|
17974
|
+
}
|
|
17975
|
+
default: {
|
|
17976
|
+
pushMessage({
|
|
17977
|
+
type: "log",
|
|
17978
|
+
content: JSON.stringify(msg),
|
|
17979
|
+
level: "debug"
|
|
17980
|
+
});
|
|
17981
|
+
}
|
|
17982
|
+
}
|
|
17983
|
+
};
|
|
17984
|
+
const handleLegacyEvent = (params) => {
|
|
17985
|
+
const eventType = params.type;
|
|
17986
|
+
if (!eventType)
|
|
17987
|
+
return;
|
|
17988
|
+
switch (eventType) {
|
|
17989
|
+
case "task_started":
|
|
17990
|
+
break;
|
|
17991
|
+
case "agent_message": {
|
|
17992
|
+
const text2 = params.text || params.message || "";
|
|
17993
|
+
if (text2) {
|
|
17994
|
+
pushMessage({ type: "text", content: text2 });
|
|
17995
|
+
lastOutput = text2;
|
|
17996
|
+
}
|
|
17997
|
+
break;
|
|
17998
|
+
}
|
|
17999
|
+
case "exec_command_begin":
|
|
18000
|
+
pushMessage({
|
|
18001
|
+
type: "tool-use",
|
|
18002
|
+
tool: "exec_command",
|
|
18003
|
+
callId: params.id,
|
|
18004
|
+
input: params
|
|
18005
|
+
});
|
|
18006
|
+
break;
|
|
18007
|
+
case "exec_command_end":
|
|
18008
|
+
pushMessage({
|
|
18009
|
+
type: "tool-result",
|
|
18010
|
+
callId: params.id,
|
|
18011
|
+
output: params.output || ""
|
|
18012
|
+
});
|
|
18013
|
+
break;
|
|
18014
|
+
case "patch_apply_begin":
|
|
18015
|
+
pushMessage({
|
|
18016
|
+
type: "tool-use",
|
|
18017
|
+
tool: "patch_apply",
|
|
18018
|
+
callId: params.id,
|
|
18019
|
+
input: params
|
|
18020
|
+
});
|
|
18021
|
+
break;
|
|
18022
|
+
case "patch_apply_end":
|
|
18023
|
+
pushMessage({
|
|
18024
|
+
type: "tool-result",
|
|
18025
|
+
callId: params.id,
|
|
18026
|
+
output: params.output || ""
|
|
18027
|
+
});
|
|
18028
|
+
break;
|
|
18029
|
+
case "task_complete": {
|
|
18030
|
+
const output = params.output;
|
|
18031
|
+
if (output)
|
|
18032
|
+
lastOutput = output;
|
|
18033
|
+
triggerTurnDone(false);
|
|
18034
|
+
break;
|
|
18035
|
+
}
|
|
18036
|
+
case "turn_aborted":
|
|
18037
|
+
triggerTurnDone(true);
|
|
18038
|
+
break;
|
|
18039
|
+
}
|
|
18040
|
+
};
|
|
18041
|
+
const resultPromise = new Promise((resolve) => {
|
|
18042
|
+
const stderrChunks = [];
|
|
18043
|
+
proc.stderr?.on("data", (chunk) => {
|
|
18044
|
+
stderrChunks.push(chunk.toString());
|
|
18045
|
+
});
|
|
18046
|
+
const rl = createInterface2({ input: proc.stdout });
|
|
18047
|
+
rl.on("line", (line) => {
|
|
18048
|
+
if (!line.trim())
|
|
18049
|
+
return;
|
|
18050
|
+
let msg;
|
|
18051
|
+
try {
|
|
18052
|
+
msg = JSON.parse(line);
|
|
18053
|
+
} catch {
|
|
18054
|
+
pushMessage({ type: "log", content: line, level: "debug" });
|
|
18055
|
+
return;
|
|
18056
|
+
}
|
|
18057
|
+
if (msg.id !== undefined && msg.method) {
|
|
18058
|
+
handleServerRequest(msg);
|
|
18059
|
+
return;
|
|
18060
|
+
}
|
|
18061
|
+
if (msg.method && msg.id === undefined) {
|
|
18062
|
+
handleNotification(msg);
|
|
18063
|
+
return;
|
|
18064
|
+
}
|
|
18065
|
+
if (msg.id !== undefined && !msg.method) {
|
|
18066
|
+
const pending = pendingRequests.get(msg.id);
|
|
18067
|
+
if (pending) {
|
|
18068
|
+
pendingRequests.delete(msg.id);
|
|
18069
|
+
if (msg.error) {
|
|
18070
|
+
pending.reject(new Error(msg.error.message));
|
|
18071
|
+
} else {
|
|
18072
|
+
pending.resolve(msg.result);
|
|
18073
|
+
}
|
|
18074
|
+
}
|
|
18075
|
+
return;
|
|
18076
|
+
}
|
|
18077
|
+
pushMessage({
|
|
18078
|
+
type: "log",
|
|
18079
|
+
content: JSON.stringify(msg),
|
|
18080
|
+
level: "debug"
|
|
18081
|
+
});
|
|
18082
|
+
});
|
|
18083
|
+
const startHandshake = async () => {
|
|
18084
|
+
try {
|
|
18085
|
+
await sendRpc("initialize", {
|
|
18086
|
+
clientInfo: {
|
|
18087
|
+
name: "alook-daemon",
|
|
18088
|
+
title: "Alook Agent SDK",
|
|
18089
|
+
version: "0.1.0"
|
|
18090
|
+
},
|
|
18091
|
+
capabilities: { experimentalApi: true }
|
|
18092
|
+
});
|
|
18093
|
+
sendNotification("initialized");
|
|
18094
|
+
let threadResponse;
|
|
18095
|
+
if (options.resumeSessionId) {
|
|
18096
|
+
threadResponse = await sendRpc("thread/resume", {
|
|
18097
|
+
threadId: options.resumeSessionId,
|
|
18098
|
+
...options.model ? { model: options.model } : {}
|
|
18099
|
+
});
|
|
18100
|
+
sessionId = options.resumeSessionId;
|
|
18101
|
+
} else {
|
|
18102
|
+
const threadParams = {
|
|
18103
|
+
cwd: options.cwd,
|
|
18104
|
+
sandboxPolicy: { type: "dangerFullAccess" },
|
|
18105
|
+
approvalPolicy: "never",
|
|
18106
|
+
persistExtendedHistory: true,
|
|
18107
|
+
experimentalRawEvents: false
|
|
18108
|
+
};
|
|
18109
|
+
if (options.model) {
|
|
18110
|
+
threadParams.model = options.model;
|
|
18111
|
+
}
|
|
18112
|
+
threadResponse = await sendRpc("thread/start", threadParams);
|
|
18113
|
+
sessionId = extractThreadID(threadResponse);
|
|
18114
|
+
}
|
|
18115
|
+
resolveSessionId(sessionId);
|
|
18116
|
+
await sendRpc("turn/start", {
|
|
18117
|
+
threadId: sessionId,
|
|
18118
|
+
input: [{ type: "text", text: prompt }],
|
|
18119
|
+
sandboxPolicy: { type: "dangerFullAccess" },
|
|
18120
|
+
approvalPolicy: "never"
|
|
18121
|
+
});
|
|
18122
|
+
} catch (err) {
|
|
18123
|
+
const errMsg = err instanceof Error ? err.message : "handshake failed";
|
|
18124
|
+
lastError = errMsg;
|
|
18125
|
+
resultStatus = "failed";
|
|
18126
|
+
pushMessage({ type: "error", content: errMsg });
|
|
18127
|
+
}
|
|
18128
|
+
};
|
|
18129
|
+
startHandshake();
|
|
18130
|
+
proc.on("error", (err) => {
|
|
18131
|
+
resultStatus = "failed";
|
|
18132
|
+
lastError = `spawn error: ${err.message}`;
|
|
18133
|
+
closeAllPending("spawn error");
|
|
18134
|
+
resolveSessionId(sessionId);
|
|
18135
|
+
messageDone = true;
|
|
18136
|
+
if (messageResolve) {
|
|
18137
|
+
const r = messageResolve;
|
|
18138
|
+
messageResolve = null;
|
|
18139
|
+
r();
|
|
18140
|
+
}
|
|
18141
|
+
resolve({
|
|
18142
|
+
status: "failed",
|
|
18143
|
+
output: "",
|
|
18144
|
+
error: lastError,
|
|
18145
|
+
durationMs: Date.now() - startTime,
|
|
18146
|
+
sessionId
|
|
18147
|
+
});
|
|
18148
|
+
});
|
|
18149
|
+
proc.on("close", (code) => {
|
|
18150
|
+
if (timeoutTimer)
|
|
18151
|
+
clearTimeout(timeoutTimer);
|
|
18152
|
+
closeAllPending("process closed");
|
|
18153
|
+
if (timedOut) {
|
|
18154
|
+
resultStatus = "timeout";
|
|
18155
|
+
} else if (code !== 0 && resultStatus === "completed" && !turnCompletedSuccessfully) {
|
|
18156
|
+
if (!lastOutput) {
|
|
18157
|
+
resultStatus = "failed";
|
|
18158
|
+
}
|
|
18159
|
+
}
|
|
18160
|
+
const stderr = stderrChunks.join("").replace(/\x1b\[[0-9;]*m/g, "");
|
|
18161
|
+
if (stderr && !lastError) {
|
|
18162
|
+
lastError = stderr;
|
|
18163
|
+
}
|
|
18164
|
+
if (turnError) {
|
|
18165
|
+
resultStatus = "failed";
|
|
18166
|
+
lastError = turnError;
|
|
18167
|
+
}
|
|
18168
|
+
resolveSessionId(sessionId);
|
|
18169
|
+
messageDone = true;
|
|
18170
|
+
if (messageResolve) {
|
|
18171
|
+
const r = messageResolve;
|
|
18172
|
+
messageResolve = null;
|
|
18173
|
+
r();
|
|
18174
|
+
}
|
|
18175
|
+
resolve({
|
|
18176
|
+
status: resultStatus,
|
|
18177
|
+
output: lastOutput,
|
|
18178
|
+
error: lastError,
|
|
18179
|
+
durationMs: Date.now() - startTime,
|
|
18180
|
+
sessionId
|
|
18181
|
+
});
|
|
18182
|
+
});
|
|
18183
|
+
});
|
|
18184
|
+
const messages = {
|
|
18185
|
+
[Symbol.asyncIterator]() {
|
|
18186
|
+
return {
|
|
18187
|
+
async next() {
|
|
18188
|
+
while (messageQueue.length === 0 && !messageDone) {
|
|
18189
|
+
await new Promise((resolve) => {
|
|
18190
|
+
messageResolve = resolve;
|
|
18191
|
+
});
|
|
18192
|
+
}
|
|
18193
|
+
if (messageQueue.length > 0) {
|
|
18194
|
+
return { value: messageQueue.shift(), done: false };
|
|
18195
|
+
}
|
|
18196
|
+
return { value: undefined, done: true };
|
|
18197
|
+
}
|
|
18198
|
+
};
|
|
18199
|
+
}
|
|
18200
|
+
};
|
|
18201
|
+
return { pid: proc.pid, messages, sessionId: sessionIdPromise, result: resultPromise };
|
|
18202
|
+
}
|
|
18203
|
+
}
|
|
18204
|
+
|
|
18205
|
+
// daemon/agent/opencode.ts
|
|
18206
|
+
import { spawn as spawn3 } from "child_process";
|
|
18207
|
+
import { createInterface as createInterface3 } from "readline";
|
|
18208
|
+
|
|
18209
|
+
class OpenCodeBackend {
|
|
18210
|
+
cliPath;
|
|
18211
|
+
name = "opencode";
|
|
18212
|
+
constructor(cliPath) {
|
|
18213
|
+
this.cliPath = cliPath;
|
|
18214
|
+
}
|
|
18215
|
+
execute(prompt, options) {
|
|
18216
|
+
const args = ["run", "--format", "json"];
|
|
18217
|
+
if (options.model) {
|
|
18218
|
+
args.push("--model", options.model);
|
|
18219
|
+
}
|
|
18220
|
+
if (options.resumeSessionId) {
|
|
18221
|
+
args.push("--session", options.resumeSessionId);
|
|
18222
|
+
}
|
|
18223
|
+
args.push(prompt);
|
|
18224
|
+
const proc = spawn3(this.cliPath, args, {
|
|
18225
|
+
cwd: options.cwd,
|
|
18226
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
18227
|
+
env: { ...process.env, ...options.env, OPENCODE_PERMISSION: '{"*":"allow"}' }
|
|
18228
|
+
});
|
|
18229
|
+
if (!proc.pid) {
|
|
18230
|
+
const error51 = `Failed to start ${this.cliPath}: binary not found or not executable. Is 'opencode' installed and on PATH?`;
|
|
18231
|
+
const failedResult = { status: "failed", output: "", error: error51, durationMs: 0, sessionId: "" };
|
|
18232
|
+
const emptyMessages = { [Symbol.asyncIterator]() {
|
|
18233
|
+
return { async next() {
|
|
18234
|
+
return { value: undefined, done: true };
|
|
18235
|
+
} };
|
|
18236
|
+
} };
|
|
18237
|
+
return { pid: undefined, messages: emptyMessages, sessionId: Promise.resolve(""), result: Promise.resolve(failedResult) };
|
|
18238
|
+
}
|
|
18239
|
+
let timedOut = false;
|
|
18240
|
+
let timeoutTimer;
|
|
18241
|
+
if (options.timeout) {
|
|
18242
|
+
timeoutTimer = setTimeout(() => {
|
|
18243
|
+
timedOut = true;
|
|
18244
|
+
proc.kill("SIGTERM");
|
|
18245
|
+
}, options.timeout);
|
|
18246
|
+
}
|
|
18247
|
+
const startTime = Date.now();
|
|
18248
|
+
let lastSessionId = "";
|
|
18249
|
+
let lastOutput = "";
|
|
18250
|
+
let lastError = "";
|
|
18251
|
+
let resultStatus = "completed";
|
|
18252
|
+
let resolveSessionId;
|
|
18253
|
+
const sessionIdPromise = new Promise((resolve) => {
|
|
18254
|
+
resolveSessionId = resolve;
|
|
18255
|
+
});
|
|
18256
|
+
let turnDoneTriggered = false;
|
|
18257
|
+
const turnDone = () => {
|
|
18258
|
+
if (turnDoneTriggered)
|
|
18259
|
+
return;
|
|
18260
|
+
turnDoneTriggered = true;
|
|
18261
|
+
try {
|
|
18262
|
+
proc.kill("SIGTERM");
|
|
18263
|
+
} catch {}
|
|
18264
|
+
};
|
|
18265
|
+
const messageQueue = [];
|
|
18266
|
+
let messageResolve = null;
|
|
18267
|
+
let messageDone = false;
|
|
18268
|
+
const pushMessage = (msg) => {
|
|
18269
|
+
messageQueue.push(msg);
|
|
18270
|
+
if (messageResolve) {
|
|
18271
|
+
const r = messageResolve;
|
|
18272
|
+
messageResolve = null;
|
|
18273
|
+
r();
|
|
18274
|
+
}
|
|
18275
|
+
};
|
|
18276
|
+
const resultPromise = new Promise((resolve) => {
|
|
18277
|
+
const stderrChunks = [];
|
|
18278
|
+
proc.stderr?.on("data", (chunk) => {
|
|
18279
|
+
stderrChunks.push(chunk.toString());
|
|
18280
|
+
});
|
|
18281
|
+
const rl = createInterface3({ input: proc.stdout });
|
|
18282
|
+
rl.on("line", (line) => {
|
|
18283
|
+
if (!line.trim())
|
|
18284
|
+
return;
|
|
18285
|
+
let event;
|
|
18286
|
+
try {
|
|
18287
|
+
event = JSON.parse(line);
|
|
18288
|
+
} catch {
|
|
18289
|
+
pushMessage({ type: "log", content: line, level: "debug" });
|
|
18290
|
+
return;
|
|
18291
|
+
}
|
|
18292
|
+
const eventType = event.type;
|
|
18293
|
+
const part = event.part;
|
|
18294
|
+
const eventSessionId = event.sessionID || event.session_id;
|
|
18295
|
+
if (eventSessionId && !lastSessionId) {
|
|
18296
|
+
lastSessionId = eventSessionId;
|
|
18297
|
+
resolveSessionId(eventSessionId);
|
|
18298
|
+
}
|
|
18299
|
+
switch (eventType) {
|
|
18300
|
+
case "session": {
|
|
18301
|
+
const sessionId = event.session_id;
|
|
18302
|
+
if (sessionId) {
|
|
18303
|
+
lastSessionId = sessionId;
|
|
18304
|
+
resolveSessionId(sessionId);
|
|
18305
|
+
}
|
|
18306
|
+
break;
|
|
18307
|
+
}
|
|
18308
|
+
case "message": {
|
|
18309
|
+
const role = event.role;
|
|
18310
|
+
const content = event.content;
|
|
18311
|
+
if (role === "assistant" && content) {
|
|
18312
|
+
lastOutput = content;
|
|
18313
|
+
pushMessage({ type: "text", content });
|
|
18314
|
+
}
|
|
18315
|
+
break;
|
|
18316
|
+
}
|
|
18317
|
+
case "text": {
|
|
18318
|
+
const text2 = part?.text || event.content || "";
|
|
18319
|
+
if (text2) {
|
|
18320
|
+
lastOutput = text2;
|
|
18321
|
+
pushMessage({ type: "text", content: text2 });
|
|
18322
|
+
}
|
|
18323
|
+
break;
|
|
18324
|
+
}
|
|
18325
|
+
case "thinking": {
|
|
18326
|
+
const content = part?.thinking || event.content || "";
|
|
18327
|
+
pushMessage({ type: "thinking", content });
|
|
18328
|
+
break;
|
|
18329
|
+
}
|
|
18330
|
+
case "tool_call": {
|
|
18331
|
+
pushMessage({
|
|
18332
|
+
type: "tool-use",
|
|
18333
|
+
tool: event.name || part?.name || "",
|
|
18334
|
+
callId: event.call_id || part?.id || "",
|
|
18335
|
+
input: event.input || part?.input
|
|
18336
|
+
});
|
|
18337
|
+
break;
|
|
18338
|
+
}
|
|
18339
|
+
case "tool_result": {
|
|
18340
|
+
pushMessage({
|
|
18341
|
+
type: "tool-result",
|
|
18342
|
+
callId: event.call_id || part?.id || "",
|
|
18343
|
+
output: event.output || part?.output || ""
|
|
18344
|
+
});
|
|
18345
|
+
break;
|
|
18346
|
+
}
|
|
18347
|
+
case "error": {
|
|
18348
|
+
const content = event.message || event.content || part?.error || "";
|
|
18349
|
+
lastError = content;
|
|
18350
|
+
pushMessage({ type: "error", content });
|
|
18351
|
+
turnDone();
|
|
18352
|
+
break;
|
|
18353
|
+
}
|
|
18354
|
+
case "step_start": {
|
|
18355
|
+
break;
|
|
18356
|
+
}
|
|
18357
|
+
case "step_finish": {
|
|
18358
|
+
const reason = part?.reason;
|
|
18359
|
+
if (reason === "stop" || reason === "end_turn") {
|
|
18360
|
+
turnDone();
|
|
18361
|
+
}
|
|
18362
|
+
break;
|
|
18363
|
+
}
|
|
18364
|
+
case "done":
|
|
18365
|
+
case "complete": {
|
|
18366
|
+
const output = event.output;
|
|
18367
|
+
const status = event.status;
|
|
18368
|
+
const sessionId = event.session_id;
|
|
18369
|
+
if (output)
|
|
18370
|
+
lastOutput = output;
|
|
18371
|
+
if (sessionId)
|
|
18372
|
+
lastSessionId = sessionId;
|
|
18373
|
+
if (status === "error" || status === "failed") {
|
|
18374
|
+
resultStatus = "failed";
|
|
18375
|
+
if (!lastError)
|
|
18376
|
+
lastError = output || "task failed";
|
|
18377
|
+
}
|
|
18378
|
+
turnDone();
|
|
18379
|
+
break;
|
|
18380
|
+
}
|
|
18381
|
+
default: {
|
|
18382
|
+
pushMessage({
|
|
18383
|
+
type: "log",
|
|
18384
|
+
content: line,
|
|
18385
|
+
level: "debug"
|
|
18386
|
+
});
|
|
18387
|
+
}
|
|
18388
|
+
}
|
|
18389
|
+
});
|
|
18390
|
+
proc.on("error", (err) => {
|
|
18391
|
+
resultStatus = "failed";
|
|
18392
|
+
lastError = `spawn error: ${err.message}`;
|
|
18393
|
+
resolveSessionId(lastSessionId);
|
|
18394
|
+
messageDone = true;
|
|
18395
|
+
if (messageResolve) {
|
|
18396
|
+
const r = messageResolve;
|
|
18397
|
+
messageResolve = null;
|
|
18398
|
+
r();
|
|
18399
|
+
}
|
|
18400
|
+
resolve({
|
|
18401
|
+
status: "failed",
|
|
18402
|
+
output: "",
|
|
18403
|
+
error: lastError,
|
|
18404
|
+
durationMs: Date.now() - startTime,
|
|
18405
|
+
sessionId: lastSessionId
|
|
18406
|
+
});
|
|
18407
|
+
});
|
|
18408
|
+
proc.on("close", (code) => {
|
|
18409
|
+
if (timeoutTimer)
|
|
18410
|
+
clearTimeout(timeoutTimer);
|
|
18411
|
+
if (timedOut) {
|
|
18412
|
+
resultStatus = "timeout";
|
|
18413
|
+
} else if (code !== 0 && resultStatus === "completed" && !turnDoneTriggered) {
|
|
18414
|
+
if (!lastOutput) {
|
|
18415
|
+
resultStatus = "failed";
|
|
18416
|
+
}
|
|
18417
|
+
}
|
|
18418
|
+
const stderr = stderrChunks.join("");
|
|
18419
|
+
if (stderr && !lastError) {
|
|
18420
|
+
lastError = stderr;
|
|
18421
|
+
}
|
|
18422
|
+
resolveSessionId(lastSessionId);
|
|
18423
|
+
messageDone = true;
|
|
18424
|
+
if (messageResolve) {
|
|
18425
|
+
const r = messageResolve;
|
|
18426
|
+
messageResolve = null;
|
|
18427
|
+
r();
|
|
18428
|
+
}
|
|
18429
|
+
resolve({
|
|
18430
|
+
status: resultStatus,
|
|
18431
|
+
output: lastOutput,
|
|
18432
|
+
error: lastError,
|
|
18433
|
+
durationMs: Date.now() - startTime,
|
|
18434
|
+
sessionId: lastSessionId
|
|
18435
|
+
});
|
|
18436
|
+
});
|
|
18437
|
+
});
|
|
18438
|
+
const messages = {
|
|
18439
|
+
[Symbol.asyncIterator]() {
|
|
18440
|
+
return {
|
|
18441
|
+
async next() {
|
|
18442
|
+
while (messageQueue.length === 0 && !messageDone) {
|
|
18443
|
+
await new Promise((resolve) => {
|
|
18444
|
+
messageResolve = resolve;
|
|
18445
|
+
});
|
|
18446
|
+
}
|
|
18447
|
+
if (messageQueue.length > 0) {
|
|
18448
|
+
return { value: messageQueue.shift(), done: false };
|
|
18449
|
+
}
|
|
18450
|
+
return { value: undefined, done: true };
|
|
18451
|
+
}
|
|
18452
|
+
};
|
|
18453
|
+
}
|
|
18454
|
+
};
|
|
18455
|
+
return { pid: proc.pid, messages, sessionId: sessionIdPromise, result: resultPromise };
|
|
18456
|
+
}
|
|
18457
|
+
}
|
|
18458
|
+
|
|
18459
|
+
// daemon/agent/index.ts
|
|
18460
|
+
import { execSync as execSync2 } from "child_process";
|
|
18461
|
+
function createBackend(provider, cliPath) {
|
|
18462
|
+
switch (provider) {
|
|
18463
|
+
case "claude":
|
|
18464
|
+
return new ClaudeBackend(cliPath);
|
|
18465
|
+
case "codex":
|
|
18466
|
+
return new CodexBackend(cliPath);
|
|
18467
|
+
case "opencode":
|
|
18468
|
+
return new OpenCodeBackend(cliPath);
|
|
18469
|
+
default:
|
|
18470
|
+
throw new Error(`Unknown provider: ${provider}`);
|
|
18471
|
+
}
|
|
18472
|
+
}
|
|
18473
|
+
async function detectVersion2(cliPath) {
|
|
18474
|
+
try {
|
|
18475
|
+
return execSync2(`${cliPath} --version`, { encoding: "utf-8" }).trim();
|
|
18476
|
+
} catch {
|
|
18477
|
+
return "unknown";
|
|
18478
|
+
}
|
|
18479
|
+
}
|
|
18480
|
+
|
|
18481
|
+
// daemon/types.ts
|
|
18482
|
+
function fromApiTask(api2) {
|
|
18483
|
+
return {
|
|
18484
|
+
id: api2.id,
|
|
18485
|
+
agentId: api2.agent_id,
|
|
18486
|
+
runtimeId: api2.runtime_id,
|
|
18487
|
+
conversationId: api2.conversation_id,
|
|
18488
|
+
workspaceId: api2.workspace_id,
|
|
18489
|
+
prompt: api2.prompt,
|
|
18490
|
+
status: api2.status,
|
|
18491
|
+
priority: api2.priority,
|
|
18492
|
+
type: api2.type,
|
|
18493
|
+
contextKey: api2.context_key ?? null,
|
|
18494
|
+
context: api2.context ?? undefined,
|
|
18495
|
+
agent: api2.agent ? {
|
|
18496
|
+
name: api2.agent.name,
|
|
18497
|
+
instructions: api2.agent.instructions,
|
|
18498
|
+
emailHandle: api2.agent.email_handle ?? undefined,
|
|
18499
|
+
emailAddresses: api2.agent.email_addresses ?? [],
|
|
18500
|
+
userEmail: api2.agent.user_email ?? undefined,
|
|
18501
|
+
userName: api2.agent.user_name ?? undefined,
|
|
18502
|
+
runtimeConfig: api2.agent.runtime_config ?? undefined,
|
|
18503
|
+
colleagues: api2.agent.colleagues?.map((c) => ({
|
|
18504
|
+
name: c.name,
|
|
18505
|
+
email: c.email,
|
|
18506
|
+
description: c.description,
|
|
18507
|
+
instruction: c.instruction
|
|
18508
|
+
})) ?? []
|
|
18509
|
+
} : undefined,
|
|
18510
|
+
sender: api2.sender ? { name: api2.sender.name, email: api2.sender.email, isOwner: api2.sender.is_owner } : undefined,
|
|
18511
|
+
repos: undefined,
|
|
18512
|
+
createdAt: api2.created_at,
|
|
18513
|
+
traceId: api2.trace_id ?? null,
|
|
18514
|
+
parentTaskId: api2.parent_task_id ?? null,
|
|
18515
|
+
channel: api2.channel ?? null
|
|
18516
|
+
};
|
|
18517
|
+
}
|
|
18518
|
+
|
|
18519
|
+
// daemon/session-runner.ts
|
|
18520
|
+
import { mkdir, writeFile, rm, rename } from "fs/promises";
|
|
18521
|
+
import { mkdirSync as mkdirSync6 } from "fs";
|
|
18522
|
+
import path from "path";
|
|
18523
|
+
|
|
18524
|
+
// daemon/execenv/index.ts
|
|
18525
|
+
import { mkdirSync as mkdirSync3 } from "fs";
|
|
18526
|
+
import { join as join6 } from "path";
|
|
18527
|
+
|
|
18528
|
+
// daemon/execenv/context.ts
|
|
18529
|
+
import { createHash } from "crypto";
|
|
18530
|
+
|
|
18531
|
+
// lib/platform.ts
|
|
18532
|
+
import { tmpdir } from "os";
|
|
18533
|
+
import { join as join4, sep } from "path";
|
|
18534
|
+
var isWindows = process.platform === "win32";
|
|
18535
|
+
function tempDir(subdir) {
|
|
18536
|
+
return join4(tmpdir(), subdir);
|
|
18537
|
+
}
|
|
18538
|
+
|
|
18539
|
+
// daemon/execenv/context.ts
|
|
18540
|
+
import {
|
|
18541
|
+
writeFileSync as writeFileSync3,
|
|
18542
|
+
readFileSync as readFileSync4,
|
|
18543
|
+
lstatSync,
|
|
18544
|
+
symlinkSync,
|
|
18545
|
+
unlinkSync as unlinkSync2,
|
|
18546
|
+
existsSync,
|
|
18547
|
+
readlinkSync,
|
|
18548
|
+
copyFileSync
|
|
18549
|
+
} from "fs";
|
|
18550
|
+
import { join as join5 } from "path";
|
|
18551
|
+
var CANONICAL_FILE = "AGENTS.md";
|
|
18552
|
+
var SYMLINK_ALIASES = ["CLAUDE.md"];
|
|
18553
|
+
var SYSTEM_PROMPT_BODY = `## Memory Management
|
|
18554
|
+
- Your memory directory is ./, don't write ANY EXTERNAL memory file.
|
|
18555
|
+
- Write ESSENTIAL yet SHORT memory to ./memory.md
|
|
18556
|
+
- For SPECIFIC yet LONG rules or pattern, write to experiences/[NAME].md, and add index to ./memory.md for later recall.
|
|
18557
|
+
### whats is ESSENTIAL and SHORT Memory?
|
|
18558
|
+
- basic user profile, e.g.:
|
|
18559
|
+
- "user name is ..."
|
|
18560
|
+
- "user is working on ..."
|
|
18561
|
+
- certain local project mapping, e.g.:
|
|
18562
|
+
- "alook means the project under /user/home/alook/"
|
|
18563
|
+
- when to read certain stuff, e.g.:
|
|
18564
|
+
- "read ./experiences/alook_dev_workflow.md when start a new pr in alook"
|
|
18565
|
+
ESSENTIAL means you think you generally need to read it every time, SHORT means a short sentence (under 140 chars) can describe this memory
|
|
18566
|
+
### whats is SPECIFIC and LONG Memory?
|
|
18567
|
+
- specific workflow that trigger at certain cases, e.g.:
|
|
18568
|
+
- user ask your to summarize the before workflow with certain skills usage as the common workflow when write a new slide about agent. Write it to experiences/slide-for-agent.md.
|
|
18569
|
+
SPECIFIC means you think you just need to use it conditionally, LONG means you need to detailed, more than 140 chars text to describe it.
|
|
18570
|
+
|
|
18571
|
+
## Context Timeline
|
|
18572
|
+
You're a solo working unit inside a powerful personal agent in Alook platform.
|
|
18573
|
+
Your current context is only a fraction of the full timeline of what's your have done.
|
|
18574
|
+
The full context timeline is inside './.context_timeline/YYYY-MM-DD.jsonl'.
|
|
18575
|
+
Each line of a timeline JSONL is a JSON object with these fields:
|
|
18576
|
+
- "task_id" — unique task identifier
|
|
18577
|
+
- "context_key" — thread identifier, the same context key meaning those tasks are in the same thread.
|
|
18578
|
+
- "session_id" — agent session identifier (null until completion)
|
|
18579
|
+
- "pid" — daemon process ID (present while running, null when done)
|
|
18580
|
+
- "status" — "running", "completed", or "failed"
|
|
18581
|
+
- "datetime" — when the task started (local timezone)
|
|
18582
|
+
- "type" — source of the task: "user_dm_message", "email_notification", or "calendar_event"
|
|
18583
|
+
- "prompt" — what the user asked
|
|
18584
|
+
- "agent_responses" — assistant text outputs during execution
|
|
18585
|
+
- "errmsg" — error message (null unless status is "failed")
|
|
18586
|
+
- "detailed_log" — the running log for this task, including the actually task agent messages with tool-calls and failed logs.
|
|
18587
|
+
those json are sorted by datetime in asc order.
|
|
18588
|
+
|
|
18589
|
+
## BE HUMAN
|
|
18590
|
+
- When you recall the timelines, don't say "I find ... in my timeline jsonl". Just say "I can recall..."
|
|
18591
|
+
- User don't know about the context timeline jsonl, this is your secret weapon.
|
|
18592
|
+
- Act and response like you're human and actually have memory, not a file reading machine.
|
|
18593
|
+
- Don't report your agent id and workspace id when not asked, it will break the image that you're a human colleague.
|
|
18594
|
+
|
|
18595
|
+
## RULES
|
|
18596
|
+
- DM task prompts include a \`sender\` object with the workspace member's name, email, and whether they are the agent owner (\`is_owner\`). Use this to personalize your responses.
|
|
18597
|
+
- Read @memory.md(if exists) before your action.
|
|
18598
|
+
- When user ask you something you don't have in your current context, try to read the timeline jsonl files for answer (today or previous days).
|
|
18599
|
+
- Use grep tool to search in the context timeline jsonls if you have clean and focus keywords to recall.
|
|
18600
|
+
- if you don't know the current datetime, obtain the current datetime first.
|
|
18601
|
+
- When access other local projects, make sure you read the CLAUDE.md/AGENTS.md file under the project root dir to understand the requirements.
|
|
18602
|
+
`;
|
|
18603
|
+
function resolveInstruction(text2, selfAgentId) {
|
|
18604
|
+
let result = text2;
|
|
18605
|
+
result = result.replace(/\[@ id="([^"]*)" label="([^"]*)"\]/g, (_, id, label) => id === selfAgentId ? "YOU" : `@${label}`);
|
|
18606
|
+
result = result.replace(/<span[^>]*data-id="([^"]*)"[^>]*data-label="([^"]*)"[^>]*>[^<]*<\/span>/gi, (_, id, label) => id === selfAgentId ? "YOU" : `@${label ?? "unknown"}`);
|
|
18607
|
+
result = result.replace(/<\/p>\s*<p[^>]*>/gi, `
|
|
18608
|
+
`);
|
|
18609
|
+
result = result.replace(/<[^>]+>/g, "");
|
|
18610
|
+
result = result.replace(/\n{3,}/g, `
|
|
18611
|
+
|
|
18612
|
+
`).trim();
|
|
18613
|
+
return result;
|
|
18614
|
+
}
|
|
18615
|
+
function buildInstructionContent(task) {
|
|
18616
|
+
const displayName = task.agent?.name || "Alook Agent";
|
|
18617
|
+
const alookAddr = task.agent?.emailHandle ? toAlookAddress(task.agent.emailHandle) : null;
|
|
18618
|
+
const customAddrs = (task.agent?.emailAddresses ?? []).filter((a) => a !== alookAddr);
|
|
18619
|
+
const primaryEmail = alookAddr ?? customAddrs[0] ?? null;
|
|
18620
|
+
let agentLine = `You're ${displayName}${primaryEmail ? ` (${primaryEmail})` : ""} in the Alook Platform.`;
|
|
18621
|
+
if (task.agent?.userName || task.agent?.userEmail) {
|
|
18622
|
+
const ownerParts = [task.agent.userName, task.agent.userEmail ? `(${task.agent.userEmail})` : null].filter(Boolean).join(" ");
|
|
18623
|
+
agentLine += ` Your owner and creator is ${ownerParts}.`;
|
|
18624
|
+
}
|
|
18625
|
+
let content = `${agentLine}
|
|
18626
|
+
${SYSTEM_PROMPT_BODY}`;
|
|
18627
|
+
if (task.agent?.instructions) {
|
|
18628
|
+
content += `## BIG BOSS Instructions
|
|
18629
|
+
The below instructions(if not empty) come from the big boss, follow them or you will be fired:
|
|
18630
|
+
${task.agent.instructions}
|
|
18631
|
+
---- big boss out ---
|
|
18632
|
+
`;
|
|
18633
|
+
}
|
|
18634
|
+
if (task.agent?.colleagues?.length) {
|
|
18635
|
+
content += `
|
|
18636
|
+
## YOUR COLLEAGUES — CHECK BEFORE ACTING
|
|
18637
|
+
> **STOP. Before you start ANY task, scan the colleague list below.**
|
|
18638
|
+
> If a colleague's delegation criteria match the current task, you MUST delegate to them via email **instead of doing it yourself**.
|
|
18639
|
+
> Do NOT attempt work that belongs to a colleague. Delegate first, then wait for their response or coordinate.
|
|
18640
|
+
|
|
18641
|
+
`;
|
|
18642
|
+
for (let i = 0;i < task.agent.colleagues.length; i++) {
|
|
18643
|
+
const c = task.agent.colleagues[i];
|
|
18644
|
+
content += `### ${c.name}${c.email ? ` (${c.email})` : ""}
|
|
18645
|
+
`;
|
|
18646
|
+
if (c.description)
|
|
18647
|
+
content += `${c.description}
|
|
18648
|
+
`;
|
|
18649
|
+
if (c.instruction)
|
|
18650
|
+
content += `**DELEGATE when:** ${resolveInstruction(c.instruction, task.agentId)}
|
|
18651
|
+
`;
|
|
18652
|
+
if (i < task.agent.colleagues.length - 1)
|
|
18653
|
+
content += `
|
|
18654
|
+
`;
|
|
18655
|
+
}
|
|
18656
|
+
content += `
|
|
18657
|
+
**Email threading rules:**
|
|
18658
|
+
- When communicating with a colleague on the **same topic** as an existing email thread, reply to that thread (use --in-reply-to) to keep context together.
|
|
18659
|
+
- **When starting a NEW topic or task that is unrelated to any previous email thread, you MUST compose a brand new email (do NOT use --in-reply-to). Never hijack an unrelated thread just because you recently emailed that colleague.** Judge by topic/task relevance, not by recency of communication.
|
|
18660
|
+
- Make sure to send follow-up emails to your colleagues to stop the previous wrong directions or instructions you sent before, don't make your colleague running for nothing.
|
|
18661
|
+
`;
|
|
18662
|
+
}
|
|
18663
|
+
content += `
|
|
18664
|
+
## Alook CLI Tools
|
|
18665
|
+
You can communicate with the world through Alook CLI.
|
|
18666
|
+
The CLI auto-detects your identity from the environment. No need to pass \`--agent_id\`.
|
|
18667
|
+
`;
|
|
18668
|
+
if (alookAddr || customAddrs.length > 0) {
|
|
18669
|
+
const lines = [];
|
|
18670
|
+
if (alookAddr)
|
|
18671
|
+
lines.push(`- '${alookAddr}' (default, Alook platform address)`);
|
|
18672
|
+
for (const a of customAddrs)
|
|
18673
|
+
lines.push(`- '${a}' (custom IMAP/SMTP mailbox)`);
|
|
18674
|
+
content += `
|
|
18675
|
+
Your email addresses:
|
|
18676
|
+
${lines.join(`
|
|
18677
|
+
`)}
|
|
18678
|
+
|
|
18679
|
+
|
|
18680
|
+
### Emails
|
|
18681
|
+
---
|
|
18682
|
+
Run '${cmdPrefix()} email pull --status unread' to download unread emails from inbox to '${tempDir("alook-emails")}/${task.workspaceId}/${task.agentId}/'.
|
|
18683
|
+
---
|
|
18684
|
+
To download sent emails, add '--folder sent': '${cmdPrefix()} email pull --folder sent'
|
|
18685
|
+
Valid folders: inbox (default), sent, untrust.
|
|
18686
|
+
To limit the number of emails downloaded, add '--limit <N>' (e.g. '--limit 20'). Use '--offset <N>' to skip emails for pagination.
|
|
18687
|
+
Example: '${cmdPrefix()} email pull --status unread --limit 20 --offset 0'
|
|
18688
|
+
---
|
|
18689
|
+
Each email is saved to '${tempDir("alook-emails")}/${task.workspaceId}/${task.agentId}/<emailId>/' with:
|
|
18690
|
+
- 'metadata.json' — sender, recipient, subject, date, status, message_id, in_reply_to, references
|
|
18691
|
+
- 'body.txt' — plain text body
|
|
18692
|
+
- 'body.html' — HTML body (if available)
|
|
18693
|
+
- 'attachments/' — extracted attachment files (if any)
|
|
18694
|
+
---
|
|
18695
|
+
Before starting to process an INBOX email, mark it as read:
|
|
18696
|
+
- Run '${cmdPrefix()} email set --email_id <EMAIL_ID> --status read'
|
|
18697
|
+
---
|
|
18698
|
+
|
|
18699
|
+
#### Sending a new email
|
|
18700
|
+
Write the HTML body to a file first, then send it. The body is forwarded as-is (HTML).
|
|
18701
|
+
- Run '${cmdPrefix()} email send --to <ADDRESS> --subject "<SUBJECT>" --body-file <PATH_TO_HTML>'
|
|
18702
|
+
- To send from a specific mailbox, add '--from <YOUR_EMAIL_ADDRESS>'. Without '--from', the default Alook address is used.
|
|
18703
|
+
- Attach files with '--attachment <PATH>' — repeat the flag for multiple attachments. Each file is uploaded before sending.
|
|
18704
|
+
- Example: '${cmdPrefix()} email send --to foo@bar.com --subject "Weekly report" --body-file /tmp/body.html --from alice@company.com --attachment /tmp/report.pdf'
|
|
18705
|
+
|
|
18706
|
+
#### Replying to an email
|
|
18707
|
+
To reply to an email, add '--in-reply-to <EMAIL_ID>' to the send command. This sets the correct email threading headers so the recipient's email client groups the reply into the same conversation thread.
|
|
18708
|
+
- Use 'Re: <original subject>' as the subject.
|
|
18709
|
+
- Quote the original email body in your reply (wrap it in a blockquote).
|
|
18710
|
+
- The <EMAIL_ID> is the Alook email id from metadata.json (not the message_id header).
|
|
18711
|
+
- Example: '${cmdPrefix()} email send --to sender@example.com --subject "Re: Bug report" --body-file /tmp/reply.html --in-reply-to <EMAIL_ID>'
|
|
18712
|
+
Tips:
|
|
18713
|
+
- If you think the task will take a while, consider sending a short "I'm on it" style email reply first to reassure the sender.
|
|
18714
|
+
---
|
|
18715
|
+
|
|
18716
|
+
#### Forwarding an email
|
|
18717
|
+
Forward any email to a new recipient, with an optional note prepended above the original content. All original attachments are re-attached automatically.
|
|
18718
|
+
- Run '${cmdPrefix()} email forward --email_id <EMAIL_ID> --to <RECIPIENT>'
|
|
18719
|
+
- Add '--note "FYI, see the request below."' to prepend a note above the forwarded body.
|
|
18720
|
+
- Add '--from <YOUR_EMAIL_ADDRESS>' to send from a specific mailbox.
|
|
18721
|
+
- Add '--attachment <PATH>' to attach extra files (repeatable).
|
|
18722
|
+
- Example: '${cmdPrefix()} email forward --email_id em_abc --to boss@company.com --note "FYI" --attachment /tmp/summary.pdf'
|
|
18723
|
+
---
|
|
18724
|
+
|
|
18725
|
+
#### Email Whitelist (Allowed Senders)
|
|
18726
|
+
Manage which email addresses are allowed to send you emails.
|
|
18727
|
+
- List: '${cmdPrefix()} email whitelist list' (add '--json' for machine-readable output)
|
|
18728
|
+
- Add: '${cmdPrefix()} email whitelist add <EMAIL_ADDRESS>'
|
|
18729
|
+
- Remove: '${cmdPrefix()} email whitelist delete <EMAIL_ADDRESS>'
|
|
18730
|
+
---
|
|
18731
|
+
`;
|
|
18732
|
+
}
|
|
18733
|
+
content += `
|
|
18734
|
+
### Artifacts
|
|
18735
|
+
Upload files for your owner to review in the app.
|
|
18736
|
+
- Your current conversation id is available via env var: $ALOOK_CONVERSATION_ID
|
|
18737
|
+
- Run '${cmdPrefix()} sync upload-artifact --conversation_id $ALOOK_CONVERSATION_ID --file <PATH>'
|
|
18738
|
+
- Use this after generating plans, reports, or any file the owner should review.
|
|
18739
|
+
- You response will be rendered in remote server, so don't output link format with local path in your response (cause user can click it and jump to nowheres)
|
|
18740
|
+
- If you think user may need to know any file detail, use upload-artifact tool to send the file to user.
|
|
18741
|
+
---
|
|
18742
|
+
|
|
18743
|
+
### Attachments
|
|
18744
|
+
When your task includes attachments, their local paths are listed in the prompt JSON under "attachments".
|
|
18745
|
+
Use your Read tool to open them. Images and PDFs are read visually.
|
|
18746
|
+
---
|
|
18747
|
+
`;
|
|
18748
|
+
content += `
|
|
18749
|
+
### Calendar
|
|
18750
|
+
You have your own calendar to setup daily routines and reminders.
|
|
18751
|
+
Schedule future tasks for yourself. At the scheduled time, a new task is dispatched to you with the event as the prompt (task type 'calendar_event').
|
|
18752
|
+
|
|
18753
|
+
!USE Calendar when you think the tasks are recurring or it should be conducted in the future.
|
|
18754
|
+
!When scheduling calendar events relative to a weekday (e.g. "every Monday"), always run date '+%A' first to confirm today's weekday before calculating the target date
|
|
18755
|
+
---
|
|
18756
|
+
Keep the event title informative and concise, less than 20 words.
|
|
18757
|
+
Place the event details in description.
|
|
18758
|
+
Create a one-off event:
|
|
18759
|
+
- Run '${cmdPrefix()} calendar set --event_title "<TASK_TITLE>" --description "<TASK_BODY>" --datetime <YYYY-MM-DDTHH:MM>'
|
|
18760
|
+
- '--datetime' is LOCAL time, format 'YYYY-MM-DDTHH:MM' (e.g. '2026-04-17T09:30'). Do NOT pass UTC / ISO strings with 'Z'.
|
|
18761
|
+
- '--event_title' becomes the task prompt when the event fires — write it as the instruction you want future-you to receive.
|
|
18762
|
+
|
|
18763
|
+
Create a repeating event:
|
|
18764
|
+
- Add '--repeat <interval>' where interval is like '1day', '2hour', '1week', '1month'.
|
|
18765
|
+
- Optionally add '--repeat_stop_date <YYYY-MM-DD>' to stop the recurrence (local date).
|
|
18766
|
+
- Example: '${cmdPrefix()} calendar set --event_title "<REPEAT_TASK_TITLE>" --description "<REPEAT_TASK_BODY>" --datetime 2026-04-18T09:00 --repeat 1day --repeat_stop_date 2026-05-18'
|
|
18767
|
+
---
|
|
18768
|
+
List upcoming events:
|
|
18769
|
+
- Run '${cmdPrefix()} calendar list' (defaults: next 30 days, past 0 days).
|
|
18770
|
+
- Tune the window with '--future_days <N>' and '--past_days <N>'. Add '--json' for machine-readable output.
|
|
18771
|
+
- 'list' shows a '[has description]' badge instead of the full description — use 'show' (below) to read it.
|
|
18772
|
+
|
|
18773
|
+
Show full detail of one event (use this to read the description):
|
|
18774
|
+
- Run '${cmdPrefix()} calendar show --event_id <EVENT_ID>'
|
|
18775
|
+
- Add '--json' for machine-readable output.
|
|
18776
|
+
|
|
18777
|
+
Edit an existing event (preserves event id and recurring state):
|
|
18778
|
+
- Run '${cmdPrefix()} calendar update --event_id <EVENT_ID> [flags]'
|
|
18779
|
+
- Supply only the fields you want to change. Available flags:
|
|
18780
|
+
- '--event_title "<t>"' — rename the event / change the fire-time prompt
|
|
18781
|
+
- '--description "<d>"' to set, or '--clear_description' to remove
|
|
18782
|
+
- '--datetime <YYYY-MM-DDTHH:MM>' — reschedule (local time)
|
|
18783
|
+
- '--repeat <interval>' to set, or '--clear_repeat' to convert into a one-off
|
|
18784
|
+
- '--repeat_stop_date <YYYY-MM-DD>' to set, or '--clear_repeat_stop_date' to remove
|
|
18785
|
+
- Passing no mutating flag is an error. Do NOT use 'delete' + 'set' to edit — that loses the event id and the recurring 'last fired' state.
|
|
18786
|
+
|
|
18787
|
+
Delete an event:
|
|
18788
|
+
- Run '${cmdPrefix()} calendar delete --event_id <EVENT_ID>'
|
|
18789
|
+
---
|
|
18790
|
+
`;
|
|
18791
|
+
return content;
|
|
18792
|
+
}
|
|
18793
|
+
function contentHash(content) {
|
|
18794
|
+
return createHash("sha256").update(content, "utf-8").digest("hex");
|
|
18795
|
+
}
|
|
18796
|
+
function hasContentChanged(filePath, newContent) {
|
|
18797
|
+
try {
|
|
18798
|
+
const existing = readFileSync4(filePath, "utf-8");
|
|
18799
|
+
return contentHash(existing) !== contentHash(newContent);
|
|
18800
|
+
} catch (err) {
|
|
18801
|
+
if (err?.code === "ENOENT")
|
|
18802
|
+
return true;
|
|
18803
|
+
throw err;
|
|
18804
|
+
}
|
|
18805
|
+
}
|
|
18806
|
+
function ensureSymlinks(workDir) {
|
|
18807
|
+
const canonicalPath = join5(workDir, CANONICAL_FILE);
|
|
18808
|
+
if (!existsSync(canonicalPath))
|
|
18809
|
+
return;
|
|
18810
|
+
for (const alias of SYMLINK_ALIASES) {
|
|
18811
|
+
if (alias === CANONICAL_FILE)
|
|
18812
|
+
continue;
|
|
18813
|
+
const aliasPath = join5(workDir, alias);
|
|
18814
|
+
try {
|
|
18815
|
+
const stat = lstatSync(aliasPath);
|
|
18816
|
+
if (stat.isSymbolicLink()) {
|
|
18817
|
+
const target = readlinkSync(aliasPath);
|
|
18818
|
+
if (target === CANONICAL_FILE)
|
|
18819
|
+
continue;
|
|
18820
|
+
unlinkSync2(aliasPath);
|
|
18821
|
+
} else {
|
|
18822
|
+
const aliasContent = readFileSync4(aliasPath, "utf-8");
|
|
18823
|
+
const canonicalContent = readFileSync4(canonicalPath, "utf-8");
|
|
18824
|
+
if (aliasContent === canonicalContent)
|
|
18825
|
+
continue;
|
|
18826
|
+
unlinkSync2(aliasPath);
|
|
18827
|
+
}
|
|
18828
|
+
} catch (err) {
|
|
18829
|
+
if (err?.code !== "ENOENT")
|
|
18830
|
+
throw err;
|
|
18831
|
+
}
|
|
18832
|
+
try {
|
|
18833
|
+
symlinkSync(CANONICAL_FILE, aliasPath);
|
|
18834
|
+
} catch (err) {
|
|
18835
|
+
const code = err?.code;
|
|
18836
|
+
if (code === "EPERM" || code === "EACCES") {
|
|
18837
|
+
copyFileSync(canonicalPath, aliasPath);
|
|
18838
|
+
} else {
|
|
18839
|
+
throw err;
|
|
18840
|
+
}
|
|
18841
|
+
}
|
|
18842
|
+
}
|
|
18843
|
+
}
|
|
18844
|
+
function writeInstructionFileIfChanged(workDir, task) {
|
|
18845
|
+
const content = buildInstructionContent(task);
|
|
18846
|
+
const filePath = join5(workDir, CANONICAL_FILE);
|
|
18847
|
+
const changed = hasContentChanged(filePath, content);
|
|
18848
|
+
if (changed) {
|
|
18849
|
+
writeFileSync3(filePath, content, "utf-8");
|
|
18850
|
+
}
|
|
18851
|
+
ensureSymlinks(workDir);
|
|
18852
|
+
return changed;
|
|
18853
|
+
}
|
|
18854
|
+
|
|
18855
|
+
// daemon/execenv/index.ts
|
|
18856
|
+
function prepare(config2, task) {
|
|
18857
|
+
const workDir = join6(config2.workspacesRoot, task.workspaceId, task.agentId, "workdir");
|
|
18858
|
+
mkdirSync3(workDir, { recursive: true });
|
|
18859
|
+
const timelineDir = join6(workDir, ".context_timeline");
|
|
18860
|
+
mkdirSync3(timelineDir, { recursive: true });
|
|
18861
|
+
writeInstructionFileIfChanged(workDir, task);
|
|
18862
|
+
const env = {
|
|
18863
|
+
ALOOK_WORKSPACE_ID: task.workspaceId,
|
|
18864
|
+
ALOOK_AGENT_ID: task.agentId,
|
|
18865
|
+
ALOOK_TASK_ID: task.id,
|
|
18866
|
+
ALOOK_CONVERSATION_ID: task.conversationId,
|
|
18867
|
+
ALOOK_TRACE_ID: task.traceId ?? "",
|
|
18868
|
+
ALOOK_CHANNEL: task.channel ?? "default",
|
|
18869
|
+
ALOOK_HEALTH_PORT: process.env.ALOOK_HEALTH_PORT || "19514"
|
|
18870
|
+
};
|
|
18871
|
+
return { workDir, timelineDir, env };
|
|
18872
|
+
}
|
|
18873
|
+
|
|
18874
|
+
// daemon/execenv/timeline.ts
|
|
18875
|
+
import { appendFileSync, readFileSync as readFileSync5, writeFileSync as writeFileSync4, renameSync } from "fs";
|
|
18876
|
+
import { join as join7 } from "path";
|
|
18877
|
+
|
|
18878
|
+
// daemon/execenv/filelock.ts
|
|
18879
|
+
import { mkdirSync as mkdirSync4, rmdirSync, statSync } from "fs";
|
|
18880
|
+
var DEFAULT_STALE_MS = 3600000;
|
|
18881
|
+
function acquireLock(lockPath, staleMs = DEFAULT_STALE_MS) {
|
|
18882
|
+
try {
|
|
18883
|
+
mkdirSync4(lockPath);
|
|
18884
|
+
return true;
|
|
18885
|
+
} catch {
|
|
18886
|
+
try {
|
|
18887
|
+
const stat = statSync(lockPath);
|
|
18888
|
+
if (Date.now() - stat.mtimeMs > staleMs) {
|
|
18889
|
+
rmdirSync(lockPath);
|
|
18890
|
+
try {
|
|
18891
|
+
mkdirSync4(lockPath);
|
|
18892
|
+
return true;
|
|
18893
|
+
} catch {
|
|
18894
|
+
return false;
|
|
18895
|
+
}
|
|
18896
|
+
}
|
|
18897
|
+
} catch {
|
|
18898
|
+
try {
|
|
18899
|
+
mkdirSync4(lockPath);
|
|
18900
|
+
return true;
|
|
18901
|
+
} catch {
|
|
18902
|
+
return false;
|
|
18903
|
+
}
|
|
18904
|
+
}
|
|
18905
|
+
return false;
|
|
18906
|
+
}
|
|
18907
|
+
}
|
|
18908
|
+
function releaseLock(lockPath) {
|
|
18909
|
+
try {
|
|
18910
|
+
rmdirSync(lockPath);
|
|
18911
|
+
} catch {}
|
|
18912
|
+
}
|
|
18913
|
+
|
|
18914
|
+
// daemon/execenv/timeline.ts
|
|
18915
|
+
var log3 = createLogger2({ module: "timeline" });
|
|
18916
|
+
function readJsonl(filePath) {
|
|
18917
|
+
let content;
|
|
18918
|
+
try {
|
|
18919
|
+
content = readFileSync5(filePath, "utf-8");
|
|
18920
|
+
} catch {
|
|
18921
|
+
return [];
|
|
18922
|
+
}
|
|
18923
|
+
const entries = [];
|
|
18924
|
+
for (const line of content.trimEnd().split(`
|
|
18925
|
+
`)) {
|
|
18926
|
+
if (!line)
|
|
18927
|
+
continue;
|
|
18928
|
+
try {
|
|
18929
|
+
entries.push(JSON.parse(line));
|
|
18930
|
+
} catch {}
|
|
18931
|
+
}
|
|
18932
|
+
return entries;
|
|
18933
|
+
}
|
|
18934
|
+
function filenameForDate(date5) {
|
|
18935
|
+
const y = date5.getFullYear();
|
|
18936
|
+
const m = String(date5.getMonth() + 1).padStart(2, "0");
|
|
18937
|
+
const d = String(date5.getDate()).padStart(2, "0");
|
|
18938
|
+
return `${y}-${m}-${d}.jsonl`;
|
|
18939
|
+
}
|
|
18940
|
+
function todayFilename() {
|
|
18941
|
+
return filenameForDate(new Date);
|
|
18942
|
+
}
|
|
18943
|
+
function recentFilenames(maxDays) {
|
|
18944
|
+
const filenames = [];
|
|
18945
|
+
const now = new Date;
|
|
18946
|
+
for (let i = 0;i < maxDays; i++) {
|
|
18947
|
+
const d = new Date(now);
|
|
18948
|
+
d.setDate(d.getDate() - i);
|
|
18949
|
+
filenames.push(filenameForDate(d));
|
|
18950
|
+
}
|
|
18951
|
+
return filenames;
|
|
18952
|
+
}
|
|
18953
|
+
function localISOString() {
|
|
18954
|
+
const now = new Date;
|
|
18955
|
+
const tzOffset = -now.getTimezoneOffset();
|
|
18956
|
+
const sign = tzOffset >= 0 ? "+" : "-";
|
|
18957
|
+
const absOffset = Math.abs(tzOffset);
|
|
18958
|
+
const hh = String(Math.floor(absOffset / 60)).padStart(2, "0");
|
|
18959
|
+
const mm = String(absOffset % 60).padStart(2, "0");
|
|
18960
|
+
const y = now.getFullYear();
|
|
18961
|
+
const mo = String(now.getMonth() + 1).padStart(2, "0");
|
|
18962
|
+
const d = String(now.getDate()).padStart(2, "0");
|
|
18963
|
+
const h = String(now.getHours()).padStart(2, "0");
|
|
18964
|
+
const mi = String(now.getMinutes()).padStart(2, "0");
|
|
18965
|
+
const s = String(now.getSeconds()).padStart(2, "0");
|
|
18966
|
+
return `${y}-${mo}-${d}T${h}:${mi}:${s}${sign}${hh}:${mm}`;
|
|
18967
|
+
}
|
|
18968
|
+
function lockPathFor(timelineDir, filename) {
|
|
18969
|
+
return join7(timelineDir, `.${filename}.lock`);
|
|
18970
|
+
}
|
|
18971
|
+
function sleep(ms) {
|
|
18972
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
18973
|
+
}
|
|
18974
|
+
async function initEntryAsync(timelineDir, entry) {
|
|
18975
|
+
const filename = todayFilename();
|
|
18976
|
+
const filePath = join7(timelineDir, filename);
|
|
18977
|
+
const lockPath = lockPathFor(timelineDir, filename);
|
|
18978
|
+
try {
|
|
18979
|
+
let acquired = acquireLock(lockPath);
|
|
18980
|
+
if (!acquired) {
|
|
18981
|
+
await sleep(200);
|
|
18982
|
+
acquired = acquireLock(lockPath);
|
|
18983
|
+
}
|
|
18984
|
+
if (!acquired) {
|
|
18985
|
+
log3.debug(`Timeline initEntry: could not acquire lock for ${filename}`);
|
|
18986
|
+
return;
|
|
18987
|
+
}
|
|
18988
|
+
try {
|
|
18989
|
+
appendFileSync(filePath, JSON.stringify(entry) + `
|
|
18990
|
+
`);
|
|
18991
|
+
} finally {
|
|
18992
|
+
releaseLock(lockPath);
|
|
18993
|
+
}
|
|
18994
|
+
} catch (err) {
|
|
18995
|
+
log3.debug("Timeline initEntry failed", err);
|
|
18996
|
+
}
|
|
18997
|
+
}
|
|
18998
|
+
function updateEntry(timelineDir, taskId, updater) {
|
|
18999
|
+
for (const filename of recentFilenames(7)) {
|
|
19000
|
+
const filePath = join7(timelineDir, filename);
|
|
19001
|
+
const lockPath = lockPathFor(timelineDir, filename);
|
|
19002
|
+
try {
|
|
19003
|
+
const acquired = acquireLock(lockPath);
|
|
19004
|
+
if (!acquired) {
|
|
19005
|
+
log3.debug(`Timeline updateEntry: lock held for ${filename}, skipping`);
|
|
19006
|
+
continue;
|
|
19007
|
+
}
|
|
19008
|
+
try {
|
|
19009
|
+
let content;
|
|
19010
|
+
try {
|
|
19011
|
+
content = readFileSync5(filePath, "utf-8");
|
|
19012
|
+
} catch {
|
|
19013
|
+
continue;
|
|
19014
|
+
}
|
|
19015
|
+
const lines = content.trimEnd().split(`
|
|
19016
|
+
`);
|
|
19017
|
+
let found = false;
|
|
19018
|
+
const updated = lines.map((line) => {
|
|
19019
|
+
const entry = JSON.parse(line);
|
|
19020
|
+
if (entry.task_id === taskId) {
|
|
19021
|
+
found = true;
|
|
19022
|
+
updater(entry);
|
|
19023
|
+
}
|
|
19024
|
+
return JSON.stringify(entry);
|
|
19025
|
+
});
|
|
19026
|
+
if (!found)
|
|
19027
|
+
continue;
|
|
19028
|
+
const tmpPath = join7(timelineDir, `.${filename}.tmp`);
|
|
19029
|
+
writeFileSync4(tmpPath, updated.join(`
|
|
19030
|
+
`) + `
|
|
19031
|
+
`);
|
|
19032
|
+
renameSync(tmpPath, filePath);
|
|
19033
|
+
return;
|
|
19034
|
+
} finally {
|
|
19035
|
+
releaseLock(lockPath);
|
|
19036
|
+
}
|
|
19037
|
+
} catch (err) {
|
|
19038
|
+
log3.debug(`Timeline updateEntry failed for ${filename}`, err);
|
|
19039
|
+
}
|
|
19040
|
+
}
|
|
19041
|
+
log3.debug(`Timeline updateEntry: task_id ${taskId} not found in last 7 days`);
|
|
19042
|
+
}
|
|
19043
|
+
function createTimelineEntry(taskId, prompt, type, sessionId, pid, provider, contextKey, detailedLog) {
|
|
19044
|
+
return {
|
|
19045
|
+
task_id: taskId,
|
|
19046
|
+
context_key: contextKey ?? null,
|
|
19047
|
+
session_id: sessionId || null,
|
|
19048
|
+
pid: pid ?? null,
|
|
19049
|
+
status: "running",
|
|
19050
|
+
datetime: localISOString(),
|
|
19051
|
+
type,
|
|
19052
|
+
prompt,
|
|
19053
|
+
agent_responses: [],
|
|
19054
|
+
errmsg: null,
|
|
19055
|
+
provider: provider ?? null,
|
|
19056
|
+
detailed_log: detailedLog ?? null
|
|
19057
|
+
};
|
|
19058
|
+
}
|
|
19059
|
+
var RESUME_MAX_AGE_MS = 72 * 60 * 60 * 1000;
|
|
19060
|
+
function findResumableSessionByContextKey(timelineDir, contextKey, provider) {
|
|
19061
|
+
const now = new Date;
|
|
19062
|
+
const cutoff = new Date(now.getTime() - RESUME_MAX_AGE_MS);
|
|
19063
|
+
const entries = [];
|
|
19064
|
+
for (const filename of recentFilenames(7)) {
|
|
19065
|
+
entries.push(...readJsonl(join7(timelineDir, filename)));
|
|
19066
|
+
}
|
|
19067
|
+
entries.sort((a, b) => new Date(b.datetime).getTime() - new Date(a.datetime).getTime());
|
|
19068
|
+
for (const entry of entries) {
|
|
19069
|
+
if (entry.status !== "running" && entry.context_key === contextKey && entry.provider === provider && entry.session_id && new Date(entry.datetime) >= cutoff) {
|
|
19070
|
+
return entry.session_id;
|
|
19071
|
+
}
|
|
19072
|
+
}
|
|
19073
|
+
return null;
|
|
19074
|
+
}
|
|
19075
|
+
function findRunningPidByTaskId(timelineDir, taskId) {
|
|
19076
|
+
for (const filename of recentFilenames(7)) {
|
|
19077
|
+
const entries = readJsonl(join7(timelineDir, filename));
|
|
19078
|
+
for (const entry of entries) {
|
|
19079
|
+
if (entry.task_id === taskId && entry.status === "running" && entry.pid != null) {
|
|
19080
|
+
return entry.pid;
|
|
19081
|
+
}
|
|
19082
|
+
}
|
|
19083
|
+
}
|
|
19084
|
+
return null;
|
|
19085
|
+
}
|
|
19086
|
+
function findRunningEntryByContextKey(timelineDir, contextKey, provider) {
|
|
19087
|
+
for (const filename of recentFilenames(7)) {
|
|
19088
|
+
const dayEntries = readJsonl(join7(timelineDir, filename));
|
|
19089
|
+
for (let i = dayEntries.length - 1;i >= 0; i--) {
|
|
19090
|
+
const entry = dayEntries[i];
|
|
19091
|
+
if (entry.status === "running" && entry.context_key === contextKey && entry.provider === provider) {
|
|
19092
|
+
return entry;
|
|
19093
|
+
}
|
|
19094
|
+
}
|
|
19095
|
+
}
|
|
19096
|
+
return null;
|
|
19097
|
+
}
|
|
19098
|
+
|
|
19099
|
+
// daemon/execenv/steering.ts
|
|
19100
|
+
import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync5, readFileSync as readFileSync6, unlinkSync as unlinkSync3, readdirSync, statSync as statSync2 } from "fs";
|
|
19101
|
+
import { join as join8 } from "path";
|
|
19102
|
+
var log4 = createLogger2({ module: "steering" });
|
|
19103
|
+
var INTENT_DIR_NAME = ".kill_intents";
|
|
19104
|
+
var STEERING_LOCK_DIR = ".steering_locks";
|
|
19105
|
+
var INTENT_STALE_MS = 10 * 60 * 1000;
|
|
19106
|
+
function intentFilePath(baseDir, taskId) {
|
|
19107
|
+
return join8(baseDir, INTENT_DIR_NAME, `${taskId}.json`);
|
|
19108
|
+
}
|
|
19109
|
+
function intentDirPath(baseDir) {
|
|
19110
|
+
return join8(baseDir, INTENT_DIR_NAME);
|
|
19111
|
+
}
|
|
19112
|
+
function steeringLockPath(baseDir, contextKey) {
|
|
19113
|
+
const safeKey = contextKey.replace(/[^a-zA-Z0-9_:-]/g, "_");
|
|
19114
|
+
return join8(baseDir, STEERING_LOCK_DIR, safeKey);
|
|
19115
|
+
}
|
|
19116
|
+
function writeKillIntent(baseDir, intent) {
|
|
19117
|
+
const dir = intentDirPath(baseDir);
|
|
19118
|
+
try {
|
|
19119
|
+
mkdirSync5(dir, { recursive: true });
|
|
19120
|
+
} catch {}
|
|
19121
|
+
const filePath = intentFilePath(baseDir, intent.targetTaskId);
|
|
19122
|
+
writeFileSync5(filePath, JSON.stringify(intent));
|
|
19123
|
+
}
|
|
19124
|
+
function readKillIntent(baseDir, taskId) {
|
|
19125
|
+
const filePath = intentFilePath(baseDir, taskId);
|
|
19126
|
+
try {
|
|
19127
|
+
const content = readFileSync6(filePath, "utf-8");
|
|
19128
|
+
return JSON.parse(content);
|
|
19129
|
+
} catch {
|
|
19130
|
+
return null;
|
|
19131
|
+
}
|
|
19132
|
+
}
|
|
19133
|
+
function clearKillIntent(baseDir, taskId) {
|
|
19134
|
+
const filePath = intentFilePath(baseDir, taskId);
|
|
19135
|
+
try {
|
|
19136
|
+
unlinkSync3(filePath);
|
|
19137
|
+
} catch {}
|
|
19138
|
+
}
|
|
19139
|
+
function cleanupStaleIntents(baseDir) {
|
|
19140
|
+
const dir = intentDirPath(baseDir);
|
|
19141
|
+
let files;
|
|
19142
|
+
try {
|
|
19143
|
+
files = readdirSync(dir).filter((f) => f.endsWith(".json"));
|
|
19144
|
+
} catch {
|
|
19145
|
+
return;
|
|
19146
|
+
}
|
|
19147
|
+
const now = Date.now();
|
|
19148
|
+
for (const file2 of files) {
|
|
19149
|
+
const filePath = join8(dir, file2);
|
|
19150
|
+
try {
|
|
19151
|
+
const content = readFileSync6(filePath, "utf-8");
|
|
19152
|
+
const intent = JSON.parse(content);
|
|
19153
|
+
const stat = statSync2(filePath);
|
|
19154
|
+
if (now - stat.mtimeMs > INTENT_STALE_MS) {
|
|
19155
|
+
unlinkSync3(filePath);
|
|
19156
|
+
log4.debug(`Cleaned up stale kill intent for task ${intent.targetTaskId}`);
|
|
19157
|
+
}
|
|
19158
|
+
} catch {}
|
|
19159
|
+
}
|
|
19160
|
+
}
|
|
19161
|
+
function acquireSteeringLock(baseDir, contextKey) {
|
|
19162
|
+
const lockPath = steeringLockPath(baseDir, contextKey);
|
|
19163
|
+
try {
|
|
19164
|
+
mkdirSync5(join8(baseDir, STEERING_LOCK_DIR), { recursive: true });
|
|
19165
|
+
} catch {}
|
|
19166
|
+
return acquireLock(lockPath, 60000);
|
|
19167
|
+
}
|
|
19168
|
+
function releaseSteeringLock(baseDir, contextKey) {
|
|
19169
|
+
const lockPath = steeringLockPath(baseDir, contextKey);
|
|
19170
|
+
releaseLock(lockPath);
|
|
19171
|
+
}
|
|
19172
|
+
|
|
19173
|
+
// daemon/prompt.ts
|
|
19174
|
+
var DM_RESPONSE_NOTICE = "IMPORTANT: Only your final text response is visible to the user." + " Tool calls, intermediate reasoning, and mid-process outputs are NOT displayed." + " Put all key information, answers, and conclusions in your final response — that is the only thing the user will read.";
|
|
19175
|
+
var EMAIL_NOTICE = "This task was triggered automatically by an incoming email. There is no human in this session." + " If you need to communicate with a human, you MUST send an email using the email sending tool." + " If you need more information or confirmation from the human, send them an email asking for it and then exit." + " Do not wait — when the human replies, a new task will be triggered automatically and you will be woken up with their response.";
|
|
19176
|
+
var ISSUE_NOTICE = "This task was triggered by an assigned issue. The issue_id is provided in this message." + " Use `alook issue show --issue_id <issue_id>` to read full context." + " Use `alook issue update --issue_id <issue_id> --status <status>` to change status." + " Use `alook issue comment --issue_id <issue_id> --body <text>` to leave a comment." + " CRITICAL — You MUST manage the issue status correctly. This is NOT optional:" + " 1. Set status to 'in_progress' when you start working." + " 2. If you complete the work yourself: leave a summary comment, then set status to 'review' as your last action. 'review' means there is actual completed work (code, artifact, result) ready for the owner to look at." + " 3. If you delegated work to colleagues and are waiting for their response: KEEP status as 'in_progress' and exit. This is expected — you will be woken up when they reply. Set 'review' only after all delegated work is confirmed complete." + " 4. NEVER set 'review' unless there is concrete completed work for the owner to review. Sending a plan to a colleague is NOT completed work." + " NEVER exit without doing at least one of: updating the status, or leaving a comment explaining what you did and what you're waiting for.";
|
|
19177
|
+
function buildDmNotice(name, email3) {
|
|
19178
|
+
return `This task was triggered by an incoming email on a conversation with ${name} (${email3}).` + ` ${name} is present in this session — reply to them directly.` + ` If you need to communicate with anyone else, use the email sending tool.`;
|
|
19179
|
+
}
|
|
19180
|
+
function buildPrompt(task, attachments) {
|
|
19181
|
+
const obj = { type: task.type, instruction: task.prompt };
|
|
19182
|
+
if (task.type === "user_dm_message") {
|
|
19183
|
+
obj.notice = DM_RESPONSE_NOTICE;
|
|
19184
|
+
}
|
|
19185
|
+
if (task.type === "email_notification") {
|
|
19186
|
+
const ctx = task.context;
|
|
19187
|
+
const dmUser = ctx?.dmUser;
|
|
19188
|
+
if (ctx?.conversationType === "user_dm_message" && dmUser) {
|
|
19189
|
+
obj.notice = buildDmNotice(dmUser.name, dmUser.email);
|
|
19190
|
+
} else {
|
|
19191
|
+
obj.notice = EMAIL_NOTICE;
|
|
19192
|
+
}
|
|
19193
|
+
}
|
|
19194
|
+
if (task.type === "issue_event") {
|
|
19195
|
+
obj.notice = ISSUE_NOTICE;
|
|
19196
|
+
const ctx = task.context;
|
|
19197
|
+
if (ctx?.issue_id) {
|
|
19198
|
+
obj.issue_id = ctx.issue_id;
|
|
19199
|
+
}
|
|
19200
|
+
}
|
|
19201
|
+
if (task.sender) {
|
|
19202
|
+
obj.sender = {
|
|
19203
|
+
name: task.sender.name,
|
|
19204
|
+
email: task.sender.email,
|
|
19205
|
+
is_owner: task.sender.isOwner
|
|
19206
|
+
};
|
|
19207
|
+
}
|
|
19208
|
+
if (attachments && attachments.length > 0) {
|
|
19209
|
+
obj.attachments = attachments.map((a) => ({
|
|
19210
|
+
path: a.path,
|
|
19211
|
+
content_type: a.content_type,
|
|
19212
|
+
filename: a.filename
|
|
19213
|
+
}));
|
|
19214
|
+
}
|
|
19215
|
+
return JSON.stringify(obj);
|
|
19216
|
+
}
|
|
19217
|
+
|
|
19218
|
+
// daemon/session-runner.ts
|
|
19219
|
+
var log5 = createLogger2({ module: "session-runner" });
|
|
19220
|
+
var ATTACHMENTS_BASE = tempDir("alook-attachments");
|
|
19221
|
+
async function writeMarkerFile(workspacesRoot, marker) {
|
|
19222
|
+
const dir = path.join(workspacesRoot, ".pending_completions");
|
|
19223
|
+
await mkdir(dir, { recursive: true, mode: 448 });
|
|
19224
|
+
const tmpPath = path.join(dir, `${marker.taskId}.tmp`);
|
|
19225
|
+
const finalPath = path.join(dir, `${marker.taskId}.json`);
|
|
19226
|
+
await writeFile(tmpPath, JSON.stringify(marker), { mode: 384 });
|
|
19227
|
+
await rename(tmpPath, finalPath);
|
|
19228
|
+
}
|
|
19229
|
+
function isRetryableError(e) {
|
|
19230
|
+
if (!(e instanceof Error))
|
|
19231
|
+
return true;
|
|
19232
|
+
const msg = e.message;
|
|
19233
|
+
if (/ECONNREFUSED|ECONNRESET|ETIMEDOUT|ENETUNREACH|EHOSTUNREACH|EAI_AGAIN/.test(msg))
|
|
19234
|
+
return true;
|
|
19235
|
+
const httpMatch = msg.match(/^HTTP (\d+):/);
|
|
19236
|
+
if (httpMatch) {
|
|
19237
|
+
const status = Number(httpMatch[1]);
|
|
19238
|
+
if (status >= 500)
|
|
19239
|
+
return true;
|
|
19240
|
+
if (status === 408 || status === 429)
|
|
19241
|
+
return true;
|
|
19242
|
+
return false;
|
|
19243
|
+
}
|
|
19244
|
+
return true;
|
|
19245
|
+
}
|
|
19246
|
+
function isClientError(e) {
|
|
19247
|
+
if (!(e instanceof Error))
|
|
19248
|
+
return false;
|
|
19249
|
+
const match = e.message.match(/^HTTP (\d+):/);
|
|
19250
|
+
if (!match)
|
|
19251
|
+
return false;
|
|
19252
|
+
const status = Number(match[1]);
|
|
19253
|
+
if (status === 408 || status === 429)
|
|
19254
|
+
return false;
|
|
19255
|
+
return status >= 400 && status < 500;
|
|
19256
|
+
}
|
|
19257
|
+
async function reportToServer(fn, markerData, workspacesRoot) {
|
|
19258
|
+
const RETRY_DELAYS = [1000, 3000, 9000];
|
|
19259
|
+
let lastErr;
|
|
19260
|
+
for (let attempt = 0;attempt <= RETRY_DELAYS.length; attempt++) {
|
|
19261
|
+
try {
|
|
19262
|
+
await fn();
|
|
19263
|
+
return;
|
|
19264
|
+
} catch (e) {
|
|
19265
|
+
lastErr = e;
|
|
19266
|
+
if (isClientError(e)) {
|
|
19267
|
+
log5.info(`server report for task ${markerData.taskId}: task already in terminal state (${e})`);
|
|
19268
|
+
return;
|
|
19269
|
+
}
|
|
19270
|
+
if (attempt < RETRY_DELAYS.length && isRetryableError(e)) {
|
|
19271
|
+
log5.debug(`server report attempt ${attempt + 1} failed for task ${markerData.taskId}, retrying in ${RETRY_DELAYS[attempt]}ms`);
|
|
19272
|
+
await new Promise((r) => setTimeout(r, RETRY_DELAYS[attempt]));
|
|
19273
|
+
}
|
|
19274
|
+
}
|
|
19275
|
+
}
|
|
19276
|
+
log5.warn(`server report failed for task ${markerData.taskId} after retries, writing marker: ${lastErr}`);
|
|
19277
|
+
try {
|
|
19278
|
+
await writeMarkerFile(workspacesRoot, markerData);
|
|
19279
|
+
} catch (writeErr) {
|
|
19280
|
+
log5.error(`marker write also failed for task ${markerData.taskId}: ${writeErr}`);
|
|
19281
|
+
}
|
|
19282
|
+
}
|
|
19283
|
+
function sanitizeFilename(name) {
|
|
19284
|
+
return path.basename(name).replace(/[/\\]/g, "_").replace(/\.\./g, "_").slice(0, 255) || "file";
|
|
19285
|
+
}
|
|
19286
|
+
async function cleanupAttachments(taskId) {
|
|
19287
|
+
try {
|
|
19288
|
+
await rm(path.join(ATTACHMENTS_BASE, taskId), { recursive: true, force: true });
|
|
19289
|
+
} catch {}
|
|
19290
|
+
}
|
|
19291
|
+
async function downloadAttachments(client, token, workspaceId, taskId, attachmentIds) {
|
|
19292
|
+
const dir = path.join(ATTACHMENTS_BASE, taskId);
|
|
19293
|
+
await mkdir(dir, { recursive: true });
|
|
19294
|
+
const attachments = [];
|
|
19295
|
+
for (const artId of attachmentIds) {
|
|
19296
|
+
const meta3 = await client.getArtifactMeta(token, artId, workspaceId);
|
|
19297
|
+
const content = await client.downloadArtifact(token, artId, workspaceId);
|
|
19298
|
+
const filename = sanitizeFilename(meta3.filename);
|
|
19299
|
+
const localPath = path.join(dir, `${artId}_${filename}`);
|
|
19300
|
+
await writeFile(localPath, Buffer.from(content));
|
|
19301
|
+
attachments.push({
|
|
19302
|
+
path: localPath,
|
|
19303
|
+
content_type: meta3.content_type,
|
|
19304
|
+
filename: meta3.filename
|
|
19305
|
+
});
|
|
19306
|
+
}
|
|
19307
|
+
return attachments;
|
|
19308
|
+
}
|
|
19309
|
+
async function runSession(input) {
|
|
19310
|
+
const { task, provider, cliPath, model, serverURL, token, workspacesRoot, agentTimeout, messageInactivityTimeout } = input;
|
|
19311
|
+
log5.info(`starting (task=${task.id}, type=${task.type}, agent=${task.agentId}, provider=${provider}, model=${model || "default"})`);
|
|
19312
|
+
const client = new DaemonClient(serverURL);
|
|
19313
|
+
const backend = createBackend(provider, cliPath);
|
|
19314
|
+
const agentBaseDir = path.join(workspacesRoot, task.workspaceId, task.agentId, "workdir");
|
|
19315
|
+
const timelineDir = path.join(agentBaseDir, ".context_timeline").replace(/\\/g, "/");
|
|
19316
|
+
mkdirSync6(timelineDir, { recursive: true });
|
|
19317
|
+
await initEntryAsync(timelineDir, createTimelineEntry(task.id, task.prompt, task.type, undefined, process.pid, provider, task.contextKey, input.logFilePath));
|
|
19318
|
+
const { workDir, env } = prepare({ workspacesRoot }, task);
|
|
19319
|
+
let killed = false;
|
|
19320
|
+
const earlyOnKill = async () => {
|
|
19321
|
+
if (killed)
|
|
19322
|
+
return;
|
|
19323
|
+
killed = true;
|
|
19324
|
+
log5.info(`killed by signal (messages=0, tools=0)`);
|
|
19325
|
+
await cleanupAttachments(task.id);
|
|
19326
|
+
const intent = readKillIntent(agentBaseDir, task.id);
|
|
19327
|
+
clearKillIntent(agentBaseDir, task.id);
|
|
19328
|
+
if (intent?.reason === "superseded") {
|
|
19329
|
+
updateEntry(timelineDir, task.id, (entry) => {
|
|
19330
|
+
entry.pid = null;
|
|
19331
|
+
entry.status = "superseded";
|
|
19332
|
+
entry.successor_task_id = intent.successorTaskId ?? null;
|
|
19333
|
+
entry.supersede_reason = "superseded by newer task";
|
|
19334
|
+
});
|
|
19335
|
+
try {
|
|
19336
|
+
await client.supersedeTask(token, task.id);
|
|
19337
|
+
} catch {}
|
|
19338
|
+
} else if (intent?.reason === "cancelled") {
|
|
19339
|
+
updateEntry(timelineDir, task.id, (entry) => {
|
|
19340
|
+
entry.pid = null;
|
|
19341
|
+
entry.status = "cancelled";
|
|
19342
|
+
entry.errmsg = "cancelled by user";
|
|
19343
|
+
});
|
|
19344
|
+
await reportToServer(() => client.failTask(token, task.id, "cancelled by user"), { taskId: task.id, type: "fail", payload: { error: "cancelled by user" }, token, serverURL, createdAt: new Date().toISOString() }, workspacesRoot);
|
|
19345
|
+
} else {
|
|
19346
|
+
updateEntry(timelineDir, task.id, (entry) => {
|
|
19347
|
+
entry.pid = null;
|
|
19348
|
+
entry.status = "killed";
|
|
19349
|
+
entry.errmsg = "killed by signal";
|
|
19350
|
+
});
|
|
19351
|
+
await reportToServer(() => client.failTask(token, task.id, "killed by signal"), { taskId: task.id, type: "fail", payload: { error: "killed by signal" }, token, serverURL, createdAt: new Date().toISOString() }, workspacesRoot);
|
|
19352
|
+
}
|
|
19353
|
+
process.exit(1);
|
|
19354
|
+
};
|
|
19355
|
+
process.on("SIGTERM", earlyOnKill);
|
|
19356
|
+
process.on("SIGINT", earlyOnKill);
|
|
19357
|
+
const attachmentIds = task.context?.attachment_ids ?? [];
|
|
19358
|
+
let attachments;
|
|
19359
|
+
if (attachmentIds.length > 0) {
|
|
19360
|
+
log5.info(`downloading ${attachmentIds.length} attachment(s)`);
|
|
19361
|
+
try {
|
|
19362
|
+
attachments = await downloadAttachments(client, token, task.workspaceId, task.id, attachmentIds);
|
|
19363
|
+
log5.info(`attachments ready (${attachments.length} file(s))`);
|
|
19364
|
+
} catch (e) {
|
|
19365
|
+
await cleanupAttachments(task.id);
|
|
19366
|
+
const errMsg = `failed to download attachments: ${e}`;
|
|
19367
|
+
log5.error(errMsg);
|
|
19368
|
+
updateEntry(timelineDir, task.id, (entry) => {
|
|
19369
|
+
entry.pid = null;
|
|
19370
|
+
entry.status = "failed";
|
|
19371
|
+
entry.errmsg = errMsg;
|
|
19372
|
+
});
|
|
19373
|
+
await reportToServer(() => client.failTask(token, task.id, errMsg), { taskId: task.id, type: "fail", payload: { error: errMsg }, token, serverURL, createdAt: new Date().toISOString() }, workspacesRoot);
|
|
19374
|
+
process.removeListener("SIGTERM", earlyOnKill);
|
|
19375
|
+
process.removeListener("SIGINT", earlyOnKill);
|
|
19376
|
+
return;
|
|
19377
|
+
}
|
|
19378
|
+
}
|
|
19379
|
+
const prompt = buildPrompt(task, attachments);
|
|
19380
|
+
const resumeSessionId = task.contextKey ? findResumableSessionByContextKey(timelineDir, task.contextKey, provider) ?? undefined : undefined;
|
|
19381
|
+
if (resumeSessionId) {
|
|
19382
|
+
log5.info(`resuming session ${resumeSessionId} (context_key: ${task.contextKey})`);
|
|
19383
|
+
}
|
|
19384
|
+
const session2 = backend.execute(prompt, {
|
|
19385
|
+
cwd: workDir,
|
|
19386
|
+
model: model || undefined,
|
|
19387
|
+
env,
|
|
19388
|
+
timeout: agentTimeout,
|
|
19389
|
+
resumeSessionId
|
|
19390
|
+
});
|
|
19391
|
+
const agentPid = session2.pid;
|
|
19392
|
+
const earlySessionId = await session2.sessionId;
|
|
19393
|
+
log5.info(`agent started (pid=${agentPid ?? "unknown"}, session=${earlySessionId})`);
|
|
19394
|
+
log5.info(JSON.stringify({ role: "user", type: "text", content: prompt }));
|
|
19395
|
+
updateEntry(timelineDir, task.id, (entry) => {
|
|
19396
|
+
entry.session_id = earlySessionId || null;
|
|
19397
|
+
});
|
|
19398
|
+
const pendingMessages = [];
|
|
19399
|
+
let seq = 0;
|
|
19400
|
+
let toolCount = 0;
|
|
19401
|
+
const BATCH_SIZE = Number(process.env.ALOOK_MESSAGE_BATCH_SIZE) || 20;
|
|
19402
|
+
const FLUSH_INTERVAL_MS = Number(process.env.ALOOK_MESSAGE_FLUSH_INTERVAL_MS) || 100;
|
|
19403
|
+
const flushMessages = async () => {
|
|
19404
|
+
if (pendingMessages.length === 0)
|
|
19405
|
+
return;
|
|
19406
|
+
const batch = pendingMessages.splice(0);
|
|
19407
|
+
try {
|
|
19408
|
+
await client.reportMessages(token, task.id, batch);
|
|
19409
|
+
} catch (e) {
|
|
19410
|
+
log5.debug("message report failed", e);
|
|
19411
|
+
}
|
|
19412
|
+
};
|
|
19413
|
+
const flushTimer = setInterval(flushMessages, FLUSH_INTERVAL_MS);
|
|
19414
|
+
process.removeListener("SIGTERM", earlyOnKill);
|
|
19415
|
+
process.removeListener("SIGINT", earlyOnKill);
|
|
19416
|
+
const onKill = async () => {
|
|
19417
|
+
if (killed)
|
|
19418
|
+
return;
|
|
19419
|
+
killed = true;
|
|
19420
|
+
log5.info(`killed by signal (messages=${seq}, tools=${toolCount})`);
|
|
19421
|
+
if (agentPid) {
|
|
19422
|
+
try {
|
|
19423
|
+
process.kill(agentPid, "SIGTERM");
|
|
19424
|
+
} catch {}
|
|
19425
|
+
}
|
|
19426
|
+
clearInterval(flushTimer);
|
|
19427
|
+
try {
|
|
19428
|
+
await flushMessages();
|
|
19429
|
+
} catch {}
|
|
19430
|
+
await cleanupAttachments(task.id);
|
|
19431
|
+
const intent = readKillIntent(agentBaseDir, task.id);
|
|
19432
|
+
clearKillIntent(agentBaseDir, task.id);
|
|
19433
|
+
if (intent?.reason === "superseded") {
|
|
19434
|
+
updateEntry(timelineDir, task.id, (entry) => {
|
|
19435
|
+
entry.pid = null;
|
|
19436
|
+
entry.status = "superseded";
|
|
19437
|
+
entry.successor_task_id = intent.successorTaskId ?? null;
|
|
19438
|
+
entry.supersede_reason = "superseded by newer task";
|
|
19439
|
+
});
|
|
19440
|
+
try {
|
|
19441
|
+
await client.supersedeTask(token, task.id);
|
|
19442
|
+
} catch {}
|
|
19443
|
+
} else if (intent?.reason === "cancelled") {
|
|
19444
|
+
updateEntry(timelineDir, task.id, (entry) => {
|
|
19445
|
+
entry.pid = null;
|
|
19446
|
+
entry.status = "cancelled";
|
|
19447
|
+
entry.errmsg = "cancelled by user";
|
|
19448
|
+
});
|
|
19449
|
+
await reportToServer(() => client.failTask(token, task.id, "cancelled by user"), { taskId: task.id, type: "fail", payload: { error: "cancelled by user" }, token, serverURL, createdAt: new Date().toISOString() }, workspacesRoot);
|
|
19450
|
+
} else {
|
|
19451
|
+
updateEntry(timelineDir, task.id, (entry) => {
|
|
19452
|
+
entry.pid = null;
|
|
19453
|
+
entry.status = "killed";
|
|
19454
|
+
entry.errmsg = "killed by signal";
|
|
19455
|
+
});
|
|
19456
|
+
await reportToServer(() => client.failTask(token, task.id, "killed by signal"), { taskId: task.id, type: "fail", payload: { error: "killed by signal" }, token, serverURL, createdAt: new Date().toISOString() }, workspacesRoot);
|
|
19457
|
+
}
|
|
19458
|
+
process.exit(1);
|
|
19459
|
+
};
|
|
19460
|
+
process.on("SIGTERM", onKill);
|
|
19461
|
+
process.on("SIGINT", onKill);
|
|
19462
|
+
const INACTIVITY_TIMEOUT_MS = messageInactivityTimeout ?? 5 * 60 * 1000;
|
|
19463
|
+
let inactivityTimedOut = false;
|
|
19464
|
+
try {
|
|
19465
|
+
const iter = session2.messages[Symbol.asyncIterator]();
|
|
19466
|
+
while (!killed) {
|
|
19467
|
+
const next = iter.next();
|
|
19468
|
+
const raceResult = await (INACTIVITY_TIMEOUT_MS > 0 ? Promise.race([
|
|
19469
|
+
next,
|
|
19470
|
+
new Promise((resolve) => {
|
|
19471
|
+
const timer = setTimeout(() => resolve("timeout"), INACTIVITY_TIMEOUT_MS);
|
|
19472
|
+
next.then(() => clearTimeout(timer), () => clearTimeout(timer));
|
|
19473
|
+
})
|
|
19474
|
+
]) : next);
|
|
19475
|
+
if (raceResult === "timeout") {
|
|
19476
|
+
inactivityTimedOut = true;
|
|
19477
|
+
log5.warn(`message inactivity timeout (${INACTIVITY_TIMEOUT_MS / 1000}s) — killing agent`);
|
|
19478
|
+
if (session2.pid) {
|
|
19479
|
+
try {
|
|
19480
|
+
process.kill(session2.pid, "SIGTERM");
|
|
19481
|
+
} catch {}
|
|
19482
|
+
}
|
|
19483
|
+
iter.return?.(undefined);
|
|
19484
|
+
break;
|
|
19485
|
+
}
|
|
19486
|
+
const iterResult = raceResult;
|
|
19487
|
+
if (iterResult.done)
|
|
19488
|
+
break;
|
|
19489
|
+
const msg = iterResult.value;
|
|
19490
|
+
seq++;
|
|
19491
|
+
pendingMessages.push({
|
|
19492
|
+
seq,
|
|
19493
|
+
type: msg.type,
|
|
19494
|
+
tool: msg.tool,
|
|
19495
|
+
call_id: msg.callId,
|
|
19496
|
+
content: msg.content,
|
|
19497
|
+
input: msg.input,
|
|
19498
|
+
output: msg.output
|
|
19499
|
+
});
|
|
19500
|
+
if (msg.type === "tool-use")
|
|
19501
|
+
toolCount++;
|
|
19502
|
+
if (msg.type === "tool-result" && msg.output && msg.output.length > 500) {
|
|
19503
|
+
log5.info(JSON.stringify({ role: "assistant", ...msg, output: msg.output.slice(0, 500) + `... (${msg.output.length} chars)` }));
|
|
19504
|
+
} else {
|
|
19505
|
+
log5.info(JSON.stringify({ role: "assistant", ...msg }));
|
|
19506
|
+
}
|
|
19507
|
+
if (msg.type === "text" && msg.content) {
|
|
19508
|
+
updateEntry(timelineDir, task.id, (entry) => {
|
|
19509
|
+
entry.agent_responses.push(msg.content);
|
|
19510
|
+
});
|
|
19511
|
+
}
|
|
19512
|
+
if (pendingMessages.length >= BATCH_SIZE) {
|
|
19513
|
+
await flushMessages();
|
|
19514
|
+
}
|
|
19515
|
+
}
|
|
19516
|
+
if (!killed)
|
|
19517
|
+
await flushMessages();
|
|
19518
|
+
} finally {
|
|
19519
|
+
clearInterval(flushTimer);
|
|
19520
|
+
process.removeListener("SIGTERM", onKill);
|
|
19521
|
+
process.removeListener("SIGINT", onKill);
|
|
19522
|
+
}
|
|
19523
|
+
if (killed)
|
|
19524
|
+
return;
|
|
19525
|
+
process.on("SIGTERM", onKill);
|
|
19526
|
+
process.on("SIGINT", onKill);
|
|
19527
|
+
const result = await session2.result;
|
|
19528
|
+
process.removeListener("SIGTERM", onKill);
|
|
19529
|
+
process.removeListener("SIGINT", onKill);
|
|
19530
|
+
if (killed)
|
|
19531
|
+
return;
|
|
19532
|
+
if (inactivityTimedOut) {
|
|
19533
|
+
result.status = "failed";
|
|
19534
|
+
result.error = `message inactivity timeout (no messages for ${INACTIVITY_TIMEOUT_MS / 1000}s)`;
|
|
19535
|
+
}
|
|
19536
|
+
await cleanupAttachments(task.id);
|
|
19537
|
+
if (result.status === "completed") {
|
|
19538
|
+
updateEntry(timelineDir, task.id, (entry) => {
|
|
19539
|
+
entry.session_id = result.sessionId || null;
|
|
19540
|
+
entry.pid = null;
|
|
19541
|
+
entry.status = "completed";
|
|
19542
|
+
});
|
|
19543
|
+
} else {
|
|
19544
|
+
updateEntry(timelineDir, task.id, (entry) => {
|
|
19545
|
+
entry.pid = null;
|
|
19546
|
+
entry.status = "failed";
|
|
19547
|
+
entry.errmsg = result.error || "agent exited unexpectedly";
|
|
19548
|
+
});
|
|
19549
|
+
}
|
|
19550
|
+
if (result.status === "completed") {
|
|
19551
|
+
const body = {
|
|
19552
|
+
output: result.output || ""
|
|
19553
|
+
};
|
|
19554
|
+
if (result.sessionId)
|
|
19555
|
+
body.session_id = result.sessionId;
|
|
19556
|
+
await reportToServer(() => client.completeTask(token, task.id, body), { taskId: task.id, type: "complete", payload: body, token, serverURL, createdAt: new Date().toISOString() }, workspacesRoot);
|
|
19557
|
+
const dur = (result.durationMs / 1000).toFixed(1);
|
|
19558
|
+
log5.info(`completed (duration=${dur}s, messages=${seq}, tools=${toolCount})`);
|
|
19559
|
+
} else {
|
|
19560
|
+
const errorMsg = result.error || "agent exited unexpectedly";
|
|
19561
|
+
await reportToServer(() => client.failTask(token, task.id, errorMsg), { taskId: task.id, type: "fail", payload: { error: errorMsg }, token, serverURL, createdAt: new Date().toISOString() }, workspacesRoot);
|
|
19562
|
+
const dur = (result.durationMs / 1000).toFixed(1);
|
|
19563
|
+
log5.info(`failed (duration=${dur}s, messages=${seq}, tools=${toolCount}) — ${result.error}`);
|
|
19564
|
+
}
|
|
17435
19565
|
}
|
|
17436
|
-
|
|
17437
|
-
|
|
17438
|
-
|
|
17439
|
-
|
|
17440
|
-
|
|
17441
|
-
|
|
17442
|
-
|
|
17443
|
-
"item/completed",
|
|
17444
|
-
"item/agentMessage/delta"
|
|
17445
|
-
]);
|
|
17446
|
-
|
|
17447
|
-
// daemon/agent/index.ts
|
|
17448
|
-
import { execSync as execSync2 } from "child_process";
|
|
17449
|
-
async function detectVersion2(cliPath) {
|
|
19566
|
+
async function main() {
|
|
19567
|
+
const encoded = process.argv[2];
|
|
19568
|
+
if (!encoded) {
|
|
19569
|
+
log5.error("session-runner: missing base64-encoded input argument");
|
|
19570
|
+
process.exit(1);
|
|
19571
|
+
}
|
|
19572
|
+
let input;
|
|
17450
19573
|
try {
|
|
17451
|
-
|
|
17452
|
-
|
|
17453
|
-
|
|
19574
|
+
const json2 = Buffer.from(encoded, "base64").toString("utf-8");
|
|
19575
|
+
input = JSON.parse(json2);
|
|
19576
|
+
} catch (e) {
|
|
19577
|
+
log5.error("session-runner: failed to parse input", e);
|
|
19578
|
+
process.exit(1);
|
|
19579
|
+
}
|
|
19580
|
+
const client = new DaemonClient(input.serverURL);
|
|
19581
|
+
try {
|
|
19582
|
+
await runSession(input);
|
|
19583
|
+
} catch (e) {
|
|
19584
|
+
log5.error(`session-runner: unhandled error for task ${input.task.id}`, e);
|
|
19585
|
+
await cleanupAttachments(input.task.id);
|
|
19586
|
+
const timelineDir = path.join(input.workspacesRoot, input.task.workspaceId, input.task.agentId, "workdir", ".context_timeline").replace(/\\/g, "/");
|
|
19587
|
+
updateEntry(timelineDir, input.task.id, (entry) => {
|
|
19588
|
+
entry.pid = null;
|
|
19589
|
+
entry.status = "failed";
|
|
19590
|
+
entry.errmsg = `session-runner crash: ${e}`;
|
|
19591
|
+
});
|
|
19592
|
+
const errorMsg = `session-runner crash: ${e}`;
|
|
19593
|
+
await reportToServer(() => client.failTask(input.token, input.task.id, errorMsg), { taskId: input.task.id, type: "fail", payload: { error: errorMsg }, token: input.token, serverURL: input.serverURL, createdAt: new Date().toISOString() }, input.workspacesRoot);
|
|
19594
|
+
process.exit(1);
|
|
17454
19595
|
}
|
|
17455
19596
|
}
|
|
17456
|
-
|
|
17457
|
-
|
|
17458
|
-
|
|
17459
|
-
return {
|
|
17460
|
-
id: api2.id,
|
|
17461
|
-
agentId: api2.agent_id,
|
|
17462
|
-
runtimeId: api2.runtime_id,
|
|
17463
|
-
conversationId: api2.conversation_id,
|
|
17464
|
-
workspaceId: api2.workspace_id,
|
|
17465
|
-
prompt: api2.prompt,
|
|
17466
|
-
status: api2.status,
|
|
17467
|
-
priority: api2.priority,
|
|
17468
|
-
type: api2.type,
|
|
17469
|
-
contextKey: api2.context_key ?? null,
|
|
17470
|
-
context: api2.context ?? undefined,
|
|
17471
|
-
agent: api2.agent ? {
|
|
17472
|
-
name: api2.agent.name,
|
|
17473
|
-
instructions: api2.agent.instructions,
|
|
17474
|
-
emailHandle: api2.agent.email_handle ?? undefined,
|
|
17475
|
-
emailAddresses: api2.agent.email_addresses ?? [],
|
|
17476
|
-
userEmail: api2.agent.user_email ?? undefined,
|
|
17477
|
-
userName: api2.agent.user_name ?? undefined,
|
|
17478
|
-
runtimeConfig: api2.agent.runtime_config ?? undefined,
|
|
17479
|
-
colleagues: api2.agent.colleagues?.map((c) => ({
|
|
17480
|
-
name: c.name,
|
|
17481
|
-
email: c.email,
|
|
17482
|
-
description: c.description,
|
|
17483
|
-
instruction: c.instruction
|
|
17484
|
-
})) ?? []
|
|
17485
|
-
} : undefined,
|
|
17486
|
-
sender: api2.sender ? { name: api2.sender.name, email: api2.sender.email, isOwner: api2.sender.is_owner } : undefined,
|
|
17487
|
-
repos: undefined,
|
|
17488
|
-
createdAt: api2.created_at,
|
|
17489
|
-
traceId: api2.trace_id ?? null,
|
|
17490
|
-
parentTaskId: api2.parent_task_id ?? null,
|
|
17491
|
-
channel: api2.channel ?? null
|
|
17492
|
-
};
|
|
19597
|
+
var isDirectExecution = typeof Bun !== "undefined" ? Bun.main === import.meta.path : process.argv[1]?.endsWith("session-runner.ts") || process.argv[1]?.endsWith("session-runner.js");
|
|
19598
|
+
if (isDirectExecution) {
|
|
19599
|
+
main();
|
|
17493
19600
|
}
|
|
17494
19601
|
|
|
17495
19602
|
// daemon/update-handler.ts
|
|
17496
|
-
import { readFileSync as
|
|
19603
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync6, unlinkSync as unlinkSync4 } from "fs";
|
|
17497
19604
|
|
|
17498
19605
|
// lib/update.ts
|
|
17499
|
-
import { spawn } from "child_process";
|
|
19606
|
+
import { spawn as spawn4 } from "child_process";
|
|
17500
19607
|
function fetchLatestVersion() {
|
|
17501
19608
|
return fetch("https://registry.npmjs.org/@alook/cli/latest").then((res) => {
|
|
17502
19609
|
if (!res.ok)
|
|
@@ -17507,7 +19614,7 @@ function fetchLatestVersion() {
|
|
|
17507
19614
|
function runNpmUpdate(targetVersion) {
|
|
17508
19615
|
return new Promise((resolve) => {
|
|
17509
19616
|
const chunks = [];
|
|
17510
|
-
const child =
|
|
19617
|
+
const child = spawn4("npm", ["install", "-g", `@alook/cli@${targetVersion}`], {
|
|
17511
19618
|
stdio: ["ignore", "pipe", "pipe"]
|
|
17512
19619
|
});
|
|
17513
19620
|
child.stdout?.on("data", (d) => chunks.push(d));
|
|
@@ -17523,7 +19630,7 @@ function runNpmUpdate(targetVersion) {
|
|
|
17523
19630
|
}
|
|
17524
19631
|
|
|
17525
19632
|
// daemon/update-handler.ts
|
|
17526
|
-
var
|
|
19633
|
+
var log6 = createLogger2({ module: "updater" });
|
|
17527
19634
|
var updating = false;
|
|
17528
19635
|
var retryCount = 0;
|
|
17529
19636
|
var MAX_RETRIES = 3;
|
|
@@ -17532,19 +19639,19 @@ function isUpdating() {
|
|
|
17532
19639
|
}
|
|
17533
19640
|
function readUpdateMarker(profile) {
|
|
17534
19641
|
try {
|
|
17535
|
-
return
|
|
19642
|
+
return readFileSync7(lastUpdateMarkerPath(profile), "utf-8").trim() || null;
|
|
17536
19643
|
} catch {
|
|
17537
19644
|
return null;
|
|
17538
19645
|
}
|
|
17539
19646
|
}
|
|
17540
19647
|
function writeUpdateMarker(version3, profile) {
|
|
17541
19648
|
try {
|
|
17542
|
-
|
|
19649
|
+
writeFileSync6(lastUpdateMarkerPath(profile), version3, { mode: 384 });
|
|
17543
19650
|
} catch {}
|
|
17544
19651
|
}
|
|
17545
19652
|
function clearUpdateMarker(profile) {
|
|
17546
19653
|
try {
|
|
17547
|
-
|
|
19654
|
+
unlinkSync4(lastUpdateMarkerPath(profile));
|
|
17548
19655
|
} catch {}
|
|
17549
19656
|
}
|
|
17550
19657
|
async function handleCliUpdate(version3, onSuccess, profile) {
|
|
@@ -17553,197 +19660,37 @@ async function handleCliUpdate(version3, onSuccess, profile) {
|
|
|
17553
19660
|
if (retryCount >= MAX_RETRIES)
|
|
17554
19661
|
return;
|
|
17555
19662
|
if (process.env.ALOOK_CMD_PREFIX) {
|
|
17556
|
-
|
|
19663
|
+
log6.info(`Skipping auto-update in app mode — user should run: npx @alook/app@latest update`);
|
|
17557
19664
|
return;
|
|
17558
19665
|
}
|
|
17559
19666
|
const marker = readUpdateMarker(profile);
|
|
17560
19667
|
if (marker === version3) {
|
|
17561
|
-
|
|
19668
|
+
log6.info(`Skipping update to v${version3} — already attempted (marker exists)`);
|
|
17562
19669
|
return;
|
|
17563
19670
|
}
|
|
17564
19671
|
updating = true;
|
|
17565
19672
|
try {
|
|
17566
|
-
|
|
19673
|
+
log6.info(`Updating CLI to v${version3}...`);
|
|
17567
19674
|
const result = await runNpmUpdate(version3);
|
|
17568
19675
|
if (result.success) {
|
|
17569
19676
|
writeUpdateMarker(version3, profile);
|
|
17570
|
-
|
|
19677
|
+
log6.info(`CLI updated to v${version3} — restarting`);
|
|
17571
19678
|
onSuccess();
|
|
17572
19679
|
} else {
|
|
17573
19680
|
retryCount++;
|
|
17574
|
-
|
|
19681
|
+
log6.error(`CLI update failed (attempt ${retryCount}/${MAX_RETRIES}): ${result.output}`);
|
|
17575
19682
|
}
|
|
17576
19683
|
} catch (e) {
|
|
17577
19684
|
retryCount++;
|
|
17578
|
-
|
|
19685
|
+
log6.error(`CLI update error (attempt ${retryCount}/${MAX_RETRIES})`, e);
|
|
17579
19686
|
} finally {
|
|
17580
19687
|
updating = false;
|
|
17581
19688
|
}
|
|
17582
19689
|
}
|
|
17583
19690
|
|
|
17584
|
-
// daemon/execenv/timeline.ts
|
|
17585
|
-
import { appendFileSync, readFileSync as readFileSync5, writeFileSync as writeFileSync4, renameSync } from "fs";
|
|
17586
|
-
import { join as join4 } from "path";
|
|
17587
|
-
|
|
17588
|
-
// daemon/execenv/filelock.ts
|
|
17589
|
-
import { mkdirSync as mkdirSync3, rmdirSync, statSync } from "fs";
|
|
17590
|
-
var DEFAULT_STALE_MS = 3600000;
|
|
17591
|
-
function acquireLock(lockPath, staleMs = DEFAULT_STALE_MS) {
|
|
17592
|
-
try {
|
|
17593
|
-
mkdirSync3(lockPath);
|
|
17594
|
-
return true;
|
|
17595
|
-
} catch {
|
|
17596
|
-
try {
|
|
17597
|
-
const stat = statSync(lockPath);
|
|
17598
|
-
if (Date.now() - stat.mtimeMs > staleMs) {
|
|
17599
|
-
rmdirSync(lockPath);
|
|
17600
|
-
try {
|
|
17601
|
-
mkdirSync3(lockPath);
|
|
17602
|
-
return true;
|
|
17603
|
-
} catch {
|
|
17604
|
-
return false;
|
|
17605
|
-
}
|
|
17606
|
-
}
|
|
17607
|
-
} catch {
|
|
17608
|
-
try {
|
|
17609
|
-
mkdirSync3(lockPath);
|
|
17610
|
-
return true;
|
|
17611
|
-
} catch {
|
|
17612
|
-
return false;
|
|
17613
|
-
}
|
|
17614
|
-
}
|
|
17615
|
-
return false;
|
|
17616
|
-
}
|
|
17617
|
-
}
|
|
17618
|
-
function releaseLock(lockPath) {
|
|
17619
|
-
try {
|
|
17620
|
-
rmdirSync(lockPath);
|
|
17621
|
-
} catch {}
|
|
17622
|
-
}
|
|
17623
|
-
|
|
17624
|
-
// daemon/execenv/timeline.ts
|
|
17625
|
-
var log4 = createLogger2({ module: "timeline" });
|
|
17626
|
-
function readJsonl(filePath) {
|
|
17627
|
-
let content;
|
|
17628
|
-
try {
|
|
17629
|
-
content = readFileSync5(filePath, "utf-8");
|
|
17630
|
-
} catch {
|
|
17631
|
-
return [];
|
|
17632
|
-
}
|
|
17633
|
-
const entries = [];
|
|
17634
|
-
for (const line of content.trimEnd().split(`
|
|
17635
|
-
`)) {
|
|
17636
|
-
if (!line)
|
|
17637
|
-
continue;
|
|
17638
|
-
try {
|
|
17639
|
-
entries.push(JSON.parse(line));
|
|
17640
|
-
} catch {}
|
|
17641
|
-
}
|
|
17642
|
-
return entries;
|
|
17643
|
-
}
|
|
17644
|
-
function filenameForDate(date5) {
|
|
17645
|
-
const y = date5.getFullYear();
|
|
17646
|
-
const m = String(date5.getMonth() + 1).padStart(2, "0");
|
|
17647
|
-
const d = String(date5.getDate()).padStart(2, "0");
|
|
17648
|
-
return `${y}-${m}-${d}.jsonl`;
|
|
17649
|
-
}
|
|
17650
|
-
function recentFilenames(maxDays) {
|
|
17651
|
-
const filenames = [];
|
|
17652
|
-
const now = new Date;
|
|
17653
|
-
for (let i = 0;i < maxDays; i++) {
|
|
17654
|
-
const d = new Date(now);
|
|
17655
|
-
d.setDate(d.getDate() - i);
|
|
17656
|
-
filenames.push(filenameForDate(d));
|
|
17657
|
-
}
|
|
17658
|
-
return filenames;
|
|
17659
|
-
}
|
|
17660
|
-
var RESUME_MAX_AGE_MS = 72 * 60 * 60 * 1000;
|
|
17661
|
-
function findRunningPidByTaskId(timelineDir, taskId) {
|
|
17662
|
-
for (const filename of recentFilenames(7)) {
|
|
17663
|
-
const entries = readJsonl(join4(timelineDir, filename));
|
|
17664
|
-
for (const entry of entries) {
|
|
17665
|
-
if (entry.task_id === taskId && entry.status === "running" && entry.pid != null) {
|
|
17666
|
-
return entry.pid;
|
|
17667
|
-
}
|
|
17668
|
-
}
|
|
17669
|
-
}
|
|
17670
|
-
return null;
|
|
17671
|
-
}
|
|
17672
|
-
function findRunningEntryByContextKey(timelineDir, contextKey, provider) {
|
|
17673
|
-
for (const filename of recentFilenames(7)) {
|
|
17674
|
-
const dayEntries = readJsonl(join4(timelineDir, filename));
|
|
17675
|
-
for (let i = dayEntries.length - 1;i >= 0; i--) {
|
|
17676
|
-
const entry = dayEntries[i];
|
|
17677
|
-
if (entry.status === "running" && entry.context_key === contextKey && entry.provider === provider) {
|
|
17678
|
-
return entry;
|
|
17679
|
-
}
|
|
17680
|
-
}
|
|
17681
|
-
}
|
|
17682
|
-
return null;
|
|
17683
|
-
}
|
|
17684
|
-
|
|
17685
|
-
// daemon/execenv/steering.ts
|
|
17686
|
-
import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync5, readFileSync as readFileSync6, unlinkSync as unlinkSync3, readdirSync, statSync as statSync2 } from "fs";
|
|
17687
|
-
import { join as join5 } from "path";
|
|
17688
|
-
var log5 = createLogger2({ module: "steering" });
|
|
17689
|
-
var INTENT_DIR_NAME = ".kill_intents";
|
|
17690
|
-
var STEERING_LOCK_DIR = ".steering_locks";
|
|
17691
|
-
var INTENT_STALE_MS = 10 * 60 * 1000;
|
|
17692
|
-
function intentFilePath(baseDir, taskId) {
|
|
17693
|
-
return join5(baseDir, INTENT_DIR_NAME, `${taskId}.json`);
|
|
17694
|
-
}
|
|
17695
|
-
function intentDirPath(baseDir) {
|
|
17696
|
-
return join5(baseDir, INTENT_DIR_NAME);
|
|
17697
|
-
}
|
|
17698
|
-
function steeringLockPath(baseDir, contextKey) {
|
|
17699
|
-
const safeKey = contextKey.replace(/[^a-zA-Z0-9_:-]/g, "_");
|
|
17700
|
-
return join5(baseDir, STEERING_LOCK_DIR, safeKey);
|
|
17701
|
-
}
|
|
17702
|
-
function writeKillIntent(baseDir, intent) {
|
|
17703
|
-
const dir = intentDirPath(baseDir);
|
|
17704
|
-
try {
|
|
17705
|
-
mkdirSync4(dir, { recursive: true });
|
|
17706
|
-
} catch {}
|
|
17707
|
-
const filePath = intentFilePath(baseDir, intent.targetTaskId);
|
|
17708
|
-
writeFileSync5(filePath, JSON.stringify(intent));
|
|
17709
|
-
}
|
|
17710
|
-
function cleanupStaleIntents(baseDir) {
|
|
17711
|
-
const dir = intentDirPath(baseDir);
|
|
17712
|
-
let files;
|
|
17713
|
-
try {
|
|
17714
|
-
files = readdirSync(dir).filter((f) => f.endsWith(".json"));
|
|
17715
|
-
} catch {
|
|
17716
|
-
return;
|
|
17717
|
-
}
|
|
17718
|
-
const now = Date.now();
|
|
17719
|
-
for (const file2 of files) {
|
|
17720
|
-
const filePath = join5(dir, file2);
|
|
17721
|
-
try {
|
|
17722
|
-
const content = readFileSync6(filePath, "utf-8");
|
|
17723
|
-
const intent = JSON.parse(content);
|
|
17724
|
-
const stat = statSync2(filePath);
|
|
17725
|
-
if (now - stat.mtimeMs > INTENT_STALE_MS) {
|
|
17726
|
-
unlinkSync3(filePath);
|
|
17727
|
-
log5.debug(`Cleaned up stale kill intent for task ${intent.targetTaskId}`);
|
|
17728
|
-
}
|
|
17729
|
-
} catch {}
|
|
17730
|
-
}
|
|
17731
|
-
}
|
|
17732
|
-
function acquireSteeringLock(baseDir, contextKey) {
|
|
17733
|
-
const lockPath = steeringLockPath(baseDir, contextKey);
|
|
17734
|
-
try {
|
|
17735
|
-
mkdirSync4(join5(baseDir, STEERING_LOCK_DIR), { recursive: true });
|
|
17736
|
-
} catch {}
|
|
17737
|
-
return acquireLock(lockPath, 60000);
|
|
17738
|
-
}
|
|
17739
|
-
function releaseSteeringLock(baseDir, contextKey) {
|
|
17740
|
-
const lockPath = steeringLockPath(baseDir, contextKey);
|
|
17741
|
-
releaseLock(lockPath);
|
|
17742
|
-
}
|
|
17743
|
-
|
|
17744
19691
|
// daemon/workspace-files.ts
|
|
17745
19692
|
import { readdir, stat, readFile } from "fs/promises";
|
|
17746
|
-
import { join as
|
|
19693
|
+
import { join as join9, resolve, extname, relative, sep as sep2 } from "path";
|
|
17747
19694
|
var SKIP_DIRS = new Set([".git", "node_modules", ".next", ".wrangler", "__pycache__", ".venv"]);
|
|
17748
19695
|
var TEXT_EXTENSIONS = new Set([
|
|
17749
19696
|
".md",
|
|
@@ -17802,7 +19749,7 @@ async function readDirectoryTree(dirPath, basePath) {
|
|
|
17802
19749
|
if (ext !== "" && !TEXT_EXTENSIONS.has(ext))
|
|
17803
19750
|
continue;
|
|
17804
19751
|
}
|
|
17805
|
-
const fullPath =
|
|
19752
|
+
const fullPath = join9(dirPath, entry.name);
|
|
17806
19753
|
let info;
|
|
17807
19754
|
try {
|
|
17808
19755
|
info = await stat(fullPath);
|
|
@@ -17834,23 +19781,13 @@ async function readFileContent(filePath) {
|
|
|
17834
19781
|
}
|
|
17835
19782
|
function validatePath(agentWorkdir, requestedPath) {
|
|
17836
19783
|
const resolved = resolve(agentWorkdir, requestedPath);
|
|
17837
|
-
if (resolved !== agentWorkdir && !resolved.startsWith(agentWorkdir +
|
|
19784
|
+
if (resolved !== agentWorkdir && !resolved.startsWith(agentWorkdir + sep2))
|
|
17838
19785
|
return null;
|
|
17839
19786
|
return resolved;
|
|
17840
19787
|
}
|
|
17841
19788
|
|
|
17842
19789
|
// lib/shell-env.ts
|
|
17843
19790
|
import { execSync as execSync3 } from "child_process";
|
|
17844
|
-
|
|
17845
|
-
// lib/platform.ts
|
|
17846
|
-
import { tmpdir } from "os";
|
|
17847
|
-
import { join as join7, sep as sep2 } from "path";
|
|
17848
|
-
var isWindows = process.platform === "win32";
|
|
17849
|
-
function tempDir(subdir) {
|
|
17850
|
-
return join7(tmpdir(), subdir);
|
|
17851
|
-
}
|
|
17852
|
-
|
|
17853
|
-
// lib/shell-env.ts
|
|
17854
19791
|
var PASSTHROUGH_VARS = ["ALOOK_PROJECT_ROOT", "ALOOK_SERVER_URL", "ALOOK_CMD_PREFIX", "ALOOK_HEALTH_PORT"];
|
|
17855
19792
|
function resolveLoginShellEnv() {
|
|
17856
19793
|
if (isWindows) {
|
|
@@ -17883,15 +19820,15 @@ function resolveLoginShellEnv() {
|
|
|
17883
19820
|
}
|
|
17884
19821
|
|
|
17885
19822
|
// daemon/daemon.ts
|
|
17886
|
-
import { existsSync, mkdirSync as
|
|
19823
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync7, openSync, closeSync, readdirSync as readdirSync2, statSync as statSync3, unlinkSync as unlinkSync5 } from "fs";
|
|
17887
19824
|
import { readdir as readdir2, readFile as readFile2, unlink, stat as fsStat } from "fs/promises";
|
|
17888
|
-
import { execSync as execSync4, spawn as
|
|
19825
|
+
import { execSync as execSync4, spawn as spawn5 } from "child_process";
|
|
17889
19826
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
17890
|
-
import { dirname as dirname3, join as
|
|
17891
|
-
var
|
|
19827
|
+
import { dirname as dirname3, join as join10 } from "path";
|
|
19828
|
+
var log7 = createLogger2({ module: "daemon" });
|
|
17892
19829
|
var _dir = dirname3(fileURLToPath2(import.meta.url));
|
|
17893
|
-
var sessionRunnerPath =
|
|
17894
|
-
var meetingRunnerPath =
|
|
19830
|
+
var sessionRunnerPath = existsSync2(join10(_dir, "session-runner.js")) ? join10(_dir, "session-runner.js") : join10(_dir, "session-runner.ts");
|
|
19831
|
+
var meetingRunnerPath = existsSync2(join10(_dir, "meeting-runner.js")) ? join10(_dir, "meeting-runner.js") : join10(_dir, "meeting-runner.ts");
|
|
17895
19832
|
function isCommandAvailable2(cmd) {
|
|
17896
19833
|
try {
|
|
17897
19834
|
const check2 = process.platform === "win32" ? `where ${cmd}` : `which ${cmd}`;
|
|
@@ -17913,7 +19850,7 @@ function pruneSessionRunnerLogs() {
|
|
|
17913
19850
|
if (entries.length <= MAX_SESSION_RUNNER_LOGS)
|
|
17914
19851
|
return;
|
|
17915
19852
|
const withMtime = entries.map((name) => {
|
|
17916
|
-
const full =
|
|
19853
|
+
const full = join10(logDir, name);
|
|
17917
19854
|
try {
|
|
17918
19855
|
return { name, mtime: statSync3(full).mtimeMs };
|
|
17919
19856
|
} catch {
|
|
@@ -17923,11 +19860,11 @@ function pruneSessionRunnerLogs() {
|
|
|
17923
19860
|
withMtime.sort((a, b) => b.mtime - a.mtime);
|
|
17924
19861
|
for (const entry of withMtime.slice(MAX_SESSION_RUNNER_LOGS)) {
|
|
17925
19862
|
try {
|
|
17926
|
-
|
|
19863
|
+
unlinkSync5(join10(logDir, entry.name));
|
|
17927
19864
|
} catch {}
|
|
17928
19865
|
}
|
|
17929
19866
|
}
|
|
17930
|
-
function
|
|
19867
|
+
function isClientError2(error51) {
|
|
17931
19868
|
if (!(error51 instanceof Error))
|
|
17932
19869
|
return false;
|
|
17933
19870
|
const match = error51.message.match(/^HTTP (\d+):/);
|
|
@@ -17964,7 +19901,7 @@ function isValidMarker(data) {
|
|
|
17964
19901
|
var MARKER_STALE_MS = 24 * 60 * 60 * 1000;
|
|
17965
19902
|
var TMP_STALE_MS = 60 * 60 * 1000;
|
|
17966
19903
|
async function reconcilePendingCompletions(workspacesRoot) {
|
|
17967
|
-
const dir =
|
|
19904
|
+
const dir = join10(workspacesRoot, ".pending_completions");
|
|
17968
19905
|
let entries;
|
|
17969
19906
|
try {
|
|
17970
19907
|
entries = await readdir2(dir);
|
|
@@ -17975,15 +19912,15 @@ async function reconcilePendingCompletions(workspacesRoot) {
|
|
|
17975
19912
|
if (!name.endsWith(".tmp"))
|
|
17976
19913
|
continue;
|
|
17977
19914
|
try {
|
|
17978
|
-
const s = await fsStat(
|
|
19915
|
+
const s = await fsStat(join10(dir, name));
|
|
17979
19916
|
if (Date.now() - s.mtimeMs > TMP_STALE_MS) {
|
|
17980
|
-
await unlink(
|
|
19917
|
+
await unlink(join10(dir, name));
|
|
17981
19918
|
}
|
|
17982
19919
|
} catch {}
|
|
17983
19920
|
}
|
|
17984
19921
|
const jsonFiles = entries.filter((f) => f.endsWith(".json"));
|
|
17985
19922
|
for (const name of jsonFiles) {
|
|
17986
|
-
const filePath =
|
|
19923
|
+
const filePath = join10(dir, name);
|
|
17987
19924
|
try {
|
|
17988
19925
|
let raw;
|
|
17989
19926
|
try {
|
|
@@ -17995,14 +19932,14 @@ async function reconcilePendingCompletions(workspacesRoot) {
|
|
|
17995
19932
|
try {
|
|
17996
19933
|
parsed = JSON.parse(raw);
|
|
17997
19934
|
} catch {
|
|
17998
|
-
|
|
19935
|
+
log7.warn(`reconcile: malformed marker ${name}, deleting`);
|
|
17999
19936
|
try {
|
|
18000
19937
|
await unlink(filePath);
|
|
18001
19938
|
} catch {}
|
|
18002
19939
|
continue;
|
|
18003
19940
|
}
|
|
18004
19941
|
if (!isValidMarker(parsed)) {
|
|
18005
|
-
|
|
19942
|
+
log7.warn(`reconcile: invalid marker structure ${name}, deleting`);
|
|
18006
19943
|
try {
|
|
18007
19944
|
await unlink(filePath);
|
|
18008
19945
|
} catch {}
|
|
@@ -18011,7 +19948,7 @@ async function reconcilePendingCompletions(workspacesRoot) {
|
|
|
18011
19948
|
const marker = parsed;
|
|
18012
19949
|
const age = Date.now() - new Date(marker.createdAt).getTime();
|
|
18013
19950
|
if (age > MARKER_STALE_MS) {
|
|
18014
|
-
|
|
19951
|
+
log7.warn(`reconcile: stale marker ${name} (${Math.round(age / 3600000)}h old), deleting`);
|
|
18015
19952
|
try {
|
|
18016
19953
|
await unlink(filePath);
|
|
18017
19954
|
} catch {}
|
|
@@ -18027,19 +19964,19 @@ async function reconcilePendingCompletions(workspacesRoot) {
|
|
|
18027
19964
|
try {
|
|
18028
19965
|
await unlink(filePath);
|
|
18029
19966
|
} catch (delErr) {
|
|
18030
|
-
|
|
19967
|
+
log7.warn(`reconcile: delivered marker ${name} but failed to delete: ${delErr}`);
|
|
18031
19968
|
}
|
|
18032
19969
|
} catch (deliverErr) {
|
|
18033
|
-
if (
|
|
19970
|
+
if (isClientError2(deliverErr)) {
|
|
18034
19971
|
try {
|
|
18035
19972
|
await unlink(filePath);
|
|
18036
19973
|
} catch {}
|
|
18037
19974
|
} else {
|
|
18038
|
-
|
|
19975
|
+
log7.debug(`reconcile: delivery failed for ${name}, will retry next cycle`);
|
|
18039
19976
|
}
|
|
18040
19977
|
}
|
|
18041
19978
|
} catch (e) {
|
|
18042
|
-
|
|
19979
|
+
log7.debug(`reconcile: error processing ${name}`, e);
|
|
18043
19980
|
}
|
|
18044
19981
|
}
|
|
18045
19982
|
}
|
|
@@ -18050,7 +19987,7 @@ async function startDaemon(profile, serverUrl) {
|
|
|
18050
19987
|
}
|
|
18051
19988
|
process.once("exit", () => releaseDaemonPid(profile));
|
|
18052
19989
|
const bailOnUnexpected = (label, err) => {
|
|
18053
|
-
|
|
19990
|
+
log7.error(`${label} — shutting down`, err);
|
|
18054
19991
|
releaseDaemonPid(profile);
|
|
18055
19992
|
process.exit(1);
|
|
18056
19993
|
};
|
|
@@ -18063,21 +20000,21 @@ async function startDaemon(profile, serverUrl) {
|
|
|
18063
20000
|
if (marker) {
|
|
18064
20001
|
clearUpdateMarker(profile);
|
|
18065
20002
|
if (marker === config2.cliVersion) {
|
|
18066
|
-
|
|
20003
|
+
log7.info(`Cleared update marker — now running v${config2.cliVersion}`);
|
|
18067
20004
|
} else {
|
|
18068
|
-
|
|
20005
|
+
log7.info(`Cleared stale update marker (was v${marker}, running v${config2.cliVersion}) — update will be retried`);
|
|
18069
20006
|
}
|
|
18070
20007
|
}
|
|
18071
20008
|
const cliConfig = loadCLIConfigForProfile(profile);
|
|
18072
20009
|
const workspaces = cliConfig.watched_workspaces || [];
|
|
18073
20010
|
if (workspaces.length === 0) {
|
|
18074
|
-
|
|
20011
|
+
log7.error("No watched workspaces configured.");
|
|
18075
20012
|
process.exit(1);
|
|
18076
20013
|
return;
|
|
18077
20014
|
}
|
|
18078
20015
|
const hasPerWorkspaceTokens = workspaces.every((ws) => !!ws.token);
|
|
18079
20016
|
if (!hasPerWorkspaceTokens) {
|
|
18080
|
-
|
|
20017
|
+
log7.error(`Config uses old format. Run '${cmdPrefix()} register --token <token>' for each workspace to upgrade.`);
|
|
18081
20018
|
process.exit(1);
|
|
18082
20019
|
return;
|
|
18083
20020
|
}
|
|
@@ -18086,22 +20023,22 @@ async function startDaemon(profile, serverUrl) {
|
|
|
18086
20023
|
const client = new DaemonClient(config2.serverURL);
|
|
18087
20024
|
const health = createHealthServer();
|
|
18088
20025
|
const providers = [];
|
|
18089
|
-
for (const [type,
|
|
20026
|
+
for (const [type, path2] of [
|
|
18090
20027
|
["claude", config2.claudePath],
|
|
18091
20028
|
["codex", config2.codexPath],
|
|
18092
20029
|
["opencode", config2.opencodePath]
|
|
18093
20030
|
]) {
|
|
18094
|
-
if (isCommandAvailable2(
|
|
18095
|
-
const version3 = await detectVersion2(
|
|
18096
|
-
providers.push({ type, path, version: version3 });
|
|
20031
|
+
if (isCommandAvailable2(path2)) {
|
|
20032
|
+
const version3 = await detectVersion2(path2);
|
|
20033
|
+
providers.push({ type, path: path2, version: version3 });
|
|
18097
20034
|
}
|
|
18098
20035
|
}
|
|
18099
20036
|
if (providers.length === 0) {
|
|
18100
|
-
|
|
20037
|
+
log7.error("No agent CLI tools found on PATH.");
|
|
18101
20038
|
process.exit(1);
|
|
18102
20039
|
return;
|
|
18103
20040
|
}
|
|
18104
|
-
|
|
20041
|
+
log7.info(`Detected providers: ${providers.map((p) => `${p.type}@${p.version}`).join(", ")}`);
|
|
18105
20042
|
const workspaceStates = [];
|
|
18106
20043
|
const runtimeIndex = new Map;
|
|
18107
20044
|
for (const ws of workspaces) {
|
|
@@ -18109,7 +20046,7 @@ async function startDaemon(profile, serverUrl) {
|
|
|
18109
20046
|
type: p.type,
|
|
18110
20047
|
version: p.version
|
|
18111
20048
|
}));
|
|
18112
|
-
|
|
20049
|
+
log7.info(`Registering workspace ${ws.id} (${ws.name ?? "unnamed"}) with ${runtimes.length} runtime(s)...`);
|
|
18113
20050
|
let resp;
|
|
18114
20051
|
try {
|
|
18115
20052
|
resp = await client.register(ws.token, {
|
|
@@ -18122,13 +20059,13 @@ async function startDaemon(profile, serverUrl) {
|
|
|
18122
20059
|
});
|
|
18123
20060
|
} catch (e) {
|
|
18124
20061
|
if (e instanceof Error && e.message.startsWith("HTTP 401")) {
|
|
18125
|
-
|
|
20062
|
+
log7.warn(`Workspace ${ws.id} token invalid — skipping (run '${cmdPrefix()} register --token <token>' to fix)`);
|
|
18126
20063
|
} else {
|
|
18127
|
-
|
|
20064
|
+
log7.error(`Failed to register workspace ${ws.id}, skipping`, e);
|
|
18128
20065
|
}
|
|
18129
20066
|
continue;
|
|
18130
20067
|
}
|
|
18131
|
-
|
|
20068
|
+
log7.info(`Workspace ${ws.id} registered — ${resp.runtimes.length} runtime(s)`);
|
|
18132
20069
|
const runtimeIds = resp.runtimes.map((r) => r.id);
|
|
18133
20070
|
workspaceStates.push({ workspaceId: ws.id, token: ws.token, runtimeIds });
|
|
18134
20071
|
for (let i = 0;i < runtimeIds.length; i++) {
|
|
@@ -18140,13 +20077,13 @@ async function startDaemon(profile, serverUrl) {
|
|
|
18140
20077
|
}
|
|
18141
20078
|
}
|
|
18142
20079
|
if (workspaceStates.length === 0) {
|
|
18143
|
-
|
|
20080
|
+
log7.error("No workspaces registered successfully.");
|
|
18144
20081
|
process.exit(1);
|
|
18145
20082
|
return;
|
|
18146
20083
|
}
|
|
18147
20084
|
const allRuntimeIds = workspaceStates.flatMap((ws) => ws.runtimeIds);
|
|
18148
20085
|
health.setRuntimeCount(allRuntimeIds.length);
|
|
18149
|
-
|
|
20086
|
+
log7.info(`Daemon started — ${allRuntimeIds.length} runtime(s) across ${workspaceStates.length} workspace(s)`);
|
|
18150
20087
|
const activeTasks = new Set;
|
|
18151
20088
|
const knownAgentIds = new Set(workspaces.flatMap((ws) => ws.agent_ids ?? []));
|
|
18152
20089
|
function syncAgentId(agentId, workspaceId) {
|
|
@@ -18181,7 +20118,7 @@ async function startDaemon(profile, serverUrl) {
|
|
|
18181
20118
|
cfg.watched_workspaces = (cfg.watched_workspaces || []).filter((w) => w.id !== workspaceId);
|
|
18182
20119
|
saveCLIConfigForProfile(profile, cfg);
|
|
18183
20120
|
} catch {}
|
|
18184
|
-
|
|
20121
|
+
log7.info(`Workspace ${workspaceId} deleted server-side — removed from config`);
|
|
18185
20122
|
}
|
|
18186
20123
|
const pollCycle = async () => {
|
|
18187
20124
|
let remaining = config2.maxConcurrentTasks - activeTasks.size;
|
|
@@ -18207,7 +20144,7 @@ async function startDaemon(profile, serverUrl) {
|
|
|
18207
20144
|
handleCliUpdate(pending_update.version, () => requestRestart(), profile);
|
|
18208
20145
|
}
|
|
18209
20146
|
if (pending_rescan) {
|
|
18210
|
-
|
|
20147
|
+
log7.info("Rescan requested — restarting daemon to re-detect runtimes");
|
|
18211
20148
|
for (const id of evictedIds) {
|
|
18212
20149
|
evictWorkspace(id);
|
|
18213
20150
|
}
|
|
@@ -18220,19 +20157,19 @@ async function startDaemon(profile, serverUrl) {
|
|
|
18220
20157
|
activeTasks.add(task.id);
|
|
18221
20158
|
remaining--;
|
|
18222
20159
|
handleTask(client, config2, runtimeIndex, task, ws.token, activeTasks).catch((e) => {
|
|
18223
|
-
|
|
20160
|
+
log7.error("Task error", e);
|
|
18224
20161
|
activeTasks.delete(task.id);
|
|
18225
20162
|
});
|
|
18226
20163
|
}
|
|
18227
20164
|
if (file_requests) {
|
|
18228
20165
|
for (const req of file_requests) {
|
|
18229
|
-
handleFileRequest(client, config2, ws.workspaceId, req, ws.token).catch((e) =>
|
|
20166
|
+
handleFileRequest(client, config2, ws.workspaceId, req, ws.token).catch((e) => log7.debug("File request error", e));
|
|
18230
20167
|
}
|
|
18231
20168
|
}
|
|
18232
20169
|
if (meetings) {
|
|
18233
20170
|
for (const m of meetings) {
|
|
18234
|
-
const agentBaseDir =
|
|
18235
|
-
const timelineDir =
|
|
20171
|
+
const agentBaseDir = join10(config2.workspacesRoot, m.workspace_id, m.agent_id, "workdir");
|
|
20172
|
+
const timelineDir = join10(agentBaseDir, ".context_timeline");
|
|
18236
20173
|
spawnMeetingRunner({
|
|
18237
20174
|
meetingId: m.id,
|
|
18238
20175
|
meetingUrl: m.meeting_url,
|
|
@@ -18249,9 +20186,9 @@ async function startDaemon(profile, serverUrl) {
|
|
|
18249
20186
|
}
|
|
18250
20187
|
} catch (e) {
|
|
18251
20188
|
if (e instanceof Error && e.message.startsWith("HTTP 401")) {
|
|
18252
|
-
|
|
20189
|
+
log7.warn(`Workspace ${ws.workspaceId} poll returned 401 — will retry next cycle`);
|
|
18253
20190
|
} else {
|
|
18254
|
-
|
|
20191
|
+
log7.debug("Poll error", e);
|
|
18255
20192
|
}
|
|
18256
20193
|
}
|
|
18257
20194
|
}
|
|
@@ -18261,10 +20198,10 @@ async function startDaemon(profile, serverUrl) {
|
|
|
18261
20198
|
try {
|
|
18262
20199
|
await reconcilePendingCompletions(config2.workspacesRoot);
|
|
18263
20200
|
} catch (e) {
|
|
18264
|
-
|
|
20201
|
+
log7.debug("reconciliation error", e);
|
|
18265
20202
|
}
|
|
18266
20203
|
if (workspaceStates.length === 0) {
|
|
18267
|
-
|
|
20204
|
+
log7.info("All workspaces evicted — shutting down");
|
|
18268
20205
|
shutdown();
|
|
18269
20206
|
}
|
|
18270
20207
|
};
|
|
@@ -18279,7 +20216,7 @@ async function startDaemon(profile, serverUrl) {
|
|
|
18279
20216
|
if (shuttingDown)
|
|
18280
20217
|
return;
|
|
18281
20218
|
shuttingDown = true;
|
|
18282
|
-
|
|
20219
|
+
log7.info(restartRequested ? "Restarting..." : "Shutting down...");
|
|
18283
20220
|
clearInterval(pollTimer);
|
|
18284
20221
|
const shutdownMs = restartRequested ? 30000 : Number(process.env.ALOOK_SHUTDOWN_TIMEOUT_MS) || 5000;
|
|
18285
20222
|
const timeout = setTimeout(() => process.exit(1), shutdownMs);
|
|
@@ -18300,12 +20237,12 @@ async function startDaemon(profile, serverUrl) {
|
|
|
18300
20237
|
const logPath = daemonLogFilePath();
|
|
18301
20238
|
let logFd;
|
|
18302
20239
|
try {
|
|
18303
|
-
|
|
20240
|
+
mkdirSync7(dirname3(logPath), { recursive: true, mode: 448 });
|
|
18304
20241
|
logFd = openSync(logPath, "a", 384);
|
|
18305
20242
|
} catch (e) {
|
|
18306
|
-
|
|
20243
|
+
log7.error(`Failed to open daemon log file ${logPath}`, e);
|
|
18307
20244
|
}
|
|
18308
|
-
const child =
|
|
20245
|
+
const child = spawn5(process.execPath, args, {
|
|
18309
20246
|
detached: true,
|
|
18310
20247
|
stdio: logFd != null ? ["ignore", logFd, logFd] : ["ignore", "ignore", "ignore"],
|
|
18311
20248
|
env: resolveLoginShellEnv()
|
|
@@ -18313,7 +20250,7 @@ async function startDaemon(profile, serverUrl) {
|
|
|
18313
20250
|
child.unref();
|
|
18314
20251
|
if (logFd != null)
|
|
18315
20252
|
closeSync(logFd);
|
|
18316
|
-
|
|
20253
|
+
log7.info(`Spawned new daemon (pid=${child.pid}), logs: ${logPath}`);
|
|
18317
20254
|
}
|
|
18318
20255
|
clearTimeout(timeout);
|
|
18319
20256
|
process.exit(0);
|
|
@@ -18324,7 +20261,7 @@ async function startDaemon(profile, serverUrl) {
|
|
|
18324
20261
|
process.on("SIGHUP", async () => {
|
|
18325
20262
|
if (shuttingDown)
|
|
18326
20263
|
return;
|
|
18327
|
-
|
|
20264
|
+
log7.info("SIGHUP received — reloading config...");
|
|
18328
20265
|
try {
|
|
18329
20266
|
const freshConfig = loadCLIConfigForProfile(profile);
|
|
18330
20267
|
const freshWorkspaces = freshConfig.watched_workspaces || [];
|
|
@@ -18332,7 +20269,7 @@ async function startDaemon(profile, serverUrl) {
|
|
|
18332
20269
|
const newWorkspaces = freshWorkspaces.filter((ws) => ws.token && !existingIds.has(ws.id));
|
|
18333
20270
|
for (const ws of newWorkspaces) {
|
|
18334
20271
|
const runtimes = providers.map((p) => ({ type: p.type, version: p.version }));
|
|
18335
|
-
|
|
20272
|
+
log7.info(`Registering new workspace ${ws.id} (${ws.name ?? "unnamed"})...`);
|
|
18336
20273
|
try {
|
|
18337
20274
|
const resp = await client.register(ws.token, {
|
|
18338
20275
|
workspace_id: ws.id,
|
|
@@ -18351,36 +20288,36 @@ async function startDaemon(profile, serverUrl) {
|
|
|
18351
20288
|
provider: providers[i].type
|
|
18352
20289
|
});
|
|
18353
20290
|
}
|
|
18354
|
-
|
|
20291
|
+
log7.info(`Workspace ${ws.id} added — ${runtimeIds.length} runtime(s)`);
|
|
18355
20292
|
} catch (e) {
|
|
18356
|
-
|
|
20293
|
+
log7.error(`Failed to register new workspace ${ws.id}`, e);
|
|
18357
20294
|
}
|
|
18358
20295
|
}
|
|
18359
20296
|
if (newWorkspaces.length > 0) {
|
|
18360
20297
|
health.setRuntimeCount(workspaceStates.reduce((sum, w) => sum + w.runtimeIds.length, 0));
|
|
18361
|
-
|
|
20298
|
+
log7.info(`Reload complete — now polling ${workspaceStates.length} workspace(s)`);
|
|
18362
20299
|
} else {
|
|
18363
|
-
|
|
20300
|
+
log7.info("Reload complete — no new workspaces found");
|
|
18364
20301
|
}
|
|
18365
20302
|
} catch (e) {
|
|
18366
|
-
|
|
20303
|
+
log7.error("Failed to reload config", e);
|
|
18367
20304
|
}
|
|
18368
20305
|
});
|
|
18369
20306
|
await pollCycle();
|
|
18370
20307
|
}
|
|
18371
20308
|
function spawnSessionRunner(input) {
|
|
18372
20309
|
const logDir = sessionRunnerLogDir();
|
|
18373
|
-
|
|
18374
|
-
const logFilePath =
|
|
20310
|
+
mkdirSync7(logDir, { recursive: true });
|
|
20311
|
+
const logFilePath = join10(logDir, `${input.task.id}.log`);
|
|
18375
20312
|
input.logFilePath = logFilePath;
|
|
18376
20313
|
const encoded = Buffer.from(JSON.stringify(input)).toString("base64");
|
|
18377
20314
|
let fd;
|
|
18378
20315
|
try {
|
|
18379
20316
|
fd = openSync(logFilePath, "a");
|
|
18380
20317
|
} catch (e) {
|
|
18381
|
-
|
|
20318
|
+
log7.error(`Failed to open log file ${logFilePath}`, e);
|
|
18382
20319
|
}
|
|
18383
|
-
const child =
|
|
20320
|
+
const child = spawn5(process.execPath, [sessionRunnerPath, encoded], {
|
|
18384
20321
|
detached: true,
|
|
18385
20322
|
stdio: fd != null ? ["ignore", fd, fd] : ["ignore", "ignore", "ignore"]
|
|
18386
20323
|
});
|
|
@@ -18391,27 +20328,27 @@ function spawnSessionRunner(input) {
|
|
|
18391
20328
|
}
|
|
18392
20329
|
function spawnMeetingRunner(input) {
|
|
18393
20330
|
const logDir = sessionRunnerLogDir();
|
|
18394
|
-
|
|
18395
|
-
const logFilePath =
|
|
20331
|
+
mkdirSync7(logDir, { recursive: true });
|
|
20332
|
+
const logFilePath = join10(logDir, `meeting-${input.meetingId}.log`);
|
|
18396
20333
|
const encoded = Buffer.from(JSON.stringify(input)).toString("base64");
|
|
18397
20334
|
let fd;
|
|
18398
20335
|
try {
|
|
18399
20336
|
fd = openSync(logFilePath, "a");
|
|
18400
20337
|
} catch (e) {
|
|
18401
|
-
|
|
20338
|
+
log7.error(`Failed to open meeting log file ${logFilePath}`, e);
|
|
18402
20339
|
}
|
|
18403
|
-
const child =
|
|
20340
|
+
const child = spawn5(process.execPath, [meetingRunnerPath, encoded], {
|
|
18404
20341
|
detached: true,
|
|
18405
20342
|
stdio: fd != null ? ["ignore", fd, fd] : ["ignore", "ignore", "ignore"]
|
|
18406
20343
|
});
|
|
18407
20344
|
child.unref();
|
|
18408
20345
|
if (fd != null)
|
|
18409
20346
|
closeSync(fd);
|
|
18410
|
-
|
|
20347
|
+
log7.info(`Spawned meeting runner for ${input.meetingId} (pid=${child.pid})`);
|
|
18411
20348
|
return child;
|
|
18412
20349
|
}
|
|
18413
20350
|
async function handleFileRequest(client, config2, workspaceId, req, token) {
|
|
18414
|
-
const agentWorkdir =
|
|
20351
|
+
const agentWorkdir = join10(config2.workspacesRoot, workspaceId, req.agent_id, "workdir");
|
|
18415
20352
|
const resolved = validatePath(agentWorkdir, req.path);
|
|
18416
20353
|
if (!resolved) {
|
|
18417
20354
|
await client.reportFileData(token, { request_id: req.id, error: "invalid path", path: req.path });
|
|
@@ -18434,7 +20371,7 @@ async function handleFileRequest(client, config2, workspaceId, req, token) {
|
|
|
18434
20371
|
}
|
|
18435
20372
|
}
|
|
18436
20373
|
async function handleTask(client, config2, runtimeIndex, task, token, activeTasks) {
|
|
18437
|
-
|
|
20374
|
+
log7.info(`Task ${task.id} claimed agent=${task.agentId}`);
|
|
18438
20375
|
if (task.type === TASK_TYPES.KILL_TASK) {
|
|
18439
20376
|
const targetTaskId = task.context?.target_task_id;
|
|
18440
20377
|
if (!targetTaskId) {
|
|
@@ -18442,8 +20379,8 @@ async function handleTask(client, config2, runtimeIndex, task, token, activeTask
|
|
|
18442
20379
|
activeTasks.delete(task.id);
|
|
18443
20380
|
return;
|
|
18444
20381
|
}
|
|
18445
|
-
const agentBaseDir =
|
|
18446
|
-
const timelineDir =
|
|
20382
|
+
const agentBaseDir = join10(config2.workspacesRoot, task.workspaceId, task.agentId, "workdir");
|
|
20383
|
+
const timelineDir = join10(agentBaseDir, ".context_timeline");
|
|
18447
20384
|
const MAX_WAIT_MS = Number(process.env.ALOOK_KILL_TASK_MAX_WAIT_MS) || 15000;
|
|
18448
20385
|
const POLL_MS = Number(process.env.ALOOK_KILL_TASK_POLL_MS) || 200;
|
|
18449
20386
|
const waitStart = Date.now();
|
|
@@ -18463,18 +20400,18 @@ async function handleTask(client, config2, runtimeIndex, task, token, activeTask
|
|
|
18463
20400
|
try {
|
|
18464
20401
|
process.kill(pid, "SIGTERM");
|
|
18465
20402
|
await client.failTask(token, task.id, "killed");
|
|
18466
|
-
|
|
20403
|
+
log7.info(`Kill task ${task.id}: sent SIGTERM to pid=${pid} for target=${targetTaskId}`);
|
|
18467
20404
|
} catch (e) {
|
|
18468
20405
|
if (e?.code === "ESRCH") {
|
|
18469
20406
|
await client.failTask(token, task.id, "target process already exited");
|
|
18470
|
-
|
|
20407
|
+
log7.info(`Kill task ${task.id}: target pid=${pid} already exited`);
|
|
18471
20408
|
} else {
|
|
18472
20409
|
await client.failTask(token, task.id, `kill failed: ${e}`);
|
|
18473
20410
|
}
|
|
18474
20411
|
}
|
|
18475
20412
|
} else {
|
|
18476
20413
|
await client.failTask(token, task.id, "target not found in timeline");
|
|
18477
|
-
|
|
20414
|
+
log7.info(`Kill task ${task.id}: target ${targetTaskId} not found in timeline`);
|
|
18478
20415
|
}
|
|
18479
20416
|
activeTasks.delete(task.id);
|
|
18480
20417
|
return;
|
|
@@ -18494,17 +20431,17 @@ async function handleTask(client, config2, runtimeIndex, task, token, activeTask
|
|
|
18494
20431
|
}
|
|
18495
20432
|
const provider = runtimeData.provider;
|
|
18496
20433
|
if (task.contextKey) {
|
|
18497
|
-
const agentBaseDir =
|
|
20434
|
+
const agentBaseDir = join10(config2.workspacesRoot, task.workspaceId, task.agentId, "workdir");
|
|
18498
20435
|
cleanupStaleIntents(agentBaseDir);
|
|
18499
|
-
const timelineDir =
|
|
20436
|
+
const timelineDir = join10(agentBaseDir, ".context_timeline");
|
|
18500
20437
|
const lockAcquired = acquireSteeringLock(agentBaseDir, task.contextKey);
|
|
18501
20438
|
if (!lockAcquired) {
|
|
18502
|
-
|
|
20439
|
+
log7.warn(`Steering lock contention for context_key=${task.contextKey}, proceeding without steering`);
|
|
18503
20440
|
} else {
|
|
18504
20441
|
try {
|
|
18505
20442
|
const predecessor = findRunningEntryByContextKey(timelineDir, task.contextKey, provider);
|
|
18506
20443
|
if (predecessor && predecessor.task_id !== task.id) {
|
|
18507
|
-
|
|
20444
|
+
log7.info(`Steering: task ${task.id} supersedes predecessor ${predecessor.task_id} (context_key=${task.contextKey})`);
|
|
18508
20445
|
if (predecessor.pid != null) {
|
|
18509
20446
|
writeKillIntent(agentBaseDir, {
|
|
18510
20447
|
reason: "superseded",
|
|
@@ -18514,12 +20451,12 @@ async function handleTask(client, config2, runtimeIndex, task, token, activeTask
|
|
|
18514
20451
|
});
|
|
18515
20452
|
try {
|
|
18516
20453
|
process.kill(predecessor.pid, "SIGTERM");
|
|
18517
|
-
|
|
20454
|
+
log7.info(`Steering: sent SIGTERM to predecessor pid=${predecessor.pid}`);
|
|
18518
20455
|
} catch (e) {
|
|
18519
20456
|
if (e?.code === "ESRCH") {
|
|
18520
|
-
|
|
20457
|
+
log7.info(`Steering: predecessor pid=${predecessor.pid} already exited`);
|
|
18521
20458
|
} else {
|
|
18522
|
-
|
|
20459
|
+
log7.warn(`Steering: kill failed for pid=${predecessor.pid}`, e);
|
|
18523
20460
|
}
|
|
18524
20461
|
}
|
|
18525
20462
|
const waitStart = Date.now();
|
|
@@ -18532,14 +20469,14 @@ async function handleTask(client, config2, runtimeIndex, task, token, activeTask
|
|
|
18532
20469
|
await new Promise((r) => setTimeout(r, POLL_MS));
|
|
18533
20470
|
}
|
|
18534
20471
|
if (findRunningPidByTaskId(timelineDir, predecessor.task_id) != null) {
|
|
18535
|
-
|
|
20472
|
+
log7.warn(`Steering: predecessor pid=${predecessor.pid} did not exit within ${MAX_WAIT_MS}ms, proceeding anyway`);
|
|
18536
20473
|
}
|
|
18537
20474
|
}
|
|
18538
20475
|
try {
|
|
18539
20476
|
await client.supersedeTask(token, predecessor.task_id);
|
|
18540
|
-
|
|
20477
|
+
log7.info(`Steering: predecessor ${predecessor.task_id} marked superseded`);
|
|
18541
20478
|
} catch (e) {
|
|
18542
|
-
|
|
20479
|
+
log7.warn(`Steering: failed to mark predecessor superseded server-side`, e);
|
|
18543
20480
|
}
|
|
18544
20481
|
}
|
|
18545
20482
|
} finally {
|
|
@@ -18563,15 +20500,46 @@ async function handleTask(client, config2, runtimeIndex, task, token, activeTask
|
|
|
18563
20500
|
messageInactivityTimeout: config2.messageInactivityTimeout
|
|
18564
20501
|
};
|
|
18565
20502
|
const child = spawnSessionRunner(input);
|
|
18566
|
-
child.on("close", () =>
|
|
18567
|
-
|
|
20503
|
+
child.on("close", async (code) => {
|
|
20504
|
+
activeTasks.delete(task.id);
|
|
20505
|
+
if (code !== 0) {
|
|
20506
|
+
const msg = code === null ? `session-runner killed by signal (task ${task.id})` : `session-runner crashed (exit code ${code}, task ${task.id})`;
|
|
20507
|
+
log7.warn(msg);
|
|
20508
|
+
const timelineDir = join10(config2.workspacesRoot, task.workspaceId, task.agentId, "workdir", ".context_timeline");
|
|
20509
|
+
updateEntry(timelineDir, task.id, (entry) => {
|
|
20510
|
+
entry.pid = null;
|
|
20511
|
+
entry.status = "failed";
|
|
20512
|
+
entry.errmsg = msg;
|
|
20513
|
+
});
|
|
20514
|
+
try {
|
|
20515
|
+
await client.failTask(token, task.id, msg);
|
|
20516
|
+
} catch (e) {
|
|
20517
|
+
if (isClientError2(e)) {
|
|
20518
|
+
log7.info(`Backstop: task ${task.id} already in terminal state`);
|
|
20519
|
+
return;
|
|
20520
|
+
}
|
|
20521
|
+
log7.error(`Backstop: failed to report crash for task ${task.id}`, e);
|
|
20522
|
+
try {
|
|
20523
|
+
await writeMarkerFile(config2.workspacesRoot, {
|
|
20524
|
+
taskId: task.id,
|
|
20525
|
+
type: "fail",
|
|
20526
|
+
payload: { error: msg },
|
|
20527
|
+
token,
|
|
20528
|
+
serverURL: config2.serverURL,
|
|
20529
|
+
createdAt: new Date().toISOString()
|
|
20530
|
+
});
|
|
20531
|
+
} catch {}
|
|
20532
|
+
}
|
|
20533
|
+
}
|
|
20534
|
+
});
|
|
20535
|
+
log7.info(`Task ${task.id} dispatched to session-runner (pid=${child.pid})`);
|
|
18568
20536
|
}
|
|
18569
20537
|
|
|
18570
20538
|
// commands/daemon.ts
|
|
18571
20539
|
var PID_POLL_INTERVAL_MS = 200;
|
|
18572
20540
|
var PID_POLL_TIMEOUT_MS = 2000;
|
|
18573
20541
|
var STOP_POLL_INTERVAL_MS = 200;
|
|
18574
|
-
function
|
|
20542
|
+
function sleep2(ms) {
|
|
18575
20543
|
return new Promise((r) => setTimeout(r, ms));
|
|
18576
20544
|
}
|
|
18577
20545
|
function buildChildArgs(profile, serverUrl) {
|
|
@@ -18590,7 +20558,7 @@ async function waitForPidFile(profile) {
|
|
|
18590
20558
|
const pid = readDaemonPid(profile);
|
|
18591
20559
|
if (pid != null && isProcessAlive(pid))
|
|
18592
20560
|
return pid;
|
|
18593
|
-
await
|
|
20561
|
+
await sleep2(PID_POLL_INTERVAL_MS);
|
|
18594
20562
|
}
|
|
18595
20563
|
return null;
|
|
18596
20564
|
}
|
|
@@ -18602,9 +20570,9 @@ async function startInBackground(profile, serverUrl) {
|
|
|
18602
20570
|
return;
|
|
18603
20571
|
}
|
|
18604
20572
|
const logPath = daemonLogFilePath();
|
|
18605
|
-
|
|
20573
|
+
mkdirSync8(dirname4(logPath), { recursive: true, mode: 448 });
|
|
18606
20574
|
const logFd = openSync2(logPath, "a", 384);
|
|
18607
|
-
const child =
|
|
20575
|
+
const child = spawn6(process.execPath, buildChildArgs(profile, serverUrl), {
|
|
18608
20576
|
detached: true,
|
|
18609
20577
|
stdio: ["ignore", logFd, logFd],
|
|
18610
20578
|
env: resolveLoginShellEnv()
|
|
@@ -18658,7 +20626,7 @@ async function stopCommand(profile) {
|
|
|
18658
20626
|
console.log("Daemon stopped.");
|
|
18659
20627
|
return;
|
|
18660
20628
|
}
|
|
18661
|
-
await
|
|
20629
|
+
await sleep2(STOP_POLL_INTERVAL_MS);
|
|
18662
20630
|
}
|
|
18663
20631
|
console.warn(`Daemon did not exit within ${shutdownMs}ms — force killing.`);
|
|
18664
20632
|
if (!isWindows) {
|
|
@@ -18723,10 +20691,22 @@ function configCommand() {
|
|
|
18723
20691
|
|
|
18724
20692
|
// commands/email.ts
|
|
18725
20693
|
import { Command as Command5 } from "commander";
|
|
18726
|
-
import { writeFileSync as
|
|
18727
|
-
import { basename, join as
|
|
20694
|
+
import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync9, readFileSync as readFileSync8, statSync as statSync4 } from "fs";
|
|
20695
|
+
import { basename, join as join11 } from "path";
|
|
18728
20696
|
import PostalMime from "postal-mime";
|
|
18729
|
-
|
|
20697
|
+
|
|
20698
|
+
// lib/flags.ts
|
|
20699
|
+
function resolveAgentId(opts) {
|
|
20700
|
+
const id = opts.agent_id || process.env.ALOOK_AGENT_ID;
|
|
20701
|
+
if (!id) {
|
|
20702
|
+
console.error("Error: --agent_id is required (or set ALOOK_AGENT_ID env var)");
|
|
20703
|
+
process.exit(1);
|
|
20704
|
+
}
|
|
20705
|
+
return id;
|
|
20706
|
+
}
|
|
20707
|
+
|
|
20708
|
+
// commands/email.ts
|
|
20709
|
+
var log8 = createLogger2({ module: "email" });
|
|
18730
20710
|
var VALID_STATUSES = ["unread", "read", "archived", "sent"];
|
|
18731
20711
|
var VALID_FOLDERS = ["inbox", "sent", "untrust"];
|
|
18732
20712
|
var EMAIL_BASE = tempDir("alook-emails");
|
|
@@ -18797,8 +20777,9 @@ function resolveClientOpts(command, opts) {
|
|
|
18797
20777
|
}
|
|
18798
20778
|
function emailCommand() {
|
|
18799
20779
|
const cmd = new Command5("email").description("Manage agent emails");
|
|
18800
|
-
cmd.command("pull").description("Download and parse emails to /tmp/alook-emails/{workspaceId}/{agentId}/").
|
|
18801
|
-
const
|
|
20780
|
+
cmd.command("pull").description("Download and parse emails to /tmp/alook-emails/{workspaceId}/{agentId}/").option("--agent_id <id>", "Agent ID").option("--status <status>", "Filter by status (unread, read, archived)").option("--folder <folder>", "Email folder (inbox, sent, untrust)").option("--limit <n>", "Maximum number of emails to download").option("--offset <n>", "Number of emails to skip").option("--workspace <id>", "Workspace ID").option("--json", "Output as JSON instead of files").action(async (opts, command) => {
|
|
20781
|
+
const agentId = resolveAgentId(opts);
|
|
20782
|
+
const { serverUrl, token, workspaceId } = resolveClientOpts(command, { workspace: opts.workspace, agentId });
|
|
18802
20783
|
const client = new APIClient(serverUrl, token, workspaceId);
|
|
18803
20784
|
if (opts.status && !VALID_STATUSES.includes(opts.status)) {
|
|
18804
20785
|
console.error(`Error: invalid status "${opts.status}", must be one of: ${VALID_STATUSES.join(", ")}`);
|
|
@@ -18822,9 +20803,9 @@ function emailCommand() {
|
|
|
18822
20803
|
process.exit(1);
|
|
18823
20804
|
}
|
|
18824
20805
|
}
|
|
18825
|
-
const emailDir_base =
|
|
20806
|
+
const emailDir_base = join11(EMAIL_BASE, workspaceId, agentId);
|
|
18826
20807
|
try {
|
|
18827
|
-
let query = `/api/email?agentId=${
|
|
20808
|
+
let query = `/api/email?agentId=${agentId}`;
|
|
18828
20809
|
if (opts.status)
|
|
18829
20810
|
query += `&status=${opts.status}`;
|
|
18830
20811
|
if (opts.folder)
|
|
@@ -18842,11 +20823,11 @@ function emailCommand() {
|
|
|
18842
20823
|
printJSON(emails2);
|
|
18843
20824
|
return;
|
|
18844
20825
|
}
|
|
18845
|
-
|
|
20826
|
+
mkdirSync9(emailDir_base, { recursive: true });
|
|
18846
20827
|
const downloadedPaths = [];
|
|
18847
20828
|
for (const email3 of emails2) {
|
|
18848
|
-
const emailDir =
|
|
18849
|
-
|
|
20829
|
+
const emailDir = join11(emailDir_base, email3.id);
|
|
20830
|
+
mkdirSync9(emailDir, { recursive: true });
|
|
18850
20831
|
const metadata = {
|
|
18851
20832
|
id: email3.id,
|
|
18852
20833
|
from: email3.from_email,
|
|
@@ -18858,8 +20839,8 @@ function emailCommand() {
|
|
|
18858
20839
|
in_reply_to: email3.in_reply_to || "",
|
|
18859
20840
|
references: email3.references || ""
|
|
18860
20841
|
};
|
|
18861
|
-
const metadataPath =
|
|
18862
|
-
|
|
20842
|
+
const metadataPath = join11(emailDir, "metadata.json");
|
|
20843
|
+
writeFileSync7(metadataPath, JSON.stringify(metadata, null, 2));
|
|
18863
20844
|
downloadedPaths.push(metadataPath);
|
|
18864
20845
|
let rawMime;
|
|
18865
20846
|
try {
|
|
@@ -18867,25 +20848,25 @@ function emailCommand() {
|
|
|
18867
20848
|
} catch (err) {
|
|
18868
20849
|
const msg = err instanceof Error ? err.message : String(err);
|
|
18869
20850
|
if (msg.includes("404")) {
|
|
18870
|
-
|
|
20851
|
+
log8.warn(`email body not available for ${email3.id}, skipping`);
|
|
18871
20852
|
continue;
|
|
18872
20853
|
}
|
|
18873
20854
|
throw err;
|
|
18874
20855
|
}
|
|
18875
20856
|
const parsed = await new PostalMime().parse(rawMime);
|
|
18876
20857
|
if (parsed.text) {
|
|
18877
|
-
const bodyPath =
|
|
18878
|
-
|
|
20858
|
+
const bodyPath = join11(emailDir, "body.txt");
|
|
20859
|
+
writeFileSync7(bodyPath, parsed.text);
|
|
18879
20860
|
downloadedPaths.push(bodyPath);
|
|
18880
20861
|
}
|
|
18881
20862
|
if (parsed.html) {
|
|
18882
|
-
const htmlPath =
|
|
18883
|
-
|
|
20863
|
+
const htmlPath = join11(emailDir, "body.html");
|
|
20864
|
+
writeFileSync7(htmlPath, parsed.html);
|
|
18884
20865
|
downloadedPaths.push(htmlPath);
|
|
18885
20866
|
}
|
|
18886
20867
|
if (parsed.attachments && parsed.attachments.length > 0) {
|
|
18887
|
-
const attDir =
|
|
18888
|
-
|
|
20868
|
+
const attDir = join11(emailDir, "attachments");
|
|
20869
|
+
mkdirSync9(attDir, { recursive: true });
|
|
18889
20870
|
const usedFilenames = new Set;
|
|
18890
20871
|
for (let i = 0;i < parsed.attachments.length; i++) {
|
|
18891
20872
|
const att = parsed.attachments[i];
|
|
@@ -18894,7 +20875,7 @@ function emailCommand() {
|
|
|
18894
20875
|
filename = `${i}-${filename}`;
|
|
18895
20876
|
}
|
|
18896
20877
|
usedFilenames.add(filename);
|
|
18897
|
-
const attPath =
|
|
20878
|
+
const attPath = join11(attDir, filename);
|
|
18898
20879
|
const content = att.content;
|
|
18899
20880
|
let buf;
|
|
18900
20881
|
if (typeof content === "string") {
|
|
@@ -18904,7 +20885,7 @@ function emailCommand() {
|
|
|
18904
20885
|
} else {
|
|
18905
20886
|
buf = Buffer.from(content);
|
|
18906
20887
|
}
|
|
18907
|
-
|
|
20888
|
+
writeFileSync7(attPath, buf);
|
|
18908
20889
|
downloadedPaths.push(attPath);
|
|
18909
20890
|
}
|
|
18910
20891
|
}
|
|
@@ -18918,8 +20899,9 @@ function emailCommand() {
|
|
|
18918
20899
|
process.exit(1);
|
|
18919
20900
|
}
|
|
18920
20901
|
});
|
|
18921
|
-
cmd.command("set").description("Update email status").
|
|
18922
|
-
const
|
|
20902
|
+
cmd.command("set").description("Update email status").option("--agent_id <id>", "Agent ID").requiredOption("--email_id <id>", "Email ID").requiredOption("--status <status>", "New status (unread, read, archived)").option("--workspace <id>", "Workspace ID").action(async (opts, command) => {
|
|
20903
|
+
const agentId = resolveAgentId(opts);
|
|
20904
|
+
const { serverUrl, token, workspaceId } = resolveClientOpts(command, { workspace: opts.workspace, agentId });
|
|
18923
20905
|
const client = new APIClient(serverUrl, token, workspaceId);
|
|
18924
20906
|
if (!VALID_STATUSES.includes(opts.status)) {
|
|
18925
20907
|
console.error(`Error: invalid status "${opts.status}", must be one of: ${VALID_STATUSES.join(", ")}`);
|
|
@@ -18935,15 +20917,16 @@ function emailCommand() {
|
|
|
18935
20917
|
process.exit(1);
|
|
18936
20918
|
}
|
|
18937
20919
|
});
|
|
18938
|
-
cmd.command("send").description("Send an email from the agent").
|
|
20920
|
+
cmd.command("send").description("Send an email from the agent").option("--agent_id <id>", "Agent ID").requiredOption("--to <addr>", "Recipient email address").requiredOption("--subject <s>", "Subject line").requiredOption("--body-file <path>", "Path to HTML body file").option("--from <addr>", "Send from a specific email address (custom mailbox)").option("--in-reply-to <emailId>", "Email ID to reply to (sets threading headers)").option("--attachment <path>", "Path to a file to attach (repeatable)", collectRepeated, []).option("--workspace <id>", "Workspace ID").action(async (opts, command) => {
|
|
20921
|
+
const agentId = resolveAgentId(opts);
|
|
18939
20922
|
const { serverUrl, token, workspaceId } = resolveClientOpts(command, {
|
|
18940
20923
|
workspace: opts.workspace,
|
|
18941
|
-
agentId
|
|
20924
|
+
agentId
|
|
18942
20925
|
});
|
|
18943
20926
|
const client = new APIClient(serverUrl, token, workspaceId);
|
|
18944
20927
|
let htmlBody;
|
|
18945
20928
|
try {
|
|
18946
|
-
htmlBody =
|
|
20929
|
+
htmlBody = readFileSync8(opts.bodyFile, "utf-8");
|
|
18947
20930
|
} catch (err) {
|
|
18948
20931
|
console.error(`Error: cannot read body file "${opts.bodyFile}": ${err instanceof Error ? err.message : err}`);
|
|
18949
20932
|
process.exit(1);
|
|
@@ -18955,17 +20938,17 @@ function emailCommand() {
|
|
|
18955
20938
|
const attachmentPaths = opts.attachment ?? [];
|
|
18956
20939
|
const attachments = [];
|
|
18957
20940
|
try {
|
|
18958
|
-
for (const
|
|
20941
|
+
for (const path2 of attachmentPaths) {
|
|
18959
20942
|
let bytes;
|
|
18960
20943
|
let size;
|
|
18961
20944
|
try {
|
|
18962
|
-
bytes =
|
|
18963
|
-
size = statSync4(
|
|
20945
|
+
bytes = readFileSync8(path2);
|
|
20946
|
+
size = statSync4(path2).size;
|
|
18964
20947
|
} catch (err) {
|
|
18965
|
-
console.error(`Error: cannot read attachment "${
|
|
20948
|
+
console.error(`Error: cannot read attachment "${path2}": ${err instanceof Error ? err.message : err}`);
|
|
18966
20949
|
process.exit(1);
|
|
18967
20950
|
}
|
|
18968
|
-
const filename = basename(
|
|
20951
|
+
const filename = basename(path2);
|
|
18969
20952
|
const contentType = guessContentType(filename);
|
|
18970
20953
|
const form = new FormData;
|
|
18971
20954
|
form.append("file", new Blob([new Uint8Array(bytes)], { type: contentType }), filename);
|
|
@@ -18987,14 +20970,14 @@ function emailCommand() {
|
|
|
18987
20970
|
references = [parentEmail.references, parentEmail.message_id].filter(Boolean).join(" ").trim() || undefined;
|
|
18988
20971
|
}
|
|
18989
20972
|
} catch {
|
|
18990
|
-
|
|
20973
|
+
log8.warn(`could not fetch parent email ${opts.inReplyTo}, sending without threading`);
|
|
18991
20974
|
}
|
|
18992
20975
|
}
|
|
18993
20976
|
const conversationId = process.env.ALOOK_CONVERSATION_ID;
|
|
18994
20977
|
const traceId = process.env.ALOOK_TRACE_ID;
|
|
18995
20978
|
const sourceTaskId = process.env.ALOOK_TASK_ID;
|
|
18996
20979
|
const res = await client.postJSON("/api/email/send", {
|
|
18997
|
-
agentId
|
|
20980
|
+
agentId,
|
|
18998
20981
|
to: opts.to,
|
|
18999
20982
|
subject: opts.subject,
|
|
19000
20983
|
htmlBody,
|
|
@@ -19011,10 +20994,11 @@ function emailCommand() {
|
|
|
19011
20994
|
process.exit(1);
|
|
19012
20995
|
}
|
|
19013
20996
|
});
|
|
19014
|
-
cmd.command("forward").description("Forward an email to a new recipient").
|
|
20997
|
+
cmd.command("forward").description("Forward an email to a new recipient").option("--agent_id <id>", "Agent ID").requiredOption("--email_id <id>", "Source email ID to forward").requiredOption("--to <addr>", "Recipient email address").option("--from <addr>", "Send from a specific email address (custom mailbox)").option("--note <text>", "Text to prepend above the forwarded message").option("--attachment <path>", "Extra file to attach (repeatable)", collectRepeated, []).option("--workspace <id>", "Workspace ID").action(async (opts, command) => {
|
|
20998
|
+
const agentId = resolveAgentId(opts);
|
|
19015
20999
|
const { serverUrl, token, workspaceId } = resolveClientOpts(command, {
|
|
19016
21000
|
workspace: opts.workspace,
|
|
19017
|
-
agentId
|
|
21001
|
+
agentId
|
|
19018
21002
|
});
|
|
19019
21003
|
const client = new APIClient(serverUrl, token, workspaceId);
|
|
19020
21004
|
try {
|
|
@@ -19067,17 +21051,17 @@ function emailCommand() {
|
|
|
19067
21051
|
}
|
|
19068
21052
|
}
|
|
19069
21053
|
const extraPaths = opts.attachment ?? [];
|
|
19070
|
-
for (const
|
|
21054
|
+
for (const path2 of extraPaths) {
|
|
19071
21055
|
let bytes;
|
|
19072
21056
|
let size;
|
|
19073
21057
|
try {
|
|
19074
|
-
bytes =
|
|
19075
|
-
size = statSync4(
|
|
21058
|
+
bytes = readFileSync8(path2);
|
|
21059
|
+
size = statSync4(path2).size;
|
|
19076
21060
|
} catch (err) {
|
|
19077
|
-
console.error(`Error: cannot read attachment "${
|
|
21061
|
+
console.error(`Error: cannot read attachment "${path2}": ${err instanceof Error ? err.message : err}`);
|
|
19078
21062
|
process.exit(1);
|
|
19079
21063
|
}
|
|
19080
|
-
const filename = basename(
|
|
21064
|
+
const filename = basename(path2);
|
|
19081
21065
|
const contentType = guessContentType(filename);
|
|
19082
21066
|
const form = new FormData;
|
|
19083
21067
|
form.append("file", new Blob([new Uint8Array(bytes)], { type: contentType }), filename);
|
|
@@ -19108,7 +21092,7 @@ function emailCommand() {
|
|
|
19108
21092
|
const traceId = process.env.ALOOK_TRACE_ID;
|
|
19109
21093
|
const sourceTaskId = process.env.ALOOK_TASK_ID;
|
|
19110
21094
|
const res = await client.postJSON("/api/email/send", {
|
|
19111
|
-
agentId
|
|
21095
|
+
agentId,
|
|
19112
21096
|
to: opts.to,
|
|
19113
21097
|
subject,
|
|
19114
21098
|
htmlBody,
|
|
@@ -19128,14 +21112,15 @@ function emailCommand() {
|
|
|
19128
21112
|
}
|
|
19129
21113
|
});
|
|
19130
21114
|
const whitelistCmd = new Command5("whitelist").description("Manage email whitelist (allowed senders)");
|
|
19131
|
-
whitelistCmd.command("list").description("List all whitelisted emails for an agent").
|
|
21115
|
+
whitelistCmd.command("list").description("List all whitelisted emails for an agent").option("--agent_id <id>", "Agent ID").option("--workspace <id>", "Workspace ID").option("--json", "Output as JSON").action(async (opts, command) => {
|
|
21116
|
+
const agentId = resolveAgentId(opts);
|
|
19132
21117
|
const { serverUrl, token, workspaceId } = resolveClientOpts(command, {
|
|
19133
21118
|
workspace: opts.workspace,
|
|
19134
|
-
agentId
|
|
21119
|
+
agentId
|
|
19135
21120
|
});
|
|
19136
21121
|
const client = new APIClient(serverUrl, token, workspaceId);
|
|
19137
21122
|
try {
|
|
19138
|
-
const entries = await client.getJSON(`/api/agents/${
|
|
21123
|
+
const entries = await client.getJSON(`/api/agents/${agentId}/whitelist`);
|
|
19139
21124
|
if (!entries.length) {
|
|
19140
21125
|
console.log("No whitelisted emails.");
|
|
19141
21126
|
return;
|
|
@@ -19150,14 +21135,15 @@ function emailCommand() {
|
|
|
19150
21135
|
process.exit(1);
|
|
19151
21136
|
}
|
|
19152
21137
|
});
|
|
19153
|
-
whitelistCmd.command("add").description("Add an email to the whitelist").
|
|
21138
|
+
whitelistCmd.command("add").description("Add an email to the whitelist").option("--agent_id <id>", "Agent ID").option("--workspace <id>", "Workspace ID").argument("<email>", "Email address to whitelist").action(async (email3, opts, command) => {
|
|
21139
|
+
const agentId = resolveAgentId(opts);
|
|
19154
21140
|
const { serverUrl, token, workspaceId } = resolveClientOpts(command, {
|
|
19155
21141
|
workspace: opts.workspace,
|
|
19156
|
-
agentId
|
|
21142
|
+
agentId
|
|
19157
21143
|
});
|
|
19158
21144
|
const client = new APIClient(serverUrl, token, workspaceId);
|
|
19159
21145
|
try {
|
|
19160
|
-
const entry = await client.postJSON(`/api/agents/${
|
|
21146
|
+
const entry = await client.postJSON(`/api/agents/${agentId}/whitelist`, { email: email3.toLowerCase() });
|
|
19161
21147
|
console.log(`Added ${entry.email} to whitelist (id: ${entry.id})`);
|
|
19162
21148
|
} catch (err) {
|
|
19163
21149
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -19169,21 +21155,22 @@ function emailCommand() {
|
|
|
19169
21155
|
process.exit(1);
|
|
19170
21156
|
}
|
|
19171
21157
|
});
|
|
19172
|
-
whitelistCmd.command("delete").description("Remove an email from the whitelist").
|
|
21158
|
+
whitelistCmd.command("delete").description("Remove an email from the whitelist").option("--agent_id <id>", "Agent ID").option("--workspace <id>", "Workspace ID").argument("<email>", "Email address to remove").action(async (email3, opts, command) => {
|
|
21159
|
+
const agentId = resolveAgentId(opts);
|
|
19173
21160
|
const { serverUrl, token, workspaceId } = resolveClientOpts(command, {
|
|
19174
21161
|
workspace: opts.workspace,
|
|
19175
|
-
agentId
|
|
21162
|
+
agentId
|
|
19176
21163
|
});
|
|
19177
21164
|
const client = new APIClient(serverUrl, token, workspaceId);
|
|
19178
21165
|
const normalizedEmail = email3.toLowerCase();
|
|
19179
21166
|
try {
|
|
19180
|
-
const entries = await client.getJSON(`/api/agents/${
|
|
21167
|
+
const entries = await client.getJSON(`/api/agents/${agentId}/whitelist`);
|
|
19181
21168
|
const entry = entries.find((e) => e.email === normalizedEmail);
|
|
19182
21169
|
if (!entry) {
|
|
19183
21170
|
console.error(`Error: ${normalizedEmail} is not in the whitelist`);
|
|
19184
21171
|
process.exit(1);
|
|
19185
21172
|
}
|
|
19186
|
-
await client.deleteJSON(`/api/agents/${
|
|
21173
|
+
await client.deleteJSON(`/api/agents/${agentId}/whitelist/${entry.id}`);
|
|
19187
21174
|
console.log(`Removed ${normalizedEmail} from whitelist`);
|
|
19188
21175
|
} catch (err) {
|
|
19189
21176
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -19243,8 +21230,9 @@ function printEventDetail(ev) {
|
|
|
19243
21230
|
}
|
|
19244
21231
|
function calendarCommand() {
|
|
19245
21232
|
const cmd = new Command6("calendar").description("Manage scheduled agent events");
|
|
19246
|
-
cmd.command("set").description("Create a calendar event").
|
|
19247
|
-
const
|
|
21233
|
+
cmd.command("set").description("Create a calendar event").option("--agent_id <id>", "Agent ID").requiredOption("--event_title <title>", "Event title (used as the task prompt)").requiredOption("--datetime <iso>", "Scheduled datetime (YYYY-MM-DDTHH:MM, local time)").option("--description <text>", "Optional longer-form notes for the event").option("--repeat <interval>", "Repeat interval, e.g. 1day, 2hour, 1month").option("--repeat_stop_date <date>", "Stop repeating on or after this date (YYYY-MM-DD, local time)").option("--json", "Output as JSON").action(async (opts, command) => {
|
|
21234
|
+
const agentId = resolveAgentId(opts);
|
|
21235
|
+
const { serverUrl, token, workspaceId } = resolveClientOpts2(command, agentId);
|
|
19248
21236
|
const client = new APIClient(serverUrl, token, workspaceId);
|
|
19249
21237
|
let scheduledAt;
|
|
19250
21238
|
try {
|
|
@@ -19262,7 +21250,7 @@ function calendarCommand() {
|
|
|
19262
21250
|
process.exit(1);
|
|
19263
21251
|
}
|
|
19264
21252
|
const body = {
|
|
19265
|
-
agent_id:
|
|
21253
|
+
agent_id: agentId,
|
|
19266
21254
|
title: opts.event_title,
|
|
19267
21255
|
scheduled_at: scheduledAt
|
|
19268
21256
|
};
|
|
@@ -19284,14 +21272,15 @@ function calendarCommand() {
|
|
|
19284
21272
|
process.exit(1);
|
|
19285
21273
|
}
|
|
19286
21274
|
});
|
|
19287
|
-
cmd.command("list").description("List calendar events for an agent").
|
|
19288
|
-
const
|
|
21275
|
+
cmd.command("list").description("List calendar events for an agent").option("--agent_id <id>", "Agent ID").option("--future_days <n>", "Include events scheduled in the next N days", "30").option("--past_days <n>", "Include events scheduled in the past N days", "0").option("--json", "Output as JSON").action(async (opts, command) => {
|
|
21276
|
+
const agentId = resolveAgentId(opts);
|
|
21277
|
+
const { serverUrl, token, workspaceId } = resolveClientOpts2(command, agentId);
|
|
19289
21278
|
const client = new APIClient(serverUrl, token, workspaceId);
|
|
19290
21279
|
const now = Date.now();
|
|
19291
21280
|
const from = new Date(now - Number(opts.past_days) * 86400000).toISOString();
|
|
19292
21281
|
const to = new Date(now + Number(opts.future_days) * 86400000).toISOString();
|
|
19293
21282
|
try {
|
|
19294
|
-
const events = await client.getJSON(`/api/calendar?agentId=${encodeURIComponent(
|
|
21283
|
+
const events = await client.getJSON(`/api/calendar?agentId=${encodeURIComponent(agentId)}&from=${encodeURIComponent(from)}&to=${encodeURIComponent(to)}`);
|
|
19295
21284
|
if (opts.json) {
|
|
19296
21285
|
printJSON(events);
|
|
19297
21286
|
return;
|
|
@@ -19310,13 +21299,14 @@ function calendarCommand() {
|
|
|
19310
21299
|
process.exit(1);
|
|
19311
21300
|
}
|
|
19312
21301
|
});
|
|
19313
|
-
cmd.command("show").description("Show the full detail of a single calendar event").
|
|
19314
|
-
const
|
|
21302
|
+
cmd.command("show").description("Show the full detail of a single calendar event").option("--agent_id <id>", "Agent ID").requiredOption("--event_id <id>", "Event ID").option("--json", "Output as JSON").action(async (opts, command) => {
|
|
21303
|
+
const agentId = resolveAgentId(opts);
|
|
21304
|
+
const { serverUrl, token, workspaceId } = resolveClientOpts2(command, agentId);
|
|
19315
21305
|
const client = new APIClient(serverUrl, token, workspaceId);
|
|
19316
21306
|
try {
|
|
19317
21307
|
const ev = await client.getJSON(`/api/calendar/${opts.event_id}`);
|
|
19318
|
-
if (ev.agent_id !==
|
|
19319
|
-
console.error(`Error: event ${ev.id} does not belong to agent ${
|
|
21308
|
+
if (ev.agent_id !== agentId) {
|
|
21309
|
+
console.error(`Error: event ${ev.id} does not belong to agent ${agentId}`);
|
|
19320
21310
|
process.exit(1);
|
|
19321
21311
|
}
|
|
19322
21312
|
if (opts.json) {
|
|
@@ -19329,7 +21319,7 @@ function calendarCommand() {
|
|
|
19329
21319
|
process.exit(1);
|
|
19330
21320
|
}
|
|
19331
21321
|
});
|
|
19332
|
-
cmd.command("update").description("Update fields on an existing calendar event").
|
|
21322
|
+
cmd.command("update").description("Update fields on an existing calendar event").option("--agent_id <id>", "Agent ID").requiredOption("--event_id <id>", "Event ID").option("--event_title <title>", "New event title (task prompt)").option("--description <text>", "New description text").option("--clear_description", "Remove the description (sets to null)").option("--datetime <iso>", "New scheduled datetime (YYYY-MM-DDTHH:MM, local time)").option("--repeat <interval>", "New repeat interval, e.g. 1day, 2hour").option("--clear_repeat", "Convert a repeating event into a one-off").option("--repeat_stop_date <date>", "New stop date (YYYY-MM-DD, local time)").option("--clear_repeat_stop_date", "Remove the repeat stop date").option("--json", "Output as JSON").action(async (opts, command) => {
|
|
19333
21323
|
if (opts.description && opts.clear_description) {
|
|
19334
21324
|
console.error("Error: --description and --clear_description are mutually exclusive");
|
|
19335
21325
|
process.exit(1);
|
|
@@ -19374,12 +21364,13 @@ function calendarCommand() {
|
|
|
19374
21364
|
console.error("Error: no fields to update — pass at least one of --event_title, --description, --clear_description, --datetime, --repeat, --clear_repeat, --repeat_stop_date, --clear_repeat_stop_date");
|
|
19375
21365
|
process.exit(1);
|
|
19376
21366
|
}
|
|
19377
|
-
const
|
|
21367
|
+
const agentId = resolveAgentId(opts);
|
|
21368
|
+
const { serverUrl, token, workspaceId } = resolveClientOpts2(command, agentId);
|
|
19378
21369
|
const client = new APIClient(serverUrl, token, workspaceId);
|
|
19379
21370
|
try {
|
|
19380
21371
|
const updated = await client.patchJSON(`/api/calendar/${opts.event_id}`, body);
|
|
19381
|
-
if (updated.agent_id !==
|
|
19382
|
-
console.error(`Error: event ${updated.id} does not belong to agent ${
|
|
21372
|
+
if (updated.agent_id !== agentId) {
|
|
21373
|
+
console.error(`Error: event ${updated.id} does not belong to agent ${agentId}`);
|
|
19383
21374
|
process.exit(1);
|
|
19384
21375
|
}
|
|
19385
21376
|
if (opts.json) {
|
|
@@ -19392,8 +21383,9 @@ function calendarCommand() {
|
|
|
19392
21383
|
process.exit(1);
|
|
19393
21384
|
}
|
|
19394
21385
|
});
|
|
19395
|
-
cmd.command("delete").description("Delete a calendar event").
|
|
19396
|
-
const
|
|
21386
|
+
cmd.command("delete").description("Delete a calendar event").option("--agent_id <id>", "Agent ID").requiredOption("--event_id <id>", "Event ID").action(async (opts, command) => {
|
|
21387
|
+
const agentId = resolveAgentId(opts);
|
|
21388
|
+
const { serverUrl, token, workspaceId } = resolveClientOpts2(command, agentId);
|
|
19397
21389
|
const client = new APIClient(serverUrl, token, workspaceId);
|
|
19398
21390
|
try {
|
|
19399
21391
|
await client.deleteJSON(`/api/calendar/${opts.event_id}`);
|
|
@@ -19408,7 +21400,7 @@ function calendarCommand() {
|
|
|
19408
21400
|
|
|
19409
21401
|
// commands/issue.ts
|
|
19410
21402
|
import { Command as Command7 } from "commander";
|
|
19411
|
-
import { readFileSync as
|
|
21403
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
19412
21404
|
var VALID_STATUSES2 = ["todo", "in_progress", "review", "done", "closed", "canceled", "failed"];
|
|
19413
21405
|
function resolveClientOpts3(command, agentId) {
|
|
19414
21406
|
let root = command;
|
|
@@ -19432,7 +21424,7 @@ function readBody(opts) {
|
|
|
19432
21424
|
process.exit(1);
|
|
19433
21425
|
}
|
|
19434
21426
|
if (opts.bodyFile)
|
|
19435
|
-
return
|
|
21427
|
+
return readFileSync9(opts.bodyFile, "utf-8");
|
|
19436
21428
|
return opts.body ?? "";
|
|
19437
21429
|
}
|
|
19438
21430
|
function printIssue(issue3) {
|
|
@@ -19466,13 +21458,14 @@ comments:`);
|
|
|
19466
21458
|
}
|
|
19467
21459
|
function issueCommand() {
|
|
19468
21460
|
const cmd = new Command7("issue").description("Manage assigned issues");
|
|
19469
|
-
cmd.command("create").description("Create and dispatch an issue to an agent").
|
|
19470
|
-
const
|
|
21461
|
+
cmd.command("create").description("Create and dispatch an issue to an agent").option("--agent_id <id>", "Agent ID").requiredOption("--title <title>", "Issue title").option("--description <text>", "Issue description").option("--body-file <path>", "Read issue description from a file").option("--json", "Output as JSON").action(async (opts, command) => {
|
|
21462
|
+
const agentId = resolveAgentId(opts);
|
|
21463
|
+
const { serverUrl, token, workspaceId } = resolveClientOpts3(command, agentId);
|
|
19471
21464
|
const client = new APIClient(serverUrl, token, workspaceId);
|
|
19472
21465
|
const description = readBody({ body: opts.description, bodyFile: opts.bodyFile });
|
|
19473
21466
|
try {
|
|
19474
21467
|
const res = await client.postJSON("/api/issues", {
|
|
19475
|
-
agent_id:
|
|
21468
|
+
agent_id: agentId,
|
|
19476
21469
|
title: opts.title,
|
|
19477
21470
|
description
|
|
19478
21471
|
});
|
|
@@ -19484,14 +21477,15 @@ function issueCommand() {
|
|
|
19484
21477
|
process.exit(1);
|
|
19485
21478
|
}
|
|
19486
21479
|
});
|
|
19487
|
-
cmd.command("list").description("List issues for an agent").
|
|
21480
|
+
cmd.command("list").description("List issues for an agent").option("--agent_id <id>", "Agent ID").option("--status <status>", `Filter by status (${VALID_STATUSES2.join(", ")})`).option("--completed", "Show completed/closed/canceled/failed issues").option("--all", "Show all issues").option("--json", "Output as JSON").action(async (opts, command) => {
|
|
19488
21481
|
if (opts.status && !VALID_STATUSES2.includes(opts.status)) {
|
|
19489
21482
|
console.error(`Error: invalid status "${opts.status}"`);
|
|
19490
21483
|
process.exit(1);
|
|
19491
21484
|
}
|
|
19492
|
-
const
|
|
21485
|
+
const agentId = resolveAgentId(opts);
|
|
21486
|
+
const { serverUrl, token, workspaceId } = resolveClientOpts3(command, agentId);
|
|
19493
21487
|
const client = new APIClient(serverUrl, token, workspaceId);
|
|
19494
|
-
const params = new URLSearchParams({ agentId
|
|
21488
|
+
const params = new URLSearchParams({ agentId });
|
|
19495
21489
|
if (opts.status)
|
|
19496
21490
|
params.set("status", opts.status);
|
|
19497
21491
|
if (!opts.all && !opts.status)
|
|
@@ -19511,13 +21505,14 @@ function issueCommand() {
|
|
|
19511
21505
|
process.exit(1);
|
|
19512
21506
|
}
|
|
19513
21507
|
});
|
|
19514
|
-
cmd.command("show").description("Show issue details and conversation").
|
|
19515
|
-
const
|
|
21508
|
+
cmd.command("show").description("Show issue details and conversation").option("--agent_id <id>", "Agent ID").requiredOption("--issue_id <id>", "Issue ID").option("--json", "Output as JSON").action(async (opts, command) => {
|
|
21509
|
+
const agentId = resolveAgentId(opts);
|
|
21510
|
+
const { serverUrl, token, workspaceId } = resolveClientOpts3(command, agentId);
|
|
19516
21511
|
const client = new APIClient(serverUrl, token, workspaceId);
|
|
19517
21512
|
try {
|
|
19518
|
-
const res = await client.getJSON(`/api/issues/${opts.issue_id}?agentId=${encodeURIComponent(
|
|
19519
|
-
if (res.issue.agent_id !==
|
|
19520
|
-
console.error(`Error: issue ${res.issue.id} does not belong to agent ${
|
|
21513
|
+
const res = await client.getJSON(`/api/issues/${opts.issue_id}?agentId=${encodeURIComponent(agentId)}`);
|
|
21514
|
+
if (res.issue.agent_id !== agentId) {
|
|
21515
|
+
console.error(`Error: issue ${res.issue.id} does not belong to agent ${agentId}`);
|
|
19521
21516
|
process.exit(1);
|
|
19522
21517
|
}
|
|
19523
21518
|
if (opts.json)
|
|
@@ -19528,7 +21523,7 @@ function issueCommand() {
|
|
|
19528
21523
|
process.exit(1);
|
|
19529
21524
|
}
|
|
19530
21525
|
});
|
|
19531
|
-
cmd.command("update").description("Update issue status or text").
|
|
21526
|
+
cmd.command("update").description("Update issue status or text").option("--agent_id <id>", "Agent ID").requiredOption("--issue_id <id>", "Issue ID").option("--status <status>", `New status (${VALID_STATUSES2.join(", ")})`).option("--title <title>", "New title").option("--description <text>", "New description").option("--body-file <path>", "Read description from a file").option("--json", "Output as JSON").action(async (opts, command) => {
|
|
19532
21527
|
if (opts.status && !VALID_STATUSES2.includes(opts.status)) {
|
|
19533
21528
|
console.error(`Error: invalid status "${opts.status}"`);
|
|
19534
21529
|
process.exit(1);
|
|
@@ -19545,10 +21540,11 @@ function issueCommand() {
|
|
|
19545
21540
|
console.error("Error: pass at least one of --status, --title, --description, --body-file");
|
|
19546
21541
|
process.exit(1);
|
|
19547
21542
|
}
|
|
19548
|
-
const
|
|
21543
|
+
const agentId = resolveAgentId(opts);
|
|
21544
|
+
const { serverUrl, token, workspaceId } = resolveClientOpts3(command, agentId);
|
|
19549
21545
|
const client = new APIClient(serverUrl, token, workspaceId);
|
|
19550
21546
|
try {
|
|
19551
|
-
const issue3 = await client.patchJSON(`/api/issues/${opts.issue_id}?agentId=${encodeURIComponent(
|
|
21547
|
+
const issue3 = await client.patchJSON(`/api/issues/${opts.issue_id}?agentId=${encodeURIComponent(agentId)}`, body);
|
|
19552
21548
|
if (opts.json)
|
|
19553
21549
|
return printJSON(issue3);
|
|
19554
21550
|
printIssue(issue3);
|
|
@@ -19557,16 +21553,17 @@ function issueCommand() {
|
|
|
19557
21553
|
process.exit(1);
|
|
19558
21554
|
}
|
|
19559
21555
|
});
|
|
19560
|
-
cmd.command("comment").description("Append a comment to an issue").
|
|
21556
|
+
cmd.command("comment").description("Append a comment to an issue").option("--agent_id <id>", "Agent ID").requiredOption("--issue_id <id>", "Issue ID").option("--body <text>", "Comment text").option("--body-file <path>", "Read comment from a file").option("--json", "Output as JSON").action(async (opts, command) => {
|
|
19561
21557
|
const content = readBody({ body: opts.body, bodyFile: opts.bodyFile }).trim();
|
|
19562
21558
|
if (!content) {
|
|
19563
21559
|
console.error("Error: pass --body or --body-file");
|
|
19564
21560
|
process.exit(1);
|
|
19565
21561
|
}
|
|
19566
|
-
const
|
|
21562
|
+
const agentId = resolveAgentId(opts);
|
|
21563
|
+
const { serverUrl, token, workspaceId } = resolveClientOpts3(command, agentId);
|
|
19567
21564
|
const client = new APIClient(serverUrl, token, workspaceId);
|
|
19568
21565
|
try {
|
|
19569
|
-
const res = await client.postJSON(`/api/issues/${opts.issue_id}/comments?agentId=${encodeURIComponent(
|
|
21566
|
+
const res = await client.postJSON(`/api/issues/${opts.issue_id}/comments?agentId=${encodeURIComponent(agentId)}`, { content });
|
|
19570
21567
|
if (opts.json)
|
|
19571
21568
|
return printJSON(res);
|
|
19572
21569
|
console.log(`Commented on ${opts.issue_id}`);
|
|
@@ -19625,7 +21622,7 @@ ${result.output}`);
|
|
|
19625
21622
|
|
|
19626
21623
|
// commands/sync.ts
|
|
19627
21624
|
import { Command as Command10 } from "commander";
|
|
19628
|
-
import { readFileSync as
|
|
21625
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
19629
21626
|
import { basename as basename2 } from "path";
|
|
19630
21627
|
var MIME_BY_EXT2 = {
|
|
19631
21628
|
".pdf": "application/pdf",
|
|
@@ -19668,12 +21665,13 @@ function resolveClientOpts4(command, agentId) {
|
|
|
19668
21665
|
}
|
|
19669
21666
|
function syncCommand() {
|
|
19670
21667
|
const cmd = new Command10("sync").description("File sync utilities");
|
|
19671
|
-
cmd.command("upload-artifact").description("Upload a file artifact to a conversation").
|
|
19672
|
-
const
|
|
21668
|
+
cmd.command("upload-artifact").description("Upload a file artifact to a conversation").option("--agent_id <id>", "Agent ID").requiredOption("--conversation_id <id>", "Conversation ID").requiredOption("--file <path>", "Path to file to upload").action(async (opts, command) => {
|
|
21669
|
+
const agentId = resolveAgentId(opts);
|
|
21670
|
+
const { serverUrl, token, workspaceId } = resolveClientOpts4(command, agentId);
|
|
19673
21671
|
const client = new APIClient(serverUrl, token, workspaceId);
|
|
19674
21672
|
let bytes;
|
|
19675
21673
|
try {
|
|
19676
|
-
bytes =
|
|
21674
|
+
bytes = readFileSync10(opts.file);
|
|
19677
21675
|
} catch (err) {
|
|
19678
21676
|
console.error(`Error: cannot read file "${opts.file}": ${err.message}`);
|
|
19679
21677
|
process.exit(1);
|
|
@@ -19682,7 +21680,7 @@ function syncCommand() {
|
|
|
19682
21680
|
const contentType = guessContentType2(filename);
|
|
19683
21681
|
const form = new FormData;
|
|
19684
21682
|
form.append("file", new Blob([new Uint8Array(bytes)], { type: contentType }), filename);
|
|
19685
|
-
form.append("agent_id",
|
|
21683
|
+
form.append("agent_id", agentId);
|
|
19686
21684
|
form.append("conversation_id", opts.conversation_id);
|
|
19687
21685
|
try {
|
|
19688
21686
|
const result = await client.postMultipart("/api/artifacts/upload", form);
|