thincoder 0.12.42 → 0.12.43

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,26 @@
2
2
 
3
3
  本文件记录 ThinCoder CLI 的发布历史。格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/),版本遵循[语义化版本](https://semver.org/lang/zh-CN/)。
4
4
 
5
+ ## [0.12.43] — 2026-08-25
6
+
7
+ ### Added
8
+
9
+ - **评审超时可配置**:`agent.advisor.timeoutMs`(默认 600s,原固定 300s)——运行期读取,非法值(0/负数/字符串)回退默认;长评审不再被固定墙钟截断
10
+ - **主 agent 轮次上限默认 100 → 200**:大任务(多文件重构、修复-验证循环)不轻易撞墙
11
+
12
+ ### Changed
13
+
14
+ - **工具输出限制全链路 16K → 64K(65536)**:落盘阈值 `TOOL_RESULT_OFFLOAD_LIMIT`/`TOOL_RESULT_PREVIEW`、advisor 内部截断 `MAX_RESULT_CHARS` 全部放宽——大输出(advisor 评审、大文件读取)不再被过早落盘/截断
15
+ - **轮末探索蒸馏异步化**:回合结束信号先行(TUI 状态栏立即恢复,不再等第二次静默 LLM 调用);蒸馏 promise 挂 `agent._pendingDistill`,下一轮开头 await(摘要必在下一轮 LLM 调用前落位),退出前 bounded flush(≤5s);`onDistilled` 回调触发压缩版落盘
16
+
17
+ ### Removed
18
+
19
+ - **`sleep` 工具删除**:编程场景零真实使用(会话历史 0 次调用),且工具说明误导模型在同步工具(advisor/subagent)后 sleep 空等——白耗 10-300 秒;等待需求改走 bash 内联命令;内部速率限制/重试退避(`_rateHooks.sleep`)不受影响
20
+
21
+ ### Fixed
22
+
23
+ - 工具输出落盘失败回退截断对齐 64K;旧阈值残留自动化断言(`MAX_RESULT_CHARS` 导出 + import 断言、helpers/run.mjs 边界匹配无残留)
24
+
5
25
  ## [0.12.42] — 2026-08-24
6
26
 
7
27
  ### Changed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thincoder",
3
- "version": "0.12.42",
3
+ "version": "0.12.43",
4
4
  "description": "Thin coding agent - zero dependencies, no build step, Node.js native. Sharp code, zero bloat.",
5
5
  "keywords": [
6
6
  "ai",
@@ -29,8 +29,8 @@ export const ADVISOR_THINKING_PLACEHOLDER = "\n[thinking…]\n"
29
29
  // Context window limits
30
30
  const MAX_CONTEXT_TOKENS = 120_000 // Reserve headroom to avoid OOM
31
31
  const TOOL_TIMEOUT_MS = 30_000 // single tool timeout
32
- const REVIEW_TIMEOUT_MS = 300_000 // whole review timeout
33
- const MAX_RESULT_CHARS = 12_000 // tool result truncation (line-aware)
32
+ const REVIEW_TIMEOUT_MS = 600_000 // whole review timeout (10 minutes)
33
+ export const MAX_RESULT_CHARS = 64 * 1024 // tool result truncation (line-aware; 64K, aligned with main offload limit)
34
34
  const MAX_UNFIXED_DISPLAY = 10 // unfixed issues shown in the cap message
35
35
  const MAX_KEY_FILES_IN_COMPACTION = 5 // files named in the compaction summary
36
36
 
@@ -156,9 +156,13 @@ async function runAdvisorToolLoop(provider, messages, onOutput, signal, agent, c
156
156
  // Interrupted (Ctrl+I) — stop immediately instead of spinning a fresh uncancellable signal
157
157
  if (signal?.aborted) return renderTimeline(timeline, "Advisor: interrupted.")
158
158
 
159
- // Check review timeout (5 minutes)
160
- if (Date.now() - startTime > REVIEW_TIMEOUT_MS) {
161
- return renderTimeline(timeline, `Advisor: review timeout after ${Math.round(REVIEW_TIMEOUT_MS / 1000)}s. Partial results may be available. Try again with a narrower scope.`)
159
+ // Check review timeout (10 minutes by default; agent.advisor.timeoutMs overrides)
160
+ // 运行时校验(设计评审 #1,2026-08-24):手写 config.json 的非法值(0/负数/字符串)
161
+ // 不得静默禁用或立即触发超时——非法一律回退默认。
162
+ const cfg = agent.config?.advisor?.timeoutMs
163
+ const timeoutMs = (Number.isFinite(cfg) && cfg > 0) ? cfg : REVIEW_TIMEOUT_MS
164
+ if (Date.now() - startTime > timeoutMs) {
165
+ return renderTimeline(timeline, `Advisor: review timeout after ${Math.round(timeoutMs / 1000)}s. Partial results may be available. Try again with a narrower scope.`)
162
166
  }
163
167
 
164
168
  if (++turns > MAX_ADVISOR_TURNS) {
@@ -8,7 +8,7 @@ import { writeFile, mkdir, readdir, stat, unlink } from "node:fs/promises"
8
8
  import { join } from "node:path"
9
9
  import { execSync } from "node:child_process"
10
10
 
11
- export const DEFAULT_MAX_TURNS = 100
11
+ export const DEFAULT_MAX_TURNS = 200
12
12
  export const DEFAULT_SUBAGENT_TURNS = 100
13
13
  export const DEFAULT_GOAL_TURNS = 200
14
14
  export const MIN_REPORT_CHARS = 200
@@ -20,8 +20,8 @@ export const REPORT_CONTINUATION =
20
20
  "3. How you verified (tests run, commands executed, with results)\n" +
21
21
  "4. Anything left undone or worth follow-up"
22
22
 
23
- const TOOL_RESULT_OFFLOAD_LIMIT = 16_000
24
- const TOOL_RESULT_PREVIEW = 2_000
23
+ const TOOL_RESULT_OFFLOAD_LIMIT = 64 * 1024 // 65536 chars — offload only above 64K (2026-08-24)
24
+ const TOOL_RESULT_PREVIEW = 64 * 1024 // chars shown inline when offloaded (aligns with CLI/VS Code webview)
25
25
 
26
26
  /** Offload-dir write-time self-cleanup retention window (2026-08-21): files older than 3 days are deleted on the next offload. */
27
27
  export const TMP_RETENTION_MS = 3 * 24 * 3600 * 1000
@@ -65,7 +65,7 @@ export async function cleanupOldToolResults(dir) {
65
65
  }
66
66
  }
67
67
 
68
- /** Offload oversized tool results (>16k chars) to disk, returning a preview + file path.
68
+ /** Offload oversized tool results (>64K chars) to disk, returning a preview + file path.
69
69
  * Writes trigger write-time self-cleanup of the offload dir first (dir param overridable for tests). */
70
70
  export async function offloadToolResult(text, callId, dir = join(configDir, "tool-results")) {
71
71
  if (text.length <= TOOL_RESULT_OFFLOAD_LIMIT) return text
package/src/agent.mjs CHANGED
@@ -97,12 +97,21 @@ export function createAgent({
97
97
  _compressFailures: 0,
98
98
  _emptyRetries: 0, // empty-response retry budget (per-run; reset on a fresh user turn)
99
99
  _runStartHistoryLen: 0, // machine-line length at the start of the current run — end-of-run exploration distillation slices from here
100
+ _pendingDistill: null, // in-flight end-of-run exploration distillation (SEND-STALL-DISTILL §2.1) — awaited at next run start / TUI exit flush
100
101
  _currentTurn: 0, _maxTurns: 100, // turn counter for status bar display
101
102
  }
102
103
  }
103
104
 
104
105
  /** Run the agent loop: LLM ↔ tool-call cycle until task completion or turn limit. Returns final text content. */
105
106
  export async function runAgent(agent, input, callbacks = {}, { depth = 0, signal, maxTurns: overrideTurns, resume = false } = {}) {
107
+ // Previous run's async exploration distillation must settle before this run pushes new
108
+ // input (SEND-STALL-DISTILL §2.2, N1): the compressed machine line is this run's starting
109
+ // point — await BEFORE prepareRun, or the history replacement would wipe the new input.
110
+ if (agent._pendingDistill) {
111
+ const p = agent._pendingDistill
112
+ agent._pendingDistill = null
113
+ await p
114
+ }
106
115
  const { maxTurns, threshold, tools, toolSchemas, toolByName, systemPrompt } = await prepareRun(
107
116
  agent, input, callbacks,
108
117
  { depth, signal, overrideTurns, resume, systemPrompt: SYSTEM_PROMPT, disciplineRules: DISCIPLINE_RULES, mainOverlay: MAIN_OVERLAY },
@@ -309,9 +318,13 @@ export async function runAgent(agent, input, callbacks = {}, { depth = 0, signal
309
318
  if (cr.action === "continue") continue
310
319
  if (depth === 0) {
311
320
  // End-of-run exploration distillation (CONTEXT-COMPACTION §5): this run's inline
312
- // exploration results become one semantic note before the final return. Silent (N3):
313
- // distillation failure must never block the return or lose history.
314
- try { await summarizeRunExplorations(agent, callbacks, signal) } catch { /* silent (N3) */ }
321
+ // exploration results become one semantic note before the final return. Async
322
+ // (SEND-STALL-DISTILL §2.1): the turn-end signal goes out first the promise hangs
323
+ // on agent._pendingDistill and settles at the next runAgent's start or the TUI's
324
+ // exit flush. Silent (N3): distillation failure must never block the return or lose
325
+ // history.
326
+ const distill = summarizeRunExplorations(agent, callbacks, signal).catch(() => {})
327
+ agent._pendingDistill = distill
315
328
  }
316
329
  return cr.content
317
330
  }
package/src/config.mjs CHANGED
@@ -39,7 +39,7 @@ export const PROVIDER_PRESETS = {
39
39
  export const DEFAULTS = {
40
40
  activeModel: null, // optional: override provider.model (set via /model picker or /model provider:model)
41
41
  agent: {
42
- maxTurns: 100,
42
+ maxTurns: 200,
43
43
  subagentTurns: 100,
44
44
  subagentModel: null, // default subagent model: "provider:model" | provider name | model name (parent provider); null = inherit parent provider
45
45
  subagentModels: {}, // per-type override: { explore, plan, coder, "eng-coder" } — priority: subagent tool model arg > this[role] > subagentModel > parent provider
@@ -52,7 +52,7 @@ export const DEFAULTS = {
52
52
  consultTurns: 40, // per-consultant tool-turn budget (diagnosis tasks)
53
53
  consultTimeoutMs: 600000, // wall-clock ceiling per consultant (10min)
54
54
  streamRules: [], // time-traveling stream rules: [{ pattern: "regex", message: "reminder", action: "abort"|"warn", repeat: "always"|"once" }]
55
- advisor: { guard: false }, // code review is always available; guard: true pushes completion back until reviewed (opt-in). Also accepts provider/model/thinking/reasoningEffort overrides. Deprecated: enabled (2026-08-21)
55
+ advisor: { guard: false }, // code review is always available; guard: true pushes completion back until reviewed (opt-in). Also accepts provider/model/thinking/reasoningEffort/timeoutMs overrides. Deprecated: enabled (2026-08-21)
56
56
  autoThink: false, // auto-classify task difficulty and set reasoning effort per-turn
57
57
  engineering: false, // strict methodology enforcement — read METHODOLOGY.md, design-before-code
58
58
  },
package/src/context.mjs CHANGED
@@ -424,8 +424,9 @@ async function distillExplorations(history, start, provider, signal) {
424
424
  * End-of-run exploration distillation (runAgent's final return). Shrinks the MACHINE line
425
425
  * (agent.history) only; agent._fullHistory is never touched. Triggers when this run added ≥3
426
426
  * exploration tool results; on LLM failure it silently keeps the original history (N3).
427
- * `callbacks` is accepted for call-site parity with the other lifecycle hooks — the distillation
428
- * is silent by design and never streams (D11).
427
+ * The distillation itself is silent and never streams (D11); `callbacks.onDistilled` fires
428
+ * ONLY after the replacement actually lands (never on no-op/failure) — callers persist the
429
+ * compressed session (SEND-STALL-DISTILL §2.3).
429
430
  */
430
431
  export async function summarizeRunExplorations(agent, callbacks, signal) {
431
432
  const next = await distillExplorations(agent.history, agent._runStartHistoryLen ?? 0, agent.provider, signal)
@@ -435,4 +436,7 @@ export async function summarizeRunExplorations(agent, callbacks, signal) {
435
436
  // Invalidate so the next compaction check re-estimates instead of over-counting stale history.
436
437
  agent._lastPromptTokens = null
437
438
  agent._usageAtLen = null
439
+ // The compressed machine line must reach the disk: the run's own save already happened,
440
+ // so without this hook the async distill would leave the session un-compressed on exit.
441
+ callbacks.onDistilled?.()
438
442
  }
@@ -25,7 +25,7 @@ Tool routing — use the dedicated tool, not bash:
25
25
  - **JavaScript** → `execute` (inline code; or `scriptFile`+`nodeArgs` for `node <file>` / `node --test` / `node --check`). Never `bash node -e`.
26
26
  - **File reads/searches** → `read` / `grep` / `ls` / `glob` — never `cat` / `type` / `findstr` / `dir` / shell-grep.
27
27
  - **File mutations** → `write` / `edit` / `apply_patch` / `hashline_edit` / `insert_after` / `file_ops` (move/copy/rename) / `delete`.
28
- - **Process / time / sleep / tree** → the dedicated tools (never `tasklist`/`ps`/`date`/`tree` via bash).
28
+ - **Process / time / tree** → the dedicated tools (never `tasklist`/`ps`/`date`/`tree` via bash); waiting (e.g. `sleep`/`timeout`) is fine via bash when truly needed.
29
29
  - Each tool's description carries a "Route to X instead of bash" mapping.
30
30
  - **bash IS correct for**: package-manager/CLI subprocesses (`npm`/`vsce`/`ovsx`, git-CLI-only flags the tool lacks), servers, interactive/TTY programs, and one-off shell pipelines no dedicated tool expresses.
31
31
 
@@ -10,7 +10,7 @@ import { checklistTool } from "./checklist.mjs";
10
10
  import { lintTool } from "./linter.mjs";
11
11
  import { lspTool } from "./lsp.mjs";
12
12
  import { executeTool } from "./execute.mjs";
13
- import { fileOpsTool, processTool, getCurrentTimeTool, sleepTool } from "./ops.mjs";
13
+ import { fileOpsTool, processTool, getCurrentTimeTool } from "./ops.mjs";
14
14
  import { treeTool } from "./tree.mjs";
15
15
 
16
16
  export const builtinTools = [
@@ -19,7 +19,7 @@ export const builtinTools = [
19
19
  websearchTool, lsTool, fetchTool, deleteTool,
20
20
  gitTool, questionTool,
21
21
  checklistTool, lintTool, lspTool, executeTool,
22
- fileOpsTool, processTool, getCurrentTimeTool, sleepTool,
22
+ fileOpsTool, processTool, getCurrentTimeTool,
23
23
  treeTool,
24
24
  ];
25
25
 
@@ -29,6 +29,6 @@ export {
29
29
  websearchTool, lsTool, fetchTool, deleteTool,
30
30
  gitTool, questionTool,
31
31
  checklistTool, lintTool, lspTool, executeTool,
32
- fileOpsTool, processTool, getCurrentTimeTool, sleepTool,
32
+ fileOpsTool, processTool, getCurrentTimeTool,
33
33
  treeTool,
34
34
  };
package/src/tools/ops.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * ops.mjs — operational tools: file_ops (move/copy/rename), process (list),
3
- * get_current_time, sleep. Each exists so the model reaches for a dedicated tool
3
+ * get_current_time. Each exists so the model reaches for a dedicated tool
4
4
  * instead of shelling out to `bash` for the same operation (parity with thinworker).
5
5
  */
6
6
  import { DESC, resolveInCwd, truncate } from "./shared.mjs"
@@ -111,32 +111,4 @@ export const getCurrentTimeTool = {
111
111
  const days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
112
112
  return `Date: ${now.toISOString()} (UTC)\nTimezone: ${tz}\nWeekday: ${days[now.getDay()]}\nLocal: ${now.toLocaleString()}`
113
113
  },
114
- }
115
-
116
- // ─── sleep ─────────────────────────────────────────────────────
117
-
118
- export const sleepTool = {
119
- name: "sleep",
120
- description: DESC("sleep"),
121
- parameters: {
122
- type: "object",
123
- properties: {
124
- seconds: { type: "number", description: "Seconds to wait (1-300)" },
125
- reason: { type: "string", description: "Why wait (shown to the user)" },
126
- },
127
- required: ["seconds"],
128
- },
129
- readonly: true,
130
- async execute({ seconds, reason }, ctx) {
131
- const raw = Number(seconds)
132
- const n = Number.isFinite(raw) ? Math.min(Math.max(Math.round(raw), 1), 300) : 1
133
- await new Promise((resolve, reject) => {
134
- const t = setTimeout(resolve, n * 1000)
135
- if (ctx?.signal) {
136
- if (ctx.signal.aborted) { clearTimeout(t); reject(new Error("aborted")) }
137
- else ctx.signal.addEventListener("abort", () => { clearTimeout(t); reject(new Error("aborted")) }, { once: true })
138
- }
139
- })
140
- return `Waited ${n}s${reason ? ` (${reason})` : ""}`
141
- },
142
114
  }
@@ -5,6 +5,11 @@ import { ansi, C } from "./ansi.mjs"
5
5
  import { formatToolSummary } from "./tool-summaries.mjs"
6
6
  import { ADVISOR_THINKING_PLACEHOLDER, resolveAdvisorProvider } from "../advisor/run.mjs"
7
7
 
8
+ /** Exit-flush bound for the async end-of-run distillation (SEND-STALL-DISTILL §2.5):
9
+ * wait at most this long for the in-flight distill before the final session save —
10
+ * never let shutdown hang on the background summary call. */
11
+ const DISTILL_FLUSH_TIMEOUT_MS = 5000
12
+
8
13
  /** Tool execution start timestamps (performance.now ms), keyed by tool name. */
9
14
  const _toolTicks = Object.create(null)
10
15
 
@@ -325,6 +330,12 @@ export async function runAgentTurn(ctx, text) {
325
330
  onCompress: () => {
326
331
  pushLine(" [context] Context too long, auto-compacted (early conversation summarized by LLM, task state preserved)", C.warn)
327
332
  },
333
+ // Async distillation landed (SEND-STALL-DISTILL §2.3): the machine line was replaced by
334
+ // the compressed version — persist it so the session file ends up compressed. Silent:
335
+ // a save failure must never surface after the turn already returned.
336
+ onDistilled: () => {
337
+ try { saveSessionImpl(agent, state.lines) } catch { /* 静默 */ }
338
+ },
328
339
  onUsage: (usage) => {
329
340
  state.tokens.prompt += usage.prompt_tokens ?? 0
330
341
  state.tokens.completion += usage.completion_tokens ?? 0
@@ -429,6 +440,10 @@ export async function runAgentTurn(ctx, text) {
429
440
  state._advisorBlocks = []
430
441
  state.controller = null
431
442
  state.status = "Ready"
443
+ // FR1: status bar must recover immediately — the awaits below (title-gen, distill flush,
444
+ // save) may take seconds and the 1s ticker is already stopped, so render NOW or the bar
445
+ // keeps showing the stale "Processing..." until the turn function fully unwinds.
446
+ render()
432
447
  // Auto-collapse todo panel when all tasks done (matching kimi-code TUI; agent.tasks are preserved)
433
448
  if (state.tasks.length > 0 && state.tasks.every((t) => t.status === "done")) {
434
449
  state.tasks = []
@@ -448,6 +463,17 @@ export async function runAgentTurn(ctx, text) {
448
463
  // Title generation failure is non-fatal
449
464
  }
450
465
  }
466
+ // Exit flush (SEND-STALL-DISTILL §2.5): the round-end distillation runs async — before
467
+ // the final save, give it a bounded window to land the compressed history on disk.
468
+ // The next turn's runAgent would await it anyway; this covers the real exit path
469
+ // (no next turn). Bounded: never let shutdown wait longer than the timeout.
470
+ // NOTE: the promise is NOT detached (no `agent._pendingDistill = null` here) — a submit
471
+ // during this window starts the next runAgentTurn concurrently, and its runAgent start
472
+ // MUST still see the in-flight distill to await it BEFORE pushing input (N1). If the
473
+ // flush times out, the next runAgent's start-await takes over — safe by construction.
474
+ if (agent._pendingDistill) {
475
+ await Promise.race([agent._pendingDistill, new Promise((r) => setTimeout(r, ctx.distillFlushTimeoutMs ?? DISTILL_FLUSH_TIMEOUT_MS))])
476
+ }
451
477
  // Save session after every turn (survives crashes)
452
478
  try {
453
479
  saveSessionImpl(agent, state.lines)
@@ -234,8 +234,8 @@ export async function handleConfigCommand(ctx, args = []) {
234
234
  while (running) {
235
235
  const consultCount = (ac.consultModels ?? []).length
236
236
  const mainEntries = [
237
- { type: "header", text: `proxy=${proxySummary()} | maxTurns=${ac.maxTurns ?? 100} | compactThreshold=${ac.compactThreshold ?? 100000} | verifyGuard=${ac.verifyGuard === true ? "on" : "off"} | consult=${consultCount} model(s) | embedding=${agent.memory?.embedder ? "on" : "off"}` },
238
- { type: "item", text: `agent.maxTurns = ${ac.maxTurns ?? 100}`, action: "agent.maxTurns" },
237
+ { type: "header", text: `proxy=${proxySummary()} | maxTurns=${ac.maxTurns ?? 200} | compactThreshold=${ac.compactThreshold ?? 100000} | verifyGuard=${ac.verifyGuard === true ? "on" : "off"} | consult=${consultCount} model(s) | embedding=${agent.memory?.embedder ? "on" : "off"}` },
238
+ { type: "item", text: `agent.maxTurns = ${ac.maxTurns ?? 200}`, action: "agent.maxTurns" },
239
239
  { type: "item", text: `agent.subagentTurns = ${ac.subagentTurns ?? 100}`, action: "agent.subagentTurns" },
240
240
  { type: "item", text: `agent.compactThreshold = ${ac.compactThreshold ?? 100000}${agent.config?.agent?.compactThresholdAuto ? " (auto)" : ""}`, action: "agent.compactThreshold" },
241
241
  { type: "item", text: `agent.verifyGuard = ${ac.verifyGuard === true ? "on" : "off"}`, action: "agent.verifyGuard" },
@@ -255,7 +255,7 @@ export async function handleConfigCommand(ctx, args = []) {
255
255
  pushLabel("❯ Config", ansi.bold + C.tool)
256
256
  pushLine(`Active: ${agent.activeProvider} / ${agent.provider.model}`, C.dim)
257
257
  pushLine(`Key: ${maskKey(agent.provider.apiKey)}`, C.dim)
258
- pushLine(`agent.maxTurns: ${ac.maxTurns ?? 100}`, C.dim)
258
+ pushLine(`agent.maxTurns: ${ac.maxTurns ?? 200}`, C.dim)
259
259
  pushLine(`agent.subagentTurns: ${ac.subagentTurns ?? 100}`, C.dim)
260
260
  pushLine(`agent.compactThreshold: ${ac.compactThreshold ?? 100000}${agent.config?.agent?.compactThresholdAuto ? " (auto)" : ""}`, C.dim)
261
261
  pushLine(`agent.verifyGuard: ${ac.verifyGuard === true ? "on" : "off"}`, C.dim)
@@ -304,7 +304,7 @@ export async function handleConfigCommand(ctx, args = []) {
304
304
  // Numeric config items
305
305
  const label = choice.action
306
306
  const isTimeout = label === "agent.consultTimeoutMs"
307
- const current = label === "agent.maxTurns" ? (ac.maxTurns ?? 100)
307
+ const current = label === "agent.maxTurns" ? (ac.maxTurns ?? 200)
308
308
  : label === "agent.subagentTurns" ? (ac.subagentTurns ?? 100)
309
309
  : label === "agent.compactThreshold" ? (ac.compactThreshold ?? 100000)
310
310
  : label === "agent.consultTurns" ? (ac.consultTurns ?? 40)
@@ -1,5 +0,0 @@
1
- Wait a number of seconds before continuing. Use to wait for a web page to load, an async task to finish, or to respect a rate limit — cheaper than repeatedly polling.
2
-
3
- Parameters:
4
- - seconds (required): how many seconds to wait (1-300)
5
- - reason (optional): why you are waiting (shown to the user)