codiedev 0.10.0-beta.5 → 0.10.0-beta.7
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 +4 -4
- package/dist/captureSignals.js +2 -0
- package/dist/cli.js +38 -50
- package/dist/codexHookTrust.js +67 -4
- package/dist/codie.js +23 -74
- package/dist/codieLauncher.js +18 -22
- package/dist/commands/agentTemplates.generated.js +136 -0
- package/dist/commands/agents.js +1068 -0
- package/dist/commands/agentsSdk.generated.js +7 -0
- package/dist/commands/doctor.js +12 -38
- package/dist/commands/evals.js +187 -0
- package/dist/commands/pack.js +1 -1
- package/dist/commands/traces.js +79 -15
- package/dist/commands/tracesLabel.js +348 -0
- package/dist/connect.js +11 -52
- package/dist/copilotHooks.js +0 -4
- package/dist/cursorHooks.js +0 -15
- package/dist/harnessDirs.js +4 -0
- package/dist/hook.js +33 -197
- package/dist/limitEventReporter.js +1 -1
- package/dist/managedPi.js +240 -0
- package/dist/mcp.js +0 -307
- package/dist/packs/harnesses.js +40 -3
- package/dist/packs/install.js +2 -1
- package/dist/piHooks.js +216 -0
- package/dist/piRuntime.js +120 -0
- package/dist/selfUpdate.js +47 -2
- package/dist/utils.js +232 -115
- package/package.json +5 -7
- package/dist/codieStartup.js +0 -27
- package/dist/commands/promote.js +0 -53
- package/dist/commands/react.js +0 -38
- package/dist/commands/reverseTicket.js +0 -444
- package/dist/commands/security.js +0 -156
- package/dist/prUrl.js +0 -28
- package/dist/security.js +0 -417
- package/dist/securityDeepScan.js +0 -235
- package/dist/securityDoctor.js +0 -61
- package/dist/securityFeature.js +0 -6
- package/dist/securityInstall.js +0 -232
- package/dist/securityInventory.js +0 -566
- package/dist/securityPilot.js +0 -59
- package/dist/securityPolicy.js +0 -181
- package/dist/securitySession.js +0 -72
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.jsPackage = exports.jsTypes = exports.jsIndex = exports.python = void 0;
|
|
4
|
+
exports.python = "\"\"\"Codiedev agents SDK — the run primitive, from Python.\n\nInside a deployed agent (everything is already configured):\n\n import codiedev_agents as agents\n\n task = agents.input() # this run's input (JSON)\n llm = agents.openai() # OpenAI client on the Codiedev gateway, traced\n child = agents.run(\"summarize\", {\"text\": ...}) # start another agent as a child run\n agents.output({\"summary\": child[\"output\"]}) # this run's result\n agents.request() # webhook runs: method, headers, raw body\n agents.respond({\"text\": \"on it\"}) # what a webhook caller waiting (?wait=) gets back\n\n agents.state.get(\"cursor\") # per-agent storage that outlives runs\n agents.state.set(\"cursor\", 42)\n agents.step(\"fetch\", lambda: fetch()) # done once per run, even across retries\n agents.start(\"triage\", {...}, delay=3600) # start a run later (up to 7 days)\n agents.request_approval(\"Refund $120?\", context={...}) # a person decides; a run acts on it\n agents.attempt() # 1, or 2+ when this run is a retry\n\nOutside a run (a script, a notebook, another service), set CODIEDEV_TOKEN\n(your CLI token) and CODIEDEV_AGENTS_URL, then call agents.run() the same way.\nStandard library only, so it drops into any project.\n\"\"\"\n\nfrom __future__ import annotations\n\nimport json\nimport os\nimport time\nimport urllib.error\nimport urllib.parse\nimport urllib.request\nfrom typing import Any\n\n__all__ = [\n \"input\", \"output\", \"request\", \"respond\", \"run\", \"start\", \"get\", \"openai\", \"attempt\",\n \"state\", \"step\", \"request_approval\", \"get_approval\", \"AgentRunError\", \"StateConflict\",\n]\n\n_INPUT_PATH = os.environ.get(\"CODIEDEV_INPUT_PATH\", \"/agent/.codiedev/input.json\")\n_OUTPUT_PATH = os.environ.get(\"CODIEDEV_OUTPUT_PATH\", \"/agent/.codiedev/output.json\")\n_REQUEST_PATH = os.environ.get(\"CODIEDEV_REQUEST_PATH\", \"/agent/.codiedev/request.json\")\n_RESPONSE_PATH = os.environ.get(\"CODIEDEV_RESPONSE_PATH\", \"/agent/.codiedev/response.json\")\n# Set by `codiedev agents run --local`: state lives in this file instead of Codiedev.\n_STATE_PATH = os.environ.get(\"CODIEDEV_STATE_PATH\")\n_DONE = {\"succeeded\", \"failed\", \"cancelled\"}\n\n\nclass StateConflict(RuntimeError):\n \"\"\"state.set(..., if_version=n) found the key at another version.\"\"\"\n\n def __init__(self, key: str, version: int):\n super().__init__(f\"state {key!r} changed (now version {version})\")\n self.key = key\n self.version = version\n\n\nclass AgentRunError(RuntimeError):\n def __init__(self, run: dict):\n super().__init__(f\"{run.get('agent')} run {run.get('id')} {run.get('status')}: {run.get('error') or 'no error message'}\")\n self.run = run\n\n\ndef input(default: Any = None) -> Any: # noqa: A001 - mirrors output()\n \"\"\"This run's input, parsed from JSON (None when started without one).\"\"\"\n try:\n with open(_INPUT_PATH) as f:\n value = json.load(f)\n except FileNotFoundError:\n return default\n return default if value is None else value\n\n\ndef output(value: Any) -> None:\n \"\"\"Set this run's result. Must be JSON-serializable; the last call wins.\"\"\"\n os.makedirs(os.path.dirname(_OUTPUT_PATH), exist_ok=True)\n with open(_OUTPUT_PATH, \"w\") as f:\n json.dump(value, f)\n\n\ndef respond(body: Any = None, status: int = 200, headers: \"dict | None\" = None) -> None:\n \"\"\"What a webhook caller gets back when it waits for the run (?wait=N):\n a string is sent as text, anything else as JSON. Without respond(), a\n waiting caller gets the run's output.\"\"\"\n os.makedirs(os.path.dirname(_RESPONSE_PATH), exist_ok=True)\n with open(_RESPONSE_PATH, \"w\") as f:\n json.dump({\"status\": status, \"headers\": headers or {}, \"body\": body}, f)\n\n\ndef attempt() -> int:\n \"\"\"This run's attempt: 1, or 2+ when it's a retry of a failed attempt.\"\"\"\n return int(os.environ.get(\"CODIEDEV_ATTEMPT\", \"1\"))\n\n\ndef request() -> \"dict | None\":\n \"\"\"For a webhook run: {\"method\", \"query\", \"headers\", \"body\"} with the raw\n body, to verify the sender's signature. None for other runs.\"\"\"\n try:\n with open(_REQUEST_PATH) as f:\n return json.load(f)\n except FileNotFoundError:\n return None\n\n\ndef _base() -> str:\n url = os.environ.get(\"CODIEDEV_AGENTS_URL\", \"\").rstrip(\"/\")\n if not url:\n raise RuntimeError(\"Set CODIEDEV_AGENTS_URL (it's set for you inside a run)\")\n return url\n\n\ndef _token() -> str:\n token = os.environ.get(\"CODIEDEV_RUN_TOKEN\") or os.environ.get(\"CODIEDEV_TOKEN\")\n if not token:\n raise RuntimeError(\"Set CODIEDEV_TOKEN to your Codiedev CLI token (inside a run it's set for you)\")\n return token\n\n\ndef _request(method: str, path: str, body: dict | None = None, ok: tuple[int, ...] = ()) -> dict:\n req = urllib.request.Request(\n f\"{_base()}{path}\",\n method=method,\n data=json.dumps(body).encode() if body is not None else None,\n headers={\"Authorization\": f\"Bearer {_token()}\", \"Content-Type\": \"application/json\"},\n )\n try:\n with urllib.request.urlopen(req, timeout=30) as res:\n return json.loads(res.read() or b\"{}\")\n except urllib.error.HTTPError as err:\n detail = err.read().decode(errors=\"replace\")\n if err.code in ok:\n return {\"_status\": err.code, **json.loads(detail or \"{}\")}\n try:\n detail = json.loads(detail).get(\"error\", detail)\n except ValueError:\n pass\n raise RuntimeError(f\"Codiedev agents API {err.code}: {detail}\") from None\n\n\ndef start(\n agent: str,\n input: Any = None, # noqa: A002\n idempotency_key: str | None = None,\n delay: float | None = None,\n retries: int | None = None,\n) -> str:\n \"\"\"Start a run and return its id without waiting. With an idempotency key,\n a second start within a day returns the first run's id. delay (seconds,\n up to 7 days) starts it later; retries overrides the agent's retries.\"\"\"\n body: dict = {\"agent\": agent, \"input\": input}\n if idempotency_key:\n body[\"idempotencyKey\"] = idempotency_key\n if delay:\n body[\"delaySec\"] = int(delay)\n if retries is not None:\n body[\"retries\"] = retries\n return _request(\"POST\", \"/api/agents/v1/run\", body)[\"runId\"]\n\n\ndef get(run_id: str) -> dict:\n \"\"\"A run's status, input, output (parsed), error, log tail and children.\"\"\"\n run = _request(\"GET\", f\"/api/agents/v1/run?id={run_id}\")\n if isinstance(run.get(\"output\"), str):\n try:\n run[\"output\"] = json.loads(run[\"output\"])\n except ValueError:\n pass\n return run\n\n\ndef run(\n agent: str,\n input: Any = None, # noqa: A002\n timeout: float = 900,\n poll: float = 2.0,\n check: bool = True,\n idempotency_key: str | None = None,\n) -> dict:\n \"\"\"Run an agent and wait for it. Returns the run; its result is run[\"output\"].\n\n Inside a run this starts a child run, so the chain shows as one tree.\n Raises AgentRunError if it fails (pass check=False to get the run anyway).\n \"\"\"\n run_id = start(agent, input, idempotency_key)\n deadline = time.monotonic() + timeout\n while True:\n result = get(run_id)\n if result.get(\"status\") in _DONE:\n if check and result[\"status\"] != \"succeeded\":\n raise AgentRunError(result)\n return result\n if time.monotonic() > deadline:\n raise TimeoutError(f\"{agent} run {run_id} still {result.get('status')} after {timeout}s\")\n time.sleep(poll)\n\n\nclass _State:\n \"\"\"Per-agent key-value storage that outlives runs (values: any JSON, up\n to 64 KB; 1,000 keys). Every write bumps the key's version; pass\n if_version to write only if nobody else did since you read it.\"\"\"\n\n def get(self, key: str, default: Any = None) -> Any:\n value, _ = self.get_versioned(key)\n return default if value is None else value\n\n def get_versioned(self, key: str) -> \"tuple[Any, int]\":\n \"\"\"(value, version); (None, 0) when the key isn't set.\"\"\"\n if _STATE_PATH:\n row = _local_state().get(key)\n return (row[\"value\"], row[\"version\"]) if row else (None, 0)\n res = _request(\"GET\", f\"/api/agents/v1/state?{urllib.parse.urlencode({'key': key, 'agent': _agent()})}\", ok=(404,))\n if res.get(\"_status\") == 404:\n return None, 0\n return json.loads(res[\"value\"]), int(res[\"version\"])\n\n def set(self, key: str, value: Any, if_version: \"int | None\" = None, ttl: \"float | None\" = None) -> int:\n \"\"\"Store value; returns the new version. Raises StateConflict when\n if_version is given and the key is at another version (0 = unset).\"\"\"\n return self._write(key, value, if_version, ttl)\n\n def delete(self, key: str, if_version: \"int | None\" = None) -> None:\n self._write(key, None, if_version, None)\n\n def list(self, prefix: str = \"\", values: bool = False) -> \"list[dict]\": # noqa: A003\n \"\"\"Keys (and with values=True, values) starting with prefix.\"\"\"\n if _STATE_PATH:\n rows = sorted((k, r) for k, r in _local_state().items() if k.startswith(prefix))\n return [{\"key\": k, \"version\": r[\"version\"], **({\"value\": r[\"value\"]} if values else {})} for k, r in rows]\n keys: list = []\n after = None\n while True:\n params = {\"prefix\": prefix, \"values\": \"1\" if values else \"0\", \"limit\": 200, \"agent\": _agent()}\n if after:\n params[\"after\"] = after\n page = _request(\"GET\", f\"/api/agents/v1/state?{urllib.parse.urlencode(params)}\")\n for row in page[\"keys\"]:\n if \"value\" in row:\n row[\"value\"] = json.loads(row[\"value\"])\n keys.extend(page[\"keys\"])\n after = page.get(\"next\")\n if not after:\n return keys\n\n def update(self, key: str, fn, default: Any = None, retries: int = 10) -> Any:\n \"\"\"Read-modify-write that's safe with runs in parallel: fn(old) -> new,\n retried if another run wrote first. Returns the new value.\"\"\"\n for _ in range(retries):\n value, version = self.get_versioned(key)\n new = fn(default if value is None else value)\n try:\n self.set(key, new, if_version=version)\n return new\n except StateConflict:\n time.sleep(0.05)\n raise StateConflict(key, -1)\n\n def _write(self, key: str, value: Any, if_version: \"int | None\", ttl: \"float | None\") -> int:\n if _STATE_PATH:\n data = _local_state()\n current = data.get(key, {}).get(\"version\", 0)\n if if_version is not None and if_version != current:\n raise StateConflict(key, current)\n if value is None:\n data.pop(key, None)\n else:\n data[key] = {\"value\": value, \"version\": current + 1}\n with open(_STATE_PATH, \"w\") as f:\n json.dump(data, f, indent=2)\n return 0 if value is None else current + 1\n body: dict = {\"key\": key, \"value\": value, \"agent\": _agent()}\n if if_version is not None:\n body[\"ifVersion\"] = if_version\n if ttl:\n body[\"ttlSec\"] = int(ttl)\n res = _request(\"POST\", \"/api/agents/v1/state\", body, ok=(409,))\n if res.get(\"_status\") == 409:\n raise StateConflict(key, int(res.get(\"version\", 0)))\n return int(res.get(\"version\", 0))\n\n\ndef _agent() -> str:\n # Inside a run the run token picks the agent; a CLI token needs it named.\n return os.environ.get(\"CODIEDEV_AGENT\", \"\")\n\n\ndef _local_state() -> dict:\n try:\n with open(_STATE_PATH or \"\") as f:\n return json.load(f)\n except (FileNotFoundError, ValueError):\n return {}\n\n\nstate = _State()\n\n\ndef step(name: str, fn, ttl: float = 7 * 86400) -> Any:\n \"\"\"Run fn() once for this run and remember its (JSON) result: if the run\n is retried after a failure, finished steps return their saved result\n instead of running again. Use it around side effects (posting a reply,\n charging a card) so a retry doesn't repeat them.\"\"\"\n key = f\"step/{os.environ.get('CODIEDEV_RUN_ID', 'local')}/{name}\"\n value, version = state.get_versioned(key)\n if version:\n return value[\"result\"]\n result = fn()\n state.set(key, {\"result\": result}, ttl=ttl)\n return result\n\n\n_SELF = object()\n\n\ndef request_approval(\n title: str,\n details: Any = None,\n options: \"list[str] | None\" = None,\n approvers: \"list[str] | None\" = None,\n teams: \"list[str] | None\" = None,\n fields: \"list[dict] | None\" = None,\n then: Any = _SELF,\n context: Any = None,\n expires_in: \"float | None\" = None,\n) -> dict:\n \"\"\"Ask a person to decide. It shows in Codiedev (Agents → Approvals) and\n at the returned [\"url\"], which you can post anywhere (Slack, email).\n\n When it's decided, a run of `then` (this agent by default; None for no\n run) starts with input {\"approval\": {\"decision\", \"comment\", \"decidedBy\",\n ...}, \"context\": context}. If nobody decides within expires_in seconds\n (default 7 days), it starts with decision \"expired\". Usually the run\n ends after asking, so nothing runs (or costs) while it waits.\n options default to [\"approve\", \"reject\"]; approvers (emails) and teams\n (team names, e.g. [\"Finance\"]) limit who can decide (admins always can).\n Apps your company connects (Agents → Approvals → Apps) are told about it\n by webhook and can decide on a person's behalf.\n\n fields asks the person to fill something in as they decide, e.g.\n [{\"name\": \"rating\", \"type\": \"rating\"}, {\"name\": \"why\", \"type\": \"textarea\"}]\n (types: text, textarea, number, rating, select, multiselect, boolean;\n required, label, help, options, min, max, default). What they entered\n arrives as input[\"approval\"][\"values\"]. With fields and no options, the\n only choice is \"submit\".\"\"\"\n body: dict = {\"title\": title, \"details\": details, \"options\": options, \"approvers\": approvers, \"teams\": teams, \"fields\": fields, \"context\": context}\n if then is not _SELF:\n body[\"onDecision\"] = then\n if expires_in:\n body[\"expiresSec\"] = int(expires_in)\n if os.environ.get(\"CODIEDEV_LOCAL\") == \"1\":\n print(f\"[local] approval requested: {title} ({', '.join(options or (['submit'] if fields else ['approve', 'reject']))}); not sent on a local run\", flush=True)\n return {\"id\": f\"local-{int(time.time())}\", \"status\": \"pending\", \"url\": None, \"title\": title}\n return _request(\"POST\", \"/api/agents/v1/approvals\", {k: v for k, v in body.items() if v is not None or k == \"onDecision\"})\n\n\ndef get_approval(approval_id: str) -> dict:\n \"\"\"An approval's status (\"pending\", \"decided\", \"expired\"), decision and comment.\"\"\"\n return _request(\"GET\", f\"/api/agents/v1/approvals?id={urllib.parse.quote(approval_id)}\")\n\n\ndef openai(**kwargs):\n \"\"\"An OpenAI client on the Codiedev gateway, with trace headers for this run.\"\"\"\n from openai import OpenAI\n\n headers = {\n \"X-Codiedev-Workload\": os.environ.get(\"CODIEDEV_AGENT\", \"local-agent\"),\n \"X-Codiedev-Run-Id\": os.environ.get(\"CODIEDEV_RUN_ID\", f\"local-{int(time.time())}\"),\n \"X-Codiedev-Environment\": \"production\" if os.environ.get(\"CODIEDEV_RUN_ID\") else \"development\",\n }\n headers.update(kwargs.pop(\"default_headers\", {}) or {})\n return OpenAI(\n base_url=os.environ.get(\"CODIEDEV_GATEWAY_URL\", \"https://gateway.codiedev.com/api/v1\"),\n api_key=os.environ.get(\"CODIEDEV_API_KEY\") or os.environ.get(\"CODIEDEV_TOKEN\"),\n default_headers=headers,\n **kwargs,\n )\n";
|
|
5
|
+
exports.jsIndex = "// Codiedev agents SDK for JavaScript and TypeScript, preinstalled in every\n// run. Same primitive as the Python SDK (codiedev_agents.py):\n//\n// import * as agents from \"codiedev-agents\";\n// const task = agents.input(); // this run's input (JSON)\n// const llm = await agents.openai(); // OpenAI client on the Codiedev gateway, traced\n// const child = await agents.run(\"summarize\", { text }); // another agent, as a child run\n// agents.output({ summary: child.output }); // this run's result\n// agents.respond({ text: \"on it\" }); // what a webhook caller waiting (?wait=) gets back\n// await agents.state.get(\"cursor\"); // per-agent storage that outlives runs\n// await agents.step(\"post\", () => post()); // done once per run, even across retries\n// await agents.start(\"triage\", input, { delaySec: 3600 }); // start a run later\n// await agents.requestApproval(\"Refund $120?\", { context }); // a person decides; a run acts on it\n//\n// Outside a run, set CODIEDEV_TOKEN and CODIEDEV_AGENTS_URL and call run() the same way.\nimport fs from \"node:fs\";\nimport path from \"node:path\";\n\nconst INPUT_PATH = process.env.CODIEDEV_INPUT_PATH || \"/agent/.codiedev/input.json\";\nconst OUTPUT_PATH = process.env.CODIEDEV_OUTPUT_PATH || \"/agent/.codiedev/output.json\";\nconst REQUEST_PATH = process.env.CODIEDEV_REQUEST_PATH || \"/agent/.codiedev/request.json\";\nconst RESPONSE_PATH = process.env.CODIEDEV_RESPONSE_PATH || \"/agent/.codiedev/response.json\";\n// Set by `codiedev agents run --local`: state lives in this file instead of Codiedev.\nconst STATE_PATH = process.env.CODIEDEV_STATE_PATH;\n// Inside a run the run token picks the agent; a CLI token needs it named.\nconst AGENT = process.env.CODIEDEV_AGENT || \"\";\nconst DONE = new Set([\"succeeded\", \"failed\", \"cancelled\"]);\n\n/** state.set(..., { ifVersion }) found the key at another version. */\nexport class StateConflict extends Error {\n constructor(key, version) {\n super(`state ${JSON.stringify(key)} changed (now version ${version})`);\n this.key = key;\n this.version = version;\n }\n}\n\nexport class AgentRunError extends Error {\n constructor(run) {\n super(`${run.agent} run ${run.id} ${run.status}: ${run.error || \"no error message\"}`);\n this.run = run;\n }\n}\n\nfunction readJson(file) {\n try {\n return JSON.parse(fs.readFileSync(file, \"utf8\"));\n } catch (err) {\n if (err.code === \"ENOENT\") return null;\n throw err;\n }\n}\n\n/** This run's input, parsed from JSON (`fallback` when started without one). */\nexport function input(fallback = null) {\n const value = readJson(INPUT_PATH);\n return value ?? fallback;\n}\n\n/** Set this run's result. Must be JSON-serializable; the last call wins. */\nexport function output(value) {\n fs.mkdirSync(path.dirname(OUTPUT_PATH), { recursive: true });\n fs.writeFileSync(OUTPUT_PATH, JSON.stringify(value));\n}\n\n/** What a webhook caller gets back when it waits for the run (?wait=N): a\n * string is sent as text, anything else as JSON. Without it, a waiting\n * caller gets the run's output. */\nexport function respond(body = null, { status = 200, headers = {} } = {}) {\n fs.mkdirSync(path.dirname(RESPONSE_PATH), { recursive: true });\n fs.writeFileSync(RESPONSE_PATH, JSON.stringify({ status, headers, body }));\n}\n\n/** This run's attempt: 1, or 2+ when it's a retry of a failed attempt. */\nexport function attempt() {\n return Number(process.env.CODIEDEV_ATTEMPT || \"1\");\n}\n\n/** For a webhook run: { method, query, headers, body } with the raw body, to verify a signature. */\nexport function request() {\n return readJson(REQUEST_PATH);\n}\n\nfunction base() {\n const url = (process.env.CODIEDEV_AGENTS_URL || \"\").replace(/\\/$/, \"\");\n if (!url) throw new Error(\"Set CODIEDEV_AGENTS_URL (it's set for you inside a run)\");\n return url;\n}\n\nfunction token() {\n const t = process.env.CODIEDEV_RUN_TOKEN || process.env.CODIEDEV_TOKEN;\n if (!t) throw new Error(\"Set CODIEDEV_TOKEN to your Codiedev CLI token (inside a run it's set for you)\");\n return t;\n}\n\nasync function call(method, route, body, ok = []) {\n const res = await fetch(`${base()}${route}`, {\n method,\n headers: { Authorization: `Bearer ${token()}`, \"Content-Type\": \"application/json\" },\n body: body === undefined ? undefined : JSON.stringify(body),\n });\n const text = await res.text();\n let data = {};\n try {\n data = text ? JSON.parse(text) : {};\n } catch {\n data = { error: text };\n }\n if (ok.includes(res.status)) return { _status: res.status, ...data };\n if (!res.ok) throw new Error(`Codiedev agents API ${res.status}: ${data.error || text}`);\n return data;\n}\n\n/** Start a run and return its id without waiting. delaySec (up to 7 days)\n * starts it later; retries overrides the agent's retries. */\nexport async function start(agent, input = null, { idempotencyKey, delaySec, retries } = {}) {\n return (await call(\"POST\", \"/api/agents/v1/run\", {\n agent,\n input,\n ...(idempotencyKey ? { idempotencyKey } : {}),\n ...(delaySec ? { delaySec } : {}),\n ...(retries !== undefined ? { retries } : {}),\n })).runId;\n}\n\nfunction localState() {\n try {\n return JSON.parse(fs.readFileSync(STATE_PATH, \"utf8\"));\n } catch {\n return {};\n }\n}\n\nasync function writeState(key, value, { ifVersion, ttlSec } = {}) {\n if (STATE_PATH) {\n const data = localState();\n const current = data[key]?.version ?? 0;\n if (ifVersion !== undefined && ifVersion !== current) throw new StateConflict(key, current);\n if (value === null) delete data[key];\n else data[key] = { value, version: current + 1 };\n fs.writeFileSync(STATE_PATH, JSON.stringify(data, null, 2));\n return value === null ? 0 : current + 1;\n }\n const res = await call(\"POST\", \"/api/agents/v1/state\", { key, value, agent: AGENT, ...(ifVersion !== undefined ? { ifVersion } : {}), ...(ttlSec ? { ttlSec } : {}) }, [409]);\n if (res._status === 409) throw new StateConflict(key, res.version ?? 0);\n return res.version ?? 0;\n}\n\n/** Per-agent key-value storage that outlives runs (values: any JSON, up to\n * 64 KB; 1,000 keys). Every write bumps the key's version; pass ifVersion\n * to write only if nobody else did since you read it. */\nexport const state = {\n async get(key, fallback = null) {\n const { value } = await state.getVersioned(key);\n return value ?? fallback;\n },\n /** { value, version }; { value: null, version: 0 } when the key isn't set. */\n async getVersioned(key) {\n if (STATE_PATH) {\n const row = localState()[key];\n return row ? { value: row.value, version: row.version } : { value: null, version: 0 };\n }\n const res = await call(\"GET\", `/api/agents/v1/state?${new URLSearchParams({ key, agent: AGENT })}`, undefined, [404]);\n if (res._status === 404) return { value: null, version: 0 };\n return { value: JSON.parse(res.value), version: res.version };\n },\n /** Store value; returns the new version. Throws StateConflict on an ifVersion mismatch (0 = unset). */\n set(key, value, { ifVersion, ttlSec } = {}) {\n return writeState(key, value, { ifVersion, ttlSec });\n },\n async delete(key, { ifVersion } = {}) {\n await writeState(key, null, { ifVersion });\n },\n /** Keys (and with values: true, values) starting with prefix. */\n async list(prefix = \"\", { values = false } = {}) {\n if (STATE_PATH) {\n return Object.entries(localState())\n .filter(([k]) => k.startsWith(prefix))\n .sort(([a], [b]) => a.localeCompare(b))\n .map(([key, row]) => ({ key, version: row.version, ...(values ? { value: row.value } : {}) }));\n }\n const out = [];\n let after = null;\n do {\n const q = new URLSearchParams({ prefix, values: values ? \"1\" : \"0\", limit: \"200\", agent: AGENT, ...(after ? { after } : {}) });\n const page = await call(\"GET\", `/api/agents/v1/state?${q}`);\n out.push(...page.keys.map((row) => (\"value\" in row ? { ...row, value: JSON.parse(row.value) } : row)));\n after = page.next;\n } while (after);\n return out;\n },\n /** Read-modify-write that's safe with runs in parallel: fn(old) => new, retried if another run wrote first. */\n async update(key, fn, fallback = null, retries = 10) {\n for (let i = 0; i < retries; i++) {\n const { value, version } = await state.getVersioned(key);\n const next = await fn(value ?? fallback);\n try {\n await state.set(key, next, { ifVersion: version });\n return next;\n } catch (err) {\n if (!(err instanceof StateConflict)) throw err;\n await new Promise((resolve) => setTimeout(resolve, 50));\n }\n }\n throw new StateConflict(key, -1);\n },\n};\n\n/** Run fn() once for this run and remember its (JSON) result: if the run is\n * retried after a failure, finished steps return their saved result instead\n * of running again. Use it around side effects so a retry doesn't repeat them. */\nexport async function step(name, fn, { ttlSec = 7 * 86400 } = {}) {\n const key = `step/${process.env.CODIEDEV_RUN_ID || \"local\"}/${name}`;\n const { value, version } = await state.getVersioned(key);\n if (version) return value.result;\n const result = await fn();\n await state.set(key, { result: result ?? null }, { ttlSec });\n return result;\n}\n\n/** Ask a person to decide. It shows in Codiedev (Agents → Approvals) and at\n * the returned .url, which you can post anywhere. When it's decided, a run of\n * `then` (this agent by default; null for none) starts with input\n * { approval: { decision, comment, decidedBy, ... }, context }. Undecided\n * after expiresSec (default 7 days): decision \"expired\". */\nexport async function requestApproval(title, { details, options, approvers, teams, fields, then, context, expiresSec } = {}) {\n if (process.env.CODIEDEV_LOCAL === \"1\") {\n console.log(`[local] approval requested: ${title} (${(options || (fields ? [\"submit\"] : [\"approve\", \"reject\"])).join(\", \")}); not sent on a local run`);\n return { id: `local-${Date.now()}`, status: \"pending\", url: null, title };\n }\n return call(\"POST\", \"/api/agents/v1/approvals\", {\n title,\n ...(details !== undefined ? { details } : {}),\n ...(options ? { options } : {}),\n ...(approvers ? { approvers } : {}),\n ...(teams ? { teams } : {}),\n ...(fields ? { fields } : {}),\n ...(then !== undefined ? { onDecision: then } : {}),\n ...(context !== undefined ? { context } : {}),\n ...(expiresSec ? { expiresSec } : {}),\n });\n}\n\n/** An approval's status (\"pending\", \"decided\", \"expired\"), decision and comment. */\nexport async function getApproval(id) {\n return call(\"GET\", `/api/agents/v1/approvals?id=${encodeURIComponent(id)}`);\n}\n\n/** A run's status, input, output (parsed), error, log and children. */\nexport async function get(runId) {\n const run = await call(\"GET\", `/api/agents/v1/run?id=${encodeURIComponent(runId)}`);\n if (typeof run.output === \"string\") {\n try {\n run.output = JSON.parse(run.output);\n } catch {\n // not JSON: keep the string\n }\n }\n return run;\n}\n\n/** Run an agent and wait for it; its result is `.output`. Inside a run this\n * starts a child run. Throws AgentRunError if it fails unless check is false. */\nexport async function run(agent, input = null, { timeoutMs = 900_000, pollMs = 2000, check = true, idempotencyKey } = {}) {\n const id = await start(agent, input, { idempotencyKey });\n const deadline = Date.now() + timeoutMs;\n for (;;) {\n const r = await get(id);\n if (DONE.has(r.status)) {\n if (check && r.status !== \"succeeded\") throw new AgentRunError(r);\n return r;\n }\n if (Date.now() > deadline) throw new Error(`${agent} run ${id} still ${r.status} after ${timeoutMs / 1000}s`);\n await new Promise((resolve) => setTimeout(resolve, pollMs));\n }\n}\n\n/** Options for `new OpenAI(...)` on the Codiedev gateway, with this run's trace headers. */\nexport function openaiOptions(extra = {}) {\n const inRun = !!process.env.CODIEDEV_RUN_ID;\n return {\n baseURL: process.env.CODIEDEV_GATEWAY_URL || \"https://gateway.codiedev.com/api/v1\",\n apiKey: process.env.CODIEDEV_API_KEY || process.env.CODIEDEV_TOKEN,\n ...extra,\n defaultHeaders: {\n \"X-Codiedev-Workload\": process.env.CODIEDEV_AGENT || \"local-agent\",\n \"X-Codiedev-Run-Id\": process.env.CODIEDEV_RUN_ID || `local-${Date.now()}`,\n \"X-Codiedev-Environment\": inRun ? \"production\" : \"development\",\n ...(extra.defaultHeaders || {}),\n },\n };\n}\n\n/** An OpenAI client on the Codiedev gateway (the `openai` package is preinstalled). */\nexport async function openai(extra = {}) {\n const { default: OpenAI } = await import(\"openai\");\n return new OpenAI(openaiOptions(extra));\n}\n";
|
|
6
|
+
exports.jsTypes = "/* eslint-disable @typescript-eslint/no-explicit-any -- defaults are `any` on purpose: agent code reads its own JSON input/state without declaring types first. */\nexport type RunStatus = \"queued\" | \"running\" | \"succeeded\" | \"failed\" | \"cancelled\";\nexport interface Run<O = unknown> {\n id: string;\n agent: string;\n version: number;\n status: RunStatus;\n trigger: \"cli\" | \"api\" | \"portal\" | \"schedule\" | \"webhook\" | \"child\" | \"approval\";\n parentRunId: string | null;\n rootRunId: string | null;\n depth: number;\n attempt: number;\n maxAttempts: number;\n scheduledFor: number | null;\n input: string | null;\n output: O;\n error: string | null;\n logTail: string | null;\n durationMs: number | null;\n children: Run[];\n}\nexport interface WebhookRequest { method: string; query: Record<string, string>; headers: Record<string, string>; body: string }\nexport interface Approval {\n id: string;\n agent: string;\n title: string;\n details: string | null;\n options: string[];\n status: \"pending\" | \"decided\" | \"expired\" | \"cancelled\";\n decision: string | null;\n comment: string | null;\n decidedBy: string | null;\n /** The approval app that sent the decision on decidedBy's behalf; null when decided in Codiedev. */\n decidedVia: string | null;\n approvers: string[];\n approverTeams: Array<{ id: string; name: string }>;\n /** The form the person fills in, if the approval asked for one. */\n fields: ApprovalField[];\n /** What they entered, by field name (null until decided). */\n values: Record<string, any> | null;\n url: string | null;\n resumeRunId: string | null;\n}\n/** One thing a person fills in when they decide. */\nexport interface ApprovalField {\n name: string;\n type: \"text\" | \"textarea\" | \"number\" | \"rating\" | \"select\" | \"multiselect\" | \"boolean\";\n label?: string;\n help?: string;\n required?: boolean;\n /** select / multiselect choices */\n options?: string[];\n /** number bounds; rating scale 1–max (default 5) */\n min?: number;\n max?: number;\n /** prefilled, e.g. a proposed amount for the person to correct */\n default?: unknown;\n}\n/** The input a run started by a decision gets. */\nexport interface ApprovalInput<C = any, V = Record<string, any>> {\n approval: { id: string; title: string; decision: string; comment: string | null; values: V; decidedBy: string | null; decidedVia: string | null; decidedAt: number; requestedByRun: string | null };\n context: C;\n}\nexport class AgentRunError extends Error { run: Run }\nexport class StateConflict extends Error { key: string; version: number }\nexport function input<T = any>(fallback?: T): T;\nexport function output(value: unknown): void;\nexport function request(): WebhookRequest | null;\nexport function respond(body?: unknown, opts?: { status?: number; headers?: Record<string, string> }): void;\nexport function attempt(): number;\nexport function start(agent: string, input?: unknown, opts?: { idempotencyKey?: string; delaySec?: number; retries?: number }): Promise<string>;\nexport function get<O = any>(runId: string): Promise<Run<O>>;\nexport function run<O = any>(agent: string, input?: unknown, opts?: { timeoutMs?: number; pollMs?: number; check?: boolean; idempotencyKey?: string }): Promise<Run<O>>;\nexport const state: {\n get<T = any>(key: string, fallback?: T): Promise<T>;\n getVersioned<T = any>(key: string): Promise<{ value: T | null; version: number }>;\n set(key: string, value: unknown, opts?: { ifVersion?: number; ttlSec?: number }): Promise<number>;\n delete(key: string, opts?: { ifVersion?: number }): Promise<void>;\n list(prefix?: string, opts?: { values?: boolean }): Promise<Array<{ key: string; version: number; value?: any }>>;\n update<T = any>(key: string, fn: (old: T) => T | Promise<T>, fallback?: T, retries?: number): Promise<T>;\n};\nexport function step<T>(name: string, fn: () => T | Promise<T>, opts?: { ttlSec?: number }): Promise<T>;\nexport function requestApproval<C = any>(title: string, opts?: { details?: unknown; options?: string[]; approvers?: string[]; teams?: string[]; fields?: ApprovalField[]; then?: string | null; context?: C; expiresSec?: number }): Promise<Approval>;\nexport function getApproval(id: string): Promise<Approval>;\nexport function openaiOptions(extra?: Record<string, unknown>): { baseURL: string; apiKey: string | undefined; defaultHeaders: Record<string, string> };\nexport function openai(extra?: Record<string, unknown>): Promise<import(\"openai\").default>;";
|
|
7
|
+
exports.jsPackage = "{\n \"name\": \"codiedev-agents\",\n \"version\": \"0.1.0\",\n \"description\": \"Codiedev agents SDK: this run's input and output, chaining, and a traced OpenAI client.\",\n \"type\": \"module\",\n \"main\": \"./index.mjs\",\n \"types\": \"./index.d.ts\",\n \"exports\": { \".\": { \"types\": \"./index.d.ts\", \"default\": \"./index.mjs\" } }\n}\n";
|
package/dist/commands/doctor.js
CHANGED
|
@@ -46,9 +46,8 @@ const version_1 = require("../version");
|
|
|
46
46
|
const upgradeNudge_1 = require("../upgradeNudge");
|
|
47
47
|
const captureReadiness_1 = require("../captureReadiness");
|
|
48
48
|
const captureLog_1 = require("../captureLog");
|
|
49
|
-
const securityDoctor_1 = require("../securityDoctor");
|
|
50
|
-
const securityFeature_1 = require("../securityFeature");
|
|
51
49
|
const copilotInstallation_1 = require("../copilotInstallation");
|
|
50
|
+
const piHooks_1 = require("../piHooks");
|
|
52
51
|
const harnessDirs_1 = require("../harnessDirs");
|
|
53
52
|
const CLAUDE_DIR = (0, harnessDirs_1.claudeConfigDir)();
|
|
54
53
|
const CLAUDE_SETTINGS_PATH = path.join(CLAUDE_DIR, "settings.json");
|
|
@@ -128,24 +127,6 @@ function hasCodiedevInstructions(instructionsPath) {
|
|
|
128
127
|
return false;
|
|
129
128
|
}
|
|
130
129
|
}
|
|
131
|
-
function hasGhCli() {
|
|
132
|
-
try {
|
|
133
|
-
(0, child_process_1.execSync)("gh --version", { stdio: ["pipe", "pipe", "pipe"] });
|
|
134
|
-
return true;
|
|
135
|
-
}
|
|
136
|
-
catch {
|
|
137
|
-
return false;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
function hasGlabCli() {
|
|
141
|
-
try {
|
|
142
|
-
(0, child_process_1.execSync)("glab --version", { stdio: ["pipe", "pipe", "pipe"] });
|
|
143
|
-
return true;
|
|
144
|
-
}
|
|
145
|
-
catch {
|
|
146
|
-
return false;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
130
|
async function runDoctor(_args) {
|
|
150
131
|
const checks = [];
|
|
151
132
|
console.log("\nCodiedev doctor — checking your setup…\n");
|
|
@@ -272,10 +253,10 @@ async function runDoctor(_args) {
|
|
|
272
253
|
const workspaceMode = workspaceScan.repoCount > 0;
|
|
273
254
|
checks.push({
|
|
274
255
|
name: "git on PATH",
|
|
275
|
-
status: readiness.gitOnPath ? "pass" : "
|
|
256
|
+
status: readiness.gitOnPath ? "pass" : "warn",
|
|
276
257
|
detail: readiness.gitOnPath
|
|
277
258
|
? undefined
|
|
278
|
-
: "not found — capture can't
|
|
259
|
+
: "not found — sessions still capture but can't be tagged to a repo (install git if you work in repos)",
|
|
279
260
|
});
|
|
280
261
|
checks.push({
|
|
281
262
|
name: "Inside a git work tree (current dir)",
|
|
@@ -448,23 +429,16 @@ async function runDoctor(_args) {
|
|
|
448
429
|
else {
|
|
449
430
|
checks.push({ name: "GitHub Copilot", status: "warn", detail: "not installed on this machine" });
|
|
450
431
|
}
|
|
451
|
-
if ((0,
|
|
452
|
-
|
|
432
|
+
if ((0, utils_1.piInstalled)()) {
|
|
433
|
+
const extension = (0, piHooks_1.inspectPiExtension)();
|
|
434
|
+
checks.push({ name: "pi capture extension", status: extension.status === "pass" ? "pass" : "fail", detail: extension.detail });
|
|
435
|
+
checks.push({ name: "pi agent instructions",
|
|
436
|
+
status: hasCodiedevInstructions((0, utils_1.piInstructionsPath)()) ? "pass" : "fail",
|
|
437
|
+
detail: hasCodiedevInstructions((0, utils_1.piInstructionsPath)()) ? (0, harnessDirs_1.displayPath)((0, utils_1.piInstructionsPath)()) : "missing — re-run `codiedev connect`" });
|
|
438
|
+
}
|
|
439
|
+
else {
|
|
440
|
+
checks.push({ name: "pi", status: "warn", detail: "not installed on this machine" });
|
|
453
441
|
}
|
|
454
|
-
checks.push({
|
|
455
|
-
name: "GitHub CLI (`gh`) for reverse-ticket from a PR url",
|
|
456
|
-
status: hasGhCli() ? "pass" : "warn",
|
|
457
|
-
detail: hasGhCli()
|
|
458
|
-
? "installed"
|
|
459
|
-
: "not installed — only needed for `codiedev reverse-ticket <github-pr-url>`",
|
|
460
|
-
});
|
|
461
|
-
checks.push({
|
|
462
|
-
name: "GitLab CLI (`glab`) for reverse-ticket from an MR url",
|
|
463
|
-
status: hasGlabCli() ? "pass" : "warn",
|
|
464
|
-
detail: hasGlabCli()
|
|
465
|
-
? "installed"
|
|
466
|
-
: "not installed — only needed for `codiedev reverse-ticket <gitlab-mr-url>`",
|
|
467
|
-
});
|
|
468
442
|
report(checks);
|
|
469
443
|
const failures = checks.filter((c) => c.status === "fail");
|
|
470
444
|
if (failures.length > 0) {
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.EVALS_HELP = void 0;
|
|
37
|
+
exports.runEvals = runEvals;
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
const shared_1 = require("./shared");
|
|
41
|
+
exports.EVALS_HELP = `
|
|
42
|
+
codiedev evals — experiments and their runs, shared with the team
|
|
43
|
+
|
|
44
|
+
codiedev evals report <run.json> [--harness name]
|
|
45
|
+
File one variant's scores under an
|
|
46
|
+
experiment (created on first use)
|
|
47
|
+
codiedev evals last <subject> [--json] The latest experiment on a subject,
|
|
48
|
+
its runs, and the current champion
|
|
49
|
+
|
|
50
|
+
run.json:
|
|
51
|
+
{
|
|
52
|
+
"subject": "session-summaries", // agent name or workload
|
|
53
|
+
"question": "Is Terra medium as good as GLM high?",
|
|
54
|
+
"experimentId": "…", // optional, instead of subject + question
|
|
55
|
+
"dataset": "30 Signal & Code sessions", // optional
|
|
56
|
+
"run": {
|
|
57
|
+
"label": "terra-med-v2", "model": "openai/gpt-6.1-terra",
|
|
58
|
+
"provider": "openai", "reasoningEffort": "medium", "prompt": "summary_extract_v2",
|
|
59
|
+
"gitSha": "…", "isControl": false, "judgeModel": "anthropic/claude-sonnet-5.5",
|
|
60
|
+
"casesTotal": 30, "casesScored": 30, "errors": 0,
|
|
61
|
+
"scoringRevision": 1, // required once experiment scoring rules are defined
|
|
62
|
+
"score": 3.85, "metrics": { "fidelity": 3.6 },
|
|
63
|
+
"costPerCaseUsd": 0.026, "latencyMsAvg": 21000,
|
|
64
|
+
"wins": 20, "losses": 8, "ties": 2, "note": "…",
|
|
65
|
+
"cases": [{ "key": "case-1", "score": 4, "note": "…" }]
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
Read scoring rules and their current revision with: codiedev evals last <subject> --json
|
|
70
|
+
|
|
71
|
+
Scores and notes only: never put prompts, transcripts or outputs in it.
|
|
72
|
+
`.trim();
|
|
73
|
+
function flag(args, i, name) {
|
|
74
|
+
const a = args[i];
|
|
75
|
+
if (a === name && i + 1 < args.length)
|
|
76
|
+
return { value: args[i + 1], skip: 1 };
|
|
77
|
+
if (a.startsWith(`${name}=`))
|
|
78
|
+
return { value: a.slice(name.length + 1), skip: 0 };
|
|
79
|
+
return { skip: -1 };
|
|
80
|
+
}
|
|
81
|
+
function variant(run) {
|
|
82
|
+
return [run.model, run.reasoningEffort, run.prompt].filter(Boolean).join(" · ");
|
|
83
|
+
}
|
|
84
|
+
function fmtScore(n) {
|
|
85
|
+
return n === null ? "–" : n.toFixed(2);
|
|
86
|
+
}
|
|
87
|
+
function fmtCost(n) {
|
|
88
|
+
return n === null ? "–" : `$${n.toFixed(4)}`;
|
|
89
|
+
}
|
|
90
|
+
async function runReport(args, config) {
|
|
91
|
+
let file;
|
|
92
|
+
let harness;
|
|
93
|
+
for (let i = 0; i < args.length; i++) {
|
|
94
|
+
const f = flag(args, i, "--harness");
|
|
95
|
+
if (f.skip >= 0) {
|
|
96
|
+
harness = f.value;
|
|
97
|
+
i += f.skip;
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (!args[i].startsWith("-") && !file)
|
|
101
|
+
file = args[i];
|
|
102
|
+
}
|
|
103
|
+
if (!file) {
|
|
104
|
+
console.error("Usage: codiedev evals report <run.json> [--harness name]");
|
|
105
|
+
process.exit(1);
|
|
106
|
+
}
|
|
107
|
+
let body;
|
|
108
|
+
try {
|
|
109
|
+
body = JSON.parse(fs.readFileSync(path.resolve(process.cwd(), file), "utf8"));
|
|
110
|
+
}
|
|
111
|
+
catch (err) {
|
|
112
|
+
console.error(`Couldn't read ${file}: ${err.message}`);
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
if (!body.subject || !body.question || !body.run?.label || !body.run?.model || typeof body.run?.casesTotal !== "number") {
|
|
116
|
+
console.error(`${file} needs subject, question, and run.label, run.model, run.casesTotal. See: codiedev evals help`);
|
|
117
|
+
process.exit(1);
|
|
118
|
+
}
|
|
119
|
+
const start = Date.now();
|
|
120
|
+
try {
|
|
121
|
+
const res = await (0, shared_1.apiRequest)("POST", "/api/cli/evals/runs", {
|
|
122
|
+
config,
|
|
123
|
+
body: { ...body, run: { ...body.run, harness: harness ?? body.run.harness ?? undefined } },
|
|
124
|
+
});
|
|
125
|
+
(0, shared_1.recordAgentEvent)(config, { tool: "codiedev_evals_report", ok: true, latencyMs: Date.now() - start });
|
|
126
|
+
console.log(`✓ Reported ${body.run.label} on ${body.subject}${res.created ? " (new experiment)" : ""}: "${body.question}"`);
|
|
127
|
+
console.log(` experiment ${res.experimentId}. See it in Codiedev → Traces → Evals.`);
|
|
128
|
+
}
|
|
129
|
+
catch (err) {
|
|
130
|
+
const e = err;
|
|
131
|
+
(0, shared_1.recordAgentEvent)(config, { tool: "codiedev_evals_report", ok: false, latencyMs: Date.now() - start, error: e.message });
|
|
132
|
+
console.error(`Couldn't report: ${e.message}`);
|
|
133
|
+
process.exit(1);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
async function runLast(args, config) {
|
|
137
|
+
const json = args.includes("--json");
|
|
138
|
+
const subject = args.find((a) => !a.startsWith("-"));
|
|
139
|
+
if (!subject) {
|
|
140
|
+
console.error("Usage: codiedev evals last <subject> [--json]");
|
|
141
|
+
process.exit(1);
|
|
142
|
+
}
|
|
143
|
+
let res;
|
|
144
|
+
try {
|
|
145
|
+
res = await (0, shared_1.apiRequest)("GET", `/api/cli/evals/last?subject=${encodeURIComponent(subject)}`, { config });
|
|
146
|
+
}
|
|
147
|
+
catch (err) {
|
|
148
|
+
const e = err;
|
|
149
|
+
console.error(e.status === 404 ? `No evals on ${subject} yet.` : `Couldn't load evals: ${e.message}`);
|
|
150
|
+
process.exit(1);
|
|
151
|
+
}
|
|
152
|
+
if (json) {
|
|
153
|
+
console.log(JSON.stringify(res, null, 2));
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
const { experiment, runs, champion } = res;
|
|
157
|
+
if (champion) {
|
|
158
|
+
console.log(`Champion: ${champion.run.label} (${variant(champion.run)}), score ${fmtScore(champion.run.score)}, ${fmtCost(champion.run.costPerCaseUsd)}/case`);
|
|
159
|
+
if (champion.decisionNote)
|
|
160
|
+
console.log(` Why: ${champion.decisionNote}`);
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
console.log("Champion: none picked yet");
|
|
164
|
+
}
|
|
165
|
+
console.log("");
|
|
166
|
+
console.log(`Latest experiment (${res.experiments} on ${experiment.subject}): ${experiment.question}`);
|
|
167
|
+
if (experiment.dataset)
|
|
168
|
+
console.log(` Dataset: ${experiment.dataset}`);
|
|
169
|
+
console.log(` id ${experiment.id}, by ${experiment.createdBy}, updated ${new Date(experiment.updatedAt).toISOString().slice(0, 16).replace("T", " ")}`);
|
|
170
|
+
console.log("");
|
|
171
|
+
for (const run of [...runs].sort((a, b) => a.ranAt - b.ranAt)) {
|
|
172
|
+
const h2h = run.wins !== null ? ` · ${run.wins}W/${run.losses ?? 0}L/${run.ties ?? 0}T` : "";
|
|
173
|
+
console.log(` ${run.isControl ? "[control] " : ""}${run.label.padEnd(24)} ${fmtScore(run.score).padStart(5)} ${fmtCost(run.costPerCaseUsd).padStart(8)}/case${h2h} ${variant(run)}`);
|
|
174
|
+
}
|
|
175
|
+
console.log("");
|
|
176
|
+
console.log(`Next run on this experiment: put "experimentId": "${experiment.id}" in run.json, then codiedev evals report run.json`);
|
|
177
|
+
}
|
|
178
|
+
async function runEvals(args, configOverride) {
|
|
179
|
+
const [sub, ...rest] = args;
|
|
180
|
+
if (sub === "report")
|
|
181
|
+
return runReport(rest, configOverride ?? (0, shared_1.requireConfig)());
|
|
182
|
+
if (sub === "last")
|
|
183
|
+
return runLast(rest, configOverride ?? (0, shared_1.requireConfig)());
|
|
184
|
+
console.log(exports.EVALS_HELP);
|
|
185
|
+
if (sub && sub !== "help")
|
|
186
|
+
process.exit(1);
|
|
187
|
+
}
|
package/dist/commands/pack.js
CHANGED
|
@@ -88,7 +88,7 @@ function ciHarnesses(args, env, home) {
|
|
|
88
88
|
}
|
|
89
89
|
const USAGE = `Usage:
|
|
90
90
|
codiedev pack list Packs assigned to your team
|
|
91
|
-
codiedev pack install [slug] [--harness claude,codex,copilot,cursor,codie] [--dry-run] [--json] [--quiet]
|
|
91
|
+
codiedev pack install [slug] [--harness claude,codex,copilot,cursor,codie,pi] [--dry-run] [--json] [--quiet]
|
|
92
92
|
Install one pack, or every pack that applies
|
|
93
93
|
codiedev pack status [--json] Installed packs and whether they're up to date
|
|
94
94
|
codiedev pack export <slug> --out <dir> [--git] [--manifest]
|
package/dist/commands/traces.js
CHANGED
|
@@ -41,14 +41,15 @@ exports.runTraces = runTraces;
|
|
|
41
41
|
const fs = __importStar(require("fs"));
|
|
42
42
|
const path = __importStar(require("path"));
|
|
43
43
|
const shared_1 = require("./shared");
|
|
44
|
+
const tracesLabel_1 = require("./tracesLabel");
|
|
44
45
|
exports.TRACES_HELP = `
|
|
45
|
-
codiedev traces — test cases
|
|
46
|
+
codiedev traces — test cases, scores, review queues and automatic scoring from Codiedev → Traces
|
|
46
47
|
|
|
47
48
|
codiedev traces agents Agents you maintain, with saved case counts
|
|
48
|
-
codiedev traces cases <agent> [--
|
|
49
|
-
Write an agent's cases (one
|
|
49
|
+
codiedev traces cases <agent> [--dataset name [--version n]] [--case id[,id]] [--model id] [--out dir] [--json]
|
|
50
|
+
Write an agent's cases (one dataset, or just
|
|
50
51
|
the cases named) into this repo, default
|
|
51
|
-
.codiedev/traces/<agent>[/<
|
|
52
|
+
.codiedev/traces/<agent>[/<dataset>[/v<n>]]/, then
|
|
52
53
|
ask your coding agent to run them.
|
|
53
54
|
--model runs them on another model, e.g.
|
|
54
55
|
the replacement for a retiring one
|
|
@@ -56,6 +57,36 @@ codiedev traces — test cases saved from Codiedev → Traces
|
|
|
56
57
|
Send the filled-in results back, so
|
|
57
58
|
Traces shows the pass rate for that model
|
|
58
59
|
|
|
60
|
+
codiedev traces overview [--days n] [--json]
|
|
61
|
+
Every agent you can see: runs, failures,
|
|
62
|
+
spend, and what's going wrong most
|
|
63
|
+
codiedev traces runs <agent> [--failed] [--flag name] [--model id] [--verdict pass|fail] [--days n] [--limit n] [--json]
|
|
64
|
+
An agent's runs, newest first
|
|
65
|
+
codiedev traces run <run> [--agent name] [--json]
|
|
66
|
+
One run: steps, what went wrong and the fix,
|
|
67
|
+
scores, prompts and replies (maintainers)
|
|
68
|
+
codiedev traces score-kinds [add <name> --type number|categories|boolean [--min n --max n] [--categories a,b]]
|
|
69
|
+
List or add the kinds of score your team uses
|
|
70
|
+
codiedev traces queue create <name> [--scores a,b] [--description text]
|
|
71
|
+
codiedev traces score <run> <score> <value> [--agent name] [--comment text] [--step n]
|
|
72
|
+
Score a run (a portal run id agent~run, or
|
|
73
|
+
the agent's own run id with --agent)
|
|
74
|
+
codiedev traces scores <agent> [--days n] [--json]
|
|
75
|
+
Each score's average or mix, and the score kinds
|
|
76
|
+
codiedev traces queue list Review queues and how many are left
|
|
77
|
+
codiedev traces queue add <queue> --agent <name> [--failed] [--days n] [--run id ...]
|
|
78
|
+
File runs into a queue for people to review
|
|
79
|
+
codiedev traces judge <agent> [--on | --off] [--scores a,b] [--instructions text]
|
|
80
|
+
[--model id] [--sample percent] [--daily n] [--budget usd | --key cdvm_…]
|
|
81
|
+
Show or set automatic scoring (a judge model
|
|
82
|
+
scores a sample of runs, billed to your key)
|
|
83
|
+
codiedev traces import <agent> <file.csv|file.jsonl> --dataset <name>
|
|
84
|
+
Add examples from a file (input, expected, name)
|
|
85
|
+
codiedev traces add-example <agent> --dataset <name> --input <text> [--expected <text>] [--name <text>]
|
|
86
|
+
codiedev traces versions <dataset> A dataset's saved versions and what changed
|
|
87
|
+
codiedev traces save-version <dataset> [--note text]
|
|
88
|
+
Save the dataset as its next version
|
|
89
|
+
|
|
59
90
|
<agent> is the agent's name (its API key name) or id. Only an agent's
|
|
60
91
|
maintainers can pull its cases or report on them.
|
|
61
92
|
`.trim();
|
|
@@ -68,7 +99,7 @@ const FLAG_LABEL = {
|
|
|
68
99
|
};
|
|
69
100
|
const TRIES = 3;
|
|
70
101
|
const plural = (n, word) => `${n} ${word}${n === 1 ? "" : "s"}`;
|
|
71
|
-
const recordedModel = (res) => res.cases.flatMap((c) => c.steps.map((s) => s.model))[0] ?? "the
|
|
102
|
+
const recordedModel = (res) => res.cases.flatMap((c) => c.steps.map((s) => s.model)).filter(Boolean)[0] ?? "the agent's current model";
|
|
72
103
|
function slugify(name) {
|
|
73
104
|
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "agent";
|
|
74
105
|
}
|
|
@@ -79,7 +110,7 @@ function casesBrief(res, casesFile, opts = {}) {
|
|
|
79
110
|
const target = opts.model;
|
|
80
111
|
const results = opts.resultsFile ? path.basename(opts.resultsFile) : "results.json";
|
|
81
112
|
const lines = [
|
|
82
|
-
`# Test cases for ${res.agent.name}${res.suite ? ` · ${res.suite}` : ""}${target ? ` on ${target}` : ""}`,
|
|
113
|
+
`# Test cases for ${res.agent.name}${res.suite ? ` · ${res.suite}` : ""}${res.version ? ` v${res.version}` : ""}${target ? ` on ${target}` : ""}`,
|
|
83
114
|
"",
|
|
84
115
|
res.agent.description ? `${res.agent.description}\n` : "",
|
|
85
116
|
`Saved from Codiedev → Traces. ${plural(res.cases.length, "case")} in \`${path.basename(casesFile)}\` (one JSON object per line):`,
|
|
@@ -90,7 +121,7 @@ function casesBrief(res, casesFile, opts = {}) {
|
|
|
90
121
|
if (target) {
|
|
91
122
|
lines.push("## This is a model check", "", `Run every case on **${target}** instead of ${recordedModel(res)} (the model the cases were recorded on). Change only the model the agent asks the gateway for; keep its prompts and tools as they are, so the result says how ${target} does on the same work.`, "");
|
|
92
123
|
}
|
|
93
|
-
lines.push("## How to run them", "", "1. Find where this agent builds its model requests and calls the Codiedev gateway.", `2. For each case, replay \`steps[0].request\` (the first prompt the agent sent) through the agent's own code path, so tools and prompts are the current version, not the recorded one${target ? `, with the model set to ${target}` : ""}.`, "3. Judge each try against the case:", " - A case's `expected`, when present, is what a passing answer looks like (a reference reply or criteria). It wins over everything below.", " - `fix` cases: the run must not reproduce its `flags` (listed below), and must finish (no failed call, no `finish_reason: \"length\"`, no repeated identical tool call).", " - `keep` cases: the final reply must still do what the recorded final reply did. Compare meaning, not wording.", " - A case's `note`, when present, is the engineer's extra context.");
|
|
124
|
+
lines.push("## How to run them", "", "1. Find where this agent builds its model requests and calls the Codiedev gateway.", `2. For each case, replay \`steps[0].request\` (the first prompt the agent sent) through the agent's own code path, so tools and prompts are the current version, not the recorded one${target ? `, with the model set to ${target}` : ""}.`, "3. Judge each try against the case:", " - A case's `expected`, when present, is what a passing answer looks like (a reference reply or criteria). It wins over everything below.", " - `fix` cases: the run must not reproduce its `flags` (listed below), and must finish (no failed call, no `finish_reason: \"length\"`, no repeated identical tool call).", " - `keep` cases: the final reply must still do what the recorded final reply did. Compare meaning, not wording.", " - A case's `note`, when present, is the engineer's extra context.", " - Cases whose `runId` starts with `manual:` or `import:` were written by hand, not recorded: `steps[0].request` is the input and there's no recorded reply, so judge them on `expected`.");
|
|
94
125
|
if (res.judge?.model || res.judge?.rubric) {
|
|
95
126
|
lines.push(` - Where passing needs judgement, judge with **${res.judge.model ?? "a strong model"}** through the gateway${res.judge.rubric ? `, using this rubric: "${res.judge.rubric}"` : ""}. Use the same judge for every model so results compare.`);
|
|
96
127
|
}
|
|
@@ -109,6 +140,7 @@ function resultsTemplate(res, model) {
|
|
|
109
140
|
return {
|
|
110
141
|
agent: res.agent.name,
|
|
111
142
|
suite: res.suite,
|
|
143
|
+
...(res.version ? { version: res.version } : {}),
|
|
112
144
|
model: model ?? recordedModel(res),
|
|
113
145
|
judgeModel: res.judge?.model ?? null,
|
|
114
146
|
harness: null,
|
|
@@ -128,7 +160,7 @@ function parseCasesArgs(args) {
|
|
|
128
160
|
for (let i = 0; i < args.length; i++) {
|
|
129
161
|
const a = args[i];
|
|
130
162
|
let matched = false;
|
|
131
|
-
for (const [name, key] of [["--out", "out"], ["-o", "out"], ["--suite", "suite"], ["--model", "model"], ["--case", "caseIds"]]) {
|
|
163
|
+
for (const [name, key] of [["--out", "out"], ["-o", "out"], ["--dataset", "suite"], ["--suite", "suite"], ["--model", "model"], ["--case", "caseIds"], ["--version", "version"]]) {
|
|
132
164
|
const f = flag(args, i, name);
|
|
133
165
|
if (f.skip >= 0) {
|
|
134
166
|
parsed[key] = f.value;
|
|
@@ -170,20 +202,26 @@ async function runAgents(config) {
|
|
|
170
202
|
console.log("\nPull one: codiedev traces cases <agent>");
|
|
171
203
|
}
|
|
172
204
|
async function runCases(args, config) {
|
|
173
|
-
const { agent, suite, model, caseIds, out, json } = parseCasesArgs(args);
|
|
205
|
+
const { agent, suite, model, caseIds, version, out, json } = parseCasesArgs(args);
|
|
174
206
|
if (!agent) {
|
|
175
|
-
console.error("Usage: codiedev traces cases <agent> [--
|
|
207
|
+
console.error("Usage: codiedev traces cases <agent> [--dataset name [--version n]] [--model id] [--out dir] [--json]");
|
|
208
|
+
process.exit(1);
|
|
209
|
+
}
|
|
210
|
+
if (version !== undefined && (!suite || !/^[1-9]\d*$/.test(version))) {
|
|
211
|
+
console.error("--version takes a whole number and needs --dataset: codiedev traces cases <agent> --dataset <name> --version <n>");
|
|
176
212
|
process.exit(1);
|
|
177
213
|
}
|
|
178
214
|
const start = Date.now();
|
|
179
215
|
let res;
|
|
180
216
|
try {
|
|
181
|
-
res = await (0, shared_1.apiRequest)("GET", "/api/cli/traces/cases", { config, query: { agent, ...(suite ? { suite } : {}), ...(caseIds ? { case: caseIds } : {}) } });
|
|
217
|
+
res = await (0, shared_1.apiRequest)("GET", "/api/cli/traces/cases", { config, query: { agent, ...(suite ? { suite } : {}), ...(version ? { version } : {}), ...(caseIds ? { case: caseIds } : {}) } });
|
|
182
218
|
}
|
|
183
219
|
catch (err) {
|
|
184
220
|
const e = err;
|
|
185
221
|
(0, shared_1.recordAgentEvent)(config, { tool: "codiedev_traces_cases", ok: false, latencyMs: Date.now() - start, error: e.message });
|
|
186
|
-
if (e.status === 404)
|
|
222
|
+
if (e.status === 404 && version && /No version/.test(e.message))
|
|
223
|
+
console.error(`No version ${version} of "${suite}". See its versions in Codiedev → Traces → Test cases.`);
|
|
224
|
+
else if (e.status === 404)
|
|
187
225
|
console.error(`No agent called "${agent}". See your agents with: codiedev traces agents`);
|
|
188
226
|
else if (e.status === 403)
|
|
189
227
|
console.error(`Only ${agent}'s maintainers can pull its test cases.`);
|
|
@@ -198,11 +236,11 @@ async function runCases(args, config) {
|
|
|
198
236
|
}
|
|
199
237
|
if (!res.cases.length) {
|
|
200
238
|
console.log(suite
|
|
201
|
-
? `${res.agent.name} has no test cases in "${suite}". See your
|
|
239
|
+
? `${res.agent.name} has no test cases in "${suite}". See your datasets in Codiedev → Traces → Test cases.`
|
|
202
240
|
: `${res.agent.name} has no saved test cases. Open a run in Codiedev → Traces and click "Save as test case".`);
|
|
203
241
|
return;
|
|
204
242
|
}
|
|
205
|
-
const dir = path.resolve(process.cwd(), out ?? path.join(".codiedev", "traces", slugify(res.agent.name), ...(res.suite ? [slugify(res.suite)] : []), ...(caseIds && res.cases.length === 1 ? [slugify(res.cases[0].name)] : []), ...(model ? [slugify(model)] : [])));
|
|
243
|
+
const dir = path.resolve(process.cwd(), out ?? path.join(".codiedev", "traces", slugify(res.agent.name), ...(res.suite ? [slugify(res.suite)] : []), ...(res.version ? [`v${res.version}`] : []), ...(caseIds && res.cases.length === 1 ? [slugify(res.cases[0].name)] : []), ...(model ? [slugify(model)] : [])));
|
|
206
244
|
fs.mkdirSync(dir, { recursive: true });
|
|
207
245
|
const casesFile = path.join(dir, "cases.jsonl");
|
|
208
246
|
fs.writeFileSync(casesFile, res.cases.map((c) => JSON.stringify(c)).join("\n") + "\n", "utf8");
|
|
@@ -212,7 +250,9 @@ async function runCases(args, config) {
|
|
|
212
250
|
fs.writeFileSync(briefFile, casesBrief(res, casesFile, { model, resultsFile }), "utf8");
|
|
213
251
|
const fix = res.cases.filter((c) => c.kind === "fix").length;
|
|
214
252
|
const rel = (file) => path.relative(process.cwd(), file) || file;
|
|
215
|
-
console.log(`✓ ${plural(res.cases.length, "case")} for ${res.agent.name}${res.suite ? ` · ${res.suite}` : ""}${model ? ` on ${model}` : ""} (${fix} to fix, ${res.cases.length - fix} to keep passing)`);
|
|
253
|
+
console.log(`✓ ${plural(res.cases.length, "case")} for ${res.agent.name}${res.suite ? ` · ${res.suite}` : ""}${res.version ? ` v${res.version}` : ""}${model ? ` on ${model}` : ""} (${fix} to fix, ${res.cases.length - fix} to keep passing)`);
|
|
254
|
+
if (res.missing)
|
|
255
|
+
console.log(` ${plural(res.missing, "case")} in v${res.version} were deleted since and can't be pulled.`);
|
|
216
256
|
console.log(` ${rel(casesFile)}`);
|
|
217
257
|
console.log(` ${rel(briefFile)}`);
|
|
218
258
|
console.log(` ${rel(resultsFile)} (filled in by your coding agent, then: codiedev traces report ${rel(resultsFile)})`);
|
|
@@ -274,6 +314,30 @@ async function runTraces(args, configOverride) {
|
|
|
274
314
|
return runCases(rest, configOverride ?? (0, shared_1.requireConfig)());
|
|
275
315
|
if (sub === "report")
|
|
276
316
|
return runReport(rest, configOverride ?? (0, shared_1.requireConfig)());
|
|
317
|
+
if (sub === "overview")
|
|
318
|
+
return (0, tracesLabel_1.runOverview)(rest, configOverride ?? (0, shared_1.requireConfig)());
|
|
319
|
+
if (sub === "runs")
|
|
320
|
+
return (0, tracesLabel_1.runRuns)(rest, configOverride ?? (0, shared_1.requireConfig)());
|
|
321
|
+
if (sub === "run")
|
|
322
|
+
return (0, tracesLabel_1.runRun)(rest, configOverride ?? (0, shared_1.requireConfig)());
|
|
323
|
+
if (sub === "score-kinds")
|
|
324
|
+
return (0, tracesLabel_1.runScoreKinds)(rest, configOverride ?? (0, shared_1.requireConfig)());
|
|
325
|
+
if (sub === "score")
|
|
326
|
+
return (0, tracesLabel_1.runScore)(rest, configOverride ?? (0, shared_1.requireConfig)());
|
|
327
|
+
if (sub === "scores")
|
|
328
|
+
return (0, tracesLabel_1.runScores)(rest, configOverride ?? (0, shared_1.requireConfig)());
|
|
329
|
+
if (sub === "queue")
|
|
330
|
+
return (0, tracesLabel_1.runQueue)(rest, configOverride ?? (0, shared_1.requireConfig)());
|
|
331
|
+
if (sub === "judge")
|
|
332
|
+
return (0, tracesLabel_1.runJudge)(rest, configOverride ?? (0, shared_1.requireConfig)());
|
|
333
|
+
if (sub === "import")
|
|
334
|
+
return (0, tracesLabel_1.runImport)(rest, configOverride ?? (0, shared_1.requireConfig)());
|
|
335
|
+
if (sub === "add-example")
|
|
336
|
+
return (0, tracesLabel_1.runAddExample)(rest, configOverride ?? (0, shared_1.requireConfig)());
|
|
337
|
+
if (sub === "versions")
|
|
338
|
+
return (0, tracesLabel_1.runVersions)(rest, configOverride ?? (0, shared_1.requireConfig)());
|
|
339
|
+
if (sub === "save-version")
|
|
340
|
+
return (0, tracesLabel_1.runSaveVersion)(rest, configOverride ?? (0, shared_1.requireConfig)());
|
|
277
341
|
console.log(exports.TRACES_HELP);
|
|
278
342
|
if (sub && sub !== "help")
|
|
279
343
|
process.exit(1);
|