@bolt-foundry/gambit 0.8.3 → 0.8.5-rc.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.
- package/CHANGELOG.md +38 -2
- package/README.md +79 -16
- package/esm/_dnt.polyfills.d.ts +17 -0
- package/esm/_dnt.polyfills.d.ts.map +1 -1
- package/esm/_dnt.polyfills.js +122 -0
- package/esm/deps/jsr.io/@std/collections/1.1.5/deep_merge.d.ts +322 -0
- package/esm/deps/jsr.io/@std/collections/1.1.5/deep_merge.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/collections/1.1.5/deep_merge.js +105 -0
- package/esm/deps/jsr.io/@std/fs/1.0.22/_create_walk_entry.d.ts +14 -0
- package/esm/deps/jsr.io/@std/fs/1.0.22/_create_walk_entry.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/fs/1.0.22/_create_walk_entry.js +34 -0
- package/esm/deps/jsr.io/@std/fs/1.0.22/_get_file_info_type.d.ts +13 -0
- package/esm/deps/jsr.io/@std/fs/1.0.22/_get_file_info_type.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/fs/1.0.22/_get_file_info_type.js +18 -0
- package/esm/deps/jsr.io/@std/fs/1.0.22/_is_same_path.d.ts +10 -0
- package/esm/deps/jsr.io/@std/fs/1.0.22/_is_same_path.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/fs/1.0.22/_is_same_path.js +17 -0
- package/esm/deps/jsr.io/@std/fs/1.0.22/_is_subdir.d.ts +12 -0
- package/esm/deps/jsr.io/@std/fs/1.0.22/_is_subdir.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/fs/1.0.22/_is_subdir.js +25 -0
- package/esm/deps/jsr.io/@std/fs/1.0.22/_to_path_string.d.ts +9 -0
- package/esm/deps/jsr.io/@std/fs/1.0.22/_to_path_string.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/fs/1.0.22/_to_path_string.js +13 -0
- package/esm/deps/jsr.io/@std/fs/1.0.22/copy.d.ts +117 -0
- package/esm/deps/jsr.io/@std/fs/1.0.22/copy.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/fs/1.0.22/copy.js +313 -0
- package/esm/deps/jsr.io/@std/fs/1.0.22/empty_dir.d.ts +48 -0
- package/esm/deps/jsr.io/@std/fs/1.0.22/empty_dir.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/fs/1.0.22/empty_dir.js +87 -0
- package/esm/deps/jsr.io/@std/fs/1.0.22/ensure_dir.d.ts +49 -0
- package/esm/deps/jsr.io/@std/fs/1.0.22/ensure_dir.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/fs/1.0.22/ensure_dir.js +102 -0
- package/esm/deps/jsr.io/@std/fs/1.0.22/ensure_file.d.ts +47 -0
- package/esm/deps/jsr.io/@std/fs/1.0.22/ensure_file.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/fs/1.0.22/ensure_file.js +90 -0
- package/esm/deps/jsr.io/@std/fs/1.0.22/ensure_link.d.ts +49 -0
- package/esm/deps/jsr.io/@std/fs/1.0.22/ensure_link.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/fs/1.0.22/ensure_link.js +61 -0
- package/esm/deps/jsr.io/@std/fs/1.0.22/ensure_symlink.d.ts +70 -0
- package/esm/deps/jsr.io/@std/fs/1.0.22/ensure_symlink.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/fs/1.0.22/ensure_symlink.js +156 -0
- package/esm/deps/jsr.io/@std/fs/1.0.22/eol.d.ts +52 -0
- package/esm/deps/jsr.io/@std/fs/1.0.22/eol.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/fs/1.0.22/eol.js +67 -0
- package/esm/deps/jsr.io/@std/fs/1.0.22/exists.d.ts +218 -0
- package/esm/deps/jsr.io/@std/fs/1.0.22/exists.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/fs/1.0.22/exists.js +271 -0
- package/esm/deps/jsr.io/@std/fs/1.0.22/expand_glob.d.ts +267 -0
- package/esm/deps/jsr.io/@std/fs/1.0.22/expand_glob.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/fs/1.0.22/expand_glob.js +442 -0
- package/esm/deps/jsr.io/@std/fs/1.0.22/mod.d.ts +29 -0
- package/esm/deps/jsr.io/@std/fs/1.0.22/mod.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/fs/1.0.22/mod.js +29 -0
- package/esm/deps/jsr.io/@std/fs/1.0.22/move.d.ts +86 -0
- package/esm/deps/jsr.io/@std/fs/1.0.22/move.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/fs/1.0.22/move.js +142 -0
- package/esm/deps/jsr.io/@std/fs/1.0.22/walk.d.ts +777 -0
- package/esm/deps/jsr.io/@std/fs/1.0.22/walk.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/fs/1.0.22/walk.js +846 -0
- package/esm/deps/jsr.io/@std/json/1.0.2/types.d.ts +5 -0
- package/esm/deps/jsr.io/@std/json/1.0.2/types.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/json/1.0.2/types.js +3 -0
- package/esm/deps/jsr.io/@std/jsonc/1.0.2/mod.d.ts +20 -0
- package/esm/deps/jsr.io/@std/jsonc/1.0.2/mod.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/jsonc/1.0.2/mod.js +21 -0
- package/esm/deps/jsr.io/@std/jsonc/1.0.2/parse.d.ts +21 -0
- package/esm/deps/jsr.io/@std/jsonc/1.0.2/parse.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/jsonc/1.0.2/parse.js +320 -0
- package/esm/deps/jsr.io/@std/toml/1.0.11/_parser.d.ts +93 -0
- package/esm/deps/jsr.io/@std/toml/1.0.11/_parser.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/toml/1.0.11/_parser.js +753 -0
- package/esm/deps/jsr.io/@std/toml/1.0.11/mod.d.ts +109 -0
- package/esm/deps/jsr.io/@std/toml/1.0.11/mod.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/toml/1.0.11/mod.js +110 -0
- package/esm/deps/jsr.io/@std/toml/1.0.11/parse.d.ts +21 -0
- package/esm/deps/jsr.io/@std/toml/1.0.11/parse.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/toml/1.0.11/parse.js +25 -0
- package/esm/deps/jsr.io/@std/toml/1.0.11/stringify.d.ts +35 -0
- package/esm/deps/jsr.io/@std/toml/1.0.11/stringify.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/toml/1.0.11/stringify.js +283 -0
- package/esm/gambit/simulator-ui/dist/bundle.js +10639 -4629
- package/esm/gambit/simulator-ui/dist/bundle.js.map +4 -4
- package/esm/mod.d.ts +13 -3
- package/esm/mod.d.ts.map +1 -1
- package/esm/mod.js +8 -2
- package/esm/src/cli_utils.d.ts +1 -0
- package/esm/src/cli_utils.d.ts.map +1 -1
- package/esm/src/cli_utils.js +13 -1
- package/esm/src/default_runtime.d.ts +46 -0
- package/esm/src/default_runtime.d.ts.map +1 -0
- package/esm/src/default_runtime.js +415 -0
- package/esm/src/durable_streams.js +26 -1
- package/esm/src/model_matchers.d.ts +10 -0
- package/esm/src/model_matchers.d.ts.map +1 -0
- package/esm/src/model_matchers.js +26 -0
- package/esm/src/openai_compat.d.ts +12 -1
- package/esm/src/openai_compat.d.ts.map +1 -1
- package/esm/src/openai_compat.js +53 -1
- package/esm/src/project_config.d.ts +47 -0
- package/esm/src/project_config.d.ts.map +1 -0
- package/esm/src/project_config.js +134 -0
- package/esm/src/providers/codex.d.ts +37 -0
- package/esm/src/providers/codex.d.ts.map +1 -0
- package/esm/src/providers/codex.js +810 -0
- package/esm/src/providers/google.d.ts +3 -1
- package/esm/src/providers/google.d.ts.map +1 -1
- package/esm/src/providers/google.js +82 -6
- package/esm/src/providers/ollama.d.ts +3 -1
- package/esm/src/providers/ollama.d.ts.map +1 -1
- package/esm/src/providers/ollama.js +238 -15
- package/esm/src/providers/openrouter.d.ts +6 -2
- package/esm/src/providers/openrouter.d.ts.map +1 -1
- package/esm/src/providers/openrouter.js +260 -23
- package/esm/src/providers/router.d.ts +19 -0
- package/esm/src/providers/router.d.ts.map +1 -0
- package/esm/src/providers/router.js +93 -0
- package/esm/src/server.d.ts +9 -0
- package/esm/src/server.d.ts.map +1 -1
- package/esm/src/server.js +3186 -652
- package/esm/src/server_feedback_grading_routes.d.ts +32 -0
- package/esm/src/server_feedback_grading_routes.d.ts.map +1 -0
- package/esm/src/server_feedback_grading_routes.js +305 -0
- package/esm/src/server_helpers.d.ts +4 -0
- package/esm/src/server_helpers.d.ts.map +1 -0
- package/esm/src/server_helpers.js +46 -0
- package/esm/src/server_session_store.d.ts +87 -0
- package/esm/src/server_session_store.d.ts.map +1 -0
- package/esm/src/server_session_store.js +873 -0
- package/esm/src/server_types.d.ts +110 -0
- package/esm/src/server_types.d.ts.map +1 -0
- package/esm/src/server_types.js +1 -0
- package/esm/src/server_ui_routes.d.ts +33 -0
- package/esm/src/server_ui_routes.d.ts.map +1 -0
- package/esm/src/server_ui_routes.js +135 -0
- package/esm/src/session_artifacts.d.ts +22 -0
- package/esm/src/session_artifacts.d.ts.map +1 -0
- package/esm/src/session_artifacts.js +243 -0
- package/esm/src/trace.d.ts.map +1 -1
- package/esm/src/trace.js +6 -3
- package/esm/src/workspace.d.ts +19 -0
- package/esm/src/workspace.d.ts.map +1 -0
- package/esm/src/workspace.js +164 -0
- package/esm/src/workspace_contract.d.ts +76 -0
- package/esm/src/workspace_contract.d.ts.map +1 -0
- package/esm/src/workspace_contract.js +74 -0
- package/package.json +2 -2
- package/script/_dnt.polyfills.d.ts +17 -0
- package/script/_dnt.polyfills.d.ts.map +1 -1
- package/script/_dnt.polyfills.js +122 -0
- package/script/deps/jsr.io/@std/collections/1.1.5/deep_merge.d.ts +322 -0
- package/script/deps/jsr.io/@std/collections/1.1.5/deep_merge.d.ts.map +1 -0
- package/script/deps/jsr.io/@std/collections/1.1.5/deep_merge.js +108 -0
- package/script/deps/jsr.io/@std/fs/1.0.22/_create_walk_entry.d.ts +14 -0
- package/script/deps/jsr.io/@std/fs/1.0.22/_create_walk_entry.d.ts.map +1 -0
- package/script/deps/jsr.io/@std/fs/1.0.22/_create_walk_entry.js +71 -0
- package/script/deps/jsr.io/@std/fs/1.0.22/_get_file_info_type.d.ts +13 -0
- package/script/deps/jsr.io/@std/fs/1.0.22/_get_file_info_type.d.ts.map +1 -0
- package/script/deps/jsr.io/@std/fs/1.0.22/_get_file_info_type.js +21 -0
- package/script/deps/jsr.io/@std/fs/1.0.22/_is_same_path.d.ts +10 -0
- package/script/deps/jsr.io/@std/fs/1.0.22/_is_same_path.d.ts.map +1 -0
- package/script/deps/jsr.io/@std/fs/1.0.22/_is_same_path.js +20 -0
- package/script/deps/jsr.io/@std/fs/1.0.22/_is_subdir.d.ts +12 -0
- package/script/deps/jsr.io/@std/fs/1.0.22/_is_subdir.d.ts.map +1 -0
- package/script/deps/jsr.io/@std/fs/1.0.22/_is_subdir.js +28 -0
- package/script/deps/jsr.io/@std/fs/1.0.22/_to_path_string.d.ts +9 -0
- package/script/deps/jsr.io/@std/fs/1.0.22/_to_path_string.d.ts.map +1 -0
- package/script/deps/jsr.io/@std/fs/1.0.22/_to_path_string.js +16 -0
- package/script/deps/jsr.io/@std/fs/1.0.22/copy.d.ts +117 -0
- package/script/deps/jsr.io/@std/fs/1.0.22/copy.d.ts.map +1 -0
- package/script/deps/jsr.io/@std/fs/1.0.22/copy.js +350 -0
- package/script/deps/jsr.io/@std/fs/1.0.22/empty_dir.d.ts +48 -0
- package/script/deps/jsr.io/@std/fs/1.0.22/empty_dir.d.ts.map +1 -0
- package/script/deps/jsr.io/@std/fs/1.0.22/empty_dir.js +124 -0
- package/script/deps/jsr.io/@std/fs/1.0.22/ensure_dir.d.ts +49 -0
- package/script/deps/jsr.io/@std/fs/1.0.22/ensure_dir.d.ts.map +1 -0
- package/script/deps/jsr.io/@std/fs/1.0.22/ensure_dir.js +139 -0
- package/script/deps/jsr.io/@std/fs/1.0.22/ensure_file.d.ts +47 -0
- package/script/deps/jsr.io/@std/fs/1.0.22/ensure_file.d.ts.map +1 -0
- package/script/deps/jsr.io/@std/fs/1.0.22/ensure_file.js +127 -0
- package/script/deps/jsr.io/@std/fs/1.0.22/ensure_link.d.ts +49 -0
- package/script/deps/jsr.io/@std/fs/1.0.22/ensure_link.d.ts.map +1 -0
- package/script/deps/jsr.io/@std/fs/1.0.22/ensure_link.js +98 -0
- package/script/deps/jsr.io/@std/fs/1.0.22/ensure_symlink.d.ts +70 -0
- package/script/deps/jsr.io/@std/fs/1.0.22/ensure_symlink.d.ts.map +1 -0
- package/script/deps/jsr.io/@std/fs/1.0.22/ensure_symlink.js +193 -0
- package/script/deps/jsr.io/@std/fs/1.0.22/eol.d.ts +52 -0
- package/script/deps/jsr.io/@std/fs/1.0.22/eol.d.ts.map +1 -0
- package/script/deps/jsr.io/@std/fs/1.0.22/eol.js +105 -0
- package/script/deps/jsr.io/@std/fs/1.0.22/exists.d.ts +218 -0
- package/script/deps/jsr.io/@std/fs/1.0.22/exists.d.ts.map +1 -0
- package/script/deps/jsr.io/@std/fs/1.0.22/exists.js +308 -0
- package/script/deps/jsr.io/@std/fs/1.0.22/expand_glob.d.ts +267 -0
- package/script/deps/jsr.io/@std/fs/1.0.22/expand_glob.d.ts.map +1 -0
- package/script/deps/jsr.io/@std/fs/1.0.22/expand_glob.js +479 -0
- package/script/deps/jsr.io/@std/fs/1.0.22/mod.d.ts +29 -0
- package/script/deps/jsr.io/@std/fs/1.0.22/mod.d.ts.map +1 -0
- package/script/deps/jsr.io/@std/fs/1.0.22/mod.js +45 -0
- package/script/deps/jsr.io/@std/fs/1.0.22/move.d.ts +86 -0
- package/script/deps/jsr.io/@std/fs/1.0.22/move.d.ts.map +1 -0
- package/script/deps/jsr.io/@std/fs/1.0.22/move.js +179 -0
- package/script/deps/jsr.io/@std/fs/1.0.22/walk.d.ts +777 -0
- package/script/deps/jsr.io/@std/fs/1.0.22/walk.d.ts.map +1 -0
- package/script/deps/jsr.io/@std/fs/1.0.22/walk.js +883 -0
- package/script/deps/jsr.io/@std/json/1.0.2/types.d.ts +5 -0
- package/script/deps/jsr.io/@std/json/1.0.2/types.d.ts.map +1 -0
- package/script/deps/jsr.io/@std/json/1.0.2/types.js +4 -0
- package/script/deps/jsr.io/@std/jsonc/1.0.2/mod.d.ts +20 -0
- package/script/deps/jsr.io/@std/jsonc/1.0.2/mod.d.ts.map +1 -0
- package/script/deps/jsr.io/@std/jsonc/1.0.2/mod.js +37 -0
- package/script/deps/jsr.io/@std/jsonc/1.0.2/parse.d.ts +21 -0
- package/script/deps/jsr.io/@std/jsonc/1.0.2/parse.d.ts.map +1 -0
- package/script/deps/jsr.io/@std/jsonc/1.0.2/parse.js +323 -0
- package/script/deps/jsr.io/@std/toml/1.0.11/_parser.d.ts +93 -0
- package/script/deps/jsr.io/@std/toml/1.0.11/_parser.d.ts.map +1 -0
- package/script/deps/jsr.io/@std/toml/1.0.11/_parser.js +781 -0
- package/script/deps/jsr.io/@std/toml/1.0.11/mod.d.ts +109 -0
- package/script/deps/jsr.io/@std/toml/1.0.11/mod.d.ts.map +1 -0
- package/script/deps/jsr.io/@std/toml/1.0.11/mod.js +126 -0
- package/script/deps/jsr.io/@std/toml/1.0.11/parse.d.ts +21 -0
- package/script/deps/jsr.io/@std/toml/1.0.11/parse.d.ts.map +1 -0
- package/script/deps/jsr.io/@std/toml/1.0.11/parse.js +28 -0
- package/script/deps/jsr.io/@std/toml/1.0.11/stringify.d.ts +35 -0
- package/script/deps/jsr.io/@std/toml/1.0.11/stringify.d.ts.map +1 -0
- package/script/deps/jsr.io/@std/toml/1.0.11/stringify.js +286 -0
- package/script/gambit/simulator-ui/dist/bundle.js +10639 -4629
- package/script/gambit/simulator-ui/dist/bundle.js.map +4 -4
- package/script/mod.d.ts +13 -3
- package/script/mod.d.ts.map +1 -1
- package/script/mod.js +14 -5
- package/script/src/cli_utils.d.ts +1 -0
- package/script/src/cli_utils.d.ts.map +1 -1
- package/script/src/cli_utils.js +14 -1
- package/script/src/default_runtime.d.ts +46 -0
- package/script/src/default_runtime.d.ts.map +1 -0
- package/script/src/default_runtime.js +452 -0
- package/script/src/durable_streams.js +26 -1
- package/script/src/model_matchers.d.ts +10 -0
- package/script/src/model_matchers.d.ts.map +1 -0
- package/script/src/model_matchers.js +29 -0
- package/script/src/openai_compat.d.ts +12 -1
- package/script/src/openai_compat.d.ts.map +1 -1
- package/script/src/openai_compat.js +85 -0
- package/script/src/project_config.d.ts +47 -0
- package/script/src/project_config.d.ts.map +1 -0
- package/script/src/project_config.js +173 -0
- package/script/src/providers/codex.d.ts +37 -0
- package/script/src/providers/codex.d.ts.map +1 -0
- package/script/src/providers/codex.js +850 -0
- package/script/src/providers/google.d.ts +3 -1
- package/script/src/providers/google.d.ts.map +1 -1
- package/script/src/providers/google.js +82 -6
- package/script/src/providers/ollama.d.ts +3 -1
- package/script/src/providers/ollama.d.ts.map +1 -1
- package/script/src/providers/ollama.js +238 -15
- package/script/src/providers/openrouter.d.ts +6 -2
- package/script/src/providers/openrouter.d.ts.map +1 -1
- package/script/src/providers/openrouter.js +260 -23
- package/script/src/providers/router.d.ts +19 -0
- package/script/src/providers/router.d.ts.map +1 -0
- package/script/src/providers/router.js +96 -0
- package/script/src/server.d.ts +9 -0
- package/script/src/server.d.ts.map +1 -1
- package/script/src/server.js +3193 -659
- package/script/src/server_feedback_grading_routes.d.ts +32 -0
- package/script/src/server_feedback_grading_routes.d.ts.map +1 -0
- package/script/src/server_feedback_grading_routes.js +343 -0
- package/script/src/server_helpers.d.ts +4 -0
- package/script/src/server_helpers.d.ts.map +1 -0
- package/script/src/server_helpers.js +84 -0
- package/script/src/server_session_store.d.ts +87 -0
- package/script/src/server_session_store.d.ts.map +1 -0
- package/script/src/server_session_store.js +910 -0
- package/script/src/server_types.d.ts +110 -0
- package/script/src/server_types.d.ts.map +1 -0
- package/script/src/server_types.js +2 -0
- package/script/src/server_ui_routes.d.ts +33 -0
- package/script/src/server_ui_routes.d.ts.map +1 -0
- package/script/src/server_ui_routes.js +172 -0
- package/script/src/session_artifacts.d.ts +22 -0
- package/script/src/session_artifacts.d.ts.map +1 -0
- package/script/src/session_artifacts.js +279 -0
- package/script/src/trace.d.ts.map +1 -1
- package/script/src/trace.js +6 -3
- package/script/src/workspace.d.ts +19 -0
- package/script/src/workspace.d.ts.map +1 -0
- package/script/src/workspace.js +201 -0
- package/script/src/workspace_contract.d.ts +76 -0
- package/script/src/workspace_contract.d.ts.map +1 -0
- package/script/src/workspace_contract.js +82 -0
package/script/src/server.js
CHANGED
|
@@ -36,11 +36,20 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.startWebSocketSimulator = startWebSocketSimulator;
|
|
37
37
|
const dntShim = __importStar(require("../_dnt.shims.js"));
|
|
38
38
|
const path = __importStar(require("../deps/jsr.io/@std/path/1.1.4/mod.js"));
|
|
39
|
+
const mod_js_1 = require("../deps/jsr.io/@std/fs/1.0.22/mod.js");
|
|
40
|
+
const mod_js_2 = require("../deps/jsr.io/@std/jsonc/1.0.2/mod.js");
|
|
41
|
+
const mod_js_3 = require("../deps/jsr.io/@std/toml/1.0.11/mod.js");
|
|
39
42
|
const gambit_core_1 = require("@bolt-foundry/gambit-core");
|
|
40
43
|
const test_bot_js_1 = require("./test_bot.js");
|
|
41
44
|
const trace_js_1 = require("./trace.js");
|
|
42
45
|
const cli_utils_js_1 = require("./cli_utils.js");
|
|
43
46
|
const gambit_core_2 = require("@bolt-foundry/gambit-core");
|
|
47
|
+
const workspace_js_1 = require("./workspace.js");
|
|
48
|
+
const server_helpers_js_1 = require("./server_helpers.js");
|
|
49
|
+
const server_session_store_js_1 = require("./server_session_store.js");
|
|
50
|
+
const server_feedback_grading_routes_js_1 = require("./server_feedback_grading_routes.js");
|
|
51
|
+
const server_ui_routes_js_1 = require("./server_ui_routes.js");
|
|
52
|
+
const workspace_contract_js_1 = require("./workspace_contract.js");
|
|
44
53
|
const durable_streams_js_1 = require("./durable_streams.js");
|
|
45
54
|
const GAMBIT_TOOL_RESPOND = "gambit_respond";
|
|
46
55
|
const logger = console;
|
|
@@ -87,34 +96,89 @@ const simulatorBundlePath = path.resolve(moduleDir, "..", "simulator-ui", "dist"
|
|
|
87
96
|
const simulatorBundleSourceMapPath = path.resolve(moduleDir, "..", "simulator-ui", "dist", "bundle.js.map");
|
|
88
97
|
const simulatorFaviconDistPath = path.resolve(moduleDir, "..", "simulator-ui", "dist", "favicon.ico");
|
|
89
98
|
const simulatorFaviconSrcPath = path.resolve(moduleDir, "..", "simulator-ui", "src", "favicon.ico");
|
|
99
|
+
const gambitVersion = (() => {
|
|
100
|
+
const envVersion = dntShim.Deno.env.get("GAMBIT_VERSION")?.trim();
|
|
101
|
+
if (envVersion)
|
|
102
|
+
return envVersion;
|
|
103
|
+
const readVersion = (configPath) => {
|
|
104
|
+
try {
|
|
105
|
+
const text = dntShim.Deno.readTextFileSync(configPath);
|
|
106
|
+
const data = (0, mod_js_2.parse)(text);
|
|
107
|
+
const version = typeof data.version === "string"
|
|
108
|
+
? data.version.trim()
|
|
109
|
+
: "";
|
|
110
|
+
return version || null;
|
|
111
|
+
}
|
|
112
|
+
catch (err) {
|
|
113
|
+
if (err instanceof dntShim.Deno.errors.NotFound)
|
|
114
|
+
return null;
|
|
115
|
+
throw err;
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
const candidates = [
|
|
119
|
+
path.resolve(moduleDir, "..", "deno.jsonc"),
|
|
120
|
+
path.resolve(moduleDir, "..", "deno.json"),
|
|
121
|
+
];
|
|
122
|
+
for (const candidate of candidates) {
|
|
123
|
+
const version = readVersion(candidate);
|
|
124
|
+
if (version)
|
|
125
|
+
return version;
|
|
126
|
+
}
|
|
127
|
+
return "unknown";
|
|
128
|
+
})();
|
|
90
129
|
const SIMULATOR_STREAM_ID = "gambit-simulator";
|
|
130
|
+
const WORKSPACE_STREAM_ID = "gambit-workspace";
|
|
91
131
|
const GRADE_STREAM_ID = "gambit-grade";
|
|
92
132
|
const TEST_STREAM_ID = "gambit-test";
|
|
133
|
+
const BUILD_STREAM_ID = "gambit-build";
|
|
134
|
+
const DEFAULT_TEST_BOT_SEED_PROMPT = "Start the conversation as the user. Do not wait for the assistant to speak first.";
|
|
135
|
+
const isWorkspaceEventDomain = (value) => value === "build" || value === "test" || value === "grade" ||
|
|
136
|
+
value === "session";
|
|
137
|
+
const extractPersistedWorkspacePayload = (record) => {
|
|
138
|
+
if (!isWorkspaceEventDomain(record.type))
|
|
139
|
+
return record;
|
|
140
|
+
const nested = record.data;
|
|
141
|
+
if (!nested || typeof nested !== "object" || Array.isArray(nested)) {
|
|
142
|
+
return record;
|
|
143
|
+
}
|
|
144
|
+
return nested;
|
|
145
|
+
};
|
|
146
|
+
const GAMBIT_BOT_SOURCE_DECK_URL = new URL("./decks/gambit-bot/PROMPT.md", globalThis[Symbol.for("import-meta-ponyfill-commonjs")](require, module).url);
|
|
147
|
+
const GAMBIT_BOT_SOURCE_DIR = GAMBIT_BOT_SOURCE_DECK_URL.protocol === "file:"
|
|
148
|
+
? path.dirname(path.fromFileUrl(GAMBIT_BOT_SOURCE_DECK_URL))
|
|
149
|
+
: "";
|
|
150
|
+
const GAMBIT_BOT_POLICY_DIR = GAMBIT_BOT_SOURCE_DIR
|
|
151
|
+
? path.join(GAMBIT_BOT_SOURCE_DIR, "policy")
|
|
152
|
+
: "";
|
|
153
|
+
async function ensureGambitPolicyInBotRoot(root) {
|
|
154
|
+
if (!GAMBIT_BOT_POLICY_DIR)
|
|
155
|
+
return;
|
|
156
|
+
try {
|
|
157
|
+
const info = await dntShim.Deno.stat(GAMBIT_BOT_POLICY_DIR);
|
|
158
|
+
if (!info.isDirectory)
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
const dest = path.join(root, ".gambit", "policy");
|
|
165
|
+
if ((0, mod_js_1.existsSync)(dest))
|
|
166
|
+
return;
|
|
167
|
+
await (0, mod_js_1.ensureDir)(path.dirname(dest));
|
|
168
|
+
await (0, mod_js_1.copy)(GAMBIT_BOT_POLICY_DIR, dest, { overwrite: false });
|
|
169
|
+
}
|
|
93
170
|
let availableTestDecks = [];
|
|
94
171
|
const testDeckByPath = new Map();
|
|
95
172
|
const testDeckById = new Map();
|
|
96
173
|
let availableGraderDecks = [];
|
|
97
174
|
const graderDeckByPath = new Map();
|
|
98
175
|
const graderDeckById = new Map();
|
|
99
|
-
function randomId(prefix) {
|
|
100
|
-
const suffix = crypto.randomUUID().replace(/-/g, "").slice(0, 24);
|
|
101
|
-
return `${prefix}-${suffix}`;
|
|
102
|
-
}
|
|
103
|
-
function resolveDefaultValue(raw) {
|
|
104
|
-
if (typeof raw === "function") {
|
|
105
|
-
try {
|
|
106
|
-
return raw();
|
|
107
|
-
}
|
|
108
|
-
catch {
|
|
109
|
-
return undefined;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
return raw;
|
|
113
|
-
}
|
|
114
176
|
async function describeDeckInputSchemaFromPath(deckPath) {
|
|
115
177
|
try {
|
|
116
178
|
const deck = await (0, gambit_core_2.loadDeck)(deckPath);
|
|
117
|
-
|
|
179
|
+
const tools = mapDeckTools(deck.actionDecks);
|
|
180
|
+
const desc = describeZodSchema(deck.inputSchema);
|
|
181
|
+
return tools ? { ...desc, tools } : desc;
|
|
118
182
|
}
|
|
119
183
|
catch (err) {
|
|
120
184
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -122,6 +186,22 @@ async function describeDeckInputSchemaFromPath(deckPath) {
|
|
|
122
186
|
return { error: message };
|
|
123
187
|
}
|
|
124
188
|
}
|
|
189
|
+
function mapDeckTools(actionDecks) {
|
|
190
|
+
if (!Array.isArray(actionDecks) || actionDecks.length === 0) {
|
|
191
|
+
return undefined;
|
|
192
|
+
}
|
|
193
|
+
const described = actionDecks
|
|
194
|
+
.filter((action) => Boolean(action?.name && typeof action.name === "string"))
|
|
195
|
+
.map((action) => ({
|
|
196
|
+
name: action.name,
|
|
197
|
+
label: typeof action.label === "string" ? action.label : undefined,
|
|
198
|
+
description: typeof action.description === "string"
|
|
199
|
+
? action.description
|
|
200
|
+
: undefined,
|
|
201
|
+
path: action.path,
|
|
202
|
+
}));
|
|
203
|
+
return described.length > 0 ? described : undefined;
|
|
204
|
+
}
|
|
125
205
|
function describeZodSchema(schema) {
|
|
126
206
|
try {
|
|
127
207
|
const normalized = normalizeSchema(schema);
|
|
@@ -218,7 +298,7 @@ function unwrapSchema(schema) {
|
|
|
218
298
|
}
|
|
219
299
|
if (typeName === "ZodDefault") {
|
|
220
300
|
if (defaultValue === undefined) {
|
|
221
|
-
defaultValue = resolveDefaultValue(def.defaultValue);
|
|
301
|
+
defaultValue = (0, server_helpers_js_1.resolveDefaultValue)(def.defaultValue);
|
|
222
302
|
}
|
|
223
303
|
current = def.innerType;
|
|
224
304
|
continue;
|
|
@@ -445,7 +525,7 @@ function buildInitFillPrompt(args) {
|
|
|
445
525
|
schemaHints,
|
|
446
526
|
};
|
|
447
527
|
return [
|
|
448
|
-
"You are filling missing required init fields for a Gambit
|
|
528
|
+
"You are filling missing required init fields for a Gambit Scenario run.",
|
|
449
529
|
"Return ONLY valid JSON that includes values for the missing fields.",
|
|
450
530
|
"Do not include any fields that are not listed as missing.",
|
|
451
531
|
"If the only missing path is '(root)', return the full init JSON value.",
|
|
@@ -501,6 +581,389 @@ function validateInitInput(schema, value) {
|
|
|
501
581
|
}
|
|
502
582
|
return result.data;
|
|
503
583
|
}
|
|
584
|
+
function jsonResponse(body, status = 200) {
|
|
585
|
+
return new Response(JSON.stringify(body), {
|
|
586
|
+
status,
|
|
587
|
+
headers: { "content-type": "application/json" },
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
function parseBodyObject(value) {
|
|
591
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
592
|
+
throw new Error("Request body must be a JSON object");
|
|
593
|
+
}
|
|
594
|
+
return value;
|
|
595
|
+
}
|
|
596
|
+
function toTextPart(role, value) {
|
|
597
|
+
if (typeof value === "string") {
|
|
598
|
+
return {
|
|
599
|
+
type: role === "assistant" ? "output_text" : "input_text",
|
|
600
|
+
text: value,
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
604
|
+
return null;
|
|
605
|
+
const record = value;
|
|
606
|
+
const text = typeof record.text === "string" ? record.text : "";
|
|
607
|
+
if (!text)
|
|
608
|
+
return null;
|
|
609
|
+
const type = typeof record.type === "string" ? record.type : "";
|
|
610
|
+
if (type === "output_text")
|
|
611
|
+
return { type: "output_text", text };
|
|
612
|
+
if (type === "input_text")
|
|
613
|
+
return { type: "input_text", text };
|
|
614
|
+
return {
|
|
615
|
+
type: role === "assistant" ? "output_text" : "input_text",
|
|
616
|
+
text,
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
function normalizeMessageItem(item) {
|
|
620
|
+
const role = item.role;
|
|
621
|
+
if (role !== "system" && role !== "user" && role !== "assistant") {
|
|
622
|
+
throw new Error("message.role must be system, user, or assistant");
|
|
623
|
+
}
|
|
624
|
+
const rawContent = item.content;
|
|
625
|
+
const content = Array.isArray(rawContent)
|
|
626
|
+
? rawContent.map((part) => toTextPart(role, part)).filter((part) => Boolean(part))
|
|
627
|
+
: [toTextPart(role, rawContent)].filter((part) => Boolean(part));
|
|
628
|
+
if (content.length === 0) {
|
|
629
|
+
throw new Error("message.content must include text");
|
|
630
|
+
}
|
|
631
|
+
return {
|
|
632
|
+
type: "message",
|
|
633
|
+
role,
|
|
634
|
+
content,
|
|
635
|
+
id: typeof item.id === "string" ? item.id : undefined,
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
function normalizeInputItems(input) {
|
|
639
|
+
if (typeof input === "string") {
|
|
640
|
+
return [{
|
|
641
|
+
type: "message",
|
|
642
|
+
role: "user",
|
|
643
|
+
content: [{ type: "input_text", text: input }],
|
|
644
|
+
}];
|
|
645
|
+
}
|
|
646
|
+
const arr = Array.isArray(input) ? input : [input];
|
|
647
|
+
const items = [];
|
|
648
|
+
for (const raw of arr) {
|
|
649
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
650
|
+
throw new Error("input items must be objects");
|
|
651
|
+
}
|
|
652
|
+
const item = raw;
|
|
653
|
+
const type = typeof item.type === "string" ? item.type : "";
|
|
654
|
+
if (type === "message") {
|
|
655
|
+
const normalized = normalizeMessageItem(item);
|
|
656
|
+
if (normalized)
|
|
657
|
+
items.push(normalized);
|
|
658
|
+
continue;
|
|
659
|
+
}
|
|
660
|
+
if (type === "function_call") {
|
|
661
|
+
const callId = item.call_id;
|
|
662
|
+
const name = item.name;
|
|
663
|
+
const args = item.arguments;
|
|
664
|
+
if (typeof callId !== "string" || typeof name !== "string" ||
|
|
665
|
+
typeof args !== "string") {
|
|
666
|
+
throw new Error("function_call requires call_id, name, and arguments strings");
|
|
667
|
+
}
|
|
668
|
+
items.push({
|
|
669
|
+
type: "function_call",
|
|
670
|
+
call_id: callId,
|
|
671
|
+
name,
|
|
672
|
+
arguments: args,
|
|
673
|
+
id: typeof item.id === "string" ? item.id : undefined,
|
|
674
|
+
});
|
|
675
|
+
continue;
|
|
676
|
+
}
|
|
677
|
+
if (type === "function_call_output") {
|
|
678
|
+
const callId = item.call_id;
|
|
679
|
+
const output = item.output;
|
|
680
|
+
if (typeof callId !== "string" || typeof output !== "string") {
|
|
681
|
+
throw new Error("function_call_output requires call_id and output strings");
|
|
682
|
+
}
|
|
683
|
+
items.push({
|
|
684
|
+
type: "function_call_output",
|
|
685
|
+
call_id: callId,
|
|
686
|
+
output,
|
|
687
|
+
id: typeof item.id === "string" ? item.id : undefined,
|
|
688
|
+
});
|
|
689
|
+
continue;
|
|
690
|
+
}
|
|
691
|
+
throw new Error(`Unsupported input item type: ${type || "(missing type)"}`);
|
|
692
|
+
}
|
|
693
|
+
return items;
|
|
694
|
+
}
|
|
695
|
+
function normalizeTools(tools) {
|
|
696
|
+
if (!Array.isArray(tools) || tools.length === 0)
|
|
697
|
+
return undefined;
|
|
698
|
+
const out = [];
|
|
699
|
+
for (const raw of tools) {
|
|
700
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
701
|
+
throw new Error("tools entries must be objects");
|
|
702
|
+
}
|
|
703
|
+
const item = raw;
|
|
704
|
+
const type = typeof item.type === "string" ? item.type : "";
|
|
705
|
+
if (type !== "function")
|
|
706
|
+
continue;
|
|
707
|
+
const nested = item.function;
|
|
708
|
+
if (nested && typeof nested === "object" && !Array.isArray(nested)) {
|
|
709
|
+
const fn = nested;
|
|
710
|
+
const name = fn.name;
|
|
711
|
+
if (typeof name !== "string" || !name) {
|
|
712
|
+
throw new Error("tool.function.name is required");
|
|
713
|
+
}
|
|
714
|
+
out.push({
|
|
715
|
+
type: "function",
|
|
716
|
+
function: {
|
|
717
|
+
name,
|
|
718
|
+
description: typeof fn.description === "string"
|
|
719
|
+
? fn.description
|
|
720
|
+
: undefined,
|
|
721
|
+
parameters: (fn.parameters &&
|
|
722
|
+
typeof fn.parameters === "object" &&
|
|
723
|
+
!Array.isArray(fn.parameters))
|
|
724
|
+
? fn.parameters
|
|
725
|
+
: {},
|
|
726
|
+
},
|
|
727
|
+
});
|
|
728
|
+
continue;
|
|
729
|
+
}
|
|
730
|
+
const name = item.name;
|
|
731
|
+
if (typeof name !== "string" || !name) {
|
|
732
|
+
throw new Error("tool.name is required");
|
|
733
|
+
}
|
|
734
|
+
out.push({
|
|
735
|
+
type: "function",
|
|
736
|
+
function: {
|
|
737
|
+
name,
|
|
738
|
+
description: typeof item.description === "string"
|
|
739
|
+
? item.description
|
|
740
|
+
: undefined,
|
|
741
|
+
parameters: (item.parameters &&
|
|
742
|
+
typeof item.parameters === "object" &&
|
|
743
|
+
!Array.isArray(item.parameters))
|
|
744
|
+
? item.parameters
|
|
745
|
+
: {},
|
|
746
|
+
},
|
|
747
|
+
});
|
|
748
|
+
}
|
|
749
|
+
return out.length ? out : undefined;
|
|
750
|
+
}
|
|
751
|
+
function normalizeToolChoice(choice) {
|
|
752
|
+
if (!choice)
|
|
753
|
+
return undefined;
|
|
754
|
+
if (choice === "none" || choice === "auto" || choice === "required") {
|
|
755
|
+
return choice;
|
|
756
|
+
}
|
|
757
|
+
if (!choice || typeof choice !== "object" || Array.isArray(choice)) {
|
|
758
|
+
return undefined;
|
|
759
|
+
}
|
|
760
|
+
const record = choice;
|
|
761
|
+
if (record.type === "allowed_tools" && Array.isArray(record.tools)) {
|
|
762
|
+
const tools = record.tools
|
|
763
|
+
.map((entry) => {
|
|
764
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
|
765
|
+
return null;
|
|
766
|
+
}
|
|
767
|
+
const tool = entry;
|
|
768
|
+
if (tool.type !== "function" || typeof tool.name !== "string") {
|
|
769
|
+
return null;
|
|
770
|
+
}
|
|
771
|
+
return { type: "function", name: tool.name };
|
|
772
|
+
})
|
|
773
|
+
.filter((entry) => Boolean(entry));
|
|
774
|
+
if (tools.length === 0)
|
|
775
|
+
return undefined;
|
|
776
|
+
const mode = record.mode === "none" || record.mode === "auto" ||
|
|
777
|
+
record.mode === "required"
|
|
778
|
+
? record.mode
|
|
779
|
+
: undefined;
|
|
780
|
+
return { type: "allowed_tools", tools, mode };
|
|
781
|
+
}
|
|
782
|
+
if (record.type !== "function")
|
|
783
|
+
return undefined;
|
|
784
|
+
if (record.function && typeof record.function === "object") {
|
|
785
|
+
const fn = record.function;
|
|
786
|
+
if (typeof fn.name === "string" && fn.name.length > 0) {
|
|
787
|
+
return { type: "function", function: { name: fn.name } };
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
if (typeof record.name === "string" && record.name.length > 0) {
|
|
791
|
+
return { type: "function", function: { name: record.name } };
|
|
792
|
+
}
|
|
793
|
+
return undefined;
|
|
794
|
+
}
|
|
795
|
+
function sseFrame(event) {
|
|
796
|
+
const encoder = new TextEncoder();
|
|
797
|
+
const type = event && typeof event === "object" && !Array.isArray(event) &&
|
|
798
|
+
typeof event.type === "string"
|
|
799
|
+
? event.type
|
|
800
|
+
: null;
|
|
801
|
+
if (type) {
|
|
802
|
+
return encoder.encode(`event: ${type}\ndata: ${JSON.stringify(event)}\n\n`);
|
|
803
|
+
}
|
|
804
|
+
return encoder.encode(`data: ${JSON.stringify(event)}\n\n`);
|
|
805
|
+
}
|
|
806
|
+
function asJsonValue(value) {
|
|
807
|
+
if (value === null || typeof value === "string" || typeof value === "number" ||
|
|
808
|
+
typeof value === "boolean") {
|
|
809
|
+
return value;
|
|
810
|
+
}
|
|
811
|
+
if (Array.isArray(value)) {
|
|
812
|
+
return value.map((entry) => asJsonValue(entry));
|
|
813
|
+
}
|
|
814
|
+
if (value && typeof value === "object") {
|
|
815
|
+
const out = {};
|
|
816
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
817
|
+
out[key] = asJsonValue(entry);
|
|
818
|
+
}
|
|
819
|
+
return out;
|
|
820
|
+
}
|
|
821
|
+
return String(value);
|
|
822
|
+
}
|
|
823
|
+
function toStrictContentPart(part) {
|
|
824
|
+
if (part.type === "output_text") {
|
|
825
|
+
return {
|
|
826
|
+
type: "output_text",
|
|
827
|
+
text: part.text,
|
|
828
|
+
annotations: [],
|
|
829
|
+
logprobs: [],
|
|
830
|
+
};
|
|
831
|
+
}
|
|
832
|
+
return {
|
|
833
|
+
type: part.type,
|
|
834
|
+
text: part.text,
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
function toStrictResponseItem(item, index) {
|
|
838
|
+
if (item.type === "message") {
|
|
839
|
+
return {
|
|
840
|
+
type: "message",
|
|
841
|
+
id: item.id ?? `msg_${index + 1}`,
|
|
842
|
+
status: "completed",
|
|
843
|
+
role: item.role,
|
|
844
|
+
content: item.content.map((part) => toStrictContentPart(part)),
|
|
845
|
+
};
|
|
846
|
+
}
|
|
847
|
+
if (item.type === "function_call") {
|
|
848
|
+
return {
|
|
849
|
+
type: "function_call",
|
|
850
|
+
id: item.id ?? item.call_id,
|
|
851
|
+
call_id: item.call_id,
|
|
852
|
+
name: item.name,
|
|
853
|
+
arguments: item.arguments,
|
|
854
|
+
status: "completed",
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
if (item.type === "function_call_output") {
|
|
858
|
+
return {
|
|
859
|
+
type: "function_call_output",
|
|
860
|
+
id: item.id ?? `${item.call_id}_out`,
|
|
861
|
+
call_id: item.call_id,
|
|
862
|
+
output: item.output,
|
|
863
|
+
status: "completed",
|
|
864
|
+
};
|
|
865
|
+
}
|
|
866
|
+
return {
|
|
867
|
+
type: "reasoning",
|
|
868
|
+
id: item.id ?? `rs_${index + 1}`,
|
|
869
|
+
content: (item.content ?? []).map((part) => toStrictContentPart(part)),
|
|
870
|
+
summary: item.summary.map((part) => toStrictContentPart(part)),
|
|
871
|
+
encrypted_content: item.encrypted_content ?? null,
|
|
872
|
+
};
|
|
873
|
+
}
|
|
874
|
+
function toStrictTools(tools) {
|
|
875
|
+
if (!tools || tools.length === 0)
|
|
876
|
+
return [];
|
|
877
|
+
return tools.map((tool) => ({
|
|
878
|
+
type: "function",
|
|
879
|
+
name: tool.function.name,
|
|
880
|
+
description: tool.function.description ?? null,
|
|
881
|
+
parameters: tool.function.parameters ?? null,
|
|
882
|
+
strict: false,
|
|
883
|
+
}));
|
|
884
|
+
}
|
|
885
|
+
function toStrictToolChoice(choice) {
|
|
886
|
+
if (!choice)
|
|
887
|
+
return "auto";
|
|
888
|
+
if (choice === "none" || choice === "auto" || choice === "required") {
|
|
889
|
+
return choice;
|
|
890
|
+
}
|
|
891
|
+
if (choice.type === "allowed_tools") {
|
|
892
|
+
return {
|
|
893
|
+
type: "allowed_tools",
|
|
894
|
+
tools: choice.tools,
|
|
895
|
+
mode: choice.mode ?? "auto",
|
|
896
|
+
};
|
|
897
|
+
}
|
|
898
|
+
return { type: "function", name: choice.function.name };
|
|
899
|
+
}
|
|
900
|
+
function toStrictResponseResource(args) {
|
|
901
|
+
const now = Math.floor(Date.now() / 1000);
|
|
902
|
+
const createdAt = args.response.created_at ?? args.response.created ?? now;
|
|
903
|
+
const status = args.statusOverride ?? args.response.status ?? "completed";
|
|
904
|
+
const usage = args.response.usage
|
|
905
|
+
? {
|
|
906
|
+
input_tokens: args.response.usage.promptTokens ?? 0,
|
|
907
|
+
output_tokens: args.response.usage.completionTokens ?? 0,
|
|
908
|
+
total_tokens: args.response.usage.totalTokens ?? 0,
|
|
909
|
+
input_tokens_details: {
|
|
910
|
+
cached_tokens: 0,
|
|
911
|
+
},
|
|
912
|
+
output_tokens_details: {
|
|
913
|
+
reasoning_tokens: args.response.usage.reasoningTokens ?? 0,
|
|
914
|
+
},
|
|
915
|
+
}
|
|
916
|
+
: null;
|
|
917
|
+
return {
|
|
918
|
+
id: args.response.id,
|
|
919
|
+
object: "response",
|
|
920
|
+
created_at: createdAt,
|
|
921
|
+
completed_at: status === "completed" ? now : null,
|
|
922
|
+
status,
|
|
923
|
+
incomplete_details: null,
|
|
924
|
+
model: args.response.model ?? args.request.model,
|
|
925
|
+
previous_response_id: args.request.previous_response_id ?? null,
|
|
926
|
+
instructions: args.request.instructions ?? null,
|
|
927
|
+
output: (args.response.output ?? []).map((item, idx) => toStrictResponseItem(item, idx)),
|
|
928
|
+
error: args.response.error ?? null,
|
|
929
|
+
tools: toStrictTools(args.request.tools),
|
|
930
|
+
tool_choice: toStrictToolChoice(args.request.tool_choice),
|
|
931
|
+
truncation: args.response.truncation ?? args.request.truncation ??
|
|
932
|
+
"disabled",
|
|
933
|
+
parallel_tool_calls: args.response.parallel_tool_calls ??
|
|
934
|
+
args.request.parallel_tool_calls ?? false,
|
|
935
|
+
text: args.response.text
|
|
936
|
+
? asJsonValue(args.response.text)
|
|
937
|
+
: args.request.text
|
|
938
|
+
? asJsonValue(args.request.text)
|
|
939
|
+
: { format: { type: "text" } },
|
|
940
|
+
top_p: args.response.top_p ?? args.request.top_p ?? 1,
|
|
941
|
+
presence_penalty: args.response.presence_penalty ??
|
|
942
|
+
args.request.presence_penalty ?? 0,
|
|
943
|
+
frequency_penalty: args.response.frequency_penalty ??
|
|
944
|
+
args.request.frequency_penalty ?? 0,
|
|
945
|
+
top_logprobs: args.response.top_logprobs ?? args.request.top_logprobs ?? 0,
|
|
946
|
+
temperature: args.response.temperature ?? args.request.temperature ?? 1,
|
|
947
|
+
reasoning: args.request.reasoning
|
|
948
|
+
? {
|
|
949
|
+
effort: args.request.reasoning.effort ?? null,
|
|
950
|
+
summary: args.request.reasoning.summary ?? null,
|
|
951
|
+
}
|
|
952
|
+
: null,
|
|
953
|
+
usage,
|
|
954
|
+
max_output_tokens: args.request.max_output_tokens ?? null,
|
|
955
|
+
max_tool_calls: args.request.max_tool_calls ?? null,
|
|
956
|
+
store: args.response.store ?? args.request.store ?? false,
|
|
957
|
+
background: args.response.background ?? args.request.background ?? false,
|
|
958
|
+
service_tier: args.response.service_tier ?? args.request.service_tier ??
|
|
959
|
+
"default",
|
|
960
|
+
metadata: args.request.metadata ? asJsonValue(args.request.metadata) : {},
|
|
961
|
+
safety_identifier: args.response.safety_identifier ??
|
|
962
|
+
args.request.safety_identifier ?? null,
|
|
963
|
+
prompt_cache_key: args.response.prompt_cache_key ??
|
|
964
|
+
args.request.prompt_cache_key ?? null,
|
|
965
|
+
};
|
|
966
|
+
}
|
|
504
967
|
/**
|
|
505
968
|
* Start the WebSocket simulator server used by the Gambit debug UI.
|
|
506
969
|
*/
|
|
@@ -511,6 +974,13 @@ function startWebSocketSimulator(opts) {
|
|
|
511
974
|
(initialContext !== undefined);
|
|
512
975
|
const consoleTracer = opts.verbose ? (0, trace_js_1.makeConsoleTracer)() : undefined;
|
|
513
976
|
let resolvedDeckPath = resolveDeckPath(opts.deckPath);
|
|
977
|
+
const buildBotRootCache = new Map();
|
|
978
|
+
const activeWorkspaceId = opts.workspace?.id ?? null;
|
|
979
|
+
const activeWorkspaceOnboarding = Boolean(opts.workspace?.onboarding);
|
|
980
|
+
const workspaceScaffoldEnabled = Boolean(opts.workspace?.scaffoldEnabled);
|
|
981
|
+
const workspaceScaffoldRoot = opts.workspace?.scaffoldRoot
|
|
982
|
+
? path.resolve(opts.workspace.scaffoldRoot)
|
|
983
|
+
: null;
|
|
514
984
|
const sessionsRoot = (() => {
|
|
515
985
|
const base = opts.sessionDir
|
|
516
986
|
? path.resolve(opts.sessionDir)
|
|
@@ -519,10 +989,23 @@ function startWebSocketSimulator(opts) {
|
|
|
519
989
|
dntShim.Deno.mkdirSync(base, { recursive: true });
|
|
520
990
|
}
|
|
521
991
|
catch (err) {
|
|
522
|
-
logger.warn(`[sim] unable to ensure
|
|
992
|
+
logger.warn(`[sim] unable to ensure workspace state directory ${base}: ${err instanceof Error ? err.message : err}`);
|
|
523
993
|
}
|
|
524
994
|
return base;
|
|
525
995
|
})();
|
|
996
|
+
const workspaceRoot = (() => {
|
|
997
|
+
const dir = workspaceScaffoldRoot ?? sessionsRoot;
|
|
998
|
+
if (workspaceScaffoldEnabled) {
|
|
999
|
+
try {
|
|
1000
|
+
dntShim.Deno.mkdirSync(dir, { recursive: true });
|
|
1001
|
+
}
|
|
1002
|
+
catch (err) {
|
|
1003
|
+
logger.warn(`[sim] unable to ensure workspace directory ${dir}: ${err instanceof Error ? err.message : err}`);
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
return dir;
|
|
1007
|
+
})();
|
|
1008
|
+
const workspaceById = new Map();
|
|
526
1009
|
const ensureDir = (dir) => {
|
|
527
1010
|
try {
|
|
528
1011
|
dntShim.Deno.mkdirSync(dir, { recursive: true });
|
|
@@ -538,19 +1021,270 @@ function startWebSocketSimulator(opts) {
|
|
|
538
1021
|
return slug || "session";
|
|
539
1022
|
};
|
|
540
1023
|
const testBotRuns = new Map();
|
|
541
|
-
const broadcastTestBot = (payload) => {
|
|
1024
|
+
const broadcastTestBot = (payload, workspaceId) => {
|
|
1025
|
+
if (workspaceId) {
|
|
1026
|
+
const state = readSessionState(workspaceId);
|
|
1027
|
+
if (state) {
|
|
1028
|
+
appendWorkspaceEnvelope(state, "test", payload);
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
(0, durable_streams_js_1.appendDurableStreamEvent)(WORKSPACE_STREAM_ID, payload);
|
|
542
1032
|
(0, durable_streams_js_1.appendDurableStreamEvent)(TEST_STREAM_ID, payload);
|
|
543
1033
|
};
|
|
1034
|
+
const buildBotRuns = new Map();
|
|
1035
|
+
const registerWorkspace = (record) => {
|
|
1036
|
+
workspaceById.set(record.id, record);
|
|
1037
|
+
return record;
|
|
1038
|
+
};
|
|
1039
|
+
const resolveWorkspaceRecord = (workspaceId) => {
|
|
1040
|
+
if (!workspaceId)
|
|
1041
|
+
return null;
|
|
1042
|
+
const cached = workspaceById.get(workspaceId);
|
|
1043
|
+
if (cached)
|
|
1044
|
+
return cached;
|
|
1045
|
+
const state = readSessionState(workspaceId);
|
|
1046
|
+
const meta = state?.meta ?? {};
|
|
1047
|
+
const deckPath = typeof meta
|
|
1048
|
+
.workspaceRootDeckPath === "string"
|
|
1049
|
+
? meta.workspaceRootDeckPath
|
|
1050
|
+
: typeof meta.deck === "string"
|
|
1051
|
+
? meta.deck
|
|
1052
|
+
: undefined;
|
|
1053
|
+
const rootDir = typeof meta.workspaceRootDir ===
|
|
1054
|
+
"string"
|
|
1055
|
+
? meta.workspaceRootDir
|
|
1056
|
+
: deckPath
|
|
1057
|
+
? path.dirname(deckPath)
|
|
1058
|
+
: undefined;
|
|
1059
|
+
if (!deckPath || !rootDir)
|
|
1060
|
+
return null;
|
|
1061
|
+
const createdAt = typeof meta.workspaceCreatedAt ===
|
|
1062
|
+
"string"
|
|
1063
|
+
? meta.workspaceCreatedAt
|
|
1064
|
+
: typeof meta.sessionCreatedAt === "string"
|
|
1065
|
+
? meta.sessionCreatedAt
|
|
1066
|
+
: new Date().toISOString();
|
|
1067
|
+
return registerWorkspace({
|
|
1068
|
+
id: workspaceId,
|
|
1069
|
+
rootDir,
|
|
1070
|
+
rootDeckPath: deckPath,
|
|
1071
|
+
createdAt,
|
|
1072
|
+
});
|
|
1073
|
+
};
|
|
1074
|
+
const resolveBuildBotRoot = async (workspaceId) => {
|
|
1075
|
+
const override = dntShim.Deno.env.get("GAMBIT_SIMULATOR_BUILD_BOT_ROOT")?.trim();
|
|
1076
|
+
if (override) {
|
|
1077
|
+
const root = await dntShim.Deno.realPath(override);
|
|
1078
|
+
const info = await dntShim.Deno.stat(root);
|
|
1079
|
+
if (!info.isDirectory) {
|
|
1080
|
+
throw new Error(`Build bot root is not a directory: ${root}`);
|
|
1081
|
+
}
|
|
1082
|
+
(0, server_helpers_js_1.assertSafeBuildBotRoot)(root, GAMBIT_BOT_SOURCE_DIR);
|
|
1083
|
+
await ensureGambitPolicyInBotRoot(root);
|
|
1084
|
+
return root;
|
|
1085
|
+
}
|
|
1086
|
+
const cacheKey = workspaceId ?? "default";
|
|
1087
|
+
const cached = buildBotRootCache.get(cacheKey);
|
|
1088
|
+
if (cached)
|
|
1089
|
+
return cached;
|
|
1090
|
+
const record = resolveWorkspaceRecord(workspaceId);
|
|
1091
|
+
const candidate = record?.rootDir ?? path.dirname(resolvedDeckPath);
|
|
1092
|
+
const root = await dntShim.Deno.realPath(candidate);
|
|
1093
|
+
const info = await dntShim.Deno.stat(root);
|
|
1094
|
+
if (!info.isDirectory) {
|
|
1095
|
+
throw new Error(`Build bot root is not a directory: ${root}`);
|
|
1096
|
+
}
|
|
1097
|
+
(0, server_helpers_js_1.assertSafeBuildBotRoot)(root, GAMBIT_BOT_SOURCE_DIR);
|
|
1098
|
+
await ensureGambitPolicyInBotRoot(root);
|
|
1099
|
+
buildBotRootCache.set(cacheKey, root);
|
|
1100
|
+
return root;
|
|
1101
|
+
};
|
|
1102
|
+
const logWorkspaceBotRoot = async (endpoint, workspaceId) => {
|
|
1103
|
+
try {
|
|
1104
|
+
const root = await resolveBuildBotRoot(workspaceId);
|
|
1105
|
+
logger.info(`[sim] ${endpoint}: workspaceId=${workspaceId ?? "(none)"} botRoot=${root}`);
|
|
1106
|
+
}
|
|
1107
|
+
catch (err) {
|
|
1108
|
+
logger.warn(`[sim] ${endpoint}: workspaceId=${workspaceId ?? "(none)"} botRoot=<unresolved> ${err instanceof Error ? err.message : String(err)}`);
|
|
1109
|
+
}
|
|
1110
|
+
};
|
|
1111
|
+
if (opts.workspace?.id && opts.workspace.rootDir && opts.workspace.rootDeckPath) {
|
|
1112
|
+
registerWorkspace({
|
|
1113
|
+
id: opts.workspace.id,
|
|
1114
|
+
rootDir: opts.workspace.rootDir,
|
|
1115
|
+
rootDeckPath: opts.workspace.rootDeckPath,
|
|
1116
|
+
createdAt: new Date().toISOString(),
|
|
1117
|
+
});
|
|
1118
|
+
}
|
|
1119
|
+
const MAX_FILE_PREVIEW_BYTES = 250_000;
|
|
1120
|
+
const shouldReadBuildDeckLabel = (relativePath) => {
|
|
1121
|
+
const lower = path.basename(relativePath).toLowerCase();
|
|
1122
|
+
return lower === "prompt.md" || lower.endsWith(".deck.md");
|
|
1123
|
+
};
|
|
1124
|
+
const readBuildDeckLabel = async (fullPath) => {
|
|
1125
|
+
try {
|
|
1126
|
+
const text = await dntShim.Deno.readTextFile(fullPath);
|
|
1127
|
+
const lines = text.split(/\r?\n/);
|
|
1128
|
+
if (lines[0] !== "+++")
|
|
1129
|
+
return undefined;
|
|
1130
|
+
const endIndex = lines.indexOf("+++", 1);
|
|
1131
|
+
if (endIndex === -1)
|
|
1132
|
+
return undefined;
|
|
1133
|
+
const frontmatter = lines.slice(1, endIndex).join("\n");
|
|
1134
|
+
const parsed = (0, mod_js_3.parse)(frontmatter);
|
|
1135
|
+
const label = typeof parsed.label === "string" ? parsed.label.trim() : "";
|
|
1136
|
+
return label.length > 0 ? label : undefined;
|
|
1137
|
+
}
|
|
1138
|
+
catch {
|
|
1139
|
+
return undefined;
|
|
1140
|
+
}
|
|
1141
|
+
};
|
|
1142
|
+
const listBuildBotFiles = async (root) => {
|
|
1143
|
+
const entries = [];
|
|
1144
|
+
const shouldSkipRelativePath = (relativePath) => {
|
|
1145
|
+
const segments = relativePath.split(/\\|\//g).filter(Boolean);
|
|
1146
|
+
return segments.includes(".gambit");
|
|
1147
|
+
};
|
|
1148
|
+
const walk = async (dir, relativePrefix) => {
|
|
1149
|
+
for await (const entry of dntShim.Deno.readDir(dir)) {
|
|
1150
|
+
if (entry.isSymlink)
|
|
1151
|
+
continue;
|
|
1152
|
+
const fullPath = path.join(dir, entry.name);
|
|
1153
|
+
const relPath = relativePrefix
|
|
1154
|
+
? path.join(relativePrefix, entry.name)
|
|
1155
|
+
: entry.name;
|
|
1156
|
+
if (shouldSkipRelativePath(relPath))
|
|
1157
|
+
continue;
|
|
1158
|
+
if (entry.isDirectory) {
|
|
1159
|
+
entries.push({ path: relPath, type: "dir" });
|
|
1160
|
+
await walk(fullPath, relPath);
|
|
1161
|
+
}
|
|
1162
|
+
else if (entry.isFile) {
|
|
1163
|
+
const info = await dntShim.Deno.stat(fullPath);
|
|
1164
|
+
const label = shouldReadBuildDeckLabel(relPath)
|
|
1165
|
+
? await readBuildDeckLabel(fullPath)
|
|
1166
|
+
: undefined;
|
|
1167
|
+
entries.push({
|
|
1168
|
+
path: relPath,
|
|
1169
|
+
type: "file",
|
|
1170
|
+
size: info.size,
|
|
1171
|
+
modifiedAt: info.mtime ? info.mtime.toISOString() : undefined,
|
|
1172
|
+
label,
|
|
1173
|
+
});
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
};
|
|
1177
|
+
await walk(root, "");
|
|
1178
|
+
return entries;
|
|
1179
|
+
};
|
|
1180
|
+
const resolveBuildBotPath = async (root, inputPath) => {
|
|
1181
|
+
if (!inputPath || typeof inputPath !== "string") {
|
|
1182
|
+
throw new Error("path is required");
|
|
1183
|
+
}
|
|
1184
|
+
const normalizedInput = path.normalize(inputPath);
|
|
1185
|
+
const segments = normalizedInput.split(/\\|\//g);
|
|
1186
|
+
if (segments.includes("..")) {
|
|
1187
|
+
throw new Error("path traversal is not allowed");
|
|
1188
|
+
}
|
|
1189
|
+
const candidate = path.isAbsolute(normalizedInput)
|
|
1190
|
+
? normalizedInput
|
|
1191
|
+
: path.resolve(root, normalizedInput);
|
|
1192
|
+
const relativePath = path.relative(root, candidate);
|
|
1193
|
+
if (relativePath.startsWith("..") || path.isAbsolute(relativePath)) {
|
|
1194
|
+
throw new Error("path escapes bot root");
|
|
1195
|
+
}
|
|
1196
|
+
const stat = await dntShim.Deno.lstat(candidate);
|
|
1197
|
+
if (stat.isSymlink) {
|
|
1198
|
+
throw new Error("symlinks are not allowed");
|
|
1199
|
+
}
|
|
1200
|
+
const realCandidate = await dntShim.Deno.realPath(candidate);
|
|
1201
|
+
const realRelative = path.relative(root, realCandidate);
|
|
1202
|
+
if (realRelative.startsWith("..") || path.isAbsolute(realRelative)) {
|
|
1203
|
+
throw new Error("path escapes bot root");
|
|
1204
|
+
}
|
|
1205
|
+
return { fullPath: candidate, relativePath, stat };
|
|
1206
|
+
};
|
|
1207
|
+
const readPreviewText = (bytes) => {
|
|
1208
|
+
const limit = Math.min(bytes.length, 8192);
|
|
1209
|
+
for (let i = 0; i < limit; i += 1) {
|
|
1210
|
+
if (bytes[i] === 0)
|
|
1211
|
+
return null;
|
|
1212
|
+
}
|
|
1213
|
+
const decoder = new TextDecoder("utf-8", { fatal: true });
|
|
1214
|
+
try {
|
|
1215
|
+
return decoder.decode(bytes);
|
|
1216
|
+
}
|
|
1217
|
+
catch {
|
|
1218
|
+
return null;
|
|
1219
|
+
}
|
|
1220
|
+
};
|
|
1221
|
+
const isBuildStreamDebugEnabled = (() => {
|
|
1222
|
+
const raw = dntShim.Deno.env.get("GAMBIT_BUILD_STREAM_DEBUG")?.trim()
|
|
1223
|
+
.toLowerCase();
|
|
1224
|
+
return raw === "1" || raw === "true" || raw === "yes";
|
|
1225
|
+
})();
|
|
1226
|
+
const logBuildStreamDebug = (event, payload) => {
|
|
1227
|
+
if (!isBuildStreamDebugEnabled)
|
|
1228
|
+
return;
|
|
1229
|
+
const ts = new Date().toISOString();
|
|
1230
|
+
if (payload && Object.keys(payload).length > 0) {
|
|
1231
|
+
logger.info(`[build-stream-debug] ${ts} ${event} ${JSON.stringify(payload)}`);
|
|
1232
|
+
return;
|
|
1233
|
+
}
|
|
1234
|
+
logger.info(`[build-stream-debug] ${ts} ${event}`);
|
|
1235
|
+
};
|
|
1236
|
+
const broadcastBuildBot = (payload, workspaceId) => {
|
|
1237
|
+
const record = payload && typeof payload === "object"
|
|
1238
|
+
? payload
|
|
1239
|
+
: null;
|
|
1240
|
+
const type = record && typeof record.type === "string"
|
|
1241
|
+
? record.type
|
|
1242
|
+
: "(unknown)";
|
|
1243
|
+
const runId = record && typeof record.runId === "string"
|
|
1244
|
+
? record.runId
|
|
1245
|
+
: record && record.run && typeof record.run === "object" &&
|
|
1246
|
+
typeof record.run.id === "string"
|
|
1247
|
+
? record.run.id
|
|
1248
|
+
: undefined;
|
|
1249
|
+
const traceType = type === "buildBotTrace" && record &&
|
|
1250
|
+
record.event && typeof record.event === "object" &&
|
|
1251
|
+
typeof record.event.type === "string"
|
|
1252
|
+
? record.event.type
|
|
1253
|
+
: undefined;
|
|
1254
|
+
logBuildStreamDebug("broadcastBuildBot", {
|
|
1255
|
+
type,
|
|
1256
|
+
runId,
|
|
1257
|
+
traceType,
|
|
1258
|
+
});
|
|
1259
|
+
const eventWorkspaceId = workspaceId ??
|
|
1260
|
+
(typeof runId === "string" ? runId : undefined);
|
|
1261
|
+
if (eventWorkspaceId) {
|
|
1262
|
+
const state = readSessionState(eventWorkspaceId);
|
|
1263
|
+
if (state) {
|
|
1264
|
+
appendWorkspaceEnvelope(state, "build", payload);
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
(0, durable_streams_js_1.appendDurableStreamEvent)(WORKSPACE_STREAM_ID, payload);
|
|
1268
|
+
(0, durable_streams_js_1.appendDurableStreamEvent)(BUILD_STREAM_ID, payload);
|
|
1269
|
+
};
|
|
544
1270
|
let deckSlug = deckSlugFromPath(resolvedDeckPath);
|
|
545
1271
|
let deckLabel = undefined;
|
|
1272
|
+
let rootStartMode = undefined;
|
|
546
1273
|
const enrichStateWithSession = (state) => {
|
|
547
1274
|
const meta = { ...(state.meta ?? {}) };
|
|
548
1275
|
const now = new Date();
|
|
1276
|
+
meta.sessionUpdatedAt = now.toISOString();
|
|
549
1277
|
if (typeof meta.sessionId !== "string") {
|
|
550
1278
|
const stamp = now.toISOString().replace(/[:.]/g, "-");
|
|
551
1279
|
meta.sessionId = `${deckSlug}-${stamp}`;
|
|
552
1280
|
meta.sessionCreatedAt = now.toISOString();
|
|
553
1281
|
}
|
|
1282
|
+
if (typeof meta.workspaceId !== "string") {
|
|
1283
|
+
meta.workspaceId = String(meta.sessionId);
|
|
1284
|
+
}
|
|
1285
|
+
if (typeof meta.workspaceSchemaVersion !== "string") {
|
|
1286
|
+
meta.workspaceSchemaVersion = workspace_contract_js_1.WORKSPACE_STATE_SCHEMA_VERSION;
|
|
1287
|
+
}
|
|
554
1288
|
if (typeof meta.deck !== "string") {
|
|
555
1289
|
meta.deck = resolvedDeckPath;
|
|
556
1290
|
}
|
|
@@ -564,39 +1298,143 @@ function startWebSocketSimulator(opts) {
|
|
|
564
1298
|
typeof meta.sessionDir === "string") {
|
|
565
1299
|
meta.sessionStatePath = path.join(meta.sessionDir, "state.json");
|
|
566
1300
|
}
|
|
1301
|
+
if (typeof meta.sessionEventsPath !== "string" &&
|
|
1302
|
+
typeof meta.sessionDir === "string") {
|
|
1303
|
+
meta.sessionEventsPath = path.join(meta.sessionDir, "events.jsonl");
|
|
1304
|
+
}
|
|
1305
|
+
if (typeof meta.sessionBuildStatePath !== "string" &&
|
|
1306
|
+
typeof meta.sessionDir === "string") {
|
|
1307
|
+
meta.sessionBuildStatePath = path.join(meta.sessionDir, "build_state.json");
|
|
1308
|
+
}
|
|
567
1309
|
const dir = typeof meta.sessionDir === "string"
|
|
568
1310
|
? meta.sessionDir
|
|
569
1311
|
: undefined;
|
|
570
1312
|
return { state: { ...state, meta }, dir };
|
|
571
1313
|
};
|
|
572
|
-
const persistSessionState = (
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
1314
|
+
const { parseFiniteInteger, selectCanonicalScenarioRunSummary, appendWorkspaceEnvelope, appendSessionEvent, appendFeedbackLog, appendGradingLog, appendServerErrorLog, persistSessionState, readSessionStateStrict, readSessionState, readBuildState, } = (0, server_session_store_js_1.createSessionStore)({
|
|
1315
|
+
sessionsRoot,
|
|
1316
|
+
ensureDir,
|
|
1317
|
+
randomId: server_helpers_js_1.randomId,
|
|
1318
|
+
logger,
|
|
1319
|
+
enrichStateWithSession,
|
|
1320
|
+
workspaceStateSchemaVersion: workspace_contract_js_1.WORKSPACE_STATE_SCHEMA_VERSION,
|
|
1321
|
+
workspaceSchemaError: workspace_contract_js_1.workspaceSchemaError,
|
|
1322
|
+
});
|
|
1323
|
+
const traceCategory = (type) => {
|
|
1324
|
+
switch (type) {
|
|
1325
|
+
case "message.user":
|
|
1326
|
+
case "model.result":
|
|
1327
|
+
return "turn";
|
|
1328
|
+
case "tool.call":
|
|
1329
|
+
case "tool.result":
|
|
1330
|
+
return "tool";
|
|
1331
|
+
case "log":
|
|
1332
|
+
case "monolog":
|
|
1333
|
+
return "status";
|
|
1334
|
+
case "run.start":
|
|
1335
|
+
case "run.end":
|
|
1336
|
+
case "deck.start":
|
|
1337
|
+
case "deck.end":
|
|
1338
|
+
case "action.start":
|
|
1339
|
+
case "action.end":
|
|
1340
|
+
case "model.call":
|
|
1341
|
+
return "lifecycle";
|
|
1342
|
+
default:
|
|
1343
|
+
return "trace";
|
|
583
1344
|
}
|
|
584
|
-
return enriched;
|
|
585
1345
|
};
|
|
586
|
-
const
|
|
587
|
-
const
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
1346
|
+
const buildWorkspaceMeta = (record, base) => {
|
|
1347
|
+
const createdAt = typeof base?.sessionCreatedAt ===
|
|
1348
|
+
"string"
|
|
1349
|
+
? base.sessionCreatedAt
|
|
1350
|
+
: typeof base
|
|
1351
|
+
?.workspaceCreatedAt === "string"
|
|
1352
|
+
? base.workspaceCreatedAt
|
|
1353
|
+
: new Date().toISOString();
|
|
1354
|
+
return {
|
|
1355
|
+
...(base ?? {}),
|
|
1356
|
+
workspaceSchemaVersion: workspace_contract_js_1.WORKSPACE_STATE_SCHEMA_VERSION,
|
|
1357
|
+
workspaceId: record.id,
|
|
1358
|
+
workspaceRootDeckPath: record.rootDeckPath,
|
|
1359
|
+
workspaceRootDir: record.rootDir,
|
|
1360
|
+
workspaceCreatedAt: base
|
|
1361
|
+
?.workspaceCreatedAt ?? createdAt,
|
|
1362
|
+
sessionCreatedAt: base
|
|
1363
|
+
?.sessionCreatedAt ?? createdAt,
|
|
1364
|
+
deck: record.rootDeckPath,
|
|
1365
|
+
deckSlug: deckSlugFromPath(record.rootDeckPath),
|
|
1366
|
+
sessionId: record.id,
|
|
1367
|
+
};
|
|
1368
|
+
};
|
|
1369
|
+
const createWorkspaceSession = async (opts) => {
|
|
1370
|
+
const createdAt = new Date().toISOString();
|
|
1371
|
+
if (workspaceScaffoldEnabled) {
|
|
1372
|
+
const scaffold = await (0, workspace_js_1.createWorkspaceScaffold)({
|
|
1373
|
+
baseDir: workspaceRoot,
|
|
1374
|
+
});
|
|
1375
|
+
const record = registerWorkspace(scaffold);
|
|
1376
|
+
persistSessionState({
|
|
1377
|
+
runId: record.id,
|
|
1378
|
+
messages: [],
|
|
1379
|
+
meta: buildWorkspaceMeta(record, {
|
|
1380
|
+
sessionCreatedAt: record.createdAt,
|
|
1381
|
+
workspaceCreatedAt: record.createdAt,
|
|
1382
|
+
workspaceOnboarding: opts?.onboarding ?? false,
|
|
1383
|
+
}),
|
|
1384
|
+
});
|
|
1385
|
+
return record;
|
|
595
1386
|
}
|
|
596
|
-
|
|
597
|
-
|
|
1387
|
+
const workspaceId = (0, server_helpers_js_1.randomId)("workspace");
|
|
1388
|
+
const rootDeckPath = resolvedDeckPath;
|
|
1389
|
+
const rootDir = path.dirname(rootDeckPath);
|
|
1390
|
+
const record = registerWorkspace({
|
|
1391
|
+
id: workspaceId,
|
|
1392
|
+
rootDir,
|
|
1393
|
+
rootDeckPath,
|
|
1394
|
+
createdAt,
|
|
1395
|
+
});
|
|
1396
|
+
persistSessionState({
|
|
1397
|
+
runId: record.id,
|
|
1398
|
+
messages: [],
|
|
1399
|
+
meta: buildWorkspaceMeta(record, {
|
|
1400
|
+
sessionCreatedAt: createdAt,
|
|
1401
|
+
workspaceCreatedAt: createdAt,
|
|
1402
|
+
workspaceOnboarding: opts?.onboarding ?? false,
|
|
1403
|
+
}),
|
|
1404
|
+
});
|
|
1405
|
+
return record;
|
|
1406
|
+
};
|
|
1407
|
+
if (opts.workspace?.id && opts.workspace.rootDir && opts.workspace.rootDeckPath) {
|
|
1408
|
+
const existing = readSessionState(opts.workspace.id);
|
|
1409
|
+
if (!existing) {
|
|
1410
|
+
persistSessionState({
|
|
1411
|
+
runId: opts.workspace.id,
|
|
1412
|
+
messages: [],
|
|
1413
|
+
meta: buildWorkspaceMeta({
|
|
1414
|
+
id: opts.workspace.id,
|
|
1415
|
+
rootDir: opts.workspace.rootDir,
|
|
1416
|
+
rootDeckPath: opts.workspace.rootDeckPath,
|
|
1417
|
+
}, {
|
|
1418
|
+
sessionCreatedAt: new Date().toISOString(),
|
|
1419
|
+
workspaceCreatedAt: new Date().toISOString(),
|
|
1420
|
+
workspaceOnboarding: activeWorkspaceOnboarding,
|
|
1421
|
+
}),
|
|
1422
|
+
});
|
|
598
1423
|
}
|
|
599
|
-
|
|
1424
|
+
}
|
|
1425
|
+
const activateWorkspaceDeck = async (workspaceId) => {
|
|
1426
|
+
if (!workspaceId)
|
|
1427
|
+
return;
|
|
1428
|
+
const record = resolveWorkspaceRecord(workspaceId);
|
|
1429
|
+
if (!record)
|
|
1430
|
+
return;
|
|
1431
|
+
const nextPath = resolveDeckPath(record.rootDeckPath);
|
|
1432
|
+
if (nextPath === resolvedDeckPath)
|
|
1433
|
+
return;
|
|
1434
|
+
resolvedDeckPath = nextPath;
|
|
1435
|
+
buildBotRootCache.delete("default");
|
|
1436
|
+
reloadPrimaryDeck();
|
|
1437
|
+
await deckLoadPromise.catch(() => null);
|
|
600
1438
|
};
|
|
601
1439
|
const deleteSessionState = (sessionId) => {
|
|
602
1440
|
if (!sessionId ||
|
|
@@ -664,23 +1502,138 @@ function startWebSocketSimulator(opts) {
|
|
|
664
1502
|
return [];
|
|
665
1503
|
}
|
|
666
1504
|
};
|
|
667
|
-
const
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
1505
|
+
const getWorkspaceIdFromQuery = (url) => (0, workspace_contract_js_1.resolveWorkspaceIdFromSearchParams)(url.searchParams);
|
|
1506
|
+
const getWorkspaceIdFromBody = (body) => {
|
|
1507
|
+
if (!body || typeof body !== "object")
|
|
1508
|
+
return undefined;
|
|
1509
|
+
return (0, workspace_contract_js_1.resolveWorkspaceIdFromRecord)(body);
|
|
1510
|
+
};
|
|
1511
|
+
const findTestRunByWorkspaceId = (workspaceId) => {
|
|
1512
|
+
for (const candidate of testBotRuns.values()) {
|
|
1513
|
+
if (candidate.run.workspaceId === workspaceId ||
|
|
1514
|
+
candidate.run.sessionId === workspaceId) {
|
|
1515
|
+
return candidate;
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
return undefined;
|
|
1519
|
+
};
|
|
1520
|
+
const buildWorkspaceReadModel = async (workspaceId, opts) => {
|
|
1521
|
+
let state;
|
|
1522
|
+
try {
|
|
1523
|
+
state = readSessionStateStrict(workspaceId, { withTraces: true });
|
|
1524
|
+
}
|
|
1525
|
+
catch (err) {
|
|
1526
|
+
return {
|
|
1527
|
+
error: err instanceof Error ? err.message : String(err),
|
|
1528
|
+
status: 400,
|
|
1529
|
+
};
|
|
1530
|
+
}
|
|
1531
|
+
if (!state) {
|
|
1532
|
+
return {
|
|
1533
|
+
error: "Workspace not found",
|
|
1534
|
+
status: 404,
|
|
1535
|
+
};
|
|
1536
|
+
}
|
|
1537
|
+
const buildEntry = buildBotRuns.get(workspaceId);
|
|
1538
|
+
const buildRun = buildEntry?.run ?? buildRunFromProjection(workspaceId);
|
|
1539
|
+
const requestedTestRunId = typeof opts?.requestedTestRunId === "string" &&
|
|
1540
|
+
opts.requestedTestRunId.trim().length > 0
|
|
1541
|
+
? opts.requestedTestRunId
|
|
1542
|
+
: null;
|
|
1543
|
+
const requestedTestEntry = requestedTestRunId
|
|
1544
|
+
? testBotRuns.get(requestedTestRunId)
|
|
675
1545
|
: undefined;
|
|
676
|
-
const
|
|
677
|
-
|
|
678
|
-
|
|
1546
|
+
const requestedLiveRun = requestedTestEntry?.run &&
|
|
1547
|
+
(requestedTestEntry.run.workspaceId === workspaceId ||
|
|
1548
|
+
requestedTestEntry.run.sessionId === workspaceId)
|
|
1549
|
+
? requestedTestEntry.run
|
|
679
1550
|
: undefined;
|
|
680
|
-
const
|
|
681
|
-
?
|
|
682
|
-
|
|
683
|
-
|
|
1551
|
+
const persistedRequestedRun = requestedTestRunId
|
|
1552
|
+
? readPersistedTestRunStatusById(state, workspaceId, requestedTestRunId)
|
|
1553
|
+
: null;
|
|
1554
|
+
const testEntry = requestedLiveRun
|
|
1555
|
+
? undefined
|
|
1556
|
+
: findTestRunByWorkspaceId(workspaceId);
|
|
1557
|
+
const testRun = requestedLiveRun ?? persistedRequestedRun ??
|
|
1558
|
+
testEntry?.run ?? {
|
|
1559
|
+
id: "",
|
|
1560
|
+
status: "idle",
|
|
1561
|
+
messages: [],
|
|
1562
|
+
traces: [],
|
|
1563
|
+
toolInserts: [],
|
|
1564
|
+
workspaceId,
|
|
1565
|
+
sessionId: workspaceId,
|
|
1566
|
+
};
|
|
1567
|
+
if (!requestedLiveRun && !persistedRequestedRun && !testEntry) {
|
|
1568
|
+
syncTestBotRunFromState(testRun, state);
|
|
1569
|
+
const meta = state.meta && typeof state.meta === "object"
|
|
1570
|
+
? state.meta
|
|
1571
|
+
: null;
|
|
1572
|
+
if (meta) {
|
|
1573
|
+
const selectedScenarioSummary = selectCanonicalScenarioRunSummary(meta);
|
|
1574
|
+
if (selectedScenarioSummary) {
|
|
1575
|
+
testRun.id = selectedScenarioSummary.scenarioRunId;
|
|
1576
|
+
if (testRun.status === "idle") {
|
|
1577
|
+
testRun.status = "completed";
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
await deckLoadPromise.catch(() => null);
|
|
1583
|
+
const requestedDeck = opts?.requestedTestDeckPath ?? null;
|
|
1584
|
+
const testSelection = requestedDeck
|
|
1585
|
+
? resolveTestDeck(requestedDeck)
|
|
1586
|
+
: availableTestDecks[0];
|
|
1587
|
+
const testSchemaDesc = testSelection
|
|
1588
|
+
? await describeDeckInputSchemaFromPath(testSelection.path)
|
|
1589
|
+
: undefined;
|
|
1590
|
+
const session = {
|
|
1591
|
+
workspaceId,
|
|
1592
|
+
messages: state.messages,
|
|
1593
|
+
messageRefs: state.messageRefs,
|
|
1594
|
+
feedback: state.feedback,
|
|
1595
|
+
traces: state.traces,
|
|
1596
|
+
notes: state.notes,
|
|
1597
|
+
meta: state.meta,
|
|
1598
|
+
};
|
|
1599
|
+
return {
|
|
1600
|
+
workspaceId,
|
|
1601
|
+
build: { run: buildRun },
|
|
1602
|
+
test: {
|
|
1603
|
+
run: testRun,
|
|
1604
|
+
botPath: testSelection?.path ?? null,
|
|
1605
|
+
botLabel: testSelection?.label ?? null,
|
|
1606
|
+
botDescription: testSelection?.description ?? null,
|
|
1607
|
+
selectedDeckId: testSelection?.id ?? null,
|
|
1608
|
+
inputSchema: testSchemaDesc?.schema ?? null,
|
|
1609
|
+
inputSchemaError: testSchemaDesc?.error ?? null,
|
|
1610
|
+
defaults: { input: testSchemaDesc?.defaults },
|
|
1611
|
+
testDecks: availableTestDecks,
|
|
1612
|
+
},
|
|
1613
|
+
grade: {
|
|
1614
|
+
graderDecks: availableGraderDecks,
|
|
1615
|
+
sessions: listSessions(),
|
|
1616
|
+
},
|
|
1617
|
+
session,
|
|
1618
|
+
};
|
|
1619
|
+
};
|
|
1620
|
+
const buildSessionMeta = (sessionId, state) => {
|
|
1621
|
+
const meta = state?.meta ?? {};
|
|
1622
|
+
const createdAt = typeof meta.sessionCreatedAt === "string"
|
|
1623
|
+
? meta.sessionCreatedAt
|
|
1624
|
+
: undefined;
|
|
1625
|
+
const deck = typeof meta.deck === "string" ? meta.deck : undefined;
|
|
1626
|
+
const deckSlug = typeof meta.deckSlug === "string"
|
|
1627
|
+
? meta.deckSlug
|
|
1628
|
+
: undefined;
|
|
1629
|
+
const testBotName = typeof meta.testBotName ===
|
|
1630
|
+
"string"
|
|
1631
|
+
? meta.testBotName
|
|
1632
|
+
: undefined;
|
|
1633
|
+
const gradingRuns = Array.isArray(meta.gradingRuns)
|
|
1634
|
+
? meta.gradingRuns.map((run) => ({
|
|
1635
|
+
id: typeof run.id === "string" ? run.id : (0, server_helpers_js_1.randomId)("cal"),
|
|
1636
|
+
graderId: run.graderId,
|
|
684
1637
|
graderPath: run.graderPath,
|
|
685
1638
|
graderLabel: run.graderLabel,
|
|
686
1639
|
status: run.status,
|
|
@@ -692,7 +1645,7 @@ function startWebSocketSimulator(opts) {
|
|
|
692
1645
|
}))
|
|
693
1646
|
: Array.isArray(meta.calibrationRuns)
|
|
694
1647
|
? meta.calibrationRuns.map((run) => ({
|
|
695
|
-
id: typeof run.id === "string" ? run.id : randomId("cal"),
|
|
1648
|
+
id: typeof run.id === "string" ? run.id : (0, server_helpers_js_1.randomId)("cal"),
|
|
696
1649
|
graderId: run.graderId,
|
|
697
1650
|
graderPath: run.graderPath,
|
|
698
1651
|
graderLabel: run.graderLabel,
|
|
@@ -848,6 +1801,11 @@ function startWebSocketSimulator(opts) {
|
|
|
848
1801
|
role: msg.role,
|
|
849
1802
|
content,
|
|
850
1803
|
messageRefId: refId,
|
|
1804
|
+
messageSource: refs[i]?.source === "scenario" ||
|
|
1805
|
+
refs[i]?.source === "manual" ||
|
|
1806
|
+
refs[i]?.source === "artifact"
|
|
1807
|
+
? refs[i].source
|
|
1808
|
+
: undefined,
|
|
851
1809
|
feedback: refId ? feedbackByRef.get(refId) : undefined,
|
|
852
1810
|
});
|
|
853
1811
|
continue;
|
|
@@ -858,6 +1816,11 @@ function startWebSocketSimulator(opts) {
|
|
|
858
1816
|
role: "assistant",
|
|
859
1817
|
content: respondSummary.displayText,
|
|
860
1818
|
messageRefId: refId,
|
|
1819
|
+
messageSource: refs[i]?.source === "scenario" ||
|
|
1820
|
+
refs[i]?.source === "manual" ||
|
|
1821
|
+
refs[i]?.source === "artifact"
|
|
1822
|
+
? refs[i].source
|
|
1823
|
+
: undefined,
|
|
861
1824
|
feedback: refId ? feedbackByRef.get(refId) : undefined,
|
|
862
1825
|
respondStatus: respondSummary.status,
|
|
863
1826
|
respondCode: respondSummary.code,
|
|
@@ -887,32 +1850,229 @@ function startWebSocketSimulator(opts) {
|
|
|
887
1850
|
: fallbackToolInserts,
|
|
888
1851
|
};
|
|
889
1852
|
};
|
|
890
|
-
const
|
|
1853
|
+
const buildScenarioConversationArtifacts = (state) => {
|
|
891
1854
|
const rawMessages = state.messages ?? [];
|
|
1855
|
+
const refs = state.messageRefs ?? [];
|
|
892
1856
|
const conversation = [];
|
|
893
|
-
|
|
1857
|
+
const assistantTurns = [];
|
|
1858
|
+
for (let i = 0; i < rawMessages.length; i++) {
|
|
1859
|
+
const msg = rawMessages[i];
|
|
1860
|
+
const messageRefId = typeof refs[i]?.id === "string"
|
|
1861
|
+
? refs[i].id
|
|
1862
|
+
: undefined;
|
|
894
1863
|
if (msg?.role === "assistant" || msg?.role === "user") {
|
|
895
1864
|
const content = stringifyContent(msg.content).trim();
|
|
896
1865
|
if (!content)
|
|
897
1866
|
continue;
|
|
898
|
-
|
|
1867
|
+
const nextMessage = {
|
|
899
1868
|
role: msg.role,
|
|
900
1869
|
content,
|
|
901
1870
|
name: msg.name,
|
|
902
1871
|
tool_calls: msg.tool_calls,
|
|
903
|
-
}
|
|
1872
|
+
};
|
|
1873
|
+
const conversationIndex = conversation.length;
|
|
1874
|
+
conversation.push(nextMessage);
|
|
1875
|
+
if (nextMessage.role === "assistant") {
|
|
1876
|
+
assistantTurns.push({
|
|
1877
|
+
conversationIndex,
|
|
1878
|
+
message: nextMessage,
|
|
1879
|
+
messageRefId,
|
|
1880
|
+
});
|
|
1881
|
+
}
|
|
904
1882
|
continue;
|
|
905
1883
|
}
|
|
906
1884
|
const respondSummary = summarizeRespondCall(msg);
|
|
907
1885
|
if (respondSummary) {
|
|
908
|
-
|
|
1886
|
+
const nextMessage = {
|
|
909
1887
|
role: "assistant",
|
|
910
1888
|
content: respondSummary.displayText,
|
|
911
1889
|
name: GAMBIT_TOOL_RESPOND,
|
|
1890
|
+
};
|
|
1891
|
+
const conversationIndex = conversation.length;
|
|
1892
|
+
conversation.push(nextMessage);
|
|
1893
|
+
assistantTurns.push({
|
|
1894
|
+
conversationIndex,
|
|
1895
|
+
message: nextMessage,
|
|
1896
|
+
messageRefId,
|
|
1897
|
+
});
|
|
1898
|
+
}
|
|
1899
|
+
}
|
|
1900
|
+
return { messages: conversation, assistantTurns };
|
|
1901
|
+
};
|
|
1902
|
+
const buildScenarioConversationArtifactsFromRun = (run) => {
|
|
1903
|
+
const conversation = [];
|
|
1904
|
+
const assistantTurns = [];
|
|
1905
|
+
const runMessages = Array.isArray(run.messages) ? run.messages : [];
|
|
1906
|
+
for (const msg of runMessages) {
|
|
1907
|
+
if (msg?.role !== "assistant" && msg?.role !== "user")
|
|
1908
|
+
continue;
|
|
1909
|
+
const content = typeof msg.content === "string" ? msg.content.trim() : "";
|
|
1910
|
+
if (!content)
|
|
1911
|
+
continue;
|
|
1912
|
+
const nextMessage = {
|
|
1913
|
+
role: msg.role,
|
|
1914
|
+
content,
|
|
1915
|
+
};
|
|
1916
|
+
const conversationIndex = conversation.length;
|
|
1917
|
+
conversation.push(nextMessage);
|
|
1918
|
+
if (nextMessage.role === "assistant") {
|
|
1919
|
+
assistantTurns.push({
|
|
1920
|
+
conversationIndex,
|
|
1921
|
+
message: nextMessage,
|
|
1922
|
+
messageRefId: msg.messageRefId,
|
|
1923
|
+
});
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1926
|
+
return { messages: conversation, assistantTurns };
|
|
1927
|
+
};
|
|
1928
|
+
const normalizePersistedTestRunStatus = (value, workspaceId) => {
|
|
1929
|
+
if (!value || typeof value !== "object")
|
|
1930
|
+
return null;
|
|
1931
|
+
const raw = value;
|
|
1932
|
+
const id = typeof raw.id === "string" ? raw.id : "";
|
|
1933
|
+
if (!id)
|
|
1934
|
+
return null;
|
|
1935
|
+
const rawStatus = raw.status;
|
|
1936
|
+
const status = rawStatus === "running" || rawStatus === "completed" ||
|
|
1937
|
+
rawStatus === "error" || rawStatus === "canceled"
|
|
1938
|
+
? rawStatus
|
|
1939
|
+
: "idle";
|
|
1940
|
+
return {
|
|
1941
|
+
id,
|
|
1942
|
+
status,
|
|
1943
|
+
workspaceId: typeof raw.workspaceId === "string"
|
|
1944
|
+
? raw.workspaceId
|
|
1945
|
+
: workspaceId,
|
|
1946
|
+
sessionId: typeof raw.sessionId === "string"
|
|
1947
|
+
? raw.sessionId
|
|
1948
|
+
: workspaceId,
|
|
1949
|
+
error: typeof raw.error === "string" ? raw.error : undefined,
|
|
1950
|
+
startedAt: typeof raw.startedAt === "string" ? raw.startedAt : undefined,
|
|
1951
|
+
finishedAt: typeof raw.finishedAt === "string"
|
|
1952
|
+
? raw.finishedAt
|
|
1953
|
+
: undefined,
|
|
1954
|
+
maxTurns: typeof raw.maxTurns === "number" && Number.isFinite(raw.maxTurns)
|
|
1955
|
+
? raw.maxTurns
|
|
1956
|
+
: undefined,
|
|
1957
|
+
messages: Array.isArray(raw.messages)
|
|
1958
|
+
? raw.messages
|
|
1959
|
+
: [],
|
|
1960
|
+
traces: Array.isArray(raw.traces) ? raw.traces : [],
|
|
1961
|
+
toolInserts: Array.isArray(raw.toolInserts)
|
|
1962
|
+
? raw.toolInserts
|
|
1963
|
+
: [],
|
|
1964
|
+
};
|
|
1965
|
+
};
|
|
1966
|
+
const readPersistedTestRunStatusById = (sessionState, workspaceId, requestedRunId) => {
|
|
1967
|
+
const eventsPath = typeof sessionState.meta?.sessionEventsPath === "string"
|
|
1968
|
+
? sessionState.meta.sessionEventsPath
|
|
1969
|
+
: undefined;
|
|
1970
|
+
if (!eventsPath)
|
|
1971
|
+
return null;
|
|
1972
|
+
try {
|
|
1973
|
+
const text = dntShim.Deno.readTextFileSync(eventsPath);
|
|
1974
|
+
let latest = null;
|
|
1975
|
+
for (const line of text.split("\n")) {
|
|
1976
|
+
if (!line.trim())
|
|
1977
|
+
continue;
|
|
1978
|
+
let parsed = null;
|
|
1979
|
+
try {
|
|
1980
|
+
parsed = JSON.parse(line);
|
|
1981
|
+
}
|
|
1982
|
+
catch {
|
|
1983
|
+
continue;
|
|
1984
|
+
}
|
|
1985
|
+
if (!parsed || typeof parsed !== "object")
|
|
1986
|
+
continue;
|
|
1987
|
+
const payload = extractPersistedWorkspacePayload(parsed);
|
|
1988
|
+
if (payload.type !== "testBotStatus" &&
|
|
1989
|
+
payload.type !== "gambit.test.status")
|
|
1990
|
+
continue;
|
|
1991
|
+
const normalized = normalizePersistedTestRunStatus(payload.run, workspaceId);
|
|
1992
|
+
if (!normalized || normalized.id !== requestedRunId)
|
|
1993
|
+
continue;
|
|
1994
|
+
latest = normalized;
|
|
1995
|
+
}
|
|
1996
|
+
return latest;
|
|
1997
|
+
}
|
|
1998
|
+
catch {
|
|
1999
|
+
return null;
|
|
2000
|
+
}
|
|
2001
|
+
};
|
|
2002
|
+
const resolveMessageByRef = (state, messageRefId) => {
|
|
2003
|
+
const refs = Array.isArray(state.messageRefs) ? state.messageRefs : [];
|
|
2004
|
+
const messages = Array.isArray(state.messages) ? state.messages : [];
|
|
2005
|
+
const idx = refs.findIndex((ref) => ref?.id === messageRefId);
|
|
2006
|
+
if (idx < 0)
|
|
2007
|
+
return {};
|
|
2008
|
+
return {
|
|
2009
|
+
message: messages[idx],
|
|
2010
|
+
ref: refs[idx],
|
|
2011
|
+
};
|
|
2012
|
+
};
|
|
2013
|
+
const isFeedbackEligibleMessageRef = (state, messageRefId) => {
|
|
2014
|
+
const { message, ref } = resolveMessageByRef(state, messageRefId);
|
|
2015
|
+
if (!message)
|
|
2016
|
+
return false;
|
|
2017
|
+
if (message.role === "assistant")
|
|
2018
|
+
return true;
|
|
2019
|
+
if (message.role === "user" && ref?.source === "scenario")
|
|
2020
|
+
return true;
|
|
2021
|
+
return summarizeRespondCall(message) !== null;
|
|
2022
|
+
};
|
|
2023
|
+
const isFeedbackEligiblePersistedTestRunMessageRef = (state, runId, messageRefId) => {
|
|
2024
|
+
const eventsPath = typeof state.meta?.sessionEventsPath === "string"
|
|
2025
|
+
? state.meta.sessionEventsPath
|
|
2026
|
+
: undefined;
|
|
2027
|
+
if (!eventsPath)
|
|
2028
|
+
return false;
|
|
2029
|
+
try {
|
|
2030
|
+
const text = dntShim.Deno.readTextFileSync(eventsPath);
|
|
2031
|
+
for (const line of text.split("\n")) {
|
|
2032
|
+
if (!line.trim())
|
|
2033
|
+
continue;
|
|
2034
|
+
let parsed = null;
|
|
2035
|
+
try {
|
|
2036
|
+
parsed = JSON.parse(line);
|
|
2037
|
+
}
|
|
2038
|
+
catch {
|
|
2039
|
+
continue;
|
|
2040
|
+
}
|
|
2041
|
+
if (!parsed || typeof parsed !== "object")
|
|
2042
|
+
continue;
|
|
2043
|
+
const payload = extractPersistedWorkspacePayload(parsed);
|
|
2044
|
+
if (payload.type !== "testBotStatus" &&
|
|
2045
|
+
payload.type !== "gambit.test.status") {
|
|
2046
|
+
continue;
|
|
2047
|
+
}
|
|
2048
|
+
const run = payload.run;
|
|
2049
|
+
if (!run || typeof run !== "object")
|
|
2050
|
+
continue;
|
|
2051
|
+
const runRecord = run;
|
|
2052
|
+
if (typeof runRecord.id !== "string" || runRecord.id !== runId) {
|
|
2053
|
+
continue;
|
|
2054
|
+
}
|
|
2055
|
+
if (!Array.isArray(runRecord.messages))
|
|
2056
|
+
continue;
|
|
2057
|
+
const found = runRecord.messages.some((entry) => {
|
|
2058
|
+
if (!entry || typeof entry !== "object")
|
|
2059
|
+
return false;
|
|
2060
|
+
const message = entry;
|
|
2061
|
+
if (message.messageRefId !== messageRefId)
|
|
2062
|
+
return false;
|
|
2063
|
+
if (message.role === "assistant")
|
|
2064
|
+
return true;
|
|
2065
|
+
return message.role === "user" &&
|
|
2066
|
+
message.messageSource === "scenario";
|
|
912
2067
|
});
|
|
2068
|
+
if (found)
|
|
2069
|
+
return true;
|
|
913
2070
|
}
|
|
914
2071
|
}
|
|
915
|
-
|
|
2072
|
+
catch {
|
|
2073
|
+
return false;
|
|
2074
|
+
}
|
|
2075
|
+
return false;
|
|
916
2076
|
};
|
|
917
2077
|
const deriveToolInsertsFromTraces = (state, messageCount) => {
|
|
918
2078
|
const traces = Array.isArray(state.traces) ? state.traces : [];
|
|
@@ -949,27 +2109,84 @@ function startWebSocketSimulator(opts) {
|
|
|
949
2109
|
}
|
|
950
2110
|
return inserts;
|
|
951
2111
|
};
|
|
2112
|
+
const applyUserMessageRefSource = (previousState, nextState, source) => {
|
|
2113
|
+
if (!Array.isArray(nextState.messages) ||
|
|
2114
|
+
!Array.isArray(nextState.messageRefs)) {
|
|
2115
|
+
return nextState;
|
|
2116
|
+
}
|
|
2117
|
+
const startIndex = Math.max(0, previousState?.messages?.length ?? 0);
|
|
2118
|
+
const nextRefs = [...nextState.messageRefs];
|
|
2119
|
+
let changed = false;
|
|
2120
|
+
for (let idx = startIndex; idx < nextState.messages.length; idx++) {
|
|
2121
|
+
const msg = nextState.messages[idx];
|
|
2122
|
+
if (!msg || msg.role !== "user")
|
|
2123
|
+
continue;
|
|
2124
|
+
const ref = nextRefs[idx];
|
|
2125
|
+
if (!ref || typeof ref.id !== "string")
|
|
2126
|
+
continue;
|
|
2127
|
+
if (ref.source === source)
|
|
2128
|
+
continue;
|
|
2129
|
+
nextRefs[idx] = { ...ref, source };
|
|
2130
|
+
changed = true;
|
|
2131
|
+
}
|
|
2132
|
+
if (!changed)
|
|
2133
|
+
return nextState;
|
|
2134
|
+
return { ...nextState, messageRefs: nextRefs };
|
|
2135
|
+
};
|
|
952
2136
|
const syncTestBotRunFromState = (run, state) => {
|
|
953
2137
|
const snapshot = buildTestBotSnapshot(state);
|
|
954
2138
|
run.messages = snapshot.messages;
|
|
955
2139
|
run.toolInserts = snapshot.toolInserts;
|
|
956
|
-
const
|
|
957
|
-
? state.meta.
|
|
958
|
-
:
|
|
959
|
-
|
|
960
|
-
|
|
2140
|
+
const workspaceId = typeof state.meta?.workspaceId === "string"
|
|
2141
|
+
? state.meta.workspaceId
|
|
2142
|
+
: typeof state.meta?.sessionId === "string"
|
|
2143
|
+
? state.meta.sessionId
|
|
2144
|
+
: undefined;
|
|
2145
|
+
if (workspaceId) {
|
|
2146
|
+
run.workspaceId = workspaceId;
|
|
2147
|
+
run.sessionId = workspaceId;
|
|
2148
|
+
}
|
|
961
2149
|
const initFill = state.meta
|
|
962
2150
|
?.testBotInitFill;
|
|
963
2151
|
if (initFill)
|
|
964
2152
|
run.initFill = initFill;
|
|
965
2153
|
run.traces = Array.isArray(state.traces) ? [...state.traces] : undefined;
|
|
966
2154
|
};
|
|
2155
|
+
const syncBuildBotRunFromState = (run, state) => {
|
|
2156
|
+
const snapshot = buildTestBotSnapshot(state);
|
|
2157
|
+
run.messages = snapshot.messages;
|
|
2158
|
+
run.toolInserts = snapshot.toolInserts;
|
|
2159
|
+
run.traces = Array.isArray(state.traces) ? [...state.traces] : undefined;
|
|
2160
|
+
};
|
|
2161
|
+
const buildRunFromProjection = (workspaceId) => {
|
|
2162
|
+
const projection = readBuildState(workspaceId);
|
|
2163
|
+
const run = projection?.run;
|
|
2164
|
+
if (!run) {
|
|
2165
|
+
return {
|
|
2166
|
+
id: workspaceId,
|
|
2167
|
+
status: "idle",
|
|
2168
|
+
messages: [],
|
|
2169
|
+
traces: [],
|
|
2170
|
+
toolInserts: [],
|
|
2171
|
+
};
|
|
2172
|
+
}
|
|
2173
|
+
return {
|
|
2174
|
+
id: run.id || workspaceId,
|
|
2175
|
+
status: run.status,
|
|
2176
|
+
error: run.error,
|
|
2177
|
+
startedAt: run.startedAt,
|
|
2178
|
+
finishedAt: run.finishedAt,
|
|
2179
|
+
messages: Array.isArray(run.messages) ? run.messages : [],
|
|
2180
|
+
traces: Array.isArray(run.traces) ? run.traces : [],
|
|
2181
|
+
toolInserts: Array.isArray(run.toolInserts) ? run.toolInserts : [],
|
|
2182
|
+
};
|
|
2183
|
+
};
|
|
967
2184
|
const startTestBotRun = (runOpts = {}) => {
|
|
968
2185
|
const botDeckPath = typeof runOpts.botDeckPath === "string"
|
|
969
2186
|
? runOpts.botDeckPath
|
|
970
2187
|
: undefined;
|
|
971
2188
|
if (!botDeckPath) {
|
|
972
|
-
throw new Error("Missing
|
|
2189
|
+
throw new Error("Missing scenario deck path");
|
|
973
2190
|
}
|
|
974
2191
|
const defaultMaxTurns = 12;
|
|
975
2192
|
const maxTurns = Math.round((0, test_bot_js_1.sanitizeNumber)(runOpts.maxTurnsOverride ?? defaultMaxTurns, defaultMaxTurns, { min: 1, max: 200 }));
|
|
@@ -981,7 +2198,9 @@ function startWebSocketSimulator(opts) {
|
|
|
981
2198
|
: "";
|
|
982
2199
|
const botConfigPath = botDeckPath;
|
|
983
2200
|
const testBotName = path.basename(botConfigPath).replace(/\.deck\.(md|ts)$/i, "");
|
|
984
|
-
const
|
|
2201
|
+
const selectedScenarioDeckId = runOpts.botDeckId ?? testBotName;
|
|
2202
|
+
const selectedScenarioDeckLabel = runOpts.botDeckLabel ?? testBotName;
|
|
2203
|
+
const runId = (0, server_helpers_js_1.randomId)("testbot");
|
|
985
2204
|
const startedAt = new Date().toISOString();
|
|
986
2205
|
const controller = new AbortController();
|
|
987
2206
|
const entry = {
|
|
@@ -999,33 +2218,44 @@ function startWebSocketSimulator(opts) {
|
|
|
999
2218
|
};
|
|
1000
2219
|
testBotRuns.set(runId, entry);
|
|
1001
2220
|
const run = entry.run;
|
|
2221
|
+
const emitTestBot = (payload) => broadcastTestBot(payload, run.workspaceId ?? runOpts.workspaceId);
|
|
1002
2222
|
if (runOpts.initFill)
|
|
1003
2223
|
run.initFill = runOpts.initFill;
|
|
1004
2224
|
let savedState = undefined;
|
|
2225
|
+
const baseMeta = runOpts.baseMeta ?? {};
|
|
2226
|
+
const workspaceMeta = runOpts.workspaceRecord
|
|
2227
|
+
? buildWorkspaceMeta(runOpts.workspaceRecord, baseMeta)
|
|
2228
|
+
: baseMeta;
|
|
1005
2229
|
let lastCount = 0;
|
|
1006
2230
|
const capturedTraces = [];
|
|
1007
2231
|
if (runOpts.initFillTrace) {
|
|
1008
|
-
const actionCallId = randomId("initfill");
|
|
2232
|
+
const actionCallId = (0, server_helpers_js_1.randomId)("initfill");
|
|
1009
2233
|
capturedTraces.push({
|
|
1010
2234
|
type: "tool.call",
|
|
1011
2235
|
runId,
|
|
1012
2236
|
actionCallId,
|
|
1013
2237
|
name: "gambit_test_bot_init_fill",
|
|
1014
2238
|
args: runOpts.initFillTrace.args,
|
|
2239
|
+
toolKind: "internal",
|
|
1015
2240
|
}, {
|
|
1016
2241
|
type: "tool.result",
|
|
1017
2242
|
runId,
|
|
1018
2243
|
actionCallId,
|
|
1019
2244
|
name: "gambit_test_bot_init_fill",
|
|
1020
2245
|
result: runOpts.initFillTrace.result,
|
|
2246
|
+
toolKind: "internal",
|
|
1021
2247
|
});
|
|
1022
2248
|
}
|
|
1023
|
-
const
|
|
1024
|
-
const
|
|
1025
|
-
? state.meta.
|
|
1026
|
-
:
|
|
1027
|
-
|
|
1028
|
-
|
|
2249
|
+
const setWorkspaceId = (state) => {
|
|
2250
|
+
const workspaceId = typeof state?.meta?.workspaceId === "string"
|
|
2251
|
+
? state.meta.workspaceId
|
|
2252
|
+
: typeof state?.meta?.sessionId === "string"
|
|
2253
|
+
? state.meta.sessionId
|
|
2254
|
+
: undefined;
|
|
2255
|
+
if (workspaceId) {
|
|
2256
|
+
run.workspaceId = workspaceId;
|
|
2257
|
+
run.sessionId = workspaceId;
|
|
2258
|
+
}
|
|
1029
2259
|
};
|
|
1030
2260
|
const appendFromState = (state) => {
|
|
1031
2261
|
const snapshot = buildTestBotSnapshot(state);
|
|
@@ -1036,16 +2266,39 @@ function startWebSocketSimulator(opts) {
|
|
|
1036
2266
|
run.messages = snapshot.messages;
|
|
1037
2267
|
run.toolInserts = snapshot.toolInserts;
|
|
1038
2268
|
lastCount = rawLength;
|
|
1039
|
-
|
|
2269
|
+
setWorkspaceId(state);
|
|
1040
2270
|
run.traces = Array.isArray(state.traces) ? [...state.traces] : undefined;
|
|
1041
2271
|
if (shouldBroadcast) {
|
|
1042
|
-
|
|
2272
|
+
emitTestBot({ type: "testBotStatus", run });
|
|
2273
|
+
}
|
|
2274
|
+
};
|
|
2275
|
+
const pendingTraceEvents = [];
|
|
2276
|
+
const flushPendingTraceEvents = (state) => {
|
|
2277
|
+
if (!pendingTraceEvents.length)
|
|
2278
|
+
return;
|
|
2279
|
+
for (const pending of pendingTraceEvents) {
|
|
2280
|
+
appendSessionEvent(state, {
|
|
2281
|
+
...pending,
|
|
2282
|
+
kind: "trace",
|
|
2283
|
+
category: traceCategory(pending.type),
|
|
2284
|
+
});
|
|
1043
2285
|
}
|
|
2286
|
+
pendingTraceEvents.length = 0;
|
|
1044
2287
|
};
|
|
1045
2288
|
const tracer = (event) => {
|
|
1046
2289
|
const stamped = event.ts ? event : { ...event, ts: Date.now() };
|
|
1047
2290
|
capturedTraces.push(stamped);
|
|
1048
2291
|
consoleTracer?.(stamped);
|
|
2292
|
+
if (savedState?.meta?.sessionId) {
|
|
2293
|
+
appendSessionEvent(savedState, {
|
|
2294
|
+
...stamped,
|
|
2295
|
+
kind: "trace",
|
|
2296
|
+
category: traceCategory(stamped.type),
|
|
2297
|
+
});
|
|
2298
|
+
}
|
|
2299
|
+
else {
|
|
2300
|
+
pendingTraceEvents.push(stamped);
|
|
2301
|
+
}
|
|
1049
2302
|
};
|
|
1050
2303
|
let deckBotState = undefined;
|
|
1051
2304
|
let sessionEnded = false;
|
|
@@ -1059,8 +2312,11 @@ function startWebSocketSimulator(opts) {
|
|
|
1059
2312
|
return undefined;
|
|
1060
2313
|
};
|
|
1061
2314
|
const generateDeckBotUserMessage = async (history, streamOpts) => {
|
|
1062
|
-
const assistantMessage = getLastAssistantMessage(history);
|
|
1063
|
-
|
|
2315
|
+
const assistantMessage = getLastAssistantMessage(history)?.trim() || "";
|
|
2316
|
+
const seedPrompt = !assistantMessage && streamOpts?.allowEmptyAssistant
|
|
2317
|
+
? DEFAULT_TEST_BOT_SEED_PROMPT
|
|
2318
|
+
: undefined;
|
|
2319
|
+
if (!assistantMessage && !seedPrompt)
|
|
1064
2320
|
return "";
|
|
1065
2321
|
const result = await runDeckWithFallback({
|
|
1066
2322
|
path: botDeckPath,
|
|
@@ -1069,13 +2325,15 @@ function startWebSocketSimulator(opts) {
|
|
|
1069
2325
|
modelProvider: opts.modelProvider,
|
|
1070
2326
|
state: deckBotState,
|
|
1071
2327
|
allowRootStringInput: true,
|
|
1072
|
-
initialUserMessage: assistantMessage,
|
|
2328
|
+
initialUserMessage: assistantMessage || seedPrompt,
|
|
1073
2329
|
onStateUpdate: (state) => {
|
|
1074
2330
|
deckBotState = state;
|
|
1075
2331
|
},
|
|
1076
2332
|
stream: Boolean(streamOpts?.onStreamText),
|
|
1077
2333
|
onStreamText: streamOpts?.onStreamText,
|
|
1078
2334
|
responsesMode: opts.responsesMode,
|
|
2335
|
+
workerSandbox: opts.workerSandbox,
|
|
2336
|
+
signal: controller.signal,
|
|
1079
2337
|
});
|
|
1080
2338
|
if ((0, gambit_core_1.isGambitEndSignal)(result)) {
|
|
1081
2339
|
sessionEnded = true;
|
|
@@ -1086,7 +2344,10 @@ function startWebSocketSimulator(opts) {
|
|
|
1086
2344
|
};
|
|
1087
2345
|
const loop = async () => {
|
|
1088
2346
|
try {
|
|
1089
|
-
|
|
2347
|
+
const effectiveStartMode = rootStartMode ?? "assistant";
|
|
2348
|
+
const shouldRunInitial = effectiveStartMode !== "user" ||
|
|
2349
|
+
Boolean(initialUserMessage);
|
|
2350
|
+
if (!controller.signal.aborted && shouldRunInitial) {
|
|
1090
2351
|
const initialResult = await (0, gambit_core_1.runDeck)({
|
|
1091
2352
|
path: resolvedDeckPath,
|
|
1092
2353
|
input: deckInput,
|
|
@@ -1100,22 +2361,33 @@ function startWebSocketSimulator(opts) {
|
|
|
1100
2361
|
allowRootStringInput: true,
|
|
1101
2362
|
initialUserMessage: initialUserMessage || undefined,
|
|
1102
2363
|
responsesMode: opts.responsesMode,
|
|
2364
|
+
workerSandbox: opts.workerSandbox,
|
|
2365
|
+
signal: controller.signal,
|
|
1103
2366
|
onStateUpdate: (state) => {
|
|
2367
|
+
const nextStateWithSource = applyUserMessageRefSource(savedState, state, "scenario");
|
|
1104
2368
|
const nextMeta = {
|
|
1105
|
-
...
|
|
1106
|
-
...(
|
|
2369
|
+
...workspaceMeta,
|
|
2370
|
+
...(nextStateWithSource.meta ?? {}),
|
|
1107
2371
|
testBot: true,
|
|
1108
2372
|
testBotRunId: runId,
|
|
1109
2373
|
testBotConfigPath: botConfigPath,
|
|
1110
2374
|
testBotName,
|
|
2375
|
+
scenarioRunId: runId,
|
|
2376
|
+
selectedScenarioDeckId,
|
|
2377
|
+
selectedScenarioDeckLabel,
|
|
2378
|
+
scenarioConfigPath: botConfigPath,
|
|
1111
2379
|
...(run.initFill ? { testBotInitFill: run.initFill } : {}),
|
|
2380
|
+
...(runOpts.workspaceId
|
|
2381
|
+
? { workspaceId: runOpts.workspaceId }
|
|
2382
|
+
: {}),
|
|
1112
2383
|
};
|
|
1113
2384
|
const enriched = persistSessionState({
|
|
1114
|
-
...
|
|
2385
|
+
...nextStateWithSource,
|
|
1115
2386
|
meta: nextMeta,
|
|
1116
2387
|
traces: capturedTraces,
|
|
1117
2388
|
});
|
|
1118
2389
|
savedState = enriched;
|
|
2390
|
+
flushPendingTraceEvents(enriched);
|
|
1119
2391
|
appendFromState(enriched);
|
|
1120
2392
|
},
|
|
1121
2393
|
});
|
|
@@ -1130,7 +2402,7 @@ function startWebSocketSimulator(opts) {
|
|
|
1130
2402
|
break;
|
|
1131
2403
|
const history = savedState?.messages ?? [];
|
|
1132
2404
|
const userMessage = await generateDeckBotUserMessage(history, {
|
|
1133
|
-
onStreamText: (chunk) =>
|
|
2405
|
+
onStreamText: (chunk) => emitTestBot({
|
|
1134
2406
|
type: "testBotStream",
|
|
1135
2407
|
runId,
|
|
1136
2408
|
role: "user",
|
|
@@ -1138,8 +2410,10 @@ function startWebSocketSimulator(opts) {
|
|
|
1138
2410
|
turn,
|
|
1139
2411
|
ts: Date.now(),
|
|
1140
2412
|
}),
|
|
2413
|
+
allowEmptyAssistant: effectiveStartMode === "user" &&
|
|
2414
|
+
!getLastAssistantMessage(history),
|
|
1141
2415
|
});
|
|
1142
|
-
|
|
2416
|
+
emitTestBot({
|
|
1143
2417
|
type: "testBotStreamEnd",
|
|
1144
2418
|
runId,
|
|
1145
2419
|
role: "user",
|
|
@@ -1161,25 +2435,36 @@ function startWebSocketSimulator(opts) {
|
|
|
1161
2435
|
allowRootStringInput: true,
|
|
1162
2436
|
initialUserMessage: userMessage,
|
|
1163
2437
|
responsesMode: opts.responsesMode,
|
|
2438
|
+
workerSandbox: opts.workerSandbox,
|
|
2439
|
+
signal: controller.signal,
|
|
1164
2440
|
onStateUpdate: (state) => {
|
|
2441
|
+
const nextStateWithSource = applyUserMessageRefSource(savedState, state, "scenario");
|
|
1165
2442
|
const nextMeta = {
|
|
1166
|
-
...
|
|
1167
|
-
...(
|
|
2443
|
+
...workspaceMeta,
|
|
2444
|
+
...(nextStateWithSource.meta ?? {}),
|
|
1168
2445
|
testBot: true,
|
|
1169
2446
|
testBotRunId: runId,
|
|
1170
2447
|
testBotConfigPath: botConfigPath,
|
|
1171
2448
|
testBotName,
|
|
2449
|
+
scenarioRunId: runId,
|
|
2450
|
+
selectedScenarioDeckId,
|
|
2451
|
+
selectedScenarioDeckLabel,
|
|
2452
|
+
scenarioConfigPath: botConfigPath,
|
|
1172
2453
|
...(run.initFill ? { testBotInitFill: run.initFill } : {}),
|
|
2454
|
+
...(runOpts.workspaceId
|
|
2455
|
+
? { workspaceId: runOpts.workspaceId }
|
|
2456
|
+
: {}),
|
|
1173
2457
|
};
|
|
1174
2458
|
const enriched = persistSessionState({
|
|
1175
|
-
...
|
|
2459
|
+
...nextStateWithSource,
|
|
1176
2460
|
meta: nextMeta,
|
|
1177
2461
|
traces: capturedTraces,
|
|
1178
2462
|
});
|
|
1179
2463
|
savedState = enriched;
|
|
2464
|
+
flushPendingTraceEvents(enriched);
|
|
1180
2465
|
appendFromState(enriched);
|
|
1181
2466
|
},
|
|
1182
|
-
onStreamText: (chunk) =>
|
|
2467
|
+
onStreamText: (chunk) => emitTestBot({
|
|
1183
2468
|
type: "testBotStream",
|
|
1184
2469
|
runId,
|
|
1185
2470
|
role: "assistant",
|
|
@@ -1192,7 +2477,7 @@ function startWebSocketSimulator(opts) {
|
|
|
1192
2477
|
sessionEnded = true;
|
|
1193
2478
|
break;
|
|
1194
2479
|
}
|
|
1195
|
-
|
|
2480
|
+
emitTestBot({
|
|
1196
2481
|
type: "testBotStreamEnd",
|
|
1197
2482
|
runId,
|
|
1198
2483
|
role: "assistant",
|
|
@@ -1201,12 +2486,18 @@ function startWebSocketSimulator(opts) {
|
|
|
1201
2486
|
});
|
|
1202
2487
|
}
|
|
1203
2488
|
run.status = controller.signal.aborted ? "canceled" : "completed";
|
|
1204
|
-
|
|
2489
|
+
emitTestBot({ type: "testBotStatus", run });
|
|
1205
2490
|
}
|
|
1206
2491
|
catch (err) {
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
2492
|
+
if (controller.signal.aborted || (0, gambit_core_1.isRunCanceledError)(err)) {
|
|
2493
|
+
run.status = "canceled";
|
|
2494
|
+
run.error = undefined;
|
|
2495
|
+
}
|
|
2496
|
+
else {
|
|
2497
|
+
run.status = "error";
|
|
2498
|
+
run.error = err instanceof Error ? err.message : String(err);
|
|
2499
|
+
}
|
|
2500
|
+
emitTestBot({ type: "testBotStatus", run });
|
|
1210
2501
|
}
|
|
1211
2502
|
finally {
|
|
1212
2503
|
if (savedState?.messages) {
|
|
@@ -1214,24 +2505,26 @@ function startWebSocketSimulator(opts) {
|
|
|
1214
2505
|
run.messages = snapshot.messages;
|
|
1215
2506
|
run.toolInserts = snapshot.toolInserts;
|
|
1216
2507
|
}
|
|
1217
|
-
|
|
2508
|
+
setWorkspaceId(savedState);
|
|
1218
2509
|
run.traces = Array.isArray(savedState?.traces)
|
|
1219
2510
|
? [...(savedState?.traces ?? [])]
|
|
1220
2511
|
: undefined;
|
|
1221
2512
|
run.finishedAt = new Date().toISOString();
|
|
1222
2513
|
entry.abort = null;
|
|
1223
2514
|
entry.promise = null;
|
|
1224
|
-
|
|
2515
|
+
emitTestBot({ type: "testBotStatus", run });
|
|
1225
2516
|
}
|
|
1226
2517
|
};
|
|
1227
2518
|
entry.promise = loop();
|
|
1228
|
-
|
|
2519
|
+
emitTestBot({ type: "testBotStatus", run });
|
|
1229
2520
|
return run;
|
|
1230
2521
|
};
|
|
1231
2522
|
const persistFailedInitFill = (args) => {
|
|
1232
|
-
const failedRunId = randomId("testbot");
|
|
2523
|
+
const failedRunId = (0, server_helpers_js_1.randomId)("testbot");
|
|
1233
2524
|
const testBotName = path.basename(args.botDeckPath).replace(/\.deck\.(md|ts)$/i, "");
|
|
1234
|
-
const
|
|
2525
|
+
const selectedScenarioDeckId = args.botDeckId ?? testBotName;
|
|
2526
|
+
const selectedScenarioDeckLabel = args.botDeckLabel ?? testBotName;
|
|
2527
|
+
const actionCallId = (0, server_helpers_js_1.randomId)("initfill");
|
|
1235
2528
|
const traces = [
|
|
1236
2529
|
{
|
|
1237
2530
|
type: "tool.call",
|
|
@@ -1239,6 +2532,7 @@ function startWebSocketSimulator(opts) {
|
|
|
1239
2532
|
actionCallId,
|
|
1240
2533
|
name: "gambit_test_bot_init_fill",
|
|
1241
2534
|
args: { missing: args.initFill?.requested ?? [] },
|
|
2535
|
+
toolKind: "internal",
|
|
1242
2536
|
},
|
|
1243
2537
|
{
|
|
1244
2538
|
type: "tool.result",
|
|
@@ -1249,6 +2543,7 @@ function startWebSocketSimulator(opts) {
|
|
|
1249
2543
|
error: args.error,
|
|
1250
2544
|
provided: args.initFill?.provided,
|
|
1251
2545
|
},
|
|
2546
|
+
toolKind: "internal",
|
|
1252
2547
|
},
|
|
1253
2548
|
];
|
|
1254
2549
|
const failedState = persistSessionState({
|
|
@@ -1260,25 +2555,52 @@ function startWebSocketSimulator(opts) {
|
|
|
1260
2555
|
testBotRunId: failedRunId,
|
|
1261
2556
|
testBotConfigPath: args.botDeckPath,
|
|
1262
2557
|
testBotName,
|
|
2558
|
+
scenarioRunId: failedRunId,
|
|
2559
|
+
selectedScenarioDeckId,
|
|
2560
|
+
selectedScenarioDeckLabel,
|
|
2561
|
+
scenarioConfigPath: args.botDeckPath,
|
|
1263
2562
|
testBotInitFill: args.initFill,
|
|
1264
2563
|
testBotInitFillError: args.error,
|
|
1265
2564
|
},
|
|
1266
2565
|
});
|
|
1267
|
-
const
|
|
1268
|
-
? failedState.meta.
|
|
2566
|
+
const workspaceId = typeof failedState.meta?.workspaceId === "string"
|
|
2567
|
+
? failedState.meta.workspaceId
|
|
1269
2568
|
: undefined;
|
|
1270
|
-
const
|
|
2569
|
+
const workspacePath = typeof failedState.meta?.sessionStatePath === "string"
|
|
1271
2570
|
? failedState.meta.sessionStatePath
|
|
1272
2571
|
: undefined;
|
|
1273
|
-
if (
|
|
1274
|
-
logger.warn(`[sim] init fill failed;
|
|
2572
|
+
if (workspacePath) {
|
|
2573
|
+
logger.warn(`[sim] init fill failed; workspace state saved to ${workspacePath}`);
|
|
2574
|
+
}
|
|
2575
|
+
return { workspaceId, workspacePath };
|
|
2576
|
+
};
|
|
2577
|
+
const resolvePreferredDeckPath = async (candidate) => {
|
|
2578
|
+
if (path.basename(candidate) === "PROMPT.md")
|
|
2579
|
+
return candidate;
|
|
2580
|
+
const promptPath = path.join(path.dirname(candidate), "PROMPT.md");
|
|
2581
|
+
try {
|
|
2582
|
+
const stat = await dntShim.Deno.stat(promptPath);
|
|
2583
|
+
if (stat.isFile)
|
|
2584
|
+
return promptPath;
|
|
1275
2585
|
}
|
|
1276
|
-
|
|
2586
|
+
catch {
|
|
2587
|
+
// ignore missing PROMPT.md
|
|
2588
|
+
}
|
|
2589
|
+
return candidate;
|
|
1277
2590
|
};
|
|
1278
|
-
const
|
|
2591
|
+
const createDeckLoadPromise = () => resolvePreferredDeckPath(resolvedDeckPath)
|
|
2592
|
+
.then((preferredPath) => {
|
|
2593
|
+
resolvedDeckPath = preferredPath;
|
|
2594
|
+
return (0, gambit_core_2.loadDeck)(preferredPath);
|
|
2595
|
+
})
|
|
1279
2596
|
.then((deck) => {
|
|
1280
2597
|
resolvedDeckPath = deck.path;
|
|
2598
|
+
buildBotRootCache.clear();
|
|
1281
2599
|
deckSlug = deckSlugFromPath(resolvedDeckPath);
|
|
2600
|
+
rootStartMode = deck.startMode === "assistant" ||
|
|
2601
|
+
deck.startMode === "user"
|
|
2602
|
+
? deck.startMode
|
|
2603
|
+
: undefined;
|
|
1282
2604
|
deckLabel = typeof deck.label === "string"
|
|
1283
2605
|
? deck.label
|
|
1284
2606
|
: toDeckLabel(deck.path);
|
|
@@ -1300,7 +2622,8 @@ function startWebSocketSimulator(opts) {
|
|
|
1300
2622
|
});
|
|
1301
2623
|
updateTestDeckRegistry(availableTestDecks);
|
|
1302
2624
|
availableGraderDecks = (deck.graderDecks ?? []).map((graderDeck, index) => {
|
|
1303
|
-
const label = graderDeck.label &&
|
|
2625
|
+
const label = graderDeck.label &&
|
|
2626
|
+
typeof graderDeck.label === "string"
|
|
1304
2627
|
? graderDeck.label
|
|
1305
2628
|
: toDeckLabel(graderDeck.path);
|
|
1306
2629
|
const id = graderDeck.id && typeof graderDeck.id === "string"
|
|
@@ -1327,21 +2650,30 @@ function startWebSocketSimulator(opts) {
|
|
|
1327
2650
|
updateGraderDeckRegistry(availableGraderDecks);
|
|
1328
2651
|
return null;
|
|
1329
2652
|
});
|
|
1330
|
-
const
|
|
2653
|
+
const createSchemaPromise = (loadPromise) => loadPromise
|
|
1331
2654
|
.then((deck) => {
|
|
1332
|
-
|
|
1333
|
-
error: "Deck failed to load"
|
|
1334
|
-
}
|
|
2655
|
+
if (!deck) {
|
|
2656
|
+
return { error: "Deck failed to load" };
|
|
2657
|
+
}
|
|
2658
|
+
const desc = describeZodSchema(deck.inputSchema);
|
|
2659
|
+
const tools = mapDeckTools(deck.actionDecks);
|
|
2660
|
+
const next = tools ? { ...desc, tools } : desc;
|
|
1335
2661
|
if (hasInitialContext) {
|
|
1336
|
-
return { ...
|
|
2662
|
+
return { ...next, defaults: initialContext };
|
|
1337
2663
|
}
|
|
1338
|
-
return
|
|
2664
|
+
return next;
|
|
1339
2665
|
})
|
|
1340
2666
|
.catch((err) => {
|
|
1341
2667
|
const message = err instanceof Error ? err.message : String(err);
|
|
1342
2668
|
logger.warn(`[sim] failed to load deck schema: ${message}`);
|
|
1343
2669
|
return { error: message };
|
|
1344
2670
|
});
|
|
2671
|
+
let deckLoadPromise = createDeckLoadPromise();
|
|
2672
|
+
let schemaPromise = createSchemaPromise(deckLoadPromise);
|
|
2673
|
+
const reloadPrimaryDeck = () => {
|
|
2674
|
+
deckLoadPromise = createDeckLoadPromise();
|
|
2675
|
+
schemaPromise = createSchemaPromise(deckLoadPromise);
|
|
2676
|
+
};
|
|
1345
2677
|
const wantsSourceMap = Boolean(opts.sourceMap);
|
|
1346
2678
|
const bundlePlatform = opts.bundlePlatform ?? "deno";
|
|
1347
2679
|
const autoBundle = opts.autoBundle ?? true;
|
|
@@ -1361,6 +2693,7 @@ function startWebSocketSimulator(opts) {
|
|
|
1361
2693
|
logger.log(`[sim] auto-bundle enabled; rebuilding simulator UI (${forceBundle ? "forced" : "stale"})...`);
|
|
1362
2694
|
logger.log(`[sim] bundling simulator UI (${forceBundle ? "forced" : "stale"})...`);
|
|
1363
2695
|
try {
|
|
2696
|
+
const decode = new TextDecoder();
|
|
1364
2697
|
const p = new dntShim.Deno.Command("deno", {
|
|
1365
2698
|
args: [
|
|
1366
2699
|
"bundle",
|
|
@@ -1372,13 +2705,23 @@ function startWebSocketSimulator(opts) {
|
|
|
1372
2705
|
"simulator-ui/src/main.tsx",
|
|
1373
2706
|
],
|
|
1374
2707
|
cwd: path.resolve(moduleDir, ".."),
|
|
1375
|
-
stdout: "
|
|
1376
|
-
stderr: "
|
|
2708
|
+
stdout: "piped",
|
|
2709
|
+
stderr: "piped",
|
|
1377
2710
|
});
|
|
1378
|
-
p.outputSync();
|
|
2711
|
+
const out = p.outputSync();
|
|
2712
|
+
if (!out.success) {
|
|
2713
|
+
const stderr = decode.decode(out.stderr).trim();
|
|
2714
|
+
const stdout = decode.decode(out.stdout).trim();
|
|
2715
|
+
const details = stderr || stdout || `exit ${out.code}`;
|
|
2716
|
+
throw new Error(`simulator UI bundle command failed (exit ${out.code}): ${details}`);
|
|
2717
|
+
}
|
|
1379
2718
|
}
|
|
1380
2719
|
catch (err) {
|
|
1381
|
-
|
|
2720
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2721
|
+
if (forceBundle) {
|
|
2722
|
+
throw new Error(`[sim] auto-bundle failed: ${message}`);
|
|
2723
|
+
}
|
|
2724
|
+
logger.warn(`[sim] auto-bundle failed: ${message}`);
|
|
1382
2725
|
}
|
|
1383
2726
|
}
|
|
1384
2727
|
const server = dntShim.Deno.serve({ port, signal: opts.signal, onListen: () => { } }, async (req) => {
|
|
@@ -1386,6 +2729,286 @@ function startWebSocketSimulator(opts) {
|
|
|
1386
2729
|
if (url.pathname.startsWith("/api/durable-streams/stream/")) {
|
|
1387
2730
|
return (0, durable_streams_js_1.handleDurableStreamRequest)(req);
|
|
1388
2731
|
}
|
|
2732
|
+
if (url.pathname === "/v1/responses") {
|
|
2733
|
+
if (req.method !== "POST") {
|
|
2734
|
+
return new Response("Method not allowed", { status: 405 });
|
|
2735
|
+
}
|
|
2736
|
+
if (!opts.modelProvider.responses) {
|
|
2737
|
+
return jsonResponse({ error: "Configured provider does not support responses." }, 501);
|
|
2738
|
+
}
|
|
2739
|
+
try {
|
|
2740
|
+
const body = parseBodyObject(await req.json());
|
|
2741
|
+
const model = typeof body.model === "string" ? body.model : undefined;
|
|
2742
|
+
if (!model) {
|
|
2743
|
+
throw new Error("model is required");
|
|
2744
|
+
}
|
|
2745
|
+
const input = normalizeInputItems(body.input);
|
|
2746
|
+
const stream = body.stream === true;
|
|
2747
|
+
const instructions = typeof body.instructions === "string"
|
|
2748
|
+
? body.instructions
|
|
2749
|
+
: undefined;
|
|
2750
|
+
const previousResponseId = typeof body.previous_response_id === "string"
|
|
2751
|
+
? body.previous_response_id
|
|
2752
|
+
: undefined;
|
|
2753
|
+
const store = typeof body.store === "boolean"
|
|
2754
|
+
? body.store
|
|
2755
|
+
: undefined;
|
|
2756
|
+
const tools = normalizeTools(body.tools);
|
|
2757
|
+
const toolChoice = normalizeToolChoice(body.tool_choice);
|
|
2758
|
+
const reasoning = (body.reasoning &&
|
|
2759
|
+
typeof body.reasoning === "object" &&
|
|
2760
|
+
!Array.isArray(body.reasoning))
|
|
2761
|
+
? body.reasoning
|
|
2762
|
+
: undefined;
|
|
2763
|
+
const parallelToolCalls = typeof body.parallel_tool_calls === "boolean"
|
|
2764
|
+
? body.parallel_tool_calls
|
|
2765
|
+
: undefined;
|
|
2766
|
+
const maxToolCalls = typeof body.max_tool_calls === "number"
|
|
2767
|
+
? body.max_tool_calls
|
|
2768
|
+
: undefined;
|
|
2769
|
+
const temperature = typeof body.temperature === "number"
|
|
2770
|
+
? body.temperature
|
|
2771
|
+
: undefined;
|
|
2772
|
+
const topP = typeof body.top_p === "number" ? body.top_p : undefined;
|
|
2773
|
+
const frequencyPenalty = typeof body.frequency_penalty === "number"
|
|
2774
|
+
? body.frequency_penalty
|
|
2775
|
+
: undefined;
|
|
2776
|
+
const presencePenalty = typeof body.presence_penalty === "number"
|
|
2777
|
+
? body.presence_penalty
|
|
2778
|
+
: undefined;
|
|
2779
|
+
const maxOutputTokens = typeof body.max_output_tokens === "number"
|
|
2780
|
+
? body.max_output_tokens
|
|
2781
|
+
: undefined;
|
|
2782
|
+
const topLogprobs = typeof body.top_logprobs === "number"
|
|
2783
|
+
? body.top_logprobs
|
|
2784
|
+
: undefined;
|
|
2785
|
+
const truncation = body.truncation === "auto" ||
|
|
2786
|
+
body.truncation === "disabled"
|
|
2787
|
+
? body.truncation
|
|
2788
|
+
: undefined;
|
|
2789
|
+
const text = (body.text && typeof body.text === "object" &&
|
|
2790
|
+
!Array.isArray(body.text))
|
|
2791
|
+
? body.text
|
|
2792
|
+
: undefined;
|
|
2793
|
+
const streamOptions = (body.stream_options &&
|
|
2794
|
+
typeof body.stream_options === "object" &&
|
|
2795
|
+
!Array.isArray(body.stream_options))
|
|
2796
|
+
? body.stream_options
|
|
2797
|
+
: undefined;
|
|
2798
|
+
const background = typeof body.background === "boolean"
|
|
2799
|
+
? body.background
|
|
2800
|
+
: undefined;
|
|
2801
|
+
const include = Array.isArray(body.include)
|
|
2802
|
+
? body.include.filter((entry) => typeof entry === "string")
|
|
2803
|
+
: undefined;
|
|
2804
|
+
const serviceTier = body.service_tier === "auto" ||
|
|
2805
|
+
body.service_tier === "default" || body.service_tier === "flex" ||
|
|
2806
|
+
body.service_tier === "priority"
|
|
2807
|
+
? body.service_tier
|
|
2808
|
+
: undefined;
|
|
2809
|
+
const metadata = (body.metadata &&
|
|
2810
|
+
typeof body.metadata === "object" &&
|
|
2811
|
+
!Array.isArray(body.metadata))
|
|
2812
|
+
? body.metadata
|
|
2813
|
+
: undefined;
|
|
2814
|
+
const safetyIdentifier = typeof body.safety_identifier === "string"
|
|
2815
|
+
? body.safety_identifier
|
|
2816
|
+
: undefined;
|
|
2817
|
+
const promptCacheKey = typeof body.prompt_cache_key === "string"
|
|
2818
|
+
? body.prompt_cache_key
|
|
2819
|
+
: undefined;
|
|
2820
|
+
const passthrough = {};
|
|
2821
|
+
for (const [key, value] of Object.entries(body)) {
|
|
2822
|
+
if (key === "model" || key === "input" || key === "stream" ||
|
|
2823
|
+
key === "instructions" || key === "tools" ||
|
|
2824
|
+
key === "tool_choice" || key === "max_output_tokens" ||
|
|
2825
|
+
key === "previous_response_id" || key === "store" ||
|
|
2826
|
+
key === "reasoning" || key === "parallel_tool_calls" ||
|
|
2827
|
+
key === "max_tool_calls" || key === "temperature" ||
|
|
2828
|
+
key === "top_p" || key === "frequency_penalty" ||
|
|
2829
|
+
key === "presence_penalty" || key === "include" ||
|
|
2830
|
+
key === "text" || key === "stream_options" ||
|
|
2831
|
+
key === "background" || key === "truncation" ||
|
|
2832
|
+
key === "service_tier" || key === "top_logprobs" ||
|
|
2833
|
+
key === "metadata" || key === "safety_identifier" ||
|
|
2834
|
+
key === "prompt_cache_key" || key === "params") {
|
|
2835
|
+
continue;
|
|
2836
|
+
}
|
|
2837
|
+
passthrough[key] = value;
|
|
2838
|
+
}
|
|
2839
|
+
const explicitParams = (body.params &&
|
|
2840
|
+
typeof body.params === "object" &&
|
|
2841
|
+
!Array.isArray(body.params))
|
|
2842
|
+
? body.params
|
|
2843
|
+
: undefined;
|
|
2844
|
+
const params = explicitParams || Object.keys(passthrough).length > 0
|
|
2845
|
+
? { ...(explicitParams ?? {}), ...passthrough }
|
|
2846
|
+
: undefined;
|
|
2847
|
+
const requestBody = {
|
|
2848
|
+
model,
|
|
2849
|
+
input,
|
|
2850
|
+
instructions,
|
|
2851
|
+
previous_response_id: previousResponseId,
|
|
2852
|
+
store,
|
|
2853
|
+
tools,
|
|
2854
|
+
tool_choice: toolChoice,
|
|
2855
|
+
reasoning,
|
|
2856
|
+
parallel_tool_calls: parallelToolCalls,
|
|
2857
|
+
max_tool_calls: maxToolCalls,
|
|
2858
|
+
temperature,
|
|
2859
|
+
top_p: topP,
|
|
2860
|
+
frequency_penalty: frequencyPenalty,
|
|
2861
|
+
presence_penalty: presencePenalty,
|
|
2862
|
+
stream,
|
|
2863
|
+
stream_options: streamOptions,
|
|
2864
|
+
max_output_tokens: maxOutputTokens,
|
|
2865
|
+
top_logprobs: topLogprobs,
|
|
2866
|
+
truncation,
|
|
2867
|
+
text,
|
|
2868
|
+
include,
|
|
2869
|
+
background,
|
|
2870
|
+
service_tier: serviceTier,
|
|
2871
|
+
metadata,
|
|
2872
|
+
safety_identifier: safetyIdentifier,
|
|
2873
|
+
prompt_cache_key: promptCacheKey,
|
|
2874
|
+
params,
|
|
2875
|
+
};
|
|
2876
|
+
if (!stream) {
|
|
2877
|
+
const response = await opts.modelProvider.responses({
|
|
2878
|
+
request: requestBody,
|
|
2879
|
+
});
|
|
2880
|
+
return jsonResponse(toStrictResponseResource({
|
|
2881
|
+
request: requestBody,
|
|
2882
|
+
response,
|
|
2883
|
+
}));
|
|
2884
|
+
}
|
|
2885
|
+
const streamBody = new ReadableStream({
|
|
2886
|
+
start: async (controller) => {
|
|
2887
|
+
let sequence = 1;
|
|
2888
|
+
const itemIdByOutputIndex = new Map();
|
|
2889
|
+
const streamRequest = {
|
|
2890
|
+
...requestBody,
|
|
2891
|
+
stream: true,
|
|
2892
|
+
};
|
|
2893
|
+
try {
|
|
2894
|
+
const result = await opts.modelProvider.responses({
|
|
2895
|
+
request: streamRequest,
|
|
2896
|
+
onStreamEvent: (event) => {
|
|
2897
|
+
if (event.type === "response.created") {
|
|
2898
|
+
controller.enqueue(sseFrame({
|
|
2899
|
+
type: "response.created",
|
|
2900
|
+
sequence_number: sequence++,
|
|
2901
|
+
response: toStrictResponseResource({
|
|
2902
|
+
request: streamRequest,
|
|
2903
|
+
response: event.response,
|
|
2904
|
+
statusOverride: "in_progress",
|
|
2905
|
+
}),
|
|
2906
|
+
}));
|
|
2907
|
+
return;
|
|
2908
|
+
}
|
|
2909
|
+
if (event.type === "response.output_text.delta") {
|
|
2910
|
+
const itemId = event.item_id ??
|
|
2911
|
+
itemIdByOutputIndex.get(event.output_index) ??
|
|
2912
|
+
`msg_${event.output_index + 1}`;
|
|
2913
|
+
itemIdByOutputIndex.set(event.output_index, itemId);
|
|
2914
|
+
controller.enqueue(sseFrame({
|
|
2915
|
+
type: "response.output_text.delta",
|
|
2916
|
+
sequence_number: sequence++,
|
|
2917
|
+
output_index: event.output_index,
|
|
2918
|
+
item_id: itemId,
|
|
2919
|
+
content_index: event.content_index ?? 0,
|
|
2920
|
+
delta: event.delta,
|
|
2921
|
+
logprobs: event.logprobs ?? [],
|
|
2922
|
+
}));
|
|
2923
|
+
return;
|
|
2924
|
+
}
|
|
2925
|
+
if (event.type === "response.output_text.done") {
|
|
2926
|
+
const itemId = event.item_id ??
|
|
2927
|
+
itemIdByOutputIndex.get(event.output_index) ??
|
|
2928
|
+
`msg_${event.output_index + 1}`;
|
|
2929
|
+
itemIdByOutputIndex.set(event.output_index, itemId);
|
|
2930
|
+
controller.enqueue(sseFrame({
|
|
2931
|
+
type: "response.output_text.done",
|
|
2932
|
+
sequence_number: sequence++,
|
|
2933
|
+
output_index: event.output_index,
|
|
2934
|
+
item_id: itemId,
|
|
2935
|
+
content_index: event.content_index ?? 0,
|
|
2936
|
+
text: event.text,
|
|
2937
|
+
logprobs: [],
|
|
2938
|
+
}));
|
|
2939
|
+
return;
|
|
2940
|
+
}
|
|
2941
|
+
if (event.type === "response.completed") {
|
|
2942
|
+
controller.enqueue(sseFrame({
|
|
2943
|
+
type: "response.completed",
|
|
2944
|
+
sequence_number: sequence++,
|
|
2945
|
+
response: toStrictResponseResource({
|
|
2946
|
+
request: streamRequest,
|
|
2947
|
+
response: event.response,
|
|
2948
|
+
statusOverride: "completed",
|
|
2949
|
+
}),
|
|
2950
|
+
}));
|
|
2951
|
+
return;
|
|
2952
|
+
}
|
|
2953
|
+
if (event.type === "response.failed") {
|
|
2954
|
+
controller.enqueue(sseFrame({
|
|
2955
|
+
type: "response.failed",
|
|
2956
|
+
sequence_number: sequence++,
|
|
2957
|
+
response: {
|
|
2958
|
+
...toStrictResponseResource({
|
|
2959
|
+
request: streamRequest,
|
|
2960
|
+
response: {
|
|
2961
|
+
id: `resp_${crypto.randomUUID().slice(0, 8)}`,
|
|
2962
|
+
object: "response",
|
|
2963
|
+
output: [],
|
|
2964
|
+
status: "failed",
|
|
2965
|
+
error: event.error ??
|
|
2966
|
+
{ message: "Unknown error" },
|
|
2967
|
+
},
|
|
2968
|
+
statusOverride: "failed",
|
|
2969
|
+
}),
|
|
2970
|
+
error: event.error ?? { message: "Unknown error" },
|
|
2971
|
+
},
|
|
2972
|
+
}));
|
|
2973
|
+
}
|
|
2974
|
+
},
|
|
2975
|
+
});
|
|
2976
|
+
controller.enqueue(sseFrame({
|
|
2977
|
+
type: "response.completed",
|
|
2978
|
+
sequence_number: sequence++,
|
|
2979
|
+
response: toStrictResponseResource({
|
|
2980
|
+
request: streamRequest,
|
|
2981
|
+
response: result,
|
|
2982
|
+
statusOverride: "completed",
|
|
2983
|
+
}),
|
|
2984
|
+
}));
|
|
2985
|
+
controller.enqueue(new TextEncoder().encode("data: [DONE]\n\n"));
|
|
2986
|
+
}
|
|
2987
|
+
catch (err) {
|
|
2988
|
+
controller.enqueue(sseFrame({
|
|
2989
|
+
type: "error",
|
|
2990
|
+
code: "internal_error",
|
|
2991
|
+
message: err instanceof Error ? err.message : String(err),
|
|
2992
|
+
param: null,
|
|
2993
|
+
}));
|
|
2994
|
+
}
|
|
2995
|
+
finally {
|
|
2996
|
+
controller.close();
|
|
2997
|
+
}
|
|
2998
|
+
},
|
|
2999
|
+
});
|
|
3000
|
+
return new Response(streamBody, {
|
|
3001
|
+
headers: {
|
|
3002
|
+
"content-type": "text/event-stream",
|
|
3003
|
+
"cache-control": "no-cache",
|
|
3004
|
+
"connection": "keep-alive",
|
|
3005
|
+
},
|
|
3006
|
+
});
|
|
3007
|
+
}
|
|
3008
|
+
catch (err) {
|
|
3009
|
+
return jsonResponse({ error: err instanceof Error ? err.message : String(err) }, 400);
|
|
3010
|
+
}
|
|
3011
|
+
}
|
|
1389
3012
|
if (url.pathname === "/favicon.ico") {
|
|
1390
3013
|
if (req.method !== "GET" && req.method !== "HEAD") {
|
|
1391
3014
|
return new Response("Method not allowed", { status: 405 });
|
|
@@ -1408,16 +3031,72 @@ function startWebSocketSimulator(opts) {
|
|
|
1408
3031
|
}
|
|
1409
3032
|
}
|
|
1410
3033
|
}
|
|
1411
|
-
|
|
3034
|
+
const workspaceTestRunGetMatch = url.pathname.match(/^\/api\/workspaces\/([^/]+)\/test\/([^/]+)$/);
|
|
3035
|
+
if (workspaceTestRunGetMatch) {
|
|
1412
3036
|
if (req.method !== "GET") {
|
|
1413
3037
|
return new Response("Method not allowed", { status: 405 });
|
|
1414
3038
|
}
|
|
1415
|
-
|
|
1416
|
-
const
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
3039
|
+
const workspaceId = decodeURIComponent(workspaceTestRunGetMatch[1]);
|
|
3040
|
+
const requestedTestRunId = decodeURIComponent(workspaceTestRunGetMatch[2]);
|
|
3041
|
+
await logWorkspaceBotRoot("/api/workspaces/:id/test/:runId", workspaceId);
|
|
3042
|
+
await activateWorkspaceDeck(workspaceId);
|
|
3043
|
+
const payload = await buildWorkspaceReadModel(workspaceId, {
|
|
3044
|
+
requestedTestDeckPath: url.searchParams.get("deckPath"),
|
|
3045
|
+
requestedTestRunId,
|
|
3046
|
+
});
|
|
3047
|
+
if ("error" in payload) {
|
|
3048
|
+
return new Response(JSON.stringify({ error: payload.error }), {
|
|
3049
|
+
status: payload.status,
|
|
3050
|
+
headers: { "content-type": "application/json" },
|
|
3051
|
+
});
|
|
3052
|
+
}
|
|
3053
|
+
return new Response(JSON.stringify(payload), {
|
|
3054
|
+
headers: { "content-type": "application/json" },
|
|
3055
|
+
});
|
|
3056
|
+
}
|
|
3057
|
+
const workspaceGradeRunGetMatch = url.pathname.match(/^\/api\/workspaces\/([^/]+)\/grade\/([^/]+)$/);
|
|
3058
|
+
if (workspaceGradeRunGetMatch) {
|
|
3059
|
+
if (req.method !== "GET") {
|
|
3060
|
+
return new Response("Method not allowed", { status: 405 });
|
|
3061
|
+
}
|
|
3062
|
+
const workspaceId = decodeURIComponent(workspaceGradeRunGetMatch[1]);
|
|
3063
|
+
const requestedGradeRunId = decodeURIComponent(workspaceGradeRunGetMatch[2]);
|
|
3064
|
+
await logWorkspaceBotRoot("/api/workspaces/:id/grade/:runId", workspaceId);
|
|
3065
|
+
await activateWorkspaceDeck(workspaceId);
|
|
3066
|
+
const payload = await buildWorkspaceReadModel(workspaceId, {
|
|
3067
|
+
requestedTestDeckPath: url.searchParams.get("deckPath"),
|
|
3068
|
+
requestedGradeRunId,
|
|
3069
|
+
});
|
|
3070
|
+
if ("error" in payload) {
|
|
3071
|
+
return new Response(JSON.stringify({ error: payload.error }), {
|
|
3072
|
+
status: payload.status,
|
|
3073
|
+
headers: { "content-type": "application/json" },
|
|
3074
|
+
});
|
|
3075
|
+
}
|
|
3076
|
+
return new Response(JSON.stringify(payload), {
|
|
3077
|
+
headers: { "content-type": "application/json" },
|
|
3078
|
+
});
|
|
3079
|
+
}
|
|
3080
|
+
const workspaceGetMatch = url.pathname.match(/^\/api\/workspaces\/([^/]+)$/);
|
|
3081
|
+
if (workspaceGetMatch) {
|
|
3082
|
+
if (req.method !== "GET") {
|
|
3083
|
+
return new Response("Method not allowed", { status: 405 });
|
|
3084
|
+
}
|
|
3085
|
+
const workspaceId = decodeURIComponent(workspaceGetMatch[1]);
|
|
3086
|
+
await logWorkspaceBotRoot("/api/workspaces/:id", workspaceId);
|
|
3087
|
+
await activateWorkspaceDeck(workspaceId);
|
|
3088
|
+
const payload = await buildWorkspaceReadModel(workspaceId, {
|
|
3089
|
+
requestedTestDeckPath: url.searchParams.get("deckPath"),
|
|
3090
|
+
});
|
|
3091
|
+
if ("error" in payload) {
|
|
3092
|
+
return new Response(JSON.stringify({ error: payload.error }), {
|
|
3093
|
+
status: payload.status,
|
|
3094
|
+
headers: { "content-type": "application/json" },
|
|
3095
|
+
});
|
|
3096
|
+
}
|
|
3097
|
+
return new Response(JSON.stringify(payload), {
|
|
3098
|
+
headers: { "content-type": "application/json" },
|
|
3099
|
+
});
|
|
1421
3100
|
}
|
|
1422
3101
|
if (url.pathname === "/api/calibrate/run") {
|
|
1423
3102
|
if (req.method !== "POST") {
|
|
@@ -1425,10 +3104,12 @@ function startWebSocketSimulator(opts) {
|
|
|
1425
3104
|
}
|
|
1426
3105
|
try {
|
|
1427
3106
|
const body = await req.json();
|
|
1428
|
-
|
|
1429
|
-
|
|
3107
|
+
const workspaceId = getWorkspaceIdFromBody(body);
|
|
3108
|
+
if (!workspaceId) {
|
|
3109
|
+
throw new Error("Missing workspaceId");
|
|
1430
3110
|
}
|
|
1431
|
-
|
|
3111
|
+
await logWorkspaceBotRoot("/api/calibrate/run", workspaceId);
|
|
3112
|
+
await activateWorkspaceDeck(workspaceId);
|
|
1432
3113
|
await deckLoadPromise.catch(() => null);
|
|
1433
3114
|
const grader = body.graderId
|
|
1434
3115
|
? resolveGraderDeck(body.graderId)
|
|
@@ -1436,9 +3117,28 @@ function startWebSocketSimulator(opts) {
|
|
|
1436
3117
|
if (!grader) {
|
|
1437
3118
|
throw new Error("Unknown grader deck selection");
|
|
1438
3119
|
}
|
|
1439
|
-
const sessionState = readSessionState(
|
|
3120
|
+
const sessionState = readSessionState(workspaceId);
|
|
1440
3121
|
if (!sessionState) {
|
|
1441
|
-
throw new Error("
|
|
3122
|
+
throw new Error("Workspace not found");
|
|
3123
|
+
}
|
|
3124
|
+
const requestedScenarioRunId = typeof body.scenarioRunId === "string" &&
|
|
3125
|
+
body.scenarioRunId.trim().length > 0
|
|
3126
|
+
? body.scenarioRunId
|
|
3127
|
+
: undefined;
|
|
3128
|
+
const requestedLiveRun = requestedScenarioRunId
|
|
3129
|
+
? testBotRuns.get(requestedScenarioRunId)?.run
|
|
3130
|
+
: undefined;
|
|
3131
|
+
const requestedLiveRunMatchesWorkspace = Boolean(requestedLiveRun &&
|
|
3132
|
+
(requestedLiveRun.workspaceId === workspaceId ||
|
|
3133
|
+
requestedLiveRun.sessionId === workspaceId));
|
|
3134
|
+
const requestedPersistedRun = requestedScenarioRunId
|
|
3135
|
+
? readPersistedTestRunStatusById(sessionState, workspaceId, requestedScenarioRunId)
|
|
3136
|
+
: null;
|
|
3137
|
+
const selectedScenarioRun = requestedLiveRunMatchesWorkspace
|
|
3138
|
+
? requestedLiveRun
|
|
3139
|
+
: requestedPersistedRun;
|
|
3140
|
+
if (requestedScenarioRunId && !selectedScenarioRun) {
|
|
3141
|
+
throw new Error(`Scenario run "${requestedScenarioRunId}" not found for workspace`);
|
|
1442
3142
|
}
|
|
1443
3143
|
const graderSchema = await describeDeckInputSchemaFromPath(grader.path);
|
|
1444
3144
|
const runMode = schemaHasField(graderSchema.schema, "messageToGrade")
|
|
@@ -1453,7 +3153,24 @@ function startWebSocketSimulator(opts) {
|
|
|
1453
3153
|
delete next.gradingRuns;
|
|
1454
3154
|
return next;
|
|
1455
3155
|
})();
|
|
1456
|
-
const
|
|
3156
|
+
const conversationArtifacts = selectedScenarioRun
|
|
3157
|
+
? buildScenarioConversationArtifactsFromRun(selectedScenarioRun)
|
|
3158
|
+
: buildScenarioConversationArtifacts(sessionState);
|
|
3159
|
+
const conversationMessages = conversationArtifacts.messages;
|
|
3160
|
+
const activeScenarioRunId = requestedScenarioRunId ??
|
|
3161
|
+
(typeof sessionState.meta?.scenarioRunId === "string" &&
|
|
3162
|
+
sessionState.meta.scenarioRunId.trim().length > 0
|
|
3163
|
+
? sessionState.meta.scenarioRunId
|
|
3164
|
+
: undefined);
|
|
3165
|
+
const sessionMetaForPayload = {
|
|
3166
|
+
...(metaForGrading ?? {}),
|
|
3167
|
+
...(activeScenarioRunId
|
|
3168
|
+
? {
|
|
3169
|
+
scenarioRunId: activeScenarioRunId,
|
|
3170
|
+
testBotRunId: activeScenarioRunId,
|
|
3171
|
+
}
|
|
3172
|
+
: {}),
|
|
3173
|
+
};
|
|
1457
3174
|
const sessionPayload = {
|
|
1458
3175
|
messages: conversationMessages.length > 0
|
|
1459
3176
|
? conversationMessages.map((msg) => ({
|
|
@@ -1462,13 +3179,13 @@ function startWebSocketSimulator(opts) {
|
|
|
1462
3179
|
name: msg.name,
|
|
1463
3180
|
}))
|
|
1464
3181
|
: undefined,
|
|
1465
|
-
meta:
|
|
3182
|
+
meta: sessionMetaForPayload,
|
|
1466
3183
|
notes: sessionState.notes
|
|
1467
3184
|
? { text: sessionState.notes.text }
|
|
1468
3185
|
: undefined,
|
|
1469
3186
|
};
|
|
1470
3187
|
const startedAt = new Date().toISOString();
|
|
1471
|
-
const runId = randomId("cal");
|
|
3188
|
+
const runId = (0, server_helpers_js_1.randomId)("cal");
|
|
1472
3189
|
let entry;
|
|
1473
3190
|
const upsertCalibrationRun = (state, nextEntry) => {
|
|
1474
3191
|
const previousRuns = Array.isArray(state.meta?.gradingRuns)
|
|
@@ -1488,10 +3205,20 @@ function startWebSocketSimulator(opts) {
|
|
|
1488
3205
|
gradingRuns: nextRuns,
|
|
1489
3206
|
},
|
|
1490
3207
|
});
|
|
1491
|
-
|
|
3208
|
+
appendGradingLog(nextState, {
|
|
3209
|
+
type: "grading.run",
|
|
3210
|
+
run: nextEntry,
|
|
3211
|
+
});
|
|
3212
|
+
const sessionMeta = buildSessionMeta(workspaceId, nextState);
|
|
3213
|
+
(0, durable_streams_js_1.appendDurableStreamEvent)(WORKSPACE_STREAM_ID, {
|
|
3214
|
+
type: "calibrateSession",
|
|
3215
|
+
workspaceId,
|
|
3216
|
+
run: nextEntry,
|
|
3217
|
+
session: sessionMeta,
|
|
3218
|
+
});
|
|
1492
3219
|
(0, durable_streams_js_1.appendDurableStreamEvent)(GRADE_STREAM_ID, {
|
|
1493
3220
|
type: "calibrateSession",
|
|
1494
|
-
|
|
3221
|
+
workspaceId,
|
|
1495
3222
|
run: nextEntry,
|
|
1496
3223
|
session: sessionMeta,
|
|
1497
3224
|
});
|
|
@@ -1503,11 +3230,13 @@ function startWebSocketSimulator(opts) {
|
|
|
1503
3230
|
if (runMode !== "turns") {
|
|
1504
3231
|
entry = {
|
|
1505
3232
|
id: runId,
|
|
3233
|
+
workspaceId,
|
|
1506
3234
|
graderId: grader.id,
|
|
1507
3235
|
graderPath: grader.path,
|
|
1508
3236
|
graderLabel: grader.label,
|
|
1509
3237
|
status: "running",
|
|
1510
3238
|
runAt: startedAt,
|
|
3239
|
+
gradingRunId: runId,
|
|
1511
3240
|
input: { session: sessionPayload },
|
|
1512
3241
|
};
|
|
1513
3242
|
currentState = upsertCalibrationRun(currentState, entry);
|
|
@@ -1523,27 +3252,28 @@ function startWebSocketSimulator(opts) {
|
|
|
1523
3252
|
});
|
|
1524
3253
|
}
|
|
1525
3254
|
const messages = sessionPayload.messages ?? [];
|
|
1526
|
-
const assistantTurns =
|
|
1527
|
-
.map((msg, idx) => ({ msg, idx }))
|
|
1528
|
-
.filter(({ msg }) => msg.role === "assistant" &&
|
|
1529
|
-
typeof msg.content === "string" &&
|
|
1530
|
-
msg.content.trim().length > 0);
|
|
3255
|
+
const assistantTurns = conversationArtifacts.assistantTurns;
|
|
1531
3256
|
const totalTurns = assistantTurns.length;
|
|
1532
3257
|
const turns = [];
|
|
1533
3258
|
entry = {
|
|
1534
3259
|
id: runId,
|
|
3260
|
+
workspaceId,
|
|
1535
3261
|
graderId: grader.id,
|
|
1536
3262
|
graderPath: grader.path,
|
|
1537
3263
|
graderLabel: grader.label,
|
|
1538
3264
|
status: "running",
|
|
1539
3265
|
runAt: startedAt,
|
|
3266
|
+
gradingRunId: runId,
|
|
3267
|
+
input: { session: sessionPayload },
|
|
1540
3268
|
result: { mode: "turns", totalTurns, turns: [] },
|
|
1541
3269
|
};
|
|
1542
3270
|
currentState = upsertCalibrationRun(currentState, entry);
|
|
1543
3271
|
if (totalTurns === 0) {
|
|
1544
3272
|
return { mode: "turns", totalTurns, turns: [] };
|
|
1545
3273
|
}
|
|
1546
|
-
for (const
|
|
3274
|
+
for (const turnEntry of assistantTurns) {
|
|
3275
|
+
const msg = turnEntry.message;
|
|
3276
|
+
const idx = turnEntry.conversationIndex;
|
|
1547
3277
|
const input = {
|
|
1548
3278
|
session: {
|
|
1549
3279
|
...sessionPayload,
|
|
@@ -1563,6 +3293,9 @@ function startWebSocketSimulator(opts) {
|
|
|
1563
3293
|
});
|
|
1564
3294
|
turns.push({
|
|
1565
3295
|
index: idx,
|
|
3296
|
+
gradingRunId: runId,
|
|
3297
|
+
artifactRevisionId: (0, server_helpers_js_1.randomId)("grade-rev"),
|
|
3298
|
+
messageRefId: turnEntry.messageRefId,
|
|
1566
3299
|
message: msg,
|
|
1567
3300
|
input,
|
|
1568
3301
|
result: turnResult,
|
|
@@ -1577,39 +3310,57 @@ function startWebSocketSimulator(opts) {
|
|
|
1577
3310
|
})();
|
|
1578
3311
|
entry = {
|
|
1579
3312
|
id: runId,
|
|
3313
|
+
workspaceId,
|
|
1580
3314
|
graderId: grader.id,
|
|
1581
3315
|
graderPath: grader.path,
|
|
1582
3316
|
graderLabel: grader.label,
|
|
1583
3317
|
status: "completed",
|
|
1584
3318
|
runAt: startedAt,
|
|
3319
|
+
gradingRunId: runId,
|
|
1585
3320
|
input: { session: sessionPayload },
|
|
1586
3321
|
result,
|
|
1587
3322
|
};
|
|
1588
3323
|
}
|
|
1589
3324
|
catch (err) {
|
|
1590
3325
|
const message = err instanceof Error ? err.message : String(err);
|
|
3326
|
+
logger.error("[sim] calibrate run failed", {
|
|
3327
|
+
workspaceId,
|
|
3328
|
+
runId,
|
|
3329
|
+
runMode,
|
|
3330
|
+
graderId: grader.id,
|
|
3331
|
+
graderPath: grader.path,
|
|
3332
|
+
error: message,
|
|
3333
|
+
stack: err instanceof Error ? err.stack : undefined,
|
|
3334
|
+
});
|
|
1591
3335
|
entry = {
|
|
1592
3336
|
id: runId,
|
|
3337
|
+
workspaceId,
|
|
1593
3338
|
graderId: grader.id,
|
|
1594
3339
|
graderPath: grader.path,
|
|
1595
3340
|
graderLabel: grader.label,
|
|
1596
3341
|
status: "error",
|
|
1597
3342
|
runAt: startedAt,
|
|
3343
|
+
gradingRunId: runId,
|
|
1598
3344
|
input: { session: sessionPayload },
|
|
1599
3345
|
error: message,
|
|
1600
3346
|
};
|
|
1601
3347
|
}
|
|
1602
3348
|
const nextState = upsertCalibrationRun(currentState, entry);
|
|
1603
|
-
const sessionMeta = buildSessionMeta(
|
|
3349
|
+
const sessionMeta = buildSessionMeta(workspaceId, nextState);
|
|
1604
3350
|
return new Response(JSON.stringify({
|
|
1605
|
-
|
|
3351
|
+
workspaceId,
|
|
1606
3352
|
run: entry,
|
|
1607
3353
|
session: sessionMeta,
|
|
1608
3354
|
}), { headers: { "content-type": "application/json" } });
|
|
1609
3355
|
}
|
|
1610
3356
|
catch (err) {
|
|
1611
|
-
|
|
1612
|
-
|
|
3357
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3358
|
+
logger.error("[sim] /api/calibrate/run request failed", {
|
|
3359
|
+
error: message,
|
|
3360
|
+
stack: err instanceof Error ? err.stack : undefined,
|
|
3361
|
+
});
|
|
3362
|
+
return new Response(JSON.stringify({
|
|
3363
|
+
error: message,
|
|
1613
3364
|
}), { status: 400, headers: { "content-type": "application/json" } });
|
|
1614
3365
|
}
|
|
1615
3366
|
}
|
|
@@ -1619,12 +3370,14 @@ function startWebSocketSimulator(opts) {
|
|
|
1619
3370
|
}
|
|
1620
3371
|
try {
|
|
1621
3372
|
const body = await req.json();
|
|
1622
|
-
|
|
1623
|
-
|
|
3373
|
+
const workspaceId = getWorkspaceIdFromBody(body);
|
|
3374
|
+
if (!workspaceId || !body.refId) {
|
|
3375
|
+
throw new Error("Missing workspaceId or refId");
|
|
1624
3376
|
}
|
|
1625
|
-
|
|
3377
|
+
await logWorkspaceBotRoot("/api/calibrate/flag", workspaceId);
|
|
3378
|
+
const state = readSessionState(workspaceId);
|
|
1626
3379
|
if (!state) {
|
|
1627
|
-
throw new Error("
|
|
3380
|
+
throw new Error("Workspace not found");
|
|
1628
3381
|
}
|
|
1629
3382
|
const meta = (state.meta && typeof state.meta === "object")
|
|
1630
3383
|
? { ...state.meta }
|
|
@@ -1635,22 +3388,25 @@ function startWebSocketSimulator(opts) {
|
|
|
1635
3388
|
const flagIndex = existingFlags.findIndex((flag) => flag?.refId === body.refId);
|
|
1636
3389
|
let nextFlags;
|
|
1637
3390
|
let flagged = false;
|
|
3391
|
+
let flagEntry;
|
|
1638
3392
|
if (flagIndex >= 0) {
|
|
3393
|
+
flagEntry = existingFlags[flagIndex];
|
|
1639
3394
|
nextFlags = existingFlags.filter((_, idx) => idx !== flagIndex);
|
|
1640
3395
|
flagged = false;
|
|
1641
3396
|
}
|
|
1642
3397
|
else {
|
|
1643
3398
|
const now = new Date().toISOString();
|
|
3399
|
+
flagEntry = {
|
|
3400
|
+
id: (0, server_helpers_js_1.randomId)("flag"),
|
|
3401
|
+
refId: body.refId,
|
|
3402
|
+
runId: body.runId,
|
|
3403
|
+
turnIndex: body.turnIndex,
|
|
3404
|
+
reason: body.reason?.trim() || undefined,
|
|
3405
|
+
createdAt: now,
|
|
3406
|
+
};
|
|
1644
3407
|
nextFlags = [
|
|
1645
3408
|
...existingFlags,
|
|
1646
|
-
|
|
1647
|
-
id: randomId("flag"),
|
|
1648
|
-
refId: body.refId,
|
|
1649
|
-
runId: body.runId,
|
|
1650
|
-
turnIndex: body.turnIndex,
|
|
1651
|
-
reason: body.reason?.trim() || undefined,
|
|
1652
|
-
createdAt: now,
|
|
1653
|
-
},
|
|
3409
|
+
flagEntry,
|
|
1654
3410
|
];
|
|
1655
3411
|
flagged = true;
|
|
1656
3412
|
}
|
|
@@ -1661,14 +3417,25 @@ function startWebSocketSimulator(opts) {
|
|
|
1661
3417
|
gradingFlags: nextFlags,
|
|
1662
3418
|
},
|
|
1663
3419
|
});
|
|
1664
|
-
|
|
3420
|
+
appendGradingLog(updated, {
|
|
3421
|
+
type: "grading.flag",
|
|
3422
|
+
flagged,
|
|
3423
|
+
flag: flagEntry,
|
|
3424
|
+
refId: body.refId,
|
|
3425
|
+
});
|
|
3426
|
+
const sessionMeta = buildSessionMeta(workspaceId, updated);
|
|
3427
|
+
(0, durable_streams_js_1.appendDurableStreamEvent)(WORKSPACE_STREAM_ID, {
|
|
3428
|
+
type: "calibrateSession",
|
|
3429
|
+
workspaceId,
|
|
3430
|
+
session: sessionMeta,
|
|
3431
|
+
});
|
|
1665
3432
|
(0, durable_streams_js_1.appendDurableStreamEvent)(GRADE_STREAM_ID, {
|
|
1666
3433
|
type: "calibrateSession",
|
|
1667
|
-
|
|
3434
|
+
workspaceId,
|
|
1668
3435
|
session: sessionMeta,
|
|
1669
3436
|
});
|
|
1670
3437
|
return new Response(JSON.stringify({
|
|
1671
|
-
|
|
3438
|
+
workspaceId,
|
|
1672
3439
|
flagged,
|
|
1673
3440
|
flags: nextFlags,
|
|
1674
3441
|
}), { headers: { "content-type": "application/json" } });
|
|
@@ -1685,12 +3452,14 @@ function startWebSocketSimulator(opts) {
|
|
|
1685
3452
|
}
|
|
1686
3453
|
try {
|
|
1687
3454
|
const body = await req.json();
|
|
1688
|
-
|
|
1689
|
-
|
|
3455
|
+
const workspaceId = getWorkspaceIdFromBody(body);
|
|
3456
|
+
if (!workspaceId || !body.refId) {
|
|
3457
|
+
throw new Error("Missing workspaceId or refId");
|
|
1690
3458
|
}
|
|
1691
|
-
|
|
3459
|
+
await logWorkspaceBotRoot("/api/calibrate/flag/reason", workspaceId);
|
|
3460
|
+
const state = readSessionState(workspaceId);
|
|
1692
3461
|
if (!state) {
|
|
1693
|
-
throw new Error("
|
|
3462
|
+
throw new Error("Workspace not found");
|
|
1694
3463
|
}
|
|
1695
3464
|
const meta = (state.meta && typeof state.meta === "object")
|
|
1696
3465
|
? { ...state.meta }
|
|
@@ -1714,104 +3483,25 @@ function startWebSocketSimulator(opts) {
|
|
|
1714
3483
|
gradingFlags: nextFlags,
|
|
1715
3484
|
},
|
|
1716
3485
|
});
|
|
1717
|
-
|
|
1718
|
-
|
|
3486
|
+
appendGradingLog(updated, {
|
|
3487
|
+
type: "grading.flag.reason",
|
|
3488
|
+
flag: updatedFlag,
|
|
3489
|
+
refId: body.refId,
|
|
3490
|
+
});
|
|
3491
|
+
const sessionMeta = buildSessionMeta(workspaceId, updated);
|
|
3492
|
+
(0, durable_streams_js_1.appendDurableStreamEvent)(WORKSPACE_STREAM_ID, {
|
|
1719
3493
|
type: "calibrateSession",
|
|
1720
|
-
|
|
3494
|
+
workspaceId,
|
|
1721
3495
|
session: sessionMeta,
|
|
1722
3496
|
});
|
|
1723
|
-
return new Response(JSON.stringify({
|
|
1724
|
-
sessionId: body.sessionId,
|
|
1725
|
-
flags: nextFlags,
|
|
1726
|
-
}), { headers: { "content-type": "application/json" } });
|
|
1727
|
-
}
|
|
1728
|
-
catch (err) {
|
|
1729
|
-
return new Response(JSON.stringify({
|
|
1730
|
-
error: err instanceof Error ? err.message : String(err),
|
|
1731
|
-
}), { status: 400, headers: { "content-type": "application/json" } });
|
|
1732
|
-
}
|
|
1733
|
-
}
|
|
1734
|
-
if (url.pathname === "/api/grading/reference") {
|
|
1735
|
-
if (req.method !== "POST") {
|
|
1736
|
-
return new Response("Method not allowed", { status: 405 });
|
|
1737
|
-
}
|
|
1738
|
-
try {
|
|
1739
|
-
const body = await req.json();
|
|
1740
|
-
if (!body.sessionId)
|
|
1741
|
-
throw new Error("Missing sessionId");
|
|
1742
|
-
if (!body.runId)
|
|
1743
|
-
throw new Error("Missing runId");
|
|
1744
|
-
if (!body.referenceSample) {
|
|
1745
|
-
throw new Error("Missing referenceSample");
|
|
1746
|
-
}
|
|
1747
|
-
const score = body.referenceSample.score;
|
|
1748
|
-
if (typeof score !== "number" || Number.isNaN(score)) {
|
|
1749
|
-
throw new Error("Invalid reference score");
|
|
1750
|
-
}
|
|
1751
|
-
const reason = body.referenceSample.reason;
|
|
1752
|
-
if (typeof reason !== "string" || reason.trim().length === 0) {
|
|
1753
|
-
throw new Error("Missing reference reason");
|
|
1754
|
-
}
|
|
1755
|
-
const evidence = Array.isArray(body.referenceSample.evidence)
|
|
1756
|
-
? body.referenceSample.evidence.filter((e) => typeof e === "string" && e.trim().length > 0)
|
|
1757
|
-
: undefined;
|
|
1758
|
-
const state = readSessionState(body.sessionId);
|
|
1759
|
-
if (!state)
|
|
1760
|
-
throw new Error("Session not found");
|
|
1761
|
-
const previousRuns = Array.isArray(state.meta?.gradingRuns)
|
|
1762
|
-
? (state.meta
|
|
1763
|
-
.gradingRuns)
|
|
1764
|
-
: Array.isArray(state.meta?.calibrationRuns)
|
|
1765
|
-
? state.meta?.calibrationRuns
|
|
1766
|
-
: [];
|
|
1767
|
-
const index = previousRuns.findIndex((run) => run.id === body.runId);
|
|
1768
|
-
if (index < 0)
|
|
1769
|
-
throw new Error("Run not found");
|
|
1770
|
-
const run = previousRuns[index];
|
|
1771
|
-
const nextRun = {
|
|
1772
|
-
...run,
|
|
1773
|
-
};
|
|
1774
|
-
if (typeof body.turnIndex === "number") {
|
|
1775
|
-
const result = run.result;
|
|
1776
|
-
const turnIndex = body.turnIndex;
|
|
1777
|
-
if (!result || typeof result !== "object" ||
|
|
1778
|
-
result.mode !== "turns" ||
|
|
1779
|
-
!Array.isArray(result.turns)) {
|
|
1780
|
-
throw new Error("Run does not support turn references");
|
|
1781
|
-
}
|
|
1782
|
-
const turns = result.turns.map((turn) => ({ ...turn }));
|
|
1783
|
-
const targetIndex = turns.findIndex((turn) => turn.index === turnIndex);
|
|
1784
|
-
if (targetIndex < 0) {
|
|
1785
|
-
throw new Error("Turn not found");
|
|
1786
|
-
}
|
|
1787
|
-
turns[targetIndex] = {
|
|
1788
|
-
...turns[targetIndex],
|
|
1789
|
-
referenceSample: { score, reason, evidence },
|
|
1790
|
-
};
|
|
1791
|
-
nextRun.result = { ...result, turns };
|
|
1792
|
-
}
|
|
1793
|
-
else {
|
|
1794
|
-
nextRun.referenceSample = { score, reason, evidence };
|
|
1795
|
-
}
|
|
1796
|
-
const nextRuns = previousRuns.map((entry, i) => i === index ? nextRun : entry);
|
|
1797
|
-
const nextState = persistSessionState({
|
|
1798
|
-
...state,
|
|
1799
|
-
meta: {
|
|
1800
|
-
...(state.meta ?? {}),
|
|
1801
|
-
gradingRuns: nextRuns,
|
|
1802
|
-
},
|
|
1803
|
-
});
|
|
1804
|
-
const sessionMeta = buildSessionMeta(body.sessionId, nextState);
|
|
1805
3497
|
(0, durable_streams_js_1.appendDurableStreamEvent)(GRADE_STREAM_ID, {
|
|
1806
3498
|
type: "calibrateSession",
|
|
1807
|
-
|
|
1808
|
-
run: nextRun,
|
|
3499
|
+
workspaceId,
|
|
1809
3500
|
session: sessionMeta,
|
|
1810
3501
|
});
|
|
1811
3502
|
return new Response(JSON.stringify({
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
session: sessionMeta,
|
|
3503
|
+
workspaceId,
|
|
3504
|
+
flags: nextFlags,
|
|
1815
3505
|
}), { headers: { "content-type": "application/json" } });
|
|
1816
3506
|
}
|
|
1817
3507
|
catch (err) {
|
|
@@ -1820,8 +3510,28 @@ function startWebSocketSimulator(opts) {
|
|
|
1820
3510
|
}), { status: 400, headers: { "content-type": "application/json" } });
|
|
1821
3511
|
}
|
|
1822
3512
|
}
|
|
3513
|
+
const gradingReferenceResponse = await (0, server_feedback_grading_routes_js_1.handleGradingReferenceRoute)({
|
|
3514
|
+
url,
|
|
3515
|
+
req,
|
|
3516
|
+
getWorkspaceIdFromBody,
|
|
3517
|
+
logWorkspaceBotRoot,
|
|
3518
|
+
readSessionState,
|
|
3519
|
+
persistSessionState,
|
|
3520
|
+
appendGradingLog,
|
|
3521
|
+
buildSessionMeta,
|
|
3522
|
+
appendDurableStreamEvent: durable_streams_js_1.appendDurableStreamEvent,
|
|
3523
|
+
workspaceStreamId: WORKSPACE_STREAM_ID,
|
|
3524
|
+
gradeStreamId: GRADE_STREAM_ID,
|
|
3525
|
+
parseFiniteInteger,
|
|
3526
|
+
randomId: server_helpers_js_1.randomId,
|
|
3527
|
+
});
|
|
3528
|
+
if (gradingReferenceResponse)
|
|
3529
|
+
return gradingReferenceResponse;
|
|
1823
3530
|
if (url.pathname === "/api/test") {
|
|
1824
3531
|
if (req.method === "GET") {
|
|
3532
|
+
const workspaceId = getWorkspaceIdFromQuery(url);
|
|
3533
|
+
await logWorkspaceBotRoot("/api/test", workspaceId);
|
|
3534
|
+
await activateWorkspaceDeck(workspaceId);
|
|
1825
3535
|
await deckLoadPromise.catch(() => null);
|
|
1826
3536
|
const requestedDeck = url.searchParams.get("deckPath");
|
|
1827
3537
|
const selection = requestedDeck
|
|
@@ -1829,7 +3539,7 @@ function startWebSocketSimulator(opts) {
|
|
|
1829
3539
|
: availableTestDecks[0];
|
|
1830
3540
|
if (requestedDeck && !selection) {
|
|
1831
3541
|
return new Response(JSON.stringify({
|
|
1832
|
-
error: "Unknown
|
|
3542
|
+
error: "Unknown scenario deck selection",
|
|
1833
3543
|
}), {
|
|
1834
3544
|
status: 400,
|
|
1835
3545
|
headers: { "content-type": "application/json" },
|
|
@@ -1873,6 +3583,7 @@ function startWebSocketSimulator(opts) {
|
|
|
1873
3583
|
let inheritBotInput = false;
|
|
1874
3584
|
let userProvidedDeckInput = false;
|
|
1875
3585
|
let initFillRequestMissing = undefined;
|
|
3586
|
+
let sessionId = undefined;
|
|
1876
3587
|
try {
|
|
1877
3588
|
const body = await req.json();
|
|
1878
3589
|
if (typeof body.maxTurns === "number" && Number.isFinite(body.maxTurns)) {
|
|
@@ -1892,10 +3603,11 @@ function startWebSocketSimulator(opts) {
|
|
|
1892
3603
|
if (body.initFill && Array.isArray(body.initFill.missing)) {
|
|
1893
3604
|
initFillRequestMissing = body.initFill.missing.filter((entry) => typeof entry === "string" && entry.trim().length > 0);
|
|
1894
3605
|
}
|
|
3606
|
+
sessionId = getWorkspaceIdFromBody(body);
|
|
1895
3607
|
if (typeof body.botDeckPath === "string") {
|
|
1896
3608
|
const resolved = resolveTestDeck(body.botDeckPath);
|
|
1897
3609
|
if (!resolved) {
|
|
1898
|
-
return new Response(JSON.stringify({ error: "Unknown
|
|
3610
|
+
return new Response(JSON.stringify({ error: "Unknown scenario deck selection" }), {
|
|
1899
3611
|
status: 400,
|
|
1900
3612
|
headers: { "content-type": "application/json" },
|
|
1901
3613
|
});
|
|
@@ -1913,6 +3625,10 @@ function startWebSocketSimulator(opts) {
|
|
|
1913
3625
|
catch {
|
|
1914
3626
|
// ignore parse errors; use defaults
|
|
1915
3627
|
}
|
|
3628
|
+
if (sessionId) {
|
|
3629
|
+
await logWorkspaceBotRoot("/api/test/run", sessionId);
|
|
3630
|
+
await activateWorkspaceDeck(sessionId);
|
|
3631
|
+
}
|
|
1916
3632
|
if (deckInput === undefined) {
|
|
1917
3633
|
try {
|
|
1918
3634
|
const desc = await schemaPromise;
|
|
@@ -1928,7 +3644,7 @@ function startWebSocketSimulator(opts) {
|
|
|
1928
3644
|
deckInput = cloneValue(botInput);
|
|
1929
3645
|
}
|
|
1930
3646
|
if (!botDeckSelection) {
|
|
1931
|
-
return new Response(JSON.stringify({ error: "No
|
|
3647
|
+
return new Response(JSON.stringify({ error: "No scenario decks configured" }), { status: 400, headers: { "content-type": "application/json" } });
|
|
1932
3648
|
}
|
|
1933
3649
|
let initFillInfo;
|
|
1934
3650
|
let initFillTrace;
|
|
@@ -1970,12 +3686,14 @@ function startWebSocketSimulator(opts) {
|
|
|
1970
3686
|
error: parsed.error,
|
|
1971
3687
|
initFill: initFillInfo,
|
|
1972
3688
|
botDeckPath: botDeckSelection.path,
|
|
3689
|
+
botDeckId: botDeckSelection.id,
|
|
3690
|
+
botDeckLabel: botDeckSelection.label,
|
|
1973
3691
|
});
|
|
1974
3692
|
return new Response(JSON.stringify({
|
|
1975
3693
|
error: parsed.error,
|
|
1976
3694
|
initFill: initFillInfo,
|
|
1977
|
-
|
|
1978
|
-
|
|
3695
|
+
workspaceId: failure.workspaceId,
|
|
3696
|
+
workspacePath: failure.workspacePath,
|
|
1979
3697
|
}), {
|
|
1980
3698
|
status: 400,
|
|
1981
3699
|
headers: { "content-type": "application/json" },
|
|
@@ -2032,12 +3750,14 @@ function startWebSocketSimulator(opts) {
|
|
|
2032
3750
|
error: message,
|
|
2033
3751
|
initFill: initFillInfo,
|
|
2034
3752
|
botDeckPath: botDeckSelection.path,
|
|
3753
|
+
botDeckId: botDeckSelection.id,
|
|
3754
|
+
botDeckLabel: botDeckSelection.label,
|
|
2035
3755
|
});
|
|
2036
3756
|
return new Response(JSON.stringify({
|
|
2037
3757
|
error: message,
|
|
2038
3758
|
initFill: initFillInfo,
|
|
2039
|
-
|
|
2040
|
-
|
|
3759
|
+
workspaceId: failure.workspaceId,
|
|
3760
|
+
workspacePath: failure.workspacePath,
|
|
2041
3761
|
}), {
|
|
2042
3762
|
status: 400,
|
|
2043
3763
|
headers: { "content-type": "application/json" },
|
|
@@ -2073,126 +3793,896 @@ function startWebSocketSimulator(opts) {
|
|
|
2073
3793
|
error: message,
|
|
2074
3794
|
initFill: initFillInfo,
|
|
2075
3795
|
botDeckPath: botDeckSelection.path,
|
|
3796
|
+
botDeckId: botDeckSelection.id,
|
|
3797
|
+
botDeckLabel: botDeckSelection.label,
|
|
2076
3798
|
});
|
|
2077
3799
|
return new Response(JSON.stringify({
|
|
2078
3800
|
error: message,
|
|
2079
3801
|
initFill: initFillInfo,
|
|
2080
|
-
|
|
2081
|
-
|
|
3802
|
+
workspaceId: failure.workspaceId,
|
|
3803
|
+
workspacePath: failure.workspacePath,
|
|
2082
3804
|
}), { status: 400, headers: { "content-type": "application/json" } });
|
|
2083
3805
|
}
|
|
3806
|
+
const existingSessionState = sessionId
|
|
3807
|
+
? readSessionState(sessionId)
|
|
3808
|
+
: undefined;
|
|
3809
|
+
const workspaceRecord = sessionId
|
|
3810
|
+
? resolveWorkspaceRecord(sessionId) ?? {
|
|
3811
|
+
id: sessionId,
|
|
3812
|
+
rootDir: path.dirname(resolvedDeckPath),
|
|
3813
|
+
rootDeckPath: resolvedDeckPath,
|
|
3814
|
+
createdAt: new Date().toISOString(),
|
|
3815
|
+
}
|
|
3816
|
+
: undefined;
|
|
3817
|
+
if (workspaceRecord && !resolveWorkspaceRecord(sessionId)) {
|
|
3818
|
+
registerWorkspace(workspaceRecord);
|
|
3819
|
+
}
|
|
2084
3820
|
const run = startTestBotRun({
|
|
2085
3821
|
maxTurnsOverride,
|
|
2086
3822
|
deckInput,
|
|
2087
3823
|
botInput,
|
|
2088
3824
|
initialUserMessage,
|
|
2089
3825
|
botDeckPath: botDeckSelection.path,
|
|
3826
|
+
botDeckId: botDeckSelection.id,
|
|
3827
|
+
botDeckLabel: botDeckSelection.label,
|
|
2090
3828
|
initFill: initFillInfo,
|
|
2091
3829
|
initFillTrace,
|
|
3830
|
+
workspaceId: sessionId,
|
|
3831
|
+
workspaceRecord,
|
|
3832
|
+
baseMeta: existingSessionState?.meta ??
|
|
3833
|
+
undefined,
|
|
2092
3834
|
});
|
|
2093
3835
|
return new Response(JSON.stringify({ run }), { headers: { "content-type": "application/json" } });
|
|
2094
3836
|
}
|
|
2095
|
-
if (url.pathname === "/api/test/
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
3837
|
+
if (url.pathname === "/api/test/message") {
|
|
3838
|
+
if (req.method !== "POST") {
|
|
3839
|
+
return new Response("Method not allowed", { status: 405 });
|
|
3840
|
+
}
|
|
3841
|
+
// 1) Parse request payload and stitch together run/session state.
|
|
3842
|
+
let payload = {};
|
|
3843
|
+
try {
|
|
3844
|
+
payload = await req.json();
|
|
3845
|
+
}
|
|
3846
|
+
catch {
|
|
3847
|
+
// ignore parse errors
|
|
3848
|
+
}
|
|
3849
|
+
const requestedRunId = typeof payload.runId === "string"
|
|
3850
|
+
? payload.runId
|
|
3851
|
+
: undefined;
|
|
3852
|
+
let runId = requestedRunId;
|
|
3853
|
+
const workspaceId = (() => {
|
|
3854
|
+
const workspaceId = typeof payload.workspaceId === "string" &&
|
|
3855
|
+
payload.workspaceId.trim().length > 0
|
|
3856
|
+
? payload.workspaceId
|
|
3857
|
+
: undefined;
|
|
3858
|
+
if (workspaceId)
|
|
3859
|
+
return workspaceId;
|
|
3860
|
+
return undefined;
|
|
3861
|
+
})();
|
|
3862
|
+
await logWorkspaceBotRoot("/api/test/message", workspaceId);
|
|
3863
|
+
if (workspaceId) {
|
|
3864
|
+
await activateWorkspaceDeck(workspaceId);
|
|
3865
|
+
}
|
|
3866
|
+
let savedState = workspaceId
|
|
3867
|
+
? readSessionState(workspaceId, { withTraces: true })
|
|
3868
|
+
: undefined;
|
|
3869
|
+
if (savedState && requestedRunId) {
|
|
3870
|
+
const savedRunId = typeof savedState.meta?.testBotRunId === "string"
|
|
3871
|
+
? savedState.meta.testBotRunId
|
|
3872
|
+
: savedState.runId;
|
|
3873
|
+
if (!savedRunId || savedRunId !== requestedRunId) {
|
|
3874
|
+
// Explicit runId in the same workspace means "start a fresh run".
|
|
3875
|
+
savedState = undefined;
|
|
3876
|
+
}
|
|
3877
|
+
}
|
|
3878
|
+
if (!savedState && runId) {
|
|
3879
|
+
const entry = testBotRuns.get(runId);
|
|
3880
|
+
const runWorkspaceId = entry?.run.workspaceId ?? entry?.run.sessionId;
|
|
3881
|
+
if (runWorkspaceId &&
|
|
3882
|
+
(!workspaceId || runWorkspaceId === workspaceId)) {
|
|
3883
|
+
savedState = readSessionState(runWorkspaceId, {
|
|
3884
|
+
withTraces: true,
|
|
3885
|
+
});
|
|
3886
|
+
}
|
|
3887
|
+
}
|
|
3888
|
+
if (savedState && !runId) {
|
|
3889
|
+
runId = typeof savedState.meta?.testBotRunId === "string"
|
|
3890
|
+
? savedState.meta.testBotRunId
|
|
3891
|
+
: savedState.runId;
|
|
3892
|
+
}
|
|
3893
|
+
runId = runId ?? (0, server_helpers_js_1.randomId)("testbot");
|
|
3894
|
+
const workspaceRecord = workspaceId
|
|
3895
|
+
? resolveWorkspaceRecord(workspaceId) ?? {
|
|
3896
|
+
id: workspaceId,
|
|
3897
|
+
rootDir: path.dirname(resolvedDeckPath),
|
|
3898
|
+
rootDeckPath: resolvedDeckPath,
|
|
3899
|
+
createdAt: new Date().toISOString(),
|
|
3900
|
+
}
|
|
3901
|
+
: undefined;
|
|
3902
|
+
if (workspaceRecord && !resolveWorkspaceRecord(workspaceId)) {
|
|
3903
|
+
registerWorkspace(workspaceRecord);
|
|
3904
|
+
}
|
|
3905
|
+
const workspaceMeta = workspaceRecord
|
|
3906
|
+
? buildWorkspaceMeta(workspaceRecord, savedState?.meta ?? {})
|
|
3907
|
+
: (savedState?.meta ?? {});
|
|
3908
|
+
const existingEntry = testBotRuns.get(runId);
|
|
3909
|
+
if (existingEntry?.promise) {
|
|
3910
|
+
return new Response(JSON.stringify({ error: "Scenario run already in progress" }), { status: 409, headers: { "content-type": "application/json" } });
|
|
3911
|
+
}
|
|
3912
|
+
// 2) Resolve which scenario deck to use and derive initial input.
|
|
3913
|
+
await deckLoadPromise.catch(() => null);
|
|
3914
|
+
const requestedDeck = typeof payload.botDeckPath === "string"
|
|
3915
|
+
? payload.botDeckPath
|
|
3916
|
+
: undefined;
|
|
3917
|
+
const selection = (() => {
|
|
3918
|
+
if (requestedDeck)
|
|
3919
|
+
return resolveTestDeck(requestedDeck);
|
|
3920
|
+
const metaPath = typeof savedState?.meta?.testBotConfigPath === "string"
|
|
3921
|
+
? savedState.meta.testBotConfigPath
|
|
3922
|
+
: undefined;
|
|
3923
|
+
if (metaPath)
|
|
3924
|
+
return resolveTestDeck(metaPath);
|
|
3925
|
+
return availableTestDecks[0];
|
|
3926
|
+
})();
|
|
3927
|
+
if (requestedDeck && !selection) {
|
|
3928
|
+
return new Response(JSON.stringify({ error: "Unknown scenario deck selection" }), { status: 400, headers: { "content-type": "application/json" } });
|
|
3929
|
+
}
|
|
3930
|
+
const botConfigPath = selection?.path ?? resolvedDeckPath;
|
|
3931
|
+
const testBotName = selection
|
|
3932
|
+
? path.basename(botConfigPath).replace(/\.deck\.(md|ts)$/i, "")
|
|
3933
|
+
: toDeckLabel(resolvedDeckPath);
|
|
3934
|
+
const selectedScenarioDeckId = selection?.id ?? testBotName;
|
|
3935
|
+
const selectedScenarioDeckLabel = selection?.label ?? testBotName;
|
|
3936
|
+
const message = typeof payload.message === "string"
|
|
3937
|
+
? payload.message.trim()
|
|
3938
|
+
: "";
|
|
3939
|
+
const hasSavedMessages = (savedState?.messages?.length ?? 0) > 0;
|
|
3940
|
+
let deckInput = payload.context ?? payload.init;
|
|
3941
|
+
if (!hasSavedMessages && deckInput === undefined) {
|
|
3942
|
+
try {
|
|
3943
|
+
const desc = await schemaPromise;
|
|
3944
|
+
deckInput = desc.defaults !== undefined
|
|
3945
|
+
? desc.defaults
|
|
3946
|
+
: deriveInitialFromSchema(desc.schema);
|
|
3947
|
+
}
|
|
3948
|
+
catch {
|
|
3949
|
+
// ignore; keep undefined
|
|
3950
|
+
}
|
|
3951
|
+
}
|
|
3952
|
+
const stream = typeof payload.stream === "boolean"
|
|
3953
|
+
? payload.stream
|
|
3954
|
+
: true;
|
|
3955
|
+
const deckForStart = await deckLoadPromise.catch(() => null);
|
|
3956
|
+
const startMode = deckForStart &&
|
|
3957
|
+
(deckForStart.startMode === "assistant" ||
|
|
3958
|
+
deckForStart.startMode === "user")
|
|
3959
|
+
? deckForStart.startMode
|
|
3960
|
+
: "assistant";
|
|
3961
|
+
const startOnly = !message && startMode === "assistant" &&
|
|
3962
|
+
!hasSavedMessages;
|
|
3963
|
+
if (!message && !startOnly) {
|
|
3964
|
+
return new Response(JSON.stringify({ error: "Missing message" }), { status: 400, headers: { "content-type": "application/json" } });
|
|
3965
|
+
}
|
|
3966
|
+
// 3) Initialize the run, sync from prior session state, and prep tracing.
|
|
3967
|
+
const entry = existingEntry ?? {
|
|
3968
|
+
run: {
|
|
3969
|
+
id: runId,
|
|
3970
|
+
status: "idle",
|
|
3971
|
+
messages: [],
|
|
3972
|
+
traces: [],
|
|
3973
|
+
toolInserts: [],
|
|
3974
|
+
},
|
|
3975
|
+
promise: null,
|
|
3976
|
+
abort: null,
|
|
3977
|
+
};
|
|
3978
|
+
testBotRuns.set(runId, entry);
|
|
3979
|
+
const run = entry.run;
|
|
3980
|
+
const emitTestBot = (payload) => broadcastTestBot(payload, run.workspaceId ?? workspaceId ?? runId);
|
|
3981
|
+
run.status = "running";
|
|
3982
|
+
run.error = undefined;
|
|
3983
|
+
run.startedAt = run.startedAt ?? new Date().toISOString();
|
|
3984
|
+
if (savedState) {
|
|
3985
|
+
syncTestBotRunFromState(run, savedState);
|
|
3986
|
+
}
|
|
3987
|
+
emitTestBot({ type: "testBotStatus", run });
|
|
3988
|
+
const controller = new AbortController();
|
|
3989
|
+
entry.abort = controller;
|
|
3990
|
+
const isAborted = () => controller.signal.aborted;
|
|
3991
|
+
const capturedTraces = Array.isArray(savedState?.traces)
|
|
3992
|
+
? cloneTraces(savedState.traces)
|
|
3993
|
+
: [];
|
|
3994
|
+
const pendingTraceEvents = [];
|
|
3995
|
+
const flushPendingTraceEvents = (state) => {
|
|
3996
|
+
if (!pendingTraceEvents.length)
|
|
3997
|
+
return;
|
|
3998
|
+
for (const pending of pendingTraceEvents) {
|
|
3999
|
+
appendSessionEvent(state, {
|
|
4000
|
+
...pending,
|
|
4001
|
+
kind: "trace",
|
|
4002
|
+
category: traceCategory(pending.type),
|
|
4003
|
+
});
|
|
4004
|
+
}
|
|
4005
|
+
pendingTraceEvents.length = 0;
|
|
4006
|
+
};
|
|
4007
|
+
const tracer = (event) => {
|
|
4008
|
+
const stamped = event.ts ? event : { ...event, ts: Date.now() };
|
|
4009
|
+
capturedTraces.push(stamped);
|
|
4010
|
+
consoleTracer?.(stamped);
|
|
4011
|
+
if (savedState?.meta?.sessionId) {
|
|
4012
|
+
appendSessionEvent(savedState, {
|
|
4013
|
+
...stamped,
|
|
4014
|
+
kind: "trace",
|
|
4015
|
+
category: traceCategory(stamped.type),
|
|
4016
|
+
});
|
|
4017
|
+
}
|
|
4018
|
+
else {
|
|
4019
|
+
pendingTraceEvents.push(stamped);
|
|
4020
|
+
}
|
|
4021
|
+
};
|
|
4022
|
+
const appendFromState = (state) => {
|
|
4023
|
+
const snapshot = buildTestBotSnapshot(state);
|
|
4024
|
+
run.messages = snapshot.messages;
|
|
4025
|
+
run.toolInserts = snapshot.toolInserts;
|
|
4026
|
+
run.traces = Array.isArray(state.traces)
|
|
4027
|
+
? [...state.traces]
|
|
4028
|
+
: undefined;
|
|
4029
|
+
const nextWorkspaceId = typeof state.meta?.workspaceId === "string"
|
|
4030
|
+
? state.meta.workspaceId
|
|
4031
|
+
: typeof state.meta?.sessionId === "string"
|
|
4032
|
+
? state.meta.sessionId
|
|
4033
|
+
: undefined;
|
|
4034
|
+
if (nextWorkspaceId) {
|
|
4035
|
+
run.workspaceId = nextWorkspaceId;
|
|
4036
|
+
run.sessionId = nextWorkspaceId;
|
|
4037
|
+
}
|
|
4038
|
+
emitTestBot({ type: "testBotStatus", run });
|
|
4039
|
+
};
|
|
4040
|
+
// 4) Execute the deck run(s): optional assistant start, then user message.
|
|
4041
|
+
entry.promise = (async () => {
|
|
4042
|
+
try {
|
|
4043
|
+
const countAssistantMessages = (state) => {
|
|
4044
|
+
if (!state?.messages?.length)
|
|
4045
|
+
return 0;
|
|
4046
|
+
let count = 0;
|
|
4047
|
+
for (const msg of state.messages) {
|
|
4048
|
+
if (msg?.role === "assistant")
|
|
4049
|
+
count += 1;
|
|
4050
|
+
}
|
|
4051
|
+
return count;
|
|
4052
|
+
};
|
|
4053
|
+
const runOnce = async (initialUserMessage, turn, shouldStream = stream) => {
|
|
4054
|
+
if (isAborted())
|
|
4055
|
+
return undefined;
|
|
4056
|
+
const hasSavedMessages = (savedState?.messages?.length ?? 0) > 0;
|
|
4057
|
+
const inputProvided = !hasSavedMessages &&
|
|
4058
|
+
deckInput !== undefined;
|
|
4059
|
+
const input = inputProvided ? deckInput : undefined;
|
|
4060
|
+
const result = await (0, gambit_core_1.runDeck)({
|
|
4061
|
+
path: resolvedDeckPath,
|
|
4062
|
+
input,
|
|
4063
|
+
inputProvided,
|
|
4064
|
+
modelProvider: opts.modelProvider,
|
|
4065
|
+
isRoot: true,
|
|
4066
|
+
allowRootStringInput: true,
|
|
4067
|
+
defaultModel: typeof payload.model === "string"
|
|
4068
|
+
? payload.model
|
|
4069
|
+
: opts.model,
|
|
4070
|
+
modelOverride: typeof payload.modelForce === "string"
|
|
4071
|
+
? payload.modelForce
|
|
4072
|
+
: opts.modelForce,
|
|
4073
|
+
trace: tracer,
|
|
4074
|
+
stream: shouldStream,
|
|
4075
|
+
state: savedState,
|
|
4076
|
+
responsesMode: opts.responsesMode,
|
|
4077
|
+
signal: controller.signal,
|
|
4078
|
+
initialUserMessage,
|
|
4079
|
+
onStateUpdate: (state) => {
|
|
4080
|
+
if (isAborted())
|
|
4081
|
+
return;
|
|
4082
|
+
const nextStateWithSource = applyUserMessageRefSource(savedState, state, "manual");
|
|
4083
|
+
const nextMeta = {
|
|
4084
|
+
...workspaceMeta,
|
|
4085
|
+
...(nextStateWithSource.meta ?? {}),
|
|
4086
|
+
testBot: true,
|
|
4087
|
+
testBotRunId: runId,
|
|
4088
|
+
testBotConfigPath: botConfigPath,
|
|
4089
|
+
testBotName,
|
|
4090
|
+
scenarioRunId: runId,
|
|
4091
|
+
selectedScenarioDeckId,
|
|
4092
|
+
selectedScenarioDeckLabel,
|
|
4093
|
+
scenarioConfigPath: botConfigPath,
|
|
4094
|
+
...(workspaceId ? { workspaceId } : {}),
|
|
4095
|
+
};
|
|
4096
|
+
const enriched = persistSessionState({
|
|
4097
|
+
...nextStateWithSource,
|
|
4098
|
+
meta: nextMeta,
|
|
4099
|
+
traces: capturedTraces,
|
|
4100
|
+
});
|
|
4101
|
+
savedState = enriched;
|
|
4102
|
+
flushPendingTraceEvents(enriched);
|
|
4103
|
+
appendFromState(enriched);
|
|
4104
|
+
},
|
|
4105
|
+
onStreamText: (chunk) => emitTestBot({
|
|
4106
|
+
type: "testBotStream",
|
|
4107
|
+
runId,
|
|
4108
|
+
role: "assistant",
|
|
4109
|
+
chunk,
|
|
4110
|
+
turn,
|
|
4111
|
+
ts: Date.now(),
|
|
4112
|
+
}),
|
|
4113
|
+
});
|
|
4114
|
+
if (isAborted())
|
|
4115
|
+
return result;
|
|
4116
|
+
if (shouldStream) {
|
|
4117
|
+
emitTestBot({
|
|
4118
|
+
type: "testBotStreamEnd",
|
|
4119
|
+
runId,
|
|
4120
|
+
role: "assistant",
|
|
4121
|
+
turn,
|
|
4122
|
+
ts: Date.now(),
|
|
4123
|
+
});
|
|
4124
|
+
}
|
|
4125
|
+
return result;
|
|
4126
|
+
};
|
|
4127
|
+
let assistantTurn = countAssistantMessages(savedState);
|
|
4128
|
+
if (startMode === "assistant" &&
|
|
4129
|
+
!hasSavedMessages) {
|
|
4130
|
+
if (isAborted()) {
|
|
4131
|
+
run.status = "canceled";
|
|
4132
|
+
return;
|
|
4133
|
+
}
|
|
4134
|
+
await runOnce(undefined, assistantTurn, stream);
|
|
4135
|
+
assistantTurn += 1;
|
|
4136
|
+
}
|
|
4137
|
+
let result = undefined;
|
|
4138
|
+
if (message) {
|
|
4139
|
+
if (isAborted()) {
|
|
4140
|
+
run.status = "canceled";
|
|
4141
|
+
return;
|
|
4142
|
+
}
|
|
4143
|
+
result = await runOnce(message, assistantTurn, stream);
|
|
2104
4144
|
}
|
|
4145
|
+
if (isAborted()) {
|
|
4146
|
+
run.status = "canceled";
|
|
4147
|
+
}
|
|
4148
|
+
else if (result !== undefined && (0, gambit_core_1.isGambitEndSignal)(result)) {
|
|
4149
|
+
run.status = "completed";
|
|
4150
|
+
}
|
|
4151
|
+
else {
|
|
4152
|
+
run.status = "completed";
|
|
4153
|
+
}
|
|
4154
|
+
}
|
|
4155
|
+
catch (err) {
|
|
4156
|
+
if (isAborted() || (0, gambit_core_1.isRunCanceledError)(err)) {
|
|
4157
|
+
run.status = "canceled";
|
|
4158
|
+
run.error = undefined;
|
|
4159
|
+
}
|
|
4160
|
+
else {
|
|
4161
|
+
run.status = "error";
|
|
4162
|
+
run.error = err instanceof Error ? err.message : String(err);
|
|
4163
|
+
logger.warn(`[sim] build bot run failed (workspaceId=${workspaceId}): ${run.error}`);
|
|
4164
|
+
}
|
|
4165
|
+
}
|
|
4166
|
+
finally {
|
|
4167
|
+
if (savedState) {
|
|
4168
|
+
syncTestBotRunFromState(run, savedState);
|
|
4169
|
+
}
|
|
4170
|
+
run.finishedAt = new Date().toISOString();
|
|
4171
|
+
entry.abort = null;
|
|
4172
|
+
entry.promise = null;
|
|
4173
|
+
emitTestBot({ type: "testBotStatus", run });
|
|
4174
|
+
}
|
|
4175
|
+
})();
|
|
4176
|
+
// 5) Return the current run snapshot to the caller.
|
|
4177
|
+
return new Response(JSON.stringify({ run }), { headers: { "content-type": "application/json" } });
|
|
4178
|
+
}
|
|
4179
|
+
if (url.pathname === "/api/test/stop") {
|
|
4180
|
+
if (req.method !== "POST") {
|
|
4181
|
+
return new Response("Method not allowed", { status: 405 });
|
|
4182
|
+
}
|
|
4183
|
+
let runId = undefined;
|
|
4184
|
+
try {
|
|
4185
|
+
const body = await req.json();
|
|
4186
|
+
if (typeof body.runId === "string")
|
|
4187
|
+
runId = body.runId;
|
|
4188
|
+
}
|
|
4189
|
+
catch {
|
|
4190
|
+
// ignore
|
|
4191
|
+
}
|
|
4192
|
+
const entry = runId ? testBotRuns.get(runId) : undefined;
|
|
4193
|
+
const wasRunning = Boolean(entry?.promise);
|
|
4194
|
+
if (entry?.abort) {
|
|
4195
|
+
entry.abort.abort();
|
|
4196
|
+
}
|
|
4197
|
+
if (entry?.run?.status === "running") {
|
|
4198
|
+
entry.run.status = "canceled";
|
|
4199
|
+
entry.run.finishedAt = entry.run.finishedAt ??
|
|
4200
|
+
new Date().toISOString();
|
|
4201
|
+
}
|
|
4202
|
+
return new Response(JSON.stringify({
|
|
4203
|
+
stopped: wasRunning,
|
|
4204
|
+
run: entry?.run ?? {
|
|
4205
|
+
id: runId ?? "",
|
|
4206
|
+
status: "idle",
|
|
4207
|
+
messages: [],
|
|
4208
|
+
traces: [],
|
|
4209
|
+
toolInserts: [],
|
|
4210
|
+
},
|
|
4211
|
+
}), { headers: { "content-type": "application/json" } });
|
|
4212
|
+
}
|
|
4213
|
+
if (url.pathname === "/api/build/reset") {
|
|
4214
|
+
if (req.method !== "POST") {
|
|
4215
|
+
return new Response("Method not allowed", { status: 405 });
|
|
4216
|
+
}
|
|
4217
|
+
let workspaceId = undefined;
|
|
4218
|
+
try {
|
|
4219
|
+
const body = await req.json();
|
|
4220
|
+
workspaceId = getWorkspaceIdFromBody(body);
|
|
4221
|
+
}
|
|
4222
|
+
catch {
|
|
4223
|
+
// ignore
|
|
4224
|
+
}
|
|
4225
|
+
if (!workspaceId) {
|
|
4226
|
+
return new Response(JSON.stringify({ error: "Missing workspaceId" }), { status: 400, headers: { "content-type": "application/json" } });
|
|
4227
|
+
}
|
|
4228
|
+
const entry = buildBotRuns.get(workspaceId);
|
|
4229
|
+
if (entry?.abort) {
|
|
4230
|
+
entry.abort.abort();
|
|
4231
|
+
}
|
|
4232
|
+
if (entry?.run) {
|
|
4233
|
+
if (entry.run.status === "running") {
|
|
4234
|
+
entry.run.status = "canceled";
|
|
4235
|
+
}
|
|
4236
|
+
entry.run.finishedAt = entry.run.finishedAt ??
|
|
4237
|
+
new Date().toISOString();
|
|
4238
|
+
const state = readSessionState(workspaceId);
|
|
4239
|
+
if (state) {
|
|
4240
|
+
persistSessionState({
|
|
4241
|
+
...state,
|
|
4242
|
+
meta: {
|
|
4243
|
+
...(state.meta ?? {}),
|
|
4244
|
+
buildStatus: entry.run.status,
|
|
4245
|
+
buildFinishedAt: entry.run.finishedAt,
|
|
4246
|
+
buildError: entry.run.error,
|
|
4247
|
+
},
|
|
4248
|
+
});
|
|
4249
|
+
}
|
|
4250
|
+
}
|
|
4251
|
+
buildBotRuns.delete(workspaceId);
|
|
4252
|
+
broadcastBuildBot({
|
|
4253
|
+
type: "buildBotStatus",
|
|
4254
|
+
run: {
|
|
4255
|
+
id: workspaceId,
|
|
4256
|
+
status: "idle",
|
|
4257
|
+
messages: [],
|
|
4258
|
+
traces: [],
|
|
4259
|
+
toolInserts: [],
|
|
4260
|
+
},
|
|
4261
|
+
}, workspaceId);
|
|
4262
|
+
return new Response(JSON.stringify({ reset: true }), {
|
|
4263
|
+
headers: { "content-type": "application/json" },
|
|
4264
|
+
});
|
|
4265
|
+
}
|
|
4266
|
+
if (url.pathname === "/api/build/stop") {
|
|
4267
|
+
if (req.method !== "POST") {
|
|
4268
|
+
return new Response("Method not allowed", { status: 405 });
|
|
4269
|
+
}
|
|
4270
|
+
let workspaceId = undefined;
|
|
4271
|
+
try {
|
|
4272
|
+
const body = await req.json();
|
|
4273
|
+
workspaceId = getWorkspaceIdFromBody(body);
|
|
4274
|
+
}
|
|
4275
|
+
catch {
|
|
4276
|
+
// ignore
|
|
4277
|
+
}
|
|
4278
|
+
if (!workspaceId) {
|
|
4279
|
+
return new Response(JSON.stringify({ error: "Missing workspaceId" }), { status: 400, headers: { "content-type": "application/json" } });
|
|
4280
|
+
}
|
|
4281
|
+
const entry = buildBotRuns.get(workspaceId);
|
|
4282
|
+
const wasRunning = Boolean(entry?.promise);
|
|
4283
|
+
if (entry?.abort) {
|
|
4284
|
+
entry.abort.abort();
|
|
4285
|
+
}
|
|
4286
|
+
if (entry?.run?.status === "running") {
|
|
4287
|
+
entry.run.status = "canceled";
|
|
4288
|
+
entry.run.finishedAt = entry.run.finishedAt ??
|
|
4289
|
+
new Date().toISOString();
|
|
4290
|
+
}
|
|
4291
|
+
if (entry?.run) {
|
|
4292
|
+
const state = readSessionState(workspaceId);
|
|
4293
|
+
if (state) {
|
|
4294
|
+
persistSessionState({
|
|
4295
|
+
...state,
|
|
4296
|
+
meta: {
|
|
4297
|
+
...(state.meta ?? {}),
|
|
4298
|
+
buildStatus: entry.run.status,
|
|
4299
|
+
buildFinishedAt: entry.run.finishedAt,
|
|
4300
|
+
buildError: entry.run.error,
|
|
4301
|
+
},
|
|
4302
|
+
});
|
|
2105
4303
|
}
|
|
2106
4304
|
}
|
|
2107
4305
|
const run = entry?.run ?? {
|
|
2108
|
-
id:
|
|
4306
|
+
id: workspaceId,
|
|
2109
4307
|
status: "idle",
|
|
2110
4308
|
messages: [],
|
|
2111
4309
|
traces: [],
|
|
2112
4310
|
toolInserts: [],
|
|
2113
|
-
sessionId,
|
|
2114
4311
|
};
|
|
2115
|
-
|
|
2116
|
-
|
|
4312
|
+
broadcastBuildBot({ type: "buildBotStatus", run, state: entry?.state ?? undefined }, workspaceId);
|
|
4313
|
+
return new Response(JSON.stringify({
|
|
4314
|
+
stopped: wasRunning,
|
|
4315
|
+
run,
|
|
4316
|
+
}), { headers: { "content-type": "application/json" } });
|
|
4317
|
+
}
|
|
4318
|
+
if (url.pathname === "/api/build/message") {
|
|
4319
|
+
if (req.method !== "POST") {
|
|
4320
|
+
return new Response("Method not allowed", { status: 405 });
|
|
4321
|
+
}
|
|
4322
|
+
let payload = {};
|
|
4323
|
+
try {
|
|
4324
|
+
payload = await req.json();
|
|
4325
|
+
}
|
|
4326
|
+
catch {
|
|
4327
|
+
// ignore
|
|
4328
|
+
}
|
|
4329
|
+
let workspaceId = typeof payload.workspaceId === "string"
|
|
4330
|
+
? payload.workspaceId
|
|
4331
|
+
: typeof payload.runId === "string"
|
|
4332
|
+
? payload.runId
|
|
4333
|
+
: undefined;
|
|
4334
|
+
if (!workspaceId) {
|
|
4335
|
+
const created = await createWorkspaceSession();
|
|
4336
|
+
workspaceId = created.id;
|
|
4337
|
+
}
|
|
4338
|
+
await logWorkspaceBotRoot("/api/build/message", workspaceId);
|
|
4339
|
+
const message = typeof payload.message === "string"
|
|
4340
|
+
? payload.message
|
|
4341
|
+
: "";
|
|
4342
|
+
const workspaceRecord = resolveWorkspaceRecord(workspaceId) ?? {
|
|
4343
|
+
id: workspaceId,
|
|
4344
|
+
rootDir: path.dirname(resolvedDeckPath),
|
|
4345
|
+
rootDeckPath: resolvedDeckPath,
|
|
4346
|
+
createdAt: new Date().toISOString(),
|
|
4347
|
+
};
|
|
4348
|
+
if (!resolveWorkspaceRecord(workspaceId)) {
|
|
4349
|
+
registerWorkspace(workspaceRecord);
|
|
4350
|
+
}
|
|
4351
|
+
const existingEntry = buildBotRuns.get(workspaceId);
|
|
4352
|
+
if (existingEntry?.promise) {
|
|
4353
|
+
return new Response(JSON.stringify({ error: "Run already in progress" }), { status: 409, headers: { "content-type": "application/json" } });
|
|
4354
|
+
}
|
|
4355
|
+
const entry = existingEntry ?? {
|
|
4356
|
+
run: {
|
|
4357
|
+
id: workspaceId,
|
|
4358
|
+
status: "idle",
|
|
4359
|
+
messages: [],
|
|
4360
|
+
traces: [],
|
|
4361
|
+
toolInserts: [],
|
|
4362
|
+
},
|
|
4363
|
+
state: null,
|
|
4364
|
+
promise: null,
|
|
4365
|
+
abort: null,
|
|
4366
|
+
};
|
|
4367
|
+
buildBotRuns.set(workspaceId, entry);
|
|
4368
|
+
if (!entry.state) {
|
|
4369
|
+
const projection = readBuildState(workspaceId);
|
|
4370
|
+
if (projection?.state) {
|
|
4371
|
+
entry.state = projection.state;
|
|
4372
|
+
}
|
|
4373
|
+
}
|
|
4374
|
+
const run = entry.run;
|
|
4375
|
+
run.status = "running";
|
|
4376
|
+
run.error = undefined;
|
|
4377
|
+
run.startedAt = run.startedAt ?? new Date().toISOString();
|
|
4378
|
+
if (entry.state) {
|
|
4379
|
+
syncBuildBotRunFromState(run, entry.state);
|
|
4380
|
+
}
|
|
4381
|
+
broadcastBuildBot({
|
|
4382
|
+
type: "buildBotStatus",
|
|
4383
|
+
run,
|
|
4384
|
+
state: entry.state ?? undefined,
|
|
4385
|
+
}, workspaceId);
|
|
4386
|
+
const workspaceBaseState = readSessionState(workspaceId) ?? {
|
|
4387
|
+
runId: workspaceId,
|
|
4388
|
+
messages: [],
|
|
4389
|
+
meta: {},
|
|
4390
|
+
};
|
|
4391
|
+
persistSessionState({
|
|
4392
|
+
...workspaceBaseState,
|
|
4393
|
+
meta: {
|
|
4394
|
+
...buildWorkspaceMeta(workspaceRecord, workspaceBaseState.meta ?? {}),
|
|
4395
|
+
buildStatus: run.status,
|
|
4396
|
+
buildStartedAt: run.startedAt,
|
|
4397
|
+
},
|
|
4398
|
+
});
|
|
4399
|
+
const controller = new AbortController();
|
|
4400
|
+
entry.abort = controller;
|
|
4401
|
+
const isAborted = () => controller.signal.aborted;
|
|
4402
|
+
const botDeckUrl = new URL("./decks/gambit-bot/PROMPT.md", globalThis[Symbol.for("import-meta-ponyfill-commonjs")](require, module).url);
|
|
4403
|
+
if (botDeckUrl.protocol !== "file:") {
|
|
4404
|
+
run.status = "error";
|
|
4405
|
+
run.error = "Unable to resolve Gambit Bot deck path";
|
|
4406
|
+
broadcastBuildBot({ type: "buildBotStatus", run }, workspaceId);
|
|
4407
|
+
const state = readSessionState(workspaceId);
|
|
2117
4408
|
if (state) {
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
4409
|
+
persistSessionState({
|
|
4410
|
+
...state,
|
|
4411
|
+
meta: {
|
|
4412
|
+
...(state.meta ?? {}),
|
|
4413
|
+
buildStatus: "error",
|
|
4414
|
+
buildError: run.error,
|
|
4415
|
+
buildFinishedAt: new Date().toISOString(),
|
|
4416
|
+
},
|
|
4417
|
+
});
|
|
4418
|
+
}
|
|
4419
|
+
return new Response(JSON.stringify({ error: run.error }), { status: 500, headers: { "content-type": "application/json" } });
|
|
4420
|
+
}
|
|
4421
|
+
const botDeckPath = path.fromFileUrl(botDeckUrl);
|
|
4422
|
+
let botRoot;
|
|
4423
|
+
try {
|
|
4424
|
+
botRoot = await resolveBuildBotRoot(workspaceId);
|
|
4425
|
+
}
|
|
4426
|
+
catch (err) {
|
|
4427
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
4428
|
+
run.status = "error";
|
|
4429
|
+
run.error = msg;
|
|
4430
|
+
broadcastBuildBot({ type: "buildBotStatus", run }, workspaceId);
|
|
4431
|
+
const state = readSessionState(workspaceId);
|
|
4432
|
+
if (state) {
|
|
4433
|
+
persistSessionState({
|
|
4434
|
+
...state,
|
|
4435
|
+
meta: {
|
|
4436
|
+
...(state.meta ?? {}),
|
|
4437
|
+
buildStatus: "error",
|
|
4438
|
+
buildError: msg,
|
|
4439
|
+
buildFinishedAt: new Date().toISOString(),
|
|
4440
|
+
},
|
|
4441
|
+
});
|
|
2121
4442
|
}
|
|
4443
|
+
return new Response(JSON.stringify({ error: msg }), { status: 400, headers: { "content-type": "application/json" } });
|
|
4444
|
+
}
|
|
4445
|
+
const prevBotRoot = dntShim.Deno.env.get("GAMBIT_BOT_ROOT");
|
|
4446
|
+
dntShim.Deno.env.set("GAMBIT_BOT_ROOT", botRoot);
|
|
4447
|
+
const capturedTraces = Array.isArray(entry.state?.traces)
|
|
4448
|
+
? cloneTraces(entry.state.traces)
|
|
4449
|
+
: [];
|
|
4450
|
+
const tracer = (event) => {
|
|
4451
|
+
const stamped = event.ts ? event : { ...event, ts: Date.now() };
|
|
4452
|
+
capturedTraces.push(stamped);
|
|
4453
|
+
consoleTracer?.(stamped);
|
|
4454
|
+
broadcastBuildBot({
|
|
4455
|
+
type: "buildBotTrace",
|
|
4456
|
+
runId: workspaceId,
|
|
4457
|
+
event: stamped,
|
|
4458
|
+
}, workspaceId);
|
|
4459
|
+
};
|
|
4460
|
+
const appendFromState = (state) => {
|
|
4461
|
+
syncBuildBotRunFromState(run, state);
|
|
4462
|
+
run.traces = Array.isArray(state.traces) ? [...state.traces] : [];
|
|
4463
|
+
broadcastBuildBot({ type: "buildBotStatus", run, state }, workspaceId);
|
|
4464
|
+
const base = readSessionState(workspaceId) ?? {
|
|
4465
|
+
runId: workspaceId,
|
|
4466
|
+
messages: [],
|
|
4467
|
+
meta: {},
|
|
4468
|
+
};
|
|
4469
|
+
persistSessionState({
|
|
4470
|
+
...base,
|
|
4471
|
+
meta: {
|
|
4472
|
+
...buildWorkspaceMeta(workspaceRecord, base.meta ?? {}),
|
|
4473
|
+
buildStatus: run.status,
|
|
4474
|
+
buildStartedAt: run.startedAt,
|
|
4475
|
+
buildFinishedAt: run.finishedAt,
|
|
4476
|
+
buildError: run.error,
|
|
4477
|
+
},
|
|
4478
|
+
});
|
|
4479
|
+
};
|
|
4480
|
+
entry.promise = (async () => {
|
|
4481
|
+
try {
|
|
4482
|
+
const runOnce = async (initialUserMessage, turn, shouldStream = true) => {
|
|
4483
|
+
if (isAborted())
|
|
4484
|
+
return undefined;
|
|
4485
|
+
const result = await (0, gambit_core_1.runDeck)({
|
|
4486
|
+
path: botDeckPath,
|
|
4487
|
+
input: undefined,
|
|
4488
|
+
inputProvided: false,
|
|
4489
|
+
modelProvider: opts.modelProvider,
|
|
4490
|
+
allowRootStringInput: true,
|
|
4491
|
+
defaultModel: typeof payload.model === "string"
|
|
4492
|
+
? payload.model
|
|
4493
|
+
: opts.model,
|
|
4494
|
+
modelOverride: typeof payload.modelForce === "string"
|
|
4495
|
+
? payload.modelForce
|
|
4496
|
+
: opts.modelForce,
|
|
4497
|
+
trace: tracer,
|
|
4498
|
+
stream: shouldStream,
|
|
4499
|
+
state: entry.state ?? undefined,
|
|
4500
|
+
responsesMode: opts.responsesMode,
|
|
4501
|
+
signal: controller.signal,
|
|
4502
|
+
initialUserMessage,
|
|
4503
|
+
onStateUpdate: (state) => {
|
|
4504
|
+
if (isAborted())
|
|
4505
|
+
return;
|
|
4506
|
+
const nextState = {
|
|
4507
|
+
...state,
|
|
4508
|
+
traces: capturedTraces,
|
|
4509
|
+
};
|
|
4510
|
+
entry.state = nextState;
|
|
4511
|
+
appendFromState(nextState);
|
|
4512
|
+
},
|
|
4513
|
+
onStreamText: (chunk) => broadcastBuildBot({
|
|
4514
|
+
type: "buildBotStream",
|
|
4515
|
+
runId: workspaceId,
|
|
4516
|
+
role: "assistant",
|
|
4517
|
+
chunk,
|
|
4518
|
+
turn,
|
|
4519
|
+
ts: Date.now(),
|
|
4520
|
+
}, workspaceId),
|
|
4521
|
+
});
|
|
4522
|
+
if (shouldStream) {
|
|
4523
|
+
broadcastBuildBot({
|
|
4524
|
+
type: "buildBotStreamEnd",
|
|
4525
|
+
runId: workspaceId,
|
|
4526
|
+
role: "assistant",
|
|
4527
|
+
turn,
|
|
4528
|
+
ts: Date.now(),
|
|
4529
|
+
}, workspaceId);
|
|
4530
|
+
}
|
|
4531
|
+
return result;
|
|
4532
|
+
};
|
|
4533
|
+
const hasSavedMessages = (entry.state?.messages?.length ?? 0) > 0;
|
|
4534
|
+
let assistantTurn = 0;
|
|
4535
|
+
if (Array.isArray(entry.state?.messages)) {
|
|
4536
|
+
for (const msg of entry.state.messages) {
|
|
4537
|
+
if (msg?.role === "assistant")
|
|
4538
|
+
assistantTurn += 1;
|
|
4539
|
+
}
|
|
4540
|
+
}
|
|
4541
|
+
if (!hasSavedMessages && message.trim().length === 0) {
|
|
4542
|
+
await runOnce(undefined, assistantTurn, true);
|
|
4543
|
+
}
|
|
4544
|
+
else {
|
|
4545
|
+
await runOnce(message, assistantTurn, true);
|
|
4546
|
+
}
|
|
4547
|
+
if (isAborted()) {
|
|
4548
|
+
run.status = "canceled";
|
|
4549
|
+
}
|
|
4550
|
+
else {
|
|
4551
|
+
run.status = "completed";
|
|
4552
|
+
}
|
|
4553
|
+
}
|
|
4554
|
+
catch (err) {
|
|
4555
|
+
if (isAborted() || (0, gambit_core_1.isRunCanceledError)(err)) {
|
|
4556
|
+
run.status = "canceled";
|
|
4557
|
+
run.error = undefined;
|
|
4558
|
+
}
|
|
4559
|
+
else {
|
|
4560
|
+
run.status = "error";
|
|
4561
|
+
run.error = err instanceof Error ? err.message : String(err);
|
|
4562
|
+
logger.warn(`[sim] build bot run failed (workspaceId=${workspaceId}): ${run.error}`);
|
|
4563
|
+
}
|
|
4564
|
+
}
|
|
4565
|
+
finally {
|
|
4566
|
+
run.finishedAt = new Date().toISOString();
|
|
4567
|
+
entry.abort = null;
|
|
4568
|
+
entry.promise = null;
|
|
4569
|
+
const base = readSessionState(workspaceId) ?? {
|
|
4570
|
+
runId: workspaceId,
|
|
4571
|
+
messages: [],
|
|
4572
|
+
meta: {},
|
|
4573
|
+
};
|
|
4574
|
+
persistSessionState({
|
|
4575
|
+
...base,
|
|
4576
|
+
meta: {
|
|
4577
|
+
...buildWorkspaceMeta(workspaceRecord, base.meta ?? {}),
|
|
4578
|
+
buildStatus: run.status,
|
|
4579
|
+
buildStartedAt: run.startedAt,
|
|
4580
|
+
buildFinishedAt: run.finishedAt,
|
|
4581
|
+
buildError: run.error,
|
|
4582
|
+
},
|
|
4583
|
+
});
|
|
4584
|
+
try {
|
|
4585
|
+
reloadPrimaryDeck();
|
|
4586
|
+
}
|
|
4587
|
+
catch (err) {
|
|
4588
|
+
logger.warn(`[sim] failed to reload primary deck after build: ${err instanceof Error ? err.message : String(err)}`);
|
|
4589
|
+
}
|
|
4590
|
+
broadcastBuildBot({ type: "buildBotStatus", run, state: entry.state ?? undefined }, workspaceId);
|
|
4591
|
+
if (prevBotRoot === undefined) {
|
|
4592
|
+
try {
|
|
4593
|
+
dntShim.Deno.env.delete("GAMBIT_BOT_ROOT");
|
|
4594
|
+
}
|
|
4595
|
+
catch {
|
|
4596
|
+
// ignore
|
|
4597
|
+
}
|
|
4598
|
+
}
|
|
4599
|
+
else {
|
|
4600
|
+
dntShim.Deno.env.set("GAMBIT_BOT_ROOT", prevBotRoot);
|
|
4601
|
+
}
|
|
4602
|
+
}
|
|
4603
|
+
})();
|
|
4604
|
+
return new Response(JSON.stringify({ run }), {
|
|
4605
|
+
headers: { "content-type": "application/json" },
|
|
4606
|
+
});
|
|
4607
|
+
}
|
|
4608
|
+
if (url.pathname === "/api/build/files") {
|
|
4609
|
+
if (req.method !== "GET") {
|
|
4610
|
+
return new Response("Method not allowed", { status: 405 });
|
|
2122
4611
|
}
|
|
2123
|
-
|
|
2124
|
-
const
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
4612
|
+
try {
|
|
4613
|
+
const workspaceId = getWorkspaceIdFromQuery(url);
|
|
4614
|
+
await logWorkspaceBotRoot("/api/build/files", workspaceId);
|
|
4615
|
+
const root = await resolveBuildBotRoot(workspaceId);
|
|
4616
|
+
const entries = await listBuildBotFiles(root);
|
|
4617
|
+
return new Response(JSON.stringify({ root, entries }), {
|
|
4618
|
+
headers: { "content-type": "application/json" },
|
|
4619
|
+
});
|
|
2128
4620
|
}
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
? resolveTestDeck(requestedDeck)
|
|
2133
|
-
: availableTestDecks[0];
|
|
2134
|
-
if (requestedDeck && !selection) {
|
|
2135
|
-
return new Response(JSON.stringify({
|
|
2136
|
-
error: "Unknown test deck selection",
|
|
2137
|
-
}), {
|
|
4621
|
+
catch (err) {
|
|
4622
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
4623
|
+
return new Response(JSON.stringify({ error: message }), {
|
|
2138
4624
|
status: 400,
|
|
2139
4625
|
headers: { "content-type": "application/json" },
|
|
2140
4626
|
});
|
|
2141
4627
|
}
|
|
2142
|
-
if (selection) {
|
|
2143
|
-
const schemaDesc = await describeDeckInputSchemaFromPath(selection.path);
|
|
2144
|
-
return new Response(JSON.stringify({
|
|
2145
|
-
run,
|
|
2146
|
-
botPath: selection.path,
|
|
2147
|
-
botLabel: selection.label,
|
|
2148
|
-
botDescription: selection.description,
|
|
2149
|
-
selectedDeckId: selection.id,
|
|
2150
|
-
inputSchema: schemaDesc.schema,
|
|
2151
|
-
inputSchemaError: schemaDesc.error,
|
|
2152
|
-
defaults: { input: schemaDesc.defaults },
|
|
2153
|
-
testDecks: availableTestDecks,
|
|
2154
|
-
}), { headers: { "content-type": "application/json" } });
|
|
2155
|
-
}
|
|
2156
|
-
return new Response(JSON.stringify({
|
|
2157
|
-
run,
|
|
2158
|
-
botPath: null,
|
|
2159
|
-
botLabel: null,
|
|
2160
|
-
botDescription: null,
|
|
2161
|
-
selectedDeckId: null,
|
|
2162
|
-
inputSchema: null,
|
|
2163
|
-
inputSchemaError: null,
|
|
2164
|
-
defaults: {},
|
|
2165
|
-
testDecks: availableTestDecks,
|
|
2166
|
-
}), { headers: { "content-type": "application/json" } });
|
|
2167
4628
|
}
|
|
2168
|
-
if (url.pathname === "/api/
|
|
2169
|
-
if (req.method !== "
|
|
4629
|
+
if (url.pathname === "/api/build/file") {
|
|
4630
|
+
if (req.method !== "GET") {
|
|
2170
4631
|
return new Response("Method not allowed", { status: 405 });
|
|
2171
4632
|
}
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
4633
|
+
const workspaceId = getWorkspaceIdFromQuery(url);
|
|
4634
|
+
await logWorkspaceBotRoot("/api/build/file", workspaceId);
|
|
4635
|
+
const inputPath = url.searchParams.get("path") ?? "";
|
|
4636
|
+
if (!inputPath) {
|
|
4637
|
+
appendServerErrorLog(workspaceId, {
|
|
4638
|
+
endpoint: "/api/build/file",
|
|
4639
|
+
status: 400,
|
|
4640
|
+
message: "Missing path",
|
|
4641
|
+
method: req.method,
|
|
4642
|
+
});
|
|
4643
|
+
return new Response(JSON.stringify({ error: "Missing path" }), {
|
|
4644
|
+
status: 400,
|
|
4645
|
+
headers: { "content-type": "application/json" },
|
|
4646
|
+
});
|
|
2177
4647
|
}
|
|
2178
|
-
|
|
2179
|
-
|
|
4648
|
+
try {
|
|
4649
|
+
const root = await resolveBuildBotRoot(workspaceId);
|
|
4650
|
+
const resolved = await resolveBuildBotPath(root, inputPath);
|
|
4651
|
+
if (!resolved.stat.isFile) {
|
|
4652
|
+
return new Response(JSON.stringify({ error: "Path is not a file" }), {
|
|
4653
|
+
status: 400,
|
|
4654
|
+
headers: { "content-type": "application/json" },
|
|
4655
|
+
});
|
|
4656
|
+
}
|
|
4657
|
+
if (resolved.stat.size > MAX_FILE_PREVIEW_BYTES) {
|
|
4658
|
+
return new Response(JSON.stringify({
|
|
4659
|
+
path: resolved.relativePath,
|
|
4660
|
+
tooLarge: true,
|
|
4661
|
+
size: resolved.stat.size,
|
|
4662
|
+
}), { headers: { "content-type": "application/json" } });
|
|
4663
|
+
}
|
|
4664
|
+
const bytes = await dntShim.Deno.readFile(resolved.fullPath);
|
|
4665
|
+
const text = readPreviewText(bytes);
|
|
4666
|
+
if (text === null) {
|
|
4667
|
+
return new Response(JSON.stringify({
|
|
4668
|
+
path: resolved.relativePath,
|
|
4669
|
+
binary: true,
|
|
4670
|
+
size: resolved.stat.size,
|
|
4671
|
+
}), { headers: { "content-type": "application/json" } });
|
|
4672
|
+
}
|
|
4673
|
+
return new Response(JSON.stringify({
|
|
4674
|
+
path: resolved.relativePath,
|
|
4675
|
+
contents: text,
|
|
4676
|
+
size: resolved.stat.size,
|
|
4677
|
+
}), { headers: { "content-type": "application/json" } });
|
|
2180
4678
|
}
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
4679
|
+
catch (err) {
|
|
4680
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
4681
|
+
return new Response(JSON.stringify({ error: message }), {
|
|
4682
|
+
status: 400,
|
|
4683
|
+
headers: { "content-type": "application/json" },
|
|
4684
|
+
});
|
|
2185
4685
|
}
|
|
2186
|
-
return new Response(JSON.stringify({
|
|
2187
|
-
stopped: wasRunning,
|
|
2188
|
-
run: entry?.run ?? {
|
|
2189
|
-
id: runId ?? "",
|
|
2190
|
-
status: "idle",
|
|
2191
|
-
messages: [],
|
|
2192
|
-
traces: [],
|
|
2193
|
-
toolInserts: [],
|
|
2194
|
-
},
|
|
2195
|
-
}), { headers: { "content-type": "application/json" } });
|
|
2196
4686
|
}
|
|
2197
4687
|
if (url.pathname === "/api/simulator/run") {
|
|
2198
4688
|
if (req.method !== "POST") {
|
|
@@ -2214,18 +4704,44 @@ function startWebSocketSimulator(opts) {
|
|
|
2214
4704
|
simulatorCapturedTraces = [];
|
|
2215
4705
|
simulatorCurrentRunId = undefined;
|
|
2216
4706
|
}
|
|
2217
|
-
if (payload.
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
4707
|
+
if (payload.workspaceId) {
|
|
4708
|
+
let loaded;
|
|
4709
|
+
try {
|
|
4710
|
+
loaded = readSessionStateStrict(payload.workspaceId, {
|
|
4711
|
+
withTraces: true,
|
|
4712
|
+
});
|
|
4713
|
+
}
|
|
4714
|
+
catch (err) {
|
|
4715
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
4716
|
+
emitSimulator({ type: "error", message });
|
|
4717
|
+
return new Response(JSON.stringify({ error: message }), { status: 400, headers: { "content-type": "application/json" } });
|
|
2224
4718
|
}
|
|
4719
|
+
if (!loaded) {
|
|
4720
|
+
const message = "Workspace not found";
|
|
4721
|
+
emitSimulator({ type: "error", message });
|
|
4722
|
+
return new Response(JSON.stringify({ error: message }), { status: 404, headers: { "content-type": "application/json" } });
|
|
4723
|
+
}
|
|
4724
|
+
simulatorSavedState = loaded;
|
|
4725
|
+
simulatorCapturedTraces = Array.isArray(loaded.traces)
|
|
4726
|
+
? cloneTraces(loaded.traces)
|
|
4727
|
+
: [];
|
|
2225
4728
|
}
|
|
2226
4729
|
simulatorCurrentRunId = undefined;
|
|
2227
4730
|
const stream = payload.stream ?? true;
|
|
2228
4731
|
const forwardTrace = payload.trace ?? true;
|
|
4732
|
+
const pendingTraceEvents = [];
|
|
4733
|
+
const flushPendingTraceEvents = (state) => {
|
|
4734
|
+
if (!pendingTraceEvents.length)
|
|
4735
|
+
return;
|
|
4736
|
+
for (const pending of pendingTraceEvents) {
|
|
4737
|
+
appendSessionEvent(state, {
|
|
4738
|
+
...pending,
|
|
4739
|
+
kind: "trace",
|
|
4740
|
+
category: traceCategory(pending.type),
|
|
4741
|
+
});
|
|
4742
|
+
}
|
|
4743
|
+
pendingTraceEvents.length = 0;
|
|
4744
|
+
};
|
|
2229
4745
|
const tracer = (event) => {
|
|
2230
4746
|
const stamped = event.ts ? event : { ...event, ts: Date.now() };
|
|
2231
4747
|
if (stamped.type === "run.start") {
|
|
@@ -2235,6 +4751,16 @@ function startWebSocketSimulator(opts) {
|
|
|
2235
4751
|
consoleTracer?.(stamped);
|
|
2236
4752
|
if (forwardTrace)
|
|
2237
4753
|
emitSimulator({ type: "trace", event: stamped });
|
|
4754
|
+
if (simulatorSavedState?.meta?.sessionId) {
|
|
4755
|
+
appendSessionEvent(simulatorSavedState, {
|
|
4756
|
+
...stamped,
|
|
4757
|
+
kind: "trace",
|
|
4758
|
+
category: traceCategory(stamped.type),
|
|
4759
|
+
});
|
|
4760
|
+
}
|
|
4761
|
+
else {
|
|
4762
|
+
pendingTraceEvents.push(stamped);
|
|
4763
|
+
}
|
|
2238
4764
|
};
|
|
2239
4765
|
let initialUserMessage = typeof payload.message === "string"
|
|
2240
4766
|
? payload.message
|
|
@@ -2281,6 +4807,7 @@ function startWebSocketSimulator(opts) {
|
|
|
2281
4807
|
traces: simulatorCapturedTraces,
|
|
2282
4808
|
});
|
|
2283
4809
|
simulatorSavedState = enrichedState;
|
|
4810
|
+
flushPendingTraceEvents(enrichedState);
|
|
2284
4811
|
emitSimulator({ type: "state", state: enrichedState });
|
|
2285
4812
|
},
|
|
2286
4813
|
initialUserMessage,
|
|
@@ -2298,7 +4825,7 @@ function startWebSocketSimulator(opts) {
|
|
|
2298
4825
|
});
|
|
2299
4826
|
return new Response(JSON.stringify({
|
|
2300
4827
|
runId: simulatorCurrentRunId,
|
|
2301
|
-
|
|
4828
|
+
workspaceId: simulatorSavedState?.meta?.workspaceId,
|
|
2302
4829
|
}), { headers: { "content-type": "application/json" } });
|
|
2303
4830
|
}
|
|
2304
4831
|
catch (err) {
|
|
@@ -2320,56 +4847,112 @@ function startWebSocketSimulator(opts) {
|
|
|
2320
4847
|
}
|
|
2321
4848
|
try {
|
|
2322
4849
|
const body = await req.json();
|
|
2323
|
-
|
|
2324
|
-
|
|
4850
|
+
const workspaceId = getWorkspaceIdFromBody(body);
|
|
4851
|
+
if (!workspaceId) {
|
|
4852
|
+
throw new Error("Missing workspaceId");
|
|
2325
4853
|
}
|
|
2326
4854
|
if (!body.messageRefId) {
|
|
2327
4855
|
throw new Error("Missing messageRefId");
|
|
2328
4856
|
}
|
|
2329
|
-
if (
|
|
4857
|
+
if (body.score !== null &&
|
|
4858
|
+
(typeof body.score !== "number" || Number.isNaN(body.score))) {
|
|
2330
4859
|
throw new Error("Invalid score");
|
|
2331
4860
|
}
|
|
2332
|
-
|
|
4861
|
+
let state;
|
|
4862
|
+
try {
|
|
4863
|
+
state = readSessionStateStrict(workspaceId, { withTraces: true });
|
|
4864
|
+
}
|
|
4865
|
+
catch (err) {
|
|
4866
|
+
throw new Error(err instanceof Error ? err.message : String(err));
|
|
4867
|
+
}
|
|
2333
4868
|
if (!state)
|
|
2334
|
-
throw new Error("
|
|
4869
|
+
throw new Error("Workspace not found");
|
|
4870
|
+
const requestedRunId = typeof body.runId === "string" &&
|
|
4871
|
+
body.runId.trim().length > 0
|
|
4872
|
+
? body.runId.trim()
|
|
4873
|
+
: undefined;
|
|
4874
|
+
const feedbackEligible = isFeedbackEligibleMessageRef(state, body.messageRefId) ||
|
|
4875
|
+
(requestedRunId
|
|
4876
|
+
? isFeedbackEligiblePersistedTestRunMessageRef(state, requestedRunId, body.messageRefId)
|
|
4877
|
+
: false);
|
|
4878
|
+
if (!feedbackEligible) {
|
|
4879
|
+
throw new Error("Feedback target is not eligible");
|
|
4880
|
+
}
|
|
2335
4881
|
simulatorSavedState = state;
|
|
2336
4882
|
simulatorCapturedTraces = Array.isArray(state.traces)
|
|
2337
4883
|
? cloneTraces(state.traces)
|
|
2338
4884
|
: [];
|
|
2339
|
-
const clamped = Math.max(-3, Math.min(3, Math.round(body.score)));
|
|
2340
|
-
const reason = typeof body.reason === "string"
|
|
2341
|
-
? body.reason
|
|
2342
|
-
: undefined;
|
|
2343
|
-
const runId = typeof state.runId === "string" ? state.runId : "run";
|
|
2344
4885
|
const existing = state.feedback ?? [];
|
|
2345
4886
|
const idx = existing.findIndex((f) => f.messageRefId === body.messageRefId);
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
4887
|
+
let entry;
|
|
4888
|
+
let feedback = existing;
|
|
4889
|
+
let deleted = false;
|
|
4890
|
+
if (body.score === null) {
|
|
4891
|
+
if (idx >= 0) {
|
|
4892
|
+
feedback = existing.filter((_, i) => i !== idx);
|
|
4893
|
+
deleted = true;
|
|
2353
4894
|
}
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
4895
|
+
}
|
|
4896
|
+
else {
|
|
4897
|
+
const clamped = Math.max(-3, Math.min(3, Math.round(body.score)));
|
|
4898
|
+
const reason = typeof body.reason === "string"
|
|
4899
|
+
? body.reason
|
|
4900
|
+
: undefined;
|
|
4901
|
+
const runId = requestedRunId ??
|
|
4902
|
+
(typeof state.runId === "string" ? state.runId : "run");
|
|
4903
|
+
const scenarioRunId = typeof state.meta?.scenarioRunId === "string"
|
|
4904
|
+
? state.meta.scenarioRunId
|
|
4905
|
+
: runId;
|
|
4906
|
+
const now = new Date().toISOString();
|
|
4907
|
+
entry = idx >= 0
|
|
4908
|
+
? {
|
|
4909
|
+
...existing[idx],
|
|
4910
|
+
score: clamped,
|
|
4911
|
+
reason,
|
|
4912
|
+
runId: existing[idx].runId ?? runId,
|
|
4913
|
+
}
|
|
4914
|
+
: {
|
|
4915
|
+
id: (0, server_helpers_js_1.randomId)("fb"),
|
|
4916
|
+
runId,
|
|
4917
|
+
messageRefId: body.messageRefId,
|
|
4918
|
+
score: clamped,
|
|
4919
|
+
reason,
|
|
4920
|
+
createdAt: now,
|
|
4921
|
+
};
|
|
4922
|
+
if (entry) {
|
|
4923
|
+
entry.workspaceId = workspaceId;
|
|
4924
|
+
entry.scenarioRunId = scenarioRunId;
|
|
4925
|
+
}
|
|
4926
|
+
feedback = idx >= 0
|
|
4927
|
+
? existing.map((f, i) => i === idx ? entry : f)
|
|
4928
|
+
: [...existing, entry];
|
|
4929
|
+
}
|
|
2365
4930
|
const enriched = persistSessionState({
|
|
2366
4931
|
...state,
|
|
2367
4932
|
feedback,
|
|
2368
4933
|
traces: simulatorCapturedTraces,
|
|
2369
4934
|
});
|
|
4935
|
+
appendFeedbackLog(enriched, {
|
|
4936
|
+
type: "feedback.update",
|
|
4937
|
+
messageRefId: body.messageRefId,
|
|
4938
|
+
feedback: entry,
|
|
4939
|
+
deleted,
|
|
4940
|
+
});
|
|
4941
|
+
appendSessionEvent(enriched, {
|
|
4942
|
+
type: "feedback.update",
|
|
4943
|
+
kind: "artifact",
|
|
4944
|
+
category: "feedback",
|
|
4945
|
+
workspaceId,
|
|
4946
|
+
scenarioRunId: typeof enriched.meta?.scenarioRunId === "string"
|
|
4947
|
+
? enriched.meta.scenarioRunId
|
|
4948
|
+
: enriched.runId,
|
|
4949
|
+
messageRefId: body.messageRefId,
|
|
4950
|
+
feedback: entry,
|
|
4951
|
+
deleted,
|
|
4952
|
+
});
|
|
2370
4953
|
simulatorSavedState = enriched;
|
|
2371
4954
|
emitSimulator({ type: "state", state: enriched });
|
|
2372
|
-
return new Response(JSON.stringify({ feedback: entry }), { headers: { "content-type": "application/json" } });
|
|
4955
|
+
return new Response(JSON.stringify({ feedback: entry, deleted }), { headers: { "content-type": "application/json" } });
|
|
2373
4956
|
}
|
|
2374
4957
|
catch (err) {
|
|
2375
4958
|
return new Response(JSON.stringify({
|
|
@@ -2383,12 +4966,19 @@ function startWebSocketSimulator(opts) {
|
|
|
2383
4966
|
}
|
|
2384
4967
|
try {
|
|
2385
4968
|
const body = await req.json();
|
|
2386
|
-
|
|
2387
|
-
|
|
4969
|
+
const workspaceId = getWorkspaceIdFromBody(body);
|
|
4970
|
+
if (!workspaceId) {
|
|
4971
|
+
throw new Error("Missing workspaceId");
|
|
4972
|
+
}
|
|
4973
|
+
let state;
|
|
4974
|
+
try {
|
|
4975
|
+
state = readSessionStateStrict(workspaceId, { withTraces: true });
|
|
4976
|
+
}
|
|
4977
|
+
catch (err) {
|
|
4978
|
+
throw new Error(err instanceof Error ? err.message : String(err));
|
|
2388
4979
|
}
|
|
2389
|
-
const state = readSessionState(body.sessionId);
|
|
2390
4980
|
if (!state)
|
|
2391
|
-
throw new Error("
|
|
4981
|
+
throw new Error("Workspace not found");
|
|
2392
4982
|
simulatorSavedState = state;
|
|
2393
4983
|
simulatorCapturedTraces = Array.isArray(state.traces)
|
|
2394
4984
|
? cloneTraces(state.traces)
|
|
@@ -2399,6 +4989,13 @@ function startWebSocketSimulator(opts) {
|
|
|
2399
4989
|
notes: { text: body.text ?? "", updatedAt: now },
|
|
2400
4990
|
traces: simulatorCapturedTraces,
|
|
2401
4991
|
});
|
|
4992
|
+
appendSessionEvent(enriched, {
|
|
4993
|
+
type: "notes.update",
|
|
4994
|
+
kind: "artifact",
|
|
4995
|
+
category: "notes",
|
|
4996
|
+
workspaceId,
|
|
4997
|
+
notes: enriched.notes,
|
|
4998
|
+
});
|
|
2402
4999
|
simulatorSavedState = enriched;
|
|
2403
5000
|
emitSimulator({ type: "state", state: enriched });
|
|
2404
5001
|
return new Response(JSON.stringify({ notes: enriched.notes, saved: true }), { headers: { "content-type": "application/json" } });
|
|
@@ -2415,15 +5012,22 @@ function startWebSocketSimulator(opts) {
|
|
|
2415
5012
|
}
|
|
2416
5013
|
try {
|
|
2417
5014
|
const body = await req.json();
|
|
2418
|
-
|
|
2419
|
-
|
|
5015
|
+
const workspaceId = getWorkspaceIdFromBody(body);
|
|
5016
|
+
if (!workspaceId) {
|
|
5017
|
+
throw new Error("Missing workspaceId");
|
|
2420
5018
|
}
|
|
2421
5019
|
if (typeof body.score !== "number" || Number.isNaN(body.score)) {
|
|
2422
5020
|
throw new Error("Invalid score");
|
|
2423
5021
|
}
|
|
2424
|
-
|
|
5022
|
+
let state;
|
|
5023
|
+
try {
|
|
5024
|
+
state = readSessionStateStrict(workspaceId, { withTraces: true });
|
|
5025
|
+
}
|
|
5026
|
+
catch (err) {
|
|
5027
|
+
throw new Error(err instanceof Error ? err.message : String(err));
|
|
5028
|
+
}
|
|
2425
5029
|
if (!state)
|
|
2426
|
-
throw new Error("
|
|
5030
|
+
throw new Error("Workspace not found");
|
|
2427
5031
|
simulatorSavedState = state;
|
|
2428
5032
|
simulatorCapturedTraces = Array.isArray(state.traces)
|
|
2429
5033
|
? cloneTraces(state.traces)
|
|
@@ -2435,6 +5039,13 @@ function startWebSocketSimulator(opts) {
|
|
|
2435
5039
|
conversationScore: { score: clamped, updatedAt: now },
|
|
2436
5040
|
traces: simulatorCapturedTraces,
|
|
2437
5041
|
});
|
|
5042
|
+
appendSessionEvent(enriched, {
|
|
5043
|
+
type: "conversation.score.update",
|
|
5044
|
+
kind: "artifact",
|
|
5045
|
+
category: "score",
|
|
5046
|
+
workspaceId,
|
|
5047
|
+
conversationScore: enriched.conversationScore,
|
|
5048
|
+
});
|
|
2438
5049
|
simulatorSavedState = enriched;
|
|
2439
5050
|
emitSimulator({ type: "state", state: enriched });
|
|
2440
5051
|
return new Response(JSON.stringify({
|
|
@@ -2454,12 +5065,19 @@ function startWebSocketSimulator(opts) {
|
|
|
2454
5065
|
}
|
|
2455
5066
|
try {
|
|
2456
5067
|
const body = await req.json();
|
|
2457
|
-
|
|
2458
|
-
|
|
5068
|
+
const workspaceId = getWorkspaceIdFromBody(body);
|
|
5069
|
+
if (!workspaceId) {
|
|
5070
|
+
throw new Error("Missing workspaceId");
|
|
5071
|
+
}
|
|
5072
|
+
let state;
|
|
5073
|
+
try {
|
|
5074
|
+
state = readSessionStateStrict(workspaceId, { withTraces: true });
|
|
5075
|
+
}
|
|
5076
|
+
catch (err) {
|
|
5077
|
+
throw new Error(err instanceof Error ? err.message : String(err));
|
|
2459
5078
|
}
|
|
2460
|
-
const state = readSessionState(body.sessionId);
|
|
2461
5079
|
if (!state) {
|
|
2462
|
-
throw new Error("
|
|
5080
|
+
throw new Error("Workspace not found");
|
|
2463
5081
|
}
|
|
2464
5082
|
simulatorSavedState = state;
|
|
2465
5083
|
simulatorCapturedTraces = Array.isArray(state.traces)
|
|
@@ -2474,48 +5092,34 @@ function startWebSocketSimulator(opts) {
|
|
|
2474
5092
|
}), { status: 400, headers: { "content-type": "application/json" } });
|
|
2475
5093
|
}
|
|
2476
5094
|
}
|
|
2477
|
-
if (url.pathname === "/api/session") {
|
|
2478
|
-
if (req.method !== "GET") {
|
|
2479
|
-
return new Response("Method not allowed", { status: 405 });
|
|
2480
|
-
}
|
|
2481
|
-
const sessionId = url.searchParams.get("sessionId");
|
|
2482
|
-
if (!sessionId) {
|
|
2483
|
-
return new Response(JSON.stringify({ error: "Missing sessionId" }), { status: 400, headers: { "content-type": "application/json" } });
|
|
2484
|
-
}
|
|
2485
|
-
const state = readSessionState(sessionId);
|
|
2486
|
-
if (!state) {
|
|
2487
|
-
return new Response(JSON.stringify({ error: "Session not found" }), { status: 404, headers: { "content-type": "application/json" } });
|
|
2488
|
-
}
|
|
2489
|
-
return new Response(JSON.stringify({
|
|
2490
|
-
sessionId,
|
|
2491
|
-
messages: state.messages,
|
|
2492
|
-
messageRefs: state.messageRefs,
|
|
2493
|
-
feedback: state.feedback,
|
|
2494
|
-
traces: state.traces,
|
|
2495
|
-
notes: state.notes,
|
|
2496
|
-
meta: state.meta,
|
|
2497
|
-
}), { headers: { "content-type": "application/json" } });
|
|
2498
|
-
}
|
|
2499
5095
|
if (url.pathname === "/api/session/notes") {
|
|
2500
5096
|
if (req.method !== "POST") {
|
|
2501
5097
|
return new Response("Method not allowed", { status: 405 });
|
|
2502
5098
|
}
|
|
2503
5099
|
try {
|
|
2504
5100
|
const body = await req.json();
|
|
2505
|
-
|
|
2506
|
-
|
|
5101
|
+
const workspaceId = getWorkspaceIdFromBody(body);
|
|
5102
|
+
if (!workspaceId) {
|
|
5103
|
+
throw new Error("Missing workspaceId");
|
|
2507
5104
|
}
|
|
2508
|
-
const state = readSessionState(
|
|
5105
|
+
const state = readSessionState(workspaceId);
|
|
2509
5106
|
if (!state) {
|
|
2510
|
-
throw new Error("
|
|
5107
|
+
throw new Error("Workspace not found");
|
|
2511
5108
|
}
|
|
2512
5109
|
const now = new Date().toISOString();
|
|
2513
5110
|
const nextState = persistSessionState({
|
|
2514
5111
|
...state,
|
|
2515
5112
|
notes: { text: body.text ?? "", updatedAt: now },
|
|
2516
5113
|
});
|
|
5114
|
+
appendSessionEvent(nextState, {
|
|
5115
|
+
type: "notes.update",
|
|
5116
|
+
kind: "artifact",
|
|
5117
|
+
category: "notes",
|
|
5118
|
+
workspaceId,
|
|
5119
|
+
notes: nextState.notes,
|
|
5120
|
+
});
|
|
2517
5121
|
return new Response(JSON.stringify({
|
|
2518
|
-
|
|
5122
|
+
workspaceId,
|
|
2519
5123
|
notes: nextState.notes,
|
|
2520
5124
|
saved: true,
|
|
2521
5125
|
}), { headers: { "content-type": "application/json" } });
|
|
@@ -2532,51 +5136,100 @@ function startWebSocketSimulator(opts) {
|
|
|
2532
5136
|
}
|
|
2533
5137
|
try {
|
|
2534
5138
|
const body = await req.json();
|
|
2535
|
-
|
|
2536
|
-
|
|
5139
|
+
const workspaceId = getWorkspaceIdFromBody(body);
|
|
5140
|
+
if (!workspaceId) {
|
|
5141
|
+
throw new Error("Missing workspaceId");
|
|
2537
5142
|
}
|
|
2538
5143
|
if (!body.messageRefId) {
|
|
2539
5144
|
throw new Error("Missing messageRefId");
|
|
2540
5145
|
}
|
|
2541
|
-
if (
|
|
5146
|
+
if (body.score !== null &&
|
|
5147
|
+
(typeof body.score !== "number" || Number.isNaN(body.score))) {
|
|
2542
5148
|
throw new Error("Invalid score");
|
|
2543
5149
|
}
|
|
2544
|
-
const state = readSessionState(
|
|
5150
|
+
const state = readSessionState(workspaceId);
|
|
2545
5151
|
if (!state) {
|
|
2546
|
-
throw new Error("
|
|
5152
|
+
throw new Error("Workspace not found");
|
|
2547
5153
|
}
|
|
2548
|
-
const
|
|
2549
|
-
|
|
2550
|
-
? body.
|
|
5154
|
+
const requestedRunId = typeof body.runId === "string" &&
|
|
5155
|
+
body.runId.trim().length > 0
|
|
5156
|
+
? body.runId.trim()
|
|
2551
5157
|
: undefined;
|
|
2552
|
-
const
|
|
2553
|
-
|
|
2554
|
-
|
|
5158
|
+
const feedbackEligible = isFeedbackEligibleMessageRef(state, body.messageRefId) ||
|
|
5159
|
+
(requestedRunId
|
|
5160
|
+
? isFeedbackEligiblePersistedTestRunMessageRef(state, requestedRunId, body.messageRefId)
|
|
5161
|
+
: false);
|
|
5162
|
+
if (!feedbackEligible) {
|
|
5163
|
+
throw new Error("Feedback target is not eligible");
|
|
5164
|
+
}
|
|
2555
5165
|
const existing = state.feedback ?? [];
|
|
2556
5166
|
const idx = existing.findIndex((entry) => entry.messageRefId === body.messageRefId);
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
5167
|
+
let entry;
|
|
5168
|
+
let feedback = existing;
|
|
5169
|
+
let deleted = false;
|
|
5170
|
+
if (body.score === null) {
|
|
5171
|
+
if (idx >= 0) {
|
|
5172
|
+
feedback = existing.filter((_, i) => i !== idx);
|
|
5173
|
+
deleted = true;
|
|
2564
5174
|
}
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
5175
|
+
}
|
|
5176
|
+
else {
|
|
5177
|
+
const clamped = Math.max(-3, Math.min(3, Math.round(body.score)));
|
|
5178
|
+
const reason = typeof body.reason === "string"
|
|
5179
|
+
? body.reason
|
|
5180
|
+
: undefined;
|
|
5181
|
+
const runId = requestedRunId ??
|
|
5182
|
+
(typeof state.runId === "string" ? state.runId : "session");
|
|
5183
|
+
const scenarioRunId = requestedRunId ??
|
|
5184
|
+
(typeof state.meta?.scenarioRunId === "string"
|
|
5185
|
+
? state.meta.scenarioRunId
|
|
5186
|
+
: runId);
|
|
5187
|
+
const now = new Date().toISOString();
|
|
5188
|
+
entry = idx >= 0
|
|
5189
|
+
? {
|
|
5190
|
+
...existing[idx],
|
|
5191
|
+
score: clamped,
|
|
5192
|
+
reason,
|
|
5193
|
+
runId: existing[idx].runId ?? runId,
|
|
5194
|
+
}
|
|
5195
|
+
: {
|
|
5196
|
+
id: (0, server_helpers_js_1.randomId)("fb"),
|
|
5197
|
+
runId,
|
|
5198
|
+
messageRefId: body.messageRefId,
|
|
5199
|
+
score: clamped,
|
|
5200
|
+
reason,
|
|
5201
|
+
createdAt: now,
|
|
5202
|
+
};
|
|
5203
|
+
if (entry) {
|
|
5204
|
+
entry.workspaceId = workspaceId;
|
|
5205
|
+
entry.scenarioRunId = scenarioRunId;
|
|
5206
|
+
}
|
|
5207
|
+
feedback = idx >= 0
|
|
5208
|
+
? existing.map((item, i) => i === idx ? entry : item)
|
|
5209
|
+
: [...existing, entry];
|
|
5210
|
+
}
|
|
2576
5211
|
const nextState = persistSessionState({
|
|
2577
5212
|
...state,
|
|
2578
5213
|
feedback,
|
|
2579
5214
|
});
|
|
5215
|
+
appendFeedbackLog(nextState, {
|
|
5216
|
+
type: "feedback.update",
|
|
5217
|
+
messageRefId: body.messageRefId,
|
|
5218
|
+
feedback: entry,
|
|
5219
|
+
deleted,
|
|
5220
|
+
});
|
|
5221
|
+
appendSessionEvent(nextState, {
|
|
5222
|
+
type: "feedback.update",
|
|
5223
|
+
kind: "artifact",
|
|
5224
|
+
category: "feedback",
|
|
5225
|
+
workspaceId,
|
|
5226
|
+
scenarioRunId: typeof nextState.meta?.scenarioRunId === "string"
|
|
5227
|
+
? nextState.meta.scenarioRunId
|
|
5228
|
+
: nextState.runId,
|
|
5229
|
+
messageRefId: body.messageRefId,
|
|
5230
|
+
feedback: entry,
|
|
5231
|
+
deleted,
|
|
5232
|
+
});
|
|
2580
5233
|
const testBotRunId = typeof nextState.meta?.testBotRunId === "string"
|
|
2581
5234
|
? nextState.meta.testBotRunId
|
|
2582
5235
|
: undefined;
|
|
@@ -2584,13 +5237,14 @@ function startWebSocketSimulator(opts) {
|
|
|
2584
5237
|
const testEntry = testBotRuns.get(testBotRunId);
|
|
2585
5238
|
if (testEntry) {
|
|
2586
5239
|
syncTestBotRunFromState(testEntry.run, nextState);
|
|
2587
|
-
broadcastTestBot({ type: "testBotStatus", run: testEntry.run });
|
|
5240
|
+
broadcastTestBot({ type: "testBotStatus", run: testEntry.run }, workspaceId);
|
|
2588
5241
|
}
|
|
2589
5242
|
}
|
|
2590
5243
|
return new Response(JSON.stringify({
|
|
2591
|
-
|
|
5244
|
+
workspaceId,
|
|
2592
5245
|
feedback: entry,
|
|
2593
|
-
saved:
|
|
5246
|
+
saved: !deleted,
|
|
5247
|
+
deleted,
|
|
2594
5248
|
}), { headers: { "content-type": "application/json" } });
|
|
2595
5249
|
}
|
|
2596
5250
|
catch (err) {
|
|
@@ -2605,118 +5259,17 @@ function startWebSocketSimulator(opts) {
|
|
|
2605
5259
|
}
|
|
2606
5260
|
try {
|
|
2607
5261
|
const body = await req.json();
|
|
2608
|
-
|
|
2609
|
-
|
|
5262
|
+
const workspaceId = getWorkspaceIdFromBody(body);
|
|
5263
|
+
if (!workspaceId) {
|
|
5264
|
+
throw new Error("Missing workspaceId");
|
|
2610
5265
|
}
|
|
2611
|
-
const removed = deleteSessionState(
|
|
5266
|
+
const removed = deleteSessionState(workspaceId);
|
|
2612
5267
|
if (!removed) {
|
|
2613
|
-
return new Response(JSON.stringify({ error: "
|
|
2614
|
-
}
|
|
2615
|
-
return new Response(JSON.stringify({ sessionId: body.sessionId, deleted: true }), { headers: { "content-type": "application/json" } });
|
|
2616
|
-
}
|
|
2617
|
-
catch (err) {
|
|
2618
|
-
return new Response(JSON.stringify({
|
|
2619
|
-
error: err instanceof Error ? err.message : String(err),
|
|
2620
|
-
}), { status: 400, headers: { "content-type": "application/json" } });
|
|
2621
|
-
}
|
|
2622
|
-
}
|
|
2623
|
-
if (url.pathname === "/api/feedback") {
|
|
2624
|
-
if (req.method !== "GET") {
|
|
2625
|
-
return new Response("Method not allowed", { status: 405 });
|
|
2626
|
-
}
|
|
2627
|
-
const deckPathParam = url.searchParams.get("deckPath");
|
|
2628
|
-
if (!deckPathParam) {
|
|
2629
|
-
return new Response(JSON.stringify({ error: "Missing deckPath" }), { status: 400, headers: { "content-type": "application/json" } });
|
|
2630
|
-
}
|
|
2631
|
-
const items = [];
|
|
2632
|
-
try {
|
|
2633
|
-
for await (const entry of dntShim.Deno.readDir(sessionsRoot)) {
|
|
2634
|
-
if (!entry.isDirectory)
|
|
2635
|
-
continue;
|
|
2636
|
-
const sessionId = entry.name;
|
|
2637
|
-
const state = readSessionState(sessionId);
|
|
2638
|
-
if (!state)
|
|
2639
|
-
continue;
|
|
2640
|
-
if (state.meta?.deck !== deckPathParam)
|
|
2641
|
-
continue;
|
|
2642
|
-
const feedbackList = Array.isArray(state.feedback)
|
|
2643
|
-
? state.feedback
|
|
2644
|
-
: [];
|
|
2645
|
-
feedbackList.forEach((fb) => {
|
|
2646
|
-
if (!fb || typeof fb !== "object")
|
|
2647
|
-
return;
|
|
2648
|
-
const messageRefId = fb
|
|
2649
|
-
.messageRefId;
|
|
2650
|
-
if (typeof messageRefId !== "string")
|
|
2651
|
-
return;
|
|
2652
|
-
let messageContent = undefined;
|
|
2653
|
-
if (Array.isArray(state.messageRefs) &&
|
|
2654
|
-
Array.isArray(state.messages)) {
|
|
2655
|
-
const idx = state.messageRefs.findIndex((ref) => ref?.id === messageRefId);
|
|
2656
|
-
if (idx >= 0) {
|
|
2657
|
-
messageContent = state.messages[idx]?.content;
|
|
2658
|
-
}
|
|
2659
|
-
}
|
|
2660
|
-
items.push({
|
|
2661
|
-
sessionId,
|
|
2662
|
-
deck: state.meta?.deck,
|
|
2663
|
-
sessionCreatedAt: state.meta?.sessionCreatedAt,
|
|
2664
|
-
messageRefId,
|
|
2665
|
-
score: fb.score,
|
|
2666
|
-
reason: fb.reason,
|
|
2667
|
-
createdAt: fb.createdAt,
|
|
2668
|
-
archivedAt: fb.archivedAt,
|
|
2669
|
-
messageContent,
|
|
2670
|
-
});
|
|
2671
|
-
});
|
|
2672
|
-
}
|
|
2673
|
-
}
|
|
2674
|
-
catch (err) {
|
|
2675
|
-
return new Response(JSON.stringify({
|
|
2676
|
-
error: err instanceof Error ? err.message : String(err),
|
|
2677
|
-
}), { status: 400, headers: { "content-type": "application/json" } });
|
|
2678
|
-
}
|
|
2679
|
-
items.sort((a, b) => {
|
|
2680
|
-
const aTime = String(a.createdAt ?? "") || "";
|
|
2681
|
-
const bTime = String(b.createdAt ?? "") || "";
|
|
2682
|
-
return bTime.localeCompare(aTime);
|
|
2683
|
-
});
|
|
2684
|
-
return new Response(JSON.stringify({ deckPath: deckPathParam, items }), {
|
|
2685
|
-
headers: { "content-type": "application/json" },
|
|
2686
|
-
});
|
|
2687
|
-
}
|
|
2688
|
-
if (url.pathname === "/api/feedback/archive" && req.method === "POST") {
|
|
2689
|
-
try {
|
|
2690
|
-
const body = await req.json();
|
|
2691
|
-
if (!body.sessionId || !body.messageRefId) {
|
|
2692
|
-
throw new Error("Missing sessionId or messageRefId");
|
|
2693
|
-
}
|
|
2694
|
-
const state = readSessionState(body.sessionId);
|
|
2695
|
-
if (!state || !Array.isArray(state.feedback)) {
|
|
2696
|
-
throw new Error("Session not found");
|
|
2697
|
-
}
|
|
2698
|
-
const idx = state.feedback.findIndex((fb) => fb.messageRefId === body.messageRefId);
|
|
2699
|
-
if (idx === -1)
|
|
2700
|
-
throw new Error("Feedback not found");
|
|
2701
|
-
const next = { ...state.feedback[idx] };
|
|
2702
|
-
if (body.archived === false) {
|
|
2703
|
-
delete next.archivedAt;
|
|
2704
|
-
}
|
|
2705
|
-
else {
|
|
2706
|
-
next.archivedAt = new Date()
|
|
2707
|
-
.toISOString();
|
|
5268
|
+
return new Response(JSON.stringify({ error: "Workspace not found" }), { status: 404, headers: { "content-type": "application/json" } });
|
|
2708
5269
|
}
|
|
2709
|
-
const nextFeedback = state.feedback.map((fb, i) => i === idx ? next : fb);
|
|
2710
|
-
const updated = persistSessionState({
|
|
2711
|
-
...state,
|
|
2712
|
-
feedback: nextFeedback,
|
|
2713
|
-
});
|
|
2714
5270
|
return new Response(JSON.stringify({
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
archivedAt: next.archivedAt,
|
|
2718
|
-
saved: true,
|
|
2719
|
-
feedbackCount: updated.feedback?.length ?? 0,
|
|
5271
|
+
workspaceId,
|
|
5272
|
+
deleted: true,
|
|
2720
5273
|
}), { headers: { "content-type": "application/json" } });
|
|
2721
5274
|
}
|
|
2722
5275
|
catch (err) {
|
|
@@ -2725,102 +5278,42 @@ function startWebSocketSimulator(opts) {
|
|
|
2725
5278
|
}), { status: 400, headers: { "content-type": "application/json" } });
|
|
2726
5279
|
}
|
|
2727
5280
|
}
|
|
2728
|
-
|
|
2729
|
-
url
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
try {
|
|
2765
|
-
const content = await dntShim.Deno.readTextFile(resolvedDeckPath);
|
|
2766
|
-
return new Response(JSON.stringify({
|
|
2767
|
-
path: resolvedDeckPath,
|
|
2768
|
-
content,
|
|
2769
|
-
}), { headers: { "content-type": "application/json; charset=utf-8" } });
|
|
2770
|
-
}
|
|
2771
|
-
catch (err) {
|
|
2772
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
2773
|
-
return new Response(JSON.stringify({
|
|
2774
|
-
path: resolvedDeckPath,
|
|
2775
|
-
error: message,
|
|
2776
|
-
}), {
|
|
2777
|
-
status: 500,
|
|
2778
|
-
headers: { "content-type": "application/json; charset=utf-8" },
|
|
2779
|
-
});
|
|
2780
|
-
}
|
|
2781
|
-
}
|
|
2782
|
-
if (url.pathname === "/ui/bundle.js") {
|
|
2783
|
-
const data = await readReactBundle();
|
|
2784
|
-
if (!data) {
|
|
2785
|
-
return new Response("Bundle missing. Run `deno task bundle:sim` (or start with `--bundle`).", { status: 404 });
|
|
2786
|
-
}
|
|
2787
|
-
try {
|
|
2788
|
-
const headers = new Headers({
|
|
2789
|
-
"content-type": "application/javascript; charset=utf-8",
|
|
2790
|
-
});
|
|
2791
|
-
// Hint the browser about the external source map since Deno's bundle
|
|
2792
|
-
// output does not embed a sourceMappingURL comment.
|
|
2793
|
-
if (shouldAdvertiseSourceMap()) {
|
|
2794
|
-
headers.set("SourceMap", "/ui/bundle.js.map");
|
|
2795
|
-
}
|
|
2796
|
-
return new Response(data, { headers });
|
|
2797
|
-
}
|
|
2798
|
-
catch (err) {
|
|
2799
|
-
return new Response(`Failed to read bundle: ${err instanceof Error ? err.message : String(err)}`, { status: 500 });
|
|
2800
|
-
}
|
|
2801
|
-
}
|
|
2802
|
-
if (url.pathname === "/ui/bundle.js.map") {
|
|
2803
|
-
const data = await readReactBundleSourceMap();
|
|
2804
|
-
if (!data) {
|
|
2805
|
-
return new Response("Source map missing. Run `deno task bundle:sim:sourcemap` (or start with `--bundle --sourcemap`).", { status: 404 });
|
|
2806
|
-
}
|
|
2807
|
-
try {
|
|
2808
|
-
return new Response(data, {
|
|
2809
|
-
headers: {
|
|
2810
|
-
"content-type": "application/json; charset=utf-8",
|
|
2811
|
-
},
|
|
2812
|
-
});
|
|
2813
|
-
}
|
|
2814
|
-
catch (err) {
|
|
2815
|
-
return new Response(`Failed to read source map: ${err instanceof Error ? err.message : String(err)}`, { status: 500 });
|
|
2816
|
-
}
|
|
2817
|
-
}
|
|
2818
|
-
if (url.pathname === "/sessions") {
|
|
2819
|
-
const sessions = listSessions();
|
|
2820
|
-
return new Response(JSON.stringify({ sessions }), {
|
|
2821
|
-
headers: { "content-type": "application/json; charset=utf-8" },
|
|
2822
|
-
});
|
|
2823
|
-
}
|
|
5281
|
+
const feedbackResponse = await (0, server_feedback_grading_routes_js_1.handleFeedbackRoutes)({
|
|
5282
|
+
url,
|
|
5283
|
+
req,
|
|
5284
|
+
sessionsRoot,
|
|
5285
|
+
getWorkspaceIdFromBody,
|
|
5286
|
+
readSessionState,
|
|
5287
|
+
persistSessionState,
|
|
5288
|
+
appendFeedbackLog,
|
|
5289
|
+
appendSessionEvent,
|
|
5290
|
+
});
|
|
5291
|
+
if (feedbackResponse)
|
|
5292
|
+
return feedbackResponse;
|
|
5293
|
+
const uiRoutesResponse = await (0, server_ui_routes_js_1.handleUiRoutes)({
|
|
5294
|
+
url,
|
|
5295
|
+
req,
|
|
5296
|
+
workspaceRouteBase: workspace_contract_js_1.WORKSPACE_ROUTE_BASE,
|
|
5297
|
+
activeWorkspaceId,
|
|
5298
|
+
activeWorkspaceOnboarding,
|
|
5299
|
+
resolvedDeckPath,
|
|
5300
|
+
deckLabel,
|
|
5301
|
+
getWorkspaceIdFromQuery,
|
|
5302
|
+
activateWorkspaceDeck,
|
|
5303
|
+
schemaPromise,
|
|
5304
|
+
deckLoadPromise,
|
|
5305
|
+
canServeReactBundle,
|
|
5306
|
+
simulatorReactHtml,
|
|
5307
|
+
toDeckLabel,
|
|
5308
|
+
readReactBundle,
|
|
5309
|
+
shouldAdvertiseSourceMap,
|
|
5310
|
+
readReactBundleSourceMap,
|
|
5311
|
+
listSessions,
|
|
5312
|
+
createWorkspaceSession,
|
|
5313
|
+
workspaceStateSchemaVersion: workspace_contract_js_1.WORKSPACE_STATE_SCHEMA_VERSION,
|
|
5314
|
+
});
|
|
5315
|
+
if (uiRoutesResponse)
|
|
5316
|
+
return uiRoutesResponse;
|
|
2824
5317
|
return new Response("Not found", { status: 404 });
|
|
2825
5318
|
});
|
|
2826
5319
|
const listenPort = server.addr.port;
|
|
@@ -2955,9 +5448,36 @@ async function readRemoteBundle(url, kind) {
|
|
|
2955
5448
|
return null;
|
|
2956
5449
|
}
|
|
2957
5450
|
}
|
|
2958
|
-
function simulatorReactHtml(deckPath, deckLabel) {
|
|
5451
|
+
function simulatorReactHtml(deckPath, deckLabel, opts) {
|
|
2959
5452
|
const safeDeckPath = deckPath.replaceAll("<", "<").replaceAll(">", ">");
|
|
2960
5453
|
const safeDeckLabel = deckLabel?.replaceAll("<", "<").replaceAll(">", ">") ?? null;
|
|
5454
|
+
const buildTabEnabled = (() => {
|
|
5455
|
+
const raw = dntShim.Deno.env.get("GAMBIT_SIMULATOR_BUILD_TAB");
|
|
5456
|
+
if (raw === undefined)
|
|
5457
|
+
return true;
|
|
5458
|
+
const normalized = raw.trim().toLowerCase();
|
|
5459
|
+
return !(normalized === "0" || normalized === "false" ||
|
|
5460
|
+
normalized === "no" ||
|
|
5461
|
+
normalized === "off");
|
|
5462
|
+
})();
|
|
5463
|
+
const chatAccordionEnabled = (() => {
|
|
5464
|
+
const raw = dntShim.Deno.env.get("GAMBIT_SIMULATOR_CHAT_ACCORDION");
|
|
5465
|
+
if (raw === undefined)
|
|
5466
|
+
return true;
|
|
5467
|
+
const normalized = raw.trim().toLowerCase();
|
|
5468
|
+
return normalized === "1" || normalized === "true" ||
|
|
5469
|
+
normalized === "yes" ||
|
|
5470
|
+
normalized === "on";
|
|
5471
|
+
})();
|
|
5472
|
+
const buildStreamDebugEnabled = (() => {
|
|
5473
|
+
const raw = dntShim.Deno.env.get("GAMBIT_SIMULATOR_BUILD_STREAM_DEBUG");
|
|
5474
|
+
if (raw === undefined)
|
|
5475
|
+
return false;
|
|
5476
|
+
const normalized = raw.trim().toLowerCase();
|
|
5477
|
+
return normalized === "1" || normalized === "true" ||
|
|
5478
|
+
normalized === "yes" ||
|
|
5479
|
+
normalized === "on";
|
|
5480
|
+
})();
|
|
2961
5481
|
const bundleStamp = (() => {
|
|
2962
5482
|
try {
|
|
2963
5483
|
const stat = dntShim.Deno.statSync(simulatorBundlePath);
|
|
@@ -2971,6 +5491,8 @@ function simulatorReactHtml(deckPath, deckLabel) {
|
|
|
2971
5491
|
const bundleUrl = bundleStamp
|
|
2972
5492
|
? `/ui/bundle.js?v=${bundleStamp}`
|
|
2973
5493
|
: "/ui/bundle.js";
|
|
5494
|
+
const workspaceId = opts?.workspaceId ?? null;
|
|
5495
|
+
const workspaceOnboarding = Boolean(opts?.onboarding);
|
|
2974
5496
|
return `<!doctype html>
|
|
2975
5497
|
<html lang="en">
|
|
2976
5498
|
<head>
|
|
@@ -2987,6 +5509,12 @@ function simulatorReactHtml(deckPath, deckLabel) {
|
|
|
2987
5509
|
<script>
|
|
2988
5510
|
window.__GAMBIT_DECK_PATH__ = ${JSON.stringify(safeDeckPath)};
|
|
2989
5511
|
window.__GAMBIT_DECK_LABEL__ = ${JSON.stringify(safeDeckLabel)};
|
|
5512
|
+
window.__GAMBIT_VERSION__ = ${JSON.stringify(gambitVersion)};
|
|
5513
|
+
window.__GAMBIT_BUILD_TAB_ENABLED__ = ${JSON.stringify(buildTabEnabled)};
|
|
5514
|
+
window.__GAMBIT_CHAT_ACCORDION_ENABLED__ = ${JSON.stringify(chatAccordionEnabled)};
|
|
5515
|
+
window.__GAMBIT_WORKSPACE_ID__ = ${JSON.stringify(workspaceId)};
|
|
5516
|
+
window.__GAMBIT_WORKSPACE_ONBOARDING__ = ${JSON.stringify(workspaceOnboarding)};
|
|
5517
|
+
window.__GAMBIT_BUILD_STREAM_DEBUG__ = ${JSON.stringify(buildStreamDebugEnabled)};
|
|
2990
5518
|
</script>
|
|
2991
5519
|
<script type="module" src="${bundleUrl}"></script>
|
|
2992
5520
|
</body>
|
|
@@ -3031,6 +5559,9 @@ async function runDeckWithFallback(args) {
|
|
|
3031
5559
|
stream: args.stream,
|
|
3032
5560
|
onStreamText: args.onStreamText,
|
|
3033
5561
|
responsesMode: args.responsesMode,
|
|
5562
|
+
workerSandbox: args.workerSandbox,
|
|
5563
|
+
signal: args.signal,
|
|
5564
|
+
onCancel: args.onCancel,
|
|
3034
5565
|
});
|
|
3035
5566
|
}
|
|
3036
5567
|
catch (error) {
|
|
@@ -3047,6 +5578,9 @@ async function runDeckWithFallback(args) {
|
|
|
3047
5578
|
stream: args.stream,
|
|
3048
5579
|
onStreamText: args.onStreamText,
|
|
3049
5580
|
responsesMode: args.responsesMode,
|
|
5581
|
+
workerSandbox: args.workerSandbox,
|
|
5582
|
+
signal: args.signal,
|
|
5583
|
+
onCancel: args.onCancel,
|
|
3050
5584
|
});
|
|
3051
5585
|
}
|
|
3052
5586
|
throw error;
|