space-architect 7.1.0 → 9.0.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/CHANGELOG.md +108 -0
- data/README.md +58 -246
- data/lib/space_architect/architect_project.rb +40 -180
- data/lib/space_architect/bug_report.rb +2 -1
- data/lib/space_architect/cli/architect.rb +69 -103
- data/lib/space_architect/cli/research.rb +1 -1
- data/lib/space_architect/cli.rb +9 -14
- data/lib/space_architect/harness.rb +97 -344
- data/lib/space_architect/pi/builder-guard.ts +103 -0
- data/lib/space_architect/research/mux.rb +47 -32
- data/lib/space_architect/research/pi_events.rb +70 -0
- data/lib/space_architect/research/renderer.rb +39 -39
- data/lib/space_architect/research/supervisor.rb +35 -14
- data/lib/space_architect/research.rb +1 -6
- data/lib/space_architect/session_sync/bin_path.rb +1 -1
- data/lib/space_architect/session_sync/cursor.rb +1 -1
- data/lib/space_architect/session_sync/plist.rb +1 -1
- data/lib/{space_core → space_architect}/version.rb +2 -2
- data/lib/space_architect.rb +1 -2
- data/skill/architect/SKILL.md +30 -25
- data/skill/architect/dispatch.md +137 -115
- data/skill/architect/research.md +14 -8
- data/skill/architect-research/SKILL.md +18 -11
- data/skill/architect-vocabulary/SKILL.md +3 -3
- metadata +20 -178
- data/exe/space +0 -13
- data/exe/src +0 -13
- data/lib/space_architect/cli/space.rb +0 -29
- data/lib/space_architect/cli/src.rb +0 -38
- data/lib/space_architect/dispatcher.rb +0 -25
- data/lib/space_core/atomic_write.rb +0 -21
- data/lib/space_core/cli/base_command.rb +0 -19
- data/lib/space_core/cli/build.rb +0 -27
- data/lib/space_core/cli/config.rb +0 -49
- data/lib/space_core/cli/current.rb +0 -16
- data/lib/space_core/cli/help.rb +0 -195
- data/lib/space_core/cli/helpers.rb +0 -115
- data/lib/space_core/cli/init.rb +0 -29
- data/lib/space_core/cli/list.rb +0 -24
- data/lib/space_core/cli/loop_status.rb +0 -47
- data/lib/space_core/cli/new.rb +0 -38
- data/lib/space_core/cli/pack.rb +0 -29
- data/lib/space_core/cli/path.rb +0 -16
- data/lib/space_core/cli/repeatable_options.rb +0 -84
- data/lib/space_core/cli/repo.rb +0 -76
- data/lib/space_core/cli/run.rb +0 -44
- data/lib/space_core/cli/shell.rb +0 -125
- data/lib/space_core/cli/show.rb +0 -21
- data/lib/space_core/cli/status.rb +0 -74
- data/lib/space_core/cli/use.rb +0 -17
- data/lib/space_core/cli.rb +0 -177
- data/lib/space_core/commands.rb +0 -51
- data/lib/space_core/config.rb +0 -152
- data/lib/space_core/errors.rb +0 -14
- data/lib/space_core/git_client.rb +0 -49
- data/lib/space_core/mise_client.rb +0 -37
- data/lib/space_core/oci_builder.rb +0 -56
- data/lib/space_core/oci_packer.rb +0 -103
- data/lib/space_core/oci_runner.rb +0 -89
- data/lib/space_core/paths.rb +0 -58
- data/lib/space_core/repo_reference.rb +0 -19
- data/lib/space_core/repo_resolver.rb +0 -167
- data/lib/space_core/shell_integration.rb +0 -438
- data/lib/space_core/slugger.rb +0 -16
- data/lib/space_core/space.rb +0 -174
- data/lib/space_core/space_store.rb +0 -319
- data/lib/space_core/state.rb +0 -86
- data/lib/space_core/templates/oci/dockerfile.erb +0 -73
- data/lib/space_core/templates/oci/dockerignore.erb +0 -17
- data/lib/space_core/templates/oci/entrypoint.sh.erb +0 -16
- data/lib/space_core/terminal.rb +0 -161
- data/lib/space_core/warnings.rb +0 -13
- data/lib/space_core/xdg.rb +0 -33
- data/lib/space_core.rb +0 -26
- data/lib/space_src/cli/clone.rb +0 -54
- data/lib/space_src/cli/config.rb +0 -65
- data/lib/space_src/cli/daemon.rb +0 -362
- data/lib/space_src/cli/options.rb +0 -21
- data/lib/space_src/cli/org.rb +0 -199
- data/lib/space_src/cli/repo.rb +0 -169
- data/lib/space_src/cli/shell.rb +0 -121
- data/lib/space_src/cli/status.rb +0 -75
- data/lib/space_src/cli/sync.rb +0 -161
- data/lib/space_src/cli.rb +0 -159
- data/lib/space_src/cloner.rb +0 -77
- data/lib/space_src/config/contract.rb +0 -54
- data/lib/space_src/config/duration.rb +0 -79
- data/lib/space_src/config/model.rb +0 -49
- data/lib/space_src/config/store.rb +0 -156
- data/lib/space_src/forge/client.rb +0 -31
- data/lib/space_src/forge/github.rb +0 -98
- data/lib/space_src/launchd/agent.rb +0 -195
- data/lib/space_src/launchd/plist.rb +0 -129
- data/lib/space_src/log_rotator.rb +0 -46
- data/lib/space_src/migration.rb +0 -43
- data/lib/space_src/nav.rb +0 -99
- data/lib/space_src/paths.rb +0 -72
- data/lib/space_src/scm/client.rb +0 -87
- data/lib/space_src/scm/git.rb +0 -232
- data/lib/space_src/scm/status.rb +0 -24
- data/lib/space_src/shell.rb +0 -90
- data/lib/space_src/shell_integration.rb +0 -321
- data/lib/space_src/state/lock.rb +0 -59
- data/lib/space_src/state/store.rb +0 -140
- data/lib/space_src/sync/engine.rb +0 -503
- data/lib/space_src/sync/repo_plan.rb +0 -215
- data/lib/space_src/sync/report.rb +0 -18
- data/lib/space_src/ui/interactive_reporter.rb +0 -280
- data/lib/space_src/ui/json_reporter.rb +0 -39
- data/lib/space_src/ui/mode.rb +0 -68
- data/lib/space_src/ui/plain_reporter.rb +0 -53
- data/lib/space_src/ui/reporter.rb +0 -48
- data/lib/space_src/version.rb +0 -7
- data/lib/space_src.rb +0 -38
|
@@ -241,13 +241,6 @@ module Space::Architect
|
|
|
241
241
|
lanes = s["lanes"] || []
|
|
242
242
|
declared.each do |d|
|
|
243
243
|
fields = { "name" => d["name"], "repo" => d["repo"], "touch_set" => Array(d["touch"]) }
|
|
244
|
-
# #89/AC3-AC4: the frozen lane's own tool grant, reviewable like every other
|
|
245
|
-
# boundary. Declared-only, like touch_set — worktree_add never touches these
|
|
246
|
-
# keys, so a re-materialize preserves them without threading them through
|
|
247
|
-
# recorded_lane_fields (unlike harness/model, which worktree_add resolves and
|
|
248
|
-
# overwrites on every call).
|
|
249
|
-
fields["allowed_tools"] = d["allowed_tools"] if d["allowed_tools"]
|
|
250
|
-
fields["append_allowed_tools"] = d["append_allowed_tools"] if d["append_allowed_tools"]
|
|
251
244
|
existing = lanes.find { |l| l["name"] == d["name"] }
|
|
252
245
|
existing ? existing.merge!(fields) : lanes << fields
|
|
253
246
|
end
|
|
@@ -841,7 +834,7 @@ module Space::Architect
|
|
|
841
834
|
variants: variant_lanes.map do |l|
|
|
842
835
|
{
|
|
843
836
|
name: l["name"],
|
|
844
|
-
harness: l["harness"] || "
|
|
837
|
+
harness: l["harness"] || "pi",
|
|
845
838
|
model: l["model"],
|
|
846
839
|
effort: l["effort"],
|
|
847
840
|
base_sha: l["base_sha"],
|
|
@@ -940,9 +933,8 @@ module Space::Architect
|
|
|
940
933
|
end
|
|
941
934
|
end
|
|
942
935
|
|
|
943
|
-
def dispatch(iteration, lane, model: nil, max_turns: 200,
|
|
944
|
-
|
|
945
|
-
allowed_tools: nil, append_allowed_tools: nil, force: false, quiet: false,
|
|
936
|
+
def dispatch(iteration, lane, model: nil, max_turns: 200, bin: nil, harness: nil, effort: nil,
|
|
937
|
+
force: false, quiet: false,
|
|
946
938
|
detach: false, push_url: nil, push_token: nil, push_host: nil, run_creator: nil,
|
|
947
939
|
push_client: nil, timeout: nil, prompt: nil, now: Time.now)
|
|
948
940
|
raise Space::Core::Error, "Specify --push-host or --push-url, not both" if push_host && push_url
|
|
@@ -959,12 +951,6 @@ module Space::Architect
|
|
|
959
951
|
|
|
960
952
|
resolved_harness, resolved_model, resolved_effort =
|
|
961
953
|
resolve_dispatch_harness(lane_entry, model: model, harness: harness, effort: effort, force: force, err: err)
|
|
962
|
-
resolved_allowed_tools, replace_tools, append_tools, tools_provenance =
|
|
963
|
-
resolve_allowed_tools(lane_entry, allowed_tools: allowed_tools, append_allowed_tools: append_allowed_tools)
|
|
964
|
-
err.puts(tools_provenance) if replace_tools || append_tools
|
|
965
|
-
|
|
966
|
-
raise Space::Core::Error, "--push-host is only supported with the claude-code harness" \
|
|
967
|
-
if push_host && resolved_harness != "claude-code"
|
|
968
954
|
|
|
969
955
|
id = iteration_id(entry)
|
|
970
956
|
wt_path = space.path.join(lane_entry["worktree"] || "build/#{id}-#{lane}/wt")
|
|
@@ -979,31 +965,31 @@ module Space::Architect
|
|
|
979
965
|
# and the CLI owns the canonical copy — byte-for-byte, like variant_add.
|
|
980
966
|
copy_and_validate_prompt!(prompt, prompt_path)
|
|
981
967
|
|
|
982
|
-
|
|
968
|
+
# Vendor the builder guard into the lane's build dir (idempotent overwrite) and
|
|
969
|
+
# inject it via -e on every dispatch — blocking and detached alike. The guard
|
|
970
|
+
# denies git-write and bash-unparseable commands before evaluation; the builder
|
|
971
|
+
# itself never needs to remember to load it.
|
|
972
|
+
guard_path = build_dir.join("builder-guard.ts")
|
|
973
|
+
FileUtils.cp(vendored_guard_path, guard_path)
|
|
974
|
+
|
|
983
975
|
harness_obj = Harness.for(resolved_harness, model: resolved_model, max_turns: max_turns, bin: bin,
|
|
984
976
|
config_dir: build_dir, effort: resolved_effort,
|
|
985
|
-
|
|
977
|
+
guard_path: guard_path, force: force, err: err)
|
|
986
978
|
|
|
987
|
-
# Stamp launch time and the resolved harness/model/effort
|
|
979
|
+
# Stamp launch time and the resolved harness/model/effort onto the
|
|
988
980
|
# lane entry: after every preflight validation has passed (a dispatch that raises
|
|
989
981
|
# above records nothing) and before the blocking run or a detached dispatch
|
|
990
982
|
# returns. A re-dispatch overwrites the prior values, so `architect status`
|
|
991
|
-
# always reads what actually ran on the last dispatch.
|
|
992
|
-
# tool grant is recorded the same way, so a denial traces back to a grant a
|
|
993
|
-
# human can read — replace_tools/append_tools are the pre-composition components
|
|
994
|
-
# (not the harness's expanded ClaudeCodeHarness.resolve_tools result), so a later
|
|
995
|
-
# bare re-dispatch re-resolves from the same components instead of compounding.
|
|
983
|
+
# always reads what actually ran on the last dispatch.
|
|
996
984
|
update_architect_block do |b|
|
|
997
985
|
(b["iterations"] || []).each do |s|
|
|
998
986
|
next unless s["name"] == iteration
|
|
999
987
|
(s["lanes"] || []).each do |l|
|
|
1000
988
|
next unless l["name"] == lane
|
|
1001
|
-
l["dispatched_at"]
|
|
1002
|
-
l["harness"]
|
|
1003
|
-
l["model"]
|
|
1004
|
-
l["effort"]
|
|
1005
|
-
l["allowed_tools"] = replace_tools if replace_tools
|
|
1006
|
-
l["append_allowed_tools"] = append_tools if append_tools
|
|
989
|
+
l["dispatched_at"] = now.iso8601
|
|
990
|
+
l["harness"] = resolved_harness
|
|
991
|
+
l["model"] = resolved_model
|
|
992
|
+
l["effort"] = resolved_effort if resolved_effort
|
|
1007
993
|
end
|
|
1008
994
|
end
|
|
1009
995
|
b
|
|
@@ -1028,96 +1014,30 @@ module Space::Architect
|
|
|
1028
1014
|
|
|
1029
1015
|
run_kwargs = { prompt_path: prompt_path, run_log_path: run_log_path, chdir: wt_path }
|
|
1030
1016
|
run_kwargs[:timeout] = timeout if timeout
|
|
1031
|
-
if
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
run_kwargs[:err] = err if quiet
|
|
1036
|
-
end
|
|
1017
|
+
run_kwargs[:push_url] = push_url if push_url
|
|
1018
|
+
run_kwargs[:push_token] = push_token if push_token
|
|
1019
|
+
run_kwargs[:push_client] = push_client if push_client
|
|
1020
|
+
run_kwargs[:err] = err if quiet
|
|
1037
1021
|
exit_code = harness_obj.run(**run_kwargs)
|
|
1038
1022
|
|
|
1039
1023
|
result = { exit_code: exit_code, run_log: run_log_path, report: report_path, worktree: wt_path }
|
|
1040
1024
|
result[:prompt_copied] = prompt_path if prompt
|
|
1041
|
-
result[:timed_out] = true if exit_code == Harness::
|
|
1025
|
+
result[:timed_out] = true if exit_code == Harness::PiHarness::TIMEOUT_EXIT_CODE
|
|
1042
1026
|
result[:created_run_id] = created_run_id if created_run_id
|
|
1043
1027
|
result[:push_url] = push_url if push_url
|
|
1044
1028
|
result
|
|
1045
1029
|
end
|
|
1046
1030
|
end
|
|
1047
1031
|
|
|
1048
|
-
# Submit the lane's builder run as a job to the space-server's queue instead of
|
|
1049
|
-
# running it locally: the sandboxed executor mounts the lane worktree + the repo
|
|
1050
|
-
# checkout at their identical host absolute paths (so the worktree's gitdir
|
|
1051
|
-
# pointer resolves) and runs the harness itself — no local run.jsonl, the
|
|
1052
|
-
# transcript lives server-side. jobs_client: is the injectable seam (mirrors
|
|
1053
|
-
# dispatch's run_creator:).
|
|
1054
|
-
def dispatch_as_job(iteration, lane, host:, token:, backend_url:, model: nil, harness: nil,
|
|
1055
|
-
max_turns: 200, effort: nil, allowed_tools: nil, append_allowed_tools: nil,
|
|
1056
|
-
force: false, quiet: false,
|
|
1057
|
-
job_model: nil, api_key_ref: nil, prompt: nil, jobs_client: nil, now: Time.now)
|
|
1058
|
-
err = quiet ? File.open(File::NULL, "w") : $stderr
|
|
1059
|
-
|
|
1060
|
-
entry = slice_entry(iteration)
|
|
1061
|
-
lane_entry = (entry["lanes"] || []).find { |l| l["name"] == lane }
|
|
1062
|
-
raise Space::Core::Error, "No lane '#{lane}' recorded for iteration '#{iteration}'" unless lane_entry
|
|
1063
|
-
lane_entry = ensure_lane_materialized(iteration, lane)
|
|
1064
|
-
|
|
1065
|
-
resolved_harness, resolved_model, resolved_effort =
|
|
1066
|
-
resolve_dispatch_harness(lane_entry, model: model, harness: harness, effort: effort, force: force, err: err)
|
|
1067
|
-
raise Space::Core::Error, "--as-job only supports the claude-code harness (lane '#{lane}' resolves to '#{resolved_harness}')" \
|
|
1068
|
-
unless resolved_harness == "claude-code"
|
|
1069
|
-
raise Space::Core::Error, "--job-model is required with --as-job" unless job_model
|
|
1070
|
-
|
|
1071
|
-
# #89: same replace/append resolution as the local dispatch path — the sandboxed
|
|
1072
|
-
# executor still runs `claude -p` with harness_args server-side.
|
|
1073
|
-
resolved_allowed_tools, replace_tools, append_tools, tools_provenance =
|
|
1074
|
-
resolve_allowed_tools(lane_entry, allowed_tools: allowed_tools, append_allowed_tools: append_allowed_tools)
|
|
1075
|
-
err.puts(tools_provenance) if replace_tools || append_tools
|
|
1076
|
-
|
|
1077
|
-
id = iteration_id(entry)
|
|
1078
|
-
wt_path = space.path.join(lane_entry["worktree"] || "build/#{id}-#{lane}/wt")
|
|
1079
|
-
raise Space::Core::Error, "Worktree directory does not exist: #{wt_path}" unless wt_path.exist?
|
|
1080
|
-
|
|
1081
|
-
build_dir = space.path.join("build", "#{id}-#{lane}")
|
|
1082
|
-
prompt_path = build_dir.join("prompt.md")
|
|
1083
|
-
copy_and_validate_prompt!(prompt, prompt_path)
|
|
1084
|
-
|
|
1085
|
-
repo_path = space.path.join("repos", lane_entry["repo"])
|
|
1086
|
-
harness_obj = Harness.for(resolved_harness, model: resolved_model, max_turns: max_turns,
|
|
1087
|
-
effort: resolved_effort, allowed_tools: resolved_allowed_tools,
|
|
1088
|
-
force: force, err: err)
|
|
1089
|
-
|
|
1090
|
-
spec = job_spec(iteration: iteration, lane: lane, wt_path: wt_path, build_dir: build_dir,
|
|
1091
|
-
repo_path: repo_path, prompt_content: prompt_path.read, backend_url: backend_url,
|
|
1092
|
-
job_model: job_model, api_key_ref: api_key_ref, harness_args: harness_obj.builder_args)
|
|
1093
|
-
|
|
1094
|
-
client = jobs_client || JobsClient.new(host, token)
|
|
1095
|
-
job_id = client.create(spec)
|
|
1096
|
-
|
|
1097
|
-
# Dispatch bookkeeping, mirroring the local path's dispatched_at stamp: a
|
|
1098
|
-
# re-dispatch overwrites the prior job_id, so `architect status` always
|
|
1099
|
-
# reflects the last dispatch regardless of mode.
|
|
1100
|
-
update_architect_block do |b|
|
|
1101
|
-
(b["iterations"] || []).each do |s|
|
|
1102
|
-
next unless s["name"] == iteration
|
|
1103
|
-
(s["lanes"] || []).each do |l|
|
|
1104
|
-
next unless l["name"] == lane
|
|
1105
|
-
l["dispatched_at"] = now.iso8601
|
|
1106
|
-
l["job_id"] = job_id
|
|
1107
|
-
end
|
|
1108
|
-
end
|
|
1109
|
-
b
|
|
1110
|
-
end
|
|
1111
|
-
|
|
1112
|
-
result = { job_id: job_id, spec: spec }
|
|
1113
|
-
result[:prompt_copied] = prompt_path if prompt
|
|
1114
|
-
result
|
|
1115
|
-
end
|
|
1116
|
-
|
|
1117
1032
|
private
|
|
1118
1033
|
|
|
1119
1034
|
attr_reader :space
|
|
1120
1035
|
|
|
1036
|
+
# The vendored builder-guard extension shipped in the gem (lib/space_architect/pi/).
|
|
1037
|
+
def vendored_guard_path
|
|
1038
|
+
Pathname.new(__dir__).join("pi/builder-guard.ts")
|
|
1039
|
+
end
|
|
1040
|
+
|
|
1121
1041
|
# Compose a commit message. Without a custom message, the canonical default
|
|
1122
1042
|
# (unchanged). With one, a short canonical prefix keeps the loop's commit
|
|
1123
1043
|
# taxonomy grep-able while the author's first line owns the subject; any
|
|
@@ -1685,23 +1605,23 @@ module Space::Architect
|
|
|
1685
1605
|
end
|
|
1686
1606
|
|
|
1687
1607
|
# Resolve harness + model by precedence: explicit value > (dispatch only) the
|
|
1688
|
-
# lane's stored value > space.yaml project.harness/project.model > the
|
|
1689
|
-
#
|
|
1690
|
-
#
|
|
1691
|
-
#
|
|
1692
|
-
#
|
|
1693
|
-
#
|
|
1608
|
+
# lane's stored value > space.yaml project.harness/project.model > the pi
|
|
1609
|
+
# default model. Shared by worktree_add and dispatch so both read the same
|
|
1610
|
+
# defaults. A stored model is only honored when its stored harness still matches
|
|
1611
|
+
# the resolved harness — a dispatch-time harness override drops the old harness's
|
|
1612
|
+
# stored model instead of leaking it into the new harness's run. A stored
|
|
1613
|
+
# legacy harness name survives resolution (so a stale space.yaml
|
|
1614
|
+
# surfaces as Harness.for's actionable pi-only error, not a silent default).
|
|
1694
1615
|
def resolve_harness_model(harness, model, stored_harness: nil, stored_model: nil)
|
|
1695
1616
|
defaults = project_defaults
|
|
1696
|
-
resolved_harness = (harness || stored_harness || defaults["harness"] || "
|
|
1617
|
+
resolved_harness = (harness || stored_harness || defaults["harness"] || "pi").to_s
|
|
1697
1618
|
stored_model = nil unless stored_harness.nil? || stored_harness.to_s == resolved_harness
|
|
1698
|
-
resolved_model = model || stored_model || defaults["model"] || Harness
|
|
1619
|
+
resolved_model = model || stored_model || defaults["model"] || Harness::DEFAULT_MODEL
|
|
1699
1620
|
[resolved_harness, resolved_model]
|
|
1700
1621
|
end
|
|
1701
1622
|
|
|
1702
1623
|
# --prompt: the caller authors the lane prompt anywhere (a fresh scratch file) and
|
|
1703
|
-
# the CLI owns the canonical copy — byte-for-byte, like variant_add.
|
|
1704
|
-
# dispatch and dispatch_as_job so both refuse the same empty/stub prompt.
|
|
1624
|
+
# the CLI owns the canonical copy — byte-for-byte, like variant_add.
|
|
1705
1625
|
def copy_and_validate_prompt!(prompt, prompt_path)
|
|
1706
1626
|
if prompt
|
|
1707
1627
|
src = Pathname.new(prompt)
|
|
@@ -1716,8 +1636,8 @@ module Space::Architect
|
|
|
1716
1636
|
if prompt_content.empty? || prompt_content == PROMPT_STUB.strip
|
|
1717
1637
|
end
|
|
1718
1638
|
|
|
1719
|
-
# Resolve harness/model/effort for a dispatch
|
|
1720
|
-
#
|
|
1639
|
+
# Resolve harness/model/effort for a dispatch, so every dispatch path reads
|
|
1640
|
+
# precedence and thinking-level translation from one place.
|
|
1721
1641
|
def resolve_dispatch_harness(lane_entry, model:, harness:, effort:, force:, err:)
|
|
1722
1642
|
model, suffix_level = Harness.parse_model_suffix(model)
|
|
1723
1643
|
resolved_harness, resolved_model = resolve_harness_model(harness, model,
|
|
@@ -1737,69 +1657,9 @@ module Space::Architect
|
|
|
1737
1657
|
[resolved_harness, resolved_model, resolved_effort]
|
|
1738
1658
|
end
|
|
1739
1659
|
|
|
1740
|
-
# #89/AC1-AC5: resolve the claude-code --allowedTools grant for a dispatch (local or
|
|
1741
|
-
# --as-job), shared so both paths resolve precedence identically. The CLI flag beats
|
|
1742
|
-
# the lane's recorded allowed_tools:/append_allowed_tools: (frozen-declaration or a
|
|
1743
|
-
# prior dispatch's stamp) — resolved independently for replace vs. append, so a flag
|
|
1744
|
-
# on one axis and a lane value on the other both apply (Objective A). Meaningless for
|
|
1745
|
-
# opencode/pi (no equivalent grant mechanism) — still resolved/recorded for visibility,
|
|
1746
|
-
# but Harness.for only wires it into the claude-code branch's argv.
|
|
1747
|
-
def resolve_allowed_tools(lane_entry, allowed_tools:, append_allowed_tools:)
|
|
1748
|
-
replace, replace_from =
|
|
1749
|
-
if allowed_tools
|
|
1750
|
-
[allowed_tools, "--allowed-tools flag"]
|
|
1751
|
-
elsif lane_entry["allowed_tools"]
|
|
1752
|
-
[lane_entry["allowed_tools"], "lane's allowed_tools:"]
|
|
1753
|
-
else
|
|
1754
|
-
[nil, "default"]
|
|
1755
|
-
end
|
|
1756
|
-
|
|
1757
|
-
append, append_from =
|
|
1758
|
-
if append_allowed_tools
|
|
1759
|
-
[append_allowed_tools, "--append-allowed-tools flag"]
|
|
1760
|
-
elsif lane_entry["append_allowed_tools"]
|
|
1761
|
-
[lane_entry["append_allowed_tools"], "lane's append_allowed_tools:"]
|
|
1762
|
-
end
|
|
1763
|
-
|
|
1764
|
-
final = Harness::ClaudeCodeHarness.resolve_tools(replace: replace, append: append)
|
|
1765
|
-
provenance = "allowed-tools: #{replace_from}#{append_from ? " + #{append_from}" : ""} → #{final}"
|
|
1766
|
-
[final, replace, append, provenance]
|
|
1767
|
-
end
|
|
1768
|
-
|
|
1769
|
-
# Compose a dispatch --as-job spec per the space-server's job contract: prompt +
|
|
1770
|
-
# workspace.dir (the lane worktree) + environment (deps/network/mounts/env or
|
|
1771
|
-
# secrets) + harness (claude/backend/args) + provenance. mounts are src:dst with
|
|
1772
|
-
# src == dst — the sandboxed executor requires the lane worktree AND the repo
|
|
1773
|
-
# checkout mounted at their identical host absolute paths for the worktree's
|
|
1774
|
-
# gitdir pointer to resolve.
|
|
1775
|
-
def job_spec(iteration:, lane:, wt_path:, build_dir:, repo_path:, prompt_content:, backend_url:,
|
|
1776
|
-
job_model:, api_key_ref:, harness_args:)
|
|
1777
|
-
harness = { "type" => "claude", "backend" => { "base_url" => backend_url }, "args" => harness_args }
|
|
1778
|
-
harness["model"] = job_model if job_model
|
|
1779
|
-
harness["backend"]["api_key_ref"] = api_key_ref if api_key_ref
|
|
1780
|
-
|
|
1781
|
-
environment = {
|
|
1782
|
-
"env" => api_key_ref ? {} : { "ANTHROPIC_API_KEY" => "unused-for-keyless-backends" },
|
|
1783
|
-
"deps" => ["git"],
|
|
1784
|
-
"permissions" => {
|
|
1785
|
-
"network" => true,
|
|
1786
|
-
"mounts" => ["#{build_dir}:#{build_dir}", "#{repo_path}:#{repo_path}"]
|
|
1787
|
-
}
|
|
1788
|
-
}
|
|
1789
|
-
environment["secrets"] = [{ "ref" => api_key_ref, "name" => "ANTHROPIC_API_KEY" }] if api_key_ref
|
|
1790
|
-
|
|
1791
|
-
{
|
|
1792
|
-
"prompt" => prompt_content,
|
|
1793
|
-
"workspace" => { "dir" => wt_path.to_s },
|
|
1794
|
-
"environment" => environment,
|
|
1795
|
-
"harness" => harness,
|
|
1796
|
-
"provenance" => { "space" => Space::Core::Slugger.slug(space.title), "iteration" => iteration, "lane" => lane }
|
|
1797
|
-
}
|
|
1798
|
-
end
|
|
1799
|
-
|
|
1800
1660
|
def recorded_lane_fields(lane_entry)
|
|
1801
1661
|
{
|
|
1802
|
-
harness: lane_entry["harness"] || "
|
|
1662
|
+
harness: lane_entry["harness"] || "pi",
|
|
1803
1663
|
model: lane_entry["model"],
|
|
1804
1664
|
variant: lane_entry["variant"] || false,
|
|
1805
1665
|
effort: lane_entry["effort"]
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
require "json"
|
|
4
4
|
require "fileutils"
|
|
5
|
-
require "space_src/launchd/agent"
|
|
6
5
|
require_relative "../jobs_client"
|
|
7
6
|
require_relative "../conversations_client"
|
|
8
7
|
require_relative "../session_sync"
|
|
@@ -12,8 +11,8 @@ module Space::Architect
|
|
|
12
11
|
# Optional loop-phase DSL on the shared command base, read by
|
|
13
12
|
# Space::Core::CLI::Help to group the help listing. A command may declare
|
|
14
13
|
# `phase <order>, "<Label>"`; groups, and members within a group, sort by
|
|
15
|
-
# <order>. Only architect commands declare one —
|
|
16
|
-
#
|
|
14
|
+
# <order>. Only architect commands declare one — commands without a phase
|
|
15
|
+
# render as the single ungrouped default listing (unchanged).
|
|
17
16
|
class BaseCommand
|
|
18
17
|
def self.phase(order = nil, label = nil)
|
|
19
18
|
return @phase if order.nil?
|
|
@@ -178,18 +177,10 @@ module Space::Architect
|
|
|
178
177
|
nn = s["ordinal"] ? format("%02d", s["ordinal"]) : "-"
|
|
179
178
|
lane_list = s["lanes"] || []
|
|
180
179
|
lanes_str = lane_list.map do |l|
|
|
181
|
-
h = l["harness"] || "
|
|
182
|
-
m = l["model"] || Harness
|
|
180
|
+
h = l["harness"] || "pi"
|
|
181
|
+
m = l["model"] || Harness::DEFAULT_MODEL
|
|
183
182
|
eff = l["effort"] ? "·#{l['effort']}" : ""
|
|
184
|
-
#
|
|
185
|
-
# values — only when it diverges from the harness default, to keep the
|
|
186
|
-
# common case (no lane touches it) uncluttered. Meaningless outside
|
|
187
|
-
# claude-code (no equivalent grant mechanism), so shown only there.
|
|
188
|
-
tools = if h == "claude-code"
|
|
189
|
-
resolved = Harness::ClaudeCodeHarness.resolve_tools(replace: l["allowed_tools"], append: l["append_allowed_tools"])
|
|
190
|
-
resolved == Harness::ClaudeCodeHarness::ALLOWED_TOOLS ? "" : "·tools:#{resolved}"
|
|
191
|
-
end
|
|
192
|
-
"#{l['name']}(#{l['repo']}·#{h}·#{m}#{eff}#{tools})"
|
|
183
|
+
"#{l['name']}(#{l['repo']}·#{h}·#{m}#{eff})"
|
|
193
184
|
end.join(", ")
|
|
194
185
|
lanes = lane_list.any? { |l| l["variant"] } ? "variant: #{lanes_str}" : lanes_str
|
|
195
186
|
lanes = "#{lanes} → winner: #{s['winner']}" if s["winner"]
|
|
@@ -493,107 +484,68 @@ module Space::Architect
|
|
|
493
484
|
argument :lane, required: true, desc: "Lane name"
|
|
494
485
|
argument :space, required: false, desc: "Space identifier (default: $PWD)"
|
|
495
486
|
option :prompt, default: nil, desc: "Read the lane prompt from this file (copied byte-for-byte to build/<id>-<lane>/prompt.md)"
|
|
496
|
-
option :model, default: nil, desc: "Builder model to pin (default: the lane's stored model, else space.yaml project.model, else the
|
|
487
|
+
option :model, default: nil, desc: "Builder model to pin (default: the lane's stored model, else space.yaml project.model, else the pi default #{Harness::DEFAULT_MODEL}). Any provider/tier; pin a full id, not a floating alias"
|
|
497
488
|
option :max_turns, default: "200", desc: "Max turns for the builder"
|
|
498
|
-
option :harness, default: nil, desc: "Harness
|
|
499
|
-
option :
|
|
500
|
-
option :
|
|
501
|
-
option :
|
|
502
|
-
option :
|
|
503
|
-
option :
|
|
504
|
-
option :
|
|
505
|
-
option :force_thinking, default: nil, desc: "Force the literal level onto the harness flag, skipping architect's clamp — alias for --force-effort/--force-reasoning (dispatch only; the binary's rejection is final)"
|
|
506
|
-
option :force_reasoning, default: nil, desc: "Force the literal level onto the harness flag, skipping architect's clamp — alias for --force-effort/--force-thinking (dispatch only; the binary's rejection is final)"
|
|
489
|
+
option :harness, default: nil, desc: "Harness (pi)"
|
|
490
|
+
option :effort, default: nil, desc: "Thinking/reasoning effort level — alias for --thinking/--reasoning (off, minimal, low, medium, high, xhigh, max)"
|
|
491
|
+
option :thinking, default: nil, desc: "Thinking/reasoning effort level — alias for --effort/--reasoning (off, minimal, low, medium, high, xhigh, max)"
|
|
492
|
+
option :reasoning, default: nil, desc: "Thinking/reasoning effort level — alias for --effort/--thinking (off, minimal, low, medium, high, xhigh, max)"
|
|
493
|
+
option :force_effort, default: nil, desc: "Force the literal level onto the --thinking flag, skipping architect's validation — alias for --force-thinking/--force-reasoning (dispatch only; the binary's rejection is final)"
|
|
494
|
+
option :force_thinking, default: nil, desc: "Force the literal level onto the --thinking flag, skipping architect's validation — alias for --force-effort/--force-reasoning (dispatch only; the binary's rejection is final)"
|
|
495
|
+
option :force_reasoning, default: nil, desc: "Force the literal level onto the --thinking flag, skipping architect's validation — alias for --force-effort/--force-thinking (dispatch only; the binary's rejection is final)"
|
|
507
496
|
option :quiet, type: :boolean, default: false, desc: "Suppress thinking-translation and harness run-time warn lines (liveness/push) on $stderr for this dispatch"
|
|
508
497
|
option :detach, type: :boolean, default: false, desc: "Detach the builder process (returns immediately with PID; poll report for completion)"
|
|
509
498
|
option :timeout, default: "14400", desc: "Wall-clock timeout in seconds (0 disables; default 4h); foreground only"
|
|
510
499
|
option :push_url, default: nil, desc: "HTTP endpoint for streaming push (POST body to this URL)"
|
|
511
500
|
option :push_token, default: nil, desc: "Bearer token for push endpoint authorization"
|
|
512
501
|
option :push_host, default: nil, desc: "Base URL of the ingest server; the CLI creates a run via POST <host>/runs and streams to /runs/<id>/ingest (requires --push-token)"
|
|
513
|
-
option :as_job, type: :boolean, default: false, desc: "Submit the run as a job to the space-server's queue instead of running locally (sandboxed executor; requires --host/--token/--backend-url)"
|
|
514
|
-
option :host, default: nil, desc: "Base URL of the space-server (required with --as-job)"
|
|
515
|
-
option :token, default: nil, desc: "Bearer token for authorization (required with --as-job)"
|
|
516
|
-
option :backend_url, default: nil, desc: "Base URL of the harness backend the sandboxed job talks to (required with --as-job)"
|
|
517
|
-
option :job_model, default: nil, desc: "Model pinned for the sandboxed job's harness — distinct from --model, which is meaningless against a non-Anthropic backend"
|
|
518
|
-
option :api_key_ref, default: nil, desc: "op:// reference resolved into ANTHROPIC_API_KEY server-side (--as-job only; omit for keyless backends)"
|
|
519
502
|
|
|
520
503
|
def call(iteration:, lane:, space: nil, prompt: nil, model: nil,
|
|
521
504
|
max_turns: "200", harness: nil, effort: nil, thinking: nil, reasoning: nil,
|
|
522
|
-
allowed_tools: nil, append_allowed_tools: nil,
|
|
523
505
|
force_effort: nil, force_thinking: nil, force_reasoning: nil, quiet: false, detach: false,
|
|
524
|
-
timeout: "14400", push_url: nil, push_token: nil, push_host: nil,
|
|
525
|
-
as_job: false, host: nil, token: nil, backend_url: nil, job_model: nil, api_key_ref: nil, **opts)
|
|
506
|
+
timeout: "14400", push_url: nil, push_token: nil, push_host: nil, **opts)
|
|
526
507
|
setup_terminal(**opts.slice(:color, :colors))
|
|
527
508
|
handle_errors do
|
|
528
509
|
level = resolve_thinking_alias(effort: effort, thinking: thinking, reasoning: reasoning)
|
|
529
510
|
forced_level = resolve_force_thinking_alias(force_effort: force_effort,
|
|
530
511
|
force_thinking: force_thinking, force_reasoning: force_reasoning)
|
|
531
512
|
|
|
532
|
-
if as_job
|
|
533
|
-
Jobs.require_credentials!(host, token)
|
|
534
|
-
raise Space::Core::Error, "--backend-url is required with --as-job" unless backend_url
|
|
535
|
-
raise Space::Core::Error, "--job-model is required with --as-job" unless job_model
|
|
536
|
-
raise Space::Core::Error, "--as-job cannot be combined with --push-url/--push-token/--push-host/--detach" \
|
|
537
|
-
if push_url || push_token || push_host || detach
|
|
538
|
-
end
|
|
539
|
-
|
|
540
513
|
render(store.find(space)) do |sp|
|
|
541
514
|
project = ArchitectProject.new(space: sp)
|
|
542
515
|
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
terminal.say "
|
|
558
|
-
terminal.say "
|
|
516
|
+
kwargs = { max_turns: max_turns.to_i, detach: detach }
|
|
517
|
+
kwargs[:prompt] = prompt if prompt
|
|
518
|
+
kwargs[:model] = model if model
|
|
519
|
+
kwargs[:harness] = harness if harness
|
|
520
|
+
kwargs[:effort] = forced_level || level if forced_level || level
|
|
521
|
+
kwargs[:force] = true if forced_level
|
|
522
|
+
kwargs[:quiet] = true if quiet
|
|
523
|
+
kwargs[:timeout] = timeout.to_i unless detach
|
|
524
|
+
kwargs[:push_url] = push_url if push_url
|
|
525
|
+
kwargs[:push_token] = push_token if push_token
|
|
526
|
+
kwargs[:push_host] = push_host if push_host
|
|
527
|
+
res = project.dispatch(iteration, lane, **kwargs)
|
|
528
|
+
terminal.say "Prompt: #{prompt} → #{terminal.path(res[:prompt_copied])}" if res[:prompt_copied]
|
|
529
|
+
if detach
|
|
530
|
+
terminal.say "PID: #{res[:pid]}"
|
|
531
|
+
terminal.say "Run log: #{terminal.path(res[:run_log])}"
|
|
532
|
+
terminal.say "Report: #{terminal.path(res[:report])}"
|
|
533
|
+
terminal.say "Dispatched detached — poll #{terminal.path(res[:report])} for completion"
|
|
559
534
|
CLI.record_outcome(Outcome.new(exit_code: 0))
|
|
535
|
+
elsif res[:timed_out]
|
|
536
|
+
terminal.say "Run log: #{terminal.path(res[:run_log])}"
|
|
537
|
+
terminal.say "Report: #{terminal.path(res[:report])}"
|
|
538
|
+
terminal.say "Builder TIMED OUT after #{timeout}s — process group killed. Re-dispatch (lanes are cheap)."
|
|
539
|
+
CLI.record_outcome(Outcome.new(exit_code: res[:exit_code]))
|
|
560
540
|
else
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
kwargs[:append_allowed_tools] = append_allowed_tools if append_allowed_tools
|
|
568
|
-
kwargs[:force] = true if forced_level
|
|
569
|
-
kwargs[:quiet] = true if quiet
|
|
570
|
-
kwargs[:timeout] = timeout.to_i unless detach
|
|
571
|
-
kwargs[:push_url] = push_url if push_url
|
|
572
|
-
kwargs[:push_token] = push_token if push_token
|
|
573
|
-
kwargs[:push_host] = push_host if push_host
|
|
574
|
-
res = project.dispatch(iteration, lane, **kwargs)
|
|
575
|
-
terminal.say "Prompt: #{prompt} → #{terminal.path(res[:prompt_copied])}" if res[:prompt_copied]
|
|
576
|
-
if detach
|
|
577
|
-
terminal.say "PID: #{res[:pid]}"
|
|
578
|
-
terminal.say "Run log: #{terminal.path(res[:run_log])}"
|
|
579
|
-
terminal.say "Report: #{terminal.path(res[:report])}"
|
|
580
|
-
terminal.say "Dispatched detached — poll #{terminal.path(res[:report])} for completion"
|
|
581
|
-
CLI.record_outcome(Outcome.new(exit_code: 0))
|
|
582
|
-
elsif res[:timed_out]
|
|
583
|
-
terminal.say "Run log: #{terminal.path(res[:run_log])}"
|
|
584
|
-
terminal.say "Report: #{terminal.path(res[:report])}"
|
|
585
|
-
terminal.say "Builder TIMED OUT after #{timeout}s — process group killed. Re-dispatch (lanes are cheap)."
|
|
586
|
-
CLI.record_outcome(Outcome.new(exit_code: res[:exit_code]))
|
|
587
|
-
else
|
|
588
|
-
terminal.say "Run log: #{terminal.path(res[:run_log])}"
|
|
589
|
-
terminal.say "Report: #{terminal.path(res[:report])}"
|
|
590
|
-
terminal.say "Ingest URL: #{res[:push_url]}" if res[:push_url]
|
|
591
|
-
terminal.say "Builder exited with status #{res[:exit_code]}"
|
|
592
|
-
unless res[:report].exist? && !res[:report].read.strip.empty?
|
|
593
|
-
terminal.say "WARNING: no report at #{terminal.path(res[:report])} — the lane produced no deliverable."
|
|
594
|
-
end
|
|
595
|
-
CLI.record_outcome(Outcome.new(exit_code: res[:exit_code]))
|
|
541
|
+
terminal.say "Run log: #{terminal.path(res[:run_log])}"
|
|
542
|
+
terminal.say "Report: #{terminal.path(res[:report])}"
|
|
543
|
+
terminal.say "Ingest URL: #{res[:push_url]}" if res[:push_url]
|
|
544
|
+
terminal.say "Builder exited with status #{res[:exit_code]}"
|
|
545
|
+
unless res[:report].exist? && !res[:report].read.strip.empty?
|
|
546
|
+
terminal.say "WARNING: no report at #{terminal.path(res[:report])} — the lane produced no deliverable."
|
|
596
547
|
end
|
|
548
|
+
CLI.record_outcome(Outcome.new(exit_code: res[:exit_code]))
|
|
597
549
|
end
|
|
598
550
|
end
|
|
599
551
|
end
|
|
@@ -847,7 +799,8 @@ module Space::Architect
|
|
|
847
799
|
terminal.say result[:command]
|
|
848
800
|
terminal.say ""
|
|
849
801
|
terminal.say "Diagnostics:"
|
|
850
|
-
terminal.say " space-architect #{Space::
|
|
802
|
+
terminal.say " space-architect #{Space::Architect::VERSION}"
|
|
803
|
+
terminal.say " space-cadet #{Space::Core::VERSION}"
|
|
851
804
|
terminal.say " ruby #{RUBY_VERSION} (#{RUBY_PLATFORM})"
|
|
852
805
|
terminal.say " space: #{space.id} — #{space.title}" if space
|
|
853
806
|
CLI.record_outcome(Outcome.new(exit_code: 0))
|
|
@@ -858,7 +811,7 @@ module Space::Architect
|
|
|
858
811
|
class InstallSkills < BaseCommand
|
|
859
812
|
desc "Install bundled skills (architect, architect-research, architect-vocabulary) for a harness"
|
|
860
813
|
phase 53, "Project"
|
|
861
|
-
option :provider, default: "claude", desc: "
|
|
814
|
+
option :provider, default: "claude", desc: "Skill install target (default: claude; validated by the installer)"
|
|
862
815
|
option :project, type: :boolean, default: false, desc: "Install to CWD instead of global"
|
|
863
816
|
option :force, type: :boolean, default: false, desc: "Overwrite existing skills that differ"
|
|
864
817
|
option :dry_run, type: :boolean, default: false, desc: "Print what would happen without writing files"
|
|
@@ -886,11 +839,11 @@ module Space::Architect
|
|
|
886
839
|
argument :lane, required: true, desc: "Lane name"
|
|
887
840
|
argument :space, required: false, desc: "Space identifier (default: $PWD)"
|
|
888
841
|
option :base, default: nil, desc: "Base ref (default: HEAD of repo)"
|
|
889
|
-
option :harness, default: nil, desc: "Harness (
|
|
890
|
-
option :model, default: nil, desc: "Model; a trailing :<level> suffix (e.g. foo:high) is parsed into --effort (default: space.yaml project.model, else the
|
|
891
|
-
option :effort, default: nil, desc: "Thinking/reasoning effort level — alias for --thinking/--reasoning (off, minimal, low, medium, high, xhigh, max)
|
|
892
|
-
option :thinking, default: nil, desc: "Thinking/reasoning effort level — alias for --effort/--reasoning (off, minimal, low, medium, high, xhigh, max)
|
|
893
|
-
option :reasoning, default: nil, desc: "Thinking/reasoning effort level — alias for --effort/--thinking (off, minimal, low, medium, high, xhigh, max)
|
|
842
|
+
option :harness, default: nil, desc: "Harness (pi; default: space.yaml project.harness, else pi)"
|
|
843
|
+
option :model, default: nil, desc: "Model; a trailing :<level> suffix (e.g. foo:high) is parsed into --effort (default: space.yaml project.model, else the pi default #{Harness::DEFAULT_MODEL})"
|
|
844
|
+
option :effort, default: nil, desc: "Thinking/reasoning effort level — alias for --thinking/--reasoning (off, minimal, low, medium, high, xhigh, max)"
|
|
845
|
+
option :thinking, default: nil, desc: "Thinking/reasoning effort level — alias for --effort/--reasoning (off, minimal, low, medium, high, xhigh, max)"
|
|
846
|
+
option :reasoning, default: nil, desc: "Thinking/reasoning effort level — alias for --effort/--thinking (off, minimal, low, medium, high, xhigh, max)"
|
|
894
847
|
option :quiet, type: :boolean, default: false, desc: "Suppress the thinking-translation inform line on $stderr"
|
|
895
848
|
option :touch, default: nil, desc: "Comma-separated file globs the lane may touch (records its touch_set for in-bounds + merge checks)"
|
|
896
849
|
option :force, type: :boolean, default: false, desc: "Clear and re-create a stale (unregistered) worktree directory"
|
|
@@ -970,7 +923,7 @@ module Space::Architect
|
|
|
970
923
|
argument :repo, required: true, desc: "Repo name (under repos/)"
|
|
971
924
|
argument :iteration, required: true, desc: "Iteration name"
|
|
972
925
|
argument :space, required: false, desc: "Space identifier (default: $PWD)"
|
|
973
|
-
option :pairs, required: true, desc: "Comma-separated harness[:model] pairs (e.g.
|
|
926
|
+
option :pairs, required: true, desc: "Comma-separated harness[:model] pairs (e.g. pi,pi:#{Harness::DEFAULT_MODEL})"
|
|
974
927
|
option :base, default: nil, desc: "Base ref (default: HEAD of repo)"
|
|
975
928
|
option :prompt, default: nil, desc: "Prompt file to fan-out byte-identical to each variant"
|
|
976
929
|
|
|
@@ -1215,6 +1168,17 @@ module Space::Architect
|
|
|
1215
1168
|
end
|
|
1216
1169
|
|
|
1217
1170
|
def format_failure(f) = f.is_a?(Hash) ? f.inspect : f.to_s
|
|
1171
|
+
|
|
1172
|
+
# The launchd-agent seam is repo-tender's — a soft dependency, never a
|
|
1173
|
+
# gemspec runtime dep. Required lazily so `sessions agent …` degrades
|
|
1174
|
+
# with an actionable hint instead of failing at boot when the gem is
|
|
1175
|
+
# absent; handle_errors turns the raise into a red message + exit 1.
|
|
1176
|
+
def agent
|
|
1177
|
+
require "repo_tender"
|
|
1178
|
+
RepoTender::Launchd::Agent
|
|
1179
|
+
rescue LoadError
|
|
1180
|
+
raise Space::Core::Error, "sessions agent needs the repo-tender gem — install it with: gem install repo-tender"
|
|
1181
|
+
end
|
|
1218
1182
|
end
|
|
1219
1183
|
|
|
1220
1184
|
class Install < BaseCommand
|
|
@@ -1243,7 +1207,7 @@ module Space::Architect
|
|
|
1243
1207
|
File.write(pp, xml)
|
|
1244
1208
|
File.chmod(0o600, pp)
|
|
1245
1209
|
|
|
1246
|
-
result =
|
|
1210
|
+
result = agent.new(label: SessionSync::LABEL).install(pp)
|
|
1247
1211
|
raise Space::Core::Error, "bootstrap failed: #{format_failure(result.failure)}" if result.failure?
|
|
1248
1212
|
|
|
1249
1213
|
terminal.say "Installed: #{terminal.path(pp)}"
|
|
@@ -1260,7 +1224,7 @@ module Space::Architect
|
|
|
1260
1224
|
setup_terminal(**opts.slice(:color, :colors))
|
|
1261
1225
|
handle_errors do
|
|
1262
1226
|
pp = plist_path
|
|
1263
|
-
result =
|
|
1227
|
+
result = agent.new(label: SessionSync::LABEL).uninstall
|
|
1264
1228
|
terminal.say "bootout reported: #{format_failure(result.failure)}" if result.failure?
|
|
1265
1229
|
|
|
1266
1230
|
if File.exist?(pp)
|
|
@@ -1281,7 +1245,7 @@ module Space::Architect
|
|
|
1281
1245
|
def call(**opts)
|
|
1282
1246
|
setup_terminal(**opts.slice(:color, :colors))
|
|
1283
1247
|
handle_errors do
|
|
1284
|
-
result =
|
|
1248
|
+
result = agent.new(label: SessionSync::LABEL).status
|
|
1285
1249
|
raise Space::Core::Error, "status failed: #{format_failure(result.failure)}" if result.failure?
|
|
1286
1250
|
|
|
1287
1251
|
s = result.success
|
|
@@ -1303,6 +1267,8 @@ end
|
|
|
1303
1267
|
# Loop-phase declarations above sort the architect help listing; its namespaces
|
|
1304
1268
|
# (brief/worktree/variant/research) declare no phase and list under this header.
|
|
1305
1269
|
Space::Core::CLI::Help.trailing_group_label = "Groups"
|
|
1270
|
+
Space::Core::CLI::Help.product_name = "architect"
|
|
1271
|
+
Space::Core::CLI::Help.product_version = Space::Architect::VERSION
|
|
1306
1272
|
|
|
1307
1273
|
Space::Architect::CLI::Registry.register "init", Space::Architect::CLI::Architect::Init
|
|
1308
1274
|
Space::Architect::CLI::Registry.register "ground", Space::Architect::CLI::Architect::Ground
|
|
@@ -8,7 +8,7 @@ module Space::Architect
|
|
|
8
8
|
desc "Dispatch detached read-only research lanes (one per prompt file)"
|
|
9
9
|
argument :prompts, type: :array, required: true,
|
|
10
10
|
desc: "Prompt file(s) to dispatch (space-separated paths)"
|
|
11
|
-
option :model, default: nil, desc: "Researcher model override (default: the
|
|
11
|
+
option :model, default: nil, desc: "Researcher model override (default: the pi default #{Space::Architect::Harness::DEFAULT_MODEL})"
|
|
12
12
|
option :max_turns, default: "40", desc: "Max turns per researcher"
|
|
13
13
|
|
|
14
14
|
def call(prompts:, model: nil, max_turns: "40", **opts)
|