carthagent 1.0.7 → 1.1.0-preview.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +80 -79
- package/bin/ctg.mjs +14 -732
- package/docs/glyph-review/host-chrome-glyph-compatibility-v4.md +83 -0
- package/docs/glyph-review/test-isolation-v1.md +26 -0
- package/docs/glyph-review/typecheck-base.log +1 -0
- package/docs/glyph-review/typecheck-v4.log +1 -0
- package/docs/glyph-review/unicode-inventory-v4.csv +402 -0
- package/docs/help-dispatch-review.md +69 -0
- package/docs/legacy-cli.md +150 -0
- package/docs/live-theme-plugin-api.md +18 -0
- package/docs/native-cli.md +102 -0
- package/docs/native-compatibility.md +16 -0
- package/docs/native-daily-quests.md +132 -0
- package/docs/native-glyph-pty.md +59 -0
- package/docs/native-manual-testing.md +314 -0
- package/docs/native-slash-acceptance-handoff.md +49 -0
- package/docs/native-terminal-paired-failure-2026-10-17.md +40 -0
- package/docs/native-terminal-performance.md +310 -0
- package/docs/native-terminal-semantics-evidence/README.md +25 -0
- package/docs/native-terminal-semantics-evidence/controlled-host-model-selftest.json +16 -0
- package/docs/native-terminal-semantics-evidence/current-acceptance-status.json +59 -0
- package/docs/native-terminal-semantics-evidence/final-binary-focused-85bb44a.json +154 -0
- package/docs/native-terminal-semantics-evidence/final-binary-full-incomplete-85bb44a.json +160 -0
- package/docs/native-terminal-semantics-evidence/final-binary-layout-85bb44a.json +149 -0
- package/docs/native-terminal-semantics-evidence/final-binary-layout-interactive-85bb44a.json +159 -0
- package/docs/native-terminal-semantics-evidence/final-binary-model-picker-escape-unchanged-85bb44a.json +44 -0
- package/docs/native-terminal-semantics-evidence/final-binary-model-picker-exact-identity-85bb44a.json +47 -0
- package/docs/native-terminal-semantics-evidence/final-binary-model-picker-unchanged-model-incomplete-85bb44a.json +58 -0
- package/docs/native-terminal-semantics-evidence/final-binary-model-selftest.json +16 -0
- package/docs/native-terminal-semantics-evidence/final-binary-picker-cancel-incomplete-85bb44a.json +62 -0
- package/docs/native-terminal-semantics-evidence/focused-clipboard-title-stale-binary.json +161 -0
- package/docs/native-terminal-semantics-evidence/layout-stale-binary-observation-not-accepted.json +153 -0
- package/docs/native-terminal-semantics-evidence/picker-latest-incomplete-attempt.json +62 -0
- package/docs/native-terminal-semantics-evidence/picker-previous-probe-pass.json +61 -0
- package/docs/native-terminal-semantics.md +89 -0
- package/docs/native-upgrade.md +177 -0
- package/docs/opencode-composed-source-review.md +89 -0
- package/docs/opencode-migration.md +225 -9
- package/docs/opencode-owned-runtime.md +241 -0
- package/docs/opencode-runtime.md +12 -0
- package/docs/opencode-theme-provider-proof/README.md +55 -0
- package/docs/opencode-theme-provider-proof/live-theme-api-base.patch +122 -0
- package/docs/opencode-theme-provider-proof/plugin-theme-provider.patch +107 -0
- package/docs/opencode-theme-provider-proof/session-ui-base.patch +1217 -0
- package/docs/owned-host-controls-proof.md +39 -0
- package/docs/owned-runtime-release-manifest.md +42 -0
- package/docs/owned-visual-clear-proof.md +40 -0
- package/docs/patches/opencode-v2.0.32-glyph-spinner-test-isolation-v1.patch +50 -0
- package/docs/patches/opencode-v2.0.32-glyphs.patch +3670 -0
- package/docs/patches/opencode-v2.0.32-help-dispatch.patch +156 -0
- package/docs/patches/opencode-v2.0.32-live-theme-api.patch +122 -0
- package/docs/patches/opencode-v2.0.32-session-ui-combined-v1.patch +5272 -0
- package/docs/patches/opencode-v2.0.32-session-ui-combined-v2.patch +6052 -0
- package/docs/patches/opencode-v2.0.32-session-ui-combined-v3.patch +8657 -0
- package/docs/patches/opencode-v2.0.32-session-ui-combined-v4.patch +9928 -0
- package/docs/patches/opencode-v2.0.32-session-ui.patch +5137 -0
- package/docs/release-notes-native-draft.md +73 -0
- package/docs/release-notes-preview.10.md +121 -0
- package/docs/release-notes-preview.11.md +76 -0
- package/docs/release-notes-preview.12.md +55 -0
- package/docs/release-notes-preview.9.md +130 -0
- package/evaluate.mjs +28 -1
- package/lib/budget.mjs +1 -1
- package/lib/codegraph.mjs +1 -1
- package/lib/delivery.mjs +3 -2
- package/lib/environment.mjs +8 -0
- package/lib/guidance.mjs +1 -1
- package/lib/opencode/cli-runtime.mjs +1171 -0
- package/lib/opencode/cli.mjs +252 -0
- package/lib/opencode/credentials.mjs +63 -8
- package/lib/opencode/demo.mjs +53 -0
- package/lib/opencode/evaluation-budget-plugin/index.ts +3 -0
- package/lib/opencode/evaluation.mjs +32 -12
- package/lib/opencode/headless.mjs +8 -4
- package/lib/opencode/provider-policy.mjs +23 -0
- package/lib/opencode/review.mjs +37 -16
- package/lib/opencode/runtime-artifact.mjs +335 -0
- package/lib/opencode/runtime-github-transport.mjs +121 -0
- package/lib/opencode/runtime-release.json +36 -0
- package/lib/opencode/runtime.mjs +298 -20
- package/lib/opencode/service.mjs +77 -35
- package/lib/opencode/session-options.mjs +6 -2
- package/lib/opencode/update.mjs +222 -0
- package/lib/opencode/workspace.mjs +1 -1
- package/lib/planning-advice.mjs +2 -2
- package/lib/tui/framing.mjs +1 -80
- package/lib/update.mjs +10 -1
- package/package.json +33 -7
- package/plugins/carthagent/README.md +23 -0
- package/plugins/carthagent/src/bootstrap.ts +1 -1
- package/plugins/carthagent/src/daily-evidence.mjs +57 -0
- package/plugins/carthagent/src/daily-quests-rpc.ts +50 -0
- package/plugins/carthagent/src/daily-source-rpc.ts +52 -0
- package/plugins/carthagent/src/index.ts +71 -24
- package/plugins/carthagent/src/rpc.ts +51 -6
- package/plugins/carthagent/src/server/codegraph.ts +12 -5
- package/plugins/carthagent/src/server/daily-quests-rpc.ts +277 -0
- package/plugins/carthagent/src/server/daily-source-rpc.ts +197 -0
- package/plugins/carthagent/src/server/delivery.ts +217 -106
- package/plugins/carthagent/src/server/framing.mjs +80 -0
- package/plugins/carthagent/src/server/policy.ts +1 -1
- package/plugins/carthagent/src/server/providers.ts +58 -30
- package/plugins/carthagent/src/server/session-controls.ts +3 -1
- package/plugins/carthagent/src/server/ship-agents.ts +77 -0
- package/plugins/carthagent/src/server/ship-context.mjs +60 -0
- package/plugins/carthagent/src/server/ship-guidance.ts +39 -0
- package/plugins/carthagent/src/server/ship-rpc.ts +69 -0
- package/plugins/carthagent/src/server/voyages-rpc.ts +223 -0
- package/plugins/carthagent/src/testing.ts +5 -0
- package/plugins/carthagent/src/tui.tsx +492 -0
- package/plugins/carthagent/src/ui/client.ts +125 -0
- package/plugins/carthagent/src/ui/commands.ts +70 -0
- package/plugins/carthagent/src/ui/daily-editor-focus.mjs +52 -0
- package/plugins/carthagent/src/ui/daily-editor.mjs +4 -0
- package/plugins/carthagent/src/ui/daily-quests-events.mjs +18 -0
- package/plugins/carthagent/src/ui/daily-quests-state.mjs +218 -0
- package/plugins/carthagent/src/ui/daily-quests.tsx +232 -0
- package/plugins/carthagent/src/ui/host-state.ts +42 -0
- package/plugins/carthagent/src/ui/native-navigation.ts +23 -0
- package/plugins/carthagent/src/ui/native-review.d.mts +31 -0
- package/plugins/carthagent/src/ui/native-review.mjs +154 -0
- package/plugins/carthagent/src/ui/resume-session.mjs +34 -0
- package/plugins/carthagent/src/ui/session-preferences.d.mts +7 -0
- package/plugins/carthagent/src/ui/session-preferences.mjs +14 -0
- package/plugins/carthagent/src/ui/theme-provision.d.mts +3 -0
- package/plugins/carthagent/src/ui/theme-provision.mjs +134 -0
- package/plugins/carthagent/src/ui/voyages-navigation-state.mjs +56 -0
- package/plugins/carthagent/src/ui/voyages-navigation.tsx +122 -0
- package/plugins/carthagent/src/ui/voyages-state.mjs +142 -0
- package/plugins/carthagent/src/ui/voyages.tsx +127 -0
- package/plugins/carthagent/src/ui/workspace-live.ts +61 -0
- package/plugins/carthagent/src/ui/workspace.tsx +379 -0
- package/plugins/carthagent/src/voyages-rpc.ts +60 -0
- package/plugins/carthagent/src/workspace-rpc.ts +36 -0
- package/plugins/carthagent/tests/commands.test.ts +107 -0
- package/plugins/carthagent/tests/session-preferences.test.mjs +41 -0
- package/plugins/carthagent/tests/voyages-navigation.test.tsx +258 -0
- package/plugins/carthagent/themes/carthage.json +67 -0
- package/plugins/carthagent/tui.ts +26 -1
- package/script/ctg/build-owned-runtime.sh +166 -0
- package/tests/budget-engine.test.mjs +4 -3
- package/tests/cli.test.mjs +52 -96
- package/tests/daily-evidence.test.mjs +62 -0
- package/tests/delivery.test.mjs +22 -1
- package/tests/fixtures/native-discovery-plugin/server.ts +15 -0
- package/tests/generate-owned-runtime-release.test.mjs +354 -0
- package/tests/installed-runtime-contract.test.mjs +43 -0
- package/tests/native-cli-host-smoke.mjs +457 -0
- package/tests/native-daily-editor-focus.test.mjs +213 -0
- package/tests/native-daily-editor.test.mjs +43 -0
- package/tests/native-daily-quests-layout.test.mjs +53 -0
- package/tests/native-daily-quests-rpc.test.mjs +456 -0
- package/tests/native-daily-quests-ui.test.mjs +778 -0
- package/tests/native-daily-source.test.mjs +426 -0
- package/tests/native-demo-ui.test.mjs +48 -0
- package/tests/native-evaluation-admission.test.mjs +24 -0
- package/tests/native-fixture-browser-cleanup.test.mjs +65 -0
- package/tests/native-fixture-runtime.test.mjs +111 -0
- package/tests/native-fixture-stop-diagnostics.test.mjs +67 -0
- package/tests/native-glyph-pty.test.mjs +107 -0
- package/tests/native-manual-projects.test.mjs +170 -0
- package/tests/native-owned-runtime-env.test.mjs +140 -0
- package/tests/native-paired-guards.test.mjs +464 -0
- package/tests/native-provider-policy.test.mjs +31 -0
- package/tests/native-pty-invocation.test.mjs +114 -0
- package/tests/native-pty-runtime.test.mjs +45 -0
- package/tests/native-resume-session.test.mjs +46 -0
- package/tests/native-review-consumer.test.mjs +267 -0
- package/tests/native-runtime-identity.test.mjs +214 -0
- package/tests/native-ship-parity.test.mjs +526 -0
- package/tests/native-ship-plan.test.mjs +75 -0
- package/tests/native-ship-rpc.test.mjs +71 -0
- package/tests/native-ui-commands.test.mjs +6 -0
- package/tests/native-voyages-layout.test.mjs +28 -0
- package/tests/native-voyages-navigation.test.mjs +169 -0
- package/tests/native-voyages-rpc.test.mjs +395 -0
- package/tests/native-voyages-ui.test.mjs +277 -0
- package/tests/opencode-atomic-write.test.mjs +55 -0
- package/tests/opencode-cli.test.mjs +1312 -0
- package/tests/opencode-codegraph-plugin.test.mjs +47 -0
- package/tests/opencode-providers.test.mjs +245 -18
- package/tests/opencode-runtime.test.mjs +546 -8
- package/tests/opencode-session-controls.test.mjs +15 -0
- package/tests/opencode-ship-rpc.test.mjs +71 -0
- package/tests/opencode-update.test.mjs +475 -0
- package/tests/opencode-workspace.test.mjs +51 -2
- package/tests/owned-runtime-publish-workflow.test.mjs +477 -0
- package/tests/release-workflow.test.mjs +46 -0
- package/tests/runtime-artifact.test.mjs +269 -0
- package/tests/runtime-development-trust.test.mjs +230 -0
- package/tests/runtime-github-transport.test.mjs +132 -0
- package/tests/runtime-token-env.test.mjs +23 -0
- package/tests/test_native_terminal_probe.py +76 -0
- package/tests/test_native_terminal_semantics.py +232 -0
- package/tests/update.test.mjs +36 -0
- package/tests/verify-draft-runtime-gate.test.mjs +280 -0
- package/tests/verify-owned-runtime-release.test.mjs +371 -0
- package/tests/workspace-live.test.mjs +127 -0
- package/tools/benchmark/diagnose-request.mjs +59 -36
- package/tools/benchmark/eval-common.mjs +3 -3
- package/tools/benchmark/evaluators.test.mjs +4 -4
- package/tools/benchmark/guard.test.mjs +35 -2
- package/tools/benchmark/guard.ts +20 -7
- package/tools/benchmark/planning-startup.test.mjs +30 -34
- package/tools/benchmark/runner.mjs +12 -7
- package/tools/benchmark/runner.test.mjs +23 -7
- package/tools/benchmark/upstream-error.test.mjs +16 -2
- package/tools/generate-owned-runtime-release.mjs +217 -0
- package/tools/installed-runtime-contract.mjs +16 -0
- package/tools/manual-runtime-manifest.mjs +39 -0
- package/tools/native-fixture-browser-cleanup.mjs +59 -0
- package/tools/native-fixture-stop-diagnostics.mjs +43 -0
- package/tools/native-fixture.mjs +199 -40
- package/tools/native-glyph-helpers.py +127 -0
- package/tools/native-glyph-pty.py +144 -0
- package/tools/native-manual-projects.mjs +259 -0
- package/tools/native-manual.mjs +338 -0
- package/tools/native-model-picker-acceptance.py +306 -0
- package/tools/native-owned-runtime-env.sh +47 -0
- package/tools/native-paired-guards.mjs +282 -0
- package/tools/native-paired-manifest.mjs +72 -0
- package/tools/native-paired-orchestration.sh +51 -0
- package/tools/native-plugin-readiness.mjs +33 -0
- package/tools/native-plugin-readiness.test.mjs +53 -0
- package/tools/native-pty-invocation.mjs +26 -0
- package/tools/native-pty-runtime.mjs +83 -0
- package/tools/native-pty-smoke.py +147 -24
- package/tools/native-public-repo-shell-result.mjs +13 -0
- package/tools/native-public-repo-shell-result.test.mjs +28 -0
- package/tools/native-runtime-identity.mjs +201 -0
- package/tools/native-slash-acceptance-env.py +38 -0
- package/tools/native-terminal-semantics.py +876 -0
- package/tools/owned-shell-observer/index.ts +16 -0
- package/tools/owned-shell-observer/package.json +1 -0
- package/tools/owned-visual-clear-env.py +48 -0
- package/tools/owned-visual-clear-model.py +21 -0
- package/tools/pinned-owned-runtime-inputs.mjs +46 -0
- package/tools/prepare-native-manual.mjs +122 -0
- package/tools/run-native-terminal-paired.sh +403 -0
- package/tools/runtime-token-env.mjs +8 -0
- package/tools/seed-native-switch-fixture.mjs +79 -0
- package/tools/test-native-controls.mjs +3 -1
- package/tools/test-native-daily-quests.mjs +65 -0
- package/tools/test-native-default-cli.mjs +106 -0
- package/tools/test-native-delivery-host.mjs +100 -18
- package/tools/test-native-evaluation.mjs +7 -3
- package/tools/test-native-glyph-pty.mjs +94 -0
- package/tools/test-native-host.mjs +83 -61
- package/tools/test-native-outcomes.mjs +3 -1
- package/tools/test-native-package.mjs +206 -38
- package/tools/test-native-performance.mjs +26 -0
- package/tools/test-native-product.mjs +90 -0
- package/tools/test-native-pty.mjs +34 -9
- package/tools/test-native-public-repo.mjs +224 -0
- package/tools/test-native-refresh-reentry.mjs +106 -0
- package/tools/test-native-report.mjs +27 -20
- package/tools/test-native-review.mjs +75 -0
- package/tools/test-native-runtime.mjs +120 -0
- package/tools/test-native-ship-models.mjs +176 -0
- package/tools/test-native-ship-walkthrough.mjs +96 -0
- package/tools/test-native-slash-commands.mjs +125 -0
- package/tools/test-native-slash-commands.py +242 -0
- package/tools/test-native-swe-host.mjs +224 -55
- package/tools/test-native-tasks.mjs +77 -0
- package/tools/test-native-terminal-performance.py +887 -0
- package/tools/test-native-terminal-readiness.sh +85 -0
- package/tools/test-native-terminal-semantics-installed.mjs +38 -0
- package/tools/test-native-visual-clear-installed.mjs +48 -0
- package/tools/test-owned-host-controls.mjs +238 -0
- package/tools/test-owned-opencode-runtime.mjs +35 -0
- package/tools/test-owned-visual-clear-env.py +120 -0
- package/tools/test-owned-visual-clear.py +373 -0
- package/tools/verify-draft-runtime-gate.mjs +138 -0
- package/tools/verify-owned-runtime-release.mjs +114 -0
- package/tools/verify-release-version.mjs +21 -1
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Paired real-PTY glyph probe. Only known host chrome is normalized/asserted."""
|
|
3
|
+
import argparse
|
|
4
|
+
import fcntl
|
|
5
|
+
import hashlib
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
import pty
|
|
10
|
+
import re
|
|
11
|
+
import select
|
|
12
|
+
import signal
|
|
13
|
+
import struct
|
|
14
|
+
import subprocess
|
|
15
|
+
import tempfile
|
|
16
|
+
import termios
|
|
17
|
+
import time
|
|
18
|
+
import importlib.util
|
|
19
|
+
import sys
|
|
20
|
+
|
|
21
|
+
_helper_path = Path(__file__).with_name('native-glyph-helpers.py')
|
|
22
|
+
_helper_spec = importlib.util.spec_from_file_location('native_glyph_helpers', _helper_path)
|
|
23
|
+
if _helper_spec is None or _helper_spec.loader is None:
|
|
24
|
+
raise ImportError(f'Could not load native glyph helper module at {_helper_path}')
|
|
25
|
+
_helpers = importlib.util.module_from_spec(_helper_spec)
|
|
26
|
+
sys.modules[_helper_spec.name] = _helpers
|
|
27
|
+
_helper_spec.loader.exec_module(_helpers)
|
|
28
|
+
CHROME_PAIRS = _helpers.CHROME_PAIRS
|
|
29
|
+
USER_TEXT = _helpers.USER_TEXT
|
|
30
|
+
GlyphScreen = _helpers.GlyphScreen
|
|
31
|
+
assert_composer_payload = _helpers.assert_composer_payload
|
|
32
|
+
child_environment = _helpers.child_environment
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def run_mode(binary, mode, timeout):
|
|
36
|
+
with tempfile.TemporaryDirectory(prefix='ctg-glyph-pty-') as temp:
|
|
37
|
+
root = Path(temp)
|
|
38
|
+
project = root / 'project'
|
|
39
|
+
config = root / 'config' / 'opencode'
|
|
40
|
+
for directory in (project, config, root / 'home', root / 'data', root / 'state', root / 'cache'):
|
|
41
|
+
directory.mkdir(parents=True, exist_ok=True)
|
|
42
|
+
(config / 'opencode.json').write_text(json.dumps({'update': 'disable', 'model': 'ctg-glyph/fixture',
|
|
43
|
+
'providers': {'ctg-glyph': {'name': 'Local glyph probe',
|
|
44
|
+
'package': '@opencode/ai/providers/openai-compatible',
|
|
45
|
+
'settings': {'baseURL': 'http://127.0.0.1:1/v1', 'apiKey': 'offline-fixture'},
|
|
46
|
+
'models': {'fixture': {'name': 'Glyph fixture', 'limit': {'context': 32000, 'output': 1024}}}}}}), encoding='utf8')
|
|
47
|
+
env = child_environment(root, config, mode)
|
|
48
|
+
master, slave = pty.openpty()
|
|
49
|
+
fcntl.ioctl(slave, termios.TIOCSWINSZ, struct.pack('HHHH', 32, 100, 0, 0))
|
|
50
|
+
child = subprocess.Popen([binary, '--standalone'], cwd=project, env=env,
|
|
51
|
+
stdin=slave, stdout=slave, stderr=slave, start_new_session=True)
|
|
52
|
+
os.close(slave)
|
|
53
|
+
screen = GlyphScreen()
|
|
54
|
+
raw = bytearray()
|
|
55
|
+
started = time.monotonic()
|
|
56
|
+
pasted = False
|
|
57
|
+
home_seen = False
|
|
58
|
+
try:
|
|
59
|
+
while time.monotonic() - started < timeout:
|
|
60
|
+
if child.poll() is not None:
|
|
61
|
+
raise RuntimeError(f'{mode}: runtime exited {child.returncode}: {screen.visible()[-1200:]}')
|
|
62
|
+
if select.select([master], [], [], .05)[0]:
|
|
63
|
+
try:
|
|
64
|
+
chunk = os.read(master, 65536)
|
|
65
|
+
except OSError:
|
|
66
|
+
break
|
|
67
|
+
raw.extend(chunk)
|
|
68
|
+
screen.feed(chunk)
|
|
69
|
+
if b'\x1b[6n' in chunk:
|
|
70
|
+
os.write(master, b'\x1b[1;1R')
|
|
71
|
+
if b'\x1b[c' in chunk:
|
|
72
|
+
os.write(master, b'\x1b[?1;2c')
|
|
73
|
+
visible = screen.visible()
|
|
74
|
+
lower = visible.lower()
|
|
75
|
+
if not pasted and ('shift+tab agents' in lower and 'ctrl+p commands' in lower):
|
|
76
|
+
home_seen = True
|
|
77
|
+
# Insert but do not submit: this makes arbitrary user text
|
|
78
|
+
# observable in the genuine composer without contacting a provider.
|
|
79
|
+
os.write(master, b'\x1b[200~' + USER_TEXT.encode() + b'\x1b[201~')
|
|
80
|
+
pasted = True
|
|
81
|
+
if pasted and any(re.fullmatch(r'\s*[┃|]\s*' + re.escape(USER_TEXT) + r'\s*', row)
|
|
82
|
+
for row in screen.visible().splitlines()):
|
|
83
|
+
break
|
|
84
|
+
visible = screen.visible()
|
|
85
|
+
if not home_seen or not pasted:
|
|
86
|
+
raise AssertionError(f'{mode}: home/composer was not reached; screen={visible[-1600:]!r}')
|
|
87
|
+
composer_row = assert_composer_payload(visible)
|
|
88
|
+
# Exclude the rendered composer row from static chrome checks.
|
|
89
|
+
lines = visible.splitlines()
|
|
90
|
+
marker_rows = [index for index, line in enumerate(lines) if USER_TEXT in line]
|
|
91
|
+
chrome_region = '\n'.join(line for index, line in enumerate(lines) if index not in marker_rows)
|
|
92
|
+
chrome = {}
|
|
93
|
+
for unicode_token, ascii_token in CHROME_PAIRS:
|
|
94
|
+
chrome[unicode_token] = {'asciiAlternative': ascii_token,
|
|
95
|
+
'unicodePresent': unicode_token in chrome_region,
|
|
96
|
+
'asciiPresent': ascii_token in chrome_region}
|
|
97
|
+
if mode == 'ascii':
|
|
98
|
+
if CHROME_PAIRS[0][0] in chrome_region or '▀▀▀' in chrome_region:
|
|
99
|
+
raise AssertionError(f'ASCII startup logo still contains Unicode block-art chrome; screen={visible[-1200:]!r}')
|
|
100
|
+
if 'CARTHAGENT' not in chrome_region.replace(' ', ''):
|
|
101
|
+
raise AssertionError(f'ASCII startup logo alternative not observed; screen={visible[-1200:]!r}')
|
|
102
|
+
else:
|
|
103
|
+
if CHROME_PAIRS[0][0] not in chrome_region:
|
|
104
|
+
raise AssertionError(f'Unicode startup logo not observed; screen={visible[-1200:]!r}')
|
|
105
|
+
if 'Carthagent' not in screen.title_updates:
|
|
106
|
+
raise AssertionError(f'{mode}: Carthagent terminal title not observed: {screen.title_updates!r}')
|
|
107
|
+
if any('opencode' in title.lower() for title in screen.title_updates):
|
|
108
|
+
raise AssertionError(f'{mode}: upstream terminal title remains: {screen.title_updates!r}')
|
|
109
|
+
if 'opencode' in chrome_region.lower():
|
|
110
|
+
raise AssertionError(f'{mode}: upstream branding remains on the home screen')
|
|
111
|
+
return {'mode': mode, 'homeObserved': home_seen, 'terminalTitles': screen.title_updates, 'composerPayloadExact': True,
|
|
112
|
+
'composerRow': composer_row,
|
|
113
|
+
'userText': USER_TEXT, 'hostChrome': chrome, 'screen': visible,
|
|
114
|
+
'ptyOutputSha256': hashlib.sha256(raw).hexdigest(), 'ptyOutputBytes': len(raw)}
|
|
115
|
+
finally:
|
|
116
|
+
if child.poll() is None:
|
|
117
|
+
os.write(master, b'\x1b')
|
|
118
|
+
os.killpg(child.pid, signal.SIGTERM)
|
|
119
|
+
try:
|
|
120
|
+
child.wait(timeout=3)
|
|
121
|
+
except subprocess.TimeoutExpired:
|
|
122
|
+
os.killpg(child.pid, signal.SIGKILL)
|
|
123
|
+
child.wait()
|
|
124
|
+
os.close(master)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def main():
|
|
128
|
+
parser = argparse.ArgumentParser()
|
|
129
|
+
parser.add_argument('--binary', required=True)
|
|
130
|
+
parser.add_argument('--timeout', type=int, default=45)
|
|
131
|
+
parser.add_argument('--artifact')
|
|
132
|
+
args = parser.parse_args()
|
|
133
|
+
modes = [run_mode(args.binary, mode, args.timeout) for mode in ('ascii', 'unicode')]
|
|
134
|
+
result = {'status': 'PASS', 'claim': 'paired actual-TUI PTY rendering; only known static chrome is asserted',
|
|
135
|
+
'provider': 'none; prompt is staged but not submitted', 'physicalClipboardVerified': False,
|
|
136
|
+
'modes': modes}
|
|
137
|
+
text = json.dumps(result, ensure_ascii=False, indent=2) + '\n'
|
|
138
|
+
print(text, end='')
|
|
139
|
+
if args.artifact:
|
|
140
|
+
Path(args.artifact).resolve().write_text(text, encoding='utf8')
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
if __name__ == '__main__':
|
|
144
|
+
main()
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import { mkdir, writeFile, readdir, readFile, rm } from 'node:fs/promises';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { execFileSync } from 'node:child_process';
|
|
4
|
+
import { dirname, join, resolve } from 'node:path';
|
|
5
|
+
import { pathToFileURL } from 'node:url';
|
|
6
|
+
|
|
7
|
+
export async function createManualProjects(
|
|
8
|
+
destination,
|
|
9
|
+
{ packages = 128 } = {},
|
|
10
|
+
) {
|
|
11
|
+
if (!Number.isInteger(packages) || packages < 2 || packages > 1024)
|
|
12
|
+
throw Error('packages must be an integer from 2 to 1024');
|
|
13
|
+
const root = resolve(destination);
|
|
14
|
+
await mkdir(dirname(root), { recursive: true });
|
|
15
|
+
await mkdir(root);
|
|
16
|
+
const write = async (project, file, contents) => {
|
|
17
|
+
const path = join(root, project, file);
|
|
18
|
+
await mkdir(dirname(path), { recursive: true });
|
|
19
|
+
await writeFile(path, contents);
|
|
20
|
+
};
|
|
21
|
+
const packageFile = (name, extra = {}) =>
|
|
22
|
+
JSON.stringify(
|
|
23
|
+
{
|
|
24
|
+
name,
|
|
25
|
+
version: '0.0.0',
|
|
26
|
+
private: true,
|
|
27
|
+
type: 'module',
|
|
28
|
+
scripts: {
|
|
29
|
+
test: 'node --test test/*.test.mjs',
|
|
30
|
+
'test:acceptance': 'node --test acceptance/*.test.mjs',
|
|
31
|
+
...extra,
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
null,
|
|
35
|
+
2,
|
|
36
|
+
) + '\n';
|
|
37
|
+
const imports =
|
|
38
|
+
"import {test} from 'node:test';\nimport assert from 'node:assert/strict';\n";
|
|
39
|
+
try {
|
|
40
|
+
await write('tiny', 'package.json', packageFile('ctg-invoice-fixture'));
|
|
41
|
+
await write(
|
|
42
|
+
'tiny',
|
|
43
|
+
'src/invoice.mjs',
|
|
44
|
+
'export function invoiceTotal(lines) { return lines.reduce((total, line) => total + line.quantity * line.unitPrice, 0); }\n',
|
|
45
|
+
);
|
|
46
|
+
await write(
|
|
47
|
+
'tiny',
|
|
48
|
+
'test/invoice.test.mjs',
|
|
49
|
+
imports +
|
|
50
|
+
"import {invoiceTotal} from '../src/invoice.mjs';\ntest('empty invoices',()=>assert.equal(invoiceTotal([]),0));\ntest('integer totals',()=>assert.equal(invoiceTotal([{quantity:2,unitPrice:500}]),1000));\n",
|
|
51
|
+
);
|
|
52
|
+
await write(
|
|
53
|
+
'tiny',
|
|
54
|
+
'acceptance/discount.test.mjs',
|
|
55
|
+
imports +
|
|
56
|
+
"import {invoiceTotal} from '../src/invoice.mjs';\ntest('discount in basis points, rounded once to cents',()=>assert.equal(invoiceTotal([{quantity:3,unitPrice:199}],{discountBps:1500}),507));\ntest('reject invalid discounts',()=>assert.throws(()=>invoiceTotal([],{discountBps:10001}),RangeError));\ntest('immutable inputs',()=>assert.equal(invoiceTotal(Object.freeze([Object.freeze({quantity:1,unitPrice:999})]),{discountBps:1000}),899));\n",
|
|
57
|
+
);
|
|
58
|
+
await write(
|
|
59
|
+
'tiny',
|
|
60
|
+
'TASK.md',
|
|
61
|
+
'# Small task\nAdd optional {discountBps} to invoiceTotal. Prices/totals are integer cents. Accept integer discounts 0..10000; reject others with RangeError. Round once after discount. Preserve old calls and never mutate inputs. Run npm test and npm run test:acceptance. Baseline is green; new-feature acceptance is intentionally red.\n',
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
await write(
|
|
65
|
+
'product',
|
|
66
|
+
'package.json',
|
|
67
|
+
packageFile('ctg-forgeboard-fixture', { start: 'node server.mjs' }),
|
|
68
|
+
);
|
|
69
|
+
await write(
|
|
70
|
+
'product',
|
|
71
|
+
'src/issues.mjs',
|
|
72
|
+
"export const initialIssues=()=>[{id:1,title:'Design onboarding',status:'todo',revision:1},{id:2,title:'Fix keyboard navigation',status:'doing',revision:1}];\nexport const selectIssues=(issues,status)=>issues.filter(issue=>!status||issue.status===status);\n",
|
|
73
|
+
);
|
|
74
|
+
await write(
|
|
75
|
+
'product',
|
|
76
|
+
'server.mjs',
|
|
77
|
+
`import {createServer} from 'node:http';
|
|
78
|
+
import {readFile} from 'node:fs/promises';
|
|
79
|
+
import {pathToFileURL} from 'node:url';
|
|
80
|
+
import {resolve} from 'node:path';
|
|
81
|
+
import {initialIssues,selectIssues} from './src/issues.mjs';
|
|
82
|
+
export function createApp(){
|
|
83
|
+
const issues=initialIssues();
|
|
84
|
+
return createServer(async(request,response)=>{
|
|
85
|
+
const url=new URL(request.url,'http://localhost');
|
|
86
|
+
if(request.method==='GET'&&url.pathname==='/api/issues'){response.writeHead(200,{'content-type':'application/json'});response.end(JSON.stringify(selectIssues(issues,url.searchParams.get('status'))));return;}
|
|
87
|
+
if(request.method==='GET'&&url.pathname==='/'){response.writeHead(200,{'content-type':'text/html; charset=utf-8'});response.end(await readFile(new URL('./public/index.html',import.meta.url)));return;}
|
|
88
|
+
response.writeHead(404,{'content-type':'application/json'});response.end(JSON.stringify({error:'not found'}));
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
if(process.argv[1]&&import.meta.url===pathToFileURL(resolve(process.argv[1])).href){const server=createApp();server.listen(Number(process.env.PORT??3017),'127.0.0.1',()=>console.log('Forgeboard: http://127.0.0.1:'+server.address().port));}
|
|
92
|
+
`,
|
|
93
|
+
);
|
|
94
|
+
await write(
|
|
95
|
+
'product',
|
|
96
|
+
'public/index.html',
|
|
97
|
+
'<!doctype html><html lang="en"><meta charset="utf-8"><title>Forgeboard</title><style>body{font:18px system-ui;background:#f4f1e9;color:#202923;max-width:900px;margin:64px auto;padding:24px}header{border-bottom:2px solid #202923}li{background:white;padding:20px;margin:12px 0;list-style:none}select{font:inherit;padding:8px}</style><header><small>CARTHAGENT TEST PROJECT / 02</small><h1>Forgeboard</h1><p>A disposable issue board. No external services.</p></header><p><label>Status <select id="status"><option value="">All</option><option>todo</option><option>doing</option><option>done</option></select></label></p><ul id="issues"></ul><script type="module">async function load(){const issues=await(await fetch("/api/issues?status="+encodeURIComponent(document.querySelector("select").value))).json();document.querySelector("ul").replaceChildren(...issues.map(issue=>{const li=document.createElement("li");li.textContent=issue.title+" · "+issue.status+" · revision "+issue.revision;return li}));}document.querySelector("select").onchange=load;load();</script></html>\n',
|
|
98
|
+
);
|
|
99
|
+
const apiTest =
|
|
100
|
+
"import {createApp} from '../server.mjs';\nasync function appFor(t){const app=createApp();await new Promise(r=>app.listen(0,'127.0.0.1',r));t.after(()=>new Promise(r=>app.close(r)));return 'http://127.0.0.1:'+app.address().port;}\n";
|
|
101
|
+
await write(
|
|
102
|
+
'product',
|
|
103
|
+
'test/api.test.mjs',
|
|
104
|
+
imports +
|
|
105
|
+
apiTest +
|
|
106
|
+
"test('real HTTP list, filtering, UI and 404',async t=>{const base=await appFor(t);assert.equal((await(await fetch(base+'/api/issues')).json()).length,2);assert.equal((await(await fetch(base+'/api/issues?status=doing')).json())[0].id,2);assert.match(await(await fetch(base)).text(),/Forgeboard/);assert.equal((await fetch(base+'/missing')).status,404);});\n",
|
|
107
|
+
);
|
|
108
|
+
await write(
|
|
109
|
+
'product',
|
|
110
|
+
'acceptance/update.test.mjs',
|
|
111
|
+
imports +
|
|
112
|
+
apiTest +
|
|
113
|
+
"test('optimistic concurrency protects competing status updates',async t=>{const base=await appFor(t);const patch=(revision,status)=>fetch(base+'/api/issues/1',{method:'PATCH',headers:{'content-type':'application/json'},body:JSON.stringify({revision,status})});const first=await patch(1,'done');assert.equal(first.status,200);assert.equal((await first.json()).revision,2);assert.equal((await patch(1,'doing')).status,409);assert.equal((await patch(2,'invalid')).status,400);const list=await(await fetch(base+'/api/issues?status=done')).json();assert.equal(list[0].id,1);assert.equal(list[0].status,'done');});\n",
|
|
114
|
+
);
|
|
115
|
+
await write(
|
|
116
|
+
'product',
|
|
117
|
+
'TASK.md',
|
|
118
|
+
'# Product task\nImplement PATCH /api/issues/:id accepting JSON {status,revision}. Allow todo/doing/done. Stale revisions ->409; invalid input/JSON ->400; unknown ids ->404; oversized bodies ->413. Increment revision exactly once on success. Add accessible status controls and error feedback to the board. Keep in-memory storage; no external services/dependencies. Preserve safe text rendering. Add malformed/oversized/unknown-id tests beyond supplied acceptance. Run npm test and npm run test:acceptance. Start npm start and inspect http://127.0.0.1:3017 manually.\n',
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
await write(
|
|
122
|
+
'scale',
|
|
123
|
+
'package.json',
|
|
124
|
+
packageFile('ctg-scale-monorepo', {
|
|
125
|
+
'test:stress': 'node --test --test-concurrency=2 test/*.test.mjs',
|
|
126
|
+
}),
|
|
127
|
+
);
|
|
128
|
+
await write(
|
|
129
|
+
'scale',
|
|
130
|
+
'shared/money.mjs',
|
|
131
|
+
'export function roundMoney(value){return Math.round(value*100)/100;}\n',
|
|
132
|
+
);
|
|
133
|
+
await write(
|
|
134
|
+
'scale',
|
|
135
|
+
'test/packages.test.mjs',
|
|
136
|
+
imports +
|
|
137
|
+
Array.from(
|
|
138
|
+
{ length: packages },
|
|
139
|
+
(_, i) =>
|
|
140
|
+
`test('domain-${i} integration',async()=>{const {total}=await import('../packages/domain-${i}/src/index.mjs');assert.equal(total([1,2,3]),6);assert.equal(total([]),0);});`,
|
|
141
|
+
).join('\n') +
|
|
142
|
+
'\n',
|
|
143
|
+
);
|
|
144
|
+
for (let i = 0; i < packages; i++) {
|
|
145
|
+
await write(
|
|
146
|
+
'scale',
|
|
147
|
+
`packages/domain-${i}/package.json`,
|
|
148
|
+
JSON.stringify({
|
|
149
|
+
name: `@fixture/domain-${i}`,
|
|
150
|
+
private: true,
|
|
151
|
+
type: 'module',
|
|
152
|
+
}) + '\n',
|
|
153
|
+
);
|
|
154
|
+
await write(
|
|
155
|
+
'scale',
|
|
156
|
+
`packages/domain-${i}/src/index.mjs`,
|
|
157
|
+
"import {roundMoney} from '../../../shared/money.mjs';\nexport const total=values=>roundMoney(values.reduce((sum,value)=>sum+value,0));\n",
|
|
158
|
+
);
|
|
159
|
+
for (let j = 0; j < 8; j++)
|
|
160
|
+
await write(
|
|
161
|
+
'scale',
|
|
162
|
+
`packages/domain-${i}/src/metric-${j}.mjs`,
|
|
163
|
+
`import {total} from './index.mjs';\nexport const metric${j}=values=>({domain:${i},metric:${j},total:total(values)});\n`,
|
|
164
|
+
);
|
|
165
|
+
await write(
|
|
166
|
+
'scale',
|
|
167
|
+
`docs/domain-${i}.md`,
|
|
168
|
+
`# Domain ${i}\nUses shared/money.mjs through its public index. Preserve domain identity.\n`,
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
await write(
|
|
172
|
+
'scale',
|
|
173
|
+
'acceptance/rounding.test.mjs',
|
|
174
|
+
imports +
|
|
175
|
+
"import {roundMoney} from '../shared/money.mjs';\nimport {total} from '../packages/domain-0/src/index.mjs';\ntest('decimal halves away from zero across shared consumers',()=>{assert.equal(roundMoney(1.005),1.01);assert.equal(roundMoney(-1.005),-1.01);assert.equal(total([1.005]),1.01);});\ntest('reject nonfinite amounts',()=>{for(const v of [NaN,Infinity,-Infinity])assert.throws(()=>roundMoney(v),RangeError);});\n",
|
|
176
|
+
);
|
|
177
|
+
await write(
|
|
178
|
+
'scale',
|
|
179
|
+
'TASK.md',
|
|
180
|
+
`# Large monorepo task\n${packages} packages share money rounding; do not rewrite generated packages. Fix representable decimal halves (1.005 ->1.01, -1.005 ->-1.01, ties away from zero); reject nonfinite numbers with RangeError. Preserve existing behavior, avoid dependencies, and add boundary regressions. Run npm test and npm run test:acceptance. Use code navigation to find affected consumers and verify at least three distinct domains.\n`,
|
|
181
|
+
);
|
|
182
|
+
const inventory = {};
|
|
183
|
+
for (const name of ['tiny', 'product', 'scale']) {
|
|
184
|
+
await write(name, '.gitignore', 'node_modules/\n.harness/\n.env\n');
|
|
185
|
+
const files = [];
|
|
186
|
+
const visit = async (directory) => {
|
|
187
|
+
for (const entry of (
|
|
188
|
+
await readdir(join(root, name, directory), { withFileTypes: true })
|
|
189
|
+
).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
190
|
+
const file = directory ? `${directory}/${entry.name}` : entry.name;
|
|
191
|
+
if (entry.isDirectory()) await visit(file);
|
|
192
|
+
else
|
|
193
|
+
files.push({
|
|
194
|
+
path: file,
|
|
195
|
+
sha256: createHash('sha256')
|
|
196
|
+
.update(await readFile(join(root, name, file)))
|
|
197
|
+
.digest('hex'),
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
await visit('');
|
|
202
|
+
inventory[name] = { files: files.length, source: files };
|
|
203
|
+
const git = (...args) =>
|
|
204
|
+
execFileSync('git', ['-c', 'core.hooksPath=disabled-hooks', ...args], {
|
|
205
|
+
cwd: join(root, name),
|
|
206
|
+
stdio: 'pipe',
|
|
207
|
+
});
|
|
208
|
+
git('init', '-b', 'main');
|
|
209
|
+
git('add', '.');
|
|
210
|
+
git(
|
|
211
|
+
'-c',
|
|
212
|
+
'user.name=CTG manual fixture',
|
|
213
|
+
'-c',
|
|
214
|
+
'user.email=fixture@localhost',
|
|
215
|
+
'commit',
|
|
216
|
+
'-m',
|
|
217
|
+
'Initialize disposable manual-test project\n\nCo-authored-by: openhands <openhands@all-hands.dev>',
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
await writeFile(
|
|
221
|
+
join(root, 'inventory.json'),
|
|
222
|
+
JSON.stringify({ version: 1, packages, projects: inventory }, null, 2) +
|
|
223
|
+
'\n',
|
|
224
|
+
);
|
|
225
|
+
return {
|
|
226
|
+
root,
|
|
227
|
+
packages,
|
|
228
|
+
projects: Object.fromEntries(
|
|
229
|
+
Object.entries(inventory).map(([name, value]) => [
|
|
230
|
+
name,
|
|
231
|
+
{ files: value.files },
|
|
232
|
+
]),
|
|
233
|
+
),
|
|
234
|
+
};
|
|
235
|
+
} catch (error) {
|
|
236
|
+
await rm(root, { recursive: true, force: true });
|
|
237
|
+
throw error;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (
|
|
242
|
+
process.argv[1] &&
|
|
243
|
+
import.meta.url === pathToFileURL(resolve(process.argv[1])).href
|
|
244
|
+
) {
|
|
245
|
+
const [destination, packages] = process.argv.slice(2);
|
|
246
|
+
if (!destination)
|
|
247
|
+
throw Error(
|
|
248
|
+
'Usage: node tools/native-manual-projects.mjs <new-directory> [packages=128]',
|
|
249
|
+
);
|
|
250
|
+
console.log(
|
|
251
|
+
JSON.stringify(
|
|
252
|
+
await createManualProjects(destination, {
|
|
253
|
+
packages: Number(packages ?? 128),
|
|
254
|
+
}),
|
|
255
|
+
null,
|
|
256
|
+
2,
|
|
257
|
+
),
|
|
258
|
+
);
|
|
259
|
+
}
|