cortexshift 0.1.0__py3-none-any.whl
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.
- cortexshift/__init__.py +10 -0
- cortexshift/__main__.py +6 -0
- cortexshift/adapters/__init__.py +22 -0
- cortexshift/adapters/command_runner.py +116 -0
- cortexshift/adapters/discovery.py +55 -0
- cortexshift/adapters/git/__init__.py +10 -0
- cortexshift/adapters/git/inspector.py +321 -0
- cortexshift/adapters/git/parser.py +140 -0
- cortexshift/adapters/headless_runner.py +92 -0
- cortexshift/adapters/process_runner.py +56 -0
- cortexshift/adapters/providers/__init__.py +4 -0
- cortexshift/adapters/providers/antigravity.py +530 -0
- cortexshift/adapters/providers/claude.py +375 -0
- cortexshift/adapters/providers/codex.py +434 -0
- cortexshift/adapters/sqlite/__init__.py +10 -0
- cortexshift/adapters/sqlite/migrations.py +268 -0
- cortexshift/adapters/sqlite/store.py +914 -0
- cortexshift/adapters/workspace_lease.py +123 -0
- cortexshift/application/__init__.py +42 -0
- cortexshift/application/checkpoint_builder.py +218 -0
- cortexshift/application/checkpoint_service.py +273 -0
- cortexshift/application/doctor.py +80 -0
- cortexshift/application/handoff_builder.py +281 -0
- cortexshift/application/handoff_renderer.py +430 -0
- cortexshift/application/handoff_service.py +66 -0
- cortexshift/application/init_service.py +86 -0
- cortexshift/application/locator.py +48 -0
- cortexshift/application/native_session.py +65 -0
- cortexshift/application/recovery_service.py +235 -0
- cortexshift/application/repository_service.py +146 -0
- cortexshift/application/resume_service.py +124 -0
- cortexshift/application/run_service.py +270 -0
- cortexshift/application/session_launcher.py +183 -0
- cortexshift/application/session_service.py +63 -0
- cortexshift/application/source_session.py +62 -0
- cortexshift/application/status_service.py +73 -0
- cortexshift/application/switch_service.py +671 -0
- cortexshift/application/task_service.py +201 -0
- cortexshift/application/task_workspace.py +152 -0
- cortexshift/cli/__init__.py +5 -0
- cortexshift/cli/app.py +2477 -0
- cortexshift/domain/__init__.py +153 -0
- cortexshift/domain/checkpoint.py +174 -0
- cortexshift/domain/doctor.py +68 -0
- cortexshift/domain/errors.py +277 -0
- cortexshift/domain/git.py +102 -0
- cortexshift/domain/handoff.py +241 -0
- cortexshift/domain/identifiers.py +27 -0
- cortexshift/domain/launch.py +58 -0
- cortexshift/domain/mcp_binding.py +81 -0
- cortexshift/domain/native_session.py +19 -0
- cortexshift/domain/project.py +37 -0
- cortexshift/domain/provider.py +67 -0
- cortexshift/domain/session.py +92 -0
- cortexshift/domain/status.py +40 -0
- cortexshift/domain/task.py +191 -0
- cortexshift/mcp/__init__.py +38 -0
- cortexshift/mcp/context.py +165 -0
- cortexshift/mcp/facade.py +513 -0
- cortexshift/mcp/models.py +178 -0
- cortexshift/mcp/resources.py +45 -0
- cortexshift/mcp/server.py +52 -0
- cortexshift/mcp/tools.py +176 -0
- cortexshift/ports/__init__.py +39 -0
- cortexshift/ports/checkpoint_store.py +45 -0
- cortexshift/ports/command_runner.py +56 -0
- cortexshift/ports/discovery.py +41 -0
- cortexshift/ports/handoff_delivery.py +91 -0
- cortexshift/ports/handoff_store.py +43 -0
- cortexshift/ports/headless_runner.py +58 -0
- cortexshift/ports/native_session.py +20 -0
- cortexshift/ports/process_runner.py +31 -0
- cortexshift/ports/provider.py +152 -0
- cortexshift/ports/repository.py +44 -0
- cortexshift/ports/session_store.py +27 -0
- cortexshift/ports/state_store.py +55 -0
- cortexshift/ports/workspace_lease.py +39 -0
- cortexshift/tui/__init__.py +24 -0
- cortexshift/tui/actions.py +58 -0
- cortexshift/tui/app.py +1051 -0
- cortexshift/tui/coordinator.py +173 -0
- cortexshift/tui/cortexshift.tcss +258 -0
- cortexshift/tui/facade.py +614 -0
- cortexshift/tui/modals.py +594 -0
- cortexshift/tui/models.py +503 -0
- cortexshift/tui/screens/__init__.py +81 -0
- cortexshift/tui/screens/checkpoints.py +188 -0
- cortexshift/tui/screens/handoffs.py +180 -0
- cortexshift/tui/screens/help.py +117 -0
- cortexshift/tui/screens/overview.py +200 -0
- cortexshift/tui/screens/providers.py +169 -0
- cortexshift/tui/screens/repository.py +143 -0
- cortexshift/tui/screens/sessions.py +146 -0
- cortexshift/tui/screens/task.py +174 -0
- cortexshift/tui/widgets.py +209 -0
- cortexshift-0.1.0.dist-info/METADATA +202 -0
- cortexshift-0.1.0.dist-info/RECORD +100 -0
- cortexshift-0.1.0.dist-info/WHEEL +4 -0
- cortexshift-0.1.0.dist-info/entry_points.txt +2 -0
- cortexshift-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
"""Claude Code native CLI probe and discovery adapter."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import re
|
|
5
|
+
import shutil
|
|
6
|
+
import sys
|
|
7
|
+
from collections.abc import Callable
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any
|
|
10
|
+
from uuid import uuid4
|
|
11
|
+
|
|
12
|
+
from cortexshift.domain.doctor import AuthenticationStatus, ProviderDiagnostic
|
|
13
|
+
from cortexshift.domain.errors import NativeResumeError
|
|
14
|
+
from cortexshift.domain.launch import LaunchSpecification
|
|
15
|
+
from cortexshift.domain.mcp_binding import McpSessionBinding
|
|
16
|
+
from cortexshift.domain.native_session import NativeSessionCapabilities, valid_native_id
|
|
17
|
+
from cortexshift.domain.provider import PROVIDER_CLAUDE, ProviderCapabilities, ProviderId
|
|
18
|
+
from cortexshift.ports.command_runner import CommandRunner
|
|
19
|
+
from cortexshift.ports.discovery import ProviderProbe
|
|
20
|
+
from cortexshift.ports.handoff_delivery import (
|
|
21
|
+
HandoffDeliveryPreparation,
|
|
22
|
+
HandoffDeliveryStrategy,
|
|
23
|
+
ProviderHandoffAdapter,
|
|
24
|
+
)
|
|
25
|
+
from cortexshift.ports.provider import ProviderRuntimeAdapter
|
|
26
|
+
|
|
27
|
+
_VERSION_RE = re.compile(r"(\d+\.\d+(?:\.\d+)?(?:[-.][a-zA-Z0-9]+)?)")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _extract_version(text: str) -> str | None:
|
|
31
|
+
"""Extract a clean semver-like version string from CLI output."""
|
|
32
|
+
match = _VERSION_RE.search(text)
|
|
33
|
+
if match:
|
|
34
|
+
return match.group(1)
|
|
35
|
+
first_line = text.splitlines()[0].strip() if text else ""
|
|
36
|
+
return first_line[:32] if first_line and len(first_line) <= 32 else None
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _classify_claude_auth(stdout: str, stderr: str, exit_code: int) -> AuthenticationStatus:
|
|
40
|
+
"""Classify Claude auth status conservatively without leaking sensitive output."""
|
|
41
|
+
combined = f"{stdout}\n{stderr}".lower()
|
|
42
|
+
|
|
43
|
+
# Try parsing stdout as JSON if formatted as such
|
|
44
|
+
if stdout.strip().startswith("{"):
|
|
45
|
+
try:
|
|
46
|
+
data = json.loads(stdout)
|
|
47
|
+
if isinstance(data, dict):
|
|
48
|
+
if data.get("loggedIn") is True or data.get("status") == "authenticated":
|
|
49
|
+
return AuthenticationStatus.AUTHENTICATED
|
|
50
|
+
if data.get("loggedIn") is False or data.get("status") == "unauthenticated":
|
|
51
|
+
return AuthenticationStatus.NOT_AUTHENTICATED
|
|
52
|
+
except json.JSONDecodeError:
|
|
53
|
+
pass
|
|
54
|
+
|
|
55
|
+
# Check for definitive unauthenticated markers
|
|
56
|
+
if (
|
|
57
|
+
"not logged in" in combined
|
|
58
|
+
or "logged out" in combined
|
|
59
|
+
or "login required" in combined
|
|
60
|
+
or "run claude auth login" in combined
|
|
61
|
+
):
|
|
62
|
+
return AuthenticationStatus.NOT_AUTHENTICATED
|
|
63
|
+
|
|
64
|
+
# Check for definitive authenticated markers
|
|
65
|
+
if exit_code == 0 and ("logged in" in combined or "authenticated" in combined):
|
|
66
|
+
return AuthenticationStatus.AUTHENTICATED
|
|
67
|
+
|
|
68
|
+
# Exit code 0 without explicit errors often indicates valid auth for status subcommands
|
|
69
|
+
if exit_code == 0 and not combined.strip():
|
|
70
|
+
return AuthenticationStatus.AUTHENTICATED
|
|
71
|
+
|
|
72
|
+
if exit_code != 0 and ("unauthorized" in combined or "not authenticated" in combined):
|
|
73
|
+
return AuthenticationStatus.NOT_AUTHENTICATED
|
|
74
|
+
|
|
75
|
+
return AuthenticationStatus.UNKNOWN
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class ClaudeProviderProbe(ProviderProbe):
|
|
79
|
+
"""Probe for detecting and diagnosing the Claude Code native CLI."""
|
|
80
|
+
|
|
81
|
+
def __init__(
|
|
82
|
+
self,
|
|
83
|
+
command_runner: CommandRunner,
|
|
84
|
+
which_fn: Callable[[str], str | None] = shutil.which,
|
|
85
|
+
) -> None:
|
|
86
|
+
self._runner = command_runner
|
|
87
|
+
self._which = which_fn
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def provider_id(self) -> ProviderId:
|
|
91
|
+
return PROVIDER_CLAUDE
|
|
92
|
+
|
|
93
|
+
@property
|
|
94
|
+
def display_name(self) -> str:
|
|
95
|
+
return "Claude Code"
|
|
96
|
+
|
|
97
|
+
@property
|
|
98
|
+
def executable(self) -> str:
|
|
99
|
+
return "claude"
|
|
100
|
+
|
|
101
|
+
def get_capabilities(self) -> ProviderCapabilities:
|
|
102
|
+
return ProviderCapabilities(
|
|
103
|
+
provider_id=self.provider_id,
|
|
104
|
+
display_name=self.display_name,
|
|
105
|
+
supports_interactive=True,
|
|
106
|
+
supports_headless=True,
|
|
107
|
+
supports_native_resume=True,
|
|
108
|
+
supports_structured_output=True,
|
|
109
|
+
supports_mcp=True,
|
|
110
|
+
supports_usage_metrics=True,
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
def probe(self) -> ProviderDiagnostic:
|
|
114
|
+
capabilities = self.get_capabilities()
|
|
115
|
+
resolved_path = self._which(self.executable)
|
|
116
|
+
|
|
117
|
+
if not resolved_path:
|
|
118
|
+
return ProviderDiagnostic(
|
|
119
|
+
provider_id=self.provider_id,
|
|
120
|
+
display_name=self.display_name,
|
|
121
|
+
executable=self.executable,
|
|
122
|
+
installed=False,
|
|
123
|
+
resolved_path=None,
|
|
124
|
+
version=None,
|
|
125
|
+
authentication_status=AuthenticationStatus.UNKNOWN,
|
|
126
|
+
capabilities=capabilities,
|
|
127
|
+
diagnostics=["Not found in PATH"],
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
# 1. Version probe
|
|
131
|
+
version_result = self._runner.run([resolved_path, "--version"], timeout=5.0)
|
|
132
|
+
version: str | None = None
|
|
133
|
+
diagnostics: list[str] = []
|
|
134
|
+
|
|
135
|
+
if version_result.success:
|
|
136
|
+
version = _extract_version(version_result.stdout)
|
|
137
|
+
if not version:
|
|
138
|
+
diagnostics.append("Version output could not be parsed")
|
|
139
|
+
else:
|
|
140
|
+
if version_result.timed_out:
|
|
141
|
+
diagnostics.append("Version probe timed out")
|
|
142
|
+
else:
|
|
143
|
+
diagnostics.append("Version probe failed")
|
|
144
|
+
|
|
145
|
+
# 2. Authentication probe: passive `claude auth status`
|
|
146
|
+
auth_result = self._runner.run([resolved_path, "auth", "status"], timeout=5.0)
|
|
147
|
+
auth_status = _classify_claude_auth(
|
|
148
|
+
stdout=auth_result.stdout,
|
|
149
|
+
stderr=auth_result.stderr,
|
|
150
|
+
exit_code=auth_result.exit_code,
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
if auth_result.timed_out:
|
|
154
|
+
diagnostics.append("Authentication probe timed out")
|
|
155
|
+
elif auth_status == AuthenticationStatus.UNKNOWN and not auth_result.success:
|
|
156
|
+
diagnostics.append("Authentication probe failed")
|
|
157
|
+
|
|
158
|
+
return ProviderDiagnostic(
|
|
159
|
+
provider_id=self.provider_id,
|
|
160
|
+
display_name=self.display_name,
|
|
161
|
+
executable=self.executable,
|
|
162
|
+
installed=True,
|
|
163
|
+
resolved_path=resolved_path,
|
|
164
|
+
version=version,
|
|
165
|
+
authentication_status=auth_status,
|
|
166
|
+
capabilities=capabilities,
|
|
167
|
+
diagnostics=diagnostics,
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
MCP_CONFIG_FLAG = "--mcp-config"
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def build_claude_mcp_config(
|
|
175
|
+
python_executable: str | None = None,
|
|
176
|
+
binding: McpSessionBinding | None = None,
|
|
177
|
+
) -> str:
|
|
178
|
+
"""Build the inline JSON string for Claude Code --mcp-config.
|
|
179
|
+
|
|
180
|
+
When a managed binding is supplied it is declared as the server's own environment, so
|
|
181
|
+
the MCP server is bound to the CortexShift session regardless of how much of Claude
|
|
182
|
+
Code's own environment reaches the server process.
|
|
183
|
+
"""
|
|
184
|
+
exe = python_executable or sys.executable
|
|
185
|
+
server: dict[str, Any] = {
|
|
186
|
+
"command": exe,
|
|
187
|
+
"args": ["-m", "cortexshift", "mcp", "serve"],
|
|
188
|
+
}
|
|
189
|
+
if binding is not None:
|
|
190
|
+
server["env"] = binding.to_env()
|
|
191
|
+
return json.dumps({"mcpServers": {"cortexshift": server}})
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
class ClaudeRuntimeAdapter(ProviderRuntimeAdapter):
|
|
195
|
+
"""Runtime adapter for launching Claude Code interactive sessions."""
|
|
196
|
+
|
|
197
|
+
def __init__(self, python_executable: str | None = None) -> None:
|
|
198
|
+
self._python_executable = python_executable
|
|
199
|
+
|
|
200
|
+
@property
|
|
201
|
+
def provider_id(self) -> ProviderId:
|
|
202
|
+
return PROVIDER_CLAUDE
|
|
203
|
+
|
|
204
|
+
@property
|
|
205
|
+
def display_name(self) -> str:
|
|
206
|
+
return "Claude Code"
|
|
207
|
+
|
|
208
|
+
@property
|
|
209
|
+
def executable(self) -> str:
|
|
210
|
+
return "claude"
|
|
211
|
+
|
|
212
|
+
def get_capabilities(self) -> ProviderCapabilities:
|
|
213
|
+
return ProviderCapabilities(
|
|
214
|
+
provider_id=self.provider_id,
|
|
215
|
+
display_name=self.display_name,
|
|
216
|
+
supports_interactive=True,
|
|
217
|
+
supports_headless=True,
|
|
218
|
+
supports_native_resume=True,
|
|
219
|
+
supports_structured_output=True,
|
|
220
|
+
supports_mcp=True,
|
|
221
|
+
supports_usage_metrics=True,
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
def get_native_capabilities(self) -> NativeSessionCapabilities:
|
|
225
|
+
return NativeSessionCapabilities(
|
|
226
|
+
supports_exact_resume=True,
|
|
227
|
+
can_allocate_native_id_before_launch=True,
|
|
228
|
+
can_resume_with_followup_context=True,
|
|
229
|
+
supports_managed_new_session=True,
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
def build_exact_resume(
|
|
233
|
+
self, project_root: Path, executable_path: str, native_session_id: str
|
|
234
|
+
) -> LaunchSpecification:
|
|
235
|
+
if not valid_native_id(native_session_id):
|
|
236
|
+
raise NativeResumeError("Invalid native session identifier.")
|
|
237
|
+
mcp_config = build_claude_mcp_config(self._python_executable)
|
|
238
|
+
return LaunchSpecification(
|
|
239
|
+
provider_id=self.provider_id,
|
|
240
|
+
executable=executable_path,
|
|
241
|
+
cwd=project_root,
|
|
242
|
+
argv=[
|
|
243
|
+
executable_path,
|
|
244
|
+
MCP_CONFIG_FLAG,
|
|
245
|
+
mcp_config,
|
|
246
|
+
"--resume",
|
|
247
|
+
native_session_id,
|
|
248
|
+
],
|
|
249
|
+
native_session_id=native_session_id,
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
def bind_managed_mcp(
|
|
253
|
+
self,
|
|
254
|
+
launch_spec: LaunchSpecification,
|
|
255
|
+
binding: McpSessionBinding,
|
|
256
|
+
) -> LaunchSpecification:
|
|
257
|
+
"""Restate the managed binding inside the inline --mcp-config payload."""
|
|
258
|
+
if launch_spec.provider_id != self.provider_id:
|
|
259
|
+
return launch_spec
|
|
260
|
+
|
|
261
|
+
argv = list(launch_spec.argv)
|
|
262
|
+
# A supplied prompt is arbitrary text in the same argv, so it is never scanned:
|
|
263
|
+
# a prompt that happens to read like a flag must not be mistaken for one.
|
|
264
|
+
options = argv[:-1] if launch_spec.prompt_supplied else argv
|
|
265
|
+
if MCP_CONFIG_FLAG not in options:
|
|
266
|
+
return launch_spec
|
|
267
|
+
index = options.index(MCP_CONFIG_FLAG) + 1
|
|
268
|
+
if index >= len(options):
|
|
269
|
+
return launch_spec
|
|
270
|
+
|
|
271
|
+
argv[index] = build_claude_mcp_config(self._python_executable, binding=binding)
|
|
272
|
+
return launch_spec.model_copy(update={"argv": argv})
|
|
273
|
+
|
|
274
|
+
def build_launch_spec(
|
|
275
|
+
self,
|
|
276
|
+
project_root: Path,
|
|
277
|
+
executable_path: str,
|
|
278
|
+
prompt: str | None = None,
|
|
279
|
+
) -> LaunchSpecification:
|
|
280
|
+
"""Build argument vector for native Claude launch."""
|
|
281
|
+
native_id = str(uuid4())
|
|
282
|
+
mcp_config = build_claude_mcp_config(self._python_executable)
|
|
283
|
+
argv = [
|
|
284
|
+
executable_path,
|
|
285
|
+
MCP_CONFIG_FLAG,
|
|
286
|
+
mcp_config,
|
|
287
|
+
"--session-id",
|
|
288
|
+
native_id,
|
|
289
|
+
]
|
|
290
|
+
prompt_supplied = False
|
|
291
|
+
if prompt is not None and prompt.strip():
|
|
292
|
+
argv.append(prompt)
|
|
293
|
+
prompt_supplied = True
|
|
294
|
+
|
|
295
|
+
return LaunchSpecification(
|
|
296
|
+
provider_id=self.provider_id,
|
|
297
|
+
executable=executable_path,
|
|
298
|
+
cwd=project_root,
|
|
299
|
+
argv=argv,
|
|
300
|
+
native_session_id=native_id,
|
|
301
|
+
interactive=True,
|
|
302
|
+
initial_prompt_supported=True,
|
|
303
|
+
prompt_supplied=prompt_supplied,
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
class ClaudeHandoffAdapter(ProviderHandoffAdapter):
|
|
308
|
+
"""Delivers canonical handoff context to Claude Code's native interactive CLI.
|
|
309
|
+
|
|
310
|
+
Claude Code's documented interactive launch accepts an initial prompt as a trailing
|
|
311
|
+
positional argument, so the canonical context is delivered directly as a single
|
|
312
|
+
argv element. No extra headless model turn is required, no transcript is imported,
|
|
313
|
+
and model, permission mode, and sandbox settings remain the user's own.
|
|
314
|
+
"""
|
|
315
|
+
|
|
316
|
+
def __init__(self, runtime_adapter: ClaudeRuntimeAdapter | None = None) -> None:
|
|
317
|
+
self._runtime = runtime_adapter or ClaudeRuntimeAdapter()
|
|
318
|
+
|
|
319
|
+
@property
|
|
320
|
+
def provider_id(self) -> ProviderId:
|
|
321
|
+
return PROVIDER_CLAUDE
|
|
322
|
+
|
|
323
|
+
@property
|
|
324
|
+
def display_name(self) -> str:
|
|
325
|
+
return self._runtime.display_name
|
|
326
|
+
|
|
327
|
+
@property
|
|
328
|
+
def executable(self) -> str:
|
|
329
|
+
return self._runtime.executable
|
|
330
|
+
|
|
331
|
+
@property
|
|
332
|
+
def delivery_strategy(self) -> HandoffDeliveryStrategy:
|
|
333
|
+
return HandoffDeliveryStrategy.DIRECT_INITIAL_PROMPT
|
|
334
|
+
|
|
335
|
+
@property
|
|
336
|
+
def bootstrap_model_turn_required(self) -> bool:
|
|
337
|
+
return False
|
|
338
|
+
|
|
339
|
+
def bind_managed_mcp(
|
|
340
|
+
self,
|
|
341
|
+
launch_spec: LaunchSpecification,
|
|
342
|
+
binding: McpSessionBinding,
|
|
343
|
+
) -> LaunchSpecification:
|
|
344
|
+
"""Delegate managed MCP binding to the runtime adapter that built the argv."""
|
|
345
|
+
return self._runtime.bind_managed_mcp(launch_spec, binding)
|
|
346
|
+
|
|
347
|
+
def prepare_delivery(
|
|
348
|
+
self,
|
|
349
|
+
executable_path: str,
|
|
350
|
+
project_root: Path,
|
|
351
|
+
rendered_context: str,
|
|
352
|
+
native_session_id: str | None = None,
|
|
353
|
+
) -> HandoffDeliveryPreparation:
|
|
354
|
+
"""Build the native interactive launch carrying the handoff as one argument."""
|
|
355
|
+
if native_session_id is not None:
|
|
356
|
+
launch_spec = self._runtime.build_exact_resume(
|
|
357
|
+
project_root, executable_path, native_session_id
|
|
358
|
+
)
|
|
359
|
+
launch_spec = launch_spec.model_copy(
|
|
360
|
+
update={
|
|
361
|
+
"argv": [*launch_spec.argv, rendered_context],
|
|
362
|
+
"prompt_supplied": True,
|
|
363
|
+
}
|
|
364
|
+
)
|
|
365
|
+
else:
|
|
366
|
+
launch_spec = self._runtime.build_launch_spec(
|
|
367
|
+
project_root=project_root,
|
|
368
|
+
executable_path=executable_path,
|
|
369
|
+
prompt=rendered_context,
|
|
370
|
+
)
|
|
371
|
+
return HandoffDeliveryPreparation(
|
|
372
|
+
launch_spec=launch_spec,
|
|
373
|
+
native_session_id=launch_spec.native_session_id,
|
|
374
|
+
bootstrap_performed=False,
|
|
375
|
+
)
|