lastlight 0.11.1 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/README.md +4 -0
  2. package/dist/admin/routes.js +101 -0
  3. package/dist/admin/routes.js.map +1 -1
  4. package/dist/cli/cli-server.d.ts +51 -9
  5. package/dist/cli/cli-server.js +101 -18
  6. package/dist/cli/cli-server.js.map +1 -1
  7. package/dist/cli/cli.js +9 -6
  8. package/dist/cli/cli.js.map +1 -1
  9. package/dist/cli/setup.js +2 -2
  10. package/dist/cli/setup.js.map +1 -1
  11. package/dist/engine/agent-executor.js +1 -0
  12. package/dist/engine/agent-executor.js.map +1 -1
  13. package/dist/engine/github/profiles.d.ts +15 -5
  14. package/dist/engine/github/profiles.js.map +1 -1
  15. package/dist/sandbox/index.d.ts +13 -6
  16. package/dist/sandbox/index.js +69 -29
  17. package/dist/sandbox/index.js.map +1 -1
  18. package/dist/sandbox/sandbox.d.ts +9 -0
  19. package/dist/sandbox/sandbox.js.map +1 -1
  20. package/dist/state/approval-store.d.ts +2 -0
  21. package/dist/state/approval-store.js +9 -0
  22. package/dist/state/approval-store.js.map +1 -1
  23. package/dist/workflows/resume.js +1 -6
  24. package/dist/workflows/resume.js.map +1 -1
  25. package/dist/workflows/runner.js +4 -0
  26. package/dist/workflows/runner.js.map +1 -1
  27. package/dist/workflows/simple.d.ts +2 -48
  28. package/dist/workflows/simple.js +12 -55
  29. package/dist/workflows/simple.js.map +1 -1
  30. package/dist/workflows/target-policy.d.ts +74 -0
  31. package/dist/workflows/target-policy.js +80 -0
  32. package/dist/workflows/target-policy.js.map +1 -0
  33. package/docker-compose.yml +18 -3
  34. package/package.json +2 -2
  35. package/plugins/lastlight/.claude-plugin/plugin.json +1 -1
  36. package/plugins/lastlight/skills/lastlight-server/SKILL.md +18 -3
  37. package/plugins/lastlight/skills/lastlight-server/references/env-schema.md +16 -3
  38. package/sandbox.Dockerfile +21 -89
  39. package/skills/issue-answer/SKILL.md +5 -4
  40. package/skills/issue-comment/SKILL.md +3 -1
  41. package/skills/issue-triage/SKILL.md +5 -3
  42. package/skills/security-review/SKILL.md +3 -1
  43. package/workflows/prompts/answer.md +2 -1
@@ -2,96 +2,28 @@
2
2
  # fallback only — the default production sandbox is gondolin, invoked
3
3
  # in-process by agentic-pi without docker).
4
4
  #
5
- # Immutable assets baked at /app/. Entrypoint wires them into the workspace
6
- # after volumes are mounted, then drops to the runtime user.
7
- FROM node:20-slim
8
-
9
- RUN apt-get update && apt-get install -y --no-install-recommends \
10
- git ripgrep curl jq ca-certificates gettext-base gosu \
11
- build-essential pkg-config python3 unzip \
12
- && rm -rf /var/lib/apt/lists/*
13
-
14
- # fnm + multiple Node versions so repos pinning a specific Node via .nvmrc /
15
- # package.json#engines just work. System node from the base image stays at
16
- # /usr/local/bin; fnm-managed versions are pre-installed under FNM_DIR and
17
- # selected per-shell by the bashrc hook below.
18
- ENV FNM_DIR=/usr/local/share/fnm
19
- ENV PATH=$FNM_DIR/aliases/default/bin:$PATH
20
- RUN curl -fsSL https://fnm.vercel.app/install \
21
- | bash -s -- --install-dir "$FNM_DIR" --skip-shell \
22
- && ln -s "$FNM_DIR/fnm" /usr/local/bin/fnm \
23
- && fnm install 22 \
24
- && fnm install 24 \
25
- && fnm default 22 \
26
- && chmod -R a+rX "$FNM_DIR" \
27
- && mkdir -p "$FNM_DIR/multishells" \
28
- && chmod 1777 "$FNM_DIR/multishells"
29
-
30
- # Source fnm in every bash invocation (interactive or not). BASH_ENV makes
31
- # non-interactive `bash -c` read this file — that's how opencode's bash tool
32
- # inherits the right node version when it runs `npm ci` inside a repo with
33
- # an .nvmrc pinning Node 24.
34
- RUN printf '%s\n' \
35
- 'export FNM_DIR=/usr/local/share/fnm' \
36
- 'export PATH="$FNM_DIR:$PATH"' \
37
- '# --shell bash is required: when sourced via BASH_ENV the parent process' \
38
- '# is not a shell so fnm cannot auto-detect.' \
39
- 'eval "$(fnm env --shell bash --use-on-cd --version-file-strategy=recursive)"' \
40
- '# The cd hook fires only on cd. Tools often launch `bash -c "..."` with' \
41
- '# cwd already set via the spawn options — no cd happens — so also' \
42
- '# auto-switch on shell start when the cwd has a version file.' \
43
- 'if [ -f "$PWD/.nvmrc" ] || [ -f "$PWD/.node-version" ]; then' \
44
- ' fnm use --silent-if-unchanged 2>/dev/null \' \
45
- ' || { fnm install 2>/dev/null && fnm use --silent-if-unchanged 2>/dev/null; } \' \
46
- ' || true' \
47
- 'fi' \
48
- > /etc/bash.bashrc.fnm \
49
- && printf '\n[ -r /etc/bash.bashrc.fnm ] && . /etc/bash.bashrc.fnm\n' >> /etc/bash.bashrc \
50
- && ln -s /etc/bash.bashrc.fnm /etc/profile.d/fnm.sh
51
- # Source the fnm file directly — Debian's /etc/bash.bashrc bails early on
52
- # non-interactive shells (`[ -z "$PS1" ] && return`), so pointing BASH_ENV
53
- # at it would skip our setup for `bash -c` invocations (which is the path
54
- # the agent's bash tool uses).
55
- ENV BASH_ENV=/etc/bash.bashrc.fnm
56
-
57
- RUN apt-get update && apt-get install -y --no-install-recommends \
58
- python3 python3-pip pipx \
59
- && PIPX_HOME=/opt/pipx PIPX_BIN_DIR=/usr/local/bin pipx install semgrep \
60
- && curl -sSfL https://github.com/gitleaks/gitleaks/releases/download/v8.21.2/gitleaks_8.21.2_linux_x64.tar.gz \
61
- | tar -xz -C /usr/local/bin gitleaks \
62
- && apt-get purge -y python3-pip \
63
- && rm -rf /var/lib/apt/lists/*
64
-
65
- # uv — fast, isolated Python runner for `type: script` (runtime: python) phases.
66
- # Single static binary; `uv run script.py` honours PEP 723 inline dependency
67
- # blocks and resolves them into a cached venv (UV_CACHE_DIR is pointed at the
68
- # shared /cache volume at runtime, mirroring the npm/pnpm/yarn cache wiring).
69
- RUN curl -LsSf https://astral.sh/uv/install.sh | env UV_INSTALL_DIR=/usr/local/bin sh \
70
- && uv --version
71
-
72
- # Create non-root agent user, UID/GID pinned to 10001 to MATCH the harness
73
- # `lastlight` user (see Dockerfile: `useradd -u 10001 lastlight`). The
74
- # entrypoint chowns the bind-mounted workspace to `agent`; per-PR workspaces
75
- # are persistent and reused across runs (issue #107), so the harness re-stages
76
- # AGENTS.md + skills into them on the next run. If `agent` lands on the default
77
- # uid (1001) those files become unwritable by the harness (10001) on reuse —
78
- # EACCES, and the review silently runs with no skill/AGENTS.md. Sharing the uid
79
- # keeps the reused workspace mutually writable by both the agent (in-container)
80
- # and the harness (host). Also self-heals dirs left at 1001 by older images:
81
- # the entrypoint's `chown -R agent:agent` re-owns them to 10001 on next reuse.
82
- RUN groupadd -g 10001 agent && useradd -m -s /bin/bash -u 10001 -g 10001 agent
5
+ # The heavy, stable toolchain (Debian deps, fnm + Node versions, semgrep/
6
+ # gitleaks, uv, the `agent` user) lives in the shared sandbox-base image so it
7
+ # stays cached across releases. This file adds only the THIN, frequently-changing
8
+ # tail: the pinned agentic-pi install + the baked agent-context + the entrypoint.
9
+ # Keep it that way — the same tail is duplicated in sandbox-qa.Dockerfile so
10
+ # Chromium stays a cached child of the base rather than of this churn.
11
+ #
12
+ # Build order matters (sandbox-base must exist first):
13
+ # docker compose --profile build-only build sandbox-base sandbox
14
+ FROM lastlight-sandbox-base:latest
83
15
 
84
16
  # Install agentic-pi globally so the harness can `docker exec ...
85
- # agentic-pi run ...` against this container. The version + integrity
86
- # come straight from lastlight's `package-lock.json` so `npm i agentic-pi@…`
87
- # in the harness automatically flows into the sandbox image — no
88
- # manual Dockerfile bumps. `npm install -g <tarball>` itself doesn't
89
- # consult a lockfile, hence the explicit integrity verification.
90
- COPY package-lock.json /tmp/lastlight-lock.json
91
- RUN node -e "const p=require('/tmp/lastlight-lock.json').packages['node_modules/agentic-pi']; \
92
- if(!p) throw new Error('agentic-pi missing from lockfile'); \
93
- process.stdout.write(p.version + '\n' + p.integrity + '\n')" > /tmp/agentic-pi.pin \
94
- && version=$(sed -n '1p' /tmp/agentic-pi.pin) \
17
+ # agentic-pi run ...` against this container. The version + integrity come from
18
+ # `sandbox/agentic-pi.pin` (regenerated from lastlight's package-lock.json by
19
+ # scripts/agentic-pi-pin.sh, drift-guarded by tests/agentic-pi-pin.test.ts). We
20
+ # COPY that tiny two-line file rather than the whole package-lock.json on
21
+ # purpose: the lockfile's hash changes on every release and would rebust this
22
+ # layer (and sandbox-qa's Chromium) on every version bump; the pin changes only
23
+ # when agentic-pi actually does. `npm install -g <tarball>` doesn't consult a
24
+ # lockfile, hence the explicit integrity verification.
25
+ COPY sandbox/agentic-pi.pin /tmp/agentic-pi.pin
26
+ RUN version=$(sed -n '1p' /tmp/agentic-pi.pin) \
95
27
  && expected=$(sed -n '2p' /tmp/agentic-pi.pin) \
96
28
  && echo "Installing agentic-pi@${version} (${expected})" \
97
29
  && curl -fsSL "https://registry.npmjs.org/agentic-pi/-/agentic-pi-${version}.tgz" -o /tmp/agentic-pi.tgz \
@@ -103,7 +35,7 @@ RUN node -e "const p=require('/tmp/lastlight-lock.json').packages['node_modules/
103
35
  exit 1; \
104
36
  fi \
105
37
  && npm install -g --no-audit --no-fund /tmp/agentic-pi.tgz \
106
- && rm /tmp/agentic-pi.tgz /tmp/agentic-pi.pin /tmp/lastlight-lock.json
38
+ && rm /tmp/agentic-pi.tgz /tmp/agentic-pi.pin
107
39
 
108
40
  # Agent context (baked at /app/ — entrypoint cats into workspace/AGENTS.md)
109
41
  COPY agent-context/ /app/agent-context/
@@ -67,10 +67,11 @@ maintainer to `@last-light build` (or `explore`) it — let triage own work item
67
67
  For broad/open-ended questions (e.g. "what's missing vs tool X"), gather a
68
68
  representative sample and answer from it — explicitly noting it's a
69
69
  sample, not an exhaustive audit — rather than enumerating everything.
70
- 3. **Label (GitHub issue only).** Apply `question` with `github_add_labels`
71
- (create it first with `github_create_label`, color `d876e3`; ignore a 422
72
- "already exists"). If label creation/adding is denied, skip it — the answer
73
- is the deliverable. For a Slack-initiated question there is no issue to label.
70
+ 3. **Label (GitHub issue only).** Ensure `question` exists idempotently with
71
+ `github_ensure_labels` (`[{name: "question", color: "d876e3"}]` — one call,
72
+ no 422 to worry about), then apply it with `github_add_labels`. If ensuring
73
+ or adding the label is denied, skip it — the answer is the deliverable. For a
74
+ Slack-initiated question there is no issue to label.
74
75
  4. **Write the answer as your final message** (the harness delivers it — see
75
76
  above; do not post it yourself):
76
77
  - Direct and structured. Lead with the answer; use short sections or a
@@ -23,7 +23,9 @@ outgoing comment.** Never make code changes, create branches, or push.
23
23
  2. **Do the bounded action:**
24
24
  - Close / reopen → `github_update_issue`
25
25
  - Label → `github_add_labels` / `github_remove_label` (use the canonical
26
- triage roles from `docs/agents/triage-labels.md` where relevant)
26
+ triage roles from `docs/agents/triage-labels.md` where relevant; if you're
27
+ applying a canonical label that may not exist yet, ensure it first with one
28
+ idempotent `github_ensure_labels` call so it lands with the right color)
27
29
  - Duplicate check → search similar issues, link the original in one comment
28
30
  - Answer a direct question → ≤5 sentences, ≤2 file reads. Don't survey the
29
31
  codebase or compile a report.
@@ -29,8 +29,10 @@ downstream build agent, which has full source access.
29
29
 
30
30
  ## 0. Ensure the labels exist
31
31
 
32
- Before applying any label, create the canonical set idempotently with
33
- `github_create_label` (ignore 422 "already exists"):
32
+ Before applying any label, create the canonical set in a **single** idempotent
33
+ `github_ensure_labels` call — pass every row below as one `labels` array
34
+ (`{name, color}`). It lists once and creates only the missing ones, so it never
35
+ errors on labels that already exist:
34
36
 
35
37
  | Label | Color | Role |
36
38
  |-------|-------|------|
@@ -44,7 +46,7 @@ Before applying any label, create the canonical set idempotently with
44
46
  | `duplicate` | `cfd3d7` | dedupe |
45
47
  | `question` | `d876e3` | question |
46
48
 
47
- If label creation is denied (the token lacks the permission), fall back to using
49
+ If the call is denied (the token lacks the permission), fall back to using
48
50
  only the labels that already exist on the repo and skip the rest. Done when the
49
51
  labels you need are present or you've confirmed you can't create them.
50
52
 
@@ -68,7 +68,9 @@ recording "no relevant changes since prior scan" and return.
68
68
 
69
69
  ### 2. Ensure labels exist
70
70
 
71
- `github_create_label` for each (idempotent — ignore 422):
71
+ One idempotent `github_ensure_labels` call — pass every row below as a single
72
+ `labels` array of `{name, color}` (it creates only the missing ones, never
73
+ errors on existing labels):
72
74
 
73
75
  | Label | Color | Purpose |
74
76
  |-------|-------|---------|
@@ -58,7 +58,8 @@ double-post. The only GitHub write you make is the label:
58
58
 
59
59
  {{#if issueNumber}}
60
60
  - Apply the `question` label to issue #{{issueNumber}} with `github_add_labels`
61
- (create it first with `github_create_label`, color `d876e3`; ignore a 422).
61
+ (ensure it exists first with one `github_ensure_labels` call —
62
+ `[{name: "question", color: "d876e3"}]`; it's idempotent, no 422 to handle).
62
63
  Leave the issue open.
63
64
  {{/if}}
64
65
  {{#if !issueNumber}}