@akasecurity/cli 0.9.12 → 0.9.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/dist/cli.js +553 -82
  2. package/extension/background.js +1 -1
  3. package/extension/bridge.js +1 -0
  4. package/extension/content.js +1 -1
  5. package/extension/manifest.json +24 -1
  6. package/extension/popup.html +9 -0
  7. package/extension/popup.js +1 -1
  8. package/extension/tap.js +1 -0
  9. package/native-host/host.js +919 -142
  10. package/native-host/scan-worker.js +209 -17
  11. package/native-host/sync.js +250 -32
  12. package/package.json +6 -6
  13. package/web-ui/web-ui/.next/BUILD_ID +1 -1
  14. package/web-ui/web-ui/.next/app-build-manifest.json +72 -72
  15. package/web-ui/web-ui/.next/app-path-routes-manifest.json +7 -7
  16. package/web-ui/web-ui/.next/build-manifest.json +2 -2
  17. package/web-ui/web-ui/.next/prerender-manifest.json +18 -18
  18. package/web-ui/web-ui/.next/server/app/(app)/activity/page.js +2 -2
  19. package/web-ui/web-ui/.next/server/app/(app)/activity/page.js.nft.json +1 -1
  20. package/web-ui/web-ui/.next/server/app/(app)/activity/page_client-reference-manifest.js +1 -1
  21. package/web-ui/web-ui/.next/server/app/(app)/data-shares/page.js +2 -2
  22. package/web-ui/web-ui/.next/server/app/(app)/data-shares/page.js.nft.json +1 -1
  23. package/web-ui/web-ui/.next/server/app/(app)/data-shares/page_client-reference-manifest.js +1 -1
  24. package/web-ui/web-ui/.next/server/app/(app)/detections/page.js +2 -2
  25. package/web-ui/web-ui/.next/server/app/(app)/detections/page.js.nft.json +1 -1
  26. package/web-ui/web-ui/.next/server/app/(app)/detections/page_client-reference-manifest.js +1 -1
  27. package/web-ui/web-ui/.next/server/app/(app)/exceptions/[id]/page.js +2 -2
  28. package/web-ui/web-ui/.next/server/app/(app)/exceptions/[id]/page.js.nft.json +1 -1
  29. package/web-ui/web-ui/.next/server/app/(app)/exceptions/[id]/page_client-reference-manifest.js +1 -1
  30. package/web-ui/web-ui/.next/server/app/(app)/exceptions/new/page.js +1 -1
  31. package/web-ui/web-ui/.next/server/app/(app)/exceptions/new/page.js.nft.json +1 -1
  32. package/web-ui/web-ui/.next/server/app/(app)/exceptions/new/page_client-reference-manifest.js +1 -1
  33. package/web-ui/web-ui/.next/server/app/(app)/exceptions/page.js +1 -1
  34. package/web-ui/web-ui/.next/server/app/(app)/exceptions/page.js.nft.json +1 -1
  35. package/web-ui/web-ui/.next/server/app/(app)/exceptions/page_client-reference-manifest.js +1 -1
  36. package/web-ui/web-ui/.next/server/app/(app)/findings/page.js +2 -2
  37. package/web-ui/web-ui/.next/server/app/(app)/findings/page.js.nft.json +1 -1
  38. package/web-ui/web-ui/.next/server/app/(app)/findings/page_client-reference-manifest.js +1 -1
  39. package/web-ui/web-ui/.next/server/app/(app)/inventory/page.js +2 -2
  40. package/web-ui/web-ui/.next/server/app/(app)/inventory/page.js.nft.json +1 -1
  41. package/web-ui/web-ui/.next/server/app/(app)/inventory/page_client-reference-manifest.js +1 -1
  42. package/web-ui/web-ui/.next/server/app/(app)/policies/page.js +2 -2
  43. package/web-ui/web-ui/.next/server/app/(app)/policies/page.js.nft.json +1 -1
  44. package/web-ui/web-ui/.next/server/app/(app)/policies/page_client-reference-manifest.js +1 -1
  45. package/web-ui/web-ui/.next/server/app/(app)/scan/page.js +3 -3
  46. package/web-ui/web-ui/.next/server/app/(app)/scan/page.js.nft.json +1 -1
  47. package/web-ui/web-ui/.next/server/app/(app)/scan/page_client-reference-manifest.js +1 -1
  48. package/web-ui/web-ui/.next/server/app/(app)/security/page.js +1 -1
  49. package/web-ui/web-ui/.next/server/app/(app)/security/page.js.nft.json +1 -1
  50. package/web-ui/web-ui/.next/server/app/(app)/security/page_client-reference-manifest.js +1 -1
  51. package/web-ui/web-ui/.next/server/app/(app)/settings/page.js +2 -2
  52. package/web-ui/web-ui/.next/server/app/(app)/settings/page.js.nft.json +1 -1
  53. package/web-ui/web-ui/.next/server/app/(app)/settings/page_client-reference-manifest.js +1 -1
  54. package/web-ui/web-ui/.next/server/app/(app)/updates/page.js +2 -2
  55. package/web-ui/web-ui/.next/server/app/(app)/updates/page.js.nft.json +1 -1
  56. package/web-ui/web-ui/.next/server/app/(app)/updates/page_client-reference-manifest.js +1 -1
  57. package/web-ui/web-ui/.next/server/app/(app)/vault/page.js +2 -2
  58. package/web-ui/web-ui/.next/server/app/(app)/vault/page.js.nft.json +1 -1
  59. package/web-ui/web-ui/.next/server/app/(app)/vault/page_client-reference-manifest.js +1 -1
  60. package/web-ui/web-ui/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  61. package/web-ui/web-ui/.next/server/app/_not-found.html +1 -1
  62. package/web-ui/web-ui/.next/server/app/_not-found.rsc +2 -2
  63. package/web-ui/web-ui/.next/server/app/index.html +1 -1
  64. package/web-ui/web-ui/.next/server/app/index.rsc +2 -2
  65. package/web-ui/web-ui/.next/server/app/page_client-reference-manifest.js +1 -1
  66. package/web-ui/web-ui/.next/server/app-paths-manifest.json +7 -7
  67. package/web-ui/web-ui/.next/server/chunks/121.js +1 -1
  68. package/web-ui/web-ui/.next/server/chunks/130.js +1026 -0
  69. package/web-ui/web-ui/.next/server/chunks/290.js +1 -1
  70. package/web-ui/web-ui/.next/server/chunks/3.js +1017 -0
  71. package/web-ui/web-ui/.next/server/chunks/300.js +1 -0
  72. package/web-ui/web-ui/.next/server/chunks/475.js +1 -1
  73. package/web-ui/web-ui/.next/server/chunks/662.js +1 -0
  74. package/web-ui/web-ui/.next/server/chunks/70.js +1 -0
  75. package/web-ui/web-ui/.next/server/functions-config-manifest.json +6 -6
  76. package/web-ui/web-ui/.next/server/middleware-manifest.json +5 -5
  77. package/web-ui/web-ui/.next/server/pages/404.html +1 -1
  78. package/web-ui/web-ui/.next/server/pages/500.html +1 -1
  79. package/web-ui/web-ui/.next/server/server-reference-manifest.js +1 -1
  80. package/web-ui/web-ui/.next/server/server-reference-manifest.json +1 -1
  81. package/web-ui/web-ui/.next/static/chunks/421-cc967c01935826d1.js +1 -0
  82. package/web-ui/web-ui/.next/static/chunks/app/(app)/activity/{page-a27c8937989448e4.js → page-41fba0354cc950be.js} +1 -1
  83. package/web-ui/web-ui/.next/static/chunks/app/(app)/data-shares/{page-2b805f9e1f995092.js → page-d2e03c3ab61af01a.js} +1 -1
  84. package/web-ui/web-ui/.next/static/chunks/app/(app)/detections/{page-a87cfee370c4e0f4.js → page-566036afcc87e4c9.js} +1 -1
  85. package/web-ui/web-ui/.next/static/chunks/app/(app)/{error-190085728a856072.js → error-4baae46bcee63009.js} +1 -1
  86. package/web-ui/web-ui/.next/static/chunks/app/(app)/exceptions/[id]/{page-4e214491c7be8d43.js → page-73dbcf0078b82ec4.js} +1 -1
  87. package/web-ui/web-ui/.next/static/chunks/app/(app)/exceptions/new/page-8051503193da1baf.js +1 -0
  88. package/web-ui/web-ui/.next/static/chunks/app/(app)/exceptions/{page-f16ba40d88e414b1.js → page-32c8a853bc8b5d34.js} +1 -1
  89. package/web-ui/web-ui/.next/static/chunks/app/(app)/findings/page-dc8fadb56d6d1e77.js +1 -0
  90. package/web-ui/web-ui/.next/static/chunks/app/(app)/inventory/page-9b49ef42350ea7bd.js +1 -0
  91. package/web-ui/web-ui/.next/static/chunks/app/(app)/{layout-cb3c8c7b6b33414c.js → layout-f5f361f817f90ac9.js} +1 -1
  92. package/web-ui/web-ui/.next/static/chunks/app/(app)/policies/{page-ab639e48f60ad7ee.js → page-01b303adf3c8ee56.js} +1 -1
  93. package/web-ui/web-ui/.next/static/chunks/app/(app)/scan/page-8db3b62245c333a3.js +1 -0
  94. package/web-ui/web-ui/.next/static/chunks/app/(app)/security/{page-a3095119b3e87341.js → page-7a4ba61626e0d130.js} +1 -1
  95. package/web-ui/web-ui/.next/static/chunks/app/(app)/settings/page-0930b49f6ab951bc.js +1 -0
  96. package/web-ui/web-ui/.next/static/chunks/app/(app)/updates/{page-5b32bd569c9569d9.js → page-9b04f3441a1a3f96.js} +1 -1
  97. package/web-ui/web-ui/.next/static/chunks/app/(app)/vault/page-911a76e75f5a1419.js +1 -0
  98. package/web-ui/web-ui/.next/static/css/298a651f6af38209.css +2 -0
  99. package/web-ui/web-ui/dist/scan-worker.js +209 -17
  100. package/web-ui/web-ui/.next/server/chunks/611.js +0 -1015
  101. package/web-ui/web-ui/.next/server/chunks/7.js +0 -1
  102. package/web-ui/web-ui/.next/server/chunks/816.js +0 -1
  103. package/web-ui/web-ui/.next/server/chunks/968.js +0 -1006
  104. package/web-ui/web-ui/.next/static/chunks/674-d8237c5d0faa4a53.js +0 -1
  105. package/web-ui/web-ui/.next/static/chunks/app/(app)/exceptions/new/page-c1bb992ee1f12566.js +0 -1
  106. package/web-ui/web-ui/.next/static/chunks/app/(app)/findings/page-59c76da638f22b0e.js +0 -1
  107. package/web-ui/web-ui/.next/static/chunks/app/(app)/inventory/page-c37447caf89a86e1.js +0 -1
  108. package/web-ui/web-ui/.next/static/chunks/app/(app)/scan/page-494614fccc8c62cc.js +0 -1
  109. package/web-ui/web-ui/.next/static/chunks/app/(app)/settings/page-469613d499e41093.js +0 -1
  110. package/web-ui/web-ui/.next/static/chunks/app/(app)/vault/page-3152ceb31f024b8d.js +0 -1
  111. package/web-ui/web-ui/.next/static/css/6e72259eccdf4435.css +0 -2
  112. /package/web-ui/web-ui/.next/static/{PMeePXfXsQ2xxrXppySG_ → pkVZ1_T311YNkJgZOLTx5}/_buildManifest.js +0 -0
  113. /package/web-ui/web-ui/.next/static/{PMeePXfXsQ2xxrXppySG_ → pkVZ1_T311YNkJgZOLTx5}/_ssgManifest.js +0 -0
package/dist/cli.js CHANGED
@@ -20885,7 +20885,17 @@ var UNPRICEABLE_PROVIDERS = Object.freeze([
20885
20885
  "gateway",
20886
20886
  "unknown",
20887
20887
  "cli",
20888
- "api"
20888
+ "api",
20889
+ // The browser extension's native host records these as `llm_call.provider`
20890
+ // for a web-chat turn — the web tool id, deliberately never the vendor id
20891
+ // (`openai`/`anthropic`) the session root carries. Subscription traffic
20892
+ // burns rate-limit budget, not dollar credits, and listing them here is
20893
+ // what keeps that true structurally: a later maintainer who wants to price
20894
+ // web-chat traffic at API rates has to delete this entry first, and meet
20895
+ // the reason on the way, rather than quietly adding one to
20896
+ // PROVIDER_PLATFORM.
20897
+ "chatgpt",
20898
+ "claude-ai"
20889
20899
  ]);
20890
20900
  function platformForProvider(provider) {
20891
20901
  return PROVIDER_PLATFORM.get(provider.trim().toLowerCase()) ?? null;
@@ -21094,7 +21104,12 @@ var HARNESS = {
21094
21104
  ClaudeDesktop: "claudedesktop",
21095
21105
  ChatGpt: "chatgpt",
21096
21106
  ClaudeAi: "claudeai",
21097
- Api: "api"
21107
+ Api: "api",
21108
+ // Not a coding assistant a person drives — an in-process SDK embedded in an
21109
+ // application, so it has no IDE/CLI/desktop/web surface of its own. Carries
21110
+ // the same id as its SOURCE_TOOL counterpart, unlike every capture-side tool
21111
+ // whose wire spelling differs from its display spelling.
21112
+ AiTcSdk: "ai-tc-sdk"
21098
21113
  };
21099
21114
  var Harness = external_exports.enum(HARNESS).meta({ id: "Harness" });
21100
21115
  var SOURCE_TOOL = {
@@ -21110,9 +21125,15 @@ var SOURCE_TOOL = {
21110
21125
  // whose tool could not be identified both render through the read side's
21111
21126
  // miss path rather than as a harness of their own.
21112
21127
  Cli: "cli",
21113
- Unknown: "unknown"
21128
+ Unknown: "unknown",
21129
+ // The wire id an in-process, request-path SDK stamps on its own structural
21130
+ // rows (`request_decision`) — never a capture of prompt/response/tool text,
21131
+ // since the SDK sits in front of a model call rather than inside a coding
21132
+ // assistant's own hook contract.
21133
+ AiTcSdk: "ai-tc-sdk"
21114
21134
  };
21115
21135
  var SourceTool = external_exports.enum(SOURCE_TOOL).meta({ id: "SourceTool" });
21136
+ var WebSourceTool = SourceTool.extract(["ChatGpt", "ClaudeAi"]);
21116
21137
  var TOOL_TO_HARNESS = {
21117
21138
  [SOURCE_TOOL.ClaudeCode]: HARNESS.ClaudeCode,
21118
21139
  [SOURCE_TOOL.ClaudeDesktop]: HARNESS.ClaudeDesktop,
@@ -21121,7 +21142,12 @@ var TOOL_TO_HARNESS = {
21121
21142
  [SOURCE_TOOL.ChatGpt]: HARNESS.ChatGpt,
21122
21143
  [SOURCE_TOOL.Codex]: HARNESS.Codex,
21123
21144
  [SOURCE_TOOL.Antigravity]: HARNESS.Antigravity,
21124
- [SOURCE_TOOL.ClaudeAi]: HARNESS.ClaudeAi
21145
+ [SOURCE_TOOL.ClaudeAi]: HARNESS.ClaudeAi,
21146
+ // Wire and display id are the same string here, but the row still belongs:
21147
+ // both vocabularies carry the `AiTcSdk` member, and the join is exactly
21148
+ // their intersection — leaving a shared member out would read as an
21149
+ // uninstrumented tool on both surfaces, which this one is not.
21150
+ [SOURCE_TOOL.AiTcSdk]: HARNESS.AiTcSdk
21125
21151
  };
21126
21152
 
21127
21153
  // ../packages/schema/src/zod/finding.ts
@@ -21155,7 +21181,8 @@ var FindingProvider = Harness.extract([
21155
21181
  "ClaudeAi",
21156
21182
  "Codex",
21157
21183
  "Antigravity",
21158
- "Api"
21184
+ "Api",
21185
+ "AiTcSdk"
21159
21186
  ]).meta({ id: "FindingProvider" });
21160
21187
  var FindingCategory = external_exports.enum([
21161
21188
  "secret",
@@ -21532,18 +21559,41 @@ var AuditEventType = external_exports.enum([
21532
21559
  // 'tool_call' is the reconciler's structural row for every call, while
21533
21560
  // 'tool_use' exists only where a hook enforced against the arguments.
21534
21561
  "tool_use",
21535
- // One row per model REFUSAL: a switch onto a prohibited model that was
21536
- // denied, or a turn refused because the session was already running on one.
21537
- // A structural row like the ones above rather than a capture — it carries
21538
- // the model that was refused and nothing the user typed, because what is
21539
- // worth recording about a governance decision is the decision, and prompt
21540
- // text is the thing this product exists to keep from travelling.
21562
+ // One row per model REFUSAL, across all four seams a prohibited model can be
21563
+ // stopped at: a switch onto it, a turn already running on it, a subagent
21564
+ // spawn asking for it, or a request-path refusal an embedded request-path
21565
+ // SDK makes in-process before the call leaves the application. Which seam
21566
+ // rides `attributes.refusal_seam`, never this member name. A structural row
21567
+ // like the ones above rather than a capture — it carries the model that was
21568
+ // refused and nothing the user typed, because what is worth recording about
21569
+ // a governance decision is the decision, and prompt text is the thing this
21570
+ // product exists to keep from travelling.
21541
21571
  "model_refusal",
21572
+ // One row per request-path DECISION: a policy check an embedded request-path
21573
+ // SDK performs in-process before a model call leaves the application, or
21574
+ // against that call's non-streamed response. A structural row like
21575
+ // 'model_refusal' rather than a capture — content-free in the same way:
21576
+ // which side, which seam, what action and which field are decided rides
21577
+ // `attributes`, never this member name, and the matched text itself never
21578
+ // travels.
21579
+ //
21580
+ // A prohibited-model refusal on the request path is deliberately NOT this
21581
+ // member: it stays 'model_refusal' with `refusal_seam: 'request'`, so it
21582
+ // shares one bucket with the plugin's switch/turn/spawn refusals rather
21583
+ // than splitting one governance concept across two event types. This
21584
+ // member carries every OTHER request-path decision.
21585
+ "request_decision",
21542
21586
  // One row per config-inventory scan, hung off the session root. It is the
21543
21587
  // fact the posture inspection findings reference (findings require an
21544
21588
  // audit_event_id), and its started_at is the "scanned Nm ago" the read
21545
21589
  // surface renders.
21546
- "config_scan"
21590
+ "config_scan",
21591
+ // One row per reported browser-extension capture status, hung off the web
21592
+ // session root. The durable home of what one tab's network interception
21593
+ // is doing — a write-through of the native host's in-memory tracker, so a
21594
+ // second process (aka extension status) and a restarted host both have
21595
+ // somewhere to read it back from.
21596
+ "capture_status"
21547
21597
  ]).meta({ id: "AuditEventType" });
21548
21598
  var AttributeBag = external_exports.record(external_exports.string(), external_exports.unknown());
21549
21599
  var HostAttributes = external_exports.object({
@@ -21703,6 +21753,20 @@ var CaptureAttributes = external_exports.object({
21703
21753
  // repeated rather than referenced because a store reader opens this file.
21704
21754
  redact_degraded_to: ActionTaken.optional()
21705
21755
  }).catchall(external_exports.unknown());
21756
+ var CaptureStatusAttributes = external_exports.object({
21757
+ source_tool: external_exports.string().optional(),
21758
+ patched: external_exports.boolean().optional(),
21759
+ live: external_exports.boolean().optional(),
21760
+ blind: external_exports.boolean().optional(),
21761
+ sends_seen_dom: external_exports.number().int().nonnegative().optional(),
21762
+ exchanges_seen_net: external_exports.number().int().nonnegative().optional(),
21763
+ parse_failures: external_exports.number().int().nonnegative().optional(),
21764
+ unparsed_bodies: external_exports.number().int().nonnegative().optional(),
21765
+ shape_misses: external_exports.array(external_exports.string()).optional(),
21766
+ conversation_endpoints: external_exports.number().int().nonnegative().optional(),
21767
+ closed: external_exports.boolean().optional(),
21768
+ enforcement: external_exports.string().optional()
21769
+ }).catchall(external_exports.unknown());
21706
21770
  var ToolCallInspection = external_exports.object({
21707
21771
  ruleId: external_exports.string().min(1),
21708
21772
  ruleName: external_exports.string(),
@@ -22562,6 +22626,30 @@ var AttachedCredential = external_exports.object({
22562
22626
  keyPrefix: external_exports.string().min(1).max(16).optional(),
22563
22627
  mintedAt: external_exports.iso.datetime().optional()
22564
22628
  });
22629
+ var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1", "[::1]"]);
22630
+ function unsafeEndpointReason(endpoint) {
22631
+ let parsed2;
22632
+ try {
22633
+ parsed2 = new URL(endpoint);
22634
+ } catch {
22635
+ return "unparseable";
22636
+ }
22637
+ if (parsed2.username !== "" || parsed2.password !== "") return "userinfo";
22638
+ if (parsed2.search !== "" || parsed2.hash !== "") return "query-or-fragment";
22639
+ if (parsed2.protocol === "https:") return null;
22640
+ return parsed2.protocol === "http:" && LOOPBACK_HOSTS.has(parsed2.hostname) ? null : "insecure";
22641
+ }
22642
+ function isSafeEndpoint(endpoint) {
22643
+ return unsafeEndpointReason(endpoint) === null;
22644
+ }
22645
+ function originOnly(endpoint) {
22646
+ try {
22647
+ const parsed2 = new URL(endpoint);
22648
+ return `${parsed2.protocol}//${parsed2.host}`;
22649
+ } catch {
22650
+ return "(unparseable endpoint)";
22651
+ }
22652
+ }
22565
22653
  var MAX_DATE_MS = 253402300799999;
22566
22654
  var MAX_INT4 = 2147483647;
22567
22655
  var StorePosturePack = external_exports.object({
@@ -22716,6 +22804,11 @@ var RemoteFailureKind = external_exports.enum([
22716
22804
  "rejected",
22717
22805
  "unreachable"
22718
22806
  ]);
22807
+ var ControlPlaneFailure = RemoteFailureKind.extract([
22808
+ "unauthorized",
22809
+ "forbidden",
22810
+ "unreachable"
22811
+ ]);
22719
22812
  var AttachDeviceRequest = external_exports.object({
22720
22813
  // This machine's own continuity id, so re-attaching ROTATES the credential
22721
22814
  // on one machine record instead of producing a second one. Client-minted
@@ -23255,7 +23348,12 @@ var EventMetadata = external_exports.object({
23255
23348
  // in — set by the browser extension's network capture so a stored `response`
23256
23349
  // row can be joined to the `llm_call` leaf describing the same turn. Absent
23257
23350
  // on every other capture path, which has no such id.
23258
- messageId: external_exports.string().optional(),
23351
+ //
23352
+ // Non-empty for the reason WebExchange.messageId is: it is the join key, and
23353
+ // a blank one matches no `llm_call` leaf. That refusal reaches only the
23354
+ // places an event is PARSED; the local write path types the event and parses
23355
+ // nothing, which is why `toCaptureAttributes` omits a blank one separately.
23356
+ messageId: external_exports.string().min(1).optional(),
23259
23357
  conversationId: external_exports.string().optional(),
23260
23358
  // How long THIS capture's inspection blocked its caller, in whole
23261
23359
  // milliseconds — the plugin's own added latency, NOT the LLM call it sat in
@@ -24358,6 +24456,22 @@ function isHistorySyncConsentStale(consent, endpoint) {
24358
24456
  if (consent === void 0 || endpoint === void 0) return false;
24359
24457
  return consent.endpoint === endpoint && consent.payloadVersion !== HISTORY_SYNC_PAYLOAD_VERSION;
24360
24458
  }
24459
+ var WEB_CHAT_CAPTURE_CONSENT_VERSION = 1;
24460
+ var WebChatCaptureConsent = external_exports.object({
24461
+ acknowledgedAt: external_exports.iso.datetime(),
24462
+ version: external_exports.number().int().positive()
24463
+ });
24464
+ var WebChatResponseCapture = external_exports.enum(["with-findings", "always", "never"]);
24465
+ var WebChatCapture = external_exports.object({
24466
+ responses: WebChatResponseCapture.default("with-findings"),
24467
+ account: external_exports.boolean().default(false),
24468
+ // Absent until granted. Presence alone does not authorize anything — see
24469
+ // isWebChatCaptureConsentValid.
24470
+ consent: WebChatCaptureConsent.optional()
24471
+ });
24472
+ function isWebChatCaptureConsentValid(consent) {
24473
+ return consent?.version === WEB_CHAT_CAPTURE_CONSENT_VERSION;
24474
+ }
24361
24475
  var BODY_RETENTION_DEFAULT_DAYS = 30;
24362
24476
  var BodyRetention = external_exports.object({
24363
24477
  enabled: external_exports.boolean().default(false),
@@ -24416,6 +24530,16 @@ var WorkspaceSettings = external_exports.object({
24416
24530
  // both widenings. Absent until granted, and a grant for a different endpoint
24417
24531
  // or an older payload no longer counts.
24418
24532
  historySyncConsent: HistorySyncConsent.optional(),
24533
+ // What the browser extension may record from a web chat, and the grant that
24534
+ // authorizes it. Absent until the user answers: recording something that was
24535
+ // never recorded before is never an assumed grant on upgrade, so the whole
24536
+ // block is optional rather than defaulted in. What an absent block means is
24537
+ // webChatCaptureOf's answer, in one place.
24538
+ //
24539
+ // Enforcement is NOT gated on this. A machine that has never answered still
24540
+ // blocks, redacts and warns on what a user sends; the grant covers what is
24541
+ // written down.
24542
+ webChatCapture: WebChatCapture.optional(),
24419
24543
  // Local body expiry (see BodyRetention). Off until switched on; expiring a
24420
24544
  // body never removes the row or its findings.
24421
24545
  bodyRetention: BodyRetention.default({
@@ -24426,6 +24550,9 @@ var WorkspaceSettings = external_exports.object({
24426
24550
  function defaultWorkspaceSettings() {
24427
24551
  return WorkspaceSettings.parse({});
24428
24552
  }
24553
+ function webChatCaptureOf(settings) {
24554
+ return settings.webChatCapture ?? WebChatCapture.parse({});
24555
+ }
24429
24556
  function isAttached(settings) {
24430
24557
  return settings.runMode === "attached" && settings.controlPlane !== void 0;
24431
24558
  }
@@ -24529,7 +24656,10 @@ function toCaptureAttributes(event) {
24529
24656
  // `.catchall(z.unknown())` carries the long tail.
24530
24657
  ...metadata?.model !== void 0 ? { model: metadata.model } : {},
24531
24658
  ...metadata?.turnIndex !== void 0 ? { turn_index: metadata.turnIndex } : {},
24532
- ...metadata?.messageId !== void 0 ? { message_id: metadata.messageId } : {},
24659
+ // A blank id is omitted rather than stored: it is a join key and `''` joins
24660
+ // nothing. This runs on the local write path, which types the event but
24661
+ // never parses it, so EventMetadata's own `.min(1)` does not reach here.
24662
+ ...metadata?.messageId !== void 0 && metadata.messageId !== "" ? { message_id: metadata.messageId } : {},
24533
24663
  ...metadata?.conversationId !== void 0 ? { conversation_id: metadata.conversationId } : {}
24534
24664
  };
24535
24665
  }
@@ -24913,12 +25043,14 @@ var RecommendedActionIdParam = external_exports.object({ id: external_exports.st
24913
25043
  // ../packages/schema/src/zod/settings-action.ts
24914
25044
  var HistorySyncConsentChoice = external_exports.enum(["granted", "revoked", "unchanged"]).meta({ id: "HistorySyncConsentChoice" });
24915
25045
  var ModelJudgeConsentChoice = external_exports.enum(["granted", "revoked", "unchanged"]).meta({ id: "ModelJudgeConsentChoice" });
25046
+ var WebChatCaptureConsentChoice = external_exports.enum(["granted", "revoked", "unchanged"]).meta({ id: "WebChatCaptureConsentChoice" });
24916
25047
  var SaveSettingsInput = external_exports.object({
24917
25048
  historicalAccess: external_exports.string(),
24918
25049
  modelJudgeConsent: ModelJudgeConsentChoice,
24919
25050
  historySyncConsent: HistorySyncConsentChoice,
24920
25051
  vaultConsent: external_exports.string(),
24921
25052
  vaultInlineReveal: external_exports.string(),
25053
+ webChatCaptureConsent: WebChatCaptureConsentChoice,
24922
25054
  // Widened to `string` like its neighbours rather than typed as
24923
25055
  // `RedactFallback`, on this module's own layering rule: shape here, VALUE at
24924
25056
  // the call site, so the domain check receives the type it was written for.
@@ -25133,11 +25265,24 @@ var WebExchange = external_exports.object({
25133
25265
  turnIndex: external_exports.number().int().nonnegative().optional(),
25134
25266
  toolCalls: external_exports.array(WebToolCall).default([]),
25135
25267
  // Absent when the adapter recovered no text. Capped by the caller at
25136
- // RESPONSE_TEXT_MAX_BYTES; `truncated` records that the cap was reached, so a
25137
- // short capture is never mistaken for a short reply.
25268
+ // RESPONSE_TEXT_MAX_BYTES, so a short capture is never mistaken for a short
25269
+ // reply.
25138
25270
  responseText: external_exports.string().optional(),
25271
+ // The stored text is short of the reply. It does NOT say which of the two
25272
+ // ceilings on this path cut it: the caller applies its own cap on the raw
25273
+ // bytes it reads off the wire, which can be reached by a stream whose
25274
+ // recovered text stays well under RESPONSE_TEXT_MAX_BYTES, and applies that
25275
+ // one to the text. A reader cannot tell them apart, and nothing downstream
25276
+ // should branch as though it could.
25139
25277
  truncated: external_exports.boolean().default(false)
25140
25278
  });
25279
+ var WebEnforcementState = external_exports.enum([
25280
+ "watching",
25281
+ "composer-only",
25282
+ "button-only",
25283
+ "unattached",
25284
+ "unknown"
25285
+ ]);
25141
25286
  var RESPONSE_TEXT_MAX_BYTES = 2 * 1024 * 1024;
25142
25287
  var WebCaptureStatus = external_exports.object({
25143
25288
  patched: external_exports.boolean(),
@@ -25149,8 +25294,66 @@ var WebCaptureStatus = external_exports.object({
25149
25294
  unparsedBodies: external_exports.number().int().nonnegative(),
25150
25295
  // The adapter-declared JSON key paths that were absent from a real payload —
25151
25296
  // the earliest signal that a site's contract moved.
25152
- shapeMisses: external_exports.array(external_exports.string()).default([])
25153
- });
25297
+ shapeMisses: external_exports.array(external_exports.string()).default([]),
25298
+ // How many `kind: 'conversation'` endpoints the reporting tab's adapter
25299
+ // compiled. Zero means this build declares none for the site, so observing
25300
+ // nothing is the design rather than a fault — the one fact that separates a
25301
+ // site nobody has surveyed yet from one whose contract moved. Defaulted so a
25302
+ // build predating the field is read as declaring nothing rather than refused.
25303
+ conversationEndpoints: external_exports.number().int().nonnegative().default(0),
25304
+ // The document that sent this report is going away. The bridge sets it on
25305
+ // its `pagehide` report and nowhere else.
25306
+ //
25307
+ // A property of the REPORT rather than of capture health, which is why
25308
+ // nothing in `deriveWebCaptureState` reads it and why it stays out of the
25309
+ // bridge's own report signature — a closing tab's last word must not be
25310
+ // suppressed for carrying the same health as the report before it. What
25311
+ // reads it is the per-site fold: a document that said it was unloading stops
25312
+ // voting on the site's state, so the reload the `blind` remediation asks for
25313
+ // can actually clear the verdict it was shown. A document that dies without
25314
+ // sending one is covered by CAPTURE_STATUS_DOCUMENT_QUIET_MS instead.
25315
+ //
25316
+ // Defaulted so a build predating the field reads as a document that never
25317
+ // said it was closing — which keeps it voting, the same as every report that
25318
+ // is not a final one.
25319
+ closed: external_exports.boolean().default(false),
25320
+ // What the DOM enforcement path is doing, which none of the counters above
25321
+ // can say: `sendsSeenDom` rises only once a send has COMPLETED, so a tab
25322
+ // whose watcher never bound reports zero exactly like a tab nobody typed in.
25323
+ // Defaulted to 'unknown' rather than 'watching' so a status from a build
25324
+ // predating the field is not read as reporting a healthy one.
25325
+ enforcement: WebEnforcementState.default("unknown")
25326
+ });
25327
+ function webCaptureStatusObservedTurnPath(status) {
25328
+ if (!status.patched) return true;
25329
+ if (status.conversationEndpoints === 0) return true;
25330
+ return status.blind || status.shapeMisses.length > 0 || status.parseFailures > 0 || status.unparsedBodies > 0 || status.exchangesSeenNet > 0;
25331
+ }
25332
+ function pickReportedCaptureStatus(candidates) {
25333
+ return candidates.find((c) => webCaptureStatusObservedTurnPath(c.status)) ?? candidates[0];
25334
+ }
25335
+ var CAPTURE_STATUS_RECENCY_MS = 30 * 24 * 60 * 60 * 1e3;
25336
+ var CAPTURE_STATUS_RECENCY_DAYS = CAPTURE_STATUS_RECENCY_MS / (24 * 60 * 60 * 1e3);
25337
+ var CAPTURE_STATUS_DOCUMENT_QUIET_MS = 12 * 60 * 60 * 1e3;
25338
+ function fromCaptureStatusAttributes(bag) {
25339
+ const parsedBag = CaptureStatusAttributes.safeParse(bag);
25340
+ if (!parsedBag.success) return null;
25341
+ const b = parsedBag.data;
25342
+ const parsedStatus = WebCaptureStatus.safeParse({
25343
+ patched: b.patched,
25344
+ live: b.live,
25345
+ blind: b.blind,
25346
+ sendsSeenDom: b.sends_seen_dom,
25347
+ exchangesSeenNet: b.exchanges_seen_net,
25348
+ parseFailures: b.parse_failures,
25349
+ unparsedBodies: b.unparsed_bodies,
25350
+ shapeMisses: b.shape_misses,
25351
+ conversationEndpoints: b.conversation_endpoints,
25352
+ closed: b.closed,
25353
+ enforcement: b.enforcement
25354
+ });
25355
+ return parsedStatus.success ? parsedStatus.data : null;
25356
+ }
25154
25357
 
25155
25358
  // ../packages/local-ops/src/registry.ts
25156
25359
  var AGENT_PLUGINS = [
@@ -27715,6 +27918,114 @@ var CONFIG_POSTURE_RULES = [
27715
27918
  }
27716
27919
  ];
27717
27920
 
27921
+ // ../packages/detections/src/posture/web-capture-posture.ts
27922
+ var RULE_VERSION2 = "1";
27923
+ var DRIFT_MIN_PARSE_FAILURES = 2;
27924
+ var WEB_CAPTURE_DRIFT_STATES = /* @__PURE__ */ new Set([
27925
+ "blind",
27926
+ "degraded"
27927
+ ]);
27928
+ var WEB_CAPTURE_DRIFT_RULE = {
27929
+ ruleId: "web-capture-drift",
27930
+ version: RULE_VERSION2,
27931
+ name: "Web chat capture is not reading the site",
27932
+ category: "config",
27933
+ severity: "medium",
27934
+ definition: JSON.stringify({
27935
+ kind: "web-capture-drift",
27936
+ states: [...WEB_CAPTURE_DRIFT_STATES],
27937
+ minParseFailures: DRIFT_MIN_PARSE_FAILURES
27938
+ })
27939
+ };
27940
+ function deriveWebCaptureState(status) {
27941
+ if (status === void 0) return "unreported";
27942
+ if (status.conversationEndpoints === 0) return "standby";
27943
+ if (!status.patched) return "unpatched";
27944
+ if (status.blind) return "blind";
27945
+ if (status.shapeMisses.length > 0) return "degraded";
27946
+ if (status.parseFailures >= DRIFT_MIN_PARSE_FAILURES) return "degraded";
27947
+ return status.live ? "active" : "idle";
27948
+ }
27949
+ var STATE_SEVERITY = {
27950
+ blind: 0,
27951
+ degraded: 1,
27952
+ unpatched: 2,
27953
+ idle: 3,
27954
+ standby: 4,
27955
+ active: 5,
27956
+ unreported: 6
27957
+ };
27958
+ function worseThan(a, b) {
27959
+ const sa = STATE_SEVERITY[deriveWebCaptureState(a.status)];
27960
+ const sb = STATE_SEVERITY[deriveWebCaptureState(b.status)];
27961
+ if (sa !== sb) return sa < sb;
27962
+ if (a.observedAt !== b.observedAt) return a.observedAt > b.observedAt;
27963
+ return (a.rootSessionId ?? "") > (b.rootSessionId ?? "");
27964
+ }
27965
+ function reportedCaptureDocumentForSite(documents) {
27966
+ let newestReport = "";
27967
+ for (const d of documents) if (d.lastReportAt > newestReport) newestReport = d.lastReportAt;
27968
+ const retired = (d) => {
27969
+ if (d.closed) return true;
27970
+ const behind = Date.parse(newestReport) - Date.parse(d.lastReportAt);
27971
+ return Number.isFinite(behind) && behind > CAPTURE_STATUS_DOCUMENT_QUIET_MS;
27972
+ };
27973
+ const voting = documents.filter((d) => !retired(d));
27974
+ const tested = voting.some((d) => webCaptureStatusObservedTurnPath(d.status));
27975
+ const pool = tested ? voting : documents;
27976
+ let worst;
27977
+ for (const d of pool) if (worst === void 0 || worseThan(d, worst)) worst = d;
27978
+ return worst;
27979
+ }
27980
+ var STATIC_COPY = {
27981
+ active: { headline: "turns are being observed on this site" },
27982
+ unreported: {
27983
+ // Says "recently" rather than "yet": the store read is bounded to
27984
+ // CAPTURE_STATUS_RECENCY_MS, so this state covers a site nothing has ever
27985
+ // reported for AND one whose last report has aged out. The two are the
27986
+ // same fact to a reader — nobody has confirmed anything lately — and the
27987
+ // copy may not claim the stronger of them.
27988
+ headline: `no report in the last ${String(CAPTURE_STATUS_RECENCY_DAYS)} days \u2014 open the site in Chrome with the extension loaded`
27989
+ },
27990
+ standby: {
27991
+ headline: "this build declares no endpoints for the site, so nothing is observed yet"
27992
+ },
27993
+ unpatched: {
27994
+ // Says what the flags say and no more. `patched` is false both for a tap
27995
+ // that installed and hooked neither transport and for one that never ran
27996
+ // at all — a page reports the same status either way, so the copy may not
27997
+ // assert one of them.
27998
+ headline: "the page tap captured neither fetch nor XHR \u2014 it may not have installed; reload the extension at chrome://extensions"
27999
+ },
28000
+ idle: { headline: "watching; no turn has been observed yet" },
28001
+ blind: {
28002
+ headline: "messages were sent in the page that the network capture never saw",
28003
+ remediation: "reload the tab. If it persists after `aka update` and reloading the extension at chrome://extensions, the site's send path has changed and needs a new extension build."
28004
+ },
28005
+ degraded: {
28006
+ headline: "the site's payloads no longer carry the fields the extension reads",
28007
+ remediation: "run `aka update`, then reload the extension at chrome://extensions. If it stays degraded after an update, the site's contract has changed and needs a new extension build."
28008
+ }
28009
+ };
28010
+ function webCaptureStateCopy(state) {
28011
+ return STATIC_COPY[state];
28012
+ }
28013
+ function webCaptureReport(documents) {
28014
+ return WebSourceTool.options.map((tool) => {
28015
+ const record2 = reportedCaptureDocumentForSite(documents.filter((d) => d.tool === tool));
28016
+ const state = deriveWebCaptureState(record2?.status);
28017
+ const copy = webCaptureStateCopy(state);
28018
+ return {
28019
+ tool,
28020
+ state,
28021
+ headline: copy.headline,
28022
+ ...copy.remediation !== void 0 ? { remediation: copy.remediation } : {},
28023
+ drift: WEB_CAPTURE_DRIFT_STATES.has(state),
28024
+ ...record2 !== void 0 ? { observedAt: record2.observedAt } : {}
28025
+ };
28026
+ });
28027
+ }
28028
+
27718
28029
  // ../packages/detections/src/security/redos-probe.ts
27719
28030
  var BUDGET_MS = 100;
27720
28031
  var EXPONENTIAL_UNITS = [
@@ -27900,17 +28211,6 @@ function publishByLink(tmp, file2, data) {
27900
28211
  function controlPlaneCredentialPath(settingsDir2) {
27901
28212
  return join5(settingsDir2, ATTACHED_CREDENTIAL_FILENAME);
27902
28213
  }
27903
- var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1", "[::1]"]);
27904
- function isSafeEndpoint(endpoint) {
27905
- let parsed2;
27906
- try {
27907
- parsed2 = new URL(endpoint);
27908
- } catch {
27909
- return false;
27910
- }
27911
- if (parsed2.protocol === "https:") return true;
27912
- return parsed2.protocol === "http:" && LOOPBACK_HOSTS.has(parsed2.hostname);
27913
- }
27914
28214
  function repairOrRefuseMode(file2) {
27915
28215
  const link = lstatSync2(file2, { throwIfNoEntry: false });
27916
28216
  if (link === void 0) return "absent";
@@ -27969,10 +28269,23 @@ function readControlPlaneCredential(settingsDir2, connection) {
27969
28269
  const read = readControlPlaneCredentialFile(settingsDir2, connection);
27970
28270
  return read.usable ? read.credential : null;
27971
28271
  }
28272
+ function unsafeEndpointRefusal(reason) {
28273
+ switch (reason) {
28274
+ case "unparseable":
28275
+ return "that does not look like a web address.";
28276
+ case "userinfo":
28277
+ return "the address carries a username or password, which must not be stored alongside the credential.";
28278
+ case "query-or-fragment":
28279
+ return "the address must be an origin, optionally with a path, not a query string or fragment.";
28280
+ case "insecure":
28281
+ return "an access key must not be stored for a non-HTTPS endpoint (http is accepted only for a loopback deployment).";
28282
+ }
28283
+ }
27972
28284
  function writeControlPlaneCredential(settingsDir2, credential) {
27973
- if (!isSafeEndpoint(credential.endpoint)) {
28285
+ const reason = unsafeEndpointReason(credential.endpoint);
28286
+ if (reason !== null) {
27974
28287
  throw new Error(
27975
- `refusing to store a control-plane credential for a non-HTTPS endpoint: ${credential.endpoint}`
28288
+ `refusing to store a control-plane credential for ${originOnly(credential.endpoint)}: ` + unsafeEndpointRefusal(reason)
27976
28289
  );
27977
28290
  }
27978
28291
  ensureDataDirSync(settingsDir2);
@@ -29716,7 +30029,7 @@ var SESSION_ROOT = `event_type = 'session'`;
29716
30029
  var HAS_ACTIVITY = `EXISTS (
29717
30030
  SELECT 1 FROM audit_events c
29718
30031
  WHERE c.root_session_id = audit_events.id
29719
- AND c.event_type NOT IN ('hook', 'config_scan'))`;
30032
+ AND c.event_type NOT IN ('hook', 'config_scan', 'capture_status'))`;
29720
30033
  var SqliteActivityRepository = class {
29721
30034
  constructor(db, now = () => Date.now()) {
29722
30035
  this.db = db;
@@ -29743,10 +30056,10 @@ var SqliteActivityRepository = class {
29743
30056
  SELECT id FROM audit_events WHERE event_type = 'session' AND started_at >= ?
29744
30057
  UNION
29745
30058
  SELECT root_session_id FROM audit_events INDEXED BY idx_audit_started_at
29746
- WHERE started_at >= ?
30059
+ WHERE started_at >= ? AND event_type <> 'capture_status'
29747
30060
  UNION
29748
30061
  SELECT root_session_id FROM audit_events INDEXED BY idx_audit_ended_at
29749
- WHERE ended_at >= ?)`,
30062
+ WHERE ended_at >= ? AND event_type <> 'capture_status')`,
29750
30063
  [liveThreshold, liveThreshold, liveThreshold]
29751
30064
  );
29752
30065
  const toolCallsToday = countScalar(
@@ -30153,7 +30466,10 @@ var SqliteAuditEventsRepository = class {
30153
30466
  attributes = excluded.attributes,
30154
30467
  ended_at = excluded.ended_at
30155
30468
  WHERE COALESCE(json_extract(excluded.attributes, '$.output_tokens'), 0)
30156
- > COALESCE(json_extract(audit_events.attributes, '$.output_tokens'), 0)`
30469
+ > COALESCE(json_extract(audit_events.attributes, '$.output_tokens'), 0)
30470
+ OR (json_extract(excluded.attributes, '$.usage_source') IS NOT NULL
30471
+ AND json_extract(excluded.attributes, '$.output_tokens') IS NULL
30472
+ AND excluded.attributes <> audit_events.attributes)`
30157
30473
  );
30158
30474
  this.upsertSessionRootStmt = db.prepare(
30159
30475
  `INSERT OR IGNORE INTO audit_events
@@ -30421,6 +30737,87 @@ var SqliteBodyRetentionRepository = class {
30421
30737
  }
30422
30738
  };
30423
30739
 
30740
+ // ../packages/persistence/src/repositories/capture-status.ts
30741
+ var STATUS_LOOKBACK_ROWS = 128;
30742
+ var SqliteCaptureStatusRepository = class {
30743
+ constructor(db) {
30744
+ this.db = db;
30745
+ this.recentStmt = db.prepare(
30746
+ `SELECT a.started_at AS startedAt,
30747
+ a.attributes AS attributes,
30748
+ a.root_session_id AS rootSessionId
30749
+ FROM audit_events a
30750
+ WHERE a.event_type = 'capture_status'
30751
+ AND a.source_tool = ?
30752
+ AND a.started_at >= ?
30753
+ ORDER BY a.started_at DESC, a.id DESC
30754
+ LIMIT ?`
30755
+ );
30756
+ }
30757
+ db;
30758
+ recentStmt;
30759
+ /**
30760
+ * Every document that reported for a site, in registry order by site, from
30761
+ * the last `CAPTURE_STATUS_RECENCY_MS`.
30762
+ *
30763
+ * SEVERAL per site, not one: a browser is many documents and each reports
30764
+ * for itself, so one row per site is a choice about which of them a user
30765
+ * sees — and the newest is the wrong one, since a healthy tab writing a
30766
+ * fresh report would hide a drifting tab's verdict, which is the whole
30767
+ * reason these rows exist. The pick WITHIN a document is made here (the
30768
+ * unchanged `pickReportedCaptureStatus`, over that document's own rows);
30769
+ * choosing between documents belongs where the state semantics live, and
30770
+ * that is `reportedCaptureDocumentForSite` in `@akasecurity/detections` —
30771
+ * this package may not import it.
30772
+ *
30773
+ * `now` is a required argument rather than a `Date.now()` read, so a caller
30774
+ * that already holds a render instant passes THAT one and a test can drive
30775
+ * the window without moving the wall clock.
30776
+ *
30777
+ * A site whose reports have all aged out contributes nothing, so it derives
30778
+ * to `unreported`. That is the point: nothing but the browser extension ever
30779
+ * writes these rows, so an uninstalled extension's last verdict would
30780
+ * otherwise stand as a live claim for ever with no later report able to
30781
+ * clear it.
30782
+ */
30783
+ latest(now) {
30784
+ const since = now - CAPTURE_STATUS_RECENCY_MS;
30785
+ const documents = [];
30786
+ for (const tool of WebSourceTool.options) {
30787
+ const rows = /* @__PURE__ */ new Map();
30788
+ const lastWord = /* @__PURE__ */ new Map();
30789
+ for (const row of allRows(this.recentStmt, [
30790
+ tool,
30791
+ since,
30792
+ STATUS_LOOKBACK_ROWS
30793
+ ])) {
30794
+ const status = fromCaptureStatusAttributes(parseJsonObject(row.attributes));
30795
+ if (status === null) continue;
30796
+ const record2 = { tool, observedAt: epochMillisToIso(row.startedAt), status };
30797
+ const group = rows.get(row.rootSessionId);
30798
+ if (group === void 0) {
30799
+ rows.set(row.rootSessionId, [record2]);
30800
+ lastWord.set(row.rootSessionId, { at: record2.observedAt, closed: status.closed });
30801
+ } else {
30802
+ group.push(record2);
30803
+ }
30804
+ }
30805
+ for (const [root, candidates] of rows) {
30806
+ const picked = pickReportedCaptureStatus(candidates);
30807
+ const last = lastWord.get(root);
30808
+ if (picked === void 0 || last === void 0) continue;
30809
+ documents.push({
30810
+ ...picked,
30811
+ ...root === null ? {} : { rootSessionId: root },
30812
+ lastReportAt: last.at,
30813
+ closed: last.closed
30814
+ });
30815
+ }
30816
+ }
30817
+ return documents;
30818
+ }
30819
+ };
30820
+
30424
30821
  // ../packages/persistence/src/repositories/classified-data.ts
30425
30822
  var SqliteClassifiedDataRepository = class {
30426
30823
  constructor(db) {
@@ -36300,6 +36697,7 @@ function openAndInitialize(file2, base, skipTags) {
36300
36697
  activity: new SqliteActivityRepository(db),
36301
36698
  sourceProject: new SqliteSourceProjectRepository(db),
36302
36699
  auditEvents: new SqliteAuditEventsRepository(db),
36700
+ captureStatus: new SqliteCaptureStatusRepository(db),
36303
36701
  classifiedData: new SqliteClassifiedDataRepository(db),
36304
36702
  inspectionDefinitions: new SqliteInspectionDefinitionsRepository(db),
36305
36703
  inspectionFindings: new SqliteInspectionFindingsRepository(db),
@@ -36340,6 +36738,7 @@ function openLocalDatabase(dir, options = {}) {
36340
36738
  activity,
36341
36739
  sourceProject,
36342
36740
  auditEvents,
36741
+ captureStatus,
36343
36742
  classifiedData,
36344
36743
  inspectionDefinitions,
36345
36744
  inspectionFindings,
@@ -36557,6 +36956,7 @@ function openLocalDatabase(dir, options = {}) {
36557
36956
  activity,
36558
36957
  sourceProject,
36559
36958
  auditEvents,
36959
+ captureStatus,
36560
36960
  classifiedData,
36561
36961
  inspectionDefinitions,
36562
36962
  inspectionFindings,
@@ -36801,11 +37201,6 @@ function keyStateOf(key) {
36801
37201
  // ../packages/persistence/src/forward-health.ts
36802
37202
  import { readFileSync as readFileSync11 } from "fs";
36803
37203
  import { join as join12 } from "path";
36804
- var FAILURES = /* @__PURE__ */ new Set([
36805
- "unauthorized",
36806
- "forbidden",
36807
- "unreachable"
36808
- ]);
36809
37204
  var BREAKER_COOLDOWN_MS = 3e4;
36810
37205
  function parseForwardHealth(raw, nowMs) {
36811
37206
  try {
@@ -36814,7 +37209,8 @@ function parseForwardHealth(raw, nowMs) {
36814
37209
  const record2 = parsed2;
36815
37210
  const failures = typeof record2.consecutiveFailures === "number" && record2.consecutiveFailures >= 0 ? record2.consecutiveFailures : 0;
36816
37211
  const openedAtMs = typeof record2.openedAtMs === "number" && Number.isFinite(record2.openedAtMs) && record2.openedAtMs <= nowMs ? record2.openedAtMs : null;
36817
- const lastFailure = typeof record2.lastFailure === "string" && FAILURES.has(record2.lastFailure) ? record2.lastFailure : null;
37212
+ const parsedFailure = ControlPlaneFailure.safeParse(record2.lastFailure);
37213
+ const lastFailure = parsedFailure.success ? parsedFailure.data : null;
36818
37214
  return { consecutiveFailures: failures, openedAtMs, lastFailure };
36819
37215
  } catch {
36820
37216
  return null;
@@ -41044,6 +41440,12 @@ var RemoteRequestInvalid = class extends Error {
41044
41440
  }
41045
41441
  cause;
41046
41442
  };
41443
+ var RemoteEndpointRefused = class extends Error {
41444
+ constructor(endpoint) {
41445
+ super(`refusing to talk to an unsafe control-plane endpoint: ${originOnly(endpoint)}`);
41446
+ this.name = "RemoteEndpointRefused";
41447
+ }
41448
+ };
41047
41449
  var RemoteResponseInvalid = class extends Error {
41048
41450
  constructor(route, detail) {
41049
41451
  super(`control plane answered ${route} with ${detail}`);
@@ -41196,14 +41598,15 @@ function parsed(schema, body, route) {
41196
41598
  }
41197
41599
  return result.data;
41198
41600
  }
41199
- function withoutTrailingSlashes(endpoint) {
41601
+ function resolveBaseUrl(endpoint) {
41602
+ if (!isSafeEndpoint(endpoint)) throw new RemoteEndpointRefused(endpoint);
41200
41603
  let end = endpoint.length;
41201
41604
  while (end > 0 && endpoint.charCodeAt(end - 1) === SLASH2) end -= 1;
41202
41605
  return endpoint.slice(0, end);
41203
41606
  }
41204
41607
  var SLASH2 = "/".charCodeAt(0);
41205
41608
  function createRemoteClient(options) {
41206
- const base = withoutTrailingSlashes(options.endpoint);
41609
+ const base = resolveBaseUrl(options.endpoint);
41207
41610
  const url2 = (route) => `${base}${route}`;
41208
41611
  const common = { apiKey: options.apiKey, timeoutMs: options.timeoutMs };
41209
41612
  const sendOne = async (event) => {
@@ -41321,7 +41724,7 @@ var ATTACH_ROUTES = {
41321
41724
  token: "/v1/attach/token"
41322
41725
  };
41323
41726
  function createAttachClient(options) {
41324
- const base = withoutTrailingSlashes(options.endpoint);
41727
+ const base = resolveBaseUrl(options.endpoint);
41325
41728
  const common = { timeoutMs: options.timeoutMs };
41326
41729
  return {
41327
41730
  notOfferedStatus: 404,
@@ -41362,6 +41765,7 @@ function classifyRemoteFailure(err) {
41362
41765
  case "RemoteRouteAbsent":
41363
41766
  return "route-absent";
41364
41767
  case "RemoteRequestInvalid":
41768
+ case "RemoteEndpointRefused":
41365
41769
  return "invalid-request";
41366
41770
  case "RemoteResponseInvalid":
41367
41771
  return "rejected";
@@ -41451,7 +41855,16 @@ function rebuildCapture(row) {
41451
41855
  const metadata = {
41452
41856
  ...rootSessionId === null ? {} : { sessionId: rootSessionId },
41453
41857
  ...filePath === void 0 ? {} : { filePath },
41454
- ...pick2(attributes, { repo: "repo", toolName: "tool_name", model: "model" }),
41858
+ ...pick2(attributes, {
41859
+ repo: "repo",
41860
+ toolName: "tool_name",
41861
+ model: "model",
41862
+ // The join back to the llm_call leaf for the same web-chat turn.
41863
+ // Stripping these would forward a response capture the deployment can
41864
+ // never correlate to the llm_call leaf describing the same turn.
41865
+ messageId: "message_id",
41866
+ conversationId: "conversation_id"
41867
+ }),
41455
41868
  // The two constrained strings, kept only if they satisfy the wire.
41456
41869
  ...withField("traceId", keep(stringOrUndefined(attributes.trace_id), TRACE_ID)),
41457
41870
  ...withField(
@@ -42099,10 +42512,15 @@ import { join as join32 } from "path";
42099
42512
  // ../packages/plugin-runtime/src/attached/atomic-publish.ts
42100
42513
  import { rename as rename2 } from "fs/promises";
42101
42514
  var RETRYABLE = /* @__PURE__ */ new Set(["EPERM", "EACCES", "EBUSY"]);
42102
- var ATTEMPTS = 5;
42515
+ var IMMEDIATE_RETRIES = 8;
42516
+ var TIMED_RETRIES = 4;
42517
+ var ATTEMPTS = 1 + IMMEDIATE_RETRIES + TIMED_RETRIES;
42103
42518
  var delay = (ms) => new Promise((resolve7) => {
42104
42519
  setTimeout(resolve7, ms);
42105
42520
  });
42521
+ var yieldToLoop = () => new Promise((resolve7) => {
42522
+ setImmediate(resolve7);
42523
+ });
42106
42524
  async function publishByRename(tmp, file2, move = rename2) {
42107
42525
  for (let attempt = 1; ; attempt += 1) {
42108
42526
  try {
@@ -42111,7 +42529,7 @@ async function publishByRename(tmp, file2, move = rename2) {
42111
42529
  } catch (err) {
42112
42530
  const code = err.code;
42113
42531
  if (attempt >= ATTEMPTS || code === void 0 || !RETRYABLE.has(code)) throw err;
42114
- await delay(attempt * 10);
42532
+ await (attempt <= IMMEDIATE_RETRIES ? yieldToLoop() : delay((attempt - IMMEDIATE_RETRIES) * 10));
42115
42533
  }
42116
42534
  }
42117
42535
  }
@@ -42854,6 +43272,18 @@ function parseAttachArgs(argv) {
42854
43272
  var CONTROL_CHARS = /[\p{Cc}\p{Cf}]/u;
42855
43273
  var MUTUALLY_EXCLUSIVE = "--sync-history and --no-sync-history are mutually exclusive";
42856
43274
  var isError = (v) => "error" in v;
43275
+ function unsafeEndpointMessage(reason) {
43276
+ switch (reason) {
43277
+ case "unparseable":
43278
+ return "that does not look like a web address; include the scheme, as in https://aka.example.com.";
43279
+ case "userinfo":
43280
+ return "the address carries a username or password; remove them, the access key is sent separately.";
43281
+ case "query-or-fragment":
43282
+ return "the address must be an origin, optionally with a path; drop the query string or fragment.";
43283
+ case "insecure":
43284
+ return "an access key must not travel in the clear. Use an https URL (http is accepted only for a loopback deployment).";
43285
+ }
43286
+ }
42857
43287
  async function runAttach(argv, deps = {}) {
42858
43288
  const io = deps.prompter ?? terminalPrompter();
42859
43289
  const exit = deps.exit ?? ((code) => process.exit(code));
@@ -42872,10 +43302,9 @@ ${USAGE}`);
42872
43302
  exit(2);
42873
43303
  return;
42874
43304
  }
42875
- if (!isSafeEndpoint(endpoint)) {
42876
- io.err(
42877
- `refusing to attach to ${endpoint}: an access key must not travel in the clear. Use an https URL (http is accepted only for a loopback deployment).`
42878
- );
43305
+ const unsafeReason = unsafeEndpointReason(endpoint);
43306
+ if (unsafeReason !== null) {
43307
+ io.err(`refusing to attach to ${originOnly(endpoint)}: ${unsafeEndpointMessage(unsafeReason)}`);
42879
43308
  exit(2);
42880
43309
  return;
42881
43310
  }
@@ -44512,6 +44941,7 @@ import { createRequire as createRequire3 } from "module";
44512
44941
  import { homedir as homedir8 } from "os";
44513
44942
  import { dirname as dirname10, join as join36 } from "path";
44514
44943
  import { fileURLToPath as fileURLToPath7 } from "url";
44944
+ import { parseArgs as parseArgs5 } from "util";
44515
44945
  var require4 = createRequire3(import.meta.url);
44516
44946
  var here2 = dirname10(fileURLToPath7(import.meta.url));
44517
44947
  function cliRoot() {
@@ -44625,13 +45055,18 @@ function registerWindowsHost(manifestPath) {
44625
45055
  ]);
44626
45056
  }
44627
45057
  function runExtension(argv) {
44628
- const sub = argv[0];
45058
+ const { values, positionals } = parseArgs5({
45059
+ args: argv,
45060
+ options: HOME_OPTION,
45061
+ allowPositionals: true
45062
+ });
45063
+ const sub = positionals[0];
44629
45064
  if (sub === "install") {
44630
45065
  runInstall(chromeManifestDir());
44631
45066
  return;
44632
45067
  }
44633
45068
  if (sub === "status") {
44634
- runStatus2(chromeManifestDir());
45069
+ runStatus2(chromeManifestDir(), homeBase(values.home));
44635
45070
  return;
44636
45071
  }
44637
45072
  process.stderr.write(
@@ -44705,7 +45140,40 @@ function manifestFault(manifestPath) {
44705
45140
  if (missing.length === 0) return null;
44706
45141
  return `does not grant ${missing.length === 1 ? "the extension id" : "the extension ids"} ${missing.join(", ")}`;
44707
45142
  }
44708
- function runStatus2(manifestDir) {
45143
+ function captureBlock(home) {
45144
+ try {
45145
+ const effective = readEffectiveSettings(home);
45146
+ const webChat = webChatCaptureOf(effective.settings);
45147
+ if (!isWebChatCaptureConsentValid(webChat.consent)) {
45148
+ return "\nnetwork capture: not enabled\n no web-chat capture consent is recorded, so nothing is observed or stored\n enable it under Settings in `aka dashboard`\n";
45149
+ }
45150
+ const db = openLocalDatabase(dataDir(home));
45151
+ let records;
45152
+ try {
45153
+ records = db.captureStatus.latest(Date.now());
45154
+ } finally {
45155
+ db.close();
45156
+ }
45157
+ const lines = webCaptureReport(records).map((site) => {
45158
+ let line = ` ${site.tool.padEnd(10)} ${site.state.padEnd(10)} ${site.headline}
45159
+ `;
45160
+ if (site.drift && site.remediation !== void 0) {
45161
+ line += ` ${WEB_CAPTURE_DRIFT_RULE.ruleId} (${WEB_CAPTURE_DRIFT_RULE.severity}) \u2014 ${site.remediation}
45162
+ `;
45163
+ }
45164
+ return line;
45165
+ });
45166
+ return `
45167
+ network capture
45168
+ ${lines.join("")}`;
45169
+ } catch (err) {
45170
+ return `
45171
+ network capture: unavailable
45172
+ the local store could not be read (${err instanceof Error ? err.message : String(err)})
45173
+ `;
45174
+ }
45175
+ }
45176
+ function runStatus2(manifestDir, home) {
44709
45177
  const manifestPath = join36(manifestDir, `${NATIVE_HOST_NAME}.json`);
44710
45178
  if (!existsSync18(manifestPath)) {
44711
45179
  process.stdout.write(
@@ -44714,29 +45182,32 @@ function runStatus2(manifestDir) {
44714
45182
  run \`aka extension install\` to set it up
44715
45183
  `
44716
45184
  );
44717
- return;
44718
- }
44719
- const fault = manifestFault(manifestPath);
44720
- process.stdout.write(
44721
- `native-messaging host: ${fault ? "installed (out of date)" : "installed"}
45185
+ } else {
45186
+ const fault = manifestFault(manifestPath);
45187
+ process.stdout.write(
45188
+ `native-messaging host: ${fault ? "installed (out of date)" : "installed"}
44722
45189
  manifest: ${manifestPath}
44723
45190
  ` + (fault ? ` this manifest ${fault}
44724
45191
  Chrome refuses a connection from any origin the manifest omits \u2014
44725
45192
  re-run \`aka extension install\` to rewrite it
44726
45193
  ` : "")
44727
- );
44728
- if (fault) process.exitCode = 1;
45194
+ );
45195
+ if (fault) process.exitCode = 1;
45196
+ }
45197
+ if (home !== void 0) {
45198
+ process.stdout.write(captureBlock(home));
45199
+ }
44729
45200
  }
44730
45201
 
44731
45202
  // src/commands/init.ts
44732
45203
  import { existsSync as existsSync20, lstatSync as lstatSync4, readdirSync as readdirSync7, statSync as statSync13 } from "fs";
44733
45204
  import { join as join37 } from "path";
44734
45205
  import * as readline from "readline/promises";
44735
- import { parseArgs as parseArgs6 } from "util";
45206
+ import { parseArgs as parseArgs7 } from "util";
44736
45207
 
44737
45208
  // src/commands/plugins.ts
44738
45209
  import { existsSync as existsSync19 } from "fs";
44739
- import { parseArgs as parseArgs5 } from "util";
45210
+ import { parseArgs as parseArgs6 } from "util";
44740
45211
  function runPlugins(argv, deps = {}) {
44741
45212
  const [sub, ...rest] = argv;
44742
45213
  if (!sub || sub === "list") return listPlugins(rest);
@@ -44746,7 +45217,7 @@ function runPlugins(argv, deps = {}) {
44746
45217
  process.exitCode = 1;
44747
45218
  }
44748
45219
  async function listPlugins(argv) {
44749
- const { values } = parseArgs5({ args: argv, options: HOME_OPTION });
45220
+ const { values } = parseArgs6({ args: argv, options: HOME_OPTION });
44750
45221
  const home = homeBase(values.home);
44751
45222
  const active = /* @__PURE__ */ new Set();
44752
45223
  if (existsSync19(dbPath(home))) {
@@ -44774,7 +45245,7 @@ async function listPlugins(argv) {
44774
45245
  out.write("Update: aka update (or: aka check-updates)\n");
44775
45246
  }
44776
45247
  async function installPlugin(argv, deps) {
44777
- const { positionals } = parseArgs5({ args: argv, options: HOME_OPTION, allowPositionals: true });
45248
+ const { positionals } = parseArgs6({ args: argv, options: HOME_OPTION, allowPositionals: true });
44778
45249
  const id = positionals[0];
44779
45250
  if (!id) {
44780
45251
  process.stderr.write("aka plugins install: missing <agent> (try: aka plugins list)\n");
@@ -44939,7 +45410,7 @@ function existsAsNonDirectory(path) {
44939
45410
  }
44940
45411
  }
44941
45412
  async function runInit(argv) {
44942
- const { values } = parseArgs6({
45413
+ const { values } = parseArgs7({
44943
45414
  args: argv,
44944
45415
  options: { ...HOME_OPTION, yes: { type: "boolean", short: "y" } }
44945
45416
  });
@@ -45036,7 +45507,7 @@ async function confirm(question) {
45036
45507
  }
45037
45508
 
45038
45509
  // src/commands/prune.ts
45039
- import { parseArgs as parseArgs7 } from "util";
45510
+ import { parseArgs as parseArgs8 } from "util";
45040
45511
  var USAGE2 = `Usage: aka prune [--dry-run] [--days <n>]
45041
45512
 
45042
45513
  Expire captured bodies past the retention horizon. Keeps every event row and
@@ -45062,7 +45533,7 @@ function fmtBytes(n) {
45062
45533
  function runPrune(argv, io = terminalPrompter(), managedOverride) {
45063
45534
  let values;
45064
45535
  try {
45065
- ({ values } = parseArgs7({
45536
+ ({ values } = parseArgs8({
45066
45537
  args: argv,
45067
45538
  options: {
45068
45539
  ...HOME_OPTION,
@@ -45137,7 +45608,7 @@ function runPrune(argv, io = terminalPrompter(), managedOverride) {
45137
45608
 
45138
45609
  // src/commands/scan.ts
45139
45610
  import { statSync as statSync14 } from "fs";
45140
- import { parseArgs as parseArgs8 } from "util";
45611
+ import { parseArgs as parseArgs9 } from "util";
45141
45612
  function severityRank2(s) {
45142
45613
  return Severity.options.indexOf(s);
45143
45614
  }
@@ -45166,7 +45637,7 @@ function renderForwardLine(outcome) {
45166
45637
  }
45167
45638
  }
45168
45639
  async function runScan(argv, deps = {}) {
45169
- const { values, positionals } = parseArgs8({
45640
+ const { values, positionals } = parseArgs9({
45170
45641
  args: argv,
45171
45642
  options: {
45172
45643
  ...HOME_OPTION,
@@ -45314,10 +45785,10 @@ async function runScan(argv, deps = {}) {
45314
45785
  }
45315
45786
 
45316
45787
  // src/commands/stats.ts
45317
- import { parseArgs as parseArgs9 } from "util";
45788
+ import { parseArgs as parseArgs10 } from "util";
45318
45789
  var DAY_MS6 = 864e5;
45319
45790
  async function runStats(argv) {
45320
- const { values } = parseArgs9({
45791
+ const { values } = parseArgs10({
45321
45792
  args: argv,
45322
45793
  options: { ...HOME_OPTION, range: { type: "string" } }
45323
45794
  });
@@ -45419,7 +45890,7 @@ function renderTokenUsage(summary, range) {
45419
45890
  }
45420
45891
 
45421
45892
  // src/commands/sync-history.ts
45422
- import { parseArgs as parseArgs10 } from "util";
45893
+ import { parseArgs as parseArgs11 } from "util";
45423
45894
  var USAGE3 = `Usage: aka sync-history [--on | --off] [--run]
45424
45895
 
45425
45896
  Whether this machine may send the activity it recorded before attaching.
@@ -45437,7 +45908,7 @@ async function runSyncHistory(argv, deps = {}) {
45437
45908
  const exit = deps.exit ?? ((code) => process.exit(code));
45438
45909
  let values;
45439
45910
  try {
45440
- ({ values } = parseArgs10({
45911
+ ({ values } = parseArgs11({
45441
45912
  args: argv,
45442
45913
  options: {
45443
45914
  ...HOME_OPTION,
@@ -45566,7 +46037,7 @@ applies. Run \`aka sync-history --on\` to grant it again.`;
45566
46037
  }
45567
46038
 
45568
46039
  // src/commands/tui.tsx
45569
- import { parseArgs as parseArgs11 } from "util";
46040
+ import { parseArgs as parseArgs12 } from "util";
45570
46041
  import { render } from "ink";
45571
46042
 
45572
46043
  // src/tui/Dashboard.tsx
@@ -46076,7 +46547,7 @@ function parseView(positionals) {
46076
46547
  return VIEWS2.includes(first) ? first : "health";
46077
46548
  }
46078
46549
  async function runTui(argv) {
46079
- const { values, positionals } = parseArgs11({
46550
+ const { values, positionals } = parseArgs12({
46080
46551
  args: argv,
46081
46552
  options: HOME_OPTION,
46082
46553
  allowPositionals: true
@@ -46120,7 +46591,7 @@ async function runTui(argv) {
46120
46591
 
46121
46592
  // src/commands/update.ts
46122
46593
  import * as readline2 from "readline/promises";
46123
- import { parseArgs as parseArgs12 } from "util";
46594
+ import { parseArgs as parseArgs13 } from "util";
46124
46595
 
46125
46596
  // src/lib/install-origin.ts
46126
46597
  import { dirname as dirname11 } from "path";
@@ -46135,7 +46606,7 @@ function cliInstallOrigin() {
46135
46606
 
46136
46607
  // src/commands/update.ts
46137
46608
  async function runUpdate(argv) {
46138
- const { values, positionals } = parseArgs12({
46609
+ const { values, positionals } = parseArgs13({
46139
46610
  args: argv,
46140
46611
  options: { ...HOME_OPTION, yes: { type: "boolean", short: "y" } },
46141
46612
  allowPositionals: true
@@ -46268,11 +46739,11 @@ function applyPluginUpdate2(status) {
46268
46739
  }
46269
46740
 
46270
46741
  // src/commands/vault.ts
46271
- import { parseArgs as parseArgs14 } from "util";
46742
+ import { parseArgs as parseArgs15 } from "util";
46272
46743
 
46273
46744
  // src/commands/vault-prune.ts
46274
46745
  import { randomUUID as randomUUID21 } from "crypto";
46275
- import { parseArgs as parseArgs13 } from "util";
46746
+ import { parseArgs as parseArgs14 } from "util";
46276
46747
 
46277
46748
  // src/lib/transcript-rewrite.ts
46278
46749
  import {
@@ -46540,7 +47011,7 @@ function plural(count2, one, many) {
46540
47011
  return `${String(count2)} ${count2 === 1 ? one : many}`;
46541
47012
  }
46542
47013
  async function runPrune2(argv, io) {
46543
- const { values } = parseArgs13({
47014
+ const { values } = parseArgs14({
46544
47015
  args: argv,
46545
47016
  options: {
46546
47017
  ...HOME_OPTION,
@@ -46768,7 +47239,7 @@ function descriptorLine(d) {
46768
47239
  return `${kind} \xB7 seen ${String(d.occurrences)}\xD7 \xB7 first ${d.firstSeen.slice(0, 10)}`;
46769
47240
  }
46770
47241
  async function runShow2(argv, io) {
46771
- const { values, positionals } = parseArgs14({
47242
+ const { values, positionals } = parseArgs15({
46772
47243
  args: argv,
46773
47244
  options: { ...HOME_OPTION, help: { type: "boolean", short: "h" } },
46774
47245
  allowPositionals: true