Skip to content

feat(sdk): add process surface with handles, log replay and reconnect - #191

Open
MDzaja wants to merge 5 commits into
mainfrom
feat/process-v2
Open

feat(sdk): add process surface with handles, log replay and reconnect#191
MDzaja wants to merge 5 commits into
mainfrom
feat/process-v2

Conversation

@MDzaja

@MDzaja MDzaja commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds a process management surface to all five SDKs (TypeScript, Python, Go, Java, Ruby): start background processes, stream and replay their output, reconnect from any client, and manage their lifecycle. Requires an updated daemon; all existing SDK methods are unchanged and keep working against older daemons.

New API

Process client (sandbox.process):

  • start(options)ProcessHandle — background process you supervise yourself. Logs are retained after exit until cleanup().
  • run(options) → collected result — one-shot: waits for exit and returns stdout/stderr + exit metadata (optional onStdout/onStderr streaming callbacks, waitTimeoutMs with partial results). Output is retained briefly after exit.
  • get(id) / connect(id) — attach to any process by id, running or finished, from any client. This is the reconnect story: persist the sandbox id + process id, reconnect later, replay everything.
  • list(filter) — server-side listing of processes (state/kind/name/session filters), including ones started by other clients.
  • exec(...) — alias of executeCommand.

ProcessHandle: logs (cursor-paged replay with truncatedHead/CURSOR_EXPIRED recovery), streamLogs (replay-then-live), stdin/stdinEof, kill, resize, wait, output, cleanup, attachTerminal (interactive PTY socket for kind: 'pty' processes).

Code interpreter ergonomics: scoped contexts that always clean up — withContext(fn) (TS/Java/Go) and context managers/blocks (with sandbox.code_interpreter.context() as ctx: in Python, code_interpreter.context { |ctx| } in Ruby).

Correctness details

  • UTF-8-safe streaming: log frames can split multibyte characters at chunk boundaries; all SDKs now collect output through incremental per-channel decoders, so callbacks and results never contain broken codepoints (binary-safe via base64 frames underneath).
  • Session get/list responses from older daemons are normalized into the standard Session model — return types are unchanged from the previous release.
  • Full API documentation on every new public method, including when to prefer start vs run and wait vs output.

Compatibility

  • No breaking changes. Every pre-existing public method keeps its exact signature and behavior (audited per SDK against main).
  • Existing methods continue to work against older daemons; only the new process surface requires an updated daemon (it fails with a clear 404 otherwise).
  • Generated toolbox clients regenerated from the updated spec.

Testing

  • Unit suites green across all SDKs (TS 311, Python 627, Java 324, Ruby 503, Go).
  • Contract test suites (test:golden) pass for TypeScript and Python: 40/40 and 50/50.
  • Live end-to-end coverage for the new surface, including cross-client reconnect and streaming resume from a saved cursor.

Summary by cubic

Adds a reconnectable process surface across all SDKs so callers can start/run processes, replay or stream logs, send stdin, and supervise via handles. Previously processes were fire‑and‑forget via exec/code_run; now clients can reconnect by id, enforce deadlines, and collect UTF‑8‑safe output, with updated error mapping and spec sync.

  • New APIs: sandbox.process.start/run/get/connect/list (with filters). exec remains (aliased for consistency). TypeScript adds ProcessClient/ProcessHandle/ProcessTransport; OpenAPI/toolbox spec regenerated and SDKs updated, including new PROCESS_START_FAILED mapping.
  • Handles: logs (cursor‑paged), streamLogs (replay‑then‑live with CURSOR_EXPIRED recovery), stdin/stdinEof, kill (signal with optional escalation), resize, wait/output, cleanup, and attachTerminal for kind: 'pty'. Streams decode incrementally for UTF‑8 correctness; collection fails fast on page‑budget exhaustion and documents retained‑suffix behavior.
  • Code interpreter ergonomics: scoped contexts that always clean up (withContext in TypeScript/Java/Go; context managers/blocks in Python/Ruby).
  • Robustness and validation:
    • TypeScript: validate terminal dims and finite timeouts; omit empty argv/blank shellCommand; reject non‑UTF‑8 stdin; browser‑safe base64; guard against cursor stalls; absent frame data decodes as empty; ProcessRunResult.timedOut only reflects a wait‑deadline miss; cancel losing deadlines; reject keepLogs: 'none' without streaming callbacks.
    • Python: enforce wait_timeout_ms on quiet SSE runs; PTY waits honor remaining budget and resolve results on reader failure; attached terminals initialize connection state; retryable probes for timed‑out waits; docs clarify run() recovery.
    • Go: error on unknown SSE events; int32 range checks for dims/timeouts; delete interpreter context even if caller context is canceled; page‑cap exhaustion errors; lighter tests.
    • Java: zero waitTimeoutMs timeouts immediately; keep callback exceptions and addSuppressed cleanup failures; page‑cap exhaustion errors.
    • Ruby: fix start() kwarg docs; honor timed_out terminal reason; wrap transport errors as connection errors; replace malformed/impossible UTF‑8 prefixes immediately.
  • Errors/spec/docs/tests: expanded daemon error mapping (CURSOR_EXPIRED, stdin closed/unavailable, terminal errors, name conflicts, unsupported operation, PROCESS_START_FAILED); SDK docs added for process APIs; TypeScript/Python golden contract suites gated by DAYTONA_API_KEY; CI lint/staticcheck/license fixes.

Compatibility & rollout

  • No breaking changes. Existing SDK methods keep behavior. New process endpoints require an updated daemon (older daemons return 404).
  • Migration: persist sandbox id + process id and the latest log cursor to enable reconnect and full log replay; on CURSOR_EXPIRED, resume from the first available cursor reported by the daemon.

Written for commit 0ddc922. Summary will update on new commits.

Review in cubic

Signed-off-by: MDzaja <mirkodzaja0@gmail.com>
@vidoc-agent

vidoc-agent Bot commented Aug 14, 2026

Copy link
Copy Markdown

Vidoc security review

Caution

Fix before merge — 11 findings (1 critical, 10 high). 10 other findings non-blocking.

Severity Finding Location
Critical Shared mutable default dict at class level (state leakage across instances)
additional_properties is defined as a class-level '{}' causing shared mutable state…
toolbox-api-client-python-async/daytona_toolbox_api_client_async/models/upload_files_response.py:35
High Shared mutable default dict for additional_properties enables cross-instance data leakage
additional_properties is defined with a mutable default {} and then populated from…
toolbox-api-client-python/daytona_toolbox_api_client/models/resize_process_request.py:34
High Shared mutable class-level additional_properties enables cross-instance data leakage
additional_properties is a mutable dict defined at class level, shared across all…
toolbox-api-client-python/daytona_toolbox_api_client/models/process_stdin_request.py:34
High Class-level mutable default dictionary enables cross-instance data leakage
additional_properties is initialized as a shared mutable dict at class scope, allowing…
toolbox-api-client-python/daytona_toolbox_api_client/models/process_result.py:36
High Shared mutable default for additional_properties enables cross-instance data leakage and request injection
additional_properties is initialized with a mutable default {} at class definition, then…
toolbox-api-client-python/daytona_toolbox_api_client/models/kill_process_request.py:35
High Shared mutable default additional_properties leads to cross-instance data leakage and parameter smuggling
additional_properties is a class-level mutable dict that is populated with unknown input…
toolbox-api-client-python/daytona_toolbox_api_client/models/create_process_request.py:51
High Shared mutable default dict for additional_properties causes cross-instance data leakage
additional_properties is defined with a mutable default {} at class scope; from_dict…
toolbox-api-client-python-async/daytona_toolbox_api_client_async/models/process_log_page.py:38
High Shared mutable default for additional_properties enables cross-instance data leakage and request parameter injection
additional_properties is initialized with a shared {} and then populated from unknown…
toolbox-api-client-python-async/daytona_toolbox_api_client_async/models/kill_process_request.py:35
High Shared mutable default dict for additional_properties enables cross-instance data leakage
additional_properties is defined as a shared mutable dict and populated from unvalidated…
toolbox-api-client-python/daytona_toolbox_api_client/models/upload_files_response.py:35
High Shared mutable default dictionary for additional_properties enables cross-instance data leakage
additional_properties is declared as a class-level empty dict, causing all ProcessLogPage…
toolbox-api-client-python/daytona_toolbox_api_client/models/process_log_page.py:38
High Shared mutable default dictionary on model enables cross-instance data leakage
additional_properties is defined with a mutable default {} at class level, causing all…
toolbox-api-client-python-async/daytona_toolbox_api_client_async/models/process_stdin_request.py:34
Non-blocking (10) — review when convenient

Reviewed 303 changed files. Each finding has an inline comment explaining the risk and the fix. Full analysis →

💬 Have questions? Tag @vidoc in a comment and I'll answer.

- replace deprecated NewDaytonaValidationError with NewDaytonaError and
  apply De Morgan simplification in the process surface (staticcheck)
- add license headers to the golden test helpers and the run-output test
- skip golden test collection when DAYTONA_API_KEY is absent so the unit
  lane no longer fails at collection (and no longer cascades into
  sdk-go:build/sdk-java:test via nx bail)
- apply black formatting drift in stub signatures and unicode escapes

Signed-off-by: MDzaja <mirkodzaja0@gmail.com>

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review completed against the latest diff

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread sdk-python/src/daytona/_sync/process.py Outdated
Comment thread sdk-typescript/src/process-utils.ts Outdated
Comment thread sdk-go/pkg/daytona/process_output.go
Comment thread sdk-ruby/lib/daytona/process.rb Outdated
Comment thread sdk-ruby/lib/daytona/process.rb
Comment thread sdk-typescript/src/ProcessTransport.ts Outdated
Comment thread sdk-typescript/src/process-utils.ts
Comment thread sdk-ruby/lib/daytona/process_handle.rb
Comment thread sdk-ruby/lib/daytona/process_handle.rb Outdated
Comment thread sdk-go/pkg/daytona/process_handle.go
TypeScript: omit empty argv and blank shellCommand from requests, validate
terminal dims and finite timeouts, browser-safe base64 decoding, reject
non-UTF-8 stdin instead of corrupting it, document SIGTERM kill default,
add timedOut to ProcessRunResult, cancel losing deadline timers, error on
log page-cap exhaustion, reject keepLogs=none without callbacks.

Python: enforce wait_timeout_ms on silent SSE streams (sync read timeout,
async wait_for), deadline-aware PTY wait with bounded result resolution,
initialize connection state for attached terminals, resolve results on
unexpected websocket failures.

Go: unknown SSE events error instead of ending streams, int32 range
validation for dims and timeouts, interpreter cleanup on canceled
contexts, error on page-cap exhaustion.

Java: preserve callback exceptions with addSuppressed cleanup, zero wait
timeout now times out immediately (TS/Ruby convention; OkHttp treats 0 as
unbounded), throw on page-cap exhaustion.

Ruby: correct start() kwarg docs to the real option names, honor the
timed_out terminal reason, wrap transport errors as ConnectionError, and
decode malformed UTF-8 with replacement instead of starving callbacks.

Output truncation on retention eviction is now documented on every SDK's
collection path (retained suffix + firstAvailableCursor recovery).

Signed-off-by: MDzaja <mirkodzaja0@gmail.com>

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 44 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread sdk-typescript/src/process-run-output.ts
Comment thread sdk-python/src/daytona/handle/async_pty_handle.py
Comment thread sdk-python/src/daytona/_async/process.py Outdated
Comment thread sdk-ruby/lib/daytona/common/process.rb Outdated
Comment thread sdk-typescript/src/utils/Binary.ts Outdated
Comment thread sdk-python/src/daytona/handle/pty_handle.py
Comment thread sdk-typescript/src/__tests__/ProcessClient.test.ts Outdated
Comment thread sdk-python/src/daytona/_sync/process.py
Comment thread sdk-go/pkg/daytona/process_surface_validation_test.go
Comment thread sdk-typescript/src/ProcessClient.ts Outdated
MDzaja added 2 commits August 14, 2026 13:03
- TypeScript: cursor-stall guard in log collection (Java parity), absent
  frame data decodes as empty bytes, cancellation-safe SSE test fixture,
  timedOut no longer conflates a wait timeout with an own-timeout kill
- Python: mark PTY handles disconnected before result resolution on
  reader failure, retryable timed-out wait probes, truthful run()
  recovery docs (watchdog stream interruption verified impossible with
  httpx and documented)
- Ruby: impossible UTF-8 prefixes (overlong/out-of-range) are replaced
  immediately instead of buffered until EOF, pinning Python codec parity
- Go: page-cap exhaustion test no longer performs 10k real round trips

Signed-off-by: MDzaja <mirkodzaja0@gmail.com>
…T_FAILED

Signed-off-by: MDzaja <mirkodzaja0@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant