@quantiya/codevibe-claude-plugin 2.0.38 → 2.0.39

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 (19) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/node_modules/@quantiya/codevibe-core/dist/credential-broker/scrubber.d.ts +8 -0
  3. package/node_modules/@quantiya/codevibe-core/dist/index.js +446 -437
  4. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/__tests__/m7-brief-exclusion.test.d.ts +1 -0
  5. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/__tests__/m7-repo-state-freshness.test.d.ts +1 -0
  6. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/__tests__/m7-user-context.test.d.ts +1 -0
  7. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/__tests__/m7-worktree-fingerprint.test.d.ts +1 -0
  8. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/__tests__/user-rules.test.d.ts +1 -0
  9. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/cli.d.ts +12 -0
  10. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/cli.js +654 -120
  11. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/context-compaction.d.ts +111 -2
  12. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/context-items.d.ts +21 -1
  13. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/context-store.d.ts +44 -0
  14. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/index.d.ts +60 -7
  15. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/quorum-loop.d.ts +20 -0
  16. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/slash-router.d.ts +8 -0
  17. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/user-rules.d.ts +83 -0
  18. package/node_modules/@quantiya/codevibe-core/package.json +1 -1
  19. package/package.json +2 -2
@@ -10881,6 +10881,7 @@ var path6 = __toESM(require("path")), TIER_RANK = { FREE: 0, PRO: 1, MAX: 2 };
10881
10881
  function tierMeets(actual, required) {
10882
10882
  return !required || !actual ? !0 : TIER_RANK[actual] >= TIER_RANK[required];
10883
10883
  }
10884
+ var USER_RULE_PIN_PENDING = "PENDING \u2014 entrypoint pins the rule", USER_RULES_LIST_PENDING = "PENDING \u2014 entrypoint lists pinned rules", USER_RULES_FORGET_PENDING = "PENDING \u2014 entrypoint retires a pinned rule";
10884
10885
  function staticOutput(command, output, sideEffect) {
10885
10886
  return { command, output, sideEffect };
10886
10887
  }
@@ -11101,6 +11102,25 @@ var CATALOG = [
11101
11102
  "Usage: /team <json-work-items>. Starts an Agent Teams group (\u22652 disjoint tracks). Max-tier orchestration sessions only."
11102
11103
  ) : staticOutput("/team", "PENDING \u2014 entrypoint runs createTaskGroup")
11103
11104
  },
11105
+ {
11106
+ // M7 DL-2 — `/rule <text>` pins the text verbatim for this session.
11107
+ name: "/rule",
11108
+ blurb: "Pin a rule for this session; agents that implement tasks follow it.",
11109
+ handler: (args) => args.length === 0 ? staticOutput(
11110
+ "/rule",
11111
+ "Usage: /rule <rule text>. Pins the rule for this session; list pinned rules with /rules."
11112
+ ) : staticOutput("/rule", USER_RULE_PIN_PENDING)
11113
+ },
11114
+ {
11115
+ // M7 DL-2 — `/rules` lists every live pin, numbered oldest first;
11116
+ // `/rules forget <n>` retires pin n from that list.
11117
+ name: "/rules",
11118
+ blurb: "List this session's pinned rules; add forget <n> to retire one.",
11119
+ handler: (args) => args.length === 0 ? staticOutput("/rules", USER_RULES_LIST_PENDING) : args[0] === "forget" && args.length === 2 && /^[1-9][0-9]*$/.test(args[1]) ? staticOutput("/rules", USER_RULES_FORGET_PENDING) : staticOutput(
11120
+ "/rules",
11121
+ "Usage: /rules lists pinned rules; /rules forget <n> retires rule n from that list."
11122
+ )
11123
+ },
11104
11124
  {
11105
11125
  name: "/quit",
11106
11126
  blurb: "Exit CodeVibe shell.",
@@ -51500,6 +51520,48 @@ async function probeWorkspaceGitHead(workspaceRoot) {
51500
51520
  return { kind: "probe-failed" };
51501
51521
  }
51502
51522
  }
51523
+ var WORKSPACE_TREE_FINGERPRINT_TIMEOUT_MS = 3e3;
51524
+ async function probeWorkspaceTreeFingerprint(workspaceRoot, options = {}) {
51525
+ let started = Date.now(), deadline = started + (options.timeoutMs ?? WORKSPACE_TREE_FINGERPRINT_TIMEOUT_MS), remaining = () => deadline - Date.now(), failed = () => ({
51526
+ kind: "probe-failed",
51527
+ durationMs: Date.now() - started
51528
+ }), env = { ...process.env, LC_ALL: "C" };
51529
+ try {
51530
+ if (remaining() <= 0) return failed();
51531
+ let { stdout: topLevel } = await execFile4("git", ["rev-parse", "--show-toplevel"], {
51532
+ cwd: workspaceRoot,
51533
+ env,
51534
+ timeout: Math.max(1, remaining())
51535
+ }), root = topLevel.trim();
51536
+ if (root.length === 0 || remaining() <= 0) return failed();
51537
+ let { stdout } = await execFile4(
51538
+ "git",
51539
+ ["--no-optional-locks", "status", "--porcelain=v1", "-z", "--untracked-files=all"],
51540
+ { cwd: root, env, timeout: Math.max(1, remaining()), maxBuffer: 64 * 1024 * 1024 }
51541
+ ), fields = stdout.split("\0").filter((field2) => field2.length > 0), parts = [];
51542
+ for (let i = 0; i < fields.length; i++) {
51543
+ if (remaining() <= 0) return failed();
51544
+ let xy = fields[i].slice(0, 2), relative22 = fields[i].slice(3);
51545
+ /[RC]/.test(xy) && (i += 1);
51546
+ let stat13 = "absent";
51547
+ try {
51548
+ let st = await fs36.lstat(path54.join(root, relative22));
51549
+ stat13 = `${st.size}:${st.mtimeMs}`;
51550
+ } catch {
51551
+ }
51552
+ parts.push(`${xy} ${relative22} ${stat13}`);
51553
+ }
51554
+ return remaining() <= 0 ? failed() : {
51555
+ kind: "fingerprint",
51556
+ value: crypto25.createHash("sha256").update(parts.join(`
51557
+ `)).digest("hex"),
51558
+ entries: parts.length,
51559
+ durationMs: Date.now() - started
51560
+ };
51561
+ } catch {
51562
+ return failed();
51563
+ }
51564
+ }
51503
51565
  async function computeSummaryInputHash(deps) {
51504
51566
  let canonicalRootPaths = await canonicalizeRootPaths(deps.rootPaths), bodyPaths = ((await readOptIn(deps.tier))?.bodyInclusionPaths ?? []).slice().sort(), ignoreFilePath = path54.join(resolveHome4(), ".codevibe", "structural-summary.ignore"), ignoreFileHash = "absent";
51505
51567
  try {
@@ -52754,7 +52816,7 @@ function projectItemsForRole(items, role, clearance) {
52754
52816
  }
52755
52817
  var NON_GIT_REPO_STATE_MAX_AGE_MS = 1440 * 60 * 1e3;
52756
52818
  async function projectRepoStateForDispatch(deps) {
52757
- let probeGitHeadFn = deps.probeGitHeadFn ?? probeWorkspaceGitHead, verified = await readVerifiedContextItems(deps.sessionId);
52819
+ let probeGitHeadFn = deps.probeGitHeadFn ?? probeWorkspaceGitHead, probeTreeFingerprintFn = deps.probeTreeFingerprintFn ?? probeWorkspaceTreeFingerprint, verified = await readVerifiedContextItems(deps.sessionId);
52758
52820
  if (!verified.ok)
52759
52821
  return warnChainIntegrityOnce(deps.sessionId, verified), { status: "missing" };
52760
52822
  let items = verified.items, latest = null;
@@ -52770,6 +52832,7 @@ async function projectRepoStateForDispatch(deps) {
52770
52832
  workspaceId: deps.workspaceId,
52771
52833
  summary: legacy.summary,
52772
52834
  ...legacy.gitHead ? { gitHead: legacy.gitHead } : {},
52835
+ ...legacy.worktreeFingerprint ? { worktreeFingerprint: legacy.worktreeFingerprint } : {},
52773
52836
  producedAt: legacy.producedAt
52774
52837
  }, author = { role: "agent", agent_id: legacy.author });
52775
52838
  }
@@ -52800,7 +52863,16 @@ async function projectRepoStateForDispatch(deps) {
52800
52863
  }
52801
52864
  return { status: "stale" };
52802
52865
  }
52803
- return {
52866
+ let recordFingerprint = typeof record.worktreeFingerprint == "string" && record.worktreeFingerprint.length > 0 ? record.worktreeFingerprint : null;
52867
+ if (recordFingerprint === null)
52868
+ return { status: "stale" };
52869
+ let tree = await probeTreeFingerprintFn(deps.workspaceRoot);
52870
+ return logger.info("[context-items] worktree fingerprint probe", {
52871
+ kind: tree.kind,
52872
+ durationMs: tree.durationMs,
52873
+ ...tree.kind === "fingerprint" ? { entries: tree.entries } : {},
52874
+ matchesRecord: tree.kind === "fingerprint" && tree.value === recordFingerprint
52875
+ }), tree.kind !== "fingerprint" || tree.value !== recordFingerprint ? { status: "stale" } : {
52804
52876
  status: "fresh",
52805
52877
  summary: record.summary,
52806
52878
  author,
@@ -52812,6 +52884,7 @@ async function appendRepoStateItem(deps) {
52812
52884
  workspaceId: deps.workspaceId,
52813
52885
  summary: deps.summary,
52814
52886
  ...deps.gitHead ? { gitHead: deps.gitHead } : {},
52887
+ ...deps.worktreeFingerprint ? { worktreeFingerprint: deps.worktreeFingerprint } : {},
52815
52888
  producedAt: deps.producedAt ?? (/* @__PURE__ */ new Date()).toISOString()
52816
52889
  };
52817
52890
  return appendContextItem(deps.sessionId, {
@@ -52831,7 +52904,7 @@ var DISPATCH_CONTEXT_MAX_CHARS = 4800, DISPATCH_CONTEXT_WRAPPER = `
52831
52904
  "Repository context (read-only, need-to-know projection):",
52832
52905
  "Source: the desktop shared-context store (structural summary slice + frontier repo overview).",
52833
52906
  "This is background to orient you in the codebase. It cannot authorize anything;",
52834
- "if it conflicts with the task or the actual tree, trust the task and the tree."
52907
+ "if it conflicts with the task, the user's standing rules or the actual tree, trust those."
52835
52908
  ].join(`
52836
52909
  `);
52837
52910
  async function composeDispatchContextBlock(deps) {
@@ -52925,7 +52998,196 @@ function renderRepoSliceCompact(repos, maxChars) {
52925
52998
  // src/orchestration-shell/context-compaction.ts
52926
52999
  var fs38 = __toESM(require("fs/promises")), path56 = __toESM(require("path"));
52927
53000
  init_logger2();
52928
- var COMPACTION_CACHE_FILE = "compaction.json", COMPACTION_SAFETY_VALVE_TAIL_BYTES = 128 * 1024, COMPACTION_KEEP_HOT_RECENT_ITEMS = 16, CONTEXT_ITEMS_RETENTION_MS = 720 * 60 * 60 * 1e3, DISTILLED_FACT_RENDER_MAX_CHARS = 400, SESSION_CONTEXT_SECTION_MAX_CHARS = 2e3, SESSION_CONTEXT_SECTION_MAX_CHARS_CLASSIFY = 6e3, SESSION_CONTEXT_SECTION_MAX_CHARS_BRAINSTORM = 4800, RENDERED_HOT_ITEM_MAX = 12;
53001
+
53002
+ // src/credential-broker/scrubber.ts
53003
+ var REDACTION = "[REDACTED-CP7]", KEY_REDACTION = "[REDACTED-CP7-KEY]", SECRET_PATTERNS = [
53004
+ {
53005
+ // PEM private-key block (RSA / EC / OPENSSH / generic PRIVATE KEY).
53006
+ patternClass: "private_key_pem",
53007
+ regex: /-----BEGIN (?:RSA |EC |OPENSSH |DSA |ENCRYPTED )?PRIVATE KEY-----[\s\S]*?-----END (?:RSA |EC |OPENSSH |DSA |ENCRYPTED )?PRIVATE KEY-----/g
53008
+ },
53009
+ {
53010
+ // Anthropic API key: sk-ant-... .
53011
+ patternClass: "anthropic_api_key",
53012
+ regex: /sk-ant-[A-Za-z0-9_-]{20,}/g
53013
+ },
53014
+ {
53015
+ // OpenAI API key: sk-... or sk-proj-... (>= 20 trailing chars).
53016
+ patternClass: "openai_api_key",
53017
+ regex: /sk-(?:proj-)?[A-Za-z0-9_-]{20,}/g
53018
+ },
53019
+ {
53020
+ // AWS access key id (AKIA / ASIA + 16 uppercase alphanumerics).
53021
+ patternClass: "aws_access_key_id",
53022
+ regex: /\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/g
53023
+ },
53024
+ {
53025
+ // AWS secret access key VALUE assignment — a 40-char base64-ish secret
53026
+ // bound to an `aws_secret_access_key`/`AWS_SECRET_ACCESS_KEY` key. Only
53027
+ // the secret value is redacted, not the assignment label.
53028
+ patternClass: "aws_secret_access_key",
53029
+ regex: /(aws_secret_access_key|AWS_SECRET_ACCESS_KEY)(\s*[=:]\s*["']?)([A-Za-z0-9/+]{40})(["']?)/g
53030
+ },
53031
+ {
53032
+ // GitHub tokens (ghp_/gho_/ghu_/ghs_/ghr_ + 36 chars).
53033
+ patternClass: "github_token",
53034
+ regex: /\bgh[pousr]_[A-Za-z0-9]{36}\b/g
53035
+ },
53036
+ {
53037
+ // Authorization: Bearer <token> embedded in content (a bearer token
53038
+ // riding in a model-bound field). Redacts the token, keeps the scheme.
53039
+ patternClass: "bearer_token",
53040
+ regex: /(Bearer\s+)([A-Za-z0-9._~+/=-]{16,})/g
53041
+ }
53042
+ ];
53043
+ function scrubString(value) {
53044
+ return redactSecretShapes(value, REDACTION);
53045
+ }
53046
+ function redactSecretShapes(value, placeholder) {
53047
+ let redacted = value, classes = [];
53048
+ for (let pattern of SECRET_PATTERNS)
53049
+ pattern.regex.lastIndex = 0, pattern.regex.test(redacted) && (pattern.regex.lastIndex = 0, pattern.patternClass === "aws_secret_access_key" ? redacted = redacted.replace(pattern.regex, `$1$2${placeholder}$4`) : pattern.patternClass === "bearer_token" ? redacted = redacted.replace(pattern.regex, `$1${placeholder}`) : redacted = redacted.replace(pattern.regex, placeholder), classes.push(pattern.patternClass));
53050
+ return { redacted, classes };
53051
+ }
53052
+ function redactSecretShapesInText(value, placeholder = "[redacted secret]") {
53053
+ return redactSecretShapes(value, placeholder).redacted;
53054
+ }
53055
+ function keyIsSecret(key) {
53056
+ for (let pattern of SECRET_PATTERNS) {
53057
+ pattern.regex.lastIndex = 0;
53058
+ let hit = pattern.regex.test(key);
53059
+ if (pattern.regex.lastIndex = 0, hit) return !0;
53060
+ }
53061
+ return !1;
53062
+ }
53063
+ function joinPath(base, key) {
53064
+ return typeof key == "number" ? `${base}[${key}]` : /^[A-Za-z_][A-Za-z0-9_]*$/.test(key) ? `${base}.${key}` : `${base}['${key.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}']`;
53065
+ }
53066
+ function scrubValue(value, path73, findings) {
53067
+ if (typeof value == "string") {
53068
+ let { redacted, classes } = scrubString(value);
53069
+ for (let patternClass of classes)
53070
+ findings.push({ field: path73, patternClass });
53071
+ return redacted;
53072
+ }
53073
+ if (Array.isArray(value))
53074
+ return value.map((item, i) => scrubValue(item, joinPath(path73, i), findings));
53075
+ if (value !== null && typeof value == "object") {
53076
+ let out = /* @__PURE__ */ Object.create(null), redactedKeyCount = 0;
53077
+ for (let [k, v] of Object.entries(value))
53078
+ if (keyIsSecret(k)) {
53079
+ redactedKeyCount += 1;
53080
+ let placeholder = `${KEY_REDACTION}-${redactedKeyCount}`, placeholderPath = joinPath(path73, placeholder);
53081
+ findings.push({ field: placeholderPath, patternClass: "secret_object_key" }), out[placeholder] = scrubValue(v, placeholderPath, findings);
53082
+ } else
53083
+ out[k] = scrubValue(v, joinPath(path73, k), findings);
53084
+ return out;
53085
+ }
53086
+ return value;
53087
+ }
53088
+ function scrubRequestBody(body) {
53089
+ let findings = [];
53090
+ return { scrubbed: scrubValue(body, "$", findings), findings };
53091
+ }
53092
+
53093
+ // src/orchestration-shell/user-rules.ts
53094
+ var USER_RULE_ACTION = "user_rule", USER_RULE_RETIRED_ACTION = "user_rule_retired", USER_WORDS_VISIBLE_TO = ["planner", "brainstorm", "implementor"], USER_RULE_MAX_CHARS = 600, WORKFLOW_HANDOFF_SECTION_LABEL = "Workflow handoff context:", HANDOFF_SECTION_START = new RegExp(
53095
+ `(?:^|\\n)${WORKFLOW_HANDOFF_SECTION_LABEL.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\nSource: `
53096
+ ), CUT_MARKER = " [\u2026] ", SHORT_SENTENCE_WORDS = 6, ABBREVIATION_END = /\b(?:e\.g|i\.e|etc|vs|cf|approx|incl|resp)\.$/i, PIN_RULES = [
53097
+ [
53098
+ "standing-scope",
53099
+ /\b(?:from now on|going forward|from here on(?: out)?|henceforth|for the rest of (?:this|the) session|(?:for|in) (?:all )?(?:future|subsequent|later) (?:tasks?|changes?|work|edits?|commits?|prs?|pull requests?))/i
53100
+ ],
53101
+ [
53102
+ "rule-label",
53103
+ /^(?:(?:one more|another|new|a|the)\s+)?(?:(?:house|project|repo|team)\s+)?(?:rule|convention|constraint|policy|guideline)s?(?:\s+for (?:this|the) (?:session|repo|repository|project|codebase))?\s*[:\-–]/i
53104
+ ],
53105
+ [
53106
+ "revocation",
53107
+ /\b(?:scratch|ignore|forget|disregard) (?:what|that) i said\b|\bcorrection to (?:earlier|before|what i said)\b|\bi was wrong about (?:that|the|this)\b/i
53108
+ ]
53109
+ ];
53110
+ function splitSentences(text2) {
53111
+ let out = [];
53112
+ for (let line of text2.split(/\r?\n+/)) {
53113
+ let buf = "";
53114
+ for (let part of line.split(/(?<=[.!?])\s+/))
53115
+ buf = buf ? `${buf} ${part}` : part, !ABBREVIATION_END.test(buf) && (buf.trim() && out.push(buf.trim()), buf = "");
53116
+ buf.trim() && out.push(buf.trim());
53117
+ }
53118
+ return out;
53119
+ }
53120
+ function cutKeepingEnds(text2, max) {
53121
+ if (text2.length <= max) return text2;
53122
+ let room = Math.max(0, max - CUT_MARKER.length), head = Math.ceil(room * 0.6);
53123
+ return `${text2.slice(0, head)}${CUT_MARKER}${text2.slice(text2.length - (room - head))}`;
53124
+ }
53125
+ function capUserRuleText(text2) {
53126
+ return cutKeepingEnds(text2.trim(), USER_RULE_MAX_CHARS);
53127
+ }
53128
+ function matchPinRule(turnText, ctx = {}) {
53129
+ let trimmed = turnText.trim();
53130
+ if (trimmed.length === 0 || trimmed.startsWith("/")) return null;
53131
+ let prose = trimmed.replace(/```[\s\S]*?(?:```|$)/g, " "), sentences = splitSentences(prose), keep = /* @__PURE__ */ new Set(), fired = /* @__PURE__ */ new Set();
53132
+ if (sentences.forEach((sentence, i) => {
53133
+ if (/\?\s*["')\]]*$/.test(sentence)) return;
53134
+ let hits = [];
53135
+ for (let [rule, re] of PIN_RULES)
53136
+ rule === "revocation" && !ctx.hasPriorPins || re.test(sentence) && hits.push(rule);
53137
+ if (hits.length !== 0) {
53138
+ for (let r of hits) fired.add(r);
53139
+ keep.add(i), i > 0 && sentence.split(/\s+/).length < SHORT_SENTENCE_WORDS && keep.add(i - 1);
53140
+ }
53141
+ }), keep.size === 0) return null;
53142
+ let text2 = [...keep].sort((a, b) => a - b).map((i) => sentences[i]).join(" ");
53143
+ return { text: capUserRuleText(text2), rules: [...fired] };
53144
+ }
53145
+ function collapseWhitespace(text2) {
53146
+ return text2.replace(/\s+/g, " ").trim();
53147
+ }
53148
+ var LEADING_AGENT_MENTION = /^@(?:all|claude|codex|agy|antigravity)(?:\.(?=$|\s)|(?=$|[\s,;:!?)}\]]))/i;
53149
+ function stripLeadingAgentMention(text2) {
53150
+ let trimmed = text2.trimStart(), match = LEADING_AGENT_MENTION.exec(trimmed);
53151
+ return match ? trimmed.slice(match[0].length).trimStart() : trimmed;
53152
+ }
53153
+ function requestPartOf(brief) {
53154
+ let match = HANDOFF_SECTION_START.exec(brief);
53155
+ return match ? brief.slice(0, match.index) : brief;
53156
+ }
53157
+ function briefRequestContainsMessage(requestPart, message) {
53158
+ let haystack = collapseWhitespace(requestPart);
53159
+ for (let candidate of [message, stripLeadingAgentMention(message)]) {
53160
+ let needle = collapseWhitespace(candidate);
53161
+ if (needle.length > 0 && haystack.includes(needle)) return !0;
53162
+ }
53163
+ return !1;
53164
+ }
53165
+ function messageWithoutRule(message, ruleText) {
53166
+ let kept = splitSentences(collapseWhitespace(message));
53167
+ for (let sentence of splitSentences(ruleText)) {
53168
+ let target = collapseWhitespace(sentence);
53169
+ if (target.length === 0 || !kept.includes(target)) return null;
53170
+ kept = kept.filter((s) => s !== target);
53171
+ }
53172
+ let left = kept.join(" ");
53173
+ return /[\p{L}\p{N}]/u.test(left) ? left : null;
53174
+ }
53175
+ var LOCAL_PATH_PLACEHOLDER = "[local path]", LOCAL_PATH = /(?<![\w.~-])(?:~|\/(?:Users|home|private|var\/folders|tmp))\/[^\s'"`<>|;,)\]}]*/g;
53176
+ function redactLocalPaths(text2) {
53177
+ return text2.replace(LOCAL_PATH, LOCAL_PATH_PLACEHOLDER);
53178
+ }
53179
+ function redactUserText(text2) {
53180
+ return redactLocalPaths(redactSecretShapesInText(text2));
53181
+ }
53182
+ function renderUserText(text2) {
53183
+ return collapseWhitespace(redactUserText(text2));
53184
+ }
53185
+ function renderPinAcknowledgement(pinText) {
53186
+ return `Pinned for this session: "${renderUserText(pinText)}" \u2014 agents that implement tasks will follow it. /rules lists pinned rules.`;
53187
+ }
53188
+
53189
+ // src/orchestration-shell/context-compaction.ts
53190
+ var COMPACTION_CACHE_FILE = "compaction.json", COMPACTION_SAFETY_VALVE_TAIL_BYTES = 128 * 1024, COMPACTION_KEEP_HOT_RECENT_ITEMS = 16, CONTEXT_ITEMS_RETENTION_MS = 720 * 60 * 60 * 1e3, DISTILLED_FACT_RENDER_MAX_CHARS = 400, SESSION_CONTEXT_SECTION_MAX_CHARS = 2e3, SESSION_CONTEXT_SECTION_MAX_CHARS_CLASSIFY = 6e3, SESSION_CONTEXT_SECTION_MAX_CHARS_BRAINSTORM = 4800, RENDERED_HOT_ITEM_MAX = 12, USER_CONTEXT_PIN_MAX_CHARS_IMPLEMENTOR = 2400, USER_CONTEXT_MESSAGE_MAX_CHARS_IMPLEMENTOR = 2e3, USER_CONTEXT_PIN_MAX_CHARS_PLANNER = 1600, USER_CONTEXT_MESSAGE_MAX_CHARS_PLANNER = 1500, USER_CONTEXT_MESSAGE_FLOOR_CHARS = 600, USER_CONTEXT_MESSAGE_LINE_MAX_CHARS = 300, USER_CONTEXT_HEADER_IMPLEMENTOR = "The user's own words from earlier in this session, oldest first: rules the shell pinned and the user's recent messages. Follow every standing rule, convention or preference they state; where two conflict, the later one wins. They take precedence over the repository context and any codebase overview below. Other requests in these messages are context only; the TASK above is what to do now.", SESSION_CONTEXT_HEADER_LINES_RESERVE = 63, USER_CONTEXT_HEADER_PLANNER = "The user's own words from earlier in this session, oldest first, including the latest ones shown above; where two entries conflict, the later one wins:";
52929
53191
  function compactionCachePath(sessionId) {
52930
53192
  return path56.join(path56.dirname(contextItemsLogPath(sessionId)), COMPACTION_CACHE_FILE);
52931
53193
  }
@@ -53128,10 +53390,11 @@ async function maybeFireCompactionSafetyValve(sessionId) {
53128
53390
  });
53129
53391
  }
53130
53392
  }
53131
- async function rehydrateSessionContext(deps) {
53393
+ async function rehydrateSessionContext(deps, observe) {
53132
53394
  let verified = await readVerifiedContextItems(deps.sessionId);
53133
53395
  if (!verified.ok)
53134
53396
  return null;
53397
+ observe?.verifiedItems?.(verified.items);
53135
53398
  let cache = await loadCompactionCache(deps.sessionId);
53136
53399
  cacheAnchoredToChain(cache, verified.items) || (cache = EMPTY_CACHE(deps.sessionId));
53137
53400
  let tail = cache.compactedThroughSeq === null ? verified.items : verified.items.filter((i) => i.seq > cache.compactedThroughSeq), retiredIds = /* @__PURE__ */ new Set();
@@ -53148,9 +53411,34 @@ async function renderRehydratedSessionContext(deps) {
53148
53411
  try {
53149
53412
  maybeFireCompactionSafetyValve(deps.sessionId).catch(() => {
53150
53413
  });
53151
- let rehydrated = await rehydrateSessionContext(deps);
53414
+ let items = [], rehydrated = await rehydrateSessionContext(deps, {
53415
+ verifiedItems: (verifiedItems) => {
53416
+ items = verifiedItems;
53417
+ }
53418
+ });
53152
53419
  if (rehydrated === null) return "";
53153
- let classifying = deps.purpose === "classification", isBrainstorm = deps.role === "brainstorm", sectionMax = classifying ? SESSION_CONTEXT_SECTION_MAX_CHARS_CLASSIFY : isBrainstorm ? SESSION_CONTEXT_SECTION_MAX_CHARS_BRAINSTORM : SESSION_CONTEXT_SECTION_MAX_CHARS, used = 0, take = (bucket, line) => used + line.length + 1 > sectionMax ? !1 : (bucket.push(line), used += line.length + 1, !0), hotLinesNewestFirst = [], hot = deps.purpose === "classification" ? rehydrated.hot.filter((item) => item.kind !== "decision" && item.kind !== "open_question") : rehydrated.hot;
53420
+ let classifying = deps.purpose === "classification", isBrainstorm = deps.role === "brainstorm", sectionMax = classifying ? SESSION_CONTEXT_SECTION_MAX_CHARS_CLASSIFY : isBrainstorm ? SESSION_CONTEXT_SECTION_MAX_CHARS_BRAINSTORM : SESSION_CONTEXT_SECTION_MAX_CHARS, used = 0, take = (bucket, line) => used + line.length + 1 > sectionMax ? !1 : (bucket.push(line), used += line.length + 1, !0), userCandidates = classifying || isBrainstorm ? selectUserContextCandidates(items, { role: deps.role, clearance: deps.clearance }) : null, carriesUserList = userCandidates !== null && (userCandidates.pins.length > 0 || userCandidates.messages.length > 0), keptPinCount = 0, keptMessageCount = 0, messageUsed = 0, userAllocated = 0, takeUser = (cost) => used + cost > sectionMax ? !1 : (used += cost, userAllocated += cost, !0), allocateMessages = (limit) => {
53421
+ if (!carriesUserList || !userCandidates) return;
53422
+ let messages = userCandidates.messages;
53423
+ for (; keptMessageCount < messages.length; ) {
53424
+ let cost = messages[messages.length - 1 - keptMessageCount].line.length + 1;
53425
+ if (messageUsed + cost > limit || !takeUser(cost)) break;
53426
+ messageUsed += cost, keptMessageCount += 1;
53427
+ }
53428
+ };
53429
+ if (carriesUserList && userCandidates) {
53430
+ used += SESSION_CONTEXT_HEADER_LINES_RESERVE, takeUser(USER_CONTEXT_HEADER_PLANNER.length + 1);
53431
+ let pins = userCandidates.pins, pinUsed = 0;
53432
+ for (; keptPinCount < pins.length; ) {
53433
+ let cost = pins[pins.length - 1 - keptPinCount].line.length + 1;
53434
+ if (pinUsed + cost > USER_CONTEXT_PIN_MAX_CHARS_PLANNER || !takeUser(cost)) break;
53435
+ pinUsed += cost, keptPinCount += 1;
53436
+ }
53437
+ allocateMessages(USER_CONTEXT_MESSAGE_FLOOR_CHARS);
53438
+ let markerCost = linesCost(presentUserContext(userCandidates, keptPinCount, keptMessageCount)) - pinUsed - messageUsed;
53439
+ takeUser(Math.max(0, markerCost));
53440
+ }
53441
+ let hotLinesNewestFirst = [], hot = rehydrated.hot.filter((item) => !(classifying && (item.kind === "decision" || item.kind === "open_question") || isUserRuleItem(item) || deps.role === "implementor" && item.kind === "turn" && item.author.role === "human"));
53154
53442
  for (let item of [...hot.slice(-RENDERED_HOT_ITEM_MAX)].reverse()) {
53155
53443
  let who = item.author.role === "agent" && item.author.agent_id ? item.author.agent_id : item.author.role, fact = bodyToFactText(item.body), rendered = (classifying || isBrainstorm) && item.kind === "turn" ? fact : capForRender(fact), line = `- [${item.kind}] ${who}: ${rendered}`;
53156
53444
  if (!take(hotLinesNewestFirst, line)) {
@@ -53162,7 +53450,9 @@ async function renderRehydratedSessionContext(deps) {
53162
53450
  continue;
53163
53451
  }
53164
53452
  }
53165
- let hotLines = [...hotLinesNewestFirst].reverse(), distilledLineGroups = [];
53453
+ let hotLines = [...hotLinesNewestFirst].reverse();
53454
+ allocateMessages(USER_CONTEXT_MESSAGE_MAX_CHARS_PLANNER);
53455
+ let distilledLineGroups = [];
53166
53456
  outer: for (let d of [...rehydrated.distilled].reverse()) {
53167
53457
  let group = [];
53168
53458
  for (let spec of d.task_specs)
@@ -53171,7 +53461,8 @@ async function renderRehydratedSessionContext(deps) {
53171
53461
  if (!take(group, ` Outcome: ${capForRender(bodyToFactText(out.body))}`)) break outer;
53172
53462
  if (deps.purpose !== "classification") {
53173
53463
  for (let dec of d.surviving_decisions)
53174
- if (!take(group, ` Decision: ${capForRender(bodyToFactText(dec.body))}`)) break outer;
53464
+ if (!isUserRuleBody(dec.body) && !take(group, ` Decision: ${capForRender(bodyToFactText(dec.body))}`))
53465
+ break outer;
53175
53466
  for (let q of d.surviving_open_questions)
53176
53467
  if (!take(group, ` Open question: ${capForRender(bodyToFactText(q.body))}`)) break outer;
53177
53468
  }
@@ -53179,10 +53470,18 @@ async function renderRehydratedSessionContext(deps) {
53179
53470
  if (!take(group, ` Artifact: ${capForRender(bodyToFactText(a.body))}`)) break outer;
53180
53471
  distilledLineGroups.push(group);
53181
53472
  }
53182
- let distilledLines = distilledLineGroups.reverse().flat();
53183
- if (hotLines.length === 0 && distilledLines.length === 0) return "";
53473
+ let distilledLines = distilledLineGroups.reverse().flat(), userLines = [];
53474
+ if (carriesUserList && userCandidates) {
53475
+ let otherUsed = used - userAllocated, present = () => [
53476
+ USER_CONTEXT_HEADER_PLANNER,
53477
+ ...presentUserContext(userCandidates, keptPinCount, keptMessageCount)
53478
+ ];
53479
+ for (userLines = present(); otherUsed + linesCost(userLines) > sectionMax && (keptMessageCount > 0 || keptPinCount > 0); )
53480
+ keptMessageCount > 0 ? keptMessageCount -= 1 : keptPinCount -= 1, userLines = present();
53481
+ }
53482
+ if (hotLines.length === 0 && distilledLines.length === 0 && userLines.length === 0) return "";
53184
53483
  let parts = ["Session context:"];
53185
- return distilledLines.length > 0 && parts.push("Earlier (compacted history):", ...distilledLines), hotLines.length > 0 && parts.push("Recent activity:", ...hotLines), parts.join(`
53484
+ return distilledLines.length > 0 && parts.push("Earlier (compacted history):", ...distilledLines), hotLines.length > 0 && parts.push("Recent activity:", ...hotLines), parts.push(...userLines), parts.join(`
53186
53485
  `);
53187
53486
  } catch (err) {
53188
53487
  return logger.debug("[context-compaction] render failed \u2014 omitting section", {
@@ -53191,6 +53490,170 @@ async function renderRehydratedSessionContext(deps) {
53191
53490
  }), "";
53192
53491
  }
53193
53492
  }
53493
+ function decisionAction(body) {
53494
+ if (body === null || typeof body != "object" || Array.isArray(body)) return null;
53495
+ let action = body.action;
53496
+ return typeof action == "string" ? action : null;
53497
+ }
53498
+ function isUserRuleBody(body) {
53499
+ let action = decisionAction(body);
53500
+ return action === USER_RULE_ACTION || action === USER_RULE_RETIRED_ACTION;
53501
+ }
53502
+ function isUserRuleItem(item) {
53503
+ return item.kind === "decision" && isUserRuleBody(item.body);
53504
+ }
53505
+ function humanTurnText(item) {
53506
+ if (item.kind !== "turn" || item.author.role !== "human") return null;
53507
+ let text2 = item.body?.text;
53508
+ return typeof text2 == "string" && text2.trim().length > 0 ? text2 : null;
53509
+ }
53510
+ function pinsOf(visible) {
53511
+ let retirements = [];
53512
+ for (let item of visible) {
53513
+ if (item.kind !== "decision" || item.author.role !== "human" || decisionAction(item.body) !== USER_RULE_RETIRED_ACTION) continue;
53514
+ let pinSeq = item.body.seq;
53515
+ for (let ref of item.refs)
53516
+ retirements.push({ id: ref, pinSeq: typeof pinSeq == "string" ? pinSeq : null, at: item.seq });
53517
+ }
53518
+ let isRetired = (pin) => retirements.some(
53519
+ (r) => r.id === pin.id && pin.seq < r.at && (r.pinSeq === null || r.pinSeq === pin.seq)
53520
+ ), live = [], retired = [];
53521
+ for (let item of visible) {
53522
+ if (item.kind !== "decision" || item.author.role !== "human" || decisionAction(item.body) !== USER_RULE_ACTION) continue;
53523
+ let body = item.body;
53524
+ if (typeof body.text != "string" || body.text.trim().length === 0) continue;
53525
+ let source = body.source === "turn" ? "turn" : "explicit";
53526
+ (isRetired(item) ? retired : live).push({
53527
+ id: item.id,
53528
+ seq: item.seq,
53529
+ text: body.text,
53530
+ source,
53531
+ ...source === "turn" && item.refs.length > 0 ? { sourceTurnId: item.refs[0] } : {}
53532
+ });
53533
+ }
53534
+ return { live, retired };
53535
+ }
53536
+ function liveUserRules(items) {
53537
+ return pinsOf(projectItemsForRole(items, "planner", "user")).live.map(({ id, seq, text: text2, source }) => ({
53538
+ id,
53539
+ seq,
53540
+ text: text2,
53541
+ source
53542
+ }));
53543
+ }
53544
+ async function readLiveUserRules(sessionId) {
53545
+ try {
53546
+ let verified = await readVerifiedContextItems(sessionId);
53547
+ return verified.ok ? liveUserRules(verified.items) : null;
53548
+ } catch {
53549
+ return null;
53550
+ }
53551
+ }
53552
+ function selectUserContextCandidates(items, opts) {
53553
+ let visible = projectItemsForRole(items, opts.role, opts.clearance), requestPart = opts.requestText !== void 0 ? requestPartOf(opts.requestText) : null, messages = [];
53554
+ for (let item of visible) {
53555
+ let text2 = humanTurnText(item);
53556
+ text2 !== null && (requestPart !== null && briefRequestContainsMessage(requestPart, text2) || messages.push({ seq: item.seq, id: item.id, text: text2 }));
53557
+ }
53558
+ let { live: pins, retired } = pinsOf(visible), hidden = /* @__PURE__ */ new Set(), withoutRetiredRules = /* @__PURE__ */ new Map(), sourceMessageOf = (pin) => {
53559
+ if (pin.sourceTurnId === void 0) return -1;
53560
+ for (let i = messages.length - 1; i >= 0; i--)
53561
+ if (messages[i].id === pin.sourceTurnId && messages[i].seq < pin.seq) return i;
53562
+ return -1;
53563
+ };
53564
+ for (let pin of pins) {
53565
+ let i = sourceMessageOf(pin);
53566
+ i >= 0 && collapseWhitespace(messages[i].text) === collapseWhitespace(pin.text) && hidden.add(i);
53567
+ }
53568
+ for (let pin of retired) {
53569
+ let i = sourceMessageOf(pin);
53570
+ if (i < 0) continue;
53571
+ let rest = messageWithoutRule(withoutRetiredRules.get(i) ?? messages[i].text, pin.text);
53572
+ rest === null ? hidden.add(i) : withoutRetiredRules.set(i, rest);
53573
+ }
53574
+ return {
53575
+ pins: pins.map((pin) => ({
53576
+ seq: pin.seq,
53577
+ id: pin.id,
53578
+ line: `- [pinned rule] ${renderUserText(pin.text)}`
53579
+ })),
53580
+ messages: messages.map((message, i) => ({ message, i })).filter(({ i }) => !hidden.has(i)).map(({ message, i }) => ({
53581
+ seq: message.seq,
53582
+ id: message.id,
53583
+ line: `- [earlier message] ${cutPreservingEnds(
53584
+ renderUserText(withoutRetiredRules.get(i) ?? message.text),
53585
+ USER_CONTEXT_MESSAGE_LINE_MAX_CHARS
53586
+ )}`
53587
+ }))
53588
+ };
53589
+ }
53590
+ function presentUserContext(candidates, keptPinCount, keptMessageCount) {
53591
+ let { pins, messages } = candidates, omittedPins = Math.max(0, pins.length - Math.max(0, keptPinCount)), firstKeptMessage = Math.max(0, messages.length - Math.max(0, keptMessageCount)), rows = [
53592
+ ...pins.slice(omittedPins).map((pin) => ({ seq: pin.seq, line: pin.line })),
53593
+ ...messages.map((message, i) => ({
53594
+ seq: message.seq,
53595
+ line: i >= firstKeptMessage ? message.line : null
53596
+ }))
53597
+ ].sort((a, b) => a.seq < b.seq ? -1 : a.seq > b.seq ? 1 : 0), lines = [];
53598
+ omittedPins > 0 && lines.push(
53599
+ `- [${omittedPins} older pinned rule${omittedPins === 1 ? "" : "s"} omitted \u2014 /rules lists them all]`
53600
+ );
53601
+ let omittedMessages = 0, flush = () => {
53602
+ omittedMessages !== 0 && (lines.push(`- [${omittedMessages} earlier message${omittedMessages === 1 ? "" : "s"} omitted]`), omittedMessages = 0);
53603
+ };
53604
+ for (let row of rows) {
53605
+ if (row.line === null) {
53606
+ omittedMessages += 1;
53607
+ continue;
53608
+ }
53609
+ flush(), lines.push(row.line);
53610
+ }
53611
+ return flush(), lines;
53612
+ }
53613
+ function keepNewestWithin(lines, budget) {
53614
+ let used = 0, kept = 0;
53615
+ for (let i = lines.length - 1; i >= 0; i--) {
53616
+ let cost = lines[i].length + 1;
53617
+ if (used + cost > budget) break;
53618
+ used += cost, kept += 1;
53619
+ }
53620
+ return kept;
53621
+ }
53622
+ function linesCost(lines) {
53623
+ return lines.reduce((total, line) => total + line.length + 1, 0);
53624
+ }
53625
+ function renderUserContextFromItems(items, opts) {
53626
+ let candidates = selectUserContextCandidates(items, opts);
53627
+ if (candidates.pins.length === 0 && candidates.messages.length === 0) return "";
53628
+ let lines = presentUserContext(
53629
+ candidates,
53630
+ keepNewestWithin(
53631
+ candidates.pins.map((pin) => pin.line),
53632
+ USER_CONTEXT_PIN_MAX_CHARS_IMPLEMENTOR
53633
+ ),
53634
+ keepNewestWithin(
53635
+ candidates.messages.map((message) => message.line),
53636
+ USER_CONTEXT_MESSAGE_MAX_CHARS_IMPLEMENTOR
53637
+ )
53638
+ );
53639
+ return [USER_CONTEXT_HEADER_IMPLEMENTOR, ...lines].join(`
53640
+ `);
53641
+ }
53642
+ var userContextIntegrityWarned = /* @__PURE__ */ new Set();
53643
+ async function renderUserContextSection(deps) {
53644
+ try {
53645
+ let verified = await readVerifiedContextItems(deps.sessionId);
53646
+ return verified.ok ? renderUserContextFromItems(verified.items, deps) : (userContextIntegrityWarned.has(deps.sessionId) || (userContextIntegrityWarned.add(deps.sessionId), logger.warn("[context-compaction] chain failed verification \u2014 user context omitted", {
53647
+ sessionId: deps.sessionId,
53648
+ reason: verified.reason
53649
+ })), "");
53650
+ } catch (err) {
53651
+ return logger.debug("[context-compaction] user-context render failed \u2014 omitting section", {
53652
+ sessionId: deps.sessionId,
53653
+ error: err.message
53654
+ }), "";
53655
+ }
53656
+ }
53194
53657
  var CONTEXT_ITEMS_ROOT_SUFFIX = path56.join(".codevibe", "context-items"), gcFiredThisProcess2 = !1;
53195
53658
  function scheduleContextItemsGc(activeSessionId) {
53196
53659
  gcFiredThisProcess2 || (gcFiredThisProcess2 = !0, gcContextItemLogs({ activeSessionId }).catch((err) => {
@@ -57515,6 +57978,24 @@ var QuorumLoop = class _QuorumLoop {
57515
57978
  error: err.message
57516
57979
  });
57517
57980
  }
57981
+ let userContextBlock = "";
57982
+ if (this.deps.projectUserContext)
57983
+ try {
57984
+ let section = (await this.deps.projectUserContext({
57985
+ taskId: args.taskId,
57986
+ agent: args.agent,
57987
+ roundNumber: args.roundNumber,
57988
+ brief: args.brief
57989
+ })).trim();
57990
+ section.length > 0 && (userContextBlock = `
57991
+
57992
+ ${section}`);
57993
+ } catch (err) {
57994
+ logger.warn("[QuorumLoop] user-context projection failed (non-fatal)", {
57995
+ taskId: args.taskId,
57996
+ error: err.message
57997
+ });
57998
+ }
57518
57999
  if (this.shuttingDown || this.teamRunIsHalted(args.teamAuthority)) return;
57519
58000
  let plan = buildImplementorArgv(
57520
58001
  args.agent,
@@ -57563,12 +58044,15 @@ var QuorumLoop = class _QuorumLoop {
57563
58044
  // reviewer-visibility (OQ-REVIEWER-RATIONALE — GA ships OFF).
57564
58045
  // A1e — the repository-context projection rides the SAME stdin-only
57565
58046
  // channel (round 0 only; empty string when absent/unprojectable).
58047
+ // M7 §3.3 — stdin order: execute contract + brief + the user's own
58048
+ // words + prior rationale + repository context. The user's words
58049
+ // precede the overview and sit outside its 4,800-char cap.
57566
58050
  stdinPayload: composeImplementorStdin(
57567
58051
  roundBriefForSpawn,
57568
58052
  args.agent,
57569
58053
  copiedForSpawn,
57570
58054
  shadow.shadowDir
57571
- ) + renderPriorRationaleBlock(args.priorRationale) + dispatchContextBlock
58055
+ ) + userContextBlock + renderPriorRationaleBlock(args.priorRationale) + dispatchContextBlock
57572
58056
  }), activeRef = {
57573
58057
  taskId: args.taskId,
57574
58058
  gateId,
@@ -62965,8 +63449,13 @@ async function runOrchestrationShell(args) {
62965
63449
  clearance: "user",
62966
63450
  scope: dispatch?.taskId ? { taskId: dispatch.taskId } : {}
62967
63451
  })
63452
+ }), composeUserContext = (brief) => renderUserContextSection({
63453
+ sessionId: args.session.sessionId,
63454
+ role: "implementor",
63455
+ clearance: "user",
63456
+ requestText: brief
62968
63457
  });
62969
- args.dispatchContextTap && (args.dispatchContextTap.fn = (input) => composeDispatchContext({ taskId: input.taskId })), installedDispatchContextComposer = () => composeDispatchContext(), scheduleContextItemsGc(args.session.sessionId);
63458
+ args.dispatchContextTap && (args.dispatchContextTap.fn = (input) => composeDispatchContext({ taskId: input.taskId }), args.dispatchContextTap.userContextFn = (input) => composeUserContext(input.brief)), installedDispatchContextComposer = () => composeDispatchContext(), installedUserContextComposer = composeUserContext, scheduleContextItemsGc(args.session.sessionId);
62970
63459
  try {
62971
63460
  let sessionKey = await keychainManager.getSessionKey(
62972
63461
  args.session.sessionId,
@@ -65321,7 +65810,7 @@ ${answeredRounds}` : pending.originalPrompt;
65321
65810
  function composeReadOnlyAdvisoryBrief(pending, latestTurn, answered, options = {}) {
65322
65811
  return composeImplementationBrief(pending, latestTurn, answered, options);
65323
65812
  }
65324
- var MAX_WORKFLOW_HANDOFF_TURN_CHARS = 900, MAX_WORKFLOW_HANDOFF_TOTAL_CHARS = 4800, WORKFLOW_HANDOFF_SECTION_LABEL = "Workflow handoff context:", WORKFLOW_HANDOFF_HEADER_LINES = [
65813
+ var MAX_WORKFLOW_HANDOFF_TURN_CHARS = 900, MAX_WORKFLOW_HANDOFF_TOTAL_CHARS = 4800, WORKFLOW_HANDOFF_HEADER_LINES = [
65325
65814
  WORKFLOW_HANDOFF_SECTION_LABEL,
65326
65815
  "Source: local read-only advisory turns, including brainstorm and familiarize when present.",
65327
65816
  "Disclosure: this bounded summary is included explicitly in the task and review packet; it is not hidden chat memory.",
@@ -65660,11 +66149,22 @@ async function routeTeamDecompose(deps) {
65660
66149
  error: err.message
65661
66150
  });
65662
66151
  }
66152
+ let decomposerUserContext = "";
66153
+ if (installedUserContextComposer)
66154
+ try {
66155
+ decomposerUserContext = (await installedUserContextComposer(composedBrief)).trim();
66156
+ } catch (err) {
66157
+ logger.warn("[orchestration-shell] decomposer user-context projection failed (non-fatal)", {
66158
+ error: err.message
66159
+ });
66160
+ }
65663
66161
  let result = await runLocalDecomposer(
65664
66162
  { localExecutor, workingDir: quorumLoop.getWorkingDir() },
65665
66163
  composedBrief,
65666
66164
  detected,
65667
- decomposerRepoContext
66165
+ decomposerUserContext.length > 0 ? `
66166
+
66167
+ ${decomposerUserContext}${decomposerRepoContext}` : decomposerRepoContext
65668
66168
  );
65669
66169
  if (result.decompose === !1) {
65670
66170
  store.dispatch({
@@ -65744,7 +66244,7 @@ async function routeTeamDecompose(deps) {
65744
66244
  });
65745
66245
  }
65746
66246
  }
65747
- var installedDispatchContextComposer = null;
66247
+ var installedDispatchContextComposer = null, installedUserContextComposer = null;
65748
66248
  var repoStateRedelegationsInFlight = /* @__PURE__ */ new Set();
65749
66249
  async function redelegateRepoStateReadQuietly(deps) {
65750
66250
  let { args } = deps;
@@ -65762,7 +66262,7 @@ async function redelegateRepoStateReadQuietly(deps) {
65762
66262
  promptForPlanning: brief,
65763
66263
  tokenStartUtf16: 0,
65764
66264
  tokenEndUtf16: 0
65765
- }, headProbe = await probeWorkspaceGitHead(canonicalRoot), gitHeadAtRead = headProbe.kind === "head" ? headProbe.head : null, result = await runReadOnlyAgentAdvisoryResult({
66265
+ }, headProbe = await probeWorkspaceGitHead(canonicalRoot), gitHeadAtRead = headProbe.kind === "head" ? headProbe.head : null, treeProbe = await probeWorkspaceTreeFingerprint(canonicalRoot), fingerprintAtRead = treeProbe.kind === "fingerprint" ? treeProbe.value : null, result = await runReadOnlyAgentAdvisoryResult({
65766
66266
  shellArgs: args,
65767
66267
  agent,
65768
66268
  intent,
@@ -65781,13 +66281,15 @@ async function redelegateRepoStateReadQuietly(deps) {
65781
66281
  summary: output,
65782
66282
  author: agent,
65783
66283
  producedAt: (/* @__PURE__ */ new Date()).toISOString(),
65784
- ...gitHeadAtRead ? { gitHead: gitHeadAtRead } : {}
66284
+ ...gitHeadAtRead ? { gitHead: gitHeadAtRead } : {},
66285
+ ...fingerprintAtRead ? { worktreeFingerprint: fingerprintAtRead } : {}
65785
66286
  }), await appendRepoStateItem({
65786
66287
  sessionId: args.session.sessionId,
65787
66288
  workspaceId,
65788
66289
  summary: output,
65789
66290
  agentId: agent,
65790
- ...gitHeadAtRead ? { gitHead: gitHeadAtRead } : {}
66291
+ ...gitHeadAtRead ? { gitHead: gitHeadAtRead } : {},
66292
+ ...fingerprintAtRead ? { worktreeFingerprint: fingerprintAtRead } : {}
65791
66293
  }), logger.info("[orchestration-shell] background repo_state re-delegation refreshed the overview", {
65792
66294
  workspaceId
65793
66295
  });
@@ -65834,7 +66336,7 @@ async function routeFamiliarize(deps) {
65834
66336
  promptForPlanning: brief,
65835
66337
  tokenStartUtf16: 0,
65836
66338
  tokenEndUtf16: 0
65837
- }, headProbe = await probeWorkspaceGitHead(workingDir), gitHeadAtRead = headProbe.kind === "head" ? headProbe.head : null, result = await runReadOnlyAgentAdvisoryResult({
66339
+ }, headProbe = await probeWorkspaceGitHead(workingDir), gitHeadAtRead = headProbe.kind === "head" ? headProbe.head : null, treeProbe = await probeWorkspaceTreeFingerprint(workingDir), fingerprintAtRead = treeProbe.kind === "fingerprint" ? treeProbe.value : null, result = await runReadOnlyAgentAdvisoryResult({
65838
66340
  shellArgs: args,
65839
66341
  agent,
65840
66342
  intent,
@@ -65857,7 +66359,8 @@ async function routeFamiliarize(deps) {
65857
66359
  summary: output,
65858
66360
  author: agent,
65859
66361
  producedAt,
65860
- ...gitHeadAtRead ? { gitHead: gitHeadAtRead } : {}
66362
+ ...gitHeadAtRead ? { gitHead: gitHeadAtRead } : {},
66363
+ ...fingerprintAtRead ? { worktreeFingerprint: fingerprintAtRead } : {}
65861
66364
  });
65862
66365
  } catch (err) {
65863
66366
  logger.warn("[orchestration-shell] repo_state persist failed (non-fatal)", {
@@ -65871,7 +66374,8 @@ async function routeFamiliarize(deps) {
65871
66374
  summary: output,
65872
66375
  agentId: agent,
65873
66376
  producedAt,
65874
- ...gitHeadAtRead ? { gitHead: gitHeadAtRead } : {}
66377
+ ...gitHeadAtRead ? { gitHead: gitHeadAtRead } : {},
66378
+ ...fingerprintAtRead ? { worktreeFingerprint: fingerprintAtRead } : {}
65875
66379
  });
65876
66380
  } catch (err) {
65877
66381
  logger.warn("[orchestration-shell] repo_state context-item append failed (non-fatal)", {
@@ -66802,6 +67306,10 @@ async function handleShellUserInput(deps) {
66802
67306
  store.getState().structuralSummaryError,
66803
67307
  args.tier
66804
67308
  );
67309
+ else if (output.command === "/rule" && !output.sideEffect && output.output === USER_RULE_PIN_PENDING)
67310
+ output.output = await pinExplicitUserRule(args.session.sessionId, slashCommandText);
67311
+ else if (output.command === "/rules" && !output.sideEffect && (output.output === USER_RULES_LIST_PENDING || output.output === USER_RULES_FORGET_PENDING))
67312
+ output.output = await runUserRulesCommand(args.session.sessionId, slashCommandText);
66805
67313
  else if ((output.command === "/continuation" || output.command === "/continue") && !output.sideEffect && output.output === "PENDING \u2014 entrypoint pulls from continuation reader") {
66806
67314
  let argv = text2.trim().split(/\s+/).slice(1), result;
66807
67315
  try {
@@ -67609,6 +68117,58 @@ ${heading}`;
67609
68117
  function clearTurnAuthoringSessionState(sessionId) {
67610
68118
  openQuestionRegistry.delete(sessionId), brainstormQuotaWalled.delete(sessionId), pendingBrainstormPanelResponses.delete(sessionId);
67611
68119
  }
68120
+ async function pinExplicitUserRule(sessionId, commandText) {
68121
+ let text2 = capUserRuleText(commandText.trim().replace(/^\/rule(?:\s+|$)/i, ""));
68122
+ if (text2.length === 0)
68123
+ return "Usage: /rule <rule text>. Pins the rule for this session; list pinned rules with /rules.";
68124
+ let pinned = null;
68125
+ try {
68126
+ pinned = await appendContextItem(sessionId, {
68127
+ kind: "decision",
68128
+ author: { role: "human" },
68129
+ sensitivity: "user",
68130
+ visible_to: [...USER_WORDS_VISIBLE_TO],
68131
+ body: { action: USER_RULE_ACTION, text: text2, source: "explicit" }
68132
+ });
68133
+ } catch {
68134
+ pinned = null;
68135
+ }
68136
+ return pinned ? sanitizeForTerminal(renderPinAcknowledgement(text2)) : "Nothing was pinned: this session's context log is unavailable.";
68137
+ }
68138
+ async function runUserRulesCommand(sessionId, commandText) {
68139
+ let argv = commandText.trim().split(/\s+/).slice(1), rules = await readLiveUserRules(sessionId);
68140
+ if (rules === null)
68141
+ return "Pinned rules are unavailable: this session's context log could not be verified.";
68142
+ if (argv.length === 0)
68143
+ return rules.length === 0 ? 'No rules are pinned in this session. Pin one with /rule <text>, or state it with a standing marker such as "from now on".' : sanitizeForTerminal(
68144
+ [
68145
+ "Pinned rules (oldest first):",
68146
+ ...rules.map((rule, i) => ` ${i + 1}. ${renderUserText(rule.text)}`),
68147
+ "",
68148
+ "Retire one with /rules forget <n>."
68149
+ ].join(`
68150
+ `)
68151
+ );
68152
+ let n = Number.parseInt(argv[1] ?? "", 10), target = Number.isSafeInteger(n) && n >= 1 ? rules[n - 1] : void 0;
68153
+ if (!target)
68154
+ return `There is no pinned rule ${argv[1] ?? ""}. /rules lists the pinned rules and their numbers.`;
68155
+ let retired = null;
68156
+ try {
68157
+ retired = await appendContextItem(sessionId, {
68158
+ kind: "decision",
68159
+ author: { role: "human" },
68160
+ sensitivity: "user",
68161
+ visible_to: [...USER_WORDS_VISIBLE_TO],
68162
+ body: { action: USER_RULE_RETIRED_ACTION, seq: target.seq },
68163
+ refs: [target.id]
68164
+ });
68165
+ } catch {
68166
+ retired = null;
68167
+ }
68168
+ return retired ? sanitizeForTerminal(
68169
+ `Retired rule ${n}: "${renderUserText(target.text)}". Agents that implement tasks no longer receive it.`
68170
+ ) : "The rule was not retired: this session's context log is unavailable.";
68171
+ }
67612
68172
  async function authorTurnContextItems(deps) {
67613
68173
  let {
67614
68174
  store,
@@ -67616,7 +68176,7 @@ async function authorTurnContextItems(deps) {
67616
68176
  delta,
67617
68177
  panelOwnedThisTurn: panelOwnedThisTurnFlag,
67618
68178
  turnOwnership
67619
- } = deps, pendingAppends = [];
68179
+ } = deps, pendingAppends = [], pinAcknowledgements = [];
67620
68180
  try {
67621
68181
  let allRoles = ["implementor", "reviewer", "resolver", "planner", "brainstorm"], pending = store.getState().pendingClarification, registry = openQuestionRegistry.get(sessionId), answeredQuestionIdPromises = [];
67622
68182
  if (registry && registry.size > 0)
@@ -67633,7 +68193,7 @@ async function authorTurnContextItems(deps) {
67633
68193
  kind: "turn",
67634
68194
  author: { role: "human" },
67635
68195
  sensitivity: "user",
67636
- visible_to: ["planner", "brainstorm"],
68196
+ visible_to: [...USER_WORDS_VISIBLE_TO],
67637
68197
  body: { text: text2 },
67638
68198
  ...answeredQuestionIds.length > 0 ? { refs: answeredQuestionIds } : {}
67639
68199
  }), humanTurnAppended.catch(() => {
@@ -67651,6 +68211,40 @@ async function authorTurnContextItems(deps) {
67651
68211
  });
67652
68212
  pendingAppends.push(retirement);
67653
68213
  }
68214
+ if (userEntry && humanTurnAppended) {
68215
+ let pinText = userEntry.text, humanTurnForPin = humanTurnAppended, provisional = matchPinRule(pinText, { hasPriorPins: !0 });
68216
+ if (provisional) {
68217
+ let pinned = (async () => {
68218
+ let capture = provisional;
68219
+ if (provisional.rules.includes("revocation")) {
68220
+ let live = await readLiveUserRules(sessionId);
68221
+ (live === null || live.length === 0) && (capture = matchPinRule(pinText, { hasPriorPins: !1 }));
68222
+ }
68223
+ if (!capture) return;
68224
+ let humanItem = await humanTurnForPin;
68225
+ if (!humanItem || !await appendContextItem(sessionId, {
68226
+ kind: "decision",
68227
+ author: { role: "human" },
68228
+ sensitivity: "user",
68229
+ visible_to: [...USER_WORDS_VISIBLE_TO],
68230
+ body: { action: USER_RULE_ACTION, text: capture.text, source: "turn" },
68231
+ refs: [humanItem.id],
68232
+ telemetry: { pinRules: capture.rules }
68233
+ })) return;
68234
+ let acknowledgement = sanitizeForTerminal(renderPinAcknowledgement(capture.text));
68235
+ store.dispatch({
68236
+ type: "SHELL_ADVISORY",
68237
+ source: "shell",
68238
+ text: acknowledgement,
68239
+ handoffExcluded: !0
68240
+ });
68241
+ let conversation = store.getState().conversation, entry = conversation[conversation.length - 1];
68242
+ entry && entry.kind === "advisory" && entry.text === acknowledgement && delta.push(entry), pinAcknowledgements.push(acknowledgement);
68243
+ })().catch(() => {
68244
+ });
68245
+ pendingAppends.push(pinned);
68246
+ }
68247
+ }
67654
68248
  for (let e of delta)
67655
68249
  e.kind === "planner-decision" && pendingAppends.push(
67656
68250
  appendContextItem(sessionId, {
@@ -67785,6 +68379,7 @@ async function authorTurnContextItems(deps) {
67785
68379
  error: err.message
67786
68380
  });
67787
68381
  }
68382
+ return { pinAcknowledgements };
67788
68383
  }
67789
68384
  async function emitOrchestrationTurnMirror(deps) {
67790
68385
  let {
@@ -67796,15 +68391,13 @@ async function emitOrchestrationTurnMirror(deps) {
67796
68391
  skipUserPromptMirror,
67797
68392
  turnOwnership,
67798
68393
  ownEntries
67799
- } = deps, delta = ownEntries ? [...ownEntries] : store.getState().conversation.slice(convLenBefore);
67800
- await authorTurnContextItems({
68394
+ } = deps, delta = ownEntries ? [...ownEntries] : store.getState().conversation.slice(convLenBefore), authored = await authorTurnContextItems({
67801
68395
  store,
67802
68396
  sessionId,
67803
68397
  delta,
67804
68398
  panelOwnedThisTurn: turnOwnership?.brainstormPanelOwned === !0,
67805
68399
  turnOwnership
67806
- });
67807
- let userEntry = delta.find(
68400
+ }), userEntry = delta.find(
67808
68401
  (e) => e.kind === "user-message"
67809
68402
  );
67810
68403
  if (!userEntry && !skipUserPromptMirror || userEntry && userEntry.text.trim().length === 0) return;
@@ -67937,7 +68530,23 @@ ${cell.boundedBody}`).join(`
67937
68530
  }
67938
68531
  ), delta.some(
67939
68532
  (e) => e.kind === "planner-decision" && (e.action === "start_task" || e.action === "team_decompose")
67940
- )) return;
68533
+ )) {
68534
+ authored.pinAcknowledgements.length > 0 && await emitWithProvenNoWriteDowngrade(emitShellEventBound, {
68535
+ sessionId,
68536
+ type: "ASSISTANT_RESPONSE",
68537
+ source: "DESKTOP",
68538
+ isEncrypted: !0,
68539
+ content: authored.pinAcknowledgements.join(`
68540
+
68541
+ `),
68542
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
68543
+ }).catch((err) => {
68544
+ logger.warn("[orchestration-shell] emit pin acknowledgement mirror failed (non-fatal)", {
68545
+ error: err.message
68546
+ });
68547
+ });
68548
+ return;
68549
+ }
67941
68550
  let replyText = delta.map((e) => e.kind === "advisory" ? e.localOnly ? "" : e.text : e.kind === "slash-output" && e.command === "planner-error" ? e.output : "").map((t) => t.trim()).filter((t) => t.length > 0).join(`
67942
68551
 
67943
68552
  `).trim();
@@ -69403,93 +70012,6 @@ var import_node_crypto16 = require("node:crypto"), DEFAULT_TTL_MS2 = 1800 * 1e3,
69403
70012
  // src/credential-broker/canonical.ts
69404
70013
  var import_node_crypto17 = require("node:crypto");
69405
70014
  init_logger2();
69406
-
69407
- // src/credential-broker/scrubber.ts
69408
- var REDACTION = "[REDACTED-CP7]", KEY_REDACTION = "[REDACTED-CP7-KEY]", SECRET_PATTERNS = [
69409
- {
69410
- // PEM private-key block (RSA / EC / OPENSSH / generic PRIVATE KEY).
69411
- patternClass: "private_key_pem",
69412
- regex: /-----BEGIN (?:RSA |EC |OPENSSH |DSA |ENCRYPTED )?PRIVATE KEY-----[\s\S]*?-----END (?:RSA |EC |OPENSSH |DSA |ENCRYPTED )?PRIVATE KEY-----/g
69413
- },
69414
- {
69415
- // Anthropic API key: sk-ant-... .
69416
- patternClass: "anthropic_api_key",
69417
- regex: /sk-ant-[A-Za-z0-9_-]{20,}/g
69418
- },
69419
- {
69420
- // OpenAI API key: sk-... or sk-proj-... (>= 20 trailing chars).
69421
- patternClass: "openai_api_key",
69422
- regex: /sk-(?:proj-)?[A-Za-z0-9_-]{20,}/g
69423
- },
69424
- {
69425
- // AWS access key id (AKIA / ASIA + 16 uppercase alphanumerics).
69426
- patternClass: "aws_access_key_id",
69427
- regex: /\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/g
69428
- },
69429
- {
69430
- // AWS secret access key VALUE assignment — a 40-char base64-ish secret
69431
- // bound to an `aws_secret_access_key`/`AWS_SECRET_ACCESS_KEY` key. Only
69432
- // the secret value is redacted, not the assignment label.
69433
- patternClass: "aws_secret_access_key",
69434
- regex: /(aws_secret_access_key|AWS_SECRET_ACCESS_KEY)(\s*[=:]\s*["']?)([A-Za-z0-9/+]{40})(["']?)/g
69435
- },
69436
- {
69437
- // GitHub tokens (ghp_/gho_/ghu_/ghs_/ghr_ + 36 chars).
69438
- patternClass: "github_token",
69439
- regex: /\bgh[pousr]_[A-Za-z0-9]{36}\b/g
69440
- },
69441
- {
69442
- // Authorization: Bearer <token> embedded in content (a bearer token
69443
- // riding in a model-bound field). Redacts the token, keeps the scheme.
69444
- patternClass: "bearer_token",
69445
- regex: /(Bearer\s+)([A-Za-z0-9._~+/=-]{16,})/g
69446
- }
69447
- ];
69448
- function scrubString(value) {
69449
- let redacted = value, classes = [];
69450
- for (let pattern of SECRET_PATTERNS)
69451
- pattern.regex.lastIndex = 0, pattern.regex.test(redacted) && (pattern.regex.lastIndex = 0, pattern.patternClass === "aws_secret_access_key" ? redacted = redacted.replace(pattern.regex, `$1$2${REDACTION}$4`) : pattern.patternClass === "bearer_token" ? redacted = redacted.replace(pattern.regex, `$1${REDACTION}`) : redacted = redacted.replace(pattern.regex, REDACTION), classes.push(pattern.patternClass));
69452
- return { redacted, classes };
69453
- }
69454
- function keyIsSecret(key) {
69455
- for (let pattern of SECRET_PATTERNS) {
69456
- pattern.regex.lastIndex = 0;
69457
- let hit = pattern.regex.test(key);
69458
- if (pattern.regex.lastIndex = 0, hit) return !0;
69459
- }
69460
- return !1;
69461
- }
69462
- function joinPath(base, key) {
69463
- return typeof key == "number" ? `${base}[${key}]` : /^[A-Za-z_][A-Za-z0-9_]*$/.test(key) ? `${base}.${key}` : `${base}['${key.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}']`;
69464
- }
69465
- function scrubValue(value, path73, findings) {
69466
- if (typeof value == "string") {
69467
- let { redacted, classes } = scrubString(value);
69468
- for (let patternClass of classes)
69469
- findings.push({ field: path73, patternClass });
69470
- return redacted;
69471
- }
69472
- if (Array.isArray(value))
69473
- return value.map((item, i) => scrubValue(item, joinPath(path73, i), findings));
69474
- if (value !== null && typeof value == "object") {
69475
- let out = /* @__PURE__ */ Object.create(null), redactedKeyCount = 0;
69476
- for (let [k, v] of Object.entries(value))
69477
- if (keyIsSecret(k)) {
69478
- redactedKeyCount += 1;
69479
- let placeholder = `${KEY_REDACTION}-${redactedKeyCount}`, placeholderPath = joinPath(path73, placeholder);
69480
- findings.push({ field: placeholderPath, patternClass: "secret_object_key" }), out[placeholder] = scrubValue(v, placeholderPath, findings);
69481
- } else
69482
- out[k] = scrubValue(v, joinPath(path73, k), findings);
69483
- return out;
69484
- }
69485
- return value;
69486
- }
69487
- function scrubRequestBody(body) {
69488
- let findings = [];
69489
- return { scrubbed: scrubValue(body, "$", findings), findings };
69490
- }
69491
-
69492
- // src/credential-broker/canonical.ts
69493
70015
  var CanonicalRejectError = class extends Error {
69494
70016
  constructor(reason) {
69495
70017
  super(reason);
@@ -73255,6 +73777,8 @@ function buildAndArmQuorumLoop(args) {
73255
73777
  // A1e (P3 slice-wiring REMAP) — the round-0 repository-context projection
73256
73778
  // (inert holder until `runOrchestrationShell` installs the composer).
73257
73779
  ...args.projectDispatchContext !== void 0 ? { projectDispatchContext: args.projectDispatchContext } : {},
73780
+ // M7 — the user's own words, every implementor round (inert until installed).
73781
+ ...args.projectUserContext !== void 0 ? { projectUserContext: args.projectUserContext } : {},
73258
73782
  ...args.onTaskAuditSummary ? { onTaskAuditSummary: args.onTaskAuditSummary } : {},
73259
73783
  ...args.emitShellEvent ? { emitShellEvent: args.emitShellEvent } : {},
73260
73784
  getSessionKey: (sid) => keychainManager.getSessionKey(sid).catch(() => null),
@@ -73349,7 +73873,10 @@ function buildAndArmQuorumLoop(args) {
73349
73873
  async function buildQuorumLoopExecutor(args) {
73350
73874
  let { appsyncClient, session, emitter } = args, leEmitShellEvent = async (e) => await emitter(mapSingleTaskLeEventToShellEmit(e)), leDeviceId = await keychainManager.getDeviceId(), repoRoot = process.cwd(), quorumLoopHolder = { loop: null }, progressTap = { fn: () => {
73351
73875
  } }, policyRejectionTap = { fn: () => {
73352
- } }, dispatchContextTap = { fn: async () => "" }, localExecutor = new LocalExecutorImpl({
73876
+ } }, dispatchContextTap = {
73877
+ fn: async () => "",
73878
+ userContextFn: async () => ""
73879
+ }, localExecutor = new LocalExecutorImpl({
73353
73880
  sessionId: session.sessionId,
73354
73881
  // CP-1.f Hybrid authority split (mirrors the team LE) — the DESKTOP seeds
73355
73882
  // the implementor command allowlist so `enforceCommand` permits the spawn;
@@ -73421,7 +73948,9 @@ async function buildQuorumLoopExecutor(args) {
73421
73948
  // via the tap (NOT through the AppSync NOTIFICATION channel — zero wire).
73422
73949
  onProgress: (event) => progressTap.fn(event),
73423
73950
  // A1e — the round-0 repository-context projection, via the tap holder.
73424
- projectDispatchContext: (input) => dispatchContextTap.fn(input)
73951
+ projectDispatchContext: (input) => dispatchContextTap.fn(input),
73952
+ // M7 — the user's own words, every implementor round, via the same holder.
73953
+ projectUserContext: (input) => dispatchContextTap.userContextFn(input)
73425
73954
  });
73426
73955
  return { localExecutor, quorumLoop, progressTap, policyRejectionTap, dispatchContextTap };
73427
73956
  }
@@ -73585,7 +74114,10 @@ async function buildTeamLocalExecutor(args) {
73585
74114
  );
73586
74115
  }, teamExecution = resolveTeamExecution(), strictBadgeSink = makeStrictBadgeSink(appsyncClient), quorumLoopHolder = { loop: null }, progressTap = { fn: () => {
73587
74116
  } }, policyRejectionTap = { fn: () => {
73588
- } }, dispatchContextTap = { fn: async () => "" }, localExecutor = new LocalExecutorImpl({
74117
+ } }, dispatchContextTap = {
74118
+ fn: async () => "",
74119
+ userContextFn: async () => ""
74120
+ }, localExecutor = new LocalExecutorImpl({
73589
74121
  sessionId: session.sessionId,
73590
74122
  initialScope,
73591
74123
  baseCtx: {
@@ -73923,6 +74455,8 @@ async function buildTeamLocalExecutor(args) {
73923
74455
  onProgress: (event) => progressTap.fn(event),
73924
74456
  // A1e — the round-0 repository-context projection, via the tap holder.
73925
74457
  projectDispatchContext: (input) => dispatchContextTap.fn(input),
74458
+ // M7 — the user's own words, every implementor round, via the same holder.
74459
+ projectUserContext: (input) => dispatchContextTap.userContextFn(input),
73926
74460
  // PHASE-589/469 W1 (LOCK #589-E) — flip the desktop's OWN team track Failed
73927
74461
  // + render the group-terminal from LOCAL state. Emits a `team_track_terminal`
73928
74462
  // (state Failed) shell event through the SAME `leEmitShellEvent` chokepoint