Commit Graph

11445 Commits

Author SHA1 Message Date
Alem Tuzlak 5de0e5fae3 chore(bot-google-chat): pre-push formatting/lint fixes 2026-06-18 12:16:34 +02:00
Alem Tuzlak 78a88ce6cb fix(bot-google-chat): resolve whitespace-only interrupt placeholder, close tool-start interrupt race, dedupe cold-cache cert fetch 2026-06-18 12:08:27 +02:00
Alem Tuzlak 9c080079ac fix(bot-google-chat): de-dupe context italic tags; convert markdown on non-streamed text path 2026-06-18 12:04:23 +02:00
Alem Tuzlak 1d8b9b99bb chore(bot-google-chat): export CertFetchError, exclude tests from published dist, harden README mount example 2026-06-17 19:30:22 +02:00
Alem Tuzlak f979e94784 fix(bot-google-chat): extract links before emphasis passes so URLs aren't corrupted; handle pipe/gt in chat links 2026-06-17 19:26:13 +02:00
Alem Tuzlak 9dc279446b fix(bot-google-chat): log handler 500s, value-less interaction is undefined, getMessages skips empty, reconcile chunked-stream docs 2026-06-17 19:07:08 +02:00
Alem Tuzlak 5150234ffc fix(bot-google-chat): card-wide unique button fallback ids, close dangling anchor on truncate, allow balanced parens in link URLs 2026-06-17 19:02:41 +02:00
Alem Tuzlak 3aa8b80b82 fix(bot-google-chat): honor GOOGLE_CHAT_CREDENTIALS path from env; don't burn cert-refetch window on failed fetch 2026-06-17 18:57:04 +02:00
Alem Tuzlak bec7d3c231 fix(bot-google-chat): shared isBotSender predicate so getMessages and translate agree on bot detection 2026-06-17 18:38:51 +02:00
Alem Tuzlak 4403e1d571 fix(bot-google-chat): surface cert-fetch failures as 500 not 401; bound concurrent cert refetch with in-flight guard 2026-06-17 18:34:46 +02:00
Alem Tuzlak 34b9a28ff0 fix(bot-google-chat): allowlist link schemes, strip sentinel bytes from input, budget card HTML post-conversion, sanitize button fallback id 2026-06-17 18:31:03 +02:00
Alem Tuzlak 8ae0ba2fda fix(bot-google-chat): retry streamed-edit flush and only mark posted on success (don't lose final answer) 2026-06-17 18:23:11 +02:00
Alem Tuzlak 42814df35e fix(bot-google-chat): filter interrupted tool-status rows from history; single source of truth for status markers 2026-06-17 18:10:28 +02:00
Alem Tuzlak 12a0782d95 fix(bot-google-chat): collision-proof code placeholder in markdownToCardHtml (\x10 sentinel); drop false-coverage probe test 2026-06-17 17:38:59 +02:00
Alem Tuzlak 9b0b90d4a7 fix(bot-google-chat): balance fences across chunk boundaries so split code blocks render valid 2026-06-17 16:55:57 +02:00
Alem Tuzlak 43638c7a52 fix(bot-google-chat): debounce JWT cert refetch to prevent bad-token amplification 2026-06-17 16:41:08 +02:00
Alem Tuzlak bcf2d24610 fix(bot-google-chat): skip url-less image widgets; convert card text to Chat HTML; omit empty button value 2026-06-17 16:27:59 +02:00
Alem Tuzlak d9b15298f1 fix(bot-google-chat): markInterrupted no longer posts spurious empty messages; resolve dangling tool rows; correct 429 comment 2026-06-17 16:14:52 +02:00
Alem Tuzlak e361f42878 fix(bot-google-chat): headings containing bold no longer produce unbalanced asterisks 2026-06-17 15:59:44 +02:00
Alem Tuzlak 4419e708bd test(bot-google-chat): direct ChunkedMessageStream + budget unit tests 2026-06-17 15:12:05 +02:00
Alem Tuzlak 813c9b99d9 docs(bot-google-chat): correct JWT method + project-number framing; drop dead ADC option; comment markdown sentinels 2026-06-17 15:05:59 +02:00
Alem Tuzlak 2966848adb fix(bot-google-chat): resolve interrupted placeholders; fail loud on empty message name; strip query from RequestInit 2026-06-17 14:54:47 +02:00
Alem Tuzlak 05af621692 fix(bot-google-chat): filter bot status/placeholder rows in getMessages to match conversation-store 2026-06-17 14:48:04 +02:00
Alem Tuzlak 67f4125f9a fix(bot-google-chat): surface cardsV2 widget overflow; uniquify button function-id fallback 2026-06-17 14:41:27 +02:00
Alem Tuzlak d3177cdf46 fix(bot-google-chat): self-heal JWT cert cache on rotation (refetch + retry once) 2026-06-17 14:36:24 +02:00
Alem Tuzlak 203c203b4e fix(bot-google-chat): scope conversation history + getMessages to the thread; thread postFile uploads 2026-06-17 14:27:56 +02:00
Alem Tuzlak 4d50f1ed25 fix(bot-google-chat): listMessages thread filter + recency ordering; thread uploadAttachment
listMessages now requests the most-recent N messages via
orderBy=createTime desc (Chat defaults to ASC, which returned the
OLDEST page and never the recent turns), then reverses the page so the
result is chronological (oldest->newest) while containing the latest
turns. New signature: listMessages(space, opts?: { pageSize?; threadName? }).
When threadName is set, a Chat `filter` query param scopes the listing to
thread.name = "<threadName>" (URL-encoded by searchParams), fixing
cross-thread history leakage. Absent threadName = whole-space/DM scope.

uploadAttachment now accepts opts?: { threadName? } and posts the
attachment message into the thread by delegating step 2 to
createMessage(space, { attachment: [...] }, { threadName, replyToThread: true }),
reusing the existing threading mechanism (thread + messageReplyOption)
instead of duplicating the POST. Previously files posted as top-level
space messages. Still returns { ok, fileId?, error? }.

Both opts are optional with backward-compatible defaults. Existing
old-signature callers compile unchanged and are left for follow-on agents:
- adapter.ts:264  this.chatClient.listMessages(t.space)
- adapter.ts:286  this.chatClient.uploadAttachment(t.space, args.bytes, args.filename)
- conversation-store.ts:41  this.client.listMessages(key.spaceId)

Tests: updated/added in chat-client.test.ts (listMessages with/without
threadName ordering+filter assertions; uploadAttachment threading
assertion). All 62 tests pass; tsc (tsconfig + tsconfig.check) and build
clean.
2026-06-17 14:22:43 +02:00
Alem Tuzlak 811f293b87 fix(bot-google-chat): listener text fallback, interaction value param, error logging, tool-status mask 2026-06-17 13:58:07 +02:00
Alem Tuzlak 6afcc2b3e8 fix(bot-google-chat): don't ship non-functional tagging by default; document v1 limitation 2026-06-17 13:53:16 +02:00
Alem Tuzlak ab29fa44d7 fix(bot-google-chat): streaming edits patch text-only mask to avoid clearing cards
Google Chat clears any field named in updateMask but absent from the request
body. The edit-in-place streaming path patches a message repeatedly with a
text-only body ({ text }) while passing mask text,cardsV2, which would blank
any cardsV2 payload on the edited message.

Changed (streaming, text-only body → mask text):
- event-renderer.ts ChunkedMessageStream updateAt closure (patchMessage(name, { text }, text))
- adapter.ts stream() ChunkedMessageStream updateAt closure (patchMessage(name, { text }, text))

Left unchanged (correct):
- adapter.ts update() — sends a full renderGoogleChatMessage(ir) body that may
  legitimately include cardsV2, so text,cardsV2 is correct.
- event-renderer.ts onToolCallEndEvent tool-status patch — out of scope for this
  finding (not a ChunkedMessageStream updateAt closure).
2026-06-17 13:47:23 +02:00
Alem Tuzlak ba290caa9e fix(bot-google-chat): cap webhook request body size to prevent pre-auth memory exhaustion 2026-06-17 13:42:49 +02:00
Alem Tuzlak d8a121ebfd fix(bot-google-chat): collision-proof code placeholder in markdownToChat 2026-06-17 13:37:50 +02:00
Alem Tuzlak e4566e3096 fix(bot-google-chat): valid cardsV2 — decoratedText text, drop empty-widget sections
Three Cards v2 validity fixes in render/cards-v2.ts (renderGoogleChatMessage):

- renderNodeWidgets "fields"/"field" cases: emit decoratedText.text instead of
  decoratedText.topLabel. Cards v2 REQUIRES text on decoratedText; topLabel is
  an optional adornment above it. Budget truncation now applied to text.
- renderGoogleChatMessage: when clamped widgets are empty and there is no
  header, return a plain text body instead of an invalid empty-section card.
  The section is now only attached when widgets are non-empty, so a header-only
  card never emits widgets: [].
- renderGoogleChatMessage: let widgets -> const widgets (never reassigned;
  prefer-const).

Tests (render/cards-v2.test.ts): added coverage that a field renders a
decoratedText whose text carries the content, and that non-plain-text IR
producing zero widgets returns a plain text body rather than an empty section.
Existing tests unchanged.
2026-06-17 13:31:01 +02:00
Alem Tuzlak 2144f12c8e fix(bot-google-chat): real Chat media upload, Blob typing, fail-loud on missing message name
- uploadAttachment now implements the real two-step Google Chat media upload:
  (1) POST bytes to {upload base}/{space}/attachments:upload?uploadType=multipart
      (the /upload/v1 base, derived from apiUrl by swapping the /v1 suffix),
  (2) POST /v1/{space}/messages with { attachment: [{ attachmentDataRef }] }.
  Returns { ok: true, fileId } (resourceName/attachmentUploadToken) so the
  postFile contract's fileId? is populated; { ok: false, error } on failure.
- Construct upload Blob via new Uint8Array(bytes) so the part is a concrete
  ArrayBuffer-backed BlobPart (fixes TS2322 under tsconfig.check.json / lib dom).
- createMessage now throws "google-chat createMessage: response missing message
  name" on a 2xx response lacking a name, instead of returning { name: "" }
  (which silently corrupted MessageRef.id and downstream patch/delete URLs).

Affected call sites:
- createMessage callers: adapter.ts postMessage/reply paths consuming { name }.
- uploadAttachment callers: adapter.ts postFile (BotReplyTarget) now receives
  fileId on success.
2026-06-17 12:21:15 +02:00
Alem Tuzlak a7a20f0c06 fix(bot-google-chat): verify inbound JWT against Chat system x509 certs
verifyIdToken silently dropped the certsUrl option (cast away), so inbound
Chat webhook JWTs were validated against Google's federated OIDC certs
instead of the chat@system.gserviceaccount.com x509 certs that actually
sign them, returning 401 to all legitimate traffic.

Now fetch the Chat system x509 cert map (cached in closure scope) and call
OAuth2Client.verifySignedJwtWithCertsAsync(token, certs, audience,
[CHAT_ISSUER]), which validates signature, audience, and issuer in one call.
Removed the unsupported certsUrl option and the as Parameters<> cast; the
separate iss check is now redundant. resolveCredentials JSON.parse now
rethrows with a clear message on invalid GOOGLE_CHAT_CREDENTIALS.

Call sites checked for the changed auth.ts symbols:
- createInboundVerifier: adapter.ts:67 (GoogleChatAdapter ctor), re-exported
  from index.ts:21; produced InboundVerifier consumed in server.ts:10/adapter.ts:43.
- resolveCredentials: internal to auth.ts, called only by createTokenProvider:39.
- UnauthorizedError / InboundVerifier: unchanged public surface, exercised by
  auth.test.ts and server.ts.

Test rewritten to mock verifySignedJwtWithCertsAsync and stub the cert fetch.
2026-06-17 12:16:27 +02:00
Alem Tuzlak 1ab2e6f72b refactor(bot-google-chat): status-row filter, verifier field, drop unused streaming option 2026-06-17 11:56:21 +02:00
Alem Tuzlak b75657834b docs(bot-google-chat): README + ARCHITECTURE 2026-06-17 11:51:54 +02:00
Alem Tuzlak db8efcdcf8 feat(bot-google-chat): public exports 2026-06-17 11:47:25 +02:00
Alem Tuzlak 2d6179bc5b feat(bot-google-chat): GoogleChatAdapter + googleChat() factory 2026-06-17 11:38:32 +02:00
Alem Tuzlak 019a39d798 feat(bot-google-chat): run renderer (edit-in-place streaming) 2026-06-17 11:27:55 +02:00
Alem Tuzlak b9370167a4 feat(bot-google-chat): conversation store (history from Chat API) 2026-06-17 11:27:08 +02:00
Alem Tuzlak f6e74c8bce feat(bot-google-chat): HTTP server + request handler 2026-06-17 11:26:35 +02:00
Alem Tuzlak dc9263f162 fix(bot-google-chat): wave 2 test type + mock-reset fixes 2026-06-17 11:18:59 +02:00
Alem Tuzlak 44167d5ce3 feat(bot-google-chat): cardsV2 renderer 2026-06-17 11:07:24 +02:00
Alem Tuzlak 02e013626c feat(bot-google-chat): webhook event routing 2026-06-17 11:05:01 +02:00
Alem Tuzlak a3f2c12322 feat(bot-google-chat): Chat REST client 2026-06-17 11:03:48 +02:00
Alem Tuzlak a12b944dc6 feat(bot-google-chat): CARD_CLICKED interaction decoding 2026-06-17 11:01:32 +02:00
Alem Tuzlak 75b3aef2ca feat(bot-google-chat): built-in tools 2026-06-17 11:01:06 +02:00
Alem Tuzlak dfb0543038 feat(bot-google-chat): service-account token + inbound JWT verify 2026-06-17 11:00:09 +02:00
Alem Tuzlak 7e3edbde66 feat(bot-google-chat): markdown→chat translation 2026-06-17 10:51:44 +02:00