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