@bastani/atomic 0.6.4 → 0.6.5

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 (120) hide show
  1. package/.agents/skills/create-spec/SKILL.md +6 -3
  2. package/.agents/skills/tdd/SKILL.md +107 -0
  3. package/.agents/skills/tdd/deep-modules.md +33 -0
  4. package/.agents/skills/tdd/interface-design.md +31 -0
  5. package/.agents/skills/tdd/mocking.md +59 -0
  6. package/.agents/skills/tdd/refactoring.md +10 -0
  7. package/.agents/skills/tdd/tests.md +61 -0
  8. package/.agents/skills/workflow-creator/SKILL.md +550 -0
  9. package/.agents/skills/workflow-creator/references/agent-sessions.md +891 -0
  10. package/.agents/skills/workflow-creator/references/agent-setup-recipe.md +266 -0
  11. package/.agents/skills/workflow-creator/references/computation-and-validation.md +201 -0
  12. package/.agents/skills/workflow-creator/references/control-flow.md +470 -0
  13. package/.agents/skills/workflow-creator/references/failure-modes.md +1014 -0
  14. package/.agents/skills/workflow-creator/references/getting-started.md +392 -0
  15. package/.agents/skills/workflow-creator/references/registry-and-validation.md +141 -0
  16. package/.agents/skills/workflow-creator/references/running-workflows.md +418 -0
  17. package/.agents/skills/workflow-creator/references/session-config.md +384 -0
  18. package/.agents/skills/workflow-creator/references/state-and-data-flow.md +356 -0
  19. package/.agents/skills/workflow-creator/references/user-input.md +234 -0
  20. package/.agents/skills/workflow-creator/references/workflow-inputs.md +392 -0
  21. package/.claude/agents/debugger.md +2 -2
  22. package/.claude/agents/reviewer.md +1 -1
  23. package/.claude/agents/worker.md +2 -2
  24. package/.github/agents/debugger.md +1 -1
  25. package/.github/agents/worker.md +1 -1
  26. package/.mcp.json +5 -1
  27. package/.opencode/agents/debugger.md +1 -1
  28. package/.opencode/agents/worker.md +1 -1
  29. package/README.md +236 -201
  30. package/dist/sdk/define-workflow.d.ts +11 -6
  31. package/dist/sdk/define-workflow.d.ts.map +1 -1
  32. package/dist/sdk/errors.d.ts +10 -0
  33. package/dist/sdk/errors.d.ts.map +1 -1
  34. package/dist/sdk/index.d.ts +21 -9
  35. package/dist/sdk/index.d.ts.map +1 -1
  36. package/dist/sdk/primitives/inputs.d.ts +36 -0
  37. package/dist/sdk/primitives/inputs.d.ts.map +1 -0
  38. package/dist/sdk/primitives/metadata.d.ts +40 -0
  39. package/dist/sdk/primitives/metadata.d.ts.map +1 -0
  40. package/dist/sdk/primitives/run.d.ts +57 -0
  41. package/dist/sdk/primitives/run.d.ts.map +1 -0
  42. package/dist/sdk/primitives/sessions.d.ts +128 -0
  43. package/dist/sdk/primitives/sessions.d.ts.map +1 -0
  44. package/dist/sdk/runtime/executor.d.ts +24 -56
  45. package/dist/sdk/runtime/executor.d.ts.map +1 -1
  46. package/dist/sdk/runtime/orchestrator-entry.d.ts +26 -0
  47. package/dist/sdk/runtime/orchestrator-entry.d.ts.map +1 -0
  48. package/dist/sdk/runtime/tmux.d.ts +20 -0
  49. package/dist/sdk/runtime/tmux.d.ts.map +1 -1
  50. package/dist/sdk/types.d.ts +26 -86
  51. package/dist/sdk/types.d.ts.map +1 -1
  52. package/dist/sdk/workflows/builtin/deep-research-codebase/claude/index.d.ts.map +1 -1
  53. package/dist/sdk/workflows/builtin/deep-research-codebase/copilot/index.d.ts.map +1 -1
  54. package/dist/sdk/workflows/builtin/deep-research-codebase/opencode/index.d.ts.map +1 -1
  55. package/dist/sdk/workflows/builtin/open-claude-design/claude/index.d.ts.map +1 -1
  56. package/dist/sdk/workflows/builtin/open-claude-design/copilot/index.d.ts.map +1 -1
  57. package/dist/sdk/workflows/builtin/open-claude-design/opencode/index.d.ts.map +1 -1
  58. package/dist/sdk/workflows/builtin/ralph/claude/index.d.ts.map +1 -1
  59. package/dist/sdk/workflows/builtin/ralph/copilot/index.d.ts.map +1 -1
  60. package/dist/sdk/workflows/builtin/ralph/opencode/index.d.ts.map +1 -1
  61. package/dist/sdk/workflows/index.d.ts +20 -12
  62. package/dist/sdk/workflows/index.d.ts.map +1 -1
  63. package/dist/services/config/additional-instructions.d.ts +1 -1
  64. package/dist/services/config/additional-instructions.d.ts.map +1 -1
  65. package/package.json +4 -4
  66. package/src/cli.ts +39 -56
  67. package/src/commands/builtin-registry.ts +37 -0
  68. package/src/commands/cli/chat/index.ts +1 -3
  69. package/src/{sdk → commands/cli}/management-commands.ts +15 -55
  70. package/src/commands/cli/session.ts +1 -1
  71. package/src/commands/cli/workflow-command.test.ts +250 -16
  72. package/src/commands/cli/workflow-inputs.test.ts +1 -0
  73. package/src/commands/cli/workflow-inputs.ts +13 -3
  74. package/src/commands/cli/workflow-list.test.ts +1 -0
  75. package/src/commands/cli/workflow-list.ts +0 -0
  76. package/src/commands/cli/workflow-status.ts +1 -1
  77. package/src/commands/cli/workflow.ts +191 -11
  78. package/src/sdk/define-workflow.test.ts +47 -16
  79. package/src/sdk/define-workflow.ts +24 -6
  80. package/src/sdk/errors.test.ts +11 -0
  81. package/src/sdk/errors.ts +13 -0
  82. package/src/sdk/index.test.ts +92 -0
  83. package/src/sdk/index.ts +71 -15
  84. package/src/sdk/primitives/inputs.ts +48 -0
  85. package/src/sdk/primitives/metadata.ts +63 -0
  86. package/src/sdk/primitives/run.ts +81 -0
  87. package/src/sdk/primitives/sessions.test.ts +594 -0
  88. package/src/sdk/primitives/sessions.ts +328 -0
  89. package/src/sdk/runtime/executor.ts +36 -115
  90. package/src/sdk/runtime/orchestrator-entry.ts +110 -0
  91. package/src/sdk/runtime/tmux.ts +33 -0
  92. package/src/sdk/types.ts +26 -91
  93. package/src/sdk/workflows/builtin/deep-research-codebase/claude/index.ts +1 -0
  94. package/src/sdk/workflows/builtin/deep-research-codebase/copilot/index.ts +1 -0
  95. package/src/sdk/workflows/builtin/deep-research-codebase/opencode/index.ts +1 -0
  96. package/src/sdk/workflows/builtin/open-claude-design/claude/index.ts +1 -0
  97. package/src/sdk/workflows/builtin/open-claude-design/copilot/index.ts +1 -0
  98. package/src/sdk/workflows/builtin/open-claude-design/opencode/index.ts +1 -0
  99. package/src/sdk/workflows/builtin/ralph/claude/index.ts +1 -0
  100. package/src/sdk/workflows/builtin/ralph/copilot/index.ts +1 -0
  101. package/src/sdk/workflows/builtin/ralph/opencode/index.ts +1 -0
  102. package/src/sdk/workflows/index.ts +68 -51
  103. package/src/services/config/additional-instructions.ts +1 -1
  104. package/.agents/skills/test-driven-development/SKILL.md +0 -371
  105. package/.agents/skills/test-driven-development/testing-anti-patterns.md +0 -299
  106. package/dist/commands/cli/session.d.ts +0 -67
  107. package/dist/commands/cli/session.d.ts.map +0 -1
  108. package/dist/commands/cli/workflow-status.d.ts +0 -63
  109. package/dist/commands/cli/workflow-status.d.ts.map +0 -1
  110. package/dist/sdk/commander.d.ts +0 -74
  111. package/dist/sdk/commander.d.ts.map +0 -1
  112. package/dist/sdk/management-commands.d.ts +0 -42
  113. package/dist/sdk/management-commands.d.ts.map +0 -1
  114. package/dist/sdk/workflow-cli.d.ts +0 -103
  115. package/dist/sdk/workflow-cli.d.ts.map +0 -1
  116. package/dist/sdk/workflows/builtin-registry.d.ts +0 -113
  117. package/dist/sdk/workflows/builtin-registry.d.ts.map +0 -1
  118. package/src/sdk/commander.ts +0 -161
  119. package/src/sdk/workflow-cli.ts +0 -409
  120. package/src/sdk/workflows/builtin-registry.ts +0 -23
@@ -0,0 +1,384 @@
1
+ # Session Configuration
2
+
3
+ Each SDK has its own configuration options for controlling model selection, tools, permissions, hooks, and structured output. Pass these via `clientOpts` (2nd arg to `ctx.stage()`) and `sessionOpts` (3rd arg to `ctx.stage()`). The runtime uses them to create the client and session automatically — no manual client or session creation needed.
4
+
5
+ ## Claude Agent SDK
6
+
7
+ ### Client options (`clientOpts` — 2nd arg to `ctx.stage()`)
8
+
9
+ These control how the Claude TUI pane is started:
10
+
11
+ ```ts
12
+ await ctx.stage({ name: "..." }, {
13
+ chatFlags: ["--model", "opus", "--dangerously-skip-permissions"],
14
+ readyTimeoutMs: 60_000, // Wait up to 60s for TUI (default: 30s)
15
+ }, {}, async (s) => {
16
+ // s.client and s.session are ready
17
+ });
18
+ ```
19
+
20
+ ### Session options (`sessionOpts` — 3rd arg to `ctx.stage()`)
21
+
22
+ Claude has **no per-session options** — the type is `Record<string, never>` and the 3rd arg must be `{}`. Interactive delivery is driven entirely by the CLI's Stop hook; idle detection is automatic (pane capture for interactive stages, SDK streaming for headless stages).
23
+
24
+ If you want to configure agent/permission/tools behavior for a **headless** Claude stage, pass those fields as the second argument to `s.session.query(prompt, options)` — they flow through as `Partial<SDKOptions>` to the Agent SDK (see the headless example below).
25
+
26
+ ```ts
27
+ await ctx.stage({ name: "..." }, {}, {}, async (s) => {
28
+ await s.session.query((s.inputs.prompt ?? ""));
29
+ s.save(s.sessionId);
30
+ });
31
+ ```
32
+
33
+ ### `query()` options (reference for `s.session.query()` sdkOptions)
34
+
35
+ **This block is a reference cheatsheet for the SDK option shape — it is
36
+ not valid workflow code.** Do not import `query` from
37
+ `@anthropic-ai/claude-agent-sdk` inside a `ctx.stage()` callback (see
38
+ `failure-modes.md` §F16). In a **headless** stage, pass these options as
39
+ the second argument to `s.session.query(prompt, sdkOptions)` — the runtime
40
+ forwards them to the Agent SDK. In an **interactive** stage, the options
41
+ are silently ignored; drive behaviour via `chatFlags` in `clientOpts`
42
+ instead.
43
+
44
+ ```ts
45
+ // ❌ Reference only — do not call query() like this from a workflow.
46
+ import { query } from "@anthropic-ai/claude-agent-sdk";
47
+
48
+ const result = query({
49
+ prompt: (ctx.inputs.prompt ?? ""),
50
+ options: {
51
+ // Model selection
52
+ model: "claude-opus-4-6", // Full model ID or alias ("opus", "sonnet", "haiku")
53
+ effort: "high", // "low", "medium", "high", "xhigh", "max" (max is Opus 4.6/4.7 only)
54
+ thinking: { type: "adaptive" }, // Default for supported models; or { type: "enabled", budgetTokens: N }
55
+ maxTurns: 50, // Maximum conversation turns
56
+ maxBudgetUsd: 5.0, // Spending cap in USD
57
+
58
+ // Permissions
59
+ permissionMode: "acceptEdits", // "default", "dontAsk", "acceptEdits", "bypassPermissions", "plan"
60
+
61
+ // Tools — base set of available built-in tools
62
+ tools: ["Read", "Write", "Edit", "Bash", "Grep", "Glob"], // or { type: "preset", preset: "claude_code" } for all defaults
63
+ allowedTools: ["Read", "Write", "Edit", "Bash", "Grep", "Glob"], // auto-allowed without prompting
64
+ disallowedTools: ["AskUserQuestion"], // removed from model's context
65
+
66
+ // System prompt — string or preset with additions
67
+ systemPrompt: "You are a senior security auditor...",
68
+ // Or: { type: "preset", preset: "claude_code", append: "Always explain your reasoning." }
69
+
70
+ // Structured output
71
+ outputFormat: {
72
+ type: "json_schema",
73
+ schema: {
74
+ type: "object",
75
+ properties: {
76
+ tasks: { type: "array", items: { type: "string" } },
77
+ },
78
+ },
79
+ },
80
+
81
+ // Subagents — Record<string, AgentDefinition> keyed by name
82
+ agents: {
83
+ worker: { description: "Implement tasks", prompt: "You are a task implementer...", tools: ["Read", "Write", "Edit", "Bash"] },
84
+ },
85
+ agent: "worker", // Main thread agent name (optional)
86
+
87
+ // MCP servers
88
+ mcpServers: {
89
+ "my-server": { command: "node", args: ["server.js"] },
90
+ },
91
+
92
+ // Session continuity
93
+ resume: previousSessionId, // Resume a prior session
94
+ forkSession: true, // When true with resume, forks to new session
95
+ persistSession: true, // Persist session to disk (default: true)
96
+
97
+ // Sandbox — isolated command execution
98
+ sandbox: { enabled: true, autoAllowBashIfSandboxed: true },
99
+
100
+ // Beta features
101
+ betas: ["context-1m-2025-08-07"], // 1M context window (Sonnet 4/4.5 only)
102
+ },
103
+ });
104
+ ```
105
+
106
+ ### `s.session.query()` usage
107
+
108
+ `s.session.query()` sends text to the Claude pane, verifies delivery, and waits for output stabilization. It uses
109
+ the pane ID from `s.paneId` automatically. Call it inside the stage callback:
110
+
111
+ ```ts
112
+ import { extractAssistantText } from "@bastani/atomic/workflows";
113
+
114
+ await ctx.stage({ name: "..." }, {}, {}, async (s) => {
115
+ const result = await s.session.query("Your prompt");
116
+ // extractAssistantText(result, 0) — extract assistant text from the result
117
+ const text = extractAssistantText(result, 0);
118
+ s.save(s.sessionId);
119
+ });
120
+ ```
121
+
122
+ For **headless stages**, SDK options (such as `permissionMode`, `agent`,
123
+ `allowDangerouslySkipPermissions`) can be passed directly as the second
124
+ argument to `s.session.query()`:
125
+
126
+ ```ts
127
+ await ctx.stage({ name: "..." }, {}, {}, async (s) => {
128
+ const result = await s.session.query("Your prompt", {
129
+ permissionMode: "bypassPermissions",
130
+ allowDangerouslySkipPermissions: true,
131
+ agent: "worker",
132
+ });
133
+ const text = extractAssistantText(result, 0);
134
+ s.save(s.sessionId);
135
+ });
136
+ ```
137
+
138
+ ### Claude hooks
139
+
140
+ Hooks intercept tool usage, session events, and context management. The `hooks` option is `Partial<Record<HookEvent, HookCallbackMatcher[]>>` — each event maps to an array of matchers with callback arrays:
141
+
142
+ ```ts
143
+ const result = query({
144
+ prompt: (ctx.inputs.prompt ?? ""),
145
+ options: {
146
+ hooks: {
147
+ PreToolUse: [{
148
+ matcher: (input) => input.tool_name === "Bash", // Optional — filter which events trigger this hook
149
+ hooks: [async (input, toolUseID, { signal }) => {
150
+ // input.tool_name, input.tool_input available
151
+ if (input.tool_input?.command?.includes("rm -rf")) {
152
+ return { decision: "deny", reason: "Dangerous command" };
153
+ }
154
+ return { decision: "allow" };
155
+ // Return values: { decision: "allow" | "deny" | "ask" | "defer" }
156
+ }],
157
+ }],
158
+ PostToolUse: [{
159
+ hooks: [async (input) => {
160
+ // React after a tool completes
161
+ console.log(`Tool ${input.tool_name} completed`);
162
+ }],
163
+ }],
164
+ Stop: [{
165
+ hooks: [async (input) => {
166
+ // Called when the agent wants to stop
167
+ }],
168
+ }],
169
+ PreCompact: [{
170
+ hooks: [async (input) => {
171
+ // Before context compaction — inject durable context
172
+ return { additionalContext: "Remember: always run tests after edits." };
173
+ }],
174
+ }],
175
+ },
176
+ },
177
+ });
178
+ ```
179
+
180
+ **Hook events** (most commonly used): `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `Stop`, `SessionStart`, `SessionEnd`, `PreCompact`, `PostCompact`, `SubagentStart`, `SubagentStop`, `Notification`, `PermissionRequest`, `PermissionDenied`, `Elicitation`, `ElicitationResult`, `ConfigChange`, `FileChanged`, `CwdChanged`.
181
+
182
+ ## Copilot SDK
183
+
184
+ ### Session options (`sessionOpts` — 3rd arg to `ctx.stage()`)
185
+
186
+ All `client.createSession()` options are passed as `sessionOpts`. The runtime
187
+ forwards them to `client.createSession()`. `onPermissionRequest` defaults to
188
+ `approveAll` when not specified.
189
+
190
+ ```ts
191
+ import { approveAll, defineTool } from "@github/copilot-sdk";
192
+
193
+ await ctx.stage({ name: "plan" }, {}, {
194
+ // Model selection
195
+ model: "claude-sonnet-4.6",
196
+ reasoningEffort: "high",
197
+
198
+ // System prompt
199
+ systemMessage: "You are a security auditor...",
200
+
201
+ // Custom tools
202
+ tools: [
203
+ defineTool({
204
+ name: "check-coverage",
205
+ description: "Check test coverage",
206
+ parameters: { type: "object", properties: { path: { type: "string" } } },
207
+ execute: async (params) => ({ content: "Coverage: 85%" }),
208
+ }),
209
+ ],
210
+
211
+ // Permissions (defaults to approveAll if omitted)
212
+ onPermissionRequest: approveAll,
213
+
214
+ // User input
215
+ onUserInputRequest: async (request) => {
216
+ return "User's response";
217
+ },
218
+ onElicitationRequest: async (request) => {
219
+ return { action: "submit", values: { choice: "option-a" } };
220
+ },
221
+
222
+ // Hooks
223
+ hooks: {
224
+ onPreToolUse: (event) => { /* before tool */ },
225
+ onPostToolUse: (event) => { /* after tool */ },
226
+ onSessionStart: (event) => { /* session started */ },
227
+ onSessionEnd: (event) => { /* session ended */ },
228
+ onErrorOccurred: (event) => { /* error handling */ },
229
+ },
230
+
231
+ // Advanced — auto-manage context via compaction. Pass an InfiniteSessionConfig,
232
+ // not a boolean. See docs/copilot-cli/sdk.md for the full threshold surface.
233
+ infiniteSessions: {
234
+ enabled: true,
235
+ backgroundCompactionThreshold: 0.8, // start compacting at 80% window usage
236
+ bufferExhaustionThreshold: 0.95, // block at 95% until compaction completes
237
+ },
238
+ }, async (s) => {
239
+ await s.session.send({ prompt: (s.inputs.prompt ?? "") });
240
+ s.save(await s.session.getMessages());
241
+ });
242
+ ```
243
+
244
+ ### Copilot permission modes
245
+
246
+ ```ts
247
+ // Approve everything (autonomous) — this is the default
248
+ await ctx.stage({ name: "plan" }, {}, { onPermissionRequest: approveAll }, async (s) => {
249
+ await s.session.send({ prompt: (s.inputs.prompt ?? "") });
250
+ s.save(await s.session.getMessages());
251
+ });
252
+
253
+ // Custom permission handler
254
+ await ctx.stage({ name: "plan" }, {}, {
255
+ onPermissionRequest: async (request) => {
256
+ // request.kind: "shell" | "write" | "read" | "mcp" | "custom-tool" | "url" | "memory" | "hook"
257
+ switch (request.kind) {
258
+ case "shell":
259
+ return request.command?.includes("rm")
260
+ ? { kind: "denied-permanently", reason: "Dangerous" }
261
+ : { kind: "approved" };
262
+ case "write":
263
+ return { kind: "approved" };
264
+ default:
265
+ return { kind: "approved" };
266
+ }
267
+ },
268
+ }, async (s) => {
269
+ await s.session.send({ prompt: (s.inputs.prompt ?? "") });
270
+ s.save(await s.session.getMessages());
271
+ });
272
+ ```
273
+
274
+ ## OpenCode SDK
275
+
276
+ ### Client options (`clientOpts` — 2nd arg to `ctx.stage()`)
277
+
278
+ The `baseUrl` is auto-injected by the runtime. Pass any additional client
279
+ options (such as `directory`) via `clientOpts`:
280
+
281
+ ```ts
282
+ await ctx.stage({ name: "..." }, {
283
+ directory: "/path/to/project", // Override working directory
284
+ }, {}, async (s) => {
285
+ // s.client is the OpencodeClient, already connected
286
+ });
287
+ ```
288
+
289
+ ### Session options (`sessionOpts` — 3rd arg to `ctx.stage()`)
290
+
291
+ These are forwarded to `client.session.create()`. Use them to set a title,
292
+ parentID, or workspaceID for the session:
293
+
294
+ ```ts
295
+ await ctx.stage({ name: "..." }, {}, {
296
+ title: "Feature implementation",
297
+ parentID: "parent-session-id",
298
+ workspaceID: "workspace-id",
299
+ }, async (s) => {
300
+ // s.session is the created OpencodeSession, s.session.id is the session ID
301
+ });
302
+ ```
303
+
304
+ ### Session prompting
305
+
306
+ Use `s.client` and `s.session.id` inside the callback:
307
+
308
+ ```ts
309
+ await ctx.stage({ name: "implement" }, {}, {}, async (s) => {
310
+ // Basic prompt
311
+ const result = await s.client.session.prompt({
312
+ sessionID: s.session.id,
313
+ parts: [{ type: "text", text: (s.inputs.prompt ?? "") }],
314
+ });
315
+
316
+ // Structured output
317
+ const structured = await s.client.session.prompt({
318
+ sessionID: s.session.id,
319
+ parts: [{ type: "text", text: "List endpoints as JSON" }],
320
+ format: {
321
+ type: "json_schema",
322
+ schema: { type: "object", properties: { endpoints: { type: "array" } } },
323
+ retryCount: 3,
324
+ },
325
+ });
326
+
327
+ // No-reply context injection
328
+ await s.client.session.prompt({
329
+ sessionID: s.session.id,
330
+ parts: [{ type: "text", text: "Background context..." }],
331
+ noReply: true,
332
+ });
333
+
334
+ s.save(result.data!);
335
+ });
336
+ ```
337
+
338
+ ### OpenCode session management
339
+
340
+ ```ts
341
+ await ctx.stage({ name: "..." }, {}, {}, async (s) => {
342
+ // Select session in TUI (auto-called by runtime, but can be called again)
343
+ await s.client.tui.selectSession({ sessionID: s.session.id });
344
+
345
+ // Fork session
346
+ await s.client.session.fork({ sessionID: s.session.id, messageID: "..." });
347
+
348
+ // Abort
349
+ await s.client.session.abort({ sessionID: s.session.id });
350
+
351
+ // Session messages
352
+ const messages = await s.client.session.messages({ sessionID: s.session.id });
353
+ });
354
+ ```
355
+
356
+ ### OpenCode event streaming
357
+
358
+ ```ts
359
+ await ctx.stage({ name: "..." }, {}, {}, async (s) => {
360
+ const unsubscribe = await s.client.event.subscribe((event) => {
361
+ switch (event.type) {
362
+ case "session.updated":
363
+ console.log("Session updated");
364
+ break;
365
+ case "message.created":
366
+ console.log("New message");
367
+ break;
368
+ }
369
+ });
370
+ });
371
+ ```
372
+
373
+ ### OpenCode permissions
374
+
375
+ ```ts
376
+ await ctx.stage({ name: "..." }, {}, {}, async (s) => {
377
+ // Handle permission requests
378
+ await s.client.session.permission({
379
+ sessionID: s.session.id,
380
+ permissionID: "...",
381
+ approved: true,
382
+ });
383
+ });
384
+ ```