@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,470 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The export model: a chat's items as turns of structured, already-redacted
|
|
3
|
+
* items, each cut labelled where it happens. Pure; the renderers
|
|
4
|
+
* (markdown, json, html) only print it.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { ChatItem } from "../chat/events";
|
|
8
|
+
import { lineDiff } from "../chat/diff";
|
|
9
|
+
import { isSecretPath } from "../secret-paths";
|
|
10
|
+
import { HIDDEN_FILE, strings } from "../share/snapshot";
|
|
11
|
+
import { htmlToMarkdown } from "../share/markdown";
|
|
12
|
+
import {
|
|
13
|
+
addHits,
|
|
14
|
+
noHits,
|
|
15
|
+
redactForShare,
|
|
16
|
+
type ShareContext,
|
|
17
|
+
} from "../share/redact";
|
|
18
|
+
import { FULL, cutInput, cutOutput, cutText, type Trunc } from "./compact";
|
|
19
|
+
import { groupTurns } from "./turns";
|
|
20
|
+
import {
|
|
21
|
+
formatBytes,
|
|
22
|
+
type ExportMode,
|
|
23
|
+
type ExportRedact,
|
|
24
|
+
type Hits,
|
|
25
|
+
} from "./view";
|
|
26
|
+
|
|
27
|
+
export type Counts = Record<string, number>;
|
|
28
|
+
|
|
29
|
+
export interface ExportItem {
|
|
30
|
+
kind:
|
|
31
|
+
| "user"
|
|
32
|
+
| "assistant"
|
|
33
|
+
| "thinking"
|
|
34
|
+
| "tool"
|
|
35
|
+
| "command_output"
|
|
36
|
+
| "todos"
|
|
37
|
+
| "task"
|
|
38
|
+
| "plan"
|
|
39
|
+
| "error"
|
|
40
|
+
| "compacted"
|
|
41
|
+
| "artifact";
|
|
42
|
+
at: string;
|
|
43
|
+
text?: string;
|
|
44
|
+
/** User: who sent it, when not typed here. */
|
|
45
|
+
from?: string;
|
|
46
|
+
name?: string;
|
|
47
|
+
title?: string;
|
|
48
|
+
status?: string;
|
|
49
|
+
input?: string;
|
|
50
|
+
output?: string;
|
|
51
|
+
durationMs?: number;
|
|
52
|
+
diff?: { path: string; added: number; removed: number; patch?: string };
|
|
53
|
+
todos?: { text: string; status: string }[];
|
|
54
|
+
/** A short remark: "sent while the agent was working", an image count. */
|
|
55
|
+
note?: string;
|
|
56
|
+
attachments?: string[];
|
|
57
|
+
truncated?: Trunc[];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface ExportTurn {
|
|
61
|
+
/** 0 is whatever came before the first message. */
|
|
62
|
+
n: number;
|
|
63
|
+
startedAt: string;
|
|
64
|
+
endedAt: string;
|
|
65
|
+
durationMs?: number;
|
|
66
|
+
costUsd?: number;
|
|
67
|
+
interrupted?: boolean;
|
|
68
|
+
/** Why the turn stopped short, when it did. */
|
|
69
|
+
ended?: string;
|
|
70
|
+
/** The message that began it, to find the turn from a row in the chat. */
|
|
71
|
+
userId?: string;
|
|
72
|
+
excerpt: string;
|
|
73
|
+
items: ExportItem[];
|
|
74
|
+
hits: Hits;
|
|
75
|
+
omitted: Counts;
|
|
76
|
+
truncated: Counts;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface ExportMeta {
|
|
80
|
+
title: string;
|
|
81
|
+
agent: string;
|
|
82
|
+
model?: string;
|
|
83
|
+
project?: string;
|
|
84
|
+
created: string;
|
|
85
|
+
timezone: string;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface ExportDoc {
|
|
89
|
+
meta: ExportMeta;
|
|
90
|
+
turns: ExportTurn[];
|
|
91
|
+
/** Redactions in the title and project name. */
|
|
92
|
+
metaHits: Hits;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export interface BuildInput {
|
|
96
|
+
items: ChatItem[];
|
|
97
|
+
meta: {
|
|
98
|
+
name: string;
|
|
99
|
+
agent: string;
|
|
100
|
+
model?: string;
|
|
101
|
+
project?: string;
|
|
102
|
+
createdAt: number;
|
|
103
|
+
};
|
|
104
|
+
mode: ExportMode;
|
|
105
|
+
redact: ExportRedact;
|
|
106
|
+
ctx: ShareContext;
|
|
107
|
+
/** Artifact pages by id, read only for a full export. */
|
|
108
|
+
artifacts?: Record<string, string>;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const pad = (n: number) => String(n).padStart(2, "0");
|
|
112
|
+
|
|
113
|
+
/** ISO 8601 in the machine's own time zone, with its offset. */
|
|
114
|
+
export function isoLocal(ms: number): string {
|
|
115
|
+
const d = new Date(ms);
|
|
116
|
+
const off = -d.getTimezoneOffset();
|
|
117
|
+
const a = Math.abs(off);
|
|
118
|
+
return (
|
|
119
|
+
`${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}` +
|
|
120
|
+
`T${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}` +
|
|
121
|
+
`${off >= 0 ? "+" : "-"}${pad(Math.floor(a / 60))}:${pad(a % 60)}`
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const bump = (c: Counts, key: string, by = 1) => {
|
|
126
|
+
c[key] = (c[key] ?? 0) + by;
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const stringify = (v: unknown, pretty: boolean) => {
|
|
130
|
+
if (typeof v === "string") return v;
|
|
131
|
+
try {
|
|
132
|
+
return JSON.stringify(v, null, pretty ? 2 : undefined) ?? "";
|
|
133
|
+
} catch {
|
|
134
|
+
return "";
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
/** Changed lines with two of context, as a unified-style patch. */
|
|
139
|
+
function patchOf(before: string, after: string) {
|
|
140
|
+
const ops = lineDiff(before, after);
|
|
141
|
+
let added = 0;
|
|
142
|
+
let removed = 0;
|
|
143
|
+
const keep = new Array<boolean>(ops.length).fill(false);
|
|
144
|
+
ops.forEach((o, i) => {
|
|
145
|
+
if (o.op === " ") return;
|
|
146
|
+
if (o.op === "+") added++;
|
|
147
|
+
else removed++;
|
|
148
|
+
for (let k = Math.max(0, i - 2); k <= Math.min(ops.length - 1, i + 2); k++)
|
|
149
|
+
keep[k] = true;
|
|
150
|
+
});
|
|
151
|
+
const lines: string[] = [];
|
|
152
|
+
let gap = false;
|
|
153
|
+
ops.forEach((o, i) => {
|
|
154
|
+
if (!keep[i]) {
|
|
155
|
+
gap = lines.length > 0;
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
if (gap) lines.push("@@");
|
|
159
|
+
gap = false;
|
|
160
|
+
lines.push(`${o.op}${o.text}`);
|
|
161
|
+
});
|
|
162
|
+
return { added, removed, lines };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function buildDoc(inp: BuildInput): ExportDoc {
|
|
166
|
+
const { mode, ctx } = inp;
|
|
167
|
+
const only = inp.redact === "secrets" ? { only: ["secret" as const] } : {};
|
|
168
|
+
let metaHits = noHits();
|
|
169
|
+
|
|
170
|
+
const doc: ExportDoc = {
|
|
171
|
+
meta: {
|
|
172
|
+
title: "",
|
|
173
|
+
agent: inp.meta.agent,
|
|
174
|
+
model: inp.meta.model,
|
|
175
|
+
project: undefined,
|
|
176
|
+
created: isoLocal(inp.meta.createdAt),
|
|
177
|
+
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
178
|
+
},
|
|
179
|
+
turns: [],
|
|
180
|
+
metaHits,
|
|
181
|
+
};
|
|
182
|
+
const redactMeta = (s: string) => {
|
|
183
|
+
const r = redactForShare(s, ctx, only);
|
|
184
|
+
metaHits = addHits(metaHits, r.hits);
|
|
185
|
+
return r.text;
|
|
186
|
+
};
|
|
187
|
+
doc.meta.title = redactMeta(inp.meta.name).slice(0, 120) || "Chat";
|
|
188
|
+
if (inp.meta.project) doc.meta.project = redactMeta(inp.meta.project);
|
|
189
|
+
|
|
190
|
+
for (const turn of groupTurns(inp.items)) {
|
|
191
|
+
const hits = noHits();
|
|
192
|
+
const omitted: Counts = {};
|
|
193
|
+
const truncated: Counts = {};
|
|
194
|
+
const take = (s: string) => {
|
|
195
|
+
const r = redactForShare(s, ctx, only);
|
|
196
|
+
Object.assign(hits, addHits(hits, r.hits));
|
|
197
|
+
return r.text;
|
|
198
|
+
};
|
|
199
|
+
/** Redact, then cut: a secret cut in half would otherwise slip through. */
|
|
200
|
+
const prose = (s: string, what: string, field?: string) => {
|
|
201
|
+
const cut = cutText(take(s), mode, field);
|
|
202
|
+
if (cut.truncated) bump(truncated, what);
|
|
203
|
+
return cut;
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
const items: ExportItem[] = [];
|
|
207
|
+
let lastTodos = -1;
|
|
208
|
+
if (mode === "compact")
|
|
209
|
+
turn.items.forEach((i, k) => {
|
|
210
|
+
if (i.kind === "todos") lastTodos = k;
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
let durationMs: number | undefined;
|
|
214
|
+
let costUsd: number | undefined;
|
|
215
|
+
let interrupted: boolean | undefined;
|
|
216
|
+
let ended: string | undefined;
|
|
217
|
+
let excerpt = "Session start";
|
|
218
|
+
let userId: string | undefined;
|
|
219
|
+
|
|
220
|
+
turn.items.forEach((item, k) => {
|
|
221
|
+
const at = isoLocal(item.createdAt);
|
|
222
|
+
switch (item.kind) {
|
|
223
|
+
case "user": {
|
|
224
|
+
const cut = prose(item.text, "user message");
|
|
225
|
+
const attachments = [
|
|
226
|
+
...(item.files ?? []).map(
|
|
227
|
+
(f) => `${take(f.name)} · ${formatBytes(f.size)}`
|
|
228
|
+
),
|
|
229
|
+
...(item.images ?? []).map(() => "[image not exported]"),
|
|
230
|
+
];
|
|
231
|
+
if (turn.n > 0 && !item.midTurn) {
|
|
232
|
+
userId = item.id;
|
|
233
|
+
excerpt = redactForShare(item.text, ctx, only)
|
|
234
|
+
.text.replace(/\s+/g, " ")
|
|
235
|
+
.trim()
|
|
236
|
+
.slice(0, 80);
|
|
237
|
+
}
|
|
238
|
+
const from = item.origin?.label ?? item.from;
|
|
239
|
+
items.push({
|
|
240
|
+
kind: "user",
|
|
241
|
+
at,
|
|
242
|
+
text: cut.text,
|
|
243
|
+
from: from ? take(from) : undefined,
|
|
244
|
+
note: item.midTurn ? "sent while the agent was working" : undefined,
|
|
245
|
+
attachments: attachments.length ? attachments : undefined,
|
|
246
|
+
truncated: cut.truncated && [cut.truncated],
|
|
247
|
+
});
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
case "assistant": {
|
|
251
|
+
if (!item.text.trim()) return;
|
|
252
|
+
const cut = prose(item.text, "agent message");
|
|
253
|
+
items.push({
|
|
254
|
+
kind: "assistant",
|
|
255
|
+
at,
|
|
256
|
+
text: cut.text,
|
|
257
|
+
truncated: cut.truncated && [cut.truncated],
|
|
258
|
+
});
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
case "reasoning": {
|
|
262
|
+
if (!item.text.trim()) return;
|
|
263
|
+
if (mode === "compact") return void bump(omitted, "thinking block");
|
|
264
|
+
const cut = prose(item.text, "thinking block");
|
|
265
|
+
items.push({
|
|
266
|
+
kind: "thinking",
|
|
267
|
+
at,
|
|
268
|
+
text: cut.text,
|
|
269
|
+
truncated: cut.truncated && [cut.truncated],
|
|
270
|
+
});
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
case "tool": {
|
|
274
|
+
const secret = strings(item.input).some(isSecretPath);
|
|
275
|
+
const command =
|
|
276
|
+
item.name === "Bash" &&
|
|
277
|
+
typeof (item.input as { command?: unknown } | null)?.command ===
|
|
278
|
+
"string";
|
|
279
|
+
const raw = command
|
|
280
|
+
? (item.input as { command: string }).command
|
|
281
|
+
: stringify(item.input, mode === "full");
|
|
282
|
+
const input = cutInput(take(raw), mode, command);
|
|
283
|
+
const output = cutOutput(
|
|
284
|
+
secret ? HIDDEN_FILE : take(item.output ?? ""),
|
|
285
|
+
mode,
|
|
286
|
+
item.status === "error"
|
|
287
|
+
);
|
|
288
|
+
const truncs: Trunc[] = [];
|
|
289
|
+
if (input.truncated) {
|
|
290
|
+
truncs.push(input.truncated);
|
|
291
|
+
bump(truncated, "tool input");
|
|
292
|
+
}
|
|
293
|
+
if (output.truncated) {
|
|
294
|
+
truncs.push(output.truncated);
|
|
295
|
+
bump(truncated, "tool output");
|
|
296
|
+
}
|
|
297
|
+
let diff: ExportItem["diff"];
|
|
298
|
+
if (item.diff) {
|
|
299
|
+
const hidden = isSecretPath(item.diff.path);
|
|
300
|
+
const p = patchOf(item.diff.before, item.diff.after);
|
|
301
|
+
diff = {
|
|
302
|
+
path: take(item.diff.path),
|
|
303
|
+
added: p.added,
|
|
304
|
+
removed: p.removed,
|
|
305
|
+
};
|
|
306
|
+
if (mode === "full") {
|
|
307
|
+
const lines = hidden ? [HIDDEN_FILE] : p.lines;
|
|
308
|
+
const over = lines.length > FULL.patchLines;
|
|
309
|
+
const shown = over ? lines.slice(0, FULL.patchLines) : lines;
|
|
310
|
+
let patch = take(shown.join("\n"));
|
|
311
|
+
if (over) {
|
|
312
|
+
patch += `\n[truncated: showing first ${FULL.patchLines} of ${lines.length} diff lines; ${lines.length - FULL.patchLines} lines omitted (Full export)]`;
|
|
313
|
+
truncs.push({
|
|
314
|
+
field: "diff",
|
|
315
|
+
how: `head${FULL.patchLines}`,
|
|
316
|
+
totalLines: lines.length,
|
|
317
|
+
keptLines: FULL.patchLines,
|
|
318
|
+
totalChars: lines.join("\n").length,
|
|
319
|
+
keptChars: patch.length,
|
|
320
|
+
});
|
|
321
|
+
bump(truncated, "file change");
|
|
322
|
+
}
|
|
323
|
+
diff.patch = patch;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
items.push({
|
|
327
|
+
kind: "tool",
|
|
328
|
+
at,
|
|
329
|
+
name: item.name,
|
|
330
|
+
title: take(item.title || item.name),
|
|
331
|
+
status: item.status,
|
|
332
|
+
input: input.text,
|
|
333
|
+
output: output.text || undefined,
|
|
334
|
+
durationMs:
|
|
335
|
+
item.endedAt && item.endedAt >= item.createdAt
|
|
336
|
+
? item.endedAt - item.createdAt
|
|
337
|
+
: undefined,
|
|
338
|
+
diff,
|
|
339
|
+
truncated: truncs.length ? truncs : undefined,
|
|
340
|
+
});
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
case "command_output": {
|
|
344
|
+
const cut = cutOutput(take(item.text), mode, false);
|
|
345
|
+
if (cut.truncated) bump(truncated, "command output");
|
|
346
|
+
items.push({
|
|
347
|
+
kind: "command_output",
|
|
348
|
+
at,
|
|
349
|
+
text: cut.text,
|
|
350
|
+
truncated: cut.truncated && [cut.truncated],
|
|
351
|
+
});
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
case "todos":
|
|
355
|
+
if (mode === "compact" && k !== lastTodos)
|
|
356
|
+
return void bump(omitted, "to-do list");
|
|
357
|
+
items.push({
|
|
358
|
+
kind: "todos",
|
|
359
|
+
at,
|
|
360
|
+
todos: item.todos.map((t) => ({
|
|
361
|
+
text: take(t.text),
|
|
362
|
+
status: t.status,
|
|
363
|
+
})),
|
|
364
|
+
});
|
|
365
|
+
return;
|
|
366
|
+
case "task":
|
|
367
|
+
items.push({
|
|
368
|
+
kind: "task",
|
|
369
|
+
at,
|
|
370
|
+
text:
|
|
371
|
+
mode === "compact"
|
|
372
|
+
? `${take(item.description)}: ${item.status}`
|
|
373
|
+
: take(
|
|
374
|
+
`${item.description}: ${item.status}${item.summary ? `. ${item.summary}` : ""}`
|
|
375
|
+
),
|
|
376
|
+
status: item.status,
|
|
377
|
+
});
|
|
378
|
+
return;
|
|
379
|
+
case "plan": {
|
|
380
|
+
const cut = prose(item.plan, "plan");
|
|
381
|
+
items.push({
|
|
382
|
+
kind: "plan",
|
|
383
|
+
at,
|
|
384
|
+
text: cut.text,
|
|
385
|
+
note: item.carried ? "carried out" : undefined,
|
|
386
|
+
truncated: cut.truncated && [cut.truncated],
|
|
387
|
+
});
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
case "error": {
|
|
391
|
+
const cut = prose(item.message, "error");
|
|
392
|
+
items.push({
|
|
393
|
+
kind: "error",
|
|
394
|
+
at,
|
|
395
|
+
text: cut.text,
|
|
396
|
+
truncated: cut.truncated && [cut.truncated],
|
|
397
|
+
});
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
case "compacted": {
|
|
401
|
+
const sizes =
|
|
402
|
+
item.before && item.after
|
|
403
|
+
? `, ${item.before} → ${item.after} tokens`
|
|
404
|
+
: "";
|
|
405
|
+
items.push({
|
|
406
|
+
kind: "compacted",
|
|
407
|
+
at,
|
|
408
|
+
text: `Conversation compacted${item.trigger ? ` (${item.trigger})` : ""}${sizes}`,
|
|
409
|
+
});
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
case "artifact": {
|
|
413
|
+
const html = inp.artifacts?.[item.artifactId];
|
|
414
|
+
const ex: ExportItem = {
|
|
415
|
+
kind: "artifact",
|
|
416
|
+
at,
|
|
417
|
+
title: take(item.title),
|
|
418
|
+
};
|
|
419
|
+
if (mode === "compact") bump(omitted, "artifact");
|
|
420
|
+
else if (html !== undefined) {
|
|
421
|
+
const cut = cutText(htmlToMarkdown(take(html), 2), mode, "page");
|
|
422
|
+
ex.text = cut.text;
|
|
423
|
+
if (cut.truncated) {
|
|
424
|
+
ex.truncated = [cut.truncated];
|
|
425
|
+
bump(truncated, "artifact");
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
items.push(ex);
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
case "turn_end":
|
|
432
|
+
if (item.durationMs !== undefined)
|
|
433
|
+
durationMs = (durationMs ?? 0) + item.durationMs;
|
|
434
|
+
if (item.costUsd !== undefined)
|
|
435
|
+
costUsd = (costUsd ?? 0) + item.costUsd;
|
|
436
|
+
if (item.interrupted) interrupted = true;
|
|
437
|
+
if (item.ended?.notice) ended = take(item.ended.notice);
|
|
438
|
+
return;
|
|
439
|
+
case "approval":
|
|
440
|
+
return void bump(omitted, "approval");
|
|
441
|
+
case "note":
|
|
442
|
+
return void bump(omitted, "orchestrator note");
|
|
443
|
+
case "mcp":
|
|
444
|
+
return void bump(omitted, "MCP listing");
|
|
445
|
+
default:
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
const first = turn.items[0];
|
|
451
|
+
const last = turn.items[turn.items.length - 1];
|
|
452
|
+
doc.turns.push({
|
|
453
|
+
n: turn.n,
|
|
454
|
+
startedAt: isoLocal(first.createdAt),
|
|
455
|
+
endedAt: isoLocal(last.createdAt),
|
|
456
|
+
durationMs,
|
|
457
|
+
costUsd,
|
|
458
|
+
interrupted,
|
|
459
|
+
ended,
|
|
460
|
+
userId,
|
|
461
|
+
excerpt,
|
|
462
|
+
items,
|
|
463
|
+
hits,
|
|
464
|
+
omitted,
|
|
465
|
+
truncated,
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
doc.metaHits = metaHits;
|
|
469
|
+
return doc;
|
|
470
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import type { ExportMode } from "./view";
|
|
2
|
+
|
|
3
|
+
// The compact rules, one table: the build reads it, and so do the tests
|
|
4
|
+
// (view.COMPACT_RULES says the same in words for the sheet).
|
|
5
|
+
export const COMPACT = {
|
|
6
|
+
outputHead: 10,
|
|
7
|
+
outputTail: 20,
|
|
8
|
+
errorHead: 3,
|
|
9
|
+
errorTail: 27,
|
|
10
|
+
outputChars: 2_000,
|
|
11
|
+
inputChars: 300,
|
|
12
|
+
commandChars: 1_000,
|
|
13
|
+
pasteOver: 8_000,
|
|
14
|
+
pasteHead: 4_000,
|
|
15
|
+
pasteTail: 2_000,
|
|
16
|
+
} as const;
|
|
17
|
+
|
|
18
|
+
export const FULL = {
|
|
19
|
+
/** One runaway log must not decide the file's size. */
|
|
20
|
+
fieldChars: 200_000,
|
|
21
|
+
patchLines: 200,
|
|
22
|
+
} as const;
|
|
23
|
+
|
|
24
|
+
const WHY = { compact: "Compact export", full: "Full export" } as const;
|
|
25
|
+
|
|
26
|
+
export interface Trunc {
|
|
27
|
+
field: string;
|
|
28
|
+
how: string;
|
|
29
|
+
totalLines: number;
|
|
30
|
+
keptLines: number;
|
|
31
|
+
totalChars: number;
|
|
32
|
+
keptChars: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface Cut {
|
|
36
|
+
text: string;
|
|
37
|
+
truncated?: Trunc;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const lineCount = (s: string) => (s ? s.split("\n").length : 0);
|
|
41
|
+
|
|
42
|
+
const trunc = (
|
|
43
|
+
field: string,
|
|
44
|
+
how: string,
|
|
45
|
+
original: string,
|
|
46
|
+
keptLines: number,
|
|
47
|
+
keptChars: number
|
|
48
|
+
): Trunc => ({
|
|
49
|
+
field,
|
|
50
|
+
how,
|
|
51
|
+
totalLines: lineCount(original),
|
|
52
|
+
keptLines,
|
|
53
|
+
totalChars: original.length,
|
|
54
|
+
keptChars,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
/** The start and end of a text by characters, with a marker between. */
|
|
58
|
+
export function cutChars(
|
|
59
|
+
text: string,
|
|
60
|
+
head: number,
|
|
61
|
+
tail: number,
|
|
62
|
+
field: string,
|
|
63
|
+
why: string
|
|
64
|
+
): Cut {
|
|
65
|
+
if (text.length <= head + tail) return { text };
|
|
66
|
+
const kept = head + tail;
|
|
67
|
+
const shown = tail ? `first ${head} and last ${tail}` : `first ${head}`;
|
|
68
|
+
const marker = `[truncated: showing ${shown} of ${text.length} characters; ${text.length - kept} characters omitted (${why})]`;
|
|
69
|
+
const out = [text.slice(0, head), marker, tail ? text.slice(-tail) : ""]
|
|
70
|
+
.filter(Boolean)
|
|
71
|
+
.join("\n");
|
|
72
|
+
return {
|
|
73
|
+
text: out,
|
|
74
|
+
truncated: trunc(
|
|
75
|
+
field,
|
|
76
|
+
tail ? `head${head}+tail${tail}chars` : `head${head}chars`,
|
|
77
|
+
text,
|
|
78
|
+
lineCount(text.slice(0, head)) +
|
|
79
|
+
(tail ? lineCount(text.slice(-tail)) : 0),
|
|
80
|
+
kept
|
|
81
|
+
),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** First `head` and last `tail` lines, with a marker; then the char limit. */
|
|
86
|
+
export function cutLines(
|
|
87
|
+
text: string,
|
|
88
|
+
head: number,
|
|
89
|
+
tail: number,
|
|
90
|
+
maxChars: number,
|
|
91
|
+
field: string
|
|
92
|
+
): Cut {
|
|
93
|
+
const lines = text.split("\n");
|
|
94
|
+
let out = text;
|
|
95
|
+
let how = "";
|
|
96
|
+
if (lines.length > head + tail) {
|
|
97
|
+
const omitted = lines.length - head - tail;
|
|
98
|
+
out = [
|
|
99
|
+
...lines.slice(0, head),
|
|
100
|
+
`[truncated: showing first ${head} and last ${tail} of ${lines.length} lines; ${omitted} lines omitted (${WHY.compact})]`,
|
|
101
|
+
...lines.slice(lines.length - tail),
|
|
102
|
+
].join("\n");
|
|
103
|
+
how = `head${head}+tail${tail}`;
|
|
104
|
+
}
|
|
105
|
+
if (out.length > maxChars) {
|
|
106
|
+
const h = Math.floor(maxChars / 4);
|
|
107
|
+
out = cutChars(out, h, maxChars - h, field, WHY.compact).text;
|
|
108
|
+
how = how ? `${how}+chars${maxChars}` : `head${h}+tail${maxChars - h}chars`;
|
|
109
|
+
}
|
|
110
|
+
if (!how) return { text };
|
|
111
|
+
return {
|
|
112
|
+
text: out,
|
|
113
|
+
truncated: trunc(field, how, text, lineCount(out), out.length),
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Full mode: the start of one field, up to the field cap. */
|
|
118
|
+
const capChars = (text: string, field: string): Cut =>
|
|
119
|
+
cutChars(
|
|
120
|
+
text,
|
|
121
|
+
FULL.fieldChars,
|
|
122
|
+
0,
|
|
123
|
+
field,
|
|
124
|
+
`${FULL.fieldChars.toLocaleString("en-US")}-character field cap`
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
/** A tool's or command's output, by mode. Errors keep their tail. */
|
|
128
|
+
export function cutOutput(
|
|
129
|
+
text: string,
|
|
130
|
+
mode: ExportMode,
|
|
131
|
+
isError: boolean
|
|
132
|
+
): Cut {
|
|
133
|
+
if (mode === "full") return capChars(text, "output");
|
|
134
|
+
const head = isError ? COMPACT.errorHead : COMPACT.outputHead;
|
|
135
|
+
const tail = isError ? COMPACT.errorTail : COMPACT.outputTail;
|
|
136
|
+
return cutLines(text, head, tail, COMPACT.outputChars, "output");
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** A tool's input text; a shell command is kept longer. */
|
|
140
|
+
export function cutInput(
|
|
141
|
+
text: string,
|
|
142
|
+
mode: ExportMode,
|
|
143
|
+
command: boolean
|
|
144
|
+
): Cut {
|
|
145
|
+
if (mode === "full") return capChars(text, "input");
|
|
146
|
+
const max = command ? COMPACT.commandChars : COMPACT.inputChars;
|
|
147
|
+
return cutChars(text, max, 0, "input", WHY.compact);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Prose (a message, a plan, thinking): whole, unless it is a huge paste. */
|
|
151
|
+
export function cutText(text: string, mode: ExportMode, field = "text"): Cut {
|
|
152
|
+
if (mode === "full") return capChars(text, field);
|
|
153
|
+
return text.length > COMPACT.pasteOver
|
|
154
|
+
? cutChars(text, COMPACT.pasteHead, COMPACT.pasteTail, field, WHY.compact)
|
|
155
|
+
: { text };
|
|
156
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { estTokens } from "../session-audit/normalize";
|
|
2
|
+
import type { ExportDoc } from "./build";
|
|
3
|
+
import type { Selected } from "./summary";
|
|
4
|
+
import {
|
|
5
|
+
DANGER_TOKENS,
|
|
6
|
+
MAX_EXPORT_BYTES,
|
|
7
|
+
WARN_TOKENS,
|
|
8
|
+
type ExportMode,
|
|
9
|
+
type ExportRedact,
|
|
10
|
+
type ExportTurnInfo,
|
|
11
|
+
type ExportWarning,
|
|
12
|
+
type Size,
|
|
13
|
+
} from "./view";
|
|
14
|
+
|
|
15
|
+
export const sizeOf = (file: string): Size => ({
|
|
16
|
+
bytes: Buffer.byteLength(file, "utf8"),
|
|
17
|
+
tokens: estTokens(file.length),
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
/** Every turn of the doc, for the range pickers: when, what, how big. */
|
|
21
|
+
export function turnInfos(doc: ExportDoc): ExportTurnInfo[] {
|
|
22
|
+
return doc.turns.map((t) => ({
|
|
23
|
+
n: t.n,
|
|
24
|
+
at: t.startedAt,
|
|
25
|
+
id: t.userId,
|
|
26
|
+
excerpt: t.excerpt,
|
|
27
|
+
items: t.items.length,
|
|
28
|
+
tokens: estTokens(JSON.stringify(t.items).length),
|
|
29
|
+
}));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function warningsFor(
|
|
33
|
+
s: Selected,
|
|
34
|
+
size: Size,
|
|
35
|
+
opts: { mode: ExportMode; redact: ExportRedact }
|
|
36
|
+
): ExportWarning[] {
|
|
37
|
+
const out: ExportWarning[] = [];
|
|
38
|
+
if (size.bytes > MAX_EXPORT_BYTES)
|
|
39
|
+
out.push({
|
|
40
|
+
level: "danger",
|
|
41
|
+
code: "too-big",
|
|
42
|
+
text: "Over the 25 MB limit. Pick a shorter range or use Compact.",
|
|
43
|
+
});
|
|
44
|
+
if (size.tokens > DANGER_TOKENS)
|
|
45
|
+
out.push({
|
|
46
|
+
level: "danger",
|
|
47
|
+
code: "huge",
|
|
48
|
+
text: "Too big for any model in one go. Split it into ranges.",
|
|
49
|
+
});
|
|
50
|
+
else if (size.tokens > WARN_TOKENS)
|
|
51
|
+
out.push({
|
|
52
|
+
level: "warn",
|
|
53
|
+
code: "large",
|
|
54
|
+
text: "Larger than many models' context windows (often 128k to 200k tokens). Try Compact or a shorter range.",
|
|
55
|
+
});
|
|
56
|
+
if (opts.redact === "secrets")
|
|
57
|
+
out.push({
|
|
58
|
+
level: "warn",
|
|
59
|
+
code: "paths-included",
|
|
60
|
+
text: "Your paths, machine names and emails are included. Credentials are still masked.",
|
|
61
|
+
});
|
|
62
|
+
if (
|
|
63
|
+
opts.mode === "full" &&
|
|
64
|
+
s.turns.some((t) => t.items.some((i) => i.output || i.diff?.patch))
|
|
65
|
+
)
|
|
66
|
+
out.push({
|
|
67
|
+
level: "warn",
|
|
68
|
+
code: "full-output",
|
|
69
|
+
text: "Full tool output can contain file contents and logs.",
|
|
70
|
+
});
|
|
71
|
+
if (s.hits.secret > 0)
|
|
72
|
+
out.push({
|
|
73
|
+
level: "warn",
|
|
74
|
+
code: "secrets-found",
|
|
75
|
+
text: `${s.hits.secret} ${s.hits.secret === 1 ? "credential was" : "credentials were"} found and masked. Check the output for any we missed.`,
|
|
76
|
+
});
|
|
77
|
+
out.push({
|
|
78
|
+
level: "info",
|
|
79
|
+
code: "sensitive",
|
|
80
|
+
text: "Exports can include code, file contents and command output. Read it before you upload it anywhere.",
|
|
81
|
+
});
|
|
82
|
+
return out;
|
|
83
|
+
}
|