@saadnvd1/agent-os 0.3.700 → 0.3.723
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/app/api/agent-roles/route.test.ts +67 -0
- package/app/api/agent-roles/route.ts +61 -0
- package/app/api/analyst/[id]/recommendations/[recId]/again/route.ts +15 -0
- package/app/api/analyst/[id]/recommendations/[recId]/dismiss/route.ts +13 -0
- package/app/api/analyst/[id]/recommendations/[recId]/send/route.ts +13 -0
- package/app/api/analyst/[id]/review/route.ts +22 -0
- package/app/api/analyst/[id]/route.ts +44 -0
- package/app/api/analyst/route.ts +10 -0
- package/app/api/bus/groups/route.ts +44 -0
- package/app/api/bus/spawn/route.ts +34 -2
- package/app/api/claims/[id]/route.ts +20 -0
- package/app/api/claims/check/route.ts +23 -0
- package/app/api/claims/check.test.ts +113 -0
- package/app/api/claims/exchange/route.ts +42 -0
- package/app/api/claims/outside.test.ts +90 -0
- package/app/api/claims/route.test.ts +193 -0
- package/app/api/claims/route.ts +73 -0
- package/app/api/hosts/[id]/route.ts +9 -1
- package/app/api/models/availability/route.ts +9 -0
- package/app/api/models/choices/route.ts +9 -0
- package/app/api/peer/unlink/route.ts +17 -0
- package/app/api/peers/[hostId]/audit/route.ts +14 -0
- package/app/api/peers/[hostId]/route.ts +20 -0
- package/app/api/peers/gate.test.ts +64 -1
- package/app/api/peers/pending/route.test.ts +37 -0
- package/app/api/peers/pending/route.ts +11 -4
- package/app/api/session-groups/[id]/restore/route.ts +11 -0
- package/app/api/session-groups/[id]/route.ts +25 -0
- package/app/api/session-groups/order/route.ts +15 -0
- package/app/api/session-groups/route.ts +21 -0
- package/app/api/sessions/[id]/analyst/route.ts +11 -0
- package/app/api/sessions/[id]/crew/route.ts +38 -0
- package/app/api/sessions/[id]/export/estimate/route.ts +23 -0
- package/app/api/sessions/[id]/export/route.test.ts +125 -0
- package/app/api/sessions/[id]/export/route.ts +34 -0
- package/app/api/sessions/[id]/fork/route.ts +3 -0
- package/app/api/sessions/[id]/group/route.ts +18 -0
- package/app/api/sessions/[id]/model/route.ts +49 -0
- package/app/api/sessions/[id]/route.ts +14 -4
- package/app/api/sessions/[id]/summarize/route.ts +2 -0
- package/app/api/sessions/claims.test.ts +122 -0
- package/app/api/sessions/group/route.ts +17 -0
- package/app/api/sessions/route.ts +31 -14
- package/app/page.tsx +10 -0
- package/components/Chat/Messages.tsx +104 -10
- package/components/Claims/ClaimFiles.tsx +38 -0
- package/components/Claims/ClaimRow.tsx +82 -0
- package/components/Claims/ClaimsSheet.tsx +176 -0
- package/components/Claims/OverlapPill.tsx +31 -0
- package/components/Claims/StartClaimsNotice.tsx +70 -0
- package/components/Claims/claims-ui.test.ts +161 -0
- package/components/Claims/format.ts +57 -0
- package/components/Claims/index.ts +3 -0
- package/components/Draft/DraftChips.tsx +6 -1
- package/components/Draft/DraftPanel.tsx +90 -3
- package/components/Draft/ModelUnavailableDialog.tsx +78 -0
- package/components/Draft/RoleChips.tsx +110 -0
- package/components/Export/ExportActions.tsx +136 -0
- package/components/Export/ExportSheet.tsx +217 -0
- package/components/Export/RangePicker.tsx +144 -0
- package/components/Export/Seg.tsx +38 -0
- package/components/Export/TurnSelectBar.tsx +45 -0
- package/components/Hosts/AccountMachines.tsx +32 -18
- package/components/Hosts/HostRow.tsx +155 -14
- package/components/Hosts/IncomingLinkSheet.tsx +28 -10
- package/components/Hosts/PeerActivitySheet.tsx +111 -0
- package/components/Models/AgentRoleCard.tsx +114 -0
- package/components/Models/AgentRolesSection.tsx +132 -0
- package/components/Models/ModelBadges.tsx +48 -0
- package/components/Models/ModelsSection.tsx +4 -2
- package/components/Models/RoleModelSelect.tsx +87 -0
- package/components/Pane/DesktopTabBar.tsx +2 -0
- package/components/Pane/MobileTabBar.tsx +12 -0
- package/components/Pane/PaneMenu.tsx +10 -0
- package/components/SessionList/GroupShelves.tsx +195 -0
- package/components/SessionList/MoveToGroupMenu.tsx +105 -0
- package/components/SessionList/RowSubtitle.tsx +7 -1
- package/components/SessionList/SelectionToolbar.tsx +52 -1
- package/components/SessionList/SessionRow.tsx +19 -7
- package/components/SessionList/SessionRowMenu.tsx +23 -0
- package/components/SessionList/SessionShelves.tsx +5 -0
- package/components/SessionList/SidebarBody.tsx +6 -0
- package/components/SessionList/SidebarContextMenu.test.ts +104 -0
- package/components/SessionList/SidebarContextMenu.tsx +59 -0
- package/components/SessionList/SidebarHeader.tsx +5 -0
- package/components/SessionList/WorkspaceSwitcher.tsx +23 -6
- package/components/SessionList/index.tsx +18 -14
- package/components/SessionList/useSidebarData.ts +10 -0
- package/components/Sessions/RolePill.tsx +196 -0
- package/components/Settings/SettingsDialog.tsx +9 -0
- package/components/Share/ShareDialog.tsx +13 -0
- package/components/ui/notice.tsx +46 -0
- package/components/ui/status-pill.tsx +39 -0
- package/data/analyst/index.ts +3 -0
- package/data/analyst/keys.ts +6 -0
- package/data/analyst/mutations.ts +116 -0
- package/data/analyst/queries.ts +35 -0
- package/data/claims/index.ts +2 -0
- package/data/claims/keys.ts +5 -0
- package/data/claims/queries.ts +47 -0
- package/data/exports/index.ts +2 -0
- package/data/exports/keys.ts +3 -0
- package/data/exports/queries.ts +51 -0
- package/data/models/index.ts +98 -0
- package/data/peers/keys.ts +1 -0
- package/data/peers/queries.ts +31 -0
- package/data/push/topics.ts +9 -0
- package/data/session-groups/index.ts +156 -0
- package/data/sessions/index.ts +0 -1
- package/data/sessions/pin.ts +2 -6
- package/data/sessions/queries.ts +36 -29
- package/data/sessions/shelf.ts +2 -6
- package/data/tasks/index.ts +2 -0
- package/hooks/useSessions.ts +0 -12
- package/lib/__fixtures__/peer-rpc.ts +65 -33
- package/lib/agents/brief.ts +1 -0
- package/lib/agents/spawn.ts +4 -0
- package/lib/analyst/__fixtures__/items.ts +57 -0
- package/lib/analyst/__fixtures__/mocks.ts +49 -0
- package/lib/analyst/brief.ts +17 -0
- package/lib/analyst/buildDigest.ts +204 -0
- package/lib/analyst/createLink.ts +98 -0
- package/lib/analyst/endLink.ts +31 -0
- package/lib/analyst/forward.ts +109 -0
- package/lib/analyst/index.ts +16 -0
- package/lib/analyst/linkView.ts +38 -0
- package/lib/analyst/onTurnEnd.ts +119 -0
- package/lib/analyst/parseReview.ts +41 -0
- package/lib/analyst/pushDigest.ts +143 -0
- package/lib/analyst/resume.ts +35 -0
- package/lib/analyst/route.ts +50 -0
- package/lib/analyst/signals.ts +121 -0
- package/lib/analyst/store.ts +303 -0
- package/lib/analyst/types.ts +140 -0
- package/lib/bus/condense.ts +5 -1
- package/lib/bus/index.ts +13 -0
- package/lib/bus/read-scope.ts +12 -0
- package/lib/bus/role-alias.ts +70 -0
- package/lib/bus/targets.ts +3 -0
- package/lib/chat/drivers/opencode-mapper.ts +55 -1
- package/lib/chat/drivers/pi-approvals.ts +7 -4
- package/lib/chat/drivers/pi-mapper.ts +38 -0
- package/lib/chat/drivers/pi-text-tools.ts +104 -0
- package/lib/chat/drivers/pi.ts +8 -3
- package/lib/chat/drivers/turn-items.ts +36 -11
- package/lib/chat/drivers/turn-outcome.ts +108 -0
- package/lib/chat/events.ts +8 -0
- package/lib/chat/registry.ts +13 -0
- package/lib/chat/store.ts +19 -0
- package/lib/chat/turns.ts +1 -0
- package/lib/chat/worker/extras.ts +4 -2
- package/lib/claims/collectLocal.ts +123 -0
- package/lib/claims/describe.ts +41 -0
- package/lib/claims/exchange.ts +91 -0
- package/lib/claims/goal.ts +45 -0
- package/lib/claims/index.ts +10 -0
- package/lib/claims/loop.ts +51 -0
- package/lib/claims/machine.ts +21 -0
- package/lib/claims/mine.ts +43 -0
- package/lib/claims/notifyOverlaps.ts +101 -0
- package/lib/claims/onMerged.ts +85 -0
- package/lib/claims/outside.ts +36 -0
- package/lib/claims/overlap.ts +87 -0
- package/lib/claims/overlapCache.ts +86 -0
- package/lib/claims/overlapText.ts +36 -0
- package/lib/claims/peers.ts +56 -0
- package/lib/claims/startCheck.ts +100 -0
- package/lib/claims/startWarning.ts +29 -0
- package/lib/claims/store.ts +366 -0
- package/lib/claims/types.ts +102 -0
- package/lib/claims/view.ts +80 -0
- package/lib/claims/yield.ts +24 -0
- package/lib/db/changes.ts +12 -0
- package/lib/db/migrations/20261020T1400_work_claims.ts +35 -0
- package/lib/db/migrations/20261020T1515_peer_unlinked.ts +22 -0
- package/lib/db/migrations/20261020T1700_clean_connect_names.ts +18 -0
- package/lib/db/migrations/20261020T1800_session_groups.ts +49 -0
- package/lib/db/migrations/20261020T1900_task_queue_after_claim.ts +13 -0
- package/lib/db/migrations/20261020T2000_claim_notices.ts +20 -0
- package/lib/db/migrations/20261020T2100_claims_via_host.ts +21 -0
- package/lib/db/migrations/20261020T2200_work_claims_watch.ts +10 -0
- package/lib/db/migrations/20261021T1400_session_agent_roles.ts +20 -0
- package/lib/db/migrations/20261021T1430_analyst_links.ts +52 -0
- package/lib/db/queries.ts +0 -40
- package/lib/db/session-parts/columns.ts +3 -0
- package/lib/db/types.ts +19 -8
- package/lib/doctor/checks.ts +64 -1
- package/lib/done/archive.ts +2 -0
- package/lib/drafts.ts +4 -0
- package/lib/export/build.ts +470 -0
- package/lib/export/compact.ts +156 -0
- package/lib/export/estimate.ts +83 -0
- package/lib/export/fixtures.ts +45 -0
- package/lib/export/html.tsx +195 -0
- package/lib/export/json.ts +39 -0
- package/lib/export/load.ts +64 -0
- package/lib/export/markdown.ts +127 -0
- package/lib/export/request.ts +36 -0
- package/lib/export/service.ts +142 -0
- package/lib/export/summary.ts +86 -0
- package/lib/export/turns.ts +63 -0
- package/lib/export/view.ts +173 -0
- package/lib/git-remote.ts +43 -0
- package/lib/hosts/index.ts +10 -1
- package/lib/models/agent-binding.ts +102 -0
- package/lib/models/agent-roles-view.ts +157 -0
- package/lib/models/agent-roles.ts +87 -0
- package/lib/models/availability-refs.ts +17 -0
- package/lib/models/availability.ts +91 -0
- package/lib/models/catalog.ts +156 -0
- package/lib/models/config.ts +27 -0
- package/lib/models/locality.ts +33 -0
- package/lib/models/providers.ts +11 -0
- package/lib/models/resolve.ts +50 -12
- package/lib/models/roles.ts +12 -0
- package/lib/needs-you.ts +1 -1
- package/lib/orchestration.ts +2 -0
- package/lib/orchestrator/act.ts +54 -4
- package/lib/orchestrator/brief.ts +2 -1
- package/lib/orchestrator/claim-wait.ts +71 -0
- package/lib/orchestrator/conditions.ts +28 -1
- package/lib/orchestrator/describe.ts +6 -1
- package/lib/orchestrator/facts.ts +12 -2
- package/lib/orchestrator/read.ts +5 -1
- package/lib/orchestrator/serve.ts +37 -2
- package/lib/orchestrator/tool-names.ts +1 -0
- package/lib/orchestrator/tool-schemas.ts +31 -1
- package/lib/orchestrator/tools.ts +3 -1
- package/lib/orchestrator/untrusted.ts +5 -0
- package/lib/orchestrator/watcher.ts +19 -1
- package/lib/peer/account.ts +74 -2
- package/lib/peer/allowlist.ts +1 -1
- package/lib/peer/client.ts +26 -1
- package/lib/peer/link-cli.ts +19 -4
- package/lib/peer/links.ts +117 -5
- package/lib/peer/machines.ts +14 -2
- package/lib/peer/name.ts +32 -0
- package/lib/peer/pairing.ts +34 -5
- package/lib/peer/pin.ts +10 -3
- package/lib/peer/reasons.ts +7 -0
- package/lib/peer/revoke.ts +75 -0
- package/lib/peer/rpc-server.ts +53 -5
- package/lib/peer/unlink.ts +62 -0
- package/lib/peer/unlinked-proof.ts +44 -0
- package/lib/projects.ts +6 -2
- package/lib/security/demo.ts +7 -0
- package/lib/session-artifacts/agentos-files.ts +94 -0
- package/lib/session-artifacts/collect.ts +17 -0
- package/lib/session-groups/assignSessions.ts +81 -0
- package/lib/session-groups/cli.ts +61 -0
- package/lib/session-groups/createGroup.ts +28 -0
- package/lib/session-groups/index.ts +21 -0
- package/lib/session-groups/removeGroup.ts +36 -0
- package/lib/session-groups/reorderGroups.ts +31 -0
- package/lib/session-groups/resolveGroup.ts +19 -0
- package/lib/session-groups/route.ts +32 -0
- package/lib/session-groups/store.ts +85 -0
- package/lib/session-groups/types.ts +25 -0
- package/lib/session-groups/updateGroup.ts +35 -0
- package/lib/session-groups/workspace.ts +21 -0
- package/lib/sessions/crew.ts +72 -0
- package/lib/sessions/launch.ts +54 -11
- package/lib/settings-url.ts +1 -0
- package/lib/share/markdown.ts +2 -2
- package/lib/share/redact.ts +16 -10
- package/lib/share/render.tsx +1 -1
- package/lib/share/snapshot.ts +2 -2
- package/lib/sidebar/rollup.ts +1 -0
- package/lib/sidebar/row-menu.ts +8 -1
- package/lib/sidebar/shelves.ts +90 -15
- package/lib/sidebar/visible-order.ts +5 -0
- package/lib/tasks/hold-checks.ts +3 -1
- package/lib/tasks/holds.ts +3 -0
- package/lib/tasks/index.ts +11 -0
- package/lib/tasks/queue-rows.ts +8 -1
- package/lib/tasks/queue-start.ts +1 -0
- package/lib/tasks/queue-types.ts +9 -1
- package/lib/tasks/queue.ts +15 -5
- package/lib/tasks/schedule-types.ts +1 -0
- package/lib/test-temp.ts +19 -3
- package/lib/workspaces.ts +11 -3
- package/lib/worktree-deps/caches.ts +3 -2
- package/lib/worktree-deps/clone.ts +3 -1
- package/lib/worktree-deps/paths.ts +26 -0
- package/lib/worktree-sweep/testTemp.ts +23 -106
- package/package.json +1 -1
- package/scripts/agent-os +1 -1
- package/scripts/autodeploy +2 -0
- package/scripts/autodeploy.test.ts +31 -1
- package/scripts/claim-hook.sh +31 -0
- package/scripts/claim-hook.test.ts +230 -0
- package/scripts/lib/cli-links.sh +36 -0
- package/scripts/lib/commands.sh +3 -0
- package/scripts/lib/machine.sh +27 -2
- package/scripts/lib/machine.test.ts +25 -3
- package/scripts/sync-cli-links +7 -0
- package/scripts/sync-cli-links.test.ts +69 -0
- package/server.ts +12 -1
- package/stores/claimsUi.ts +22 -0
- package/stores/exportUi.ts +87 -0
- package/app/api/groups/[...path]/route.ts +0 -136
- package/app/api/groups/route.ts +0 -93
- package/data/groups/index.ts +0 -1
- package/data/groups/mutations.ts +0 -95
- package/hooks/useGroups.ts +0 -37
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { getDb } from "@/lib/db";
|
|
2
|
+
import { listGroups } from "./store";
|
|
3
|
+
import { GroupError, type SessionGroup } from "./types";
|
|
4
|
+
|
|
5
|
+
// Sets a workspace's group order. `ids` may name only some of them (a move
|
|
6
|
+
// up or down sends the whole list; a stale list from another device leaves
|
|
7
|
+
// newer groups at the end), but never one from another workspace.
|
|
8
|
+
export function reorderGroups(
|
|
9
|
+
workspaceId: string | null,
|
|
10
|
+
ids: unknown
|
|
11
|
+
): SessionGroup[] {
|
|
12
|
+
if (!Array.isArray(ids) || ids.some((i) => typeof i !== "string"))
|
|
13
|
+
throw new GroupError("invalid", "ids is a list of group ids");
|
|
14
|
+
const current = listGroups(workspaceId);
|
|
15
|
+
const known = new Set(current.map((g) => g.id));
|
|
16
|
+
const wanted = [...new Set(ids as string[])];
|
|
17
|
+
const stranger = wanted.find((i) => !known.has(i));
|
|
18
|
+
if (stranger)
|
|
19
|
+
throw new GroupError("workspace", "A group is not in this workspace");
|
|
20
|
+
const order = [
|
|
21
|
+
...wanted,
|
|
22
|
+
...current.map((g) => g.id).filter((i) => !wanted.includes(i)),
|
|
23
|
+
];
|
|
24
|
+
const db = getDb();
|
|
25
|
+
const set = db.prepare(
|
|
26
|
+
`UPDATE session_groups SET sort_order = ?, updated_at = datetime('now')
|
|
27
|
+
WHERE id = ? AND sort_order != ?`
|
|
28
|
+
);
|
|
29
|
+
db.transaction(() => order.forEach((id, i) => set.run(i, id, i)))();
|
|
30
|
+
return listGroups(workspaceId);
|
|
31
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { groupByName, listGroups } from "./store";
|
|
2
|
+
import { GroupError, type SessionGroup } from "./types";
|
|
3
|
+
|
|
4
|
+
// A group named by a caller (`aos --group`, an orchestrator tool): it must
|
|
5
|
+
// exist in that workspace, and the error lists the ones that do.
|
|
6
|
+
export function resolveGroup(
|
|
7
|
+
workspaceId: string | null,
|
|
8
|
+
name: string
|
|
9
|
+
): SessionGroup {
|
|
10
|
+
const found = groupByName(workspaceId, name);
|
|
11
|
+
if (found) return found;
|
|
12
|
+
const names = listGroups(workspaceId).map((g) => `"${g.name}"`);
|
|
13
|
+
throw new GroupError(
|
|
14
|
+
"not-found",
|
|
15
|
+
names.length
|
|
16
|
+
? `No group "${name.trim()}" in this workspace. Groups: ${names.join(", ")}`
|
|
17
|
+
: `No group "${name.trim()}": this workspace has no groups`
|
|
18
|
+
);
|
|
19
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import type { z } from "zod";
|
|
3
|
+
import { GROUP_STATUS, GroupError } from "./types";
|
|
4
|
+
|
|
5
|
+
// Runs a group action for a route: a body that fails its schema is a 400, a
|
|
6
|
+
// GroupError becomes its status code.
|
|
7
|
+
export async function groupRoute<S extends z.ZodType>(
|
|
8
|
+
request: Request,
|
|
9
|
+
schema: S,
|
|
10
|
+
act: (body: z.infer<S>) => unknown
|
|
11
|
+
): Promise<NextResponse> {
|
|
12
|
+
const parsed = schema.safeParse(await request.json().catch(() => null));
|
|
13
|
+
if (!parsed.success)
|
|
14
|
+
return NextResponse.json(
|
|
15
|
+
{ error: parsed.error.issues[0]?.message ?? "Bad request" },
|
|
16
|
+
{ status: 400 }
|
|
17
|
+
);
|
|
18
|
+
return run(() => act(parsed.data));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function run(act: () => unknown): NextResponse {
|
|
22
|
+
try {
|
|
23
|
+
return NextResponse.json(act());
|
|
24
|
+
} catch (err) {
|
|
25
|
+
if (err instanceof GroupError)
|
|
26
|
+
return NextResponse.json(
|
|
27
|
+
{ error: err.message, code: err.code },
|
|
28
|
+
{ status: GROUP_STATUS[err.code] }
|
|
29
|
+
);
|
|
30
|
+
throw err;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { getDb } from "@/lib/db";
|
|
2
|
+
import { GroupError, NAME_MAX, type SessionGroup } from "./types";
|
|
3
|
+
|
|
4
|
+
interface GroupRow extends Omit<SessionGroup, "collapsed"> {
|
|
5
|
+
collapsed: number;
|
|
6
|
+
deleted_at: string | null;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const COLUMNS = `id, workspace_id, name, sort_order, collapsed, created_at,
|
|
10
|
+
updated_at, deleted_at`;
|
|
11
|
+
|
|
12
|
+
const toGroup = ({ deleted_at: _, ...row }: GroupRow): SessionGroup => ({
|
|
13
|
+
...row,
|
|
14
|
+
collapsed: !!row.collapsed,
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
export function cleanName(name: unknown): string {
|
|
18
|
+
const trimmed =
|
|
19
|
+
typeof name === "string" ? name.trim().replace(/\s+/g, " ") : "";
|
|
20
|
+
if (!trimmed) throw new GroupError("invalid", "A group needs a name");
|
|
21
|
+
if (trimmed.length > NAME_MAX)
|
|
22
|
+
throw new GroupError(
|
|
23
|
+
"invalid",
|
|
24
|
+
`A group name is at most ${NAME_MAX} characters`
|
|
25
|
+
);
|
|
26
|
+
return trimmed;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Live groups (not deleted), in the order to show them within a workspace.
|
|
30
|
+
export function listGroups(workspaceId?: string | null): SessionGroup[] {
|
|
31
|
+
const rows = (
|
|
32
|
+
workspaceId === undefined
|
|
33
|
+
? getDb()
|
|
34
|
+
.prepare(
|
|
35
|
+
`SELECT ${COLUMNS} FROM session_groups WHERE deleted_at IS NULL
|
|
36
|
+
ORDER BY sort_order, created_at`
|
|
37
|
+
)
|
|
38
|
+
.all()
|
|
39
|
+
: getDb()
|
|
40
|
+
.prepare(
|
|
41
|
+
`SELECT ${COLUMNS} FROM session_groups WHERE deleted_at IS NULL
|
|
42
|
+
AND COALESCE(workspace_id, '') = COALESCE(?, '')
|
|
43
|
+
ORDER BY sort_order, created_at`
|
|
44
|
+
)
|
|
45
|
+
.all(workspaceId)
|
|
46
|
+
) as GroupRow[];
|
|
47
|
+
return rows.map(toGroup);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Live groups' names by id, for labelling.
|
|
51
|
+
export function groupNames(): Map<string, string> {
|
|
52
|
+
return new Map(listGroups().map((g) => [g.id, g.name]));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Any group by id, deleted or not, with whether it is deleted.
|
|
56
|
+
export function findGroup(
|
|
57
|
+
id: string
|
|
58
|
+
): (SessionGroup & { deleted: boolean }) | null {
|
|
59
|
+
const row = getDb()
|
|
60
|
+
.prepare(`SELECT ${COLUMNS} FROM session_groups WHERE id = ?`)
|
|
61
|
+
.get(id) as GroupRow | undefined;
|
|
62
|
+
return row ? { ...toGroup(row), deleted: !!row.deleted_at } : null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// A live group by id, or the error a caller should surface.
|
|
66
|
+
export function liveGroup(id: unknown): SessionGroup {
|
|
67
|
+
const group = typeof id === "string" ? findGroup(id) : null;
|
|
68
|
+
if (!group) throw new GroupError("not-found", "Group not found");
|
|
69
|
+
if (group.deleted) throw new GroupError("deleted", "That group was deleted");
|
|
70
|
+
return group;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function groupByName(
|
|
74
|
+
workspaceId: string | null,
|
|
75
|
+
name: string
|
|
76
|
+
): SessionGroup | null {
|
|
77
|
+
const row = getDb()
|
|
78
|
+
.prepare(
|
|
79
|
+
`SELECT ${COLUMNS} FROM session_groups
|
|
80
|
+
WHERE deleted_at IS NULL AND lower(name) = lower(?)
|
|
81
|
+
AND COALESCE(workspace_id, '') = COALESCE(?, '')`
|
|
82
|
+
)
|
|
83
|
+
.get(name.trim(), workspaceId) as GroupRow | undefined;
|
|
84
|
+
return row ? toGroup(row) : null;
|
|
85
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export type { SessionGroup } from "@/lib/sidebar/shelves";
|
|
2
|
+
|
|
3
|
+
export type GroupErrorCode =
|
|
4
|
+
"not-found" | "duplicate" | "workspace" | "invalid" | "deleted";
|
|
5
|
+
|
|
6
|
+
// Thrown for anything the caller got wrong; routes map the code to a status.
|
|
7
|
+
export class GroupError extends Error {
|
|
8
|
+
constructor(
|
|
9
|
+
readonly code: GroupErrorCode,
|
|
10
|
+
message: string
|
|
11
|
+
) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.name = "GroupError";
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const GROUP_STATUS: Record<GroupErrorCode, number> = {
|
|
18
|
+
"not-found": 404,
|
|
19
|
+
duplicate: 409,
|
|
20
|
+
workspace: 409,
|
|
21
|
+
invalid: 400,
|
|
22
|
+
deleted: 409,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const NAME_MAX = 60;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { getDb } from "@/lib/db";
|
|
2
|
+
import { cleanName, groupByName, liveGroup } from "./store";
|
|
3
|
+
import { GroupError, type SessionGroup } from "./types";
|
|
4
|
+
|
|
5
|
+
// Rename and fold. Folding is stored here so every device agrees.
|
|
6
|
+
export function updateGroup(
|
|
7
|
+
id: string,
|
|
8
|
+
patch: { name?: unknown; collapsed?: unknown }
|
|
9
|
+
): SessionGroup {
|
|
10
|
+
const group = liveGroup(id);
|
|
11
|
+
const sets: string[] = [];
|
|
12
|
+
const args: (string | number)[] = [];
|
|
13
|
+
if (patch.name !== undefined) {
|
|
14
|
+
const clean = cleanName(patch.name);
|
|
15
|
+
const clash = groupByName(group.workspace_id, clean);
|
|
16
|
+
if (clash && clash.id !== id)
|
|
17
|
+
throw new GroupError("duplicate", `There is already a group "${clean}"`);
|
|
18
|
+
sets.push("name = ?");
|
|
19
|
+
args.push(clean);
|
|
20
|
+
}
|
|
21
|
+
if (patch.collapsed !== undefined) {
|
|
22
|
+
if (typeof patch.collapsed !== "boolean")
|
|
23
|
+
throw new GroupError("invalid", "collapsed must be a boolean");
|
|
24
|
+
sets.push("collapsed = ?");
|
|
25
|
+
args.push(patch.collapsed ? 1 : 0);
|
|
26
|
+
}
|
|
27
|
+
if (sets.length)
|
|
28
|
+
getDb()
|
|
29
|
+
.prepare(
|
|
30
|
+
`UPDATE session_groups SET ${sets.join(", ")},
|
|
31
|
+
updated_at = datetime('now') WHERE id = ?`
|
|
32
|
+
)
|
|
33
|
+
.run(...args, id);
|
|
34
|
+
return liveGroup(id);
|
|
35
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { getDb } from "@/lib/db";
|
|
2
|
+
|
|
3
|
+
// The workspace a session belongs to: its project's, else the one kept on the
|
|
4
|
+
// session (an orchestrator, or a scratch session in no project). Same rule as
|
|
5
|
+
// inWorkspace in lib/sidebar/shelves, read from the database.
|
|
6
|
+
export function sessionWorkspaceId(
|
|
7
|
+
sessionId: string
|
|
8
|
+
): string | null | undefined {
|
|
9
|
+
const row = getDb()
|
|
10
|
+
.prepare(
|
|
11
|
+
`SELECT s.role AS role, s.workspace_id AS own, p.workspace_id AS byProject
|
|
12
|
+
FROM sessions s LEFT JOIN projects p ON p.id = s.project_id
|
|
13
|
+
WHERE s.id = ?`
|
|
14
|
+
)
|
|
15
|
+
.get(sessionId) as
|
|
16
|
+
| { role: string | null; own: string | null; byProject: string | null }
|
|
17
|
+
| undefined;
|
|
18
|
+
if (!row) return undefined;
|
|
19
|
+
if (row.role === "orchestrator") return row.own;
|
|
20
|
+
return row.byProject ?? row.own;
|
|
21
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A crew: the sessions started in roles that work together. Its id is the
|
|
3
|
+
* session that began it, so a crew needs no table of its own.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { db } from "../db";
|
|
7
|
+
import type { AgentRole } from "../models/agent-roles";
|
|
8
|
+
|
|
9
|
+
const crewOf = (id: string | null | undefined): string | null =>
|
|
10
|
+
id
|
|
11
|
+
? ((
|
|
12
|
+
db.prepare(`SELECT crew_id FROM session_rows WHERE id = ?`).get(id) as
|
|
13
|
+
{ crew_id: string | null } | undefined
|
|
14
|
+
)?.crew_id ?? null)
|
|
15
|
+
: null;
|
|
16
|
+
|
|
17
|
+
// Puts a session in a role and a crew: the one named, else its starter's (or
|
|
18
|
+
// its calling session's), else a new one it begins.
|
|
19
|
+
export function joinCrew(
|
|
20
|
+
id: string,
|
|
21
|
+
role: AgentRole,
|
|
22
|
+
from: {
|
|
23
|
+
crewId?: string | null;
|
|
24
|
+
startedBy?: string | null;
|
|
25
|
+
parent?: string | null;
|
|
26
|
+
}
|
|
27
|
+
): string {
|
|
28
|
+
const crew =
|
|
29
|
+
from.crewId || crewOf(from.startedBy) || crewOf(from.parent) || id;
|
|
30
|
+
db.prepare(
|
|
31
|
+
`UPDATE sessions SET agent_role = ?, crew_id = ? WHERE id = ?`
|
|
32
|
+
).run(role, crew, id);
|
|
33
|
+
return crew;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface CrewMember {
|
|
37
|
+
id: string;
|
|
38
|
+
name: string;
|
|
39
|
+
agentRole: string | null;
|
|
40
|
+
model: string | null;
|
|
41
|
+
modelProvider: string | null;
|
|
42
|
+
agentType: string;
|
|
43
|
+
taskStatus: string | null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function crewMembers(crewId: string): CrewMember[] {
|
|
47
|
+
return (
|
|
48
|
+
db
|
|
49
|
+
.prepare(
|
|
50
|
+
`SELECT id, name, agent_role, model, model_provider, agent_type, task_status
|
|
51
|
+
FROM session_rows WHERE crew_id = ? AND archived_at IS NULL
|
|
52
|
+
ORDER BY created_at`
|
|
53
|
+
)
|
|
54
|
+
.all(crewId) as {
|
|
55
|
+
id: string;
|
|
56
|
+
name: string;
|
|
57
|
+
agent_role: string | null;
|
|
58
|
+
model: string | null;
|
|
59
|
+
model_provider: string | null;
|
|
60
|
+
agent_type: string;
|
|
61
|
+
task_status: string | null;
|
|
62
|
+
}[]
|
|
63
|
+
).map((r) => ({
|
|
64
|
+
id: r.id,
|
|
65
|
+
name: r.name,
|
|
66
|
+
agentRole: r.agent_role,
|
|
67
|
+
model: r.model,
|
|
68
|
+
modelProvider: r.model_provider,
|
|
69
|
+
agentType: r.agent_type,
|
|
70
|
+
taskStatus: r.task_status,
|
|
71
|
+
}));
|
|
72
|
+
}
|
package/lib/sessions/launch.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* then (for a worktree) the setup in the background, then the first message.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import { fileInGroup } from "../session-groups/assignSessions";
|
|
6
7
|
import { agentosHome } from "../agentos-home";
|
|
7
8
|
import type { StartVia } from "../tasks/starter";
|
|
8
9
|
import fs from "fs";
|
|
@@ -15,6 +16,9 @@ import { discoversModels, resolveModelForAgent } from "../model-catalog";
|
|
|
15
16
|
import { isDiscoveredModel } from "../chat/discovered-models";
|
|
16
17
|
import { parseOverride, resolveRole, setSessionModel } from "../models/resolve";
|
|
17
18
|
import { ANTHROPIC } from "../models/roles";
|
|
19
|
+
import { joinCrew } from "./crew";
|
|
20
|
+
import { prepareRoleStart } from "../models/agent-binding";
|
|
21
|
+
import { isAgentRole, roleBrief, type AgentRole } from "../models/agent-roles";
|
|
18
22
|
import { getProject } from "../projects";
|
|
19
23
|
import { getWorkspace } from "../workspaces";
|
|
20
24
|
import { getHost, isRemoteHost } from "../hosts";
|
|
@@ -82,8 +86,16 @@ export interface LaunchInput {
|
|
|
82
86
|
// The workspace selected when it was started. A session in a project
|
|
83
87
|
// follows the project; one in none keeps this (see inWorkspace).
|
|
84
88
|
workspaceId?: string | null;
|
|
85
|
-
groupPath?: string;
|
|
86
89
|
systemPrompt?: string | null;
|
|
90
|
+
// Start it in a role: the model comes from the role's binding (or
|
|
91
|
+
// `model`), and its brief joins the instructions.
|
|
92
|
+
agentRole?: AgentRole | null;
|
|
93
|
+
// The crew it joins: the id of the session that began it.
|
|
94
|
+
crewId?: string | null;
|
|
95
|
+
// When the role's model is down, start on the role's fallback instead.
|
|
96
|
+
useFallback?: boolean;
|
|
97
|
+
// The user's group it starts in (lib/session-groups).
|
|
98
|
+
groupId?: string;
|
|
87
99
|
}
|
|
88
100
|
|
|
89
101
|
export interface Launched {
|
|
@@ -259,7 +271,17 @@ async function launch(input: LaunchInput, id: string): Promise<Launched> {
|
|
|
259
271
|
if (scratch && projectPath === SCRATCH_DIR)
|
|
260
272
|
await fs.promises.mkdir(SCRATCH_DIR, { recursive: true });
|
|
261
273
|
|
|
262
|
-
|
|
274
|
+
if (input.agentRole && (remote || !isAgentRole(input.agentRole)))
|
|
275
|
+
throw new Error("A role session runs on this machine");
|
|
276
|
+
const roleStart = input.agentRole
|
|
277
|
+
? await prepareRoleStart(input.agentRole, {
|
|
278
|
+
workspaceId: project?.workspace_id ?? home,
|
|
279
|
+
projectId: project && !project.is_uncategorized ? project.id : null,
|
|
280
|
+
override: input.model?.trim() || null,
|
|
281
|
+
useFallback: input.useFallback,
|
|
282
|
+
})
|
|
283
|
+
: null;
|
|
284
|
+
const agentType: AgentType = roleStart?.agentType ?? input.agentType;
|
|
263
285
|
const view =
|
|
264
286
|
input.view === "terminal" || !supportsChat(agentType) || remote
|
|
265
287
|
? "terminal"
|
|
@@ -271,20 +293,30 @@ async function launch(input: LaunchInput, id: string): Promise<Launched> {
|
|
|
271
293
|
const picked = input.startedBy ? parseOverride(input.model) : null;
|
|
272
294
|
if (picked && typeof picked !== "string")
|
|
273
295
|
throw new Error(`model: a session takes one "provider/model"`);
|
|
274
|
-
const routed =
|
|
275
|
-
agentType === "claude"
|
|
296
|
+
const routed = roleStart
|
|
297
|
+
? roleStart.agentType === "claude"
|
|
298
|
+
? roleStart.routed
|
|
299
|
+
: null
|
|
300
|
+
: agentType === "claude" && (picked || !input.model?.trim()) && !remote
|
|
276
301
|
? resolveRole(role, {
|
|
277
302
|
workspaceId: project?.workspace_id ?? home,
|
|
278
303
|
override: picked,
|
|
279
304
|
})
|
|
280
305
|
: null;
|
|
281
|
-
if (
|
|
306
|
+
if (
|
|
307
|
+
(picked || roleStart) &&
|
|
308
|
+
routed &&
|
|
309
|
+
routed.provider.id !== ANTHROPIC &&
|
|
310
|
+
view !== "chat"
|
|
311
|
+
)
|
|
282
312
|
throw new Error("Another provider's models run only as a chat");
|
|
283
313
|
const interactive =
|
|
284
314
|
routed && (routed.provider.id === ANTHROPIC || view === "chat")
|
|
285
315
|
? routed
|
|
286
316
|
: null;
|
|
287
|
-
const wanted =
|
|
317
|
+
const wanted = roleStart
|
|
318
|
+
? roleStart.model
|
|
319
|
+
: input.model?.trim() || project?.default_model;
|
|
288
320
|
// An agent without a static list runs what it reports for this folder.
|
|
289
321
|
const discovered =
|
|
290
322
|
!interactive &&
|
|
@@ -293,9 +325,11 @@ async function launch(input: LaunchInput, id: string): Promise<Launched> {
|
|
|
293
325
|
(await isDiscoveredModel(agentType, projectPath, wanted));
|
|
294
326
|
const model = interactive
|
|
295
327
|
? interactive.model
|
|
296
|
-
:
|
|
297
|
-
?
|
|
298
|
-
:
|
|
328
|
+
: roleStart
|
|
329
|
+
? roleStart.model
|
|
330
|
+
: discovered && wanted
|
|
331
|
+
? wanted
|
|
332
|
+
: resolveModelForAgent(agentType, wanted);
|
|
299
333
|
const text = input.prompt?.trim() ?? "";
|
|
300
334
|
const naming: Naming = text
|
|
301
335
|
? await nameFor(text, projectPath, input.name)
|
|
@@ -316,8 +350,10 @@ async function launch(input: LaunchInput, id: string): Promise<Launched> {
|
|
|
316
350
|
cwd,
|
|
317
351
|
input.parentSessionId ?? null,
|
|
318
352
|
model,
|
|
319
|
-
input.systemPrompt
|
|
320
|
-
|
|
353
|
+
[input.systemPrompt, roleBrief(input.agentRole)]
|
|
354
|
+
.filter(Boolean)
|
|
355
|
+
.join("\n\n") || null,
|
|
356
|
+
"sessions",
|
|
321
357
|
agentType,
|
|
322
358
|
input.autoApprove || input.access === "full" ? 1 : 0,
|
|
323
359
|
project?.id ?? "uncategorized",
|
|
@@ -335,6 +371,13 @@ async function launch(input: LaunchInput, id: string): Promise<Launched> {
|
|
|
335
371
|
id
|
|
336
372
|
);
|
|
337
373
|
setHome(id, home);
|
|
374
|
+
if (input.agentRole)
|
|
375
|
+
joinCrew(id, input.agentRole, {
|
|
376
|
+
crewId: input.crewId,
|
|
377
|
+
startedBy: input.startedBy,
|
|
378
|
+
parent: input.parentSessionId,
|
|
379
|
+
});
|
|
380
|
+
if (input.groupId) fileInGroup(id, input.groupId);
|
|
338
381
|
if (interactive) setSessionModel(id, interactive);
|
|
339
382
|
if (input.access)
|
|
340
383
|
db.prepare(`UPDATE sessions SET chat_access = ? WHERE id = ?`).run(
|
package/lib/settings-url.ts
CHANGED
package/lib/share/markdown.ts
CHANGED
|
@@ -83,13 +83,13 @@ export function htmlToMarkdown(html: string, demote = 0): string {
|
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
/** A fenced block whose fence is longer than any backtick run inside. */
|
|
86
|
-
function fenced(text: string): string {
|
|
86
|
+
export function fenced(text: string, lang = ""): string {
|
|
87
87
|
const longest = Math.max(
|
|
88
88
|
2,
|
|
89
89
|
...(text.match(/`+/g) ?? []).map((m) => m.length)
|
|
90
90
|
);
|
|
91
91
|
const fence = "`".repeat(longest + 1);
|
|
92
|
-
return `${fence}\n${text}\n${fence}`;
|
|
92
|
+
return `${fence}${lang}\n${text}\n${fence}`;
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
function itemToMarkdown(item: SnapshotItem): string {
|
package/lib/share/redact.ts
CHANGED
|
@@ -97,10 +97,12 @@ function sub(
|
|
|
97
97
|
|
|
98
98
|
export function redactForShare(
|
|
99
99
|
input: string,
|
|
100
|
-
ctx: ShareContext = NO_CONTEXT
|
|
100
|
+
ctx: ShareContext = NO_CONTEXT,
|
|
101
|
+
opts: { only?: Kind[] } = {}
|
|
101
102
|
): { text: string; hits: Hits } {
|
|
102
103
|
const hits = noHits();
|
|
103
104
|
let t = input;
|
|
105
|
+
const on = (k: Kind) => !opts.only || opts.only.includes(k);
|
|
104
106
|
|
|
105
107
|
// 1. Credentials by shape.
|
|
106
108
|
t = sub(t, PRIVATE_KEY_BLOCK, SECRET, hits, "secret");
|
|
@@ -143,7 +145,7 @@ export function redactForShare(
|
|
|
143
145
|
}
|
|
144
146
|
|
|
145
147
|
// 3. Home paths, and the account name before an @.
|
|
146
|
-
if (ctx.home.length > 1)
|
|
148
|
+
if (on("path") && ctx.home.length > 1)
|
|
147
149
|
t = sub(
|
|
148
150
|
t,
|
|
149
151
|
new RegExp(escape(ctx.home) + "(?![\\w.-])", "g"),
|
|
@@ -151,8 +153,8 @@ export function redactForShare(
|
|
|
151
153
|
hits,
|
|
152
154
|
"path"
|
|
153
155
|
);
|
|
154
|
-
t = sub(t, new RegExp(HOME.source, "g"), "~", hits, "path");
|
|
155
|
-
if (ctx.user.length > 2)
|
|
156
|
+
if (on("path")) t = sub(t, new RegExp(HOME.source, "g"), "~", hits, "path");
|
|
157
|
+
if (on("path") && ctx.user.length > 2)
|
|
156
158
|
t = sub(
|
|
157
159
|
t,
|
|
158
160
|
new RegExp(`\\b${escape(ctx.user)}(?=@)`, "g"),
|
|
@@ -162,14 +164,16 @@ export function redactForShare(
|
|
|
162
164
|
);
|
|
163
165
|
|
|
164
166
|
// 4. Addresses.
|
|
165
|
-
|
|
166
|
-
|
|
167
|
+
if (on("ip")) {
|
|
168
|
+
t = sub(t, IPV4, "[ip]", hits, "ip", (m) => KEEP_IPV4.has(m));
|
|
169
|
+
t = sub(t, IPV6, "[ip]", hits, "ip", (m) => KEEP_IPV6.has(m));
|
|
170
|
+
}
|
|
167
171
|
|
|
168
172
|
// 5. This machine's names, and private-network suffixes.
|
|
169
173
|
const names = [...new Set(ctx.hosts.filter((h) => h.length > 2))].sort(
|
|
170
174
|
(a, b) => b.length - a.length
|
|
171
175
|
);
|
|
172
|
-
if (names.length)
|
|
176
|
+
if (on("host") && names.length)
|
|
173
177
|
t = sub(
|
|
174
178
|
t,
|
|
175
179
|
new RegExp(
|
|
@@ -180,11 +184,13 @@ export function redactForShare(
|
|
|
180
184
|
hits,
|
|
181
185
|
"host"
|
|
182
186
|
);
|
|
183
|
-
|
|
184
|
-
|
|
187
|
+
if (on("host")) {
|
|
188
|
+
t = sub(t, CONNECT_HOST, "[host]", hits, "host");
|
|
189
|
+
t = sub(t, LOCAL_HOST, "[host]", hits, "host");
|
|
190
|
+
}
|
|
185
191
|
|
|
186
192
|
// 6. Email addresses.
|
|
187
|
-
t = sub(t, EMAIL, "[email]", hits, "email");
|
|
193
|
+
if (on("email")) t = sub(t, EMAIL, "[email]", hits, "email");
|
|
188
194
|
|
|
189
195
|
return { text: t, hits };
|
|
190
196
|
}
|
package/lib/share/render.tsx
CHANGED
|
@@ -60,7 +60,7 @@ const components: Components = {
|
|
|
60
60
|
img: ({ alt }) => <span>[image{alt ? `: ${alt}` : ""}]</span>,
|
|
61
61
|
};
|
|
62
62
|
|
|
63
|
-
function Markdown({ text }: { text: string }) {
|
|
63
|
+
export function Markdown({ text }: { text: string }) {
|
|
64
64
|
return (
|
|
65
65
|
<ReactMarkdown remarkPlugins={[remarkGfm]} components={components}>
|
|
66
66
|
{text}
|
package/lib/share/snapshot.ts
CHANGED
|
@@ -44,7 +44,7 @@ export interface Snapshot {
|
|
|
44
44
|
|
|
45
45
|
export const MAX_TOOL_INPUT = 4_000;
|
|
46
46
|
export const MAX_TOOL_OUTPUT = 20_000;
|
|
47
|
-
const HIDDEN_FILE = "[contents of a secret file hidden]";
|
|
47
|
+
export const HIDDEN_FILE = "[contents of a secret file hidden]";
|
|
48
48
|
const NO_IMAGE = "[image not shared]";
|
|
49
49
|
|
|
50
50
|
const clip = (s: string, max: number) =>
|
|
@@ -55,7 +55,7 @@ const clip = (s: string, max: number) =>
|
|
|
55
55
|
const excerptOf = (s: string) => s.replace(/\s+/g, " ").trim().slice(0, 120);
|
|
56
56
|
|
|
57
57
|
// Every string in a tool's input, to see whether it names a secret file.
|
|
58
|
-
function strings(v: unknown, out: string[] = []): string[] {
|
|
58
|
+
export function strings(v: unknown, out: string[] = []): string[] {
|
|
59
59
|
if (typeof v === "string") out.push(v);
|
|
60
60
|
else if (Array.isArray(v)) v.forEach((x) => strings(x, out));
|
|
61
61
|
else if (v && typeof v === "object")
|
package/lib/sidebar/rollup.ts
CHANGED
package/lib/sidebar/row-menu.ts
CHANGED
|
@@ -16,11 +16,13 @@ export type RowMenuItem =
|
|
|
16
16
|
| "open-recipe"
|
|
17
17
|
| "copy-link"
|
|
18
18
|
| "share"
|
|
19
|
+
| "export"
|
|
19
20
|
| "fork"
|
|
20
21
|
| "fresh-start"
|
|
21
22
|
| "new-conversation"
|
|
22
23
|
| "move-machine"
|
|
23
24
|
| "move-project"
|
|
25
|
+
| "move-group"
|
|
24
26
|
| "check-ins"
|
|
25
27
|
| "save-recipe"
|
|
26
28
|
| "run-again"
|
|
@@ -120,7 +122,11 @@ export function rowMenu(input: RowMenuInput): RowMenuPlan {
|
|
|
120
122
|
input.hasCard && "card",
|
|
121
123
|
input.recipeRun && "open-recipe",
|
|
122
124
|
]),
|
|
123
|
-
...group("share", [
|
|
125
|
+
...group("share", [
|
|
126
|
+
"copy-link",
|
|
127
|
+
s.view === "chat" && "share",
|
|
128
|
+
s.view === "chat" && "export",
|
|
129
|
+
]),
|
|
124
130
|
...group("restart", [
|
|
125
131
|
s.agent_type === "claude" && agentRow && live && "fork",
|
|
126
132
|
agentRow && live && "fresh-start",
|
|
@@ -133,6 +139,7 @@ export function rowMenu(input: RowMenuInput): RowMenuPlan {
|
|
|
133
139
|
...group("move", [
|
|
134
140
|
input.machineTargets > 0 && "move-machine",
|
|
135
141
|
agentRow && input.otherProjects > 0 && live && "move-project",
|
|
142
|
+
agentRow && "move-group",
|
|
136
143
|
]),
|
|
137
144
|
...group("automate", [
|
|
138
145
|
input.hasWorkspace &&
|