@openrig/cli 0.6.8 → 0.6.9
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/daemon/assets/plugins/openrig-core/hooks/scripts/refocus.cjs +41 -2
- package/daemon/assets/plugins/openrig-core/skills/messaging-the-human/SKILL.md +12 -4
- package/daemon/assets/plugins/openrig-core/skills/refocusing/scripts/trace-to-root.py +218 -7
- package/daemon/context-packs/help/manifest.yaml +1 -1
- package/daemon/context-packs/onboarding-width/manifest.yaml +1 -1
- package/daemon/context-packs/onboarding-width/public-what-you-can-do.md +17 -7
- package/daemon/context-packs/reference/manifest.yaml +1 -1
- package/daemon/context-packs/reference/whats-new.md +69 -74
- package/daemon/context-packs/skills/agent-operated-software/manifest.yaml +1 -1
- package/daemon/context-packs/skills/agent-operated-workflows/manifest.yaml +1 -1
- package/daemon/context-packs/skills/applying-a-permission-policy/manifest.yaml +1 -1
- package/daemon/context-packs/skills/claude-compaction-restore/manifest.yaml +1 -1
- package/daemon/context-packs/skills/core/agent-starters/manifest.yaml +1 -1
- package/daemon/context-packs/skills/core/agent-startup-and-context-ingestion/manifest.yaml +1 -1
- package/daemon/context-packs/skills/core/cross-host-rig-commands/SKILL.md +1 -0
- package/daemon/context-packs/skills/core/cross-host-rig-commands/manifest.yaml +1 -1
- package/daemon/context-packs/skills/core/human-in-the-loop/manifest.yaml +1 -1
- package/daemon/context-packs/skills/core/messaging-the-human/SKILL.md +12 -4
- package/daemon/context-packs/skills/core/messaging-the-human/manifest.yaml +1 -1
- package/daemon/context-packs/skills/core/openrig-architect/manifest.yaml +1 -1
- package/daemon/context-packs/skills/core/openrig-cmux/manifest.yaml +1 -1
- package/daemon/context-packs/skills/core/openrig-herdr/manifest.yaml +1 -1
- package/daemon/context-packs/skills/core/openrig-skills/manifest.yaml +1 -1
- package/daemon/context-packs/skills/core/openrig-software-factory/manifest.yaml +1 -1
- package/daemon/context-packs/skills/core/openrig-upgrade/manifest.yaml +1 -1
- package/daemon/context-packs/skills/core/rig-bundles-and-shareable-artifacts/manifest.yaml +1 -1
- package/daemon/context-packs/skills/core/rig-lifecycle/manifest.yaml +1 -1
- package/daemon/context-packs/skills/core/session-source-fork/manifest.yaml +1 -1
- package/daemon/context-packs/skills/core/specification-system/manifest.yaml +1 -1
- package/daemon/context-packs/skills/core/topology-mutation-and-seat-management/manifest.yaml +1 -1
- package/daemon/context-packs/skills/core/watchdog/manifest.yaml +1 -1
- package/daemon/context-packs/skills/delegating-work/manifest.yaml +1 -1
- package/daemon/context-packs/skills/forming-an-openrig-mental-model/manifest.yaml +1 -1
- package/daemon/context-packs/skills/loading-addressable-markdown/manifest.yaml +1 -1
- package/daemon/context-packs/skills/mission-slice-sop/manifest.yaml +1 -1
- package/daemon/context-packs/skills/openrig-operating-model/manifest.yaml +1 -1
- package/daemon/context-packs/skills/openrig-user/manifest.yaml +1 -1
- package/daemon/context-packs/skills/orienting-to-an-inherited-seat/manifest.yaml +1 -1
- package/daemon/context-packs/skills/pm/backlog-capture/manifest.yaml +1 -1
- package/daemon/context-packs/skills/pm/context-builder/manifest.yaml +1 -1
- package/daemon/context-packs/skills/pm/exec-summary/manifest.yaml +1 -1
- package/daemon/context-packs/skills/pm/office-hours/manifest.yaml +1 -1
- package/daemon/context-packs/skills/pm/plan-review/manifest.yaml +1 -1
- package/daemon/context-packs/skills/pm/requirements-writer/manifest.yaml +1 -1
- package/daemon/context-packs/skills/pm/ui-mockup/manifest.yaml +1 -1
- package/daemon/context-packs/skills/pods/development-team/manifest.yaml +1 -1
- package/daemon/context-packs/skills/pods/orchestration-team/manifest.yaml +1 -1
- package/daemon/context-packs/skills/pods/oversight-team/manifest.yaml +1 -1
- package/daemon/context-packs/skills/pods/review-team/manifest.yaml +1 -1
- package/daemon/context-packs/skills/process/agent-browser/manifest.yaml +1 -1
- package/daemon/context-packs/skills/process/context-engineering/manifest.yaml +1 -1
- package/daemon/context-packs/skills/process/dogfood/manifest.yaml +1 -1
- package/daemon/context-packs/skills/process/frontend-design/manifest.yaml +1 -1
- package/daemon/context-packs/skills/process/systematic-debugging/manifest.yaml +1 -1
- package/daemon/context-packs/skills/process/test-driven-development/manifest.yaml +1 -1
- package/daemon/context-packs/skills/process/verification-before-completion/manifest.yaml +1 -1
- package/daemon/context-packs/skills/queue-handoff/manifest.yaml +1 -1
- package/daemon/context-packs/skills/refocusing/manifest.yaml +1 -1
- package/daemon/context-packs/skills/refocusing/scripts/trace-to-root.py +218 -7
- package/daemon/context-packs/skills/retiring-and-inheriting-a-seat/manifest.yaml +1 -1
- package/daemon/context-packs/skills/seat-continuity-and-handover/manifest.yaml +1 -1
- package/daemon/context-packs/skills/session-compaction-and-restore/manifest.yaml +1 -1
- package/daemon/context-packs/skills/software-for-agents/manifest.yaml +1 -1
- package/daemon/context-packs/world-example/manifest.yaml +1 -1
- package/daemon/context-packs/world-public/manifest.yaml +1 -1
- package/daemon/dist/build-info.js +3 -3
- package/daemon/dist/db/all-migrations.d.ts.map +1 -1
- package/daemon/dist/db/all-migrations.js +2 -0
- package/daemon/dist/db/all-migrations.js.map +1 -1
- package/daemon/dist/db/migrations/098_queue_transitions_qitem_id_order.d.ts +3 -0
- package/daemon/dist/db/migrations/098_queue_transitions_qitem_id_order.d.ts.map +1 -0
- package/daemon/dist/db/migrations/098_queue_transitions_qitem_id_order.js +12 -0
- package/daemon/dist/db/migrations/098_queue_transitions_qitem_id_order.js.map +1 -0
- package/daemon/dist/domain/current-work.d.ts +74 -0
- package/daemon/dist/domain/current-work.d.ts.map +1 -1
- package/daemon/dist/domain/current-work.js +165 -0
- package/daemon/dist/domain/current-work.js.map +1 -1
- package/daemon/dist/domain/gateway/connections-projection.d.ts +2 -0
- package/daemon/dist/domain/gateway/connections-projection.d.ts.map +1 -1
- package/daemon/dist/domain/gateway/connections-projection.js +3 -1
- package/daemon/dist/domain/gateway/connections-projection.js.map +1 -1
- package/daemon/dist/domain/gateway/gateway-subsystem.d.ts +3 -1
- package/daemon/dist/domain/gateway/gateway-subsystem.d.ts.map +1 -1
- package/daemon/dist/domain/gateway/gateway-subsystem.js +3 -1
- package/daemon/dist/domain/gateway/gateway-subsystem.js.map +1 -1
- package/daemon/dist/domain/gateway/slack/queue-access.d.ts +3 -0
- package/daemon/dist/domain/gateway/slack/queue-access.d.ts.map +1 -1
- package/daemon/dist/domain/gateway/slack/queue-access.js +5 -1
- package/daemon/dist/domain/gateway/slack/queue-access.js.map +1 -1
- package/daemon/dist/domain/gateway/slack/slack-subsystem.d.ts.map +1 -1
- package/daemon/dist/domain/gateway/slack/slack-subsystem.js +74 -19
- package/daemon/dist/domain/gateway/slack/slack-subsystem.js.map +1 -1
- package/daemon/dist/domain/gateway/slack/socket-inbound.d.ts +37 -0
- package/daemon/dist/domain/gateway/slack/socket-inbound.d.ts.map +1 -1
- package/daemon/dist/domain/gateway/slack/socket-inbound.js +281 -38
- package/daemon/dist/domain/gateway/slack/socket-inbound.js.map +1 -1
- package/daemon/dist/domain/gateway/slack/state-store.d.ts +14 -1
- package/daemon/dist/domain/gateway/slack/state-store.d.ts.map +1 -1
- package/daemon/dist/domain/gateway/slack/state-store.js +16 -0
- package/daemon/dist/domain/gateway/slack/state-store.js.map +1 -1
- package/daemon/dist/domain/human-questions.d.ts +12 -3
- package/daemon/dist/domain/human-questions.d.ts.map +1 -1
- package/daemon/dist/domain/human-questions.js +10 -5
- package/daemon/dist/domain/human-questions.js.map +1 -1
- package/daemon/dist/domain/kernel-boot-tracker.d.ts +20 -1
- package/daemon/dist/domain/kernel-boot-tracker.d.ts.map +1 -1
- package/daemon/dist/domain/kernel-boot-tracker.js +66 -14
- package/daemon/dist/domain/kernel-boot-tracker.js.map +1 -1
- package/daemon/dist/domain/kernel-boot.d.ts +4 -0
- package/daemon/dist/domain/kernel-boot.d.ts.map +1 -1
- package/daemon/dist/domain/kernel-boot.js +26 -1
- package/daemon/dist/domain/kernel-boot.js.map +1 -1
- package/daemon/dist/domain/native-process-lineage.d.ts.map +1 -1
- package/daemon/dist/domain/native-process-lineage.js +30 -7
- package/daemon/dist/domain/native-process-lineage.js.map +1 -1
- package/daemon/dist/domain/queue-repository.d.ts +12 -0
- package/daemon/dist/domain/queue-repository.d.ts.map +1 -1
- package/daemon/dist/domain/queue-repository.js +47 -1
- package/daemon/dist/domain/queue-repository.js.map +1 -1
- package/daemon/dist/domain/queue-transition-log.d.ts +12 -4
- package/daemon/dist/domain/queue-transition-log.d.ts.map +1 -1
- package/daemon/dist/domain/queue-transition-log.js +24 -0
- package/daemon/dist/domain/queue-transition-log.js.map +1 -1
- package/daemon/dist/domain/scope/scope-approve.d.ts +21 -0
- package/daemon/dist/domain/scope/scope-approve.d.ts.map +1 -1
- package/daemon/dist/domain/scope/scope-approve.js +48 -4
- package/daemon/dist/domain/scope/scope-approve.js.map +1 -1
- package/daemon/dist/routes/gateway.d.ts.map +1 -1
- package/daemon/dist/routes/gateway.js +2 -0
- package/daemon/dist/routes/gateway.js.map +1 -1
- package/daemon/dist/routes/kernel-status.js +3 -1
- package/daemon/dist/routes/kernel-status.js.map +1 -1
- package/daemon/dist/routes/queue.d.ts.map +1 -1
- package/daemon/dist/routes/queue.js +17 -2
- package/daemon/dist/routes/queue.js.map +1 -1
- package/daemon/dist/routes/scope-approve.d.ts.map +1 -1
- package/daemon/dist/routes/scope-approve.js +3 -0
- package/daemon/dist/routes/scope-approve.js.map +1 -1
- package/daemon/docs/reference/refocus-channel.md +33 -0
- package/daemon/docs/reference/slack-app-setup.md +13 -0
- package/daemon/docs/reference/whats-new.md +69 -74
- package/daemon/specs/agents/shared/skills/core/cross-host-rig-commands/SKILL.md +1 -0
- package/daemon/specs/agents/shared/skills/core/messaging-the-human/SKILL.md +12 -4
- package/dist/build-info.js +3 -3
- package/dist/commands/chatroom.d.ts.map +1 -1
- package/dist/commands/chatroom.js +25 -5
- package/dist/commands/chatroom.js.map +1 -1
- package/dist/commands/queue.d.ts.map +1 -1
- package/dist/commands/queue.js +3 -0
- package/dist/commands/queue.js.map +1 -1
- package/dist/commands/scope.d.ts.map +1 -1
- package/dist/commands/scope.js +10 -1
- package/dist/commands/scope.js.map +1 -1
- package/dist/commands/send.d.ts.map +1 -1
- package/dist/commands/send.js +23 -5
- package/dist/commands/send.js.map +1 -1
- package/dist/commands/slack.d.ts +17 -0
- package/dist/commands/slack.d.ts.map +1 -1
- package/dist/commands/slack.js +41 -0
- package/dist/commands/slack.js.map +1 -1
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +5 -0
- package/dist/commands/status.js.map +1 -1
- package/dist/cross-host-types.d.ts +1 -1
- package/dist/cross-host-types.d.ts.map +1 -1
- package/dist/lib/scope/scope-fs.d.ts +13 -0
- package/dist/lib/scope/scope-fs.d.ts.map +1 -1
- package/dist/lib/scope/scope-fs.js +28 -4
- package/dist/lib/scope/scope-fs.js.map +1 -1
- package/dist/remote-host-ops.d.ts +4 -0
- package/dist/remote-host-ops.d.ts.map +1 -1
- package/dist/remote-host-ops.js +20 -2
- package/dist/remote-host-ops.js.map +1 -1
- package/package.json +1 -1
- package/tui/dist/connections/connections-model.d.ts +5 -0
- package/tui/dist/connections/connections-model.d.ts.map +1 -1
- package/tui/dist/connections/connections-model.js +16 -1
- package/tui/dist/connections/connections-model.js.map +1 -1
|
@@ -102,10 +102,29 @@ function readConfiguredContent(home) {
|
|
|
102
102
|
// current work, its basis travels to the trace script, which alone decides how to render it;
|
|
103
103
|
// a failed or unreadable answer is passed as UNKNOWN. Never guess a work node here: a guess
|
|
104
104
|
// would silently re-point the whole trace.
|
|
105
|
+
// OPR.0.7.0.12 — the work packet, switched off by default: the trace receives the daemon's
|
|
106
|
+
// labelled work candidates and carries duties and notes as text. With it off, every path below
|
|
107
|
+
// is unchanged.
|
|
108
|
+
function workPacketEnabled() {
|
|
109
|
+
return enabled(process.env.OPENRIG_REFOCUS_WORK_PACKET, false);
|
|
110
|
+
}
|
|
111
|
+
|
|
105
112
|
function readQueueWhoami(timeout = 2_000) {
|
|
106
|
-
const
|
|
113
|
+
const args = ["queue", "whoami", "--json", ...(workPacketEnabled() ? ["--work-candidates"] : [])];
|
|
114
|
+
let result = spawnSync("rig", args, {
|
|
107
115
|
encoding: "utf8", env: process.env, timeout, maxBuffer: 16 * 1024 * 1024,
|
|
108
116
|
});
|
|
117
|
+
// A rig CLI older than this hook rejects the flag. Ask again without it, inside what is left of
|
|
118
|
+
// the budget, so the role and the strict answer still arrive (just without candidates).
|
|
119
|
+
if (args.includes("--work-candidates") && !result.error && result.status !== 0
|
|
120
|
+
&& /unknown option/i.test(`${result.stderr || ""}${result.stdout || ""}`)) {
|
|
121
|
+
const remaining = remainingLookupBudget();
|
|
122
|
+
if (remaining > 250) {
|
|
123
|
+
result = spawnSync("rig", ["queue", "whoami", "--json"], {
|
|
124
|
+
encoding: "utf8", env: process.env, timeout: remaining, maxBuffer: 16 * 1024 * 1024,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
109
128
|
if (result.error) return { unknown: `queue whoami failed: ${result.error.message}` };
|
|
110
129
|
if (result.status !== 0 || !result.stdout || !result.stdout.trim()) {
|
|
111
130
|
return { unknown: `queue whoami exited ${result.status ?? "without a status"} with no answer` };
|
|
@@ -208,11 +227,31 @@ function renderTrace() {
|
|
|
208
227
|
}
|
|
209
228
|
role = renderRole(whoami);
|
|
210
229
|
}
|
|
211
|
-
|
|
230
|
+
const packet = workPacketEnabled();
|
|
231
|
+
let evidence = null;
|
|
232
|
+
if (packet) {
|
|
233
|
+
args.push("--packet");
|
|
234
|
+
if (trees !== "topology") {
|
|
235
|
+
if (!whoami) {
|
|
236
|
+
const timeout = remainingLookupBudget();
|
|
237
|
+
whoami = timeout > 250 ? readQueueWhoami(timeout) : { unknown: "no budget left" };
|
|
238
|
+
}
|
|
239
|
+
const candidates = whoami.answer?.workCandidates;
|
|
240
|
+
if (candidates && typeof candidates === "object") evidence = JSON.stringify(candidates);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
// An explicit start always wins. Otherwise the candidates replace the strict answer: the
|
|
244
|
+
// strict node ignores mission-only rows, so it could hide a second mission in flight.
|
|
245
|
+
const explicitStart = Boolean(process.env.OPENRIG_REFOCUS_WORK_NODE);
|
|
246
|
+
if (work.start && (explicitStart || !evidence)) args.push("--work-start", work.start);
|
|
247
|
+
else if (evidence) args.push("--work-candidates", "-");
|
|
212
248
|
else if (work.basis) args.push("--work-basis", work.basis);
|
|
213
249
|
else if (work.unknown) args.push("--work-unknown", work.unknown);
|
|
250
|
+
// With an explicit start, the evidence supplies only held and next work.
|
|
251
|
+
if (explicitStart && evidence) args.push("--work-candidates", "-");
|
|
214
252
|
const result = spawnSync(process.env.PYTHON || "python3", args, {
|
|
215
253
|
encoding: "utf8",
|
|
254
|
+
...(evidence ? { input: evidence } : {}),
|
|
216
255
|
env: traceEnv(trees),
|
|
217
256
|
timeout: 2_000,
|
|
218
257
|
maxBuffer: 16 * 1024 * 1024,
|
|
@@ -104,10 +104,18 @@ a JSON array of 1–4 questions, each
|
|
|
104
104
|
options (labels up to 75 characters, at most one recommended). Slack shows each
|
|
105
105
|
question as a row of buttons. Each click records that answer on the item, and
|
|
106
106
|
the decision resolves once every question has one. You then receive one reply
|
|
107
|
-
row listing the answers
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
107
|
+
row listing the answers. In `humanAnswers`, clicked answers are option-id strings.
|
|
108
|
+
A whole typed reply is an object:
|
|
109
|
+
`{kind: "typed-reply", text: "…", placement: "first-unanswered", unansweredCount: N}`.
|
|
110
|
+
It is placed automatically under the first unanswered question; the person did
|
|
111
|
+
not select that question. `N` counts the other question slots still empty in
|
|
112
|
+
`humanAnswers` after that placement.
|
|
113
|
+
Earlier button answers stay intact. Read `text` as the person's words, never as
|
|
114
|
+
an option id even if the strings match. The typed reply still closes the decision
|
|
115
|
+
with the other questions unanswered; `done` is not approval. If every button
|
|
116
|
+
answer was already recorded, that final answer set stays intact and the typed
|
|
117
|
+
reply remains in its correlated reply row. Keep the brief complete: the questions
|
|
118
|
+
add buttons, they do not replace the explanation.
|
|
111
119
|
|
|
112
120
|
If an existing agent-owned row must wait for a **decision**, block it on the **new live qitem ID**
|
|
113
121
|
(`rig queue block <work-id> --on <human-qitem-id> ...`), not on the human address.
|
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
"""Render topology and work context by ascending directory paths only."""
|
|
3
3
|
|
|
4
4
|
import argparse
|
|
5
|
+
import datetime
|
|
5
6
|
import json
|
|
6
7
|
import os
|
|
7
8
|
import re
|
|
8
9
|
import subprocess
|
|
10
|
+
import sys
|
|
9
11
|
from pathlib import Path
|
|
10
12
|
|
|
11
13
|
SHELVES = {"rigs", "pods", "seats", "missions", "slices"}
|
|
@@ -148,7 +150,82 @@ def light_learned(text):
|
|
|
148
150
|
return body[:boundary if boundary > 0 else 800].rstrip() + "\n[… use --depth full for the rest]"
|
|
149
151
|
|
|
150
152
|
|
|
151
|
-
|
|
153
|
+
# OPR.0.7.0.12 — the refocus packet (--packet). Duties and notes are carried as text by
|
|
154
|
+
# conventional section, with an honest "not found" and a bounded excerpt otherwise; nobody is
|
|
155
|
+
# asked to add headings. Old files are named with their measured age as a cue, not a verdict.
|
|
156
|
+
DUTY_HEADINGS = ("my job here", "standing duties")
|
|
157
|
+
NOTES_STATE_HEADINGS = ("current state",)
|
|
158
|
+
HEADING = re.compile(r"^(#{1,6})\s+(.*?)\s*#*\s*$")
|
|
159
|
+
DEFAULT_NOTES_MAX_AGE_DAYS = 14
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def heading_sections(text, wanted):
|
|
163
|
+
"""[(title, body)] for each Markdown heading whose title starts with a wanted phrase."""
|
|
164
|
+
lines = re.sub(r"^---\s*\n.*?\n---\s*\n", "", text or "", count=1, flags=re.S).splitlines()
|
|
165
|
+
found = []
|
|
166
|
+
for index, line in enumerate(lines):
|
|
167
|
+
match = HEADING.match(line)
|
|
168
|
+
if not match or not match.group(2).strip().lower().startswith(wanted):
|
|
169
|
+
continue
|
|
170
|
+
level = len(match.group(1))
|
|
171
|
+
body = []
|
|
172
|
+
for later in lines[index + 1:]:
|
|
173
|
+
heading = HEADING.match(later)
|
|
174
|
+
if heading and len(heading.group(1)) <= level:
|
|
175
|
+
break
|
|
176
|
+
body.append(later)
|
|
177
|
+
found.append((match.group(2).strip(), "\n".join(body).strip()))
|
|
178
|
+
return found
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def by_section(text, wanted, path):
|
|
182
|
+
found = heading_sections(text, wanted)
|
|
183
|
+
shown = "\n\n".join(f"{title}\n{body}" if body else title for title, body in found)
|
|
184
|
+
missing = [phrase for phrase in wanted if not any(title.lower().startswith(phrase) for title, _ in found)]
|
|
185
|
+
if not missing:
|
|
186
|
+
return shown
|
|
187
|
+
if not found:
|
|
188
|
+
names = " or ".join(f'"{phrase.upper()}"' for phrase in wanted)
|
|
189
|
+
return f"SECTION NOT FOUND — no {names} heading in {path}; bounded excerpt follows\n{light_learned(text)}"
|
|
190
|
+
# Some sections matched: name each one that did not, with the same bounded excerpt, so text
|
|
191
|
+
# kept under another heading is not silently dropped.
|
|
192
|
+
names = ", ".join(f'"{phrase.upper()}"' for phrase in missing)
|
|
193
|
+
return f"{shown}\n\nSECTION NOT FOUND — no {names} heading in {path}; bounded excerpt follows\n{light_learned(text)}"
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def notes_max_age_days():
|
|
197
|
+
try:
|
|
198
|
+
value = float(os.environ.get("OPENRIG_REFOCUS_NOTES_MAX_AGE_DAYS", DEFAULT_NOTES_MAX_AGE_DAYS))
|
|
199
|
+
except ValueError:
|
|
200
|
+
return DEFAULT_NOTES_MAX_AGE_DAYS
|
|
201
|
+
return value if value > 0 else DEFAULT_NOTES_MAX_AGE_DAYS
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def age_cue(path, text):
|
|
205
|
+
"""An OLD NOTES line when the file is past the threshold, measured from `updated:` or mtime."""
|
|
206
|
+
threshold = notes_max_age_days()
|
|
207
|
+
updated, source = None, "mtime"
|
|
208
|
+
match = re.match(r"^---\s*\n(.*?)\n---", text or "", re.S)
|
|
209
|
+
if match:
|
|
210
|
+
found = re.search(r"^updated:\s*[\"']?(\d{4}-\d{2}-\d{2})", match.group(1), re.M)
|
|
211
|
+
if found:
|
|
212
|
+
try:
|
|
213
|
+
updated = datetime.datetime.strptime(found.group(1), "%Y-%m-%d").replace(tzinfo=datetime.timezone.utc)
|
|
214
|
+
source = "updated"
|
|
215
|
+
except ValueError:
|
|
216
|
+
updated = None
|
|
217
|
+
if updated is None:
|
|
218
|
+
try:
|
|
219
|
+
updated = datetime.datetime.fromtimestamp(path.stat().st_mtime, datetime.timezone.utc)
|
|
220
|
+
except OSError:
|
|
221
|
+
return None
|
|
222
|
+
age = (datetime.datetime.now(datetime.timezone.utc) - updated).total_seconds() / 86400
|
|
223
|
+
if age <= threshold:
|
|
224
|
+
return None
|
|
225
|
+
return f"OLD NOTES — {path}, {age:.0f} days old, source {source}, threshold {threshold:g} days"
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def render_topology(start, root, depth, failures, packet=False):
|
|
152
229
|
output = ["## TOPOLOGY TRACE", f"root: {root}", f"start: {start}"]
|
|
153
230
|
nodes, error = ascent(start, root)
|
|
154
231
|
if error:
|
|
@@ -161,12 +238,21 @@ def render_topology(start, root, depth, failures):
|
|
|
161
238
|
if text is None:
|
|
162
239
|
output.append(f"\n### {label}\nMISSING LINK — {chain_file}")
|
|
163
240
|
continue
|
|
164
|
-
|
|
241
|
+
if depth == "full":
|
|
242
|
+
body = text.strip()
|
|
243
|
+
elif packet and node == nodes[-1]:
|
|
244
|
+
# The seat's own file: its job and standing duties as text.
|
|
245
|
+
body = by_section(text, DUTY_HEADINGS, chain_file)
|
|
246
|
+
else:
|
|
247
|
+
body = light_learned(text)
|
|
165
248
|
output.append(f"\n### {label} · LEARNED.md\n{body}")
|
|
249
|
+
cue = age_cue(chain_file, text) if packet else None
|
|
250
|
+
if cue:
|
|
251
|
+
output.append(cue)
|
|
166
252
|
return "\n".join(output)
|
|
167
253
|
|
|
168
254
|
|
|
169
|
-
def render_work(start, root, depth, failures, fallback=None):
|
|
255
|
+
def render_work(start, root, depth, failures, fallback=None, packet=False):
|
|
170
256
|
output = ["## WORK TRACE", f"root: {root}", f"start: {start}"]
|
|
171
257
|
if fallback:
|
|
172
258
|
output.append(f"FALLBACK — {fallback}")
|
|
@@ -201,6 +287,19 @@ def render_work(start, root, depth, failures, fallback=None):
|
|
|
201
287
|
output.append(f"NOTES RESOLUTION GAP — resolved {notes_name} became unreadable at {notes_path}")
|
|
202
288
|
else:
|
|
203
289
|
output.append(f"\nNOTES · {notes_name}\n{notes_text.strip()}")
|
|
290
|
+
cue = age_cue(notes_path, notes_text) if packet else None
|
|
291
|
+
if cue:
|
|
292
|
+
output.append(cue)
|
|
293
|
+
elif packet:
|
|
294
|
+
notes_text = read(notes_path)
|
|
295
|
+
if notes_text is None:
|
|
296
|
+
failures.append(f"unreadable notes: {notes_path}")
|
|
297
|
+
output.append(f"NOTES RESOLUTION GAP — resolved {notes_name} became unreadable at {notes_path}")
|
|
298
|
+
else:
|
|
299
|
+
output.append(f"\nNOTES · {notes_name}\n{by_section(notes_text, NOTES_STATE_HEADINGS, notes_path)}")
|
|
300
|
+
cue = age_cue(notes_path, notes_text)
|
|
301
|
+
if cue:
|
|
302
|
+
output.append(cue)
|
|
204
303
|
else:
|
|
205
304
|
try:
|
|
206
305
|
size = notes_path.stat().st_size
|
|
@@ -211,9 +310,107 @@ def render_work(start, root, depth, failures, fallback=None):
|
|
|
211
310
|
output.append(f"NOTES · {notes_name} · {size} bytes · {notes_path}")
|
|
212
311
|
else:
|
|
213
312
|
output.append(f"NOTES GAP — no readable mission notes at {node}")
|
|
313
|
+
|
|
314
|
+
# The SDLC convention keeps a node's current delivery state in PROGRESS.md; carry it
|
|
315
|
+
# as text when present. Its absence is not a gap: many nodes have no PROGRESS.md.
|
|
316
|
+
progress_path = node / "PROGRESS.md"
|
|
317
|
+
progress_text = read(progress_path) if packet else None
|
|
318
|
+
if progress_text is not None:
|
|
319
|
+
progress_body = progress_text.strip() if depth == "full" else by_section(progress_text, NOTES_STATE_HEADINGS, progress_path)
|
|
320
|
+
output.append(f"\nPROGRESS · PROGRESS.md\n{progress_body}")
|
|
321
|
+
cue = age_cue(progress_path, progress_text)
|
|
322
|
+
if cue:
|
|
323
|
+
output.append(cue)
|
|
324
|
+
return "\n".join(output)
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def candidate_line(candidate):
|
|
328
|
+
name = candidate.get("mission") or "?"
|
|
329
|
+
if candidate.get("slice"):
|
|
330
|
+
name += f" / {candidate['slice']}"
|
|
331
|
+
intent_text = candidate.get("intent") or "no readable intent"
|
|
332
|
+
line = f"- {name} — {intent_text} — source: {candidate.get('source') or 'unknown'}"
|
|
333
|
+
if candidate.get("unresolved"):
|
|
334
|
+
line += f" — UNRESOLVED ON THIS HOST: {candidate['unresolved']}"
|
|
335
|
+
return line
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def render_held_and_next(evidence):
|
|
339
|
+
output = []
|
|
340
|
+
held = evidence.get("held") or []
|
|
341
|
+
if held:
|
|
342
|
+
output.append("HELD WORK (blocked; this is not next):")
|
|
343
|
+
for row in held:
|
|
344
|
+
output.append(f"- {row.get('qitemId') or 'a row'} — {row.get('summary') or 'no summary'} — "
|
|
345
|
+
f"blocked on {row.get('blockedOn') or 'unknown'} — "
|
|
346
|
+
f"continuation: {row.get('continuation') or 'none recorded'}")
|
|
347
|
+
following = evidence.get("next") or []
|
|
348
|
+
if following:
|
|
349
|
+
output.append("POSSIBLE NEXT WORK (pending; no order implied):")
|
|
350
|
+
for row in following:
|
|
351
|
+
missions = ", ".join(row.get("missions") or [])
|
|
352
|
+
output.append(f"- {row.get('qitemId') or 'a row'} — {row.get('summary') or 'no summary'}"
|
|
353
|
+
+ (f" [mission: {missions}]" if missions else ""))
|
|
354
|
+
if evidence.get("heldAndNextTruncated"):
|
|
355
|
+
output.append("(held and next lists were cut; `rig queue list` has the rest)")
|
|
214
356
|
return "\n".join(output)
|
|
215
357
|
|
|
216
358
|
|
|
359
|
+
def render_candidates(evidence, root, depth, failures):
|
|
360
|
+
"""The work section from labelled queue evidence. One direct, resolved tag is shown as the
|
|
361
|
+
work with its source; anything less certain lists every candidate and asks the agent."""
|
|
362
|
+
candidates = evidence.get("candidates") or []
|
|
363
|
+
unknown = evidence.get("unknown") or []
|
|
364
|
+
if (len(candidates) == 1 and not unknown and candidates[0].get("kind") == "tag"
|
|
365
|
+
and candidates[0].get("workNodePath") and not candidates[0].get("unresolved")):
|
|
366
|
+
only = candidates[0]
|
|
367
|
+
notes = [f"WORK FROM QUEUE EVIDENCE — {only.get('source')}. If this is stale, say what you are actually doing."]
|
|
368
|
+
if not only.get("slice"):
|
|
369
|
+
notes.append("no slice named")
|
|
370
|
+
traced = render_work(Path(only["workNodePath"]), root, depth, failures, packet=True)
|
|
371
|
+
head, _, rest = traced.partition("\n")
|
|
372
|
+
return "\n".join([head, *notes, rest])
|
|
373
|
+
|
|
374
|
+
missions_root = evidence.get("missionsRoot") or str(root / "missions")
|
|
375
|
+
output = ["## WORK — name it"]
|
|
376
|
+
if not candidates and not unknown:
|
|
377
|
+
output.append("No in-progress queue row names your work. If you are working from messages, "
|
|
378
|
+
"name the mission they belong to; if you have no current work, say so.")
|
|
379
|
+
elif len(candidates) > 1:
|
|
380
|
+
output.append(f"{len(candidates)} candidates are in flight. Say which mission you are actually working on.")
|
|
381
|
+
else:
|
|
382
|
+
output.append("The queue evidence does not settle your work. Confirm a candidate or name your mission.")
|
|
383
|
+
if candidates:
|
|
384
|
+
output.append("Candidates (evidence, not a verdict; none is adopted for you):")
|
|
385
|
+
output.extend(candidate_line(candidate) for candidate in candidates)
|
|
386
|
+
if unknown:
|
|
387
|
+
output.append("In-progress rows the queue cannot place:")
|
|
388
|
+
output.extend(f"- {row.get('qitemId') or 'a row'} — {row.get('summary') or 'no summary'}: {row.get('reason')}"
|
|
389
|
+
for row in unknown)
|
|
390
|
+
if not candidates:
|
|
391
|
+
names = evidence.get("missionsOnHost") or []
|
|
392
|
+
if names:
|
|
393
|
+
listing = ", ".join(names) + (", …" if evidence.get("missionsOnHostTruncated") else "")
|
|
394
|
+
output.append(f"Missions on this host: {listing}")
|
|
395
|
+
output.append("Then read that mission's current intent as text:")
|
|
396
|
+
output.append(f" python3 {Path(__file__).resolve()} --trees work --packet --work-start {missions_root}/<mission>")
|
|
397
|
+
orientation = render_work(root, root, "light", failures, packet=True)
|
|
398
|
+
_, _, rest = orientation.partition("\n")
|
|
399
|
+
output.append("\n## ORIENTATION — the project-level chain, not your work\n" + rest.lstrip("\n"))
|
|
400
|
+
return "\n".join(output)
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
def read_candidates(source):
|
|
404
|
+
try:
|
|
405
|
+
raw = sys.stdin.read() if source == "-" else Path(source).read_text(encoding="utf8")
|
|
406
|
+
evidence = json.loads(raw)
|
|
407
|
+
except (OSError, json.JSONDecodeError) as error:
|
|
408
|
+
return None, f"work candidates unreadable: {error}"
|
|
409
|
+
if not isinstance(evidence, dict) or not isinstance(evidence.get("candidates", []), list):
|
|
410
|
+
return None, "work candidates have an invalid shape"
|
|
411
|
+
return evidence, None
|
|
412
|
+
|
|
413
|
+
|
|
217
414
|
def canonical_seat(session):
|
|
218
415
|
"""(member, rig) for a canonical session name, else None so the caller asks `rig whoami`.
|
|
219
416
|
|
|
@@ -279,6 +476,8 @@ def main():
|
|
|
279
476
|
parser.add_argument("--work-basis", help="the daemon's reason no current work node was named")
|
|
280
477
|
parser.add_argument("--work-unknown", help="why the current work node could not be read")
|
|
281
478
|
parser.add_argument("--check", action="store_true", help="exit nonzero on trace or notes resolution failure; rendered text is unchanged")
|
|
479
|
+
parser.add_argument("--packet", action="store_true", help="carry duties and notes as text by section, with old-notes cues")
|
|
480
|
+
parser.add_argument("--work-candidates", help="labelled work evidence as JSON (a path, or - for stdin), from `rig queue whoami --work-candidates`")
|
|
282
481
|
args = parser.parse_args()
|
|
283
482
|
|
|
284
483
|
sections = []
|
|
@@ -293,7 +492,7 @@ def main():
|
|
|
293
492
|
sections.append(gap("## TOPOLOGY TRACE\nTRACE GAP — topology.root is unresolved"))
|
|
294
493
|
else:
|
|
295
494
|
start = Path(args.topology_start) if args.topology_start else derive_topology_start(root)
|
|
296
|
-
sections.append(render_topology(start, root, args.depth, failures) if start else
|
|
495
|
+
sections.append(render_topology(start, root, args.depth, failures, packet=args.packet) if start else
|
|
297
496
|
gap("## TOPOLOGY TRACE\nTRACE GAP — current topology node is unresolved; pass --topology-start with a literal absolute path (or configure OPENRIG_REFOCUS_TOPOLOGY_NODE separately)"))
|
|
298
497
|
|
|
299
498
|
if args.trees in {"work", "both"}:
|
|
@@ -304,10 +503,19 @@ def main():
|
|
|
304
503
|
# Precedence: an explicit start wins; then the hook's daemon answer (basis or unknown);
|
|
305
504
|
# only a standalone run with neither falls back to inferring from the working directory.
|
|
306
505
|
explicit = args.work_start or os.environ.get("OPENRIG_REFOCUS_WORK_NODE")
|
|
506
|
+
evidence = None
|
|
507
|
+
if args.work_candidates:
|
|
508
|
+
evidence, error = read_candidates(args.work_candidates)
|
|
509
|
+
if error:
|
|
510
|
+
sections.append(gap(f"## WORK TRACE\nTRACE GAP — {error}"))
|
|
307
511
|
if explicit:
|
|
308
|
-
sections.append(render_work(Path(explicit), root, args.depth, failures))
|
|
512
|
+
sections.append(render_work(Path(explicit), root, args.depth, failures, packet=args.packet))
|
|
513
|
+
elif evidence is not None:
|
|
514
|
+
sections.append(render_candidates(evidence, root, args.depth, failures))
|
|
515
|
+
elif args.work_candidates:
|
|
516
|
+
pass # the unreadable-evidence gap above is the answer; never fall back to the root
|
|
309
517
|
elif args.work_basis == NO_CURRENT_BATON_BASIS:
|
|
310
|
-
sections.append(render_work(root, root, args.depth, failures, fallback=(
|
|
518
|
+
sections.append(render_work(root, root, args.depth, failures, packet=args.packet, fallback=(
|
|
311
519
|
f"no current typed baton ({args.work_basis}). Showing the project-level chain from the work root: "
|
|
312
520
|
"a broad orientation, not evidence of a current mission")))
|
|
313
521
|
elif args.work_basis:
|
|
@@ -316,8 +524,11 @@ def main():
|
|
|
316
524
|
sections.append(gap(f"## WORK TRACE\nTRACE GAP — current work node UNKNOWN: {args.work_unknown}"))
|
|
317
525
|
else:
|
|
318
526
|
start = derive_work_start(root)
|
|
319
|
-
sections.append(render_work(start, root, args.depth, failures) if start else
|
|
527
|
+
sections.append(render_work(start, root, args.depth, failures, packet=args.packet) if start else
|
|
320
528
|
gap("## WORK TRACE\nTRACE GAP — current work node is unresolved; pass --work-start with a literal absolute path (or configure OPENRIG_REFOCUS_WORK_NODE separately)"))
|
|
529
|
+
held_and_next = render_held_and_next(evidence) if evidence else ""
|
|
530
|
+
if held_and_next:
|
|
531
|
+
sections.append(held_and_next)
|
|
321
532
|
|
|
322
533
|
print("\n\n".join(sections))
|
|
323
534
|
return 1 if args.check and failures else 0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Agent help: the single troubleshooting and escalation guide (docs/reference/help.md).
|
|
2
2
|
name: help
|
|
3
|
-
version: "0.6.
|
|
3
|
+
version: "0.6.9"
|
|
4
4
|
taxonomy: world
|
|
5
5
|
purpose: "What an agent does when its user says OpenRig isn't working: diagnose, fix within permissions, verify, and escalate to OpenRig support."
|
|
6
6
|
files:
|
|
@@ -11,7 +11,7 @@ not know exists.** So the point of what follows is not skill. It is recognition:
|
|
|
11
11
|
lands, something rhymes, and you go check instead of building.
|
|
12
12
|
|
|
13
13
|
Eighty-seven top-level verbs ship (capability canon refreshed through
|
|
14
|
-
`capability-delta-v0.6.
|
|
14
|
+
`capability-delta-v0.6.9`). This marker describes the pack's teaching, not publication or live
|
|
15
15
|
adoption. Read this once for shape, and let it make you suspicious that a thing already exists.
|
|
16
16
|
Model-divergence proclamations are live product (trust them; pins use canonical
|
|
17
17
|
model IDs).
|
|
@@ -89,6 +89,9 @@ unseen — which makes messages the one delivery channel that never gets skipped
|
|
|
89
89
|
confirm by effect, never relaunch on it. A send or wake to a seat whose latest hook is a
|
|
90
90
|
question waiting for a person is refused with `target_needs_input`, even when its screen
|
|
91
91
|
can't be read: read it with `rig capture` and get the question answered instead of retrying.
|
|
92
|
+
Over `--host` to an http-registered host, a send that may have reached the remote reads
|
|
93
|
+
`remote-outcome-unknown` ("Delivery UNCONFIRMED"): check the target with `rig capture <session>
|
|
94
|
+
--host <id>` before any resend. Only a host that was never reached reads as unreachable.
|
|
92
95
|
- **`rig send --raw`** — send exact text or keystrokes without the From/To envelope;
|
|
93
96
|
the interactive-prompt guard still applies. To deliberately drive an interactive prompt,
|
|
94
97
|
**`--dangerously-interact --reason "<why>"`** is the explicit override and implies raw text.
|
|
@@ -110,7 +113,9 @@ unseen — which makes messages the one delivery channel that never gets skipped
|
|
|
110
113
|
runtime tokens.
|
|
111
114
|
- **`rig chatroom wait`** — block until a peer actually says something, instead of polling
|
|
112
115
|
`capture` in a loop. Also `history`, `watch`, `topic` for a named thread that stays retrievable.
|
|
113
|
-
Its `--timeout` is in seconds and ignores a suffix, so `2m` means 2 seconds.
|
|
116
|
+
Its `--timeout` is in seconds and ignores a suffix, so `2m` means 2 seconds. Poll timeouts,
|
|
117
|
+
connection refusals, and reset or closed connections are retried until that deadline; other errors,
|
|
118
|
+
including an error answer from the daemon such as a removed rig, still end the wait.
|
|
114
119
|
- **`rig stream emit` / `list` / `watch` / `archive`** — drop an observation somewhere the *next*
|
|
115
120
|
agent will find it. Emitting costs nothing and does not interrupt anyone; the value appears when
|
|
116
121
|
someone lists the stream before starting.
|
|
@@ -148,7 +153,10 @@ unseen — which makes messages the one delivery channel that never gets skipped
|
|
|
148
153
|
readiness for the delivery decision and `queue create --verify` for its receipt; connector
|
|
149
154
|
setup or verification is not an outbound human message. After a connection gap the daemon
|
|
150
155
|
catches up on missed channel messages and marks them late; `rig slack status` shows the
|
|
151
|
-
connection
|
|
156
|
+
connection, the catch-up state, and the count of retained retry records for inbound messages,
|
|
157
|
+
reactions and click answers (kept across restarts, not a distinct-message count). A retained alert whose row closed before its retry isn't
|
|
158
|
+
posted; the row says why. Its `Delivery:` line says whether events are confirmed arriving;
|
|
159
|
+
"connected" alone is not delivery, and a connection that stops delivering is replaced. **`rig slack channel-map list` / `set <match> <channel>` /
|
|
152
160
|
`remove <match>`** sends a rig's or seat's human-bound posts to its own channel (a seat match
|
|
153
161
|
beats a rig match; the rest use the default channel). After a change, invite the app to each
|
|
154
162
|
channel, run `rig slack verify`, and rewire the connector (`rig slack disable` then `enable`, or
|
|
@@ -340,8 +348,8 @@ not have to tear one down to change it, and you rarely have to start from nothin
|
|
|
340
348
|
human mode it first prints one line with each agent's last known activity, or “unknown”: a
|
|
341
349
|
best-effort snapshot, not a record of what was interrupted. With `--host`, a remote teardown
|
|
342
350
|
that reports errors exits 2.
|
|
343
|
-
- **`rig launch <
|
|
344
|
-
rest. Add `--plan` to preview it first, locally or with `--host`; a plan is never sent to a
|
|
351
|
+
- **`rig launch <rigId> [seat]`** — one seat is down; start just that one, without disturbing the
|
|
352
|
+
rest. It takes the rig ID from `rig ps --json`; a rig name gives `rig_not_found`. Add `--plan` to preview it first, locally or with `--host`; a plan is never sent to a
|
|
345
353
|
daemon older than 0.5.9, which would ignore it and launch.
|
|
346
354
|
- **`rig seat launch <seat> --fresh --reason <why>`** — deliberately create a blank occupant for
|
|
347
355
|
exactly one existing seat. It uses no resume, fork, rebuild, snapshot, or restore packet;
|
|
@@ -519,7 +527,8 @@ scheme, and nothing downstream can see it.
|
|
|
519
527
|
- **`rig scope slice progress` / `mission progress`** — record that a step moved, in a form the
|
|
520
528
|
progress view can parse.
|
|
521
529
|
- **`rig scope slice approve`** — freeze a decision — *this is the plan* or *this is delivered* —
|
|
522
|
-
so the freeze is recorded rather than asserted in chat.
|
|
530
|
+
so the freeze is recorded rather than asserted in chat. With `--workspace`, approve stamps the
|
|
531
|
+
tree you named, and refuses one with no `missions/` folder.
|
|
523
532
|
- **`rig scope slice close` / `ship` / `move`** — retire it with the reason attached, move it into
|
|
524
533
|
the release it belongs to preserving git history, or re-file it under a different mission.
|
|
525
534
|
- **`rig scope slice stage` / `verified`** — how mature is this, and *when was it last checked and
|
|
@@ -679,7 +688,8 @@ reaching them is ordinary work rather than an escalation.
|
|
|
679
688
|
request, then boundedly inspect its transport receipt. Posted means posted, not read;
|
|
680
689
|
pending, failed, or indeterminate delivery leaves the row intact. Inspect the same row's
|
|
681
690
|
transitions before retrying. `rig send` remains agent-terminal delivery only.
|
|
682
|
-
`--human-questions-file` adds clickable decision questions.
|
|
691
|
+
`--human-questions-file` adds clickable decision questions. A typed Slack reply to one is kept
|
|
692
|
+
in `humanAnswers`, placed under the first unanswered question; closing isn't approval.
|
|
683
693
|
- **When project policy calls for human judgment or an update, load `messaging-the-human`.**
|
|
684
694
|
Use `rig context get skills/core/messaging-the-human/SKILL.md` for a complete decision brief,
|
|
685
695
|
related supplemental detail and explicit quiet-update intent. An FYI creates no approval
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Reference guides the help pack links to, loaded one file or section at a time by address
|
|
2
2
|
# (rig context get reference/<file>#<section>). Each file is a symlink to docs/reference/.
|
|
3
3
|
name: reference
|
|
4
|
-
version: "0.6.
|
|
4
|
+
version: "0.6.9"
|
|
5
5
|
taxonomy: world
|
|
6
6
|
purpose: "Version-matched reference guides the help guide links to. Load a single file or section by address; don't load the whole pack."
|
|
7
7
|
files:
|