little_ghost 0.7.0 → 0.9.0
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.
- checksums.yaml +4 -4
- data/README.md +34 -6
- data/docs/guides/assemblies.md +21 -9
- data/docs/guides/code_mode.md +2 -2
- data/docs/guides/getting_started.md +27 -2
- data/docs/guides/integrations.md +20 -161
- data/docs/guides/mcp.md +288 -0
- data/docs/guides/models_and_providers.md +21 -54
- data/docs/guides/production.md +14 -5
- data/docs/guides/prompt_views.md +234 -27
- data/docs/guides/providers.md +275 -0
- data/docs/guides/sandboxing.md +42 -7
- data/docs/guides/skills.md +1 -3
- data/docs/guides/tools.md +2 -0
- data/exe/little_ghost +12 -0
- data/lib/little_ghost/agent/context_management.rb +2 -13
- data/lib/little_ghost/agent/delegation.rb +7 -4
- data/lib/little_ghost/agent/skills.rb +8 -6
- data/lib/little_ghost/agent/tool_loop.rb +4 -4
- data/lib/little_ghost/agent.rb +230 -70
- data/lib/little_ghost/agent_builder.rb +16 -4
- data/lib/little_ghost/artifacts/workspace_store.rb +16 -14
- data/lib/little_ghost/assembly.rb +48 -7
- data/lib/little_ghost/assembly_builder.rb +3 -3
- data/lib/little_ghost/cli.rb +429 -0
- data/lib/little_ghost/code_mode/broker.rb +11 -3
- data/lib/little_ghost/code_mode/javascript/client.rb +58 -11
- data/lib/little_ghost/code_mode/javascript/host.rb +37 -13
- data/lib/little_ghost/code_mode/javascript/session.rb +21 -9
- data/lib/little_ghost/code_mode/javascript_engine.rb +21 -42
- data/lib/little_ghost/code_mode/ruby/host.rb +8 -4
- data/lib/little_ghost/code_mode/ruby/session.rb +41 -14
- data/lib/little_ghost/code_mode/ruby_engine.rb +6 -36
- data/lib/little_ghost/code_mode/runtime.rb +57 -24
- data/lib/little_ghost/console.rb +17 -0
- data/lib/little_ghost/errors.rb +20 -2
- data/lib/little_ghost/framework_prompts.rb +376 -0
- data/lib/little_ghost/generators/application_generator.rb +179 -0
- data/lib/little_ghost/generators/templates/application/Gemfile.tt +3 -0
- data/lib/little_ghost/generators/templates/application/README.md.tt +29 -0
- data/lib/little_ghost/generators/templates/application/app/agents/application_agent.rb.tt +5 -0
- data/lib/little_ghost/generators/templates/application/app/prompts/application/system_prompt.erb.tt +1 -0
- data/lib/little_ghost/generators/templates/application/bin/application.tt +10 -0
- data/lib/little_ghost/generators/templates/application/config/little_ghost.rb.tt +5 -0
- data/lib/little_ghost/generators/templates/application/gitignore.tt +2 -0
- data/lib/little_ghost/generators/templates/application/keep.tt +0 -0
- data/lib/little_ghost/graph.rb +24 -5
- data/lib/little_ghost/mcp/client.rb +307 -610
- data/lib/little_ghost/mcp/toolset.rb +133 -95
- data/lib/little_ghost/mcp.rb +21 -3
- data/lib/little_ghost/model_operations.rb +103 -36
- data/lib/little_ghost/model_resolver.rb +2 -0
- data/lib/little_ghost/models/catalog/models_dev_source.rb +4 -1
- data/lib/little_ghost/prompt_resolver.rb +61 -27
- data/lib/little_ghost/prompts/little_ghost/agent/interjections/instructions.erb +12 -0
- data/lib/little_ghost/prompts/little_ghost/agent/system/default.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/artifacts/batch/feedback/batch_too_large.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/artifacts/batch/feedback/batch_too_many.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/artifacts/batch/feedback/item_too_large.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/artifacts/errors/preparation_failed.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/artifacts/errors/resolver_invalid.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/artifacts/errors/value_unstorable.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/artifacts/presentation/format/reference.erb +13 -0
- data/lib/little_ghost/prompts/little_ghost/artifacts/presentation/format/references.erb +12 -0
- data/lib/little_ghost/prompts/little_ghost/artifacts/presentation/format/workspace_references.erb +12 -0
- data/lib/little_ghost/prompts/little_ghost/artifacts/presentation/notices/full_result.erb +14 -0
- data/lib/little_ghost/prompts/little_ghost/artifacts/presentation/notices/storage_failed.erb +13 -0
- data/lib/little_ghost/prompts/little_ghost/artifacts/storage/errors/destination_unsafe.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/artifacts/storage/errors/secure_unavailable.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/artifacts/storage/errors/workspace_changed.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/artifacts/storage/errors/workspace_unavailable.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/artifacts/storage/errors/workspace_unsafe.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/artifacts/storage/feedback/count_limit.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/artifacts/storage/feedback/destination_exists.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/artifacts/storage/feedback/too_large.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/artifacts/storage/feedback/total_limit.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/assembly/feedback/transition_only.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/assembly/tools/fallback/description.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/code_mode/artifacts/format/references.erb +12 -0
- data/lib/little_ghost/prompts/little_ghost/code_mode/errors/brokered_close.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/code_mode/errors/cleanup_failed.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/code_mode/errors/execution_failed.erb +11 -0
- data/lib/little_ghost/prompts/little_ghost/code_mode/feedback/arguments_object.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/code_mode/feedback/closed.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/code_mode/feedback/control_active.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/code_mode/feedback/limit_exceeded.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/code_mode/feedback/not_active.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/code_mode/feedback/program_active.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/code_mode/feedback/program_timed_out.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/code_mode/feedback/unavailable_tool.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/code_mode/feedback/unknown_program.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/code_mode/javascript/errors/active_programs_limit.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/code_mode/javascript/errors/cleanup_failed.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/code_mode/javascript/errors/execution_limit.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/code_mode/javascript/errors/invalid_request.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/code_mode/javascript/errors/memory_limit.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/code_mode/javascript/errors/pending_calls_limit.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/code_mode/javascript/errors/source_size_limit.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/code_mode/javascript/errors/tools_array.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/code_mode/javascript/instructions.erb +35 -0
- data/lib/little_ghost/prompts/little_ghost/code_mode/ruby/feedback/parallel_callables.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/code_mode/ruby/instructions.erb +40 -0
- data/lib/little_ghost/prompts/little_ghost/code_mode/tools/exec/description.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/code_mode/tools/stop/description.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/code_mode/tools/stop/inputs/max_output_tokens/description.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/code_mode/tools/wait/description.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/code_mode/tools/wait/inputs/max_output_tokens/description.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/context_management/summary/request.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/context_management/summary/system_instruction.erb +16 -0
- data/lib/little_ghost/prompts/little_ghost/graph/context/format/failed_input.erb +11 -0
- data/lib/little_ghost/prompts/little_ghost/graph/context/format/previous_input.erb +13 -0
- data/lib/little_ghost/prompts/little_ghost/graph/context/headings/original_task.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/graph/context/headings/previous_inputs.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/mcp/errors/request_failed.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/mcp/errors/transformation_unserializable.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/mcp/tools/fallback/description.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/output/truncation/marker.erb +11 -0
- data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/errors/mount_changed.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/errors/secure_traversal_unavailable.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/errors/workspace_root_changed.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/directory_listing_limit.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/entry_required.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/file_read_limit.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/invalid_utf8.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/mount_escape.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/mount_missing.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/multiply_linked.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/not_directory.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/not_file.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/null_byte.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/outside_scope.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/parent_escape.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/path_missing.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/path_traverses_symlink.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/read_only.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/replace_empty.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/symlink_path.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/text_multiple.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/text_not_found.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/write_limit.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/write_parent_missing.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/write_target_symlink.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/sandbox/policy/feedback/feature_denied.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/sandbox/process/errors/program_supervisor_failed.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/sandbox/process/feedback/command_timed_out.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/sandbox/process/feedback/executable_required.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/sandbox/process/feedback/program_memory_exceeded.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/sandbox/process/feedback/program_output_exceeded.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/sandbox/process/feedback/program_timed_out.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/skills/discovery/instructions.erb +18 -0
- data/lib/little_ghost/prompts/little_ghost/skills/feedback/unknown.erb +11 -0
- data/lib/little_ghost/prompts/little_ghost/skills/resources/notices/truncated.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/skills/tools/activate/content.erb +28 -0
- data/lib/little_ghost/prompts/little_ghost/skills/tools/activate/description.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/skills/tools/activate/inputs/name/description.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/structured_output/persistence/format/redaction.erb +12 -0
- data/lib/little_ghost/prompts/little_ghost/structured_output/repair/feedback/invalid_result.erb +11 -0
- data/lib/little_ghost/prompts/little_ghost/structured_output/repair/feedback/schema_errors.erb +11 -0
- data/lib/little_ghost/prompts/little_ghost/structured_output/repair/request.erb +14 -0
- data/lib/little_ghost/prompts/little_ghost/structured_output/tools/result/description.erb +11 -0
- data/lib/little_ghost/prompts/little_ghost/structured_output/validation/feedback/invalid_json.erb +11 -0
- data/lib/little_ghost/prompts/little_ghost/structured_output/validation/feedback/missing_tool.erb +11 -0
- data/lib/little_ghost/prompts/little_ghost/structured_output/validation/feedback/multiple_tools.erb +11 -0
- data/lib/little_ghost/prompts/little_ghost/structured_output/validation/feedback/too_complex.erb +11 -0
- data/lib/little_ghost/prompts/little_ghost/structured_output/validation/feedback/too_deep.erb +11 -0
- data/lib/little_ghost/prompts/little_ghost/structured_output/validation/feedback/too_large.erb +11 -0
- data/lib/little_ghost/prompts/little_ghost/structured_output/validation/feedback/tool_not_exclusive.erb +11 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/errors/create_failed.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/errors/previous_turn_failed.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/errors/restore_failed.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/errors/turn_failed.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/feedback/duplicate_ids.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/feedback/duplicate_path.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/feedback/identity_capacity.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/feedback/inactive.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/feedback/interjection_chars_limit.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/feedback/interjection_limit.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/feedback/interjection_unsupported.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/feedback/invalid_cursor.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/feedback/invalid_mode.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/feedback/list_limit.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/feedback/message_limit.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/feedback/message_type.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/feedback/not_running.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/feedback/queue_capacity.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/feedback/task_name_invalid.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/feedback/task_name_limit.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/feedback/terminal_identity.erb +11 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/feedback/turn_capacity.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/feedback/unknown_id.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/feedback/unknown_kind.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/notices/turn_cancelled.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/tools/interject/description.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/tools/interject/inputs/active_id/description.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/tools/interject/inputs/message/description.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/tools/list/description.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/tools/send/description.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/tools/send/inputs/id/description.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/tools/send/inputs/message/description.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/tools/send/inputs/send_mode/description.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/tools/spawn/description.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/tools/spawn/inputs/kind/description.erb +11 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/tools/spawn/inputs/spawn_mode/description.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/tools/spawn/inputs/task/description.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/tools/spawn/inputs/task_name/description.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/tools/wait/description.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/tools/wait/inputs/ids/description.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/swarm/handoff/notice.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/swarm/handoff/request.erb +16 -0
- data/lib/little_ghost/prompts/little_ghost/swarm/tools/handoff/description.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/tools/built_in/list_files/description.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/tools/built_in/read_file/description.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/tools/built_in/replace_in_file/description.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/tools/built_in/shell/description.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/tools/built_in/write_file/description.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/tools/built_in/write_todos/description.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/tools/built_in/write_todos/feedback/single_progress.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/tools/built_in/write_todos/feedback/unique_ids.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/tools/errors/unexpected_failure.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/tools/errors/unserializable_result.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/tools/feedback/invalid_input.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/tools/feedback/unknown.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/tools/loop/notices/final_warning.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/tools/loop/notices/termination.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/tools/loop/notices/warning.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/tools/validation/schema/additional_property.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/tools/validation/schema/enum.erb +11 -0
- data/lib/little_ghost/prompts/little_ghost/tools/validation/schema/invalid_format.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/tools/validation/schema/invalid_pattern.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/tools/validation/schema/max_items.erb +11 -0
- data/lib/little_ghost/prompts/little_ghost/tools/validation/schema/max_length.erb +11 -0
- data/lib/little_ghost/prompts/little_ghost/tools/validation/schema/maximum.erb +11 -0
- data/lib/little_ghost/prompts/little_ghost/tools/validation/schema/min_items.erb +11 -0
- data/lib/little_ghost/prompts/little_ghost/tools/validation/schema/min_length.erb +11 -0
- data/lib/little_ghost/prompts/little_ghost/tools/validation/schema/minimum.erb +11 -0
- data/lib/little_ghost/prompts/little_ghost/tools/validation/schema/required.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/tools/validation/schema/type.erb +11 -0
- data/lib/little_ghost/prompts/little_ghost/workspace/feedback/path_changed.erb +10 -0
- data/lib/little_ghost/provider_registry.rb +1 -0
- data/lib/little_ghost/providers/lm_studio/catalog_source.rb +201 -0
- data/lib/little_ghost/providers/lm_studio.rb +52 -0
- data/lib/little_ghost/providers/openai.rb +3 -75
- data/lib/little_ghost/providers/openai_compatible.rb +78 -1
- data/lib/little_ghost/runtime/hooks/artifacts.rb +58 -22
- data/lib/little_ghost/runtime.rb +8 -3
- data/lib/little_ghost/sandbox/environment_policy.rb +29 -4
- data/lib/little_ghost/sandbox/filesystem.rb +51 -33
- data/lib/little_ghost/sandbox/policy.rb +28 -6
- data/lib/little_ghost/sandbox/process_runner.rb +6 -2
- data/lib/little_ghost/sandbox/process_session.rb +8 -4
- data/lib/little_ghost/sandbox/scope.rb +4 -1
- data/lib/little_ghost/sandboxes/seatbelt.rb +3 -1
- data/lib/little_ghost/sandboxes/unrestricted.rb +17 -9
- data/lib/little_ghost/skills/catalog.rb +41 -36
- data/lib/little_ghost/structured_output.rb +6 -6
- data/lib/little_ghost/subagents/control_tool.rb +58 -0
- data/lib/little_ghost/subagents/manager.rb +99 -74
- data/lib/little_ghost/support/output_truncation.rb +28 -4
- data/lib/little_ghost/swarm.rb +20 -7
- data/lib/little_ghost/tool.rb +47 -20
- data/lib/little_ghost/tool_registry.rb +2 -2
- data/lib/little_ghost/tools/filesystem.rb +12 -4
- data/lib/little_ghost/tools/shell.rb +5 -1
- data/lib/little_ghost/tools/write_todos.rb +9 -9
- data/lib/little_ghost/version.rb +1 -1
- data/lib/little_ghost/workflow.rb +199 -27
- data/lib/little_ghost/workspace.rb +2 -2
- data/lib/little_ghost.rb +6 -2
- metadata +222 -7
- data/lib/little_ghost/mcp/types.rb +0 -216
|
@@ -16,6 +16,18 @@ module LittleGhost
|
|
|
16
16
|
JAVASCRIPT_TIMEOUT_MS = 10_000
|
|
17
17
|
MAX_PENDING_TOOL_CALLS = 1_024
|
|
18
18
|
Terminated = Class.new(StandardError)
|
|
19
|
+
DEFAULT_MESSAGES = {
|
|
20
|
+
"execution_limit" => "JavaScript execution exceeded its limit.",
|
|
21
|
+
"memory_limit" => "JavaScript execution exceeded its memory limit.",
|
|
22
|
+
"cleanup_failed" => "JavaScript context cleanup failed.",
|
|
23
|
+
"pending_calls_limit" => "The code-mode program exceeded the pending Tool-call limit.",
|
|
24
|
+
"active_programs_limit" => "The code-mode host has too many active programs.",
|
|
25
|
+
"invalid_request" => "Invalid code-mode request: __DETAIL__",
|
|
26
|
+
"unavailable_tool" => "Unavailable code-mode Tool: __TOOL_NAME__",
|
|
27
|
+
"execution_failed" => "__ERROR_CLASS__: __ERROR_MESSAGE__",
|
|
28
|
+
"source_size_limit" => "Code-mode source exceeds the size limit.",
|
|
29
|
+
"tools_array" => "Code-mode tools must be an array."
|
|
30
|
+
}.freeze
|
|
19
31
|
|
|
20
32
|
BOOTSTRAP = <<~'JAVASCRIPT'
|
|
21
33
|
const __LITTLE_GHOST_CONTROL_IDENTIFIER__ = (() => {
|
|
@@ -26,6 +38,7 @@ module LittleGhost
|
|
|
26
38
|
const outputs = [];
|
|
27
39
|
const pending = new Map();
|
|
28
40
|
const exitSignal = Object.freeze({exit: true});
|
|
41
|
+
const unavailableToolMessage = __LITTLE_GHOST_UNAVAILABLE_TOOL_MESSAGE__;
|
|
29
42
|
let nextCallId = 0;
|
|
30
43
|
let done = false;
|
|
31
44
|
let failure = null;
|
|
@@ -40,7 +53,7 @@ module LittleGhost
|
|
|
40
53
|
|
|
41
54
|
const enqueue = (name, args) => new Promise((resolve, reject) => {
|
|
42
55
|
if (!Object.prototype.hasOwnProperty.call(definitionIndex, name)) {
|
|
43
|
-
reject(new Error(
|
|
56
|
+
reject(new Error(unavailableToolMessage.split("__TOOL_NAME__").join(name)));
|
|
44
57
|
return;
|
|
45
58
|
}
|
|
46
59
|
const id = String(++nextCallId);
|
|
@@ -129,10 +142,11 @@ module LittleGhost
|
|
|
129
142
|
JAVASCRIPT
|
|
130
143
|
|
|
131
144
|
class Program
|
|
132
|
-
def initialize(id:, source:, tools:, writer:, finished:)
|
|
145
|
+
def initialize(id:, source:, tools:, writer:, finished:, messages: nil)
|
|
133
146
|
@id = id
|
|
134
147
|
@source = source
|
|
135
148
|
@tools = tools
|
|
149
|
+
@messages = DEFAULT_MESSAGES.merge(messages || {}).freeze
|
|
136
150
|
@writer = writer
|
|
137
151
|
@finished = finished
|
|
138
152
|
@incoming = Queue.new
|
|
@@ -175,7 +189,11 @@ module LittleGhost
|
|
|
175
189
|
context.eval(
|
|
176
190
|
BOOTSTRAP
|
|
177
191
|
.gsub("__LITTLE_GHOST_CONTROL_IDENTIFIER__", @control_identifier)
|
|
178
|
-
.sub("__LITTLE_GHOST_TOOL_DEFINITIONS__", definitions)
|
|
192
|
+
.sub("__LITTLE_GHOST_TOOL_DEFINITIONS__", definitions)
|
|
193
|
+
.sub(
|
|
194
|
+
"__LITTLE_GHOST_UNAVAILABLE_TOOL_MESSAGE__",
|
|
195
|
+
JSON.generate(@messages.fetch("unavailable_tool"))
|
|
196
|
+
),
|
|
179
197
|
filename: "little-ghost-code-mode-bootstrap.js"
|
|
180
198
|
)
|
|
181
199
|
call_control(context, :run, @source)
|
|
@@ -186,15 +204,18 @@ module LittleGhost
|
|
|
186
204
|
terminal = if @context_mutex.synchronize { @terminating }
|
|
187
205
|
{type: "terminated"}
|
|
188
206
|
else
|
|
189
|
-
{type: "failed", error: "
|
|
207
|
+
{type: "failed", error: @messages.fetch("execution_limit"), fatal: true}
|
|
190
208
|
end
|
|
191
209
|
rescue MiniRacer::V8OutOfMemoryError
|
|
192
|
-
terminal = {type: "failed", error: "
|
|
210
|
+
terminal = {type: "failed", error: @messages.fetch("memory_limit"), fatal: true}
|
|
193
211
|
rescue => error
|
|
194
|
-
|
|
212
|
+
failure = @messages.fetch("execution_failed")
|
|
213
|
+
.gsub("__ERROR_CLASS__") { error.class.to_s }
|
|
214
|
+
.gsub("__ERROR_MESSAGE__") { error.message }
|
|
215
|
+
terminal = {type: "failed", error: failure, fatal: true}
|
|
195
216
|
ensure
|
|
196
217
|
cleanup_error = dispose_context
|
|
197
|
-
terminal = {type: "failed", error: "
|
|
218
|
+
terminal = {type: "failed", error: @messages.fetch("cleanup_failed"), fatal: true} if cleanup_error
|
|
198
219
|
begin
|
|
199
220
|
emit(**terminal) if terminal
|
|
200
221
|
ensure
|
|
@@ -210,7 +231,7 @@ module LittleGhost
|
|
|
210
231
|
if calls.length > MAX_PENDING_TOOL_CALLS
|
|
211
232
|
return {
|
|
212
233
|
type: "failed",
|
|
213
|
-
error: "
|
|
234
|
+
error: @messages.fetch("pending_calls_limit"),
|
|
214
235
|
fatal: true
|
|
215
236
|
}
|
|
216
237
|
end
|
|
@@ -308,23 +329,26 @@ module LittleGhost
|
|
|
308
329
|
id = message.fetch("program_id").to_s
|
|
309
330
|
source = message.fetch("source").to_s
|
|
310
331
|
tools = message.fetch("tools")
|
|
311
|
-
|
|
312
|
-
raise Protocol::Error, "
|
|
332
|
+
messages = DEFAULT_MESSAGES.merge(message["messages"] || {})
|
|
333
|
+
raise Protocol::Error, messages.fetch("source_size_limit") if source.bytesize > MAX_SOURCE_BYTES
|
|
334
|
+
raise Protocol::Error, messages.fetch("tools_array") unless tools.is_a?(Array)
|
|
313
335
|
|
|
314
336
|
created = @programs_mutex.synchronize do
|
|
315
337
|
next false if @programs.key?(id) || @programs.length >= MAX_ACTIVE_PROGRAMS
|
|
316
338
|
|
|
317
339
|
@programs[id] = Program.new(
|
|
318
|
-
id:, source:, tools:, writer: method(:write),
|
|
340
|
+
id:, source:, tools:, messages:, writer: method(:write),
|
|
319
341
|
finished: ->(program_id) { @programs_mutex.synchronize { @programs.delete(program_id) } }
|
|
320
342
|
)
|
|
321
343
|
true
|
|
322
344
|
end
|
|
323
345
|
unless created
|
|
324
|
-
write(type: "failed", program_id: id, error: "
|
|
346
|
+
write(type: "failed", program_id: id, error: messages.fetch("active_programs_limit"), fatal: true)
|
|
325
347
|
end
|
|
326
348
|
rescue KeyError, TypeError, Protocol::Error => error
|
|
327
|
-
|
|
349
|
+
template = messages.fetch("invalid_request")
|
|
350
|
+
detail = error.message
|
|
351
|
+
write(type: "failed", program_id: id.to_s, error: template.gsub("__DETAIL__") { detail }, fatal: true)
|
|
328
352
|
end
|
|
329
353
|
|
|
330
354
|
def program(id)
|
|
@@ -17,12 +17,14 @@ module LittleGhost
|
|
|
17
17
|
Deadline = Struct.new(:task, :cancelled, :expiring, :finished, :error)
|
|
18
18
|
|
|
19
19
|
def initialize(broker:, client:, sandbox: nil, workspace: nil, max_concurrency: 8,
|
|
20
|
-
wall_seconds: 3_600, observation_seconds: OBSERVATION_SECONDS, cleanup_timeout: CLEANUP_TIMEOUT
|
|
20
|
+
wall_seconds: 3_600, observation_seconds: OBSERVATION_SECONDS, cleanup_timeout: CLEANUP_TIMEOUT,
|
|
21
|
+
framework_prompt_scope: {})
|
|
21
22
|
@broker = broker
|
|
22
23
|
@task_runner = broker.task_runner
|
|
23
24
|
@client = client
|
|
24
25
|
@sandbox = sandbox
|
|
25
26
|
@workspace = workspace
|
|
27
|
+
@framework_prompt_scope = framework_prompt_scope.freeze
|
|
26
28
|
@max_concurrency = Integer(max_concurrency)
|
|
27
29
|
raise ArgumentError, "max_concurrency must be positive" unless @max_concurrency.positive?
|
|
28
30
|
@wall_seconds = Float(wall_seconds)
|
|
@@ -119,6 +121,16 @@ module LittleGhost
|
|
|
119
121
|
|
|
120
122
|
private
|
|
121
123
|
|
|
124
|
+
def prompt(key, **locals)
|
|
125
|
+
framework_prompts = @framework_prompt_scope[:framework_prompts] || FrameworkPrompts.new
|
|
126
|
+
framework_prompts.render(
|
|
127
|
+
key,
|
|
128
|
+
locals:,
|
|
129
|
+
invocation_paths: @framework_prompt_scope.fetch(:invocation_paths, []),
|
|
130
|
+
agent_path: @framework_prompt_scope[:agent_path]
|
|
131
|
+
)
|
|
132
|
+
end
|
|
133
|
+
|
|
122
134
|
def execute_program(source:, catalog:, frame:, max_output_tokens:, context:)
|
|
123
135
|
ensure_open
|
|
124
136
|
raise_pending_deadline_error!
|
|
@@ -126,7 +138,7 @@ module LittleGhost
|
|
|
126
138
|
program_id = SecureRandom.uuid
|
|
127
139
|
@frames_mutex.synchronize do
|
|
128
140
|
if @current_program_id
|
|
129
|
-
raise LittleGhost::ToolError, "
|
|
141
|
+
raise LittleGhost::ToolError, prompt("code_mode/feedback/program_active")
|
|
130
142
|
end
|
|
131
143
|
@frames[program_id] = javascript_catalog
|
|
132
144
|
@current_program_id = program_id
|
|
@@ -152,7 +164,7 @@ module LittleGhost
|
|
|
152
164
|
ensure_open
|
|
153
165
|
program_id = @frames_mutex.synchronize { @current_program_id }
|
|
154
166
|
raise_pending_deadline_error! unless program_id
|
|
155
|
-
raise LittleGhost::ToolError, "
|
|
167
|
+
raise LittleGhost::ToolError, prompt("code_mode/feedback/not_active") unless program_id
|
|
156
168
|
result = @client.observe(
|
|
157
169
|
owner: self, program_id:, timeout: @observation_seconds,
|
|
158
170
|
max_tokens: output_tokens(max_output_tokens), context:
|
|
@@ -175,7 +187,7 @@ module LittleGhost
|
|
|
175
187
|
context&.check!
|
|
176
188
|
program_id = @frames_mutex.synchronize { @current_program_id }
|
|
177
189
|
raise_pending_deadline_error! unless program_id
|
|
178
|
-
raise LittleGhost::ToolError, "
|
|
190
|
+
raise LittleGhost::ToolError, prompt("code_mode/feedback/not_active") unless program_id
|
|
179
191
|
|
|
180
192
|
finish(program_id, terminate_program(program_id, max_output_tokens:))
|
|
181
193
|
rescue LittleGhost::CleanupError
|
|
@@ -196,7 +208,7 @@ module LittleGhost
|
|
|
196
208
|
)
|
|
197
209
|
end
|
|
198
210
|
@mutex.synchronize do
|
|
199
|
-
raise LittleGhost::ToolError, "
|
|
211
|
+
raise LittleGhost::ToolError, prompt("code_mode/feedback/closed", resource: "session") if @closed
|
|
200
212
|
|
|
201
213
|
ensure_worker
|
|
202
214
|
register_dispatch(batch)
|
|
@@ -213,7 +225,7 @@ module LittleGhost
|
|
|
213
225
|
def with_control
|
|
214
226
|
acquired = @control_mutex.try_lock
|
|
215
227
|
unless acquired
|
|
216
|
-
raise LittleGhost::ToolError, "
|
|
228
|
+
raise LittleGhost::ToolError, prompt("code_mode/feedback/control_active")
|
|
217
229
|
end
|
|
218
230
|
|
|
219
231
|
yield
|
|
@@ -441,8 +453,8 @@ module LittleGhost
|
|
|
441
453
|
|
|
442
454
|
def ensure_open
|
|
443
455
|
@mutex.synchronize do
|
|
444
|
-
raise LittleGhost::ToolError, "
|
|
445
|
-
raise LittleGhost::ToolError, "
|
|
456
|
+
raise LittleGhost::ToolError, prompt("code_mode/feedback/closed", resource: "session") if @closed
|
|
457
|
+
raise LittleGhost::ToolError, prompt("code_mode/errors/cleanup_failed") if @poisoned
|
|
446
458
|
end
|
|
447
459
|
end
|
|
448
460
|
|
|
@@ -482,7 +494,7 @@ module LittleGhost
|
|
|
482
494
|
end
|
|
483
495
|
return unless current
|
|
484
496
|
|
|
485
|
-
error = LittleGhost::ToolError.new("
|
|
497
|
+
error = LittleGhost::ToolError.new(FrameworkPrompts.reference("code_mode/feedback/program_timed_out"))
|
|
486
498
|
begin
|
|
487
499
|
terminate_program(program_id)
|
|
488
500
|
rescue => cleanup_error
|
|
@@ -50,40 +50,15 @@ module LittleGhost
|
|
|
50
50
|
|
|
51
51
|
# Builds JavaScript usage instructions and TypeScript declarations for
|
|
52
52
|
# +catalog+.
|
|
53
|
-
def instructions(catalog:)
|
|
53
|
+
def instructions(catalog:, prompts: nil)
|
|
54
54
|
javascript_catalog = Javascript::Catalog.new(catalog)
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
Program lifecycle:
|
|
59
|
-
- Every exec call starts a fresh program in a new V8 context.
|
|
60
|
-
- Exec and wait observe it for up to one minute. They return sooner when it finishes.
|
|
61
|
-
- If exec or wait returns `still_working`, call wait to observe the same running program again.
|
|
62
|
-
- Wait does not pause, resume, or restart the program.
|
|
63
|
-
- Use stop when the result is no longer needed.
|
|
64
|
-
- Do not call wait or stop after `completed`, `error`, or `terminated`.
|
|
65
|
-
|
|
66
|
-
Tool calls:
|
|
67
|
-
- Tool functions return Promises. Use await or Promise.all.
|
|
68
|
-
- JSON results become objects or arrays. Other results remain strings.
|
|
69
|
-
- Unawaited tool calls finish before the program exits.
|
|
70
|
-
- `ALL_TOOLS` contains the complete runtime catalog.
|
|
71
|
-
|
|
72
|
-
Output and completion:
|
|
73
|
-
- Use `text(value)` for user-visible output.
|
|
74
|
-
- Use `exit()` to complete early.
|
|
75
|
-
|
|
76
|
-
The V8 context has no Node.js APIs, filesystem, network, console, WebAssembly, or process API.
|
|
77
|
-
|
|
78
|
-
Available tool methods:
|
|
79
|
-
|
|
80
|
-
#{javascript_catalog.declarations}
|
|
81
|
-
INSTRUCTIONS
|
|
55
|
+
renderer = prompts || ->(key, **locals) { FrameworkPrompts.new.render(key, locals:) }
|
|
56
|
+
renderer.call("code_mode/javascript/instructions", declarations: javascript_catalog.declarations)
|
|
82
57
|
end
|
|
83
58
|
|
|
84
59
|
# Opens a JavaScript Session with engine defaults merged with +limits+.
|
|
85
60
|
# Unsupported limit keys raise ArgumentError.
|
|
86
|
-
def open_session(broker:, sandbox_factory:, limits: {})
|
|
61
|
+
def open_session(broker:, sandbox_factory:, limits: {}, framework_prompt_scope: {})
|
|
87
62
|
configured_limits = normalize_limits(limits, defaults: DEFAULT_LIMITS)
|
|
88
63
|
root = Dir.mktmpdir("little-ghost-javascript-")
|
|
89
64
|
runtime_paths = javascript_runtime_paths
|
|
@@ -99,22 +74,26 @@ module LittleGhost
|
|
|
99
74
|
required_runtime_paths: runtime_paths.keys.to_h { |name| [name, :read_only] }
|
|
100
75
|
)
|
|
101
76
|
sandbox.open
|
|
102
|
-
client = Javascript::Client.new(
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
77
|
+
client = Javascript::Client.new(
|
|
78
|
+
session_factory: lambda {
|
|
79
|
+
sandbox.start_program(
|
|
80
|
+
host_command,
|
|
81
|
+
cwd: ".",
|
|
82
|
+
environment: child_environment,
|
|
83
|
+
output_bytes: configured_limits.fetch(:output_bytes),
|
|
84
|
+
memory_bytes: configured_limits[:memory_bytes],
|
|
85
|
+
cpu_seconds: configured_limits[:cpu_seconds],
|
|
86
|
+
file_bytes: configured_limits[:file_bytes],
|
|
87
|
+
allow_subprocesses: allow_subprocesses_for(sandbox)
|
|
88
|
+
)
|
|
89
|
+
},
|
|
90
|
+
framework_prompt_scope:
|
|
91
|
+
)
|
|
114
92
|
Javascript::Session.new(
|
|
115
93
|
broker:, client:, sandbox:, workspace:,
|
|
116
94
|
max_concurrency: configured_limits.fetch(:max_concurrency),
|
|
117
|
-
wall_seconds: configured_limits.fetch(:wall_seconds)
|
|
95
|
+
wall_seconds: configured_limits.fetch(:wall_seconds),
|
|
96
|
+
framework_prompt_scope:
|
|
118
97
|
)
|
|
119
98
|
rescue
|
|
120
99
|
sandbox&.close
|
|
@@ -35,6 +35,7 @@ module LittleGhost
|
|
|
35
35
|
response_queues = {}
|
|
36
36
|
calls = 0
|
|
37
37
|
max_calls = request.fetch("tool_calls")
|
|
38
|
+
messages = request.fetch("messages")
|
|
38
39
|
output_buffer = +""
|
|
39
40
|
flush_output = lambda do
|
|
40
41
|
unless output_buffer.empty?
|
|
@@ -81,7 +82,7 @@ module LittleGhost
|
|
|
81
82
|
invoke = lambda do |name, arguments|
|
|
82
83
|
id, queue = queues_lock.synchronize do
|
|
83
84
|
calls += 1
|
|
84
|
-
raise "
|
|
85
|
+
raise messages.fetch("tool_calls_limit") if calls > max_calls
|
|
85
86
|
id = "call-#{calls}"
|
|
86
87
|
queue = Queue.new
|
|
87
88
|
response_queues[id] = queue
|
|
@@ -102,7 +103,7 @@ module LittleGhost
|
|
|
102
103
|
end
|
|
103
104
|
concurrency = request.fetch("concurrency")
|
|
104
105
|
tools.define_singleton_method(:parallel) do |*operations|
|
|
105
|
-
raise ArgumentError, "
|
|
106
|
+
raise ArgumentError, messages.fetch("parallel_callables") unless operations.all? { |operation| operation.respond_to?(:call) }
|
|
106
107
|
operations.each_slice(concurrency).flat_map do |batch|
|
|
107
108
|
batch.map { |operation| Thread.new { operation.call } }.map(&:value)
|
|
108
109
|
end
|
|
@@ -130,8 +131,11 @@ module LittleGhost
|
|
|
130
131
|
rescue SignalException
|
|
131
132
|
exit 0
|
|
132
133
|
rescue Exception => error
|
|
133
|
-
|
|
134
|
-
|
|
134
|
+
failure = messages.fetch("execution_failed")
|
|
135
|
+
.gsub("__ERROR_CLASS__") { error.class.to_s }
|
|
136
|
+
.gsub("__ERROR_MESSAGE__") { error.message }
|
|
137
|
+
STDERR.puts(failure)
|
|
138
|
+
error_frame = {type: "error", error: failure}
|
|
135
139
|
emit ? emit.call(error_frame) : write_frame&.call(error_frame)
|
|
136
140
|
exit 1
|
|
137
141
|
end
|
|
@@ -12,13 +12,17 @@ module LittleGhost
|
|
|
12
12
|
class Session < CodeMode::Session # :nodoc:
|
|
13
13
|
OBSERVATION_SECONDS = 60
|
|
14
14
|
|
|
15
|
-
def initialize(
|
|
15
|
+
def initialize(
|
|
16
|
+
broker:, sandbox_factory:, subprocess_policy:, limits:, observation_seconds: OBSERVATION_SECONDS,
|
|
17
|
+
framework_prompt_scope: {}
|
|
18
|
+
)
|
|
16
19
|
@broker = broker
|
|
17
20
|
@task_runner = broker.task_runner
|
|
18
21
|
@sandbox_factory = sandbox_factory
|
|
19
22
|
@subprocess_policy = subprocess_policy
|
|
20
23
|
@limits = limits
|
|
21
24
|
@observation_seconds = Float(observation_seconds)
|
|
25
|
+
@framework_prompt_scope = framework_prompt_scope.freeze
|
|
22
26
|
raise ArgumentError, "observation_seconds must be positive" unless @observation_seconds.positive?
|
|
23
27
|
@session = nil
|
|
24
28
|
@workspace = nil
|
|
@@ -65,7 +69,7 @@ module LittleGhost
|
|
|
65
69
|
@closed = true
|
|
66
70
|
acquired = @control_mutex.try_lock
|
|
67
71
|
unless acquired
|
|
68
|
-
raise ToolError, "
|
|
72
|
+
raise ToolError, prompt("code_mode/feedback/control_active")
|
|
69
73
|
end
|
|
70
74
|
cleanup_error = begin
|
|
71
75
|
close_process
|
|
@@ -87,13 +91,23 @@ module LittleGhost
|
|
|
87
91
|
|
|
88
92
|
private
|
|
89
93
|
|
|
94
|
+
def prompt(key, **locals)
|
|
95
|
+
framework_prompts = @framework_prompt_scope[:framework_prompts] || FrameworkPrompts.new
|
|
96
|
+
framework_prompts.render(
|
|
97
|
+
key,
|
|
98
|
+
locals:,
|
|
99
|
+
invocation_paths: @framework_prompt_scope.fetch(:invocation_paths, []),
|
|
100
|
+
agent_path: @framework_prompt_scope[:agent_path]
|
|
101
|
+
)
|
|
102
|
+
end
|
|
103
|
+
|
|
90
104
|
def execute_program(source:, catalog:, frame:, max_output_tokens:, context:)
|
|
91
|
-
raise ToolError, "
|
|
105
|
+
raise ToolError, prompt("code_mode/feedback/closed", resource: "session") if @closed
|
|
92
106
|
ensure_program_can_start!
|
|
93
107
|
@programs += 1
|
|
94
|
-
raise ToolError, "
|
|
108
|
+
raise ToolError, prompt("code_mode/feedback/limit_exceeded", limit: "program") if @programs > @limits.fetch(:programs)
|
|
95
109
|
source = String(source)
|
|
96
|
-
raise ToolError, "
|
|
110
|
+
raise ToolError, prompt("code_mode/feedback/limit_exceeded", limit: "source size") if source.bytesize > @limits.fetch(:source_bytes)
|
|
97
111
|
normalized_catalog = Catalog.new(catalog).host_definitions
|
|
98
112
|
|
|
99
113
|
generation, process = open_process
|
|
@@ -103,7 +117,16 @@ module LittleGhost
|
|
|
103
117
|
catalog: normalized_catalog,
|
|
104
118
|
frame:,
|
|
105
119
|
tool_calls: @limits.fetch(:tool_calls),
|
|
106
|
-
concurrency: @limits.fetch(:concurrency)
|
|
120
|
+
concurrency: @limits.fetch(:concurrency),
|
|
121
|
+
messages: {
|
|
122
|
+
parallel_callables: prompt("code_mode/ruby/feedback/parallel_callables"),
|
|
123
|
+
tool_calls_limit: prompt("code_mode/feedback/limit_exceeded", limit: "Tool call"),
|
|
124
|
+
execution_failed: prompt(
|
|
125
|
+
"code_mode/errors/execution_failed",
|
|
126
|
+
error_class: "__ERROR_CLASS__",
|
|
127
|
+
message: "__ERROR_MESSAGE__"
|
|
128
|
+
)
|
|
129
|
+
}
|
|
107
130
|
))
|
|
108
131
|
drive(generation:, process:, max_output_tokens:, context:)
|
|
109
132
|
rescue
|
|
@@ -144,7 +167,7 @@ module LittleGhost
|
|
|
144
167
|
def with_control
|
|
145
168
|
acquired = @control_mutex.try_lock
|
|
146
169
|
unless acquired
|
|
147
|
-
raise ToolError, "
|
|
170
|
+
raise ToolError, prompt("code_mode/feedback/control_active")
|
|
148
171
|
end
|
|
149
172
|
|
|
150
173
|
yield
|
|
@@ -156,7 +179,7 @@ module LittleGhost
|
|
|
156
179
|
error = @lifecycle_mutex.synchronize do
|
|
157
180
|
@lifecycle_condition.wait(@lifecycle_mutex) while @expiring_generation
|
|
158
181
|
pending = take_pending_program_error
|
|
159
|
-
raise ToolError, "
|
|
182
|
+
raise ToolError, prompt("code_mode/feedback/program_active") if !pending && (@generation || @session)
|
|
160
183
|
|
|
161
184
|
pending
|
|
162
185
|
end
|
|
@@ -178,7 +201,7 @@ module LittleGhost
|
|
|
178
201
|
factory.new(workspace: @workspace)
|
|
179
202
|
end
|
|
180
203
|
@sandbox.open
|
|
181
|
-
raise ToolError, "
|
|
204
|
+
raise ToolError, prompt("code_mode/feedback/closed", resource: "session") if @closed
|
|
182
205
|
@session = @sandbox.start_program(
|
|
183
206
|
[RbConfig.ruby, "-e", Host::SOURCE],
|
|
184
207
|
output_bytes: @limits.fetch(:output_bytes),
|
|
@@ -275,7 +298,7 @@ module LittleGhost
|
|
|
275
298
|
active = @call_tasks.count(&:alive?)
|
|
276
299
|
raise ProtocolError, "code-mode concurrent tool call limit exceeded" if active >= @limits.fetch(:concurrency)
|
|
277
300
|
@tool_calls += 1
|
|
278
|
-
raise ToolError, "
|
|
301
|
+
raise ToolError, prompt("code_mode/feedback/limit_exceeded", limit: "Tool call") if @tool_calls > @limits.fetch(:tool_calls)
|
|
279
302
|
|
|
280
303
|
call_errors = @call_errors
|
|
281
304
|
closing_marker = @closing_marker
|
|
@@ -290,7 +313,7 @@ module LittleGhost
|
|
|
290
313
|
def append_output(value)
|
|
291
314
|
value = String(value).dup.force_encoding(Encoding::UTF_8).scrub
|
|
292
315
|
@output_bytes += value.bytesize
|
|
293
|
-
raise ToolError, "
|
|
316
|
+
raise ToolError, prompt("code_mode/feedback/limit_exceeded", limit: "output") if @output_bytes > @limits.fetch(:output_bytes)
|
|
294
317
|
|
|
295
318
|
@output << value
|
|
296
319
|
end
|
|
@@ -314,7 +337,11 @@ module LittleGhost
|
|
|
314
337
|
@output = +""
|
|
315
338
|
return output unless max_output_tokens
|
|
316
339
|
|
|
317
|
-
Support::OutputTruncation.truncate_middle_with_token_budget(
|
|
340
|
+
Support::OutputTruncation.truncate_middle_with_token_budget(
|
|
341
|
+
output,
|
|
342
|
+
max_output_tokens,
|
|
343
|
+
**@framework_prompt_scope
|
|
344
|
+
).first
|
|
318
345
|
end
|
|
319
346
|
|
|
320
347
|
def active_process
|
|
@@ -323,7 +350,7 @@ module LittleGhost
|
|
|
323
350
|
@lifecycle_condition.wait(@lifecycle_mutex) while @expiring_generation
|
|
324
351
|
error = take_pending_program_error
|
|
325
352
|
raise error if error
|
|
326
|
-
raise ToolError, "
|
|
353
|
+
raise ToolError, prompt("code_mode/feedback/not_active") unless @generation && @session
|
|
327
354
|
|
|
328
355
|
[@generation, @session]
|
|
329
356
|
end
|
|
@@ -442,7 +469,7 @@ module LittleGhost
|
|
|
442
469
|
end
|
|
443
470
|
return unless process
|
|
444
471
|
|
|
445
|
-
error = ToolError.new("
|
|
472
|
+
error = ToolError.new(FrameworkPrompts.reference("code_mode/feedback/program_timed_out"))
|
|
446
473
|
begin
|
|
447
474
|
mark_process_closing
|
|
448
475
|
process.terminate
|
|
@@ -34,50 +34,20 @@ module LittleGhost
|
|
|
34
34
|
def language = :ruby
|
|
35
35
|
|
|
36
36
|
# Builds Ruby usage instructions and method declarations for +catalog+.
|
|
37
|
-
def instructions(catalog:)
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
Program lifecycle:
|
|
42
|
-
- Every exec call starts a fresh program in a new Ruby process.
|
|
43
|
-
- Exec and wait observe it for up to one minute. They return sooner when it finishes.
|
|
44
|
-
- If exec or wait returns `still_working`, call wait to observe the same running program again.
|
|
45
|
-
- Wait does not pause, resume, or restart the program.
|
|
46
|
-
- Use stop when the result is no longer needed.
|
|
47
|
-
- Do not call wait or stop after `completed`, `error`, or `terminated`.
|
|
48
|
-
- Local variables, constants, and other process state do not persist between exec calls.
|
|
49
|
-
|
|
50
|
-
Tool calls:
|
|
51
|
-
- Call a tool with `tools.<method>(keyword: value)` and only its documented keywords.
|
|
52
|
-
- Use `tools.call(name, arguments)` when the name is dynamic.
|
|
53
|
-
- Tool calls are synchronous. Use `tools.parallel` for independent calls; results keep callable order.
|
|
54
|
-
- JSON results become ordinary Ruby values. Other results remain strings.
|
|
55
|
-
- A failed call raises. Rescue it only when the program can recover.
|
|
56
|
-
- `ALL_TOOLS` contains the complete runtime catalog.
|
|
57
|
-
|
|
58
|
-
Output and completion:
|
|
59
|
-
- The final Ruby expression becomes the completed program value.
|
|
60
|
-
- Use `text(value)` for user-visible output.
|
|
61
|
-
- Ordinary `puts`, `print`, `printf`, and `p` output is captured and combined into bounded chunks.
|
|
62
|
-
- Use `finish(value)` to complete early with a value.
|
|
63
|
-
|
|
64
|
-
The Sandbox controls filesystem, network, subprocess, and optional-library access. Do not assume host
|
|
65
|
-
capabilities are available.
|
|
66
|
-
|
|
67
|
-
Available tool methods:
|
|
68
|
-
|
|
69
|
-
#{Ruby::Catalog.new(catalog).declarations}
|
|
70
|
-
INSTRUCTIONS
|
|
37
|
+
def instructions(catalog:, prompts: nil)
|
|
38
|
+
renderer = prompts || ->(key, **locals) { FrameworkPrompts.new.render(key, locals:) }
|
|
39
|
+
renderer.call("code_mode/ruby/instructions", declarations: Ruby::Catalog.new(catalog).declarations)
|
|
71
40
|
end
|
|
72
41
|
|
|
73
42
|
# Opens a Ruby Session with engine defaults merged with +limits+.
|
|
74
43
|
# Unsupported limit keys raise ArgumentError.
|
|
75
|
-
def open_session(broker:, sandbox_factory:, limits: {})
|
|
44
|
+
def open_session(broker:, sandbox_factory:, limits: {}, framework_prompt_scope: {})
|
|
76
45
|
Ruby::Session.new(
|
|
77
46
|
broker:,
|
|
78
47
|
sandbox_factory:,
|
|
79
48
|
subprocess_policy: method(:allow_subprocesses_for),
|
|
80
|
-
limits: normalize_limits(limits, defaults: DEFAULT_LIMITS)
|
|
49
|
+
limits: normalize_limits(limits, defaults: DEFAULT_LIMITS),
|
|
50
|
+
framework_prompt_scope:
|
|
81
51
|
)
|
|
82
52
|
end
|
|
83
53
|
end
|