mirror of
https://github.com/tiennm99/DocsGPT.git
synced 2026-10-04 22:13:08 +00:00
Add document I/O to workflows and surface run artifacts
Let workflow runs consume and produce documents end to end: bridge uploaded attachments into run-scoped artifacts so nodes receive the input documents (with a per-run cap and server-computed size/sha256, and the run row pre-created so produced artifacts are authorized during the run); emit the run id to the client and add a builder panel that lists, previews, and downloads a run's artifacts; and allow attaching documents to a Preview run via the existing upload flow. Also fixes issues a compliance workflow surfaced: attachment ownership now keys on the raw identity instead of a sanitized one (the sanitized form could not be read back and could collide across users); workflow code nodes read prior state from a state.json data file instead of templating it into the program, so untrusted document content can never be interpolated into executed code; structured node output wrapped in code fences is recovered; and the live speech-to-text ownership check compares the raw identity.
This commit is contained in:
1 parent
396eb92ab0
commit
5a87fa6258
20 files changed
+1186
-47
No files matched your search
@@ -5,6 +5,7 @@ patched persistence helper (no live gateway / DB / storage), plus the
|
||||
serialization round-trip and CEL branching on an artifact reference.
|
||||
"""
|
||||
|
||||
import json
|
||||
from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
@@ -61,19 +62,25 @@ class _Result:
|
||||
|
||||
|
||||
class _FakeManager:
|
||||
"""Records open/close and returns a fixed exec result; no real sandbox."""
|
||||
"""Records open/close/put_file/exec and returns a fixed result; no real sandbox."""
|
||||
|
||||
def __init__(self, result):
|
||||
self._result = result
|
||||
self.opened = []
|
||||
self.closed = []
|
||||
self.put_files = []
|
||||
self.last_code = None
|
||||
|
||||
def open(self, session_id, ttl=None):
|
||||
self.opened.append(session_id)
|
||||
return session_id
|
||||
|
||||
def put_file(self, session_id, dest_path, data):
|
||||
self.put_files.append((dest_path, data))
|
||||
|
||||
def exec(self, session_id, code, timeout=None):
|
||||
self.last_timeout = timeout
|
||||
self.last_code = code
|
||||
return self._result
|
||||
|
||||
def close(self, session_id):
|
||||
@@ -141,6 +148,40 @@ def test_code_node_no_artifacts_still_writes_status(patch_sandbox):
|
||||
assert engine.state["out"] == {"artifacts": [], "status": "ok"}
|
||||
|
||||
|
||||
def test_code_node_reads_prior_state_from_state_json(patch_sandbox):
|
||||
# Prior state is staged as DATA in state.json (workspace root = kernel cwd) so
|
||||
# node code reads it with json.load(open("state.json")) -- e.g. state["decision"].
|
||||
engine = _engine()
|
||||
engine.state["decision"] = {"pass": True, "score": 7}
|
||||
node = _code_node(
|
||||
output_variable="out",
|
||||
code="import json\nd = json.load(open('state.json'))\nprint(d['decision'])\n",
|
||||
)
|
||||
|
||||
list(engine._execute_code_node(node))
|
||||
|
||||
manager = patch_sandbox["manager_holder"]["manager"]
|
||||
staged = dict(manager.put_files)
|
||||
assert "state.json" in staged
|
||||
payload = json.loads(staged["state.json"].decode("utf-8"))
|
||||
assert payload["decision"] == {"pass": True, "score": 7}
|
||||
|
||||
|
||||
def test_code_node_literal_braces_passed_verbatim_not_templated(patch_sandbox):
|
||||
# Proves code nodes are NOT Jinja-rendered: a literal ``{{ ... }}`` in the code
|
||||
# reaches exec() byte-for-byte (no injection path that interpolates state).
|
||||
engine = _engine()
|
||||
engine.state["decision"] = "INJECTED"
|
||||
literal = "x = '{{ agent.decision }}'\nprint(x)\n"
|
||||
node = _code_node(output_variable="out", code=literal)
|
||||
|
||||
list(engine._execute_code_node(node))
|
||||
|
||||
manager = patch_sandbox["manager_holder"]["manager"]
|
||||
assert manager.last_code == literal
|
||||
assert "INJECTED" not in manager.last_code
|
||||
|
||||
|
||||
def test_code_node_failure_raises(patch_sandbox):
|
||||
engine = _engine()
|
||||
patch_sandbox["result"] = _Result(ok=False, error_name="ValueError", error_value="boom")
|
||||
|
||||
Reference in new issue
Block a user