forgexa-cli 1.62.5__tar.gz → 1.63.0__tar.gz
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.
- {forgexa_cli-1.62.5 → forgexa_cli-1.63.0}/PKG-INFO +1 -1
- {forgexa_cli-1.62.5 → forgexa_cli-1.63.0}/forgexa_cli/__init__.py +1 -1
- {forgexa_cli-1.62.5 → forgexa_cli-1.63.0}/forgexa_cli/agent_core.py +19 -12
- {forgexa_cli-1.62.5 → forgexa_cli-1.63.0}/forgexa_cli/daemon.py +31 -23
- {forgexa_cli-1.62.5 → forgexa_cli-1.63.0}/forgexa_cli.egg-info/PKG-INFO +1 -1
- {forgexa_cli-1.62.5 → forgexa_cli-1.63.0}/pyproject.toml +1 -1
- {forgexa_cli-1.62.5 → forgexa_cli-1.63.0}/README.md +0 -0
- {forgexa_cli-1.62.5 → forgexa_cli-1.63.0}/forgexa_cli/_build_config.py +0 -0
- {forgexa_cli-1.62.5 → forgexa_cli-1.63.0}/forgexa_cli/_local_bind.py +0 -0
- {forgexa_cli-1.62.5 → forgexa_cli-1.63.0}/forgexa_cli/autoupgrade.py +0 -0
- {forgexa_cli-1.62.5 → forgexa_cli-1.63.0}/forgexa_cli/main.py +0 -0
- {forgexa_cli-1.62.5 → forgexa_cli-1.63.0}/forgexa_cli/py.typed +0 -0
- {forgexa_cli-1.62.5 → forgexa_cli-1.63.0}/forgexa_cli/runtime_evidence.py +0 -0
- {forgexa_cli-1.62.5 → forgexa_cli-1.63.0}/forgexa_cli/zcode_protocol.py +0 -0
- {forgexa_cli-1.62.5 → forgexa_cli-1.63.0}/forgexa_cli.egg-info/SOURCES.txt +0 -0
- {forgexa_cli-1.62.5 → forgexa_cli-1.63.0}/forgexa_cli.egg-info/dependency_links.txt +0 -0
- {forgexa_cli-1.62.5 → forgexa_cli-1.63.0}/forgexa_cli.egg-info/entry_points.txt +0 -0
- {forgexa_cli-1.62.5 → forgexa_cli-1.63.0}/forgexa_cli.egg-info/requires.txt +0 -0
- {forgexa_cli-1.62.5 → forgexa_cli-1.63.0}/forgexa_cli.egg-info/top_level.txt +0 -0
- {forgexa_cli-1.62.5 → forgexa_cli-1.63.0}/setup.cfg +0 -0
- {forgexa_cli-1.62.5 → forgexa_cli-1.63.0}/tests/test_auth_and_runtime_commands.py +0 -0
- {forgexa_cli-1.62.5 → forgexa_cli-1.63.0}/tests/test_autoupgrade.py +0 -0
- {forgexa_cli-1.62.5 → forgexa_cli-1.63.0}/tests/test_check_command.py +0 -0
- {forgexa_cli-1.62.5 → forgexa_cli-1.63.0}/tests/test_expiry_warnings_and_revoke.py +0 -0
- {forgexa_cli-1.62.5 → forgexa_cli-1.63.0}/tests/test_local_bind_commands.py +0 -0
- {forgexa_cli-1.62.5 → forgexa_cli-1.63.0}/tests/test_runtime_credentials.py +0 -0
- {forgexa_cli-1.62.5 → forgexa_cli-1.63.0}/tests/test_session_credentials.py +0 -0
- {forgexa_cli-1.62.5 → forgexa_cli-1.63.0}/tests/test_silent_install.py +0 -0
- {forgexa_cli-1.62.5 → forgexa_cli-1.63.0}/tests/test_upgrade_observability.py +0 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""forgexa-cli — Forgexa command-line client."""
|
|
2
|
-
__version__ = "1.
|
|
2
|
+
__version__ = "1.63.0"
|
|
@@ -1309,12 +1309,14 @@ def _kill_proc(proc: asyncio.subprocess.Process) -> None:
|
|
|
1309
1309
|
(npm, yarn, ssh, git, etc.) spawned by the agent stay alive, keep pipes
|
|
1310
1310
|
open, and exhaust system resources. ``os.killpg`` sends SIGKILL to the
|
|
1311
1311
|
whole process group, reliably cleaning up all descendants.
|
|
1312
|
+
Owned agents start a new session, so their PID remains the process-group
|
|
1313
|
+
ID even after the leader exits.
|
|
1312
1314
|
"""
|
|
1313
1315
|
try:
|
|
1314
1316
|
if sys.platform != "win32":
|
|
1315
1317
|
import signal as _signal
|
|
1316
1318
|
try:
|
|
1317
|
-
os.killpg(
|
|
1319
|
+
os.killpg(proc.pid, _signal.SIGKILL)
|
|
1318
1320
|
except (ProcessLookupError, PermissionError, OSError):
|
|
1319
1321
|
pass
|
|
1320
1322
|
else:
|
|
@@ -1322,6 +1324,7 @@ def _kill_proc(proc: asyncio.subprocess.Process) -> None:
|
|
|
1322
1324
|
_subprocess.run(
|
|
1323
1325
|
["taskkill", "/F", "/T", "/PID", str(proc.pid)],
|
|
1324
1326
|
capture_output=True,
|
|
1327
|
+
timeout=5,
|
|
1325
1328
|
**_agent_process_group_kwargs(),
|
|
1326
1329
|
)
|
|
1327
1330
|
except Exception:
|
|
@@ -2462,7 +2465,7 @@ def deepseek_harness_build_info(binary: str) -> dict:
|
|
|
2462
2465
|
if result.returncode or not re.fullmatch(r"\d+\.\d+\.\d+", node_version) or parsed is None or not ((parsed[0] == 22 and parsed >= (22, 19, 0)) or parsed[0] >= 24):
|
|
2463
2466
|
raise RuntimeError("DeepSeek Harness requires Node 22.19+ or Node 24+")
|
|
2464
2467
|
digest = hashlib.sha256()
|
|
2465
|
-
digest.update(f"{sys.platform}:{platform.machine()}:{platform.release()}:{node_version}:harness-policy-
|
|
2468
|
+
digest.update(f"{sys.platform}:{platform.machine()}:{platform.release()}:{node_version}:harness-policy-4".encode())
|
|
2466
2469
|
extensions = [manifest.parent for manifest in (roots[0] / "node_modules" / "@deepseek-ai").glob("dsh*/package.json")]
|
|
2467
2470
|
for root in sorted(set([*roots, *extensions])):
|
|
2468
2471
|
files = [root / "package.json"] + sorted((root / "lib").rglob("*.js"))
|
|
@@ -2592,10 +2595,15 @@ def deepseek_harness_overlay(*, cwd: Path, credentials_path: Path, provider: str
|
|
|
2592
2595
|
"watch": False,
|
|
2593
2596
|
}},
|
|
2594
2597
|
{"id": "tool-skill", "disabled": False},
|
|
2598
|
+
{"id": "tool-subagent", "disabled": False, "config": {
|
|
2599
|
+
"provider": "spawn", "enableRunInBackground": False,
|
|
2600
|
+
"backgroundMode": "one-shot", "maxDepth": 1,
|
|
2601
|
+
"modelSelectionSettings": False,
|
|
2602
|
+
}},
|
|
2595
2603
|
]
|
|
2596
2604
|
disabled = (
|
|
2597
2605
|
"session-telemetry-otel", "hmr", "plugin-manager", "tool-plugin-manager",
|
|
2598
|
-
"llm-pi-ai", "tool-subagent
|
|
2606
|
+
"llm-pi-ai", "tool-subagent-fork",
|
|
2599
2607
|
"tool-subagent-control", "tool-subagent-list-agents", "tool-workflow",
|
|
2600
2608
|
"goal-round-driver", "command-goal", "tool-goal", "command-feedback", "tool-web",
|
|
2601
2609
|
)
|
|
@@ -2862,7 +2870,7 @@ def mimo_base_env(env: dict[str, str]) -> dict[str, str]:
|
|
|
2862
2870
|
"git * reset --hard*": "deny",
|
|
2863
2871
|
},
|
|
2864
2872
|
"external_directory": "deny",
|
|
2865
|
-
"task": "
|
|
2873
|
+
"task": "allow",
|
|
2866
2874
|
"skill": {"*": "allow", "compose:*": "deny"},
|
|
2867
2875
|
"webfetch": "deny",
|
|
2868
2876
|
"websearch": "deny",
|
|
@@ -3168,9 +3176,9 @@ def zcode_build_verification_issue(binary: str | Path, env: dict[str, str] | Non
|
|
|
3168
3176
|
return None
|
|
3169
3177
|
|
|
3170
3178
|
|
|
3171
|
-
ZCODE_ALLOWED_TOOLS = ("Read", "Glob", "Grep", "Edit", "Write", "Skill")
|
|
3179
|
+
ZCODE_ALLOWED_TOOLS = ("Read", "Glob", "Grep", "Edit", "Write", "Skill", "Agent", "Task")
|
|
3172
3180
|
ZCODE_DENIED_TOOLS = (
|
|
3173
|
-
"Bash", "MCP", "Plugin", "
|
|
3181
|
+
"Bash", "MCP", "Plugin", "WebFetch", "WebSearch",
|
|
3174
3182
|
)
|
|
3175
3183
|
|
|
3176
3184
|
|
|
@@ -3247,10 +3255,8 @@ def agent_execution_verification_issue(agent_id: str, binary: str) -> str | None
|
|
|
3247
3255
|
return None
|
|
3248
3256
|
|
|
3249
3257
|
|
|
3250
|
-
_FORGEXA_CODING_AGENT_TOOLS = ("Read", "Glob", "Grep", "Edit", "Write", "Bash", "Skill")
|
|
3258
|
+
_FORGEXA_CODING_AGENT_TOOLS = ("Read", "Glob", "Grep", "Edit", "Write", "Bash", "Skill", "Agent", "Task")
|
|
3251
3259
|
_FORGEXA_CODING_AGENT_DENIED_TOOLS = (
|
|
3252
|
-
"Agent",
|
|
3253
|
-
"Task",
|
|
3254
3260
|
"SlashCommand",
|
|
3255
3261
|
"WebFetch",
|
|
3256
3262
|
"WebSearch",
|
|
@@ -3655,9 +3661,10 @@ def build_copilot_command(
|
|
|
3655
3661
|
|
|
3656
3662
|
``prompt_via_stdin`` omits ``-p`` so the caller pipes the prompt into
|
|
3657
3663
|
stdin instead - verified live on CLI 1.0.86, where piped stdin is a full
|
|
3658
|
-
non-interactive prompt source. The
|
|
3659
|
-
|
|
3660
|
-
|
|
3664
|
+
non-interactive prompt source. The daemon uses it for native launchers
|
|
3665
|
+
to avoid single-argument and total command-line limits; Windows batch
|
|
3666
|
+
wrappers retain task-file redirection. The host uses it when the argv
|
|
3667
|
+
form would exceed the platform command-line limit.
|
|
3661
3668
|
"""
|
|
3662
3669
|
cmd = [
|
|
3663
3670
|
binary,
|
|
@@ -1196,7 +1196,7 @@ except (ImportError, ModuleNotFoundError):
|
|
|
1196
1196
|
# DAEMON_VERSION is the protocol/logic version of the daemon code.
|
|
1197
1197
|
# Kept in sync with pyproject.toml version via bump-version.sh.
|
|
1198
1198
|
# CLIENT_TYPE identifies which packaging/distribution this daemon runs in.
|
|
1199
|
-
DAEMON_VERSION = "1.
|
|
1199
|
+
DAEMON_VERSION = "1.63.0"
|
|
1200
1200
|
|
|
1201
1201
|
|
|
1202
1202
|
def _detect_client_type() -> str:
|
|
@@ -6525,10 +6525,11 @@ class ProcessManager:
|
|
|
6525
6525
|
_last_activity_at: list[float] = [time.monotonic()]
|
|
6526
6526
|
|
|
6527
6527
|
# Write prompt and close stdin so the agent knows input is done.
|
|
6528
|
-
if
|
|
6528
|
+
if proc.stdin:
|
|
6529
6529
|
try:
|
|
6530
|
-
|
|
6531
|
-
|
|
6530
|
+
if stdin_input:
|
|
6531
|
+
proc.stdin.write(stdin_input)
|
|
6532
|
+
await proc.stdin.drain()
|
|
6532
6533
|
except Exception:
|
|
6533
6534
|
pass
|
|
6534
6535
|
proc.stdin.close()
|
|
@@ -7668,7 +7669,8 @@ class ProcessManager:
|
|
|
7668
7669
|
explicitly configured supported token environment variable.
|
|
7669
7670
|
|
|
7670
7671
|
Flags:
|
|
7671
|
-
|
|
7672
|
+
stdin Full non-interactive prompt, avoiding argv limits.
|
|
7673
|
+
-p / --prompt Short task-file instruction for Windows wrappers.
|
|
7672
7674
|
--output-format json JSONL stream of session events.
|
|
7673
7675
|
--allow-all Grant all tool + path + URL permissions required
|
|
7674
7676
|
for autonomous file-editing tasks.
|
|
@@ -7768,11 +7770,13 @@ class ProcessManager:
|
|
|
7768
7770
|
_effective_prompt, _task_file = _redirect_prompt_through_task_file(
|
|
7769
7771
|
cwd, prompt, task_id, "Copilot",
|
|
7770
7772
|
)
|
|
7773
|
+
_stdin_input = None if _task_file is not None else prompt.encode("utf-8")
|
|
7771
7774
|
|
|
7772
7775
|
cmd = agent_core.build_copilot_command(
|
|
7773
7776
|
agent.command,
|
|
7774
7777
|
cwd=cwd,
|
|
7775
7778
|
prompt=_effective_prompt,
|
|
7779
|
+
prompt_via_stdin=_stdin_input is not None,
|
|
7776
7780
|
model=model_override,
|
|
7777
7781
|
reasoning=reasoning if not _retry_without_effort else None,
|
|
7778
7782
|
disable_builtin_mcps=disable_builtin_mcps,
|
|
@@ -7786,9 +7790,20 @@ class ProcessManager:
|
|
|
7786
7790
|
# reproduce failures manually and diagnose CLI version incompatibilities.
|
|
7787
7791
|
_cmd_display = " ".join(
|
|
7788
7792
|
f'"{a}"' if " " in a else a
|
|
7789
|
-
for a in cmd
|
|
7790
|
-
)
|
|
7791
|
-
|
|
7793
|
+
for a in (cmd if _stdin_input is not None else cmd[:-1])
|
|
7794
|
+
)
|
|
7795
|
+
if _stdin_input is None:
|
|
7796
|
+
_cmd_display += ' "<prompt>"'
|
|
7797
|
+
logger.debug(
|
|
7798
|
+
"Copilot invocation for task %s: %s (prompt via %s)",
|
|
7799
|
+
task_id, _cmd_display, "stdin" if _stdin_input is not None else "task file",
|
|
7800
|
+
)
|
|
7801
|
+
logger.info(
|
|
7802
|
+
"Copilot task %s prompt transport: %s (%d UTF-8 bytes)",
|
|
7803
|
+
task_id,
|
|
7804
|
+
"stdin" if _stdin_input is not None else "task file",
|
|
7805
|
+
len(_stdin_input) if _stdin_input is not None else len(prompt.encode("utf-8")),
|
|
7806
|
+
)
|
|
7792
7807
|
|
|
7793
7808
|
# On Windows, CREATE_NO_WINDOW ensures GetConsoleWindow() returns NULL for
|
|
7794
7809
|
# the child process, even when the parent (daemon or Tauri app) has a console
|
|
@@ -7796,9 +7811,9 @@ class ProcessManager:
|
|
|
7796
7811
|
# check via GetConsoleWindow(): if a console window is visible, it uses only
|
|
7797
7812
|
# the first line of -p as a brief context note and reports "No task was
|
|
7798
7813
|
# provided" for the rest of the prompt — silently discarding the full task.
|
|
7799
|
-
#
|
|
7814
|
+
# A pipe or DEVNULL covers the primary check (GetConsoleMode on stdin);
|
|
7800
7815
|
# CREATE_NO_WINDOW covers this secondary check so Copilot always sees a fully
|
|
7801
|
-
# headless environment and processes
|
|
7816
|
+
# headless environment and processes the complete task specification.
|
|
7802
7817
|
proc: asyncio.subprocess.Process | None = None
|
|
7803
7818
|
|
|
7804
7819
|
async def _terminate_copilot_process(
|
|
@@ -7817,18 +7832,11 @@ class ProcessManager:
|
|
|
7817
7832
|
*cmd,
|
|
7818
7833
|
stdout=asyncio.subprocess.PIPE,
|
|
7819
7834
|
stderr=asyncio.subprocess.PIPE,
|
|
7820
|
-
|
|
7821
|
-
|
|
7822
|
-
|
|
7823
|
-
|
|
7824
|
-
|
|
7825
|
-
# and enters interactive/session-check mode, ignoring -p as a task:
|
|
7826
|
-
# it checks the session todo table (empty), reports "no tracked work
|
|
7827
|
-
# items", and waits for user input instead of executing the task.
|
|
7828
|
-
# On Linux/macOS (daemon as service), stdin is typically /dev/null so
|
|
7829
|
-
# the bug doesn't manifest. Explicit DEVNULL ensures non-interactive
|
|
7830
|
-
# mode on all platforms, matching the intended headless operation.
|
|
7831
|
-
stdin=asyncio.subprocess.DEVNULL,
|
|
7835
|
+
stdin=(
|
|
7836
|
+
asyncio.subprocess.PIPE
|
|
7837
|
+
if _stdin_input is not None
|
|
7838
|
+
else asyncio.subprocess.DEVNULL
|
|
7839
|
+
),
|
|
7832
7840
|
cwd=str(cwd),
|
|
7833
7841
|
env=env,
|
|
7834
7842
|
limit=100 * 1024 * 1024,
|
|
@@ -7836,7 +7844,7 @@ class ProcessManager:
|
|
|
7836
7844
|
)
|
|
7837
7845
|
self.active_processes[task_id] = proc
|
|
7838
7846
|
stdout, stderr, returncode = await self._stream_process(
|
|
7839
|
-
proc,
|
|
7847
|
+
proc, _stdin_input, timeout, task_id, on_chunk,
|
|
7840
7848
|
workspace_path=cwd,
|
|
7841
7849
|
stream_stderr=stream_stderr,
|
|
7842
7850
|
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|