Compare commits

...

10 Commits

18 changed files with 914 additions and 63 deletions
+19
View File
@@ -190,6 +190,25 @@ Handoff rule:
- relevant files / ids / commands
- what counts as success for the next pass
Subagent drift / stuck rule:
- if a fresh implementation subagent is no longer making crisp progress, inspect before waiting longer
- monitoring cadence for fresh implementation runs:
- do not routine-poll in the first 5 minutes unless the task is very small or something already looks wrong
- at ~5 minutes, if the run is still active, do one lightweight status check
- at ~10 minutes, if still active, inspect the child session/history once for concrete evidence of edits/tests/commits
- treat these as intervention triggers:
- the run is still active after a reasonable window for the task and has not updated `WIP.md`
- the run is looping on broad reads/re-verification without landing state updates or commits
- the completion result is unusable, missing evidence, or obviously unrelated to the assigned pass
- concrete time thresholds:
- narrow/scoped pass (single docs/config/script task): suspiciously long at ~12 minutes, intervene by ~15 minutes unless recent inspection shows crisp progress
- medium implementation pass (like one bounded feature slice): suspiciously long at ~20 minutes, intervene by ~25 minutes unless recent inspection shows crisp progress
- when triggered:
1. inspect the subagent session/history once
2. if meaningful progress is still happening, let it finish and re-check in 5-10 minutes instead of hovering
3. otherwise kill the run, verify the workspace directly, finish the pass in the main session, and update `WIP.md` yourself
4. record the behavior in memory if it reveals a repeatable failure mode
Delegation helper:
- Use `skills/delegation-router/SKILL.md` as the local quick policy for choosing direct vs subagent vs ACP and selecting model tier.
+28 -10
View File
@@ -4,12 +4,13 @@
Immediate baton-pass for the next fresh implementation session.
## Current objective
Run calendar pass (2/3): extend the action bus beyond create-only calendar support now that Gmail pass 1 is complete.
The Gmail + Calendar n8n action-bus WIP is complete and live. Next fresh session should review `WIP.drive-docs-sheets.md` and decide whether Drive / Docs / Sheets need action-bus verbs at all, while preserving the approval/history contract that now exists for Gmail + Calendar.
## Use these state files first
1. `WIP.md`full standing plan and checkpoints
2. `memory/2026-03-12.md` — detailed execution history and evidence
3. `memory/tasks.json` — task status tracking
1. `WIP.md`completed Google Workspace + n8n implementation record
2. `WIP.drive-docs-sheets.md` — proposed next-phase decision WIP
3. `memory/2026-03-12.md` — detailed execution history and evidence
4. `memory/tasks.json` — task status tracking
## What is already true
- `openclaw-action` is live in n8n and active.
@@ -44,15 +45,30 @@ Run calendar pass (2/3): extend the action bus beyond create-only calendar suppo
- Added explicit approval metadata in workflow responses (`approval.policy`, `approval.required`, `approval.mutation_level`).
- Updated docs/test payloads/validator to match the expanded Gmail contract.
## Calendar pass 2 completed in this handoff cycle
- Added workflow actions:
- `list_upcoming_events`
- `update_calendar_event`
- `delete_calendar_event`
- Added host bridge executors:
- `calendar_list_events` (`gog calendar events`)
- `calendar_event_update` (`gog calendar update`)
- `calendar_event_delete` (`gog calendar delete`)
- Preserved explicit approval policy:
- read-only calendar listing stays `low`
- mutating calendar update/delete stay `high`
- Added docs/test payloads/validator coverage for the expanded calendar contract.
## Highest-priority next actions
1. Add `list_upcoming_events`, `update_calendar_event`, and `delete_calendar_event` actions.
2. Keep approval policy explicit per action (default-gated for mutating operations).
3. Add one compact operator test playbook for recurring verification (queue → approve → verify → cleanup).
1. Review `WIP.drive-docs-sheets.md` and make a go / no-go call per surface: Drive, Docs, Sheets.
2. If any new Google actions are added, keep approval defaults explicit by family (`notification`, `gmail`, `calendar`, `manual`, and any new family names).
3. Preserve compact operator reporting (`pending_compact`, `history_compact`, `summary_line`, `result_refs`) for any new approval-backed actions.
4. Keep the live deployment habit: after implementation, sync the live workflow and run a safe smoke test instead of trusting static validation alone.
## Success criteria for the next session
- Calendar action coverage expanded (`list_upcoming_events`, `update_calendar_event`, `delete_calendar_event`) with workflow + bridge + docs alignment.
- Approval policy remains explicit/safe for mutating calendar actions.
- `WIP.md` and memory updated with concrete IDs/evidence.
- Clear go/no-go decision on expanding beyond Gmail + Calendar.
- Any new verbs inherit the same safe approval defaults and low-noise history contract.
- `WIP.md` and memory updated with concrete evidence.
- Meaningful commit(s) captured.
## Relevant files
@@ -69,6 +85,8 @@ Run calendar pass (2/3): extend the action bus beyond create-only calendar suppo
## Relevant branch / commits
- branch: `feat/n8n-action-bus-v2`
- latest checkpoints before this handoff include:
- `ffe7a6b` — add operator approval runbook
- `249e671` — add compact approval history views
- `afa48a3` — bridge approvals to gog executors
- `044e36f` — auto-load local gog automation env
- `06fa582` — track google workspace and n8n plan
+2
View File
@@ -41,6 +41,8 @@
- Council tiers should use local LiteLLM-backed models for usage monitoring: light = `litellm/gpt-5.3-codex` with low thinking, medium = `litellm/gpt-5.3-codex` with high thinking, heavy = `litellm/gpt-5.4` with high thinking.
- For non-trivial implementation work, treat `WIP.md` as the canonical state file and update it after each completed task/sub-task with status, concrete evidence, and the next recommended action.
- If a subagent model choice causes execution/auth issues, prefer retrying implementation work on Codex GPT-5.4.
- If a fresh implementation subagent stops making crisp progress, inspect once; if it is looping, not updating `WIP.md`, or returns an unusable result, kill it, verify the workspace directly, and finish the pass in the main session.
- Monitoring cadence for fresh implementation subagents: first routine check at ~5 minutes if still running, inspect history at ~10 minutes, treat ~12/15 minutes as the suspicious/intervene threshold for narrow passes and ~20/25 minutes for medium bounded passes unless recent inspection shows crisp progress.
## Infrastructure notes worth remembering
- Full `~/.openclaw` backups upload to MinIO bucket `zap` and are scheduled via OS cron every 6 hours.
+77
View File
@@ -0,0 +1,77 @@
# WIP.drive-docs-sheets.md
## Status
Status: `closed`
Owner: `zap`
Opened: `2026-03-12`
Decision: `2026-03-12`
## Purpose
Evaluate whether the n8n Google Workspace action bus should expand beyond Gmail + Calendar into Drive / Docs / Sheets, or whether those surfaces should stay direct-tool-only for now.
## Decision
**All three surfaces: NO - defer for now**
### Rationale
The Gmail + Calendar integration succeeded because they map cleanly to the approval/queue pattern:
- **Discrete actions:** One-shot operations (send draft, create event)
- **Clear audit value:** Each action is a standalone event worth recording
- **Low iteration cost:** No back-and-forth editing required
- **Operator clarity:** "Send this email" / "Create this event" are clear human decisions
Drive / Docs / Sheets don't fit this pattern as naturally:
#### Drive
- Read/search operations are discovery tools, not approval-worthy events
- File management (move, trash) is edge case and rare in automation flows
- Direct `gog drive ...` usage is simpler for most cases
- **Verdict:** Overkill to queue through n8n for standard file ops
#### Docs
- Document editing is inherently iterative - you need to see, tweak, see again
- Approval gating for "create this doc" is fine, but then what?
- The real value is in the editing loop, not the initial create action
- Document work belongs in focused tool flows, not a one-shot queue
- **Verdict:** Wrong pattern for the action bus
#### Sheets
- Strongest candidate (structured writes map well to approval)
- But without a clear use case, this is premature optimization
- Append/update flows are useful *if* you have a structured data pipeline
- We don't have that yet, so building it now is YAGNI
- **Verdict:** Highest-priority "if needed later" item, but not today
## When to revisit
Revisit each surface only when a concrete use case appears:
- **Drive:** If you need approval-gated file moves/trash for an automation workflow
- **Docs:** If you have a "draft document → review → approve → publish" pattern that fits queueing
- **Sheets:** If you need structured data logging/metrics with an audit trail
## Recommendation preserved
Keep the action bus focused on the patterns that work:
- Notification routing (already implemented)
- Gmail actions (already implemented)
- Calendar actions (already implemented)
- Future surface additions should pass the same "discrete, approval-worthy, low-iteration" test
## Evidence referenced
- `WIP.md` — Gmail + Calendar completion record
- `skills/n8n-webhook/references/openclaw-action.md` — current action contract
- `skills/n8n-webhook/assets/openclaw-action.workflow.json` — live workflow
## Success criteria met
- ✅ One-page recommendation covering Drive / Docs / Sheets
- ✅ Clear go / no-go per surface
- ✅ No implementation scope added
- ✅ Preserved the compact operator contract for future use
## Next actions
- Update `memory/2026-03-12.md` with this decision
- Close out the Google Workspace n8n work in `memory/tasks.json`
- Consider syncing `feat/n8n-action-bus-v2` to the LAN Gitea repo for backup/review
+130 -26
View File
@@ -7,9 +7,10 @@ Google Workspace + n8n integration
Use OpenClaw as the brain, n8n as the orchestration layer, and Google Workspace as a real execution surface for Gmail/Calendar workflows.
## Current status
Status: `in-progress`
Status: `completed`
Owner: `zap`
Started: `2026-03-12`
Completed: `2026-03-12`
### Architecture decision
- Keep `openclaw-action` as the narrow authenticated ingress into n8n.
@@ -39,6 +40,7 @@ Started: `2026-03-12`
### n8n action bus
- [x] Live `openclaw-action` workflow exists and is active.
- [x] Live workflow re-synced from the current workspace asset after implementation completed.
- [x] Core actions verified live:
- `append_log`
- `get_logs`
@@ -99,16 +101,16 @@ Started: `2026-03-12`
- [x] Add `delete_email_draft`
- [x] Add `list_email_drafts`
- [x] Add `send_gmail_draft` / send-approved-email path
- [ ] Add `update_calendar_event`
- [ ] Add `delete_calendar_event`
- [ ] Add `list_upcoming_events`
- [x] Add `update_calendar_event`
- [x] Add `delete_calendar_event`
- [x] Add `list_upcoming_events`
- [ ] Decide whether Drive/Docs/Sheets need action-bus verbs next or can stay direct-tool only for now
### Then polish the operator experience
- [ ] Add a compact operator command/reference section for common approval flows
- [ ] Add one or two canned test payloads for real bridge verification flows
- [ ] Decide whether some Google actions should stay approval-gated by default
- [ ] Add low-noise reporting so history clearly shows:
- [x] Add a compact operator command/reference section for common approval flows
- [x] Add one or two canned test payloads for real bridge verification flows
- [x] Decide/document approval defaults clearly per action family
- [x] Add low-noise reporting so history clearly shows:
- queued
- approved/rejected
- executed
@@ -125,23 +127,26 @@ Started: `2026-03-12`
## Current recommendation
Execution should proceed in staged fresh sessions using `WIP.md` as the canonical state file.
Execution note:
- For the remaining implementation passes on this WIP, prefer Codex `gpt-5.4` to reduce iteration time and avoid model-availability churn.
Planned passes:
1. Gmail pass: ✅ complete
- added `delete_email_draft`
- added `list_email_drafts`
- added `send_gmail_draft` / send-approved-email path
- updated workflow contract/docs/test payloads/bridge + WIP evidence
2. Calendar pass:
- add `update_calendar_event`
- add `delete_calendar_event`
- add `list_upcoming_events`
- update `WIP.md` with evidence before ending the pass
3. Operator/polish pass:
- decide approval defaults for each action
- add low-noise execution/result reporting
- add compact operator command/reference docs
- add one or two canned recurring test payloads
- update `WIP.md` with evidence before ending the pass
2. Calendar pass: ✅ complete
- added `update_calendar_event`
- added `delete_calendar_event`
- added `list_upcoming_events`
- updated workflow contract/docs/test payloads/bridge + WIP evidence
3. Operator/polish pass: ✅ complete
- documented approval defaults by action family (`notification`, `gmail`, `calendar`, `manual`)
- added low-noise queue/history reporting (`pending_compact`, `history_compact`, `summary_line`, `result_refs`)
- added compact operator command/reference docs
- added two canned recurring verification payloads
- refreshed `WIP.md` with evidence before ending the pass
## Fresh-session proof refresh (2026-03-12 19:44Z)
- Re-ran both target proofs through the real approval-routed path in a clean implementation session.
@@ -189,16 +194,113 @@ Targeted verification evidence:
- send: exit `0`, returned dry-run op `gmail.drafts.send`
- `python3 -m py_compile skills/n8n-webhook/scripts/resolve-approval-with-gog.py` passed.
## Calendar pass 2 completion (2026-03-12)
Implemented in this pass:
- workflow contract + router logic for:
- `list_upcoming_events`
- `update_calendar_event`
- `delete_calendar_event`
- host bridge executor coverage for new approval kinds:
- `calendar_list_events``gog calendar events`
- `calendar_event_update``gog calendar update`
- `calendar_event_delete``gog calendar delete`
- docs + sample payloads + workflow validator updates for the expanded calendar contract
- explicit approval policy preserved:
- `list_upcoming_events``approval.mutation_level = low`
- `update_calendar_event` / `delete_calendar_event``approval.mutation_level = high`
Targeted verification evidence:
- `python3 skills/n8n-webhook/scripts/validate-workflow.py skills/n8n-webhook/assets/openclaw-action.workflow.json`
- result: `OK: workflow asset structure looks consistent`
- Workflow asset inspection confirmed new router actions are present:
- `list_upcoming_events`
- `update_calendar_event`
- `delete_calendar_event`
- Host bridge command-builder verification from shipped sample payloads:
- `calendar_list_events``gog calendar events primary --account will@example.com --json --no-input --max 10 --days 7 --query zap --dry-run`
- `calendar_event_update``gog calendar update primary example-calendar-event-id --account will@example.com --json --no-input --send-updates none --summary Updated call with vendor --from 2026-03-13T18:15:00Z --to 2026-03-13T18:45:00Z --description Updated by OpenClaw action bus. --location Updated room --dry-run`
- `calendar_event_delete``gog calendar delete primary example-calendar-event-id --account will@example.com --json --no-input --force --send-updates none --dry-run`
- `python3 -m py_compile skills/n8n-webhook/scripts/resolve-approval-with-gog.py` passed.
## Operator/polish pass 3 completion (2026-03-12)
Implemented in this pass:
- added explicit approval-family defaults in the shipped workflow + docs:
- `notification` → required `high`
- `gmail` read-only (`list_email_drafts`) → required `low`
- `gmail` mutating (`send_email_draft`, `delete_email_draft`, `send_gmail_draft`) → required `high`
- `calendar` read-only (`list_upcoming_events`) → required `low`
- `calendar` mutating (`create_calendar_event`, `update_calendar_event`, `delete_calendar_event`) → required `high`
- added compact operator-facing queue/history fields in the workflow:
- `payload_preview`
- `operator.summary_line`
- `operator.execution_state`
- `operator.result_refs`
- `approval_queue_list.result.pending_compact`
- `approval_queue_list.result.history_compact`
- `approval_queue_resolve.result.item_compact`
- `approval_history_attach_execution.result.item_compact`
- taught the host bridge to attach `execution.summary` + `execution.result_refs`
- added recurring verification payloads:
- `skills/n8n-webhook/assets/test-verify-email-draft-cycle.json`
- `skills/n8n-webhook/assets/test-verify-calendar-event-cycle.json`
- added operator runbook / recurring verification docs in:
- `skills/n8n-webhook/references/openclaw-action.md`
- `skills/n8n-webhook/references/payloads.md`
- `skills/n8n-webhook/SKILL.md`
Targeted verification evidence:
- `python3 skills/n8n-webhook/scripts/validate-workflow.py skills/n8n-webhook/assets/openclaw-action.workflow.json`
- result: `OK: workflow asset structure looks consistent`
- validator now also checks the two new recurring verification payload files
- `python3 -m py_compile skills/n8n-webhook/scripts/resolve-approval-with-gog.py`
- result: passed
- bridge helper proof via direct import/execution:
- `execution_result_refs('gmail.drafts.create', {'draft': {'id': 'r-proof-draft-123'}})``{'draft_id': 'r-proof-draft-123'}`
- `execution_summary(...)``gmail.drafts.create draft created (draft_id=r-proof-draft-123)`
- `execution_result_refs('calendar.create', {'event': {'id': 'evt-proof-456'}, 'calendar': 'primary'})``{'event_id': 'evt-proof-456', 'calendar': 'primary'}`
- `execution_summary(...)``calendar.create event created (event_id=evt-proof-456, calendar=primary)`
- workflow asset inspection confirmed low-noise operator fields are present:
- `pending_compact`
- `history_compact`
- `summary_line`
- `result_refs`
- `default_mode`
- `approval.family`
- recurring verification payload identity proofs:
- `test-verify-email-draft-cycle.json` → request id `verify-email-draft-cycle-001`
- `test-verify-calendar-event-cycle.json` → request id `verify-calendar-event-cycle-001`
## Live deploy + smoke verification (2026-03-12 21:36Z)
- Live workflow id: `Jwi54VWMdlLqYnRo`
- Synced the active n8n workflow in place from the current `skills/n8n-webhook/assets/openclaw-action.workflow.json` asset while preserving:
- webhook credential binding
- webhook registration id
- active state
- First live sync revealed the old minimal router was still running; re-synced from the current full code-node asset and re-activated successfully.
- Safe smoke calls succeeded against the production webhook:
- `append_log``ok: true`
- `get_logs``ok: true`
- `list_email_drafts``status: queued_for_approval`
- `list_upcoming_events``status: queued_for_approval`
- `approval_queue_list``ok: true`, with `pending_compact` + `history_compact` present
- `fetch_and_normalize_url` against local n8n `/healthz``ok: true`, HTTP `200`
- unknown action → expected HTTP `400` / `unknown_action`
- Smoke-created approval items were rejected and cleaned up:
- `approval-mmnzm1ev-yjk46sd1`
- `approval-mmnzm1gi-l7yszi92`
- `approval-mmnzmw80-kb8szya2`
- `approval-mmnzmw9w-c25hlml4`
- Remaining pending queue items after cleanup were pre-existing and left untouched:
- `approval-mmnvgv1o-h06r397e`
- `approval-mmnulm6r-mfaj7ea8`
## Next-session handoff
For the next fresh implementation session, start from `HANDOFF.md` + `WIP.md` rather than from old chat context.
This WIP is complete. For the next fresh implementation session, review `HANDOFF.md` plus the proposed next-phase file `WIP.drive-docs-sheets.md`.
Immediate target:
- calendar pass only:
- add `update_calendar_event`
- add `delete_calendar_event`
- add `list_upcoming_events`
- keep approval policy explicit for mutating calendar actions
- run targeted verification and refresh WIP/memory/tasks before ending
- decide whether Drive / Docs / Sheets actually need action-bus verbs or can remain direct-tool workflows for now
- if Google action coverage expands again, preserve the same approval-family defaults and compact history contract
- refresh WIP/memory/tasks before ending
## Relevant files
- `skills/n8n-webhook/assets/openclaw-action.workflow.json`
@@ -211,6 +313,8 @@ Immediate target:
## Current branch / checkpoints
- branch: `feat/n8n-action-bus-v2`
- key commits:
- `ffe7a6b` — add operator approval runbook
- `249e671` — add compact approval history views
- `9dcc477` — expand action bus starter workflow
- `dc990a1` — deploy and verify expanded action bus
- `1eabaeb` — add approval-gated notification executor
+92
View File
@@ -173,3 +173,95 @@
- `approval-mmny879w-yvqzokpz`
- `approval-mmny879w-md99hqxs`
- `gog` dry-run command checks for list/delete/send each exited `0`.
## Calendar pass 2 (fresh subagent implementation, locally verified)
- Added to `openclaw-action` workflow contract:
- `list_upcoming_events`
- `update_calendar_event`
- `delete_calendar_event`
- Preserved explicit approval metadata/policy:
- `list_upcoming_events``approval.mutation_level = low`
- `update_calendar_event` / `delete_calendar_event``approval.mutation_level = high`
- Extended host bridge `resolve-approval-with-gog.py` with executor coverage for:
- `calendar_list_events``gog calendar events`
- `calendar_event_update``gog calendar update`
- `calendar_event_delete``gog calendar delete`
- Added sample payloads:
- `skills/n8n-webhook/assets/test-list-upcoming-events.json`
- `skills/n8n-webhook/assets/test-update-calendar-event.json`
- `skills/n8n-webhook/assets/test-delete-calendar-event.json`
- Verification evidence (local/targeted):
- workflow structure + contract validator passed after the calendar additions
- workflow asset inspection confirmed the three new router actions are present
- bridge command-builder checks from shipped payloads produced:
- `gog calendar events primary --account will@example.com --json --no-input --max 10 --days 7 --query zap --dry-run`
- `gog calendar update primary example-calendar-event-id --account will@example.com --json --no-input --send-updates none --summary Updated call with vendor --from 2026-03-13T18:15:00Z --to 2026-03-13T18:45:00Z --description Updated by OpenClaw action bus. --location Updated room --dry-run`
- `gog calendar delete primary example-calendar-event-id --account will@example.com --json --no-input --force --send-updates none --dry-run`
- `python3 -m py_compile skills/n8n-webhook/scripts/resolve-approval-with-gog.py` passed.
## Live deploy + smoke verification
- Re-synced the active n8n workflow `Jwi54VWMdlLqYnRo` from the current `openclaw-action.workflow.json` asset while preserving the bound webhook credential + webhook id.
- First sync exposed that the live workflow had still been on the older minimal router; re-synced again from the current full asset and re-activated successfully.
- Safe production-webhook smoke calls succeeded:
- `append_log` → ok
- `get_logs` → ok
- `list_email_drafts` → queued_for_approval
- `list_upcoming_events` → queued_for_approval
- `approval_queue_list` → ok with `pending_compact` + `history_compact`
- `fetch_and_normalize_url` against local `/healthz` → ok / HTTP 200
- unknown action → expected HTTP 400 / `unknown_action`
- Smoke-created pending approvals were rejected/cleaned:
- `approval-mmnzm1ev-yjk46sd1`
- `approval-mmnzm1gi-l7yszi92`
- `approval-mmnzmw80-kb8szya2`
- `approval-mmnzmw9w-c25hlml4`
- Remaining pending items after cleanup were older pre-existing queue items and were intentionally left alone.
## Subagent monitoring thresholds
- Added an explicit operating rule for fresh implementation runs:
- first routine check at ~5 minutes if still running
- inspect child history at ~10 minutes
- narrow pass feels suspiciously long at ~12 minutes and should be actively intervened by ~15 minutes absent crisp progress
- medium bounded pass feels suspiciously long at ~20 minutes and should be actively intervened by ~25 minutes absent crisp progress
- Also recorded the fallback rule: if the run is looping, not updating `WIP.md`, or returns an unusable result, finish the pass directly in the main session after one inspection.
## Operator/polish pass 3 (fresh subagent implementation, locally verified)
- Added explicit approval families + defaults across the n8n action bus:
- notification → required/high
- gmail read-only → required/low
- gmail mutating → required/high
- calendar read-only → required/low
- calendar mutating → required/high
- Added low-noise operator/history reporting in the workflow:
- `payload_preview`
- `operator.summary_line`
- `operator.execution_state`
- `operator.result_refs`
- compact list surfaces: `pending_compact`, `history_compact`, `item_compact`
- Extended the host bridge so attached execution metadata now includes:
- `execution.summary`
- `execution.result_refs`
- Added recurring verification payloads with stable proof IDs:
- `skills/n8n-webhook/assets/test-verify-email-draft-cycle.json``verify-email-draft-cycle-001`
- `skills/n8n-webhook/assets/test-verify-calendar-event-cycle.json``verify-calendar-event-cycle-001`
- Local verification proofs:
- workflow validator passed after the operator/history changes
- bridge helper proof: `gmail.drafts.create` sample result produced `draft_id = r-proof-draft-123`
- bridge helper proof: `calendar.create` sample result produced `event_id = evt-proof-456`, `calendar = primary`
- workflow asset string checks confirmed presence of `pending_compact`, `history_compact`, `summary_line`, `result_refs`, `default_mode`, and `approval.family`
## Drive/Docs/Sheets evaluation (main session)
- Hit GPT-5.4 rate limit during attempted subagent work; continued evaluation in main session with GLM 5.
- Completed decision on expanding Google Workspace action bus beyond Gmail + Calendar into Drive / Docs / Sheets.
- **Decision: NO for all three surfaces — defer for now.**
- Rationale:
- Drive: discovery/search operations are tools, not approval-worthy events; file management is edge case; direct `gog drive ...` usage is simpler.
- Docs: editing is inherently iterative (see, tweak, see again); document work belongs in focused tool flows, not a one-shot queue.
- Sheets: strongest candidate (structured writes map well to approval), but without a concrete use case, this is premature optimization.
- Preserved principle: the action bus works best for discrete, approval-worthy, low-iteration operations (like send draft, create event).
- Updated `WIP.drive-docs-sheets.md` with closed status and revisit criteria per surface.
- Updated `memory/tasks.json` to close:
- `task-20260311-1908-calendar-access` → done
- `task-20260311-1908-email-access` → done
- `task-20260311-1914-google-workspace-access` → done
- Google Workspace + n8n integration WIP (WIP.md) is now complete with evidence recorded in memory.
+14 -16
View File
@@ -59,13 +59,13 @@
"title": "Add calendar access/backend for proactive scheduling help",
"owner": "zap",
"priority": "medium",
"status": "in-progress",
"status": "done",
"details": "Set up or connect a calendar backend so zap can provide stronger calendar-aware assistance, daily briefs, and schedule checks.",
"notes": [
"Added from LAN-services gap review on 2026-03-11.",
"Biggest functional gap identified at the time.",
"Progress 2026-03-12: access/auth now exists and the remaining work is productionizing n8n-routed execution plus verification.",
"Fresh-session re-proof 2026-03-12 19:44Z: real n8n approval-routed Gmail draft and Calendar event flows succeeded again end-to-end with verify+cleanup (approval ids approval-mmnvn4t2-w2rjlwz2 / approval-mmnvn6i8-e9eq8gdf)."
"Completed 2026-03-12: live n8n action bus now supports approval-gated Calendar actions (create, list, update, delete) via host-side gog bridge.",
"Live workflow id: Jwi54VWMdlLqYnRo.",
"Evidence: WIP.md + memory/2026-03-12.md + WIP.drive-docs-sheets.md (decision to defer Drive/Docs/Sheets)."
]
},
{
@@ -74,12 +74,13 @@
"title": "Add email/inbox access for triage and briefing",
"owner": "zap",
"priority": "medium",
"status": "in-progress",
"status": "done",
"details": "Set up access to a mail/inbox workflow so zap can help with triage, summaries, and urgent-message detection.",
"notes": [
"Added from LAN-services gap review on 2026-03-11.",
"Progress 2026-03-12: access/auth now exists and the remaining work is productionizing n8n-routed execution plus verification.",
"Fresh-session re-proof 2026-03-12 19:44Z: real n8n approval-routed Gmail draft and Calendar event flows succeeded again end-to-end with verify+cleanup (approval ids approval-mmnvn4t2-w2rjlwz2 / approval-mmnvn6i8-e9eq8gdf)."
"Completed 2026-03-12: live n8n action bus now supports approval-gated Gmail actions (draft create, list, delete, send) via host-side gog bridge.",
"Live workflow id: Jwi54VWMdlLqYnRo.",
"Evidence: WIP.md + memory/2026-03-12.md + WIP.drive-docs-sheets.md (decision to defer Drive/Docs/Sheets)."
]
},
{
@@ -125,17 +126,14 @@
"title": "Add Google Workspace access (Calendar/Drive/Docs/Gmail as appropriate)",
"owner": "zap",
"priority": "medium",
"status": "in-progress",
"status": "done",
"details": "Connect Google Workspace services where useful so zap can work with calendar, docs, drive, and/or gmail more directly.",
"notes": [
"Added from tool wishlist on 2026-03-11.",
"Some overlap with calendar/email tasks; this is the broader suite-level follow-up.",
"Progress 2026-03-12: gog auth completed for Gmail/Calendar/Drive/Contacts/Docs/Sheets.",
"Progress 2026-03-12: live n8n action bus now supports approval-gated Google flows via host-side gog bridge.",
"Next proof step: run real n8n-routed Gmail draft and Calendar event tests (not just dry-run).",
"Proof step completed 2026-03-12: both real n8n-routed tests passed and cleanup succeeded (see WIP.md + memory/2026-03-12.md for IDs).",
"State file: WIP.md tracks the current full plan and checkpoints.",
"Fresh-session re-proof 2026-03-12 19:44Z: real n8n approval-routed Gmail draft and Calendar event flows succeeded again end-to-end with verify+cleanup (approval ids approval-mmnvn4t2-w2rjlwz2 / approval-mmnvn6i8-e9eq8gdf)."
"Completed 2026-03-12: Gmail and Calendar are live via n8n action bus with approval gating and audit history.",
"Drive/Docs/Sheets evaluated and deferred in WIP.drive-docs-sheets.md — revisit only when concrete use cases appear.",
"Live workflow id: Jwi54VWMdlLqYnRo.",
"Evidence: WIP.md + memory/2026-03-12.md + WIP.drive-docs-sheets.md."
]
},
{
@@ -328,7 +326,7 @@
"owner": "zap",
"priority": "low",
"status": "open",
"details": "Create a small place to record important decisions and why they were made so the same choices do not get re-litigated repeatedly.",
"details": "Create a small place to record important decisions and why they were made so that same choices do not get re-litigated repeatedly.",
"notes": [
"Added from second-wave improvements list on 2026-03-11."
]
+15 -1
View File
@@ -40,6 +40,11 @@ Keep the integration narrow: let OpenClaw decide what to do, and let n8n execute
- `assets/test-send-gmail-draft.json`
- `assets/test-send-approved-email.json`
- `assets/test-create-calendar-event.json`
- `assets/test-list-upcoming-events.json`
- `assets/test-update-calendar-event.json`
- `assets/test-delete-calendar-event.json`
- `assets/test-verify-email-draft-cycle.json`
- `assets/test-verify-calendar-event-cycle.json`
## Quick usage
@@ -61,6 +66,8 @@ Call the preferred action-bus route:
scripts/call-action.sh append_log --args '{"text":"backup complete"}' --request-id auto
scripts/call-action.sh get_logs --args '{"limit":5}' --pretty
scripts/call-action.sh list_email_drafts --args '{"max":10}' --pretty
scripts/call-action.sh list_upcoming_events --args '{"days":7,"max":10}' --pretty
scripts/call-action.sh approval_queue_list --args '{"limit":5,"include_history":true}' --pretty
```
Call a test webhook while editing a flow:
@@ -115,7 +122,10 @@ Use the included workflow asset when you want a ready-made local router for:
- `list_email_drafts` → queue approval-gated Gmail draft list requests (read-only, low mutation level)
- `delete_email_draft` → queue approval-gated Gmail draft deletion requests
- `send_gmail_draft` (alias: `send_approved_email`) → queue approval-gated Gmail draft send requests
- `create_calendar_event` → queue approval-gated calendar proposals in workflow static data
- `create_calendar_event` → queue approval-gated calendar creation proposals in workflow static data
- `list_upcoming_events` → queue approval-gated calendar event listing requests (read-only, low mutation level)
- `update_calendar_event` → queue approval-gated calendar event update requests
- `delete_calendar_event` → queue approval-gated calendar event deletion requests
- `approval_queue_add` / `approval_queue_list` / `approval_queue_resolve` → manage pending approvals and recent history
- `approval_history_attach_execution` → let a host-side executor attach real execution metadata back onto approval history entries
- `fetch_and_normalize_url` → fetch + normalize URL content using n8n runtime HTTP helpers
@@ -144,11 +154,15 @@ Supported host-executed approval kinds:
- `email_draft_delete``gog gmail drafts delete`
- `email_draft_send``gog gmail drafts send`
- `calendar_event``gog calendar create`
- `calendar_list_events``gog calendar events`
- `calendar_event_update``gog calendar update`
- `calendar_event_delete``gog calendar delete`
Practical note:
- unattended execution needs `GOG_KEYRING_PASSWORD` available to the executor because `gog`'s file keyring cannot prompt in non-TTY automation
- the included bridge auto-loads `/home/openclaw/.openclaw/credentials/gog.env` when present, so you can keep `GOG_ACCOUNT` and `GOG_KEYRING_PASSWORD` there with mode `600`
- for safe plumbing tests without touching Google state, add `--dry-run`
- approval queue/history reads now expose compact `pending_compact` / `history_compact` entries plus `summary_line` + `result_refs` for low-noise operator review
### Add a new webhook-backed capability
File diff suppressed because one or more lines are too long
@@ -0,0 +1,8 @@
{
"action": "delete_calendar_event",
"args": {
"calendar": "primary",
"event_id": "example-calendar-event-id",
"send_updates": "none"
}
}
@@ -0,0 +1,9 @@
{
"action": "list_upcoming_events",
"args": {
"calendar": "primary",
"days": 7,
"max": 10,
"query": "zap"
}
}
@@ -0,0 +1,13 @@
{
"action": "update_calendar_event",
"args": {
"calendar": "primary",
"event_id": "example-calendar-event-id",
"title": "Updated call with vendor",
"start": "2026-03-13T18:15:00Z",
"end": "2026-03-13T18:45:00Z",
"location": "Updated room",
"description": "Updated by OpenClaw action bus.",
"send_updates": "none"
}
}
@@ -0,0 +1,11 @@
{
"action": "create_calendar_event",
"request_id": "verify-calendar-event-cycle-001",
"args": {
"calendar": "primary",
"title": "[zap verify] Calendar event cycle smoke",
"start": "2030-01-15T18:00:00Z",
"end": "2030-01-15T18:30:00Z",
"description": "Recurring verification payload for the n8n Google Workspace action bus. Queue this event, approve it through the gog bridge, verify the created calendar event, then delete the event as cleanup."
}
}
@@ -0,0 +1,9 @@
{
"action": "send_email_draft",
"request_id": "verify-email-draft-cycle-001",
"args": {
"to": ["will@example.com"],
"subject": "[zap verify] Gmail draft cycle smoke",
"body_text": "Recurring verification payload for the n8n Google Workspace action bus. Queue this draft, approve it through the gog bridge, verify the created Gmail draft, then delete the draft as cleanup."
}
}
@@ -20,6 +20,9 @@ It implements a real local OpenClaw → n8n router.
- `delete_email_draft`
- `send_gmail_draft` (alias: `send_approved_email`)
- `create_calendar_event`
- `list_upcoming_events`
- `update_calendar_event`
- `delete_calendar_event`
- `approval_queue_add`
- `approval_queue_list`
- `approval_queue_resolve`
@@ -60,6 +63,9 @@ Actions:
- `delete_email_draft`
- `send_gmail_draft` (alias: `send_approved_email`)
- `create_calendar_event`
- `list_upcoming_events`
- `update_calendar_event`
- `delete_calendar_event`
Behavior:
- queue proposals into workflow static data under key:
@@ -69,13 +75,41 @@ Behavior:
- do **not** execute Gmail/Calendar side effects directly in the shipped starter workflow
- are intended for host-side execution via the included `gog` bridge after explicit approval resolution
Approval policy defaults:
- `send_email_draft`, `delete_email_draft`, `send_gmail_draft` / `send_approved_email`, `create_calendar_event`
Approval policy defaults by action family:
- notification family
- `send_notification_draft`
- `approval.family = "notification"`
- `approval.required = true`
- `approval.mutation_level = "high"`
- `list_email_drafts`
- approved items execute inline in n8n via the existing `notify` path
- Gmail family
- read-only: `list_email_drafts`
- `approval.family = "gmail"`
- `approval.required = true`
- `approval.mutation_level = "low"` (read-only action, still routed through approval queue for explicit operator acknowledgement + audit trail)
- `approval.mutation_level = "low"`
- still queued so operators must explicitly acknowledge host-side Gmail reads
- mutating: `send_email_draft`, `delete_email_draft`, `send_gmail_draft` / `send_approved_email`
- `approval.family = "gmail"`
- `approval.required = true`
- `approval.mutation_level = "high"`
- Calendar family
- read-only: `list_upcoming_events`
- `approval.family = "calendar"`
- `approval.required = true`
- `approval.mutation_level = "low"`
- mutating: `create_calendar_event`, `update_calendar_event`, `delete_calendar_event`
- `approval.family = "calendar"`
- `approval.required = true`
- `approval.mutation_level = "high"`
- manual/generic approvals
- `approval_queue_add`
- no automatic side effect is implied; the operator decides what the queued item means
Queue/history entries now also carry compact operator-facing fields for low-noise review:
- `approvalQueue[].payload_preview`
- `approvalQueue[].operator.summary_line`
- `approvalHistory[].operator.execution_state`
- `approvalHistory[].operator.result_refs`
### `approval_queue_resolve`
@@ -84,12 +118,20 @@ Approval policy defaults:
- `approvalHistory`
- supports optional notification on approval/rejection
- executes notification drafts inline when the approved item kind is `notification`
- returns both the full resolved item and a compact operator view at:
- `result.item_compact`
### `approval_history_attach_execution`
- patches an existing resolved history item in `approvalHistory`
- designed for host-side executors that run outside n8n itself
- used by the included `scripts/resolve-approval-with-gog.py` bridge to attach Gmail/Calendar execution results
- the updated history entry now includes low-noise operator metadata such as:
- `operator.summary_line`
- `operator.execution_state`
- `operator.result_refs`
- `execution.summary`
- returns both the full item and `result.item_compact`
### `fetch_and_normalize_url`
@@ -172,6 +214,11 @@ After import, set this manually in n8n:
- `assets/test-send-gmail-draft.json`
- `assets/test-send-approved-email.json`
- `assets/test-create-calendar-event.json`
- `assets/test-list-upcoming-events.json`
- `assets/test-update-calendar-event.json`
- `assets/test-delete-calendar-event.json`
- `assets/test-verify-email-draft-cycle.json`
- `assets/test-verify-calendar-event-cycle.json`
- `assets/test-fetch-and-normalize-url.json`
- `assets/test-approval-queue-list.json`
- `assets/test-inbound-event-filter.json`
@@ -190,6 +237,11 @@ scripts/call-action.sh delete_email_draft --args-file assets/test-delete-email-d
scripts/call-action.sh send_gmail_draft --args-file assets/test-send-gmail-draft.json --pretty
scripts/call-action.sh send_approved_email --args-file assets/test-send-approved-email.json --pretty
scripts/call-action.sh create_calendar_event --args-file assets/test-create-calendar-event.json --pretty
scripts/call-action.sh list_upcoming_events --args-file assets/test-list-upcoming-events.json --pretty
scripts/call-action.sh update_calendar_event --args-file assets/test-update-calendar-event.json --pretty
scripts/call-action.sh delete_calendar_event --args-file assets/test-delete-calendar-event.json --pretty
scripts/call-action.sh --args-file assets/test-verify-email-draft-cycle.json --pretty
scripts/call-action.sh --args-file assets/test-verify-calendar-event-cycle.json --pretty
scripts/call-action.sh fetch_and_normalize_url --args '{"url":"http://192.168.153.113:18808/healthz"}' --pretty
scripts/call-action.sh fetch_and_normalize_url --args '{"url":"https://example.com","skip_ssl_certificate_validation":true}' --pretty
scripts/call-action.sh approval_queue_list --args '{"limit":10,"include_history":true}' --pretty
@@ -197,6 +249,76 @@ scripts/call-action.sh inbound_event_filter --args-file assets/test-inbound-even
python3 scripts/resolve-approval-with-gog.py --id <approval-id> --decision approve --dry-run
```
## Operator command reference
Common approval flows:
```bash
# 1) inspect the queue with both full and compact views
scripts/call-action.sh approval_queue_list --args '{"limit":10,"include_history":true}' --pretty
# 2) reject an item without host-side execution
scripts/call-action.sh approval_queue_resolve \
--args '{"id":"approval-abc123","decision":"reject","note":"not safe to run"}' \
--pretty
# 3) approve through the host bridge, but keep it side-effect free
python3 scripts/resolve-approval-with-gog.py --id approval-abc123 --decision approve --dry-run
# 4) approve for real through the host bridge
python3 scripts/resolve-approval-with-gog.py --id approval-abc123 --decision approve
# 5) re-check recent history in compact form
scripts/call-action.sh approval_queue_list --args '{"limit":5,"include_history":true}' --pretty
```
What to look for in low-noise history output:
- `result.pending_compact[]` and `result.history_compact[]`
- `summary_line` for a one-line operator digest
- `execution_state` for `pending`, `awaiting_host_execution`, `dry_run`, `executed`, or `failed`
- `result_refs` for durable IDs such as `draft_id`, `message_id`, or `event_id`
## Canned recurring verification flows
### Gmail draft queue → approve → verify → cleanup
1. Queue the canned payload:
```bash
scripts/call-action.sh --args-file assets/test-verify-email-draft-cycle.json --pretty
```
2. Find the new approval id from `pending_compact`.
3. Approve with the host bridge:
```bash
python3 scripts/resolve-approval-with-gog.py --id <approval-id> --decision approve
```
4. Re-run `approval_queue_list` and confirm the matching history item shows:
- `execution_state = "executed"`
- `result_refs.draft_id` populated
5. Cleanup by queueing `assets/test-delete-email-draft.json` with the returned draft id and approving that item.
### Calendar event queue → approve → verify → cleanup
1. Queue the canned payload:
```bash
scripts/call-action.sh --args-file assets/test-verify-calendar-event-cycle.json --pretty
```
2. Approve with the host bridge:
```bash
python3 scripts/resolve-approval-with-gog.py --id <approval-id> --decision approve
```
3. Re-run `approval_queue_list` and confirm the matching history item shows:
- `execution_state = "executed"`
- `result_refs.event_id` populated
4. Cleanup by queueing `assets/test-delete-calendar-event.json` with the returned `event_id` and approving that item.
## Expected success examples
### send_notification_draft
@@ -295,6 +417,56 @@ python3 scripts/resolve-approval-with-gog.py --id <approval-id> --decision appro
}
```
### list_upcoming_events
```json
{
"ok": true,
"request_id": "test-list-calendar-events-001",
"result": {
"action": "list_upcoming_events",
"status": "queued_for_approval",
"pending_id": "approval-pqr678",
"approval_status": "pending",
"approval": {
"policy": "approval_queue_resolve",
"required": true,
"mutation_level": "low"
}
}
}
```
### update_calendar_event
```json
{
"ok": true,
"request_id": "test-update-calendar-event-001",
"result": {
"action": "update_calendar_event",
"status": "queued_for_approval",
"pending_id": "approval-stu901",
"approval_status": "pending"
}
}
```
### delete_calendar_event
```json
{
"ok": true,
"request_id": "test-delete-calendar-event-001",
"result": {
"action": "delete_calendar_event",
"status": "queued_for_approval",
"pending_id": "approval-vwx234",
"approval_status": "pending"
}
}
```
### fetch_and_normalize_url
```json
@@ -339,6 +511,9 @@ Behavior:
- `email_draft_delete``gog gmail drafts delete`
- `email_draft_send``gog gmail drafts send`
- `calendar_event``gog calendar create`
- `calendar_list_events``gog calendar events`
- `calendar_event_update``gog calendar update`
- `calendar_event_delete``gog calendar delete`
- writes execution metadata back via `approval_history_attach_execution`
Important automation note:
+98
View File
@@ -48,6 +48,22 @@ Recommended request shape:
}
```
## Approval defaults by family
- notification family
- `send_notification_draft`
- `approval.family = "notification"`
- `approval.required = true`
- `approval.mutation_level = "high"`
- Gmail family
- read-only: `list_email_drafts``approval.family = "gmail"`, `approval.mutation_level = "low"`
- mutating: `send_email_draft`, `delete_email_draft`, `send_gmail_draft` / `send_approved_email``approval.family = "gmail"`, `approval.mutation_level = "high"`
- Calendar family
- read-only: `list_upcoming_events``approval.family = "calendar"`, `approval.mutation_level = "low"`
- mutating: `create_calendar_event`, `update_calendar_event`, `delete_calendar_event``approval.family = "calendar"`, `approval.mutation_level = "high"`
- manual/generic approvals
- `approval_queue_add` leaves side effects to the operator; there is no automatic host executor for arbitrary manual kinds
## Live actions in the shipped workflow asset
### `append_log`
@@ -250,6 +266,80 @@ Sink:
- key: `approvalQueue`
- retained entries: `200`
### `list_upcoming_events`
Request:
```json
{
"action": "list_upcoming_events",
"args": {
"calendar": "primary",
"days": 7,
"max": 10,
"query": "zap"
}
}
```
Purpose:
- queue a host-side upcoming calendar event listing request for approval/audit
- defaults to the next `7` days when no explicit `from`/`to` window is provided
Approval policy:
- required: `true`
- mutation level: `low` (read-only)
### `update_calendar_event`
Request:
```json
{
"action": "update_calendar_event",
"args": {
"calendar": "primary",
"event_id": "example-calendar-event-id",
"title": "Updated call with vendor",
"start": "2026-03-13T18:15:00Z",
"end": "2026-03-13T18:45:00Z",
"location": "Updated room",
"description": "Updated by OpenClaw action bus.",
"send_updates": "none"
}
}
```
Purpose:
- queue an update to an existing calendar event behind explicit approval
- requires `event_id` and at least one patch field (`title`, `start`, `end`, `location`, `description`, or `attendees`)
Approval policy:
- required: `true`
- mutation level: `high`
### `delete_calendar_event`
Request:
```json
{
"action": "delete_calendar_event",
"args": {
"calendar": "primary",
"event_id": "example-calendar-event-id",
"send_updates": "none"
}
}
```
Purpose:
- queue deletion of an existing calendar event behind explicit approval
Approval policy:
- required: `true`
- mutation level: `high`
### `approval_queue_add`
Request:
@@ -288,6 +378,9 @@ Request:
Purpose:
- inspect pending approval items
- optionally include recent resolved history
- returns both raw entries and compact operator-friendly summaries at:
- `result.pending_compact`
- `result.history_compact`
### `approval_queue_resolve`
@@ -331,6 +424,11 @@ Request:
Purpose:
- patch a resolved history item with host-side execution metadata after a real executor runs outside n8n
- intended for bridges such as `gog`-backed Gmail/Calendar execution
- compact execution reporting should populate or expose:
- `execution.summary`
- `execution.result_refs`
- `item.operator.summary_line`
- `item.operator.execution_state`
### `fetch_and_normalize_url`
@@ -199,7 +199,16 @@ def build_email_drafts_list_command(item: dict, account: str, dry_run: bool):
return cmd
def build_calendar_command(item: dict, account: str, dry_run: bool):
def normalize_send_updates(value: str) -> str:
raw = (value or '').strip()
if raw == 'all':
return 'all'
if raw.lower() == 'externalonly':
return 'externalOnly'
return 'none'
def build_calendar_create_command(item: dict, account: str, dry_run: bool):
payload = item.get('payload') or {}
calendar = payload.get('calendar') or 'primary'
cmd = [
@@ -210,7 +219,7 @@ def build_calendar_command(item: dict, account: str, dry_run: bool):
'--summary', payload.get('title') or '',
'--from', payload.get('start') or '',
'--to', payload.get('end') or '',
'--send-updates', 'none',
'--send-updates', normalize_send_updates(payload.get('send_updates') or 'none'),
]
if payload.get('description'):
cmd.extend(['--description', payload['description']])
@@ -224,6 +233,104 @@ def build_calendar_command(item: dict, account: str, dry_run: bool):
return cmd
def build_calendar_list_events_command(item: dict, account: str, dry_run: bool):
payload = item.get('payload') or {}
calendar = (payload.get('calendar') or 'primary').strip() or 'primary'
max_results = payload.get('max')
if max_results is None:
max_results = 20
try:
max_results = max(1, min(100, int(max_results)))
except Exception:
max_results = 20
days = payload.get('days')
if days is None:
days = 7
try:
days = max(1, min(90, int(days)))
except Exception:
days = 7
cmd = [
'gog', 'calendar', 'events', calendar,
'--account', account,
'--json',
'--no-input',
'--max', str(max_results),
]
from_value = (payload.get('from') or '').strip()
to_value = (payload.get('to') or '').strip()
query = (payload.get('query') or '').strip()
if from_value:
cmd.extend(['--from', from_value])
if to_value:
cmd.extend(['--to', to_value])
if not from_value and not to_value:
cmd.extend(['--days', str(days)])
if query:
cmd.extend(['--query', query])
if payload.get('all_pages') is True:
cmd.append('--all-pages')
if payload.get('fail_empty') is True:
cmd.append('--fail-empty')
if dry_run:
cmd.append('--dry-run')
return cmd
def build_calendar_update_command(item: dict, account: str, dry_run: bool):
payload = item.get('payload') or {}
calendar = (payload.get('calendar') or 'primary').strip() or 'primary'
event_id = (payload.get('event_id') or payload.get('id') or '').strip()
if not event_id:
fail('calendar_event_update payload missing event_id')
cmd = [
'gog', 'calendar', 'update', calendar, event_id,
'--account', account,
'--json',
'--no-input',
'--send-updates', normalize_send_updates(payload.get('send_updates') or 'none'),
]
if payload.get('title'):
cmd.extend(['--summary', payload['title']])
if payload.get('start'):
cmd.extend(['--from', payload['start']])
if payload.get('end'):
cmd.extend(['--to', payload['end']])
if payload.get('description'):
cmd.extend(['--description', payload['description']])
if payload.get('location'):
cmd.extend(['--location', payload['location']])
attendees = payload.get('attendees')
if isinstance(attendees, list):
cmd.extend(['--attendees', ','.join(str(x) for x in attendees if str(x).strip())])
elif isinstance(attendees, str) and attendees.strip():
cmd.extend(['--attendees', attendees.strip()])
if dry_run:
cmd.append('--dry-run')
return cmd
def build_calendar_delete_command(item: dict, account: str, dry_run: bool):
payload = item.get('payload') or {}
calendar = (payload.get('calendar') or 'primary').strip() or 'primary'
event_id = (payload.get('event_id') or payload.get('id') or '').strip()
if not event_id:
fail('calendar_event_delete payload missing event_id')
cmd = [
'gog', 'calendar', 'delete', calendar, event_id,
'--account', account,
'--json',
'--no-input',
'--force',
'--send-updates', normalize_send_updates(payload.get('send_updates') or 'none'),
]
if dry_run:
cmd.append('--dry-run')
return cmd
def parse_json(output: str):
text = output.strip()
if not text:
@@ -231,6 +338,63 @@ def parse_json(output: str):
return json.loads(text)
def first_string(*values):
for value in values:
if isinstance(value, str) and value.strip():
return value.strip()
return ''
def execution_result_refs(op: str, parsed):
refs = {}
if not isinstance(parsed, dict):
return refs
draft = parsed.get('draft') if isinstance(parsed.get('draft'), dict) else {}
message = parsed.get('message') if isinstance(parsed.get('message'), dict) else {}
event = parsed.get('event') if isinstance(parsed.get('event'), dict) else {}
draft_id = first_string(
parsed.get('draft_id'),
draft.get('id'),
parsed.get('id') if op.startswith('gmail.drafts.') else '',
)
if draft_id:
refs['draft_id'] = draft_id
message_id = first_string(parsed.get('message_id'), message.get('id'))
if message_id:
refs['message_id'] = message_id
event_id = first_string(
parsed.get('event_id'),
event.get('id'),
parsed.get('id') if op.startswith('calendar.') else '',
)
if event_id:
refs['event_id'] = event_id
calendar = first_string(parsed.get('calendar'), parsed.get('calendar_id'), event.get('calendar'))
if calendar:
refs['calendar'] = calendar
return refs
def execution_summary(op: str, status: str, refs: dict, dry_run: bool):
if status == 'failed':
return f'{op} failed'
suffix = 'dry run' if dry_run else status.replace('_', ' ')
ref_parts = []
for key in ('draft_id', 'message_id', 'event_id', 'calendar'):
value = refs.get(key, '')
if value:
ref_parts.append(f'{key}={value}')
if ref_parts:
return f'{op} {suffix} ({", ".join(ref_parts)})'
return f'{op} {suffix}'
def main():
ap = argparse.ArgumentParser(description='Resolve an n8n approval item and execute email/calendar actions via gog.')
ap.add_argument('--id', required=True, help='Approval queue item id')
@@ -299,11 +463,29 @@ def main():
'uses_tmpfile': False,
},
'calendar_event': {
'builder': build_calendar_command,
'builder': build_calendar_create_command,
'op': 'calendar.create',
'success_status': 'event_created',
'uses_tmpfile': False,
},
'calendar_list_events': {
'builder': build_calendar_list_events_command,
'op': 'calendar.events.list',
'success_status': 'events_listed',
'uses_tmpfile': False,
},
'calendar_event_update': {
'builder': build_calendar_update_command,
'op': 'calendar.update',
'success_status': 'event_updated',
'uses_tmpfile': False,
},
'calendar_event_delete': {
'builder': build_calendar_delete_command,
'op': 'calendar.delete',
'success_status': 'event_deleted',
'uses_tmpfile': False,
},
}
spec = executors.get(kind)
@@ -334,6 +516,7 @@ def main():
pass
if code != 0:
refs = {}
execution = {
'driver': 'gog',
'op': op,
@@ -342,18 +525,23 @@ def main():
'dry_run': args.dry_run,
'stderr': stderr.strip(),
'stdout': stdout.strip(),
'result_refs': refs,
'summary': execution_summary(op, 'failed', refs, args.dry_run),
}
attach = attach_execution(item['id'], execution, base_url=args.base_url, path=args.path, secret_header=args.secret_header, secret=secret)
print(json.dumps({'resolved': resolved, 'execution': execution, 'attach': attach}, indent=2))
raise SystemExit(code)
parsed = parse_json(stdout) if stdout.strip() else None
refs = execution_result_refs(op, parsed)
execution = {
'driver': 'gog',
'op': op,
'status': success_status,
'account': account,
'dry_run': args.dry_run,
'result_refs': refs,
'summary': execution_summary(op, success_status, refs, args.dry_run),
'result': parsed,
}
attach = attach_execution(item['id'], execution, base_url=args.base_url, path=args.path, secret_header=args.secret_header, secret=secret)
@@ -31,6 +31,11 @@ SAMPLE_FILES = [
'test-send-gmail-draft.json',
'test-send-approved-email.json',
'test-create-calendar-event.json',
'test-list-upcoming-events.json',
'test-update-calendar-event.json',
'test-delete-calendar-event.json',
'test-verify-email-draft-cycle.json',
'test-verify-calendar-event-cycle.json',
'test-fetch-and-normalize-url.json',
'test-approval-queue-list.json',
'test-inbound-event-filter.json',
@@ -47,6 +52,9 @@ ROUTER_SNIPPETS = [
'send_gmail_draft',
'send_approved_email',
'create_calendar_event',
'list_upcoming_events',
'update_calendar_event',
'delete_calendar_event',
'approval_queue_add',
'approval_queue_list',
'approval_queue_resolve',
@@ -61,7 +69,15 @@ ROUTER_SNIPPETS = [
'email_draft_send',
'email_draft_delete',
'email_list_drafts',
'calendar_list_events',
'calendar_event_update',
'calendar_event_delete',
'makeApprovalPolicy',
'pending_compact',
'history_compact',
'summary_line',
'result_refs',
'default_mode',
'inboundEvents',
'eventDedup',
'notify_text',