runwork 0.24.1 → 0.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +958 -107
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2169,7 +2169,7 @@ var init_agent_guidance = __esm(() => {
|
|
|
2169
2169
|
"worker/components.ts": "Reusable UI components other workspace apps can embed.",
|
|
2170
2170
|
"shared/types.ts": "TypeScript interfaces shared between frontend and backend.",
|
|
2171
2171
|
"src/pages/": "Frontend React pages. Each file becomes a route.",
|
|
2172
|
-
"blueprint.json": "App feature registry. Update after adding entities, workflows, agents, etc.",
|
|
2172
|
+
"blueprint.json": "App feature registry. Update after adding entities, workflows, agents, etc., including each one's valueProfile (what manual work it replaced).",
|
|
2173
2173
|
"CLAUDE.md": "Complete framework documentation. Read this before editing anything."
|
|
2174
2174
|
};
|
|
2175
2175
|
COMMON_TIPS = [
|
|
@@ -2180,6 +2180,7 @@ var init_agent_guidance = __esm(() => {
|
|
|
2180
2180
|
"Every workflow MUST have a trigger (API endpoint, scheduled job, or UI button). Workflows without triggers are dead code.",
|
|
2181
2181
|
"Every conversational agent MUST have a frontend page to access it.",
|
|
2182
2182
|
"After adding entities, workflows, or agents, update blueprint.json with the new metadata.",
|
|
2183
|
+
"When you add a feature that takes over manual work, ASK your human what they used to do by hand, how often THEY did it, and how long one round took, then record it as a valueProfile on that blueprint element. Set confirmedWithHuman only if they actually agreed to the numbers. If they cannot answer, omit valueProfile rather than guessing. See the valueProfile section in CLAUDE.md.",
|
|
2183
2184
|
"Never guess integration IDs. Run: runwork integrations search <query>"
|
|
2184
2185
|
];
|
|
2185
2186
|
});
|
|
@@ -7839,7 +7840,7 @@ function createKeyboardListener() {
|
|
|
7839
7840
|
}
|
|
7840
7841
|
|
|
7841
7842
|
// src/generated/version.ts
|
|
7842
|
-
var VERSION = "0.
|
|
7843
|
+
var VERSION = "0.25.0";
|
|
7843
7844
|
|
|
7844
7845
|
// src/commands/dev.ts
|
|
7845
7846
|
var exports_dev = {};
|
|
@@ -9747,6 +9748,187 @@ var init_registry = __esm(() => {
|
|
|
9747
9748
|
});
|
|
9748
9749
|
|
|
9749
9750
|
// src/agents/utils/session-digest.ts
|
|
9751
|
+
function emptyGapBuckets() {
|
|
9752
|
+
return { lt10s: 0, s10to30: 0, s30to2m: 0, m2to5: 0, m5to15: 0, gte15m: 0 };
|
|
9753
|
+
}
|
|
9754
|
+
function gapBucketKey(seconds) {
|
|
9755
|
+
if (seconds < 10)
|
|
9756
|
+
return "lt10s";
|
|
9757
|
+
if (seconds < 30)
|
|
9758
|
+
return "s10to30";
|
|
9759
|
+
if (seconds < 120)
|
|
9760
|
+
return "s30to2m";
|
|
9761
|
+
if (seconds < 300)
|
|
9762
|
+
return "m2to5";
|
|
9763
|
+
if (seconds < 900)
|
|
9764
|
+
return "m5to15";
|
|
9765
|
+
return "gte15m";
|
|
9766
|
+
}
|
|
9767
|
+
function buildGapHistogram(epochMs) {
|
|
9768
|
+
const ts = epochMs.filter((n) => Number.isFinite(n)).sort((a, b) => a - b);
|
|
9769
|
+
if (ts.length < 2)
|
|
9770
|
+
return null;
|
|
9771
|
+
const gapCounts = emptyGapBuckets();
|
|
9772
|
+
const gapSeconds = emptyGapBuckets();
|
|
9773
|
+
let longest = 0;
|
|
9774
|
+
for (let i = 1;i < ts.length; i++) {
|
|
9775
|
+
const gap = (ts[i] - ts[i - 1]) / 1000;
|
|
9776
|
+
const key = gapBucketKey(gap);
|
|
9777
|
+
gapCounts[key]++;
|
|
9778
|
+
gapSeconds[key] += gap;
|
|
9779
|
+
if (gap > longest)
|
|
9780
|
+
longest = gap;
|
|
9781
|
+
}
|
|
9782
|
+
for (const key of Object.keys(gapSeconds)) {
|
|
9783
|
+
gapSeconds[key] = Math.round(gapSeconds[key]);
|
|
9784
|
+
}
|
|
9785
|
+
return {
|
|
9786
|
+
gapCounts,
|
|
9787
|
+
gapSeconds,
|
|
9788
|
+
spanMinutes: Math.round((ts[ts.length - 1] - ts[0]) / 6000) / 10,
|
|
9789
|
+
longestGapSeconds: Math.round(longest)
|
|
9790
|
+
};
|
|
9791
|
+
}
|
|
9792
|
+
function emptySessionSignals() {
|
|
9793
|
+
return {
|
|
9794
|
+
provenance: null,
|
|
9795
|
+
gapCounts: null,
|
|
9796
|
+
gapSeconds: null,
|
|
9797
|
+
spanMinutes: null,
|
|
9798
|
+
longestGapSeconds: null,
|
|
9799
|
+
permissionModes: null,
|
|
9800
|
+
modeEscalations: null,
|
|
9801
|
+
approvalPolicy: null,
|
|
9802
|
+
sandboxPolicy: null,
|
|
9803
|
+
subagentCount: null,
|
|
9804
|
+
planModeUsed: null,
|
|
9805
|
+
slashCommandCount: null,
|
|
9806
|
+
modelInitiatedSkillCount: null,
|
|
9807
|
+
promptLengthBuckets: null,
|
|
9808
|
+
toolFailureStreakMax: null,
|
|
9809
|
+
repeatedCallMax: null,
|
|
9810
|
+
models: null,
|
|
9811
|
+
tokens: null,
|
|
9812
|
+
tokensByModel: null
|
|
9813
|
+
};
|
|
9814
|
+
}
|
|
9815
|
+
function applyGapHistogram(signals, epochMs) {
|
|
9816
|
+
const h = buildGapHistogram(epochMs);
|
|
9817
|
+
if (!h)
|
|
9818
|
+
return;
|
|
9819
|
+
signals.gapCounts = h.gapCounts;
|
|
9820
|
+
signals.gapSeconds = h.gapSeconds;
|
|
9821
|
+
signals.spanMinutes = h.spanMinutes;
|
|
9822
|
+
signals.longestGapSeconds = h.longestGapSeconds;
|
|
9823
|
+
}
|
|
9824
|
+
function promptLengthBucket(len) {
|
|
9825
|
+
if (len < 120)
|
|
9826
|
+
return "short";
|
|
9827
|
+
if (len <= 600)
|
|
9828
|
+
return "medium";
|
|
9829
|
+
return "long";
|
|
9830
|
+
}
|
|
9831
|
+
function classifyClaudeProvenance(obj) {
|
|
9832
|
+
const entrypoint = typeof obj.entrypoint === "string" ? obj.entrypoint : null;
|
|
9833
|
+
const promptSource = typeof obj.promptSource === "string" ? obj.promptSource : null;
|
|
9834
|
+
const originKind = obj.origin?.kind ?? null;
|
|
9835
|
+
if (entrypoint === "sdk-cli" || entrypoint === "sdk-ts")
|
|
9836
|
+
return "headless";
|
|
9837
|
+
if (promptSource === "sdk")
|
|
9838
|
+
return "headless";
|
|
9839
|
+
if (originKind !== null && originKind !== "human")
|
|
9840
|
+
return "agent-spawned";
|
|
9841
|
+
if (entrypoint === "cli" || entrypoint === "claude-desktop" || entrypoint === "local-agent")
|
|
9842
|
+
return "user-driven";
|
|
9843
|
+
if (originKind === "human" || promptSource === "typed")
|
|
9844
|
+
return "user-driven";
|
|
9845
|
+
return null;
|
|
9846
|
+
}
|
|
9847
|
+
function sumTokenTotals(byModel) {
|
|
9848
|
+
let tokensIn = 0;
|
|
9849
|
+
let tokensOut = 0;
|
|
9850
|
+
let cacheReadTokens = null;
|
|
9851
|
+
let cacheCreationTokens = null;
|
|
9852
|
+
for (const t of byModel.values()) {
|
|
9853
|
+
tokensIn += t.tokensIn;
|
|
9854
|
+
tokensOut += t.tokensOut;
|
|
9855
|
+
if (t.cacheReadTokens !== null)
|
|
9856
|
+
cacheReadTokens = (cacheReadTokens ?? 0) + t.cacheReadTokens;
|
|
9857
|
+
if (t.cacheCreationTokens !== null)
|
|
9858
|
+
cacheCreationTokens = (cacheCreationTokens ?? 0) + t.cacheCreationTokens;
|
|
9859
|
+
}
|
|
9860
|
+
return { tokensIn, tokensOut, cacheReadTokens, cacheCreationTokens };
|
|
9861
|
+
}
|
|
9862
|
+
function applyTokenMap(signals, byModel) {
|
|
9863
|
+
if (byModel.size === 0)
|
|
9864
|
+
return;
|
|
9865
|
+
signals.models = [...byModel.keys()];
|
|
9866
|
+
signals.tokens = sumTokenTotals(byModel);
|
|
9867
|
+
signals.tokensByModel = Object.fromEntries(byModel);
|
|
9868
|
+
}
|
|
9869
|
+
function stableStringify(value) {
|
|
9870
|
+
if (Array.isArray(value))
|
|
9871
|
+
return "[" + value.map(stableStringify).join(",") + "]";
|
|
9872
|
+
if (value && typeof value === "object") {
|
|
9873
|
+
return "{" + Object.keys(value).sort().map((k) => JSON.stringify(k) + ":" + stableStringify(value[k])).join(",") + "}";
|
|
9874
|
+
}
|
|
9875
|
+
return JSON.stringify(value) ?? "undefined";
|
|
9876
|
+
}
|
|
9877
|
+
|
|
9878
|
+
class FrictionCounters {
|
|
9879
|
+
failStreak = 0;
|
|
9880
|
+
failStreakMax = 0;
|
|
9881
|
+
callCounts = new Map;
|
|
9882
|
+
repeatedCallMax = 0;
|
|
9883
|
+
result(failed) {
|
|
9884
|
+
if (failed) {
|
|
9885
|
+
this.failStreak++;
|
|
9886
|
+
if (this.failStreak > this.failStreakMax)
|
|
9887
|
+
this.failStreakMax = this.failStreak;
|
|
9888
|
+
} else {
|
|
9889
|
+
this.failStreak = 0;
|
|
9890
|
+
}
|
|
9891
|
+
}
|
|
9892
|
+
call(name, args) {
|
|
9893
|
+
const key = name + "\x00" + stableStringify(args ?? null);
|
|
9894
|
+
const n = (this.callCounts.get(key) ?? 0) + 1;
|
|
9895
|
+
this.callCounts.set(key, n);
|
|
9896
|
+
if (n > this.repeatedCallMax)
|
|
9897
|
+
this.repeatedCallMax = n;
|
|
9898
|
+
}
|
|
9899
|
+
}
|
|
9900
|
+
function recordAssetAttempt(acc, rawName, epochMs, turnsBefore, toolCallsBefore) {
|
|
9901
|
+
const existing = acc.get(rawName);
|
|
9902
|
+
if (existing) {
|
|
9903
|
+
existing.attempts++;
|
|
9904
|
+
return;
|
|
9905
|
+
}
|
|
9906
|
+
acc.set(rawName, { firstEpochMs: epochMs, attempts: 1, turnsBefore, toolCallsBefore });
|
|
9907
|
+
}
|
|
9908
|
+
function buildAssetMarkers(acc, epochMs) {
|
|
9909
|
+
if (acc.size === 0)
|
|
9910
|
+
return;
|
|
9911
|
+
const sorted = epochMs.filter((n) => Number.isFinite(n)).sort((a, b) => a - b);
|
|
9912
|
+
const start = sorted[0];
|
|
9913
|
+
const markers = [];
|
|
9914
|
+
for (const [rawName, m] of acc) {
|
|
9915
|
+
const first = m.firstEpochMs;
|
|
9916
|
+
const histogram = first !== null ? buildGapHistogram(sorted.filter((ms) => ms <= first)) : null;
|
|
9917
|
+
markers.push({
|
|
9918
|
+
kind: "skill",
|
|
9919
|
+
rawName,
|
|
9920
|
+
attempts: m.attempts,
|
|
9921
|
+
turnsBefore: m.turnsBefore,
|
|
9922
|
+
toolCallsBefore: m.toolCallsBefore,
|
|
9923
|
+
wallSecondsToAsset: first !== null && sorted.length > 0 ? Math.round((first - start) / 1000) : null,
|
|
9924
|
+
gapCountsToAsset: histogram?.gapCounts ?? null,
|
|
9925
|
+
gapSecondsToAsset: histogram?.gapSeconds ?? null
|
|
9926
|
+
});
|
|
9927
|
+
if (markers.length >= 8)
|
|
9928
|
+
break;
|
|
9929
|
+
}
|
|
9930
|
+
return markers;
|
|
9931
|
+
}
|
|
9750
9932
|
function extractClaudeJsonlSession(raw, agentSlug, project) {
|
|
9751
9933
|
const digest = {
|
|
9752
9934
|
agentSlug,
|
|
@@ -9760,6 +9942,21 @@ function extractClaudeJsonlSession(raw, agentSlug, project) {
|
|
|
9760
9942
|
errorCount: 0,
|
|
9761
9943
|
assistantTurns: 0
|
|
9762
9944
|
};
|
|
9945
|
+
const signals = emptySessionSignals();
|
|
9946
|
+
const epochMs = [];
|
|
9947
|
+
const modeStream = [];
|
|
9948
|
+
let subagentCount = 0;
|
|
9949
|
+
let planToolSeen = false;
|
|
9950
|
+
let slashCommandCount = 0;
|
|
9951
|
+
let modelInitiatedSkillCount = 0;
|
|
9952
|
+
const promptBuckets = { short: 0, medium: 0, long: 0 };
|
|
9953
|
+
const tokensByModel = new Map;
|
|
9954
|
+
const seenUsagePairs = new Set;
|
|
9955
|
+
let provenanceClassified = false;
|
|
9956
|
+
let sidechainUserSeen = false;
|
|
9957
|
+
let toolCallsSeen = 0;
|
|
9958
|
+
const assetAcc = new Map;
|
|
9959
|
+
const friction = new FrictionCounters;
|
|
9763
9960
|
for (const line of raw.split(`
|
|
9764
9961
|
`)) {
|
|
9765
9962
|
if (!line.trim())
|
|
@@ -9775,12 +9972,29 @@ function extractClaudeJsonlSession(raw, agentSlug, project) {
|
|
|
9775
9972
|
if (!digest.start)
|
|
9776
9973
|
digest.start = ts;
|
|
9777
9974
|
digest.end = ts;
|
|
9975
|
+
const ms = Date.parse(ts);
|
|
9976
|
+
if (Number.isFinite(ms))
|
|
9977
|
+
epochMs.push(ms);
|
|
9778
9978
|
}
|
|
9779
9979
|
const type = obj.type;
|
|
9980
|
+
if (type === "permission-mode" && typeof obj.permissionMode === "string") {
|
|
9981
|
+
modeStream.push(obj.permissionMode);
|
|
9982
|
+
continue;
|
|
9983
|
+
}
|
|
9780
9984
|
const message = obj.message;
|
|
9781
9985
|
if (!message || typeof message !== "object")
|
|
9782
9986
|
continue;
|
|
9783
9987
|
if (type === "user" && message.role === "user") {
|
|
9988
|
+
if (obj.isSidechain === true)
|
|
9989
|
+
sidechainUserSeen = true;
|
|
9990
|
+
else if (!provenanceClassified) {
|
|
9991
|
+
provenanceClassified = true;
|
|
9992
|
+
signals.provenance = classifyClaudeProvenance(obj);
|
|
9993
|
+
}
|
|
9994
|
+
if (typeof obj.permissionMode === "string")
|
|
9995
|
+
modeStream.push(obj.permissionMode);
|
|
9996
|
+
const originKind = obj.origin?.kind;
|
|
9997
|
+
const humanLine = obj.isSidechain !== true && (originKind === undefined || originKind === "human");
|
|
9784
9998
|
const texts = [];
|
|
9785
9999
|
if (typeof message.content === "string") {
|
|
9786
10000
|
texts.push(message.content);
|
|
@@ -9790,15 +10004,22 @@ function extractClaudeJsonlSession(raw, agentSlug, project) {
|
|
|
9790
10004
|
const it = item;
|
|
9791
10005
|
if (it.type === "text" && typeof it.text === "string")
|
|
9792
10006
|
texts.push(it.text);
|
|
9793
|
-
if (it.type === "tool_result"
|
|
9794
|
-
|
|
10007
|
+
if (it.type === "tool_result") {
|
|
10008
|
+
if (it.is_error === true)
|
|
10009
|
+
digest.errorCount++;
|
|
10010
|
+
friction.result(it.is_error === true);
|
|
10011
|
+
}
|
|
9795
10012
|
}
|
|
9796
10013
|
}
|
|
9797
10014
|
}
|
|
9798
10015
|
for (const text2 of texts) {
|
|
9799
10016
|
const trimmed = text2.trim();
|
|
10017
|
+
if (trimmed.startsWith("<command-name>"))
|
|
10018
|
+
slashCommandCount++;
|
|
9800
10019
|
if (!trimmed || SKIP_PREFIXES.some((p) => trimmed.startsWith(p)))
|
|
9801
10020
|
continue;
|
|
10021
|
+
if (humanLine)
|
|
10022
|
+
promptBuckets[promptLengthBucket(trimmed.length)]++;
|
|
9802
10023
|
if (digest.userMessages.length >= MAX_MSGS_PER_SESSION) {
|
|
9803
10024
|
digest.droppedUserMessages++;
|
|
9804
10025
|
continue;
|
|
@@ -9806,15 +10027,47 @@ function extractClaudeJsonlSession(raw, agentSlug, project) {
|
|
|
9806
10027
|
digest.userMessages.push(trimmed.length > MAX_MSG_CHARS ? trimmed.slice(0, MAX_MSG_CHARS) + " [...]" : trimmed);
|
|
9807
10028
|
}
|
|
9808
10029
|
}
|
|
9809
|
-
if (type === "assistant" && message.role === "assistant"
|
|
10030
|
+
if (type === "assistant" && message.role === "assistant") {
|
|
10031
|
+
const am = message;
|
|
10032
|
+
const model = typeof am.model === "string" ? am.model : null;
|
|
10033
|
+
const usage = am.usage;
|
|
10034
|
+
if (usage && typeof usage === "object" && model && model !== "<synthetic>") {
|
|
10035
|
+
const requestId = typeof obj.requestId === "string" ? obj.requestId : null;
|
|
10036
|
+
const pairKey = typeof am.id === "string" && requestId ? `${am.id}:${requestId}` : null;
|
|
10037
|
+
if (!pairKey || !seenUsagePairs.has(pairKey)) {
|
|
10038
|
+
if (pairKey)
|
|
10039
|
+
seenUsagePairs.add(pairKey);
|
|
10040
|
+
const t = tokensByModel.get(model) ?? { tokensIn: 0, tokensOut: 0, cacheReadTokens: 0, cacheCreationTokens: 0 };
|
|
10041
|
+
t.tokensIn += finiteNum(usage.input_tokens);
|
|
10042
|
+
t.tokensOut += finiteNum(usage.output_tokens);
|
|
10043
|
+
t.cacheReadTokens = (t.cacheReadTokens ?? 0) + finiteNum(usage.cache_read_input_tokens);
|
|
10044
|
+
t.cacheCreationTokens = (t.cacheCreationTokens ?? 0) + finiteNum(usage.cache_creation_input_tokens);
|
|
10045
|
+
tokensByModel.set(model, t);
|
|
10046
|
+
}
|
|
10047
|
+
}
|
|
10048
|
+
if (!Array.isArray(message.content))
|
|
10049
|
+
continue;
|
|
9810
10050
|
digest.assistantTurns++;
|
|
9811
10051
|
for (const item of message.content) {
|
|
9812
10052
|
if (item && typeof item === "object") {
|
|
9813
10053
|
const it = item;
|
|
9814
10054
|
if (it.type === "tool_use" && typeof it.name === "string") {
|
|
9815
10055
|
digest.toolCounts[it.name] = (digest.toolCounts[it.name] ?? 0) + 1;
|
|
9816
|
-
|
|
9817
|
-
|
|
10056
|
+
friction.call(it.name, it.input);
|
|
10057
|
+
if (it.name === "Agent" || it.name === "Task")
|
|
10058
|
+
subagentCount++;
|
|
10059
|
+
if (it.name === "EnterPlanMode" || it.name === "ExitPlanMode")
|
|
10060
|
+
planToolSeen = true;
|
|
10061
|
+
if (it.name === "Skill") {
|
|
10062
|
+
modelInitiatedSkillCount++;
|
|
10063
|
+
if (it.input?.skill)
|
|
10064
|
+
digest.skillInvocations.push(it.input.skill);
|
|
10065
|
+
}
|
|
10066
|
+
if (it.name.endsWith("__save_skill") && typeof it.input?.name === "string" && it.input.name.trim()) {
|
|
10067
|
+
const ms = ts ? Date.parse(ts) : NaN;
|
|
10068
|
+
recordAssetAttempt(assetAcc, it.input.name.trim(), Number.isFinite(ms) ? ms : null, Math.max(0, digest.assistantTurns - 1), toolCallsSeen);
|
|
10069
|
+
}
|
|
10070
|
+
toolCallsSeen++;
|
|
9818
10071
|
}
|
|
9819
10072
|
}
|
|
9820
10073
|
}
|
|
@@ -9822,11 +10075,40 @@ function extractClaudeJsonlSession(raw, agentSlug, project) {
|
|
|
9822
10075
|
}
|
|
9823
10076
|
if (digest.userMessages.length === 0)
|
|
9824
10077
|
return null;
|
|
10078
|
+
if (!provenanceClassified && sidechainUserSeen)
|
|
10079
|
+
signals.provenance = "agent-spawned";
|
|
10080
|
+
applyGapHistogram(signals, epochMs);
|
|
10081
|
+
const collapsed = modeStream.filter((m, i) => i === 0 || m !== modeStream[i - 1]);
|
|
10082
|
+
signals.permissionModes = collapsed.length > 0 ? [...new Set(collapsed)] : null;
|
|
10083
|
+
signals.modeEscalations = collapsed.length > 0 ? collapsed.length - 1 : null;
|
|
10084
|
+
signals.subagentCount = subagentCount;
|
|
10085
|
+
signals.planModeUsed = planToolSeen || collapsed.includes("plan");
|
|
10086
|
+
signals.slashCommandCount = slashCommandCount;
|
|
10087
|
+
signals.modelInitiatedSkillCount = modelInitiatedSkillCount;
|
|
10088
|
+
signals.promptLengthBuckets = promptBuckets;
|
|
10089
|
+
signals.toolFailureStreakMax = friction.failStreakMax;
|
|
10090
|
+
signals.repeatedCallMax = friction.repeatedCallMax;
|
|
10091
|
+
applyTokenMap(signals, tokensByModel);
|
|
10092
|
+
digest.signals = signals;
|
|
10093
|
+
digest.assetMarkers = buildAssetMarkers(assetAcc, epochMs);
|
|
9825
10094
|
return digest;
|
|
9826
10095
|
}
|
|
9827
10096
|
function decodeProjectDir(encoded) {
|
|
9828
10097
|
return encoded.replace(/^-/, "/").replace(/-/g, "/");
|
|
9829
10098
|
}
|
|
10099
|
+
function classifyCodexProvenance(meta) {
|
|
10100
|
+
const source = meta.source;
|
|
10101
|
+
const originator = typeof meta.originator === "string" ? meta.originator : null;
|
|
10102
|
+
if (source && typeof source === "object" && "subagent" in source)
|
|
10103
|
+
return "agent-spawned";
|
|
10104
|
+
if (originator === "Claude Code")
|
|
10105
|
+
return "agent-spawned";
|
|
10106
|
+
if (source === "exec")
|
|
10107
|
+
return "headless";
|
|
10108
|
+
if (source === "cli" || source === "vscode")
|
|
10109
|
+
return "user-driven";
|
|
10110
|
+
return null;
|
|
10111
|
+
}
|
|
9830
10112
|
function geminiMessageText(content) {
|
|
9831
10113
|
if (typeof content === "string")
|
|
9832
10114
|
return content;
|
|
@@ -9862,6 +10144,9 @@ function extractGeminiSession(raw, agentSlug, project) {
|
|
|
9862
10144
|
digest.start = session.startTime;
|
|
9863
10145
|
if (typeof session.lastUpdated === "string")
|
|
9864
10146
|
digest.end = session.lastUpdated;
|
|
10147
|
+
const signals = emptySessionSignals();
|
|
10148
|
+
const epochMs = [];
|
|
10149
|
+
const tokensByModel = new Map;
|
|
9865
10150
|
for (const raw2 of session.messages) {
|
|
9866
10151
|
const m = raw2;
|
|
9867
10152
|
if (typeof m.timestamp === "string") {
|
|
@@ -9869,9 +10154,21 @@ function extractGeminiSession(raw, agentSlug, project) {
|
|
|
9869
10154
|
digest.start = m.timestamp;
|
|
9870
10155
|
if (!digest.end || m.timestamp > digest.end)
|
|
9871
10156
|
digest.end = m.timestamp;
|
|
10157
|
+
const ms = Date.parse(m.timestamp);
|
|
10158
|
+
if (Number.isFinite(ms))
|
|
10159
|
+
epochMs.push(ms);
|
|
9872
10160
|
}
|
|
9873
10161
|
if (m.type === "gemini") {
|
|
9874
10162
|
digest.assistantTurns++;
|
|
10163
|
+
const tok = m.tokens;
|
|
10164
|
+
if (tok && typeof tok === "object") {
|
|
10165
|
+
const model = typeof m.model === "string" ? m.model : "unknown";
|
|
10166
|
+
const t = tokensByModel.get(model) ?? { tokensIn: 0, tokensOut: 0, cacheReadTokens: 0, cacheCreationTokens: null };
|
|
10167
|
+
t.tokensIn += finiteNum(tok.input);
|
|
10168
|
+
t.tokensOut += finiteNum(tok.output) + finiteNum(tok.thoughts);
|
|
10169
|
+
t.cacheReadTokens = (t.cacheReadTokens ?? 0) + finiteNum(tok.cached);
|
|
10170
|
+
tokensByModel.set(model, t);
|
|
10171
|
+
}
|
|
9875
10172
|
continue;
|
|
9876
10173
|
}
|
|
9877
10174
|
if (m.type === "error") {
|
|
@@ -9889,7 +10186,12 @@ function extractGeminiSession(raw, agentSlug, project) {
|
|
|
9889
10186
|
}
|
|
9890
10187
|
digest.userMessages.push(text2.length > MAX_MSG_CHARS ? `${text2.slice(0, MAX_MSG_CHARS)}...` : text2);
|
|
9891
10188
|
}
|
|
9892
|
-
|
|
10189
|
+
if (digest.userMessages.length === 0)
|
|
10190
|
+
return null;
|
|
10191
|
+
applyGapHistogram(signals, epochMs);
|
|
10192
|
+
applyTokenMap(signals, tokensByModel);
|
|
10193
|
+
digest.signals = signals;
|
|
10194
|
+
return digest;
|
|
9893
10195
|
}
|
|
9894
10196
|
function extractCodexRolloutSession(raw, agentSlug) {
|
|
9895
10197
|
const digest = {
|
|
@@ -9904,6 +10206,14 @@ function extractCodexRolloutSession(raw, agentSlug) {
|
|
|
9904
10206
|
errorCount: 0,
|
|
9905
10207
|
assistantTurns: 0
|
|
9906
10208
|
};
|
|
10209
|
+
const signals = emptySessionSignals();
|
|
10210
|
+
const epochMs = [];
|
|
10211
|
+
const models = [];
|
|
10212
|
+
let lastTokenTotals = null;
|
|
10213
|
+
let toolCallsSeen = 0;
|
|
10214
|
+
const assetAcc = new Map;
|
|
10215
|
+
const friction = new FrictionCounters;
|
|
10216
|
+
let failureMarkerSeen = false;
|
|
9907
10217
|
for (const line of raw.split(`
|
|
9908
10218
|
`)) {
|
|
9909
10219
|
if (!line.trim())
|
|
@@ -9919,12 +10229,49 @@ function extractCodexRolloutSession(raw, agentSlug) {
|
|
|
9919
10229
|
if (!digest.start)
|
|
9920
10230
|
digest.start = ts;
|
|
9921
10231
|
digest.end = ts;
|
|
10232
|
+
const ms = Date.parse(ts);
|
|
10233
|
+
if (Number.isFinite(ms))
|
|
10234
|
+
epochMs.push(ms);
|
|
9922
10235
|
}
|
|
9923
10236
|
const p = o.payload;
|
|
9924
10237
|
if (!p || typeof p !== "object")
|
|
9925
10238
|
continue;
|
|
9926
|
-
if (o.type === "session_meta"
|
|
9927
|
-
|
|
10239
|
+
if (o.type === "session_meta") {
|
|
10240
|
+
if (typeof p.cwd === "string")
|
|
10241
|
+
digest.project = p.cwd;
|
|
10242
|
+
signals.provenance = classifyCodexProvenance(p);
|
|
10243
|
+
continue;
|
|
10244
|
+
}
|
|
10245
|
+
if (o.type === "turn_context") {
|
|
10246
|
+
const ap = p.approval_policy;
|
|
10247
|
+
if (typeof ap === "string")
|
|
10248
|
+
signals.approvalPolicy = ap;
|
|
10249
|
+
else if (ap && typeof ap === "object")
|
|
10250
|
+
signals.approvalPolicy = Object.keys(ap)[0] ?? signals.approvalPolicy;
|
|
10251
|
+
const sp = p.sandbox_policy;
|
|
10252
|
+
if (sp && typeof sp === "object" && typeof sp.type === "string")
|
|
10253
|
+
signals.sandboxPolicy = sp.type;
|
|
10254
|
+
if (typeof p.model === "string" && !models.includes(p.model))
|
|
10255
|
+
models.push(p.model);
|
|
10256
|
+
continue;
|
|
10257
|
+
}
|
|
10258
|
+
if (o.type === "event_msg") {
|
|
10259
|
+
if (p.type === "token_count") {
|
|
10260
|
+
const info = p.info;
|
|
10261
|
+
if (info && typeof info === "object" && info.total_token_usage && typeof info.total_token_usage === "object") {
|
|
10262
|
+
lastTokenTotals = info.total_token_usage;
|
|
10263
|
+
}
|
|
10264
|
+
} else if (p.type === "exec_command_end") {
|
|
10265
|
+
failureMarkerSeen = true;
|
|
10266
|
+
friction.result(typeof p.exit_code === "number" && p.exit_code !== 0);
|
|
10267
|
+
} else if (p.type === "mcp_tool_call_end") {
|
|
10268
|
+
const result = p.result;
|
|
10269
|
+
failureMarkerSeen = true;
|
|
10270
|
+
friction.result(Boolean(result && typeof result === "object" && "Err" in result));
|
|
10271
|
+
} else if (p.type === "patch_apply_end") {
|
|
10272
|
+
failureMarkerSeen = true;
|
|
10273
|
+
friction.result(p.success === false);
|
|
10274
|
+
}
|
|
9928
10275
|
continue;
|
|
9929
10276
|
}
|
|
9930
10277
|
if (o.type !== "response_item")
|
|
@@ -9950,6 +10297,17 @@ function extractCodexRolloutSession(raw, agentSlug) {
|
|
|
9950
10297
|
}
|
|
9951
10298
|
} else if (pt === "function_call" && typeof p.name === "string") {
|
|
9952
10299
|
digest.toolCounts[p.name] = (digest.toolCounts[p.name] ?? 0) + 1;
|
|
10300
|
+
friction.call(p.name, typeof p.arguments === "string" ? p.arguments : null);
|
|
10301
|
+
if (p.name === "save_skill" && typeof p.arguments === "string") {
|
|
10302
|
+
try {
|
|
10303
|
+
const args = JSON.parse(p.arguments);
|
|
10304
|
+
if (typeof args.name === "string" && args.name.trim()) {
|
|
10305
|
+
const ms = ts ? Date.parse(ts) : NaN;
|
|
10306
|
+
recordAssetAttempt(assetAcc, args.name.trim(), Number.isFinite(ms) ? ms : null, digest.assistantTurns, toolCallsSeen);
|
|
10307
|
+
}
|
|
10308
|
+
} catch {}
|
|
10309
|
+
}
|
|
10310
|
+
toolCallsSeen++;
|
|
9953
10311
|
} else if (pt === "tool_search_call") {
|
|
9954
10312
|
digest.toolCounts["tool_search"] = (digest.toolCounts["tool_search"] ?? 0) + 1;
|
|
9955
10313
|
}
|
|
@@ -9958,6 +10316,21 @@ function extractCodexRolloutSession(raw, agentSlug) {
|
|
|
9958
10316
|
return null;
|
|
9959
10317
|
if (!digest.project)
|
|
9960
10318
|
digest.project = "codex";
|
|
10319
|
+
applyGapHistogram(signals, epochMs);
|
|
10320
|
+
if (models.length > 0)
|
|
10321
|
+
signals.models = models;
|
|
10322
|
+
if (lastTokenTotals) {
|
|
10323
|
+
signals.tokens = {
|
|
10324
|
+
tokensIn: finiteNum(lastTokenTotals.input_tokens),
|
|
10325
|
+
tokensOut: finiteNum(lastTokenTotals.output_tokens),
|
|
10326
|
+
cacheReadTokens: finiteNum(lastTokenTotals.cached_input_tokens),
|
|
10327
|
+
cacheCreationTokens: null
|
|
10328
|
+
};
|
|
10329
|
+
}
|
|
10330
|
+
signals.toolFailureStreakMax = failureMarkerSeen ? friction.failStreakMax : null;
|
|
10331
|
+
signals.repeatedCallMax = friction.repeatedCallMax;
|
|
10332
|
+
digest.signals = signals;
|
|
10333
|
+
digest.assetMarkers = buildAssetMarkers(assetAcc, epochMs);
|
|
9961
10334
|
return digest;
|
|
9962
10335
|
}
|
|
9963
10336
|
function extractCursorSessions(composerRows, bubbleRows, sinceISO) {
|
|
@@ -10006,6 +10379,7 @@ function extractCursorSessions(composerRows, bubbleRows, sinceISO) {
|
|
|
10006
10379
|
errorCount: 0,
|
|
10007
10380
|
assistantTurns: 0
|
|
10008
10381
|
};
|
|
10382
|
+
const friction = new FrictionCounters;
|
|
10009
10383
|
for (const b of bubbles) {
|
|
10010
10384
|
if (b.ts) {
|
|
10011
10385
|
if (!digest.start)
|
|
@@ -10016,6 +10390,7 @@ function extractCursorSessions(composerRows, bubbleRows, sinceISO) {
|
|
|
10016
10390
|
digest.toolCounts[b.tool] = (digest.toolCounts[b.tool] ?? 0) + 1;
|
|
10017
10391
|
if (b.status === "error")
|
|
10018
10392
|
digest.errorCount++;
|
|
10393
|
+
friction.result(b.status === "error");
|
|
10019
10394
|
} else if (b.type === 2) {
|
|
10020
10395
|
digest.assistantTurns++;
|
|
10021
10396
|
}
|
|
@@ -10042,6 +10417,9 @@ function extractCursorSessions(composerRows, bubbleRows, sinceISO) {
|
|
|
10042
10417
|
continue;
|
|
10043
10418
|
if (sinceISO && digest.end && digest.end < sinceISO)
|
|
10044
10419
|
continue;
|
|
10420
|
+
const signals = emptySessionSignals();
|
|
10421
|
+
signals.toolFailureStreakMax = friction.failStreakMax;
|
|
10422
|
+
digest.signals = signals;
|
|
10045
10423
|
digests.push(digest);
|
|
10046
10424
|
}
|
|
10047
10425
|
return digests;
|
|
@@ -10085,7 +10463,7 @@ function formatCombinedDigest(sessions, opts = { days: 7 }) {
|
|
|
10085
10463
|
return lines.join(`
|
|
10086
10464
|
`);
|
|
10087
10465
|
}
|
|
10088
|
-
var MAX_MSG_CHARS = 700, MAX_MSGS_PER_SESSION = 40, SKIP_PREFIXES, CODEX_SKIP_PREFIXES;
|
|
10466
|
+
var MAX_MSG_CHARS = 700, MAX_MSGS_PER_SESSION = 40, finiteNum = (v) => typeof v === "number" && Number.isFinite(v) ? v : 0, SKIP_PREFIXES, CODEX_SKIP_PREFIXES;
|
|
10089
10467
|
var init_session_digest = __esm(() => {
|
|
10090
10468
|
SKIP_PREFIXES = [
|
|
10091
10469
|
"<system-reminder",
|
|
@@ -10424,7 +10802,7 @@ import { createHash as createHash2 } from "crypto";
|
|
|
10424
10802
|
function contentHash(content) {
|
|
10425
10803
|
return "sha256:" + createHash2("sha256").update(content).digest("hex");
|
|
10426
10804
|
}
|
|
10427
|
-
function
|
|
10805
|
+
function stableStringify2(value) {
|
|
10428
10806
|
return JSON.stringify(sortValue(value));
|
|
10429
10807
|
}
|
|
10430
10808
|
function sortValue(value) {
|
|
@@ -10441,7 +10819,7 @@ function sortValue(value) {
|
|
|
10441
10819
|
return value;
|
|
10442
10820
|
}
|
|
10443
10821
|
function configHash(value) {
|
|
10444
|
-
return contentHash(
|
|
10822
|
+
return contentHash(stableStringify2(value));
|
|
10445
10823
|
}
|
|
10446
10824
|
var init_hash = () => {};
|
|
10447
10825
|
|
|
@@ -15855,10 +16233,104 @@ var init_conversation_registry = __esm(async () => {
|
|
|
15855
16233
|
|
|
15856
16234
|
// src/reflect/session-summary.ts
|
|
15857
16235
|
import { createHash as createHash4 } from "crypto";
|
|
16236
|
+
import { execFileSync as execFileSync3 } from "child_process";
|
|
15858
16237
|
import { readFileSync as readFileSync35 } from "fs";
|
|
16238
|
+
import { isAbsolute as isAbsolute4 } from "path";
|
|
16239
|
+
function validateAssetMarkers(markers, knownSkills) {
|
|
16240
|
+
if (!markers || markers.length === 0)
|
|
16241
|
+
return null;
|
|
16242
|
+
let known = null;
|
|
16243
|
+
if (knownSkills) {
|
|
16244
|
+
known = new Map;
|
|
16245
|
+
for (const skill of knownSkills) {
|
|
16246
|
+
const name = canonicalSkillName(skill.name);
|
|
16247
|
+
if (name)
|
|
16248
|
+
known.set(name, skill.id);
|
|
16249
|
+
}
|
|
16250
|
+
}
|
|
16251
|
+
return markers.map((m) => {
|
|
16252
|
+
const canonical = canonicalSkillName(m.rawName);
|
|
16253
|
+
const validated = Boolean(known && canonical && known.has(canonical));
|
|
16254
|
+
return {
|
|
16255
|
+
assetKind: m.kind,
|
|
16256
|
+
assetKey: validated ? canonical : null,
|
|
16257
|
+
assetKeyId: validated ? known.get(canonical) ?? null : null,
|
|
16258
|
+
saveAttempts: m.attempts,
|
|
16259
|
+
turnsToAsset: m.turnsBefore,
|
|
16260
|
+
toolCallsToAsset: m.toolCallsBefore,
|
|
16261
|
+
wallSecondsToAsset: m.wallSecondsToAsset,
|
|
16262
|
+
gapCountsToAsset: m.gapCountsToAsset,
|
|
16263
|
+
gapSecondsToAsset: m.gapSecondsToAsset
|
|
16264
|
+
};
|
|
16265
|
+
});
|
|
16266
|
+
}
|
|
16267
|
+
function clampVendorString(value) {
|
|
16268
|
+
if (value === null)
|
|
16269
|
+
return null;
|
|
16270
|
+
return value.length > VENDOR_STRING_MAX_CHARS ? value.slice(0, VENDOR_STRING_MAX_CHARS) : value;
|
|
16271
|
+
}
|
|
16272
|
+
function clampVendorList(values) {
|
|
16273
|
+
if (values === null)
|
|
16274
|
+
return null;
|
|
16275
|
+
return values.slice(0, VENDOR_LIST_MAX_ITEMS).map((v) => clampVendorString(v));
|
|
16276
|
+
}
|
|
16277
|
+
function clampTokensByModel(map) {
|
|
16278
|
+
if (map === null)
|
|
16279
|
+
return null;
|
|
16280
|
+
const entries = Object.entries(map).slice(0, VENDOR_LIST_MAX_ITEMS).map(([model, totals]) => [clampVendorString(model), totals]);
|
|
16281
|
+
return Object.fromEntries(entries);
|
|
16282
|
+
}
|
|
15859
16283
|
function hashSessionKey(key) {
|
|
15860
16284
|
return createHash4("sha256").update(key).digest("hex").slice(0, 32);
|
|
15861
16285
|
}
|
|
16286
|
+
function normalizeGitRemote(url) {
|
|
16287
|
+
let out = url.trim().toLowerCase();
|
|
16288
|
+
if (!out)
|
|
16289
|
+
return null;
|
|
16290
|
+
const hadScheme = /^[a-z+]+:\/\//.test(out);
|
|
16291
|
+
out = out.replace(/^[a-z+]+:\/\//, "");
|
|
16292
|
+
out = out.replace(/^[^@/]+@/, "");
|
|
16293
|
+
if (hadScheme)
|
|
16294
|
+
out = out.replace(/^([^/:]+):(\d+)(?=\/)/, "$1");
|
|
16295
|
+
out = out.replace(":", "/");
|
|
16296
|
+
out = out.replace(/\/+$/, "");
|
|
16297
|
+
out = out.replace(/\.git$/, "");
|
|
16298
|
+
out = out.replace(/\/+$/, "");
|
|
16299
|
+
return out || null;
|
|
16300
|
+
}
|
|
16301
|
+
function normalizeProjectName(project) {
|
|
16302
|
+
if (!project)
|
|
16303
|
+
return null;
|
|
16304
|
+
const trimmed = project.trim().replace(/[/\\]+$/, "");
|
|
16305
|
+
if (!trimmed)
|
|
16306
|
+
return null;
|
|
16307
|
+
const idx = Math.max(trimmed.lastIndexOf("/"), trimmed.lastIndexOf("\\"));
|
|
16308
|
+
const base = (idx >= 0 ? trimmed.slice(idx + 1) : trimmed).toLowerCase();
|
|
16309
|
+
if (!base)
|
|
16310
|
+
return null;
|
|
16311
|
+
if (idx < 0 && PROJECT_PLACEHOLDERS.has(base))
|
|
16312
|
+
return null;
|
|
16313
|
+
return base;
|
|
16314
|
+
}
|
|
16315
|
+
function resolveProjectHash(project) {
|
|
16316
|
+
const normalized = normalizeProjectName(project);
|
|
16317
|
+
return normalized ? hashSessionKey(normalized) : null;
|
|
16318
|
+
}
|
|
16319
|
+
function resolveRepoHash(projectDir) {
|
|
16320
|
+
if (!projectDir || !isAbsolute4(projectDir))
|
|
16321
|
+
return null;
|
|
16322
|
+
let url;
|
|
16323
|
+
try {
|
|
16324
|
+
url = execFileSync3("git", ["-C", projectDir, "config", "--get", "remote.origin.url"], {
|
|
16325
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
16326
|
+
timeout: 3000
|
|
16327
|
+
}).toString().trim();
|
|
16328
|
+
} catch {
|
|
16329
|
+
return null;
|
|
16330
|
+
}
|
|
16331
|
+
const normalized = url ? normalizeGitRemote(url) : null;
|
|
16332
|
+
return normalized ? hashSessionKey(normalized) : null;
|
|
16333
|
+
}
|
|
15862
16334
|
function digestForEntry(entry) {
|
|
15863
16335
|
if (!entry.transcriptPath)
|
|
15864
16336
|
return null;
|
|
@@ -15892,9 +16364,10 @@ function deriveMcpUsage(toolCounts) {
|
|
|
15892
16364
|
}
|
|
15893
16365
|
return { mcpCallCount, mcpServers };
|
|
15894
16366
|
}
|
|
15895
|
-
function buildSessionSummary(key, entry, digest) {
|
|
16367
|
+
function buildSessionSummary(key, entry, digest, opts = {}) {
|
|
15896
16368
|
const toolCallCount = digest ? Object.values(digest.toolCounts).reduce((sum, n) => sum + n, 0) : null;
|
|
15897
16369
|
const userMessageCount = digest ? digest.userMessages.length + digest.droppedUserMessages : null;
|
|
16370
|
+
const sig = digest?.signals;
|
|
15898
16371
|
return {
|
|
15899
16372
|
agentSlug: entry.agentSlug,
|
|
15900
16373
|
sessionIdHash: hashSessionKey(key),
|
|
@@ -15907,7 +16380,32 @@ function buildSessionSummary(key, entry, digest) {
|
|
|
15907
16380
|
toolCounts: digest ? digest.toolCounts : null,
|
|
15908
16381
|
...deriveMcpUsage(digest ? digest.toolCounts : null),
|
|
15909
16382
|
errorCount: digest?.errorCount ?? null,
|
|
15910
|
-
skillsUsed: [...new Set((digest?.skillInvocations ?? []).map(canonicalSkillName).filter(Boolean))]
|
|
16383
|
+
skillsUsed: clampVendorList([...new Set((digest?.skillInvocations ?? []).map(canonicalSkillName).filter(Boolean))]) ?? [],
|
|
16384
|
+
provenance: sig?.provenance ?? null,
|
|
16385
|
+
gapCounts: sig?.gapCounts ?? null,
|
|
16386
|
+
gapSeconds: sig?.gapSeconds ?? null,
|
|
16387
|
+
spanMinutes: sig?.spanMinutes ?? null,
|
|
16388
|
+
longestGapSeconds: sig?.longestGapSeconds ?? null,
|
|
16389
|
+
permissionModes: clampVendorList(sig?.permissionModes ?? null),
|
|
16390
|
+
modeEscalations: sig?.modeEscalations ?? null,
|
|
16391
|
+
approvalPolicy: clampVendorString(sig?.approvalPolicy ?? null),
|
|
16392
|
+
sandboxPolicy: clampVendorString(sig?.sandboxPolicy ?? null),
|
|
16393
|
+
subagentCount: sig?.subagentCount ?? null,
|
|
16394
|
+
planModeUsed: sig?.planModeUsed ?? null,
|
|
16395
|
+
slashCommandCount: sig?.slashCommandCount ?? null,
|
|
16396
|
+
modelInitiatedSkillCount: sig?.modelInitiatedSkillCount ?? null,
|
|
16397
|
+
promptLengthBuckets: sig?.promptLengthBuckets ?? null,
|
|
16398
|
+
toolFailureStreakMax: sig?.toolFailureStreakMax ?? null,
|
|
16399
|
+
repeatedCallMax: sig?.repeatedCallMax ?? null,
|
|
16400
|
+
models: clampVendorList(sig?.models ?? null),
|
|
16401
|
+
tokensIn: sig?.tokens?.tokensIn ?? null,
|
|
16402
|
+
tokensOut: sig?.tokens?.tokensOut ?? null,
|
|
16403
|
+
cacheReadTokens: sig?.tokens?.cacheReadTokens ?? null,
|
|
16404
|
+
cacheCreationTokens: sig?.tokens?.cacheCreationTokens ?? null,
|
|
16405
|
+
tokensByModel: clampTokensByModel(sig?.tokensByModel ?? null),
|
|
16406
|
+
assetsCreated: validateAssetMarkers(digest?.assetMarkers, opts.knownSkills ?? null),
|
|
16407
|
+
repoHash: opts.repoHash ?? null,
|
|
16408
|
+
projectHash: resolveProjectHash(entry.project)
|
|
15911
16409
|
};
|
|
15912
16410
|
}
|
|
15913
16411
|
function buildSessionSummaryEvent(summary, nowISO) {
|
|
@@ -15928,14 +16426,41 @@ function buildSessionSummaryEvent(summary, nowISO) {
|
|
|
15928
16426
|
mcpCallCount: summary.mcpCallCount,
|
|
15929
16427
|
mcpServers: summary.mcpServers,
|
|
15930
16428
|
errorCount: summary.errorCount,
|
|
15931
|
-
skillsUsed: summary.skillsUsed
|
|
16429
|
+
skillsUsed: summary.skillsUsed,
|
|
16430
|
+
provenance: summary.provenance,
|
|
16431
|
+
gapCounts: summary.gapCounts,
|
|
16432
|
+
gapSeconds: summary.gapSeconds,
|
|
16433
|
+
spanMinutes: summary.spanMinutes,
|
|
16434
|
+
longestGapSeconds: summary.longestGapSeconds,
|
|
16435
|
+
permissionModes: summary.permissionModes,
|
|
16436
|
+
modeEscalations: summary.modeEscalations,
|
|
16437
|
+
approvalPolicy: summary.approvalPolicy,
|
|
16438
|
+
sandboxPolicy: summary.sandboxPolicy,
|
|
16439
|
+
subagentCount: summary.subagentCount,
|
|
16440
|
+
planModeUsed: summary.planModeUsed,
|
|
16441
|
+
slashCommandCount: summary.slashCommandCount,
|
|
16442
|
+
modelInitiatedSkillCount: summary.modelInitiatedSkillCount,
|
|
16443
|
+
promptLengthBuckets: summary.promptLengthBuckets,
|
|
16444
|
+
toolFailureStreakMax: summary.toolFailureStreakMax,
|
|
16445
|
+
repeatedCallMax: summary.repeatedCallMax,
|
|
16446
|
+
models: summary.models,
|
|
16447
|
+
tokensIn: summary.tokensIn,
|
|
16448
|
+
tokensOut: summary.tokensOut,
|
|
16449
|
+
cacheReadTokens: summary.cacheReadTokens,
|
|
16450
|
+
cacheCreationTokens: summary.cacheCreationTokens,
|
|
16451
|
+
tokensByModel: summary.tokensByModel,
|
|
16452
|
+
assetsCreated: summary.assetsCreated,
|
|
16453
|
+
repoHash: summary.repoHash,
|
|
16454
|
+
projectHash: summary.projectHash
|
|
15932
16455
|
},
|
|
15933
16456
|
timestamp: nowISO
|
|
15934
16457
|
}
|
|
15935
16458
|
};
|
|
15936
16459
|
}
|
|
16460
|
+
var VENDOR_STRING_MAX_CHARS = 64, VENDOR_LIST_MAX_ITEMS = 12, PROJECT_PLACEHOLDERS;
|
|
15937
16461
|
var init_session_summary = __esm(() => {
|
|
15938
16462
|
init_session_digest();
|
|
16463
|
+
PROJECT_PLACEHOLDERS = new Set(["codex", "cowork", "cursor"]);
|
|
15939
16464
|
});
|
|
15940
16465
|
|
|
15941
16466
|
// src/reflect/telemetry-outbox.ts
|
|
@@ -15977,6 +16502,222 @@ var init_telemetry_outbox = __esm(() => {
|
|
|
15977
16502
|
init_atomic_json();
|
|
15978
16503
|
});
|
|
15979
16504
|
|
|
16505
|
+
// src/reflect/active-time.ts
|
|
16506
|
+
function isValidActiveTimeCap(capSeconds) {
|
|
16507
|
+
return VALID_ACTIVE_TIME_CAPS_SECONDS.includes(capSeconds);
|
|
16508
|
+
}
|
|
16509
|
+
function activeSecondsFromBuckets(gapCounts, gapSeconds, capSeconds) {
|
|
16510
|
+
if (!gapCounts || !gapSeconds)
|
|
16511
|
+
return null;
|
|
16512
|
+
if (!isValidActiveTimeCap(capSeconds))
|
|
16513
|
+
return null;
|
|
16514
|
+
let total = 0;
|
|
16515
|
+
for (const { key, lower } of BUCKET_LOWER_EDGES) {
|
|
16516
|
+
if (lower >= capSeconds) {
|
|
16517
|
+
total += capSeconds * (gapCounts[key] ?? 0);
|
|
16518
|
+
} else {
|
|
16519
|
+
total += gapSeconds[key] ?? 0;
|
|
16520
|
+
}
|
|
16521
|
+
}
|
|
16522
|
+
return total;
|
|
16523
|
+
}
|
|
16524
|
+
var BUCKET_LOWER_EDGES, VALID_ACTIVE_TIME_CAPS_SECONDS;
|
|
16525
|
+
var init_active_time = __esm(() => {
|
|
16526
|
+
BUCKET_LOWER_EDGES = [
|
|
16527
|
+
{ key: "lt10s", lower: 0 },
|
|
16528
|
+
{ key: "s10to30", lower: 10 },
|
|
16529
|
+
{ key: "s30to2m", lower: 30 },
|
|
16530
|
+
{ key: "m2to5", lower: 120 },
|
|
16531
|
+
{ key: "m5to15", lower: 300 },
|
|
16532
|
+
{ key: "gte15m", lower: 900 }
|
|
16533
|
+
];
|
|
16534
|
+
VALID_ACTIVE_TIME_CAPS_SECONDS = [10, 30, 120, 300, 900];
|
|
16535
|
+
});
|
|
16536
|
+
|
|
16537
|
+
// src/reflect/pattern-store.ts
|
|
16538
|
+
import { createHash as createHash5 } from "crypto";
|
|
16539
|
+
import { join as join42 } from "path";
|
|
16540
|
+
import { homedir as homedir23 } from "os";
|
|
16541
|
+
function addBuckets(a, b) {
|
|
16542
|
+
if (!a)
|
|
16543
|
+
return b ? { ...b } : null;
|
|
16544
|
+
if (!b)
|
|
16545
|
+
return { ...a };
|
|
16546
|
+
return {
|
|
16547
|
+
lt10s: a.lt10s + b.lt10s,
|
|
16548
|
+
s10to30: a.s10to30 + b.s10to30,
|
|
16549
|
+
s30to2m: a.s30to2m + b.s30to2m,
|
|
16550
|
+
m2to5: a.m2to5 + b.m2to5,
|
|
16551
|
+
m5to15: a.m5to15 + b.m5to15,
|
|
16552
|
+
gte15m: a.gte15m + b.gte15m
|
|
16553
|
+
};
|
|
16554
|
+
}
|
|
16555
|
+
function canonicalizeEntities(entities) {
|
|
16556
|
+
const cleaned = entities.map((e) => e.trim().toLowerCase().replace(/\s+/g, " ")).filter((e) => e.length > 0);
|
|
16557
|
+
return [...new Set(cleaned)].sort().slice(0, MAX_KEY_ENTITIES);
|
|
16558
|
+
}
|
|
16559
|
+
function patternKeyHash(taskType, entities) {
|
|
16560
|
+
const task = taskType.trim().toLowerCase();
|
|
16561
|
+
const canonical = canonicalizeEntities(entities);
|
|
16562
|
+
if (!task || canonical.length === 0)
|
|
16563
|
+
return null;
|
|
16564
|
+
return createHash5("sha256").update(`${task}|${canonical.join(",")}`).digest("hex").slice(0, 32);
|
|
16565
|
+
}
|
|
16566
|
+
function median(sorted) {
|
|
16567
|
+
if (sorted.length === 0)
|
|
16568
|
+
return 0;
|
|
16569
|
+
const mid = Math.floor(sorted.length / 2);
|
|
16570
|
+
return sorted.length % 2 === 0 ? (sorted[mid - 1] + sorted[mid]) / 2 : sorted[mid];
|
|
16571
|
+
}
|
|
16572
|
+
function medianOf(values) {
|
|
16573
|
+
return median([...values].sort((a, b) => a - b));
|
|
16574
|
+
}
|
|
16575
|
+
function modal(values) {
|
|
16576
|
+
const counts = new Map;
|
|
16577
|
+
for (const v of values)
|
|
16578
|
+
counts.set(v, (counts.get(v) ?? 0) + 1);
|
|
16579
|
+
let best = null;
|
|
16580
|
+
let bestCount = 0;
|
|
16581
|
+
for (const [value, count] of counts) {
|
|
16582
|
+
if (count > bestCount) {
|
|
16583
|
+
best = value;
|
|
16584
|
+
bestCount = count;
|
|
16585
|
+
}
|
|
16586
|
+
}
|
|
16587
|
+
return best;
|
|
16588
|
+
}
|
|
16589
|
+
function emptyPatternStore() {
|
|
16590
|
+
return { version: PATTERN_STORE_VERSION, patterns: {} };
|
|
16591
|
+
}
|
|
16592
|
+
function recordObservation(state, observation) {
|
|
16593
|
+
const key = patternKeyHash(observation.taskType, observation.keyEntities);
|
|
16594
|
+
if (!key)
|
|
16595
|
+
return { state, key: null };
|
|
16596
|
+
const at = Date.parse(observation.at);
|
|
16597
|
+
if (!Number.isFinite(at))
|
|
16598
|
+
return { state, key: null };
|
|
16599
|
+
const activeSeconds = observation.gapCounts && observation.gapSeconds ? activeSecondsFromBuckets(observation.gapCounts, observation.gapSeconds, ACTIVE_MINUTES_CAP_SECONDS) : null;
|
|
16600
|
+
const existing = state.patterns[key];
|
|
16601
|
+
const occurrences = existing ? [...existing.occurrences] : [];
|
|
16602
|
+
const mergeIndex = occurrences.findIndex((o) => Math.abs(Date.parse(o.at) - at) <= SAME_OCCURRENCE_WINDOW_HOURS * 3600 * 1000);
|
|
16603
|
+
if (mergeIndex >= 0) {
|
|
16604
|
+
const prior = occurrences[mergeIndex];
|
|
16605
|
+
const priorAt = Date.parse(prior.at);
|
|
16606
|
+
occurrences[mergeIndex] = {
|
|
16607
|
+
at: priorAt <= at ? prior.at : observation.at,
|
|
16608
|
+
activeSeconds: prior.activeSeconds === null && activeSeconds === null ? null : (prior.activeSeconds ?? 0) + (activeSeconds ?? 0),
|
|
16609
|
+
gapCounts: addBuckets(prior.gapCounts, observation.gapCounts),
|
|
16610
|
+
gapSeconds: addBuckets(prior.gapSeconds, observation.gapSeconds),
|
|
16611
|
+
confidence: Math.min(prior.confidence, observation.confidence),
|
|
16612
|
+
sessionIdHashes: observation.sessionIdHash && !prior.sessionIdHashes.includes(observation.sessionIdHash) ? [...prior.sessionIdHashes, observation.sessionIdHash] : prior.sessionIdHashes,
|
|
16613
|
+
suggestedCapability: prior.suggestedCapability ?? observation.suggestedCapability ?? null,
|
|
16614
|
+
projectHash: prior.projectHash ?? observation.projectHash ?? null
|
|
16615
|
+
};
|
|
16616
|
+
} else {
|
|
16617
|
+
occurrences.push({
|
|
16618
|
+
at: observation.at,
|
|
16619
|
+
activeSeconds,
|
|
16620
|
+
gapCounts: observation.gapCounts ?? null,
|
|
16621
|
+
gapSeconds: observation.gapSeconds ?? null,
|
|
16622
|
+
confidence: observation.confidence,
|
|
16623
|
+
sessionIdHashes: observation.sessionIdHash ? [observation.sessionIdHash] : [],
|
|
16624
|
+
suggestedCapability: observation.suggestedCapability ?? null,
|
|
16625
|
+
projectHash: observation.projectHash ?? null
|
|
16626
|
+
});
|
|
16627
|
+
}
|
|
16628
|
+
occurrences.sort((a, b) => Date.parse(a.at) - Date.parse(b.at));
|
|
16629
|
+
const newest = Date.parse(occurrences[occurrences.length - 1].at);
|
|
16630
|
+
const kept = occurrences.filter((o) => newest - Date.parse(o.at) <= OCCURRENCE_RETENTION_DAYS * 24 * 3600 * 1000);
|
|
16631
|
+
return {
|
|
16632
|
+
state: {
|
|
16633
|
+
...state,
|
|
16634
|
+
patterns: {
|
|
16635
|
+
...state.patterns,
|
|
16636
|
+
[key]: {
|
|
16637
|
+
taskType: observation.taskType.trim().toLowerCase(),
|
|
16638
|
+
keyEntities: canonicalizeEntities(observation.keyEntities),
|
|
16639
|
+
occurrences: kept
|
|
16640
|
+
}
|
|
16641
|
+
}
|
|
16642
|
+
},
|
|
16643
|
+
key
|
|
16644
|
+
};
|
|
16645
|
+
}
|
|
16646
|
+
function evaluateMaturity(key, record) {
|
|
16647
|
+
const sorted = [...record.occurrences].sort((a, b) => Date.parse(a.at) - Date.parse(b.at));
|
|
16648
|
+
if (sorted.length < MATURITY_MIN_OCCURRENCES)
|
|
16649
|
+
return null;
|
|
16650
|
+
const newest = Date.parse(sorted[sorted.length - 1].at);
|
|
16651
|
+
const inWindow = sorted.filter((o) => newest - Date.parse(o.at) <= MATURITY_WINDOW_DAYS * 24 * 3600 * 1000);
|
|
16652
|
+
if (inWindow.length < MATURITY_MIN_OCCURRENCES)
|
|
16653
|
+
return null;
|
|
16654
|
+
const gapsDays = [];
|
|
16655
|
+
for (let i = 1;i < inWindow.length; i++) {
|
|
16656
|
+
gapsDays.push((Date.parse(inWindow[i].at) - Date.parse(inWindow[i - 1].at)) / (24 * 3600 * 1000));
|
|
16657
|
+
}
|
|
16658
|
+
const medianGap = medianOf(gapsDays);
|
|
16659
|
+
if (medianGap < MATURITY_MIN_PERIODICITY_DAYS)
|
|
16660
|
+
return null;
|
|
16661
|
+
const mad = medianOf(gapsDays.map((g) => Math.abs(g - medianGap)));
|
|
16662
|
+
if (mad > MATURITY_MAX_MAD_RATIO * medianGap)
|
|
16663
|
+
return null;
|
|
16664
|
+
const activeSecondsValues = inWindow.map((o) => o.activeSeconds).filter((s) => typeof s === "number");
|
|
16665
|
+
let activeGapCounts = null;
|
|
16666
|
+
let activeGapSeconds = null;
|
|
16667
|
+
for (const occurrence of inWindow) {
|
|
16668
|
+
activeGapCounts = addBuckets(activeGapCounts, occurrence.gapCounts);
|
|
16669
|
+
activeGapSeconds = addBuckets(activeGapSeconds, occurrence.gapSeconds);
|
|
16670
|
+
}
|
|
16671
|
+
const projectHashes = inWindow.map((o) => o.projectHash).filter((p) => typeof p === "string" && p.length > 0);
|
|
16672
|
+
const capabilities = inWindow.map((o) => o.suggestedCapability).filter((c) => typeof c === "string" && c.length > 0);
|
|
16673
|
+
return {
|
|
16674
|
+
patternKeyHash: key,
|
|
16675
|
+
taskType: record.taskType,
|
|
16676
|
+
occurrences: inWindow.length,
|
|
16677
|
+
periodicityDays: Math.round(medianGap * 10) / 10,
|
|
16678
|
+
medianActiveMinutes: activeSecondsValues.length > 0 ? Math.round(medianOf(activeSecondsValues) / 60 * 10) / 10 : null,
|
|
16679
|
+
activeMinutesCapSeconds: ACTIVE_MINUTES_CAP_SECONDS,
|
|
16680
|
+
activeGapCounts,
|
|
16681
|
+
activeGapSeconds,
|
|
16682
|
+
suggestedCapability: modal(capabilities),
|
|
16683
|
+
firstSeenAt: inWindow[0].at,
|
|
16684
|
+
lastSeenAt: inWindow[inWindow.length - 1].at,
|
|
16685
|
+
confidence: Math.min(...inWindow.map((o) => o.confidence)),
|
|
16686
|
+
projectHash: modal(projectHashes),
|
|
16687
|
+
distinctProjectHashes: new Set(projectHashes).size
|
|
16688
|
+
};
|
|
16689
|
+
}
|
|
16690
|
+
function buildPatternEvent(payload, nowISO) {
|
|
16691
|
+
return {
|
|
16692
|
+
dedupeKey: `pattern:${payload.patternKeyHash}`,
|
|
16693
|
+
event: {
|
|
16694
|
+
eventType: "local_agent.repeated_pattern",
|
|
16695
|
+
metadata: { ...payload },
|
|
16696
|
+
timestamp: nowISO
|
|
16697
|
+
}
|
|
16698
|
+
};
|
|
16699
|
+
}
|
|
16700
|
+
function storePath3() {
|
|
16701
|
+
return join42(homedir23(), ".runwork", "pattern-store.json");
|
|
16702
|
+
}
|
|
16703
|
+
function loadPatternStore() {
|
|
16704
|
+
const parsed = readJsonOrNull(storePath3());
|
|
16705
|
+
if (!parsed || typeof parsed !== "object" || !parsed.patterns || typeof parsed.patterns !== "object") {
|
|
16706
|
+
return emptyPatternStore();
|
|
16707
|
+
}
|
|
16708
|
+
if (parsed.version !== PATTERN_STORE_VERSION)
|
|
16709
|
+
return emptyPatternStore();
|
|
16710
|
+
return parsed;
|
|
16711
|
+
}
|
|
16712
|
+
function savePatternStore(state) {
|
|
16713
|
+
writeJsonAtomic(storePath3(), state);
|
|
16714
|
+
}
|
|
16715
|
+
var SAME_OCCURRENCE_WINDOW_HOURS = 4, MATURITY_MIN_OCCURRENCES = 3, MATURITY_WINDOW_DAYS = 45, MATURITY_MAX_MAD_RATIO = 0.5, MATURITY_MIN_PERIODICITY_DAYS = 1, OCCURRENCE_RETENTION_DAYS = 180, MAX_KEY_ENTITIES = 4, ACTIVE_MINUTES_CAP_SECONDS = 300, PATTERN_STORE_VERSION = 1;
|
|
16716
|
+
var init_pattern_store = __esm(() => {
|
|
16717
|
+
init_atomic_json();
|
|
16718
|
+
init_active_time();
|
|
16719
|
+
});
|
|
16720
|
+
|
|
15980
16721
|
// src/reflect/triage.ts
|
|
15981
16722
|
var exports_triage = {};
|
|
15982
16723
|
__export(exports_triage, {
|
|
@@ -15987,7 +16728,8 @@ __export(exports_triage, {
|
|
|
15987
16728
|
packTriageBatches: () => packTriageBatches,
|
|
15988
16729
|
buildTriagePrompt: () => buildTriagePrompt,
|
|
15989
16730
|
TRIAGE_ITEM_CHAR_CAP: () => TRIAGE_ITEM_CHAR_CAP,
|
|
15990
|
-
TRIAGE_BATCH_CHAR_BUDGET: () => TRIAGE_BATCH_CHAR_BUDGET
|
|
16731
|
+
TRIAGE_BATCH_CHAR_BUDGET: () => TRIAGE_BATCH_CHAR_BUDGET,
|
|
16732
|
+
TASK_TYPES: () => TASK_TYPES
|
|
15991
16733
|
});
|
|
15992
16734
|
function renderTriageItem(id, candidate) {
|
|
15993
16735
|
const d = candidate.digest;
|
|
@@ -16036,12 +16778,17 @@ You are the cheap triage tier of Runwork's reflection engine. At the end of this
|
|
|
16036
16778
|
|
|
16037
16779
|
Mark a conversation NOT worthy when it is trivial or one-shot, is routine work with no repeated pattern, or would only re-derive something in the already-suggested list. Most conversations are not worthy; be strict. Do not analyze content deeply — skim and judge.
|
|
16038
16780
|
|
|
16781
|
+
For EACH conversation also label the work itself, which is used to notice the same chore recurring across conversations:
|
|
16782
|
+
|
|
16783
|
+
- "taskType": exactly one of ${TASK_TYPES.join(" | ")}.
|
|
16784
|
+
- "keyEntities": 1 to 4 short CANONICAL noun phrases naming the OBJECT of the work ("invoice reformatting", "weekly sales report", "hubspot contact cleanup"). Canonical means: singular, lowercase, generic. NO dates, file names, person names, company names, counts, or version numbers. Two conversations doing the same chore in different words MUST produce the same phrases, so prefer the plainest wording of the underlying task over the user's phrasing.
|
|
16785
|
+
|
|
16039
16786
|
Reply with ONLY a single fenced \`json\` block:
|
|
16040
16787
|
|
|
16041
16788
|
\`\`\`json
|
|
16042
16789
|
{
|
|
16043
16790
|
"verdicts": [
|
|
16044
|
-
{ "id": "<the conversation id shown in its header>", "worthy": true|false, "confidence": <0..1> }
|
|
16791
|
+
{ "id": "<the conversation id shown in its header>", "worthy": true|false, "confidence": <0..1>, "taskType": "<one of the values above>", "keyEntities": ["<canonical phrase>"] }
|
|
16045
16792
|
]
|
|
16046
16793
|
}
|
|
16047
16794
|
\`\`\`
|
|
@@ -16071,9 +16818,13 @@ function parseTriageVerdicts(text2) {
|
|
|
16071
16818
|
const v = raw;
|
|
16072
16819
|
if (typeof v.id !== "string" || typeof v.worthy !== "boolean")
|
|
16073
16820
|
continue;
|
|
16821
|
+
const taskType = typeof v.taskType === "string" && TASK_TYPES.includes(v.taskType.trim().toLowerCase()) ? v.taskType.trim().toLowerCase() : undefined;
|
|
16822
|
+
const keyEntities = Array.isArray(v.keyEntities) ? v.keyEntities.filter((e) => typeof e === "string" && e.trim().length > 0).map((e) => e.trim()) : [];
|
|
16074
16823
|
out.set(v.id, {
|
|
16075
16824
|
worthy: v.worthy,
|
|
16076
|
-
confidence: typeof v.confidence === "number" ? Math.max(0, Math.min(1, v.confidence)) : 0.5
|
|
16825
|
+
confidence: typeof v.confidence === "number" ? Math.max(0, Math.min(1, v.confidence)) : 0.5,
|
|
16826
|
+
...taskType ? { taskType } : {},
|
|
16827
|
+
...keyEntities.length > 0 ? { keyEntities } : {}
|
|
16077
16828
|
});
|
|
16078
16829
|
}
|
|
16079
16830
|
return out.size > 0 ? out : null;
|
|
@@ -16144,7 +16895,7 @@ async function runTriagePass(candidates, opts) {
|
|
|
16144
16895
|
}
|
|
16145
16896
|
return { verdicts, summary };
|
|
16146
16897
|
}
|
|
16147
|
-
var TRIAGE_ITEM_CHAR_CAP = 12000, TRIAGE_BATCH_CHAR_BUDGET = 150000, TRIAGE_TIMEOUT_MS;
|
|
16898
|
+
var TRIAGE_ITEM_CHAR_CAP = 12000, TRIAGE_BATCH_CHAR_BUDGET = 150000, TRIAGE_TIMEOUT_MS, TASK_TYPES;
|
|
16148
16899
|
var init_triage = __esm(async () => {
|
|
16149
16900
|
init_which();
|
|
16150
16901
|
init_session_listing();
|
|
@@ -16153,6 +16904,7 @@ var init_triage = __esm(async () => {
|
|
|
16153
16904
|
init_conversation_analysis()
|
|
16154
16905
|
]);
|
|
16155
16906
|
TRIAGE_TIMEOUT_MS = 2 * 60 * 1000;
|
|
16907
|
+
TASK_TYPES = ["build", "automate", "analyze", "write", "research", "admin", "debug"];
|
|
16156
16908
|
});
|
|
16157
16909
|
|
|
16158
16910
|
// src/reflect/conversation-analysis.ts
|
|
@@ -16317,6 +17069,48 @@ async function analyzeConversations(opts = {}) {
|
|
|
16317
17069
|
releaseRunLock();
|
|
16318
17070
|
}
|
|
16319
17071
|
}
|
|
17072
|
+
function recordPatternObservations(verdicts, state, progress) {
|
|
17073
|
+
try {
|
|
17074
|
+
let store = loadPatternStore();
|
|
17075
|
+
const touchedKeys = new Set;
|
|
17076
|
+
for (const [key, verdict] of verdicts) {
|
|
17077
|
+
const entry = state.entries[key];
|
|
17078
|
+
if (!entry || !verdict.taskType || !verdict.keyEntities?.length)
|
|
17079
|
+
continue;
|
|
17080
|
+
const stats = entry.stats;
|
|
17081
|
+
const result = recordObservation(store, {
|
|
17082
|
+
taskType: verdict.taskType,
|
|
17083
|
+
keyEntities: verdict.keyEntities,
|
|
17084
|
+
at: entry.lastActivityAt,
|
|
17085
|
+
confidence: verdict.confidence,
|
|
17086
|
+
sessionIdHash: stats?.sessionIdHash,
|
|
17087
|
+
gapCounts: stats?.gapCounts ?? null,
|
|
17088
|
+
gapSeconds: stats?.gapSeconds ?? null,
|
|
17089
|
+
projectHash: stats?.projectHash ?? null
|
|
17090
|
+
});
|
|
17091
|
+
store = result.state;
|
|
17092
|
+
if (result.key)
|
|
17093
|
+
touchedKeys.add(result.key);
|
|
17094
|
+
}
|
|
17095
|
+
if (touchedKeys.size === 0)
|
|
17096
|
+
return;
|
|
17097
|
+
savePatternStore(store);
|
|
17098
|
+
const nowISO = new Date().toISOString();
|
|
17099
|
+
const events = [];
|
|
17100
|
+
for (const key of touchedKeys) {
|
|
17101
|
+
const record = store.patterns[key];
|
|
17102
|
+
if (!record)
|
|
17103
|
+
continue;
|
|
17104
|
+
const payload = evaluateMaturity(key, record);
|
|
17105
|
+
if (payload)
|
|
17106
|
+
events.push(buildPatternEvent(payload, nowISO));
|
|
17107
|
+
}
|
|
17108
|
+
if (events.length > 0) {
|
|
17109
|
+
appendToTelemetryOutbox(events);
|
|
17110
|
+
progress(`${events.length} recurring chore pattern(s) reached the recurrence threshold.`);
|
|
17111
|
+
}
|
|
17112
|
+
} catch {}
|
|
17113
|
+
}
|
|
16320
17114
|
async function analyzeConversationsLocked(opts, outcome, gates) {
|
|
16321
17115
|
const { cadence, explicitSession, manual } = gates;
|
|
16322
17116
|
if (!cadence.enabled && !manual && !explicitSession) {
|
|
@@ -16453,9 +17247,11 @@ async function analyzeConversationsLocked(opts, outcome, gates) {
|
|
|
16453
17247
|
for (const [key, verdict] of pass.verdicts) {
|
|
16454
17248
|
const entry = state.entries[key];
|
|
16455
17249
|
if (entry) {
|
|
16456
|
-
|
|
17250
|
+
const { keyEntities: _entities, ...cacheable } = verdict;
|
|
17251
|
+
state = { entries: { ...state.entries, [key]: { ...entry, triage: { ...cacheable, at: entry.lastActivityAt } } } };
|
|
16457
17252
|
}
|
|
16458
17253
|
}
|
|
17254
|
+
recordPatternObservations(pass.verdicts, state, progress);
|
|
16459
17255
|
}
|
|
16460
17256
|
}
|
|
16461
17257
|
const ranked = [];
|
|
@@ -16675,6 +17471,7 @@ var init_conversation_analysis = __esm(async () => {
|
|
|
16675
17471
|
init_session_listing();
|
|
16676
17472
|
init_insight_store();
|
|
16677
17473
|
init_telemetry_outbox();
|
|
17474
|
+
init_pattern_store();
|
|
16678
17475
|
init_run_log();
|
|
16679
17476
|
init_insight_store();
|
|
16680
17477
|
await __promiseAll([
|
|
@@ -19171,14 +19968,18 @@ init_session_summary();
|
|
|
19171
19968
|
init_telemetry_outbox();
|
|
19172
19969
|
init_insight_store();
|
|
19173
19970
|
init_atomic_json();
|
|
19971
|
+
init_store();
|
|
19972
|
+
init_client();
|
|
19973
|
+
init_resolve();
|
|
19974
|
+
init_workspace_state();
|
|
19174
19975
|
init_colors();
|
|
19175
19976
|
await __promiseAll([
|
|
19176
19977
|
init_conversation_registry(),
|
|
19177
19978
|
init_conversation_analysis()
|
|
19178
19979
|
]);
|
|
19179
19980
|
import { Command as Command15 } from "commander";
|
|
19180
|
-
import { join as
|
|
19181
|
-
import { homedir as
|
|
19981
|
+
import { join as join43 } from "path";
|
|
19982
|
+
import { homedir as homedir24 } from "os";
|
|
19182
19983
|
var DEFAULT_LOOKBACK_DAYS = 30;
|
|
19183
19984
|
function sinceFromDays(days) {
|
|
19184
19985
|
if (days <= 0)
|
|
@@ -19191,6 +19992,46 @@ function parseDays(raw) {
|
|
|
19191
19992
|
return DEFAULT_LOOKBACK_DAYS;
|
|
19192
19993
|
return Math.floor(n);
|
|
19193
19994
|
}
|
|
19995
|
+
function bareRegistrySkillId(skill) {
|
|
19996
|
+
if (skill.type !== "external" || typeof skill.id !== "string")
|
|
19997
|
+
return null;
|
|
19998
|
+
return skill.id.startsWith("ext-") ? skill.id.slice("ext-".length) || null : null;
|
|
19999
|
+
}
|
|
20000
|
+
async function fetchKnownSkills() {
|
|
20001
|
+
const creds = getCredentials();
|
|
20002
|
+
if (!creds)
|
|
20003
|
+
return null;
|
|
20004
|
+
let workspaceId;
|
|
20005
|
+
const client = new ApiClient(creds);
|
|
20006
|
+
try {
|
|
20007
|
+
workspaceId = (await resolveWorkspace2(client, {})).workspaceId;
|
|
20008
|
+
} catch {
|
|
20009
|
+
workspaceId = creds.defaultWorkspaceId;
|
|
20010
|
+
}
|
|
20011
|
+
if (!workspaceId)
|
|
20012
|
+
return null;
|
|
20013
|
+
try {
|
|
20014
|
+
const skills = await client.listWorkspaceSkills(workspaceId);
|
|
20015
|
+
const known = [];
|
|
20016
|
+
const seen = new Set;
|
|
20017
|
+
for (const sk of skills) {
|
|
20018
|
+
if (sk.type === "app")
|
|
20019
|
+
continue;
|
|
20020
|
+
const name = canonicalSkillName(sk.name);
|
|
20021
|
+
if (!name || seen.has(name))
|
|
20022
|
+
continue;
|
|
20023
|
+
seen.add(name);
|
|
20024
|
+
known.push({ name, id: bareRegistrySkillId(sk) });
|
|
20025
|
+
}
|
|
20026
|
+
updateWorkspaceRecord(workspaceId, { knownSkills: known });
|
|
20027
|
+
return known;
|
|
20028
|
+
} catch {
|
|
20029
|
+
const record = loadWorkspaceRecord(workspaceId);
|
|
20030
|
+
if (record.knownSkills)
|
|
20031
|
+
return record.knownSkills;
|
|
20032
|
+
return record.skillNames ? record.skillNames.map((name) => ({ name, id: null })) : null;
|
|
20033
|
+
}
|
|
20034
|
+
}
|
|
19194
20035
|
function projectLabel(project) {
|
|
19195
20036
|
const trimmed = project.replace(/[/\\]+$/, "");
|
|
19196
20037
|
const idx = Math.max(trimmed.lastIndexOf("/"), trimmed.lastIndexOf("\\"));
|
|
@@ -19247,11 +20088,21 @@ conversationsCommand.command("scan").description("Detect finished conversations
|
|
|
19247
20088
|
const nowISO = new Date().toISOString();
|
|
19248
20089
|
const summaryEvents = [];
|
|
19249
20090
|
const newlyPending = new Set(scan.newlyPendingKeys);
|
|
20091
|
+
const knownSkills = newlyPending.size > 0 ? await fetchKnownSkills() : null;
|
|
20092
|
+
const repoHashByProject = new Map;
|
|
20093
|
+
const repoHashFor = (project) => {
|
|
20094
|
+
if (!repoHashByProject.has(project))
|
|
20095
|
+
repoHashByProject.set(project, resolveRepoHash(project));
|
|
20096
|
+
return repoHashByProject.get(project) ?? null;
|
|
20097
|
+
};
|
|
19250
20098
|
let statsWritten = 0;
|
|
19251
20099
|
for (const [key, entry] of Object.entries(scan.state.entries)) {
|
|
19252
20100
|
if (!newlyPending.has(key) && entry.stats)
|
|
19253
20101
|
continue;
|
|
19254
|
-
const summary = buildSessionSummary(key, entry, digestForEntry(entry)
|
|
20102
|
+
const summary = buildSessionSummary(key, entry, digestForEntry(entry), {
|
|
20103
|
+
knownSkills,
|
|
20104
|
+
repoHash: repoHashFor(entry.project)
|
|
20105
|
+
});
|
|
19255
20106
|
scan.state.entries[key] = { ...entry, stats: summary };
|
|
19256
20107
|
statsWritten++;
|
|
19257
20108
|
if (newlyPending.has(key))
|
|
@@ -19290,7 +20141,7 @@ conversationsCommand.command("scan").description("Detect finished conversations
|
|
|
19290
20141
|
idleMinutes,
|
|
19291
20142
|
conversations: [...listed, ...rescued].sort((a, b) => b.lastActivityAt.localeCompare(a.lastActivityAt))
|
|
19292
20143
|
};
|
|
19293
|
-
writeJsonAtomic(
|
|
20144
|
+
writeJsonAtomic(join43(homedir24(), ".runwork", "conversations.json"), snapshot);
|
|
19294
20145
|
if (json) {
|
|
19295
20146
|
jsonOut({ queued: scan.queued, reopened: scan.reopened, pruned: scan.pruned, pending, finished: finished.length });
|
|
19296
20147
|
return;
|
|
@@ -19320,8 +20171,8 @@ init_client();
|
|
|
19320
20171
|
init_resolve();
|
|
19321
20172
|
import { Command as Command16 } from "commander";
|
|
19322
20173
|
import { readFileSync as readFileSync37, existsSync as existsSync48 } from "fs";
|
|
19323
|
-
import { join as
|
|
19324
|
-
import { homedir as
|
|
20174
|
+
import { join as join44 } from "path";
|
|
20175
|
+
import { homedir as homedir25 } from "os";
|
|
19325
20176
|
|
|
19326
20177
|
// ../../shared/agent-instructions/runwork-instructions.ts
|
|
19327
20178
|
function formatList(items, max = 8) {
|
|
@@ -19801,8 +20652,8 @@ async function buildInstructionContext(client, workspace) {
|
|
|
19801
20652
|
// src/commands/instructions.ts
|
|
19802
20653
|
function readSetupExtras(workspaceId) {
|
|
19803
20654
|
for (const path2 of [
|
|
19804
|
-
|
|
19805
|
-
|
|
20655
|
+
join44(process.cwd(), ".runwork", "setup.json"),
|
|
20656
|
+
join44(homedir25(), ".runwork", "setup.json")
|
|
19806
20657
|
]) {
|
|
19807
20658
|
if (!existsSync48(path2))
|
|
19808
20659
|
continue;
|
|
@@ -21541,8 +22392,8 @@ init_resolve();
|
|
|
21541
22392
|
init_prompt();
|
|
21542
22393
|
await init_detect();
|
|
21543
22394
|
import { Command as Command27 } from "commander";
|
|
21544
|
-
import { join as
|
|
21545
|
-
import { homedir as
|
|
22395
|
+
import { join as join48 } from "path";
|
|
22396
|
+
import { homedir as homedir27 } from "os";
|
|
21546
22397
|
|
|
21547
22398
|
// src/commands/sync.ts
|
|
21548
22399
|
init_store();
|
|
@@ -21554,8 +22405,8 @@ await __promiseAll([
|
|
|
21554
22405
|
]);
|
|
21555
22406
|
import { Command as Command26 } from "commander";
|
|
21556
22407
|
import { readFileSync as readFileSync40, existsSync as existsSync51 } from "fs";
|
|
21557
|
-
import { join as
|
|
21558
|
-
import { homedir as
|
|
22408
|
+
import { join as join47 } from "path";
|
|
22409
|
+
import { homedir as homedir26 } from "os";
|
|
21559
22410
|
|
|
21560
22411
|
// src/commands/mcp-entries.ts
|
|
21561
22412
|
init_types();
|
|
@@ -22454,13 +23305,13 @@ function readLocalSkills(state) {
|
|
|
22454
23305
|
if (!baseDir)
|
|
22455
23306
|
continue;
|
|
22456
23307
|
for (const skillName of state.skills) {
|
|
22457
|
-
const skillMdPath =
|
|
23308
|
+
const skillMdPath = join47(baseDir, skillName, "SKILL.md");
|
|
22458
23309
|
if (existsSync51(skillMdPath)) {
|
|
22459
23310
|
results.push({ name: skillName, content: readFileSync40(skillMdPath, "utf-8") });
|
|
22460
23311
|
continue;
|
|
22461
23312
|
}
|
|
22462
23313
|
const filename = skillName.toLowerCase().replace(/[^a-z0-9]+/g, "-");
|
|
22463
|
-
const flatPath =
|
|
23314
|
+
const flatPath = join47(baseDir, `${filename}.md`);
|
|
22464
23315
|
if (existsSync51(flatPath)) {
|
|
22465
23316
|
results.push({ name: skillName, content: readFileSync40(flatPath, "utf-8") });
|
|
22466
23317
|
}
|
|
@@ -22942,7 +23793,7 @@ This account is not a member of "${state.workspaceName || state.workspaceId}".`)
|
|
|
22942
23793
|
}
|
|
22943
23794
|
for (const adapter2 of adapters) {
|
|
22944
23795
|
if (adapter2 instanceof CodexAdapter) {
|
|
22945
|
-
const runworkDir =
|
|
23796
|
+
const runworkDir = join47(homedir26(), ".runwork");
|
|
22946
23797
|
const result = adapter2.registerDesktopWorkspace(runworkDir, "Runwork");
|
|
22947
23798
|
if (result === "written") {
|
|
22948
23799
|
vlog(` [${adapter2.name}] Registered workspace in Codex desktop app`);
|
|
@@ -23084,8 +23935,8 @@ var syncCommand = new Command26("sync").description("Sync skills bidirectionally
|
|
|
23084
23935
|
verbose: !!opts.verbose,
|
|
23085
23936
|
redetect: !!opts.redetect
|
|
23086
23937
|
};
|
|
23087
|
-
const projectStatePath =
|
|
23088
|
-
const userStatePath =
|
|
23938
|
+
const projectStatePath = join47(process.cwd(), ".runwork", "setup.json");
|
|
23939
|
+
const userStatePath = join47(homedir26(), ".runwork", "setup.json");
|
|
23089
23940
|
const projectState = loadSetupState(projectStatePath);
|
|
23090
23941
|
const userState = loadSetupState(userStatePath);
|
|
23091
23942
|
if (!projectState && !userState) {
|
|
@@ -23154,7 +24005,7 @@ function toSkillFilename(name) {
|
|
|
23154
24005
|
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-");
|
|
23155
24006
|
}
|
|
23156
24007
|
function loadSetupStateForScope(scope) {
|
|
23157
|
-
const path4 = scope === "project" ?
|
|
24008
|
+
const path4 = scope === "project" ? join48(process.cwd(), ".runwork", "setup.json") : join48(homedir27(), ".runwork", "setup.json");
|
|
23158
24009
|
return readJsonOrNull(path4);
|
|
23159
24010
|
}
|
|
23160
24011
|
async function parkAndTeardownWorkspace(previous, scopes) {
|
|
@@ -23323,8 +24174,8 @@ Re-run without --dry-run to sync workspace data.`);
|
|
|
23323
24174
|
}
|
|
23324
24175
|
persistDefaultWorkspace(workspaceId, workspaceName);
|
|
23325
24176
|
for (const s of scopes) {
|
|
23326
|
-
const dir = s === "project" ? ".runwork" :
|
|
23327
|
-
writeJsonAtomic(
|
|
24177
|
+
const dir = s === "project" ? ".runwork" : join48(homedir27(), ".runwork");
|
|
24178
|
+
writeJsonAtomic(join48(dir, "setup.json"), state);
|
|
23328
24179
|
}
|
|
23329
24180
|
if (restored)
|
|
23330
24181
|
clearParkedState(workspaceId);
|
|
@@ -23332,7 +24183,7 @@ Re-run without --dry-run to sync workspace data.`);
|
|
|
23332
24183
|
Syncing workspace data...
|
|
23333
24184
|
`);
|
|
23334
24185
|
for (const s of scopes) {
|
|
23335
|
-
const statePath2 = s === "project" ?
|
|
24186
|
+
const statePath2 = s === "project" ? join48(process.cwd(), ".runwork", "setup.json") : join48(homedir27(), ".runwork", "setup.json");
|
|
23336
24187
|
await syncFromState(state, statePath2, credentials, {
|
|
23337
24188
|
dryRun: false,
|
|
23338
24189
|
pullOnly: true,
|
|
@@ -23353,11 +24204,11 @@ import { Command as Command28 } from "commander";
|
|
|
23353
24204
|
|
|
23354
24205
|
// src/utils/setup-state.ts
|
|
23355
24206
|
import { existsSync as existsSync52, readFileSync as readFileSync41 } from "fs";
|
|
23356
|
-
import { join as
|
|
23357
|
-
import { homedir as
|
|
24207
|
+
import { join as join49 } from "path";
|
|
24208
|
+
import { homedir as homedir28 } from "os";
|
|
23358
24209
|
function loadSetupState2() {
|
|
23359
|
-
const projectPath =
|
|
23360
|
-
const userPath =
|
|
24210
|
+
const projectPath = join49(process.cwd(), ".runwork", "setup.json");
|
|
24211
|
+
const userPath = join49(homedir28(), ".runwork", "setup.json");
|
|
23361
24212
|
for (const p of [projectPath, userPath]) {
|
|
23362
24213
|
if (existsSync52(p)) {
|
|
23363
24214
|
try {
|
|
@@ -23421,8 +24272,8 @@ init_types();
|
|
|
23421
24272
|
await init_detect();
|
|
23422
24273
|
import { Command as Command29 } from "commander";
|
|
23423
24274
|
import { existsSync as existsSync53, readFileSync as readFileSync42 } from "fs";
|
|
23424
|
-
import { resolve as resolve3, join as
|
|
23425
|
-
import { homedir as
|
|
24275
|
+
import { resolve as resolve3, join as join50 } from "path";
|
|
24276
|
+
import { homedir as homedir29 } from "os";
|
|
23426
24277
|
function loadSetupState3(filePath) {
|
|
23427
24278
|
if (!existsSync53(filePath))
|
|
23428
24279
|
return null;
|
|
@@ -23443,8 +24294,8 @@ var buildPluginCommand = new Command29("build-plugin").description("Build an ins
|
|
|
23443
24294
|
process.exit(1);
|
|
23444
24295
|
}
|
|
23445
24296
|
const credentials = requireAuth();
|
|
23446
|
-
const projectStatePath =
|
|
23447
|
-
const userStatePath =
|
|
24297
|
+
const projectStatePath = join50(process.cwd(), ".runwork", "setup.json");
|
|
24298
|
+
const userStatePath = join50(homedir29(), ".runwork", "setup.json");
|
|
23448
24299
|
const state = loadSetupState3(projectStatePath) ?? loadSetupState3(userStatePath);
|
|
23449
24300
|
if (!state) {
|
|
23450
24301
|
console.error("No setup state found. Run `runwork setup` first.");
|
|
@@ -23537,8 +24388,8 @@ init_prompt();
|
|
|
23537
24388
|
await init_detect();
|
|
23538
24389
|
import { Command as Command30 } from "commander";
|
|
23539
24390
|
import { existsSync as existsSync54, readFileSync as readFileSync43, rmSync as rmSync13, unlinkSync as unlinkSync8 } from "fs";
|
|
23540
|
-
import { join as
|
|
23541
|
-
import { homedir as
|
|
24391
|
+
import { join as join51 } from "path";
|
|
24392
|
+
import { homedir as homedir30 } from "os";
|
|
23542
24393
|
function loadSetupState4(filePath) {
|
|
23543
24394
|
if (!existsSync54(filePath))
|
|
23544
24395
|
return null;
|
|
@@ -23549,8 +24400,8 @@ function loadSetupState4(filePath) {
|
|
|
23549
24400
|
}
|
|
23550
24401
|
}
|
|
23551
24402
|
var uninstallCommand = new Command30("uninstall").description("Remove all Runwork configuration from local agents (MCP servers, skills, instructions)").option("-y, --yes", "Skip confirmation prompt").option("--keep-auth", "Keep authentication credentials (only remove agent configs)").action(async (opts) => {
|
|
23552
|
-
const projectStatePath =
|
|
23553
|
-
const userStatePath =
|
|
24403
|
+
const projectStatePath = join51(process.cwd(), ".runwork", "setup.json");
|
|
24404
|
+
const userStatePath = join51(homedir30(), ".runwork", "setup.json");
|
|
23554
24405
|
const projectState = loadSetupState4(projectStatePath);
|
|
23555
24406
|
const userState = loadSetupState4(userStatePath);
|
|
23556
24407
|
if (!projectState && !userState) {
|
|
@@ -23630,9 +24481,9 @@ This will remove all Runwork configuration from your local agents:
|
|
|
23630
24481
|
}
|
|
23631
24482
|
}
|
|
23632
24483
|
}
|
|
23633
|
-
const stateDir = label === "project" ?
|
|
24484
|
+
const stateDir = label === "project" ? join51(process.cwd(), ".runwork") : join51(homedir30(), ".runwork");
|
|
23634
24485
|
if (opts.keepAuth && label === "user") {
|
|
23635
|
-
const setupFile =
|
|
24486
|
+
const setupFile = join51(stateDir, "setup.json");
|
|
23636
24487
|
if (existsSync54(setupFile)) {
|
|
23637
24488
|
try {
|
|
23638
24489
|
unlinkSync8(setupFile);
|
|
@@ -23883,8 +24734,8 @@ init_credentials();
|
|
|
23883
24734
|
await init_detect();
|
|
23884
24735
|
import { parse as parse2 } from "smol-toml";
|
|
23885
24736
|
import { existsSync as existsSync55, readFileSync as readFileSync45 } from "fs";
|
|
23886
|
-
import { join as
|
|
23887
|
-
import { homedir as
|
|
24737
|
+
import { join as join52, sep as sep4 } from "path";
|
|
24738
|
+
import { homedir as homedir31, platform as osPlatform2, arch as osArch } from "os";
|
|
23888
24739
|
var BASE_URL2 = process.env.RUNWORK_DOWNLOAD_BASE_URL || "https://runwork.ai";
|
|
23889
24740
|
var LATEST_JSON_URL2 = `${BASE_URL2}/cli/latest.json`;
|
|
23890
24741
|
function detectPlatform() {
|
|
@@ -23912,7 +24763,7 @@ function buildContext() {
|
|
|
23912
24763
|
const credentials = getCredentials();
|
|
23913
24764
|
const client = credentials ? new ApiClient(credentials) : null;
|
|
23914
24765
|
let config = null;
|
|
23915
|
-
const configPath =
|
|
24766
|
+
const configPath = join52(process.cwd(), ".runwork.json");
|
|
23916
24767
|
if (existsSync55(configPath)) {
|
|
23917
24768
|
try {
|
|
23918
24769
|
config = JSON.parse(readFileSync45(configPath, "utf-8"));
|
|
@@ -24016,9 +24867,9 @@ async function checkCliArtifactReachable() {
|
|
|
24016
24867
|
}
|
|
24017
24868
|
async function checkCliInstallLocation() {
|
|
24018
24869
|
const isWindows2 = osPlatform2() === "win32";
|
|
24019
|
-
const home =
|
|
24020
|
-
const canonicalDir =
|
|
24021
|
-
const canonicalBinary = isWindows2 ?
|
|
24870
|
+
const home = homedir31();
|
|
24871
|
+
const canonicalDir = join52(home, ".runwork", "bin");
|
|
24872
|
+
const canonicalBinary = isWindows2 ? join52(canonicalDir, "runwork.exe") : join52(canonicalDir, "runwork");
|
|
24022
24873
|
const candidates = [process.execPath, process.argv[1] || ""].filter(Boolean);
|
|
24023
24874
|
const runsFromCanonical = candidates.some((p) => normalizePath(p) === normalizePath(canonicalBinary));
|
|
24024
24875
|
if (runsFromCanonical) {
|
|
@@ -24150,7 +25001,7 @@ async function checkGitCredentialHelper(ctx) {
|
|
|
24150
25001
|
};
|
|
24151
25002
|
}
|
|
24152
25003
|
async function checkProjectConfig(ctx) {
|
|
24153
|
-
const configPath =
|
|
25004
|
+
const configPath = join52(ctx.cwd, ".runwork.json");
|
|
24154
25005
|
if (!existsSync55(configPath)) {
|
|
24155
25006
|
if (!ctx.credentials) {
|
|
24156
25007
|
return { name: "project-config", status: "skip", message: "no project (not logged in)" };
|
|
@@ -24213,7 +25064,7 @@ async function checkGitRemote(ctx) {
|
|
|
24213
25064
|
if (!ctx.config) {
|
|
24214
25065
|
return { name: "git-remote", status: "skip", message: "skipped (no project)" };
|
|
24215
25066
|
}
|
|
24216
|
-
if (!existsSync55(
|
|
25067
|
+
if (!existsSync55(join52(ctx.cwd, ".git"))) {
|
|
24217
25068
|
return {
|
|
24218
25069
|
name: "git-remote",
|
|
24219
25070
|
status: "fail",
|
|
@@ -24267,8 +25118,8 @@ async function checkDeployFreshness(ctx) {
|
|
|
24267
25118
|
return { name: "deploy-freshness", status: "skip", message: "local HEAD unknown" };
|
|
24268
25119
|
}
|
|
24269
25120
|
function loadSetupState5() {
|
|
24270
|
-
const projectPath =
|
|
24271
|
-
const userPath =
|
|
25121
|
+
const projectPath = join52(process.cwd(), ".runwork", "setup.json");
|
|
25122
|
+
const userPath = join52(homedir31(), ".runwork", "setup.json");
|
|
24272
25123
|
for (const p of [projectPath, userPath]) {
|
|
24273
25124
|
if (existsSync55(p)) {
|
|
24274
25125
|
try {
|
|
@@ -24287,7 +25138,7 @@ async function checkCodexNetwork() {
|
|
|
24287
25138
|
if (!state || !state.configuredAgents.includes("codex")) {
|
|
24288
25139
|
return { name, status: "skip", message: "Codex not configured for Runwork" };
|
|
24289
25140
|
}
|
|
24290
|
-
const configPath =
|
|
25141
|
+
const configPath = join52(homedir31(), ".codex", "config.toml");
|
|
24291
25142
|
if (!existsSync55(configPath)) {
|
|
24292
25143
|
return { name, status: "skip", message: "no Codex config found" };
|
|
24293
25144
|
}
|
|
@@ -24346,7 +25197,7 @@ async function checkCodexDesktopProject() {
|
|
|
24346
25197
|
if (!usesCodex) {
|
|
24347
25198
|
return { name, status: "skip", message: "Codex not configured for Runwork" };
|
|
24348
25199
|
}
|
|
24349
|
-
const statePath2 =
|
|
25200
|
+
const statePath2 = join52(homedir31(), ".codex", ".codex-global-state.json");
|
|
24350
25201
|
if (!existsSync55(statePath2)) {
|
|
24351
25202
|
return { name, status: "skip", message: "Codex desktop app not detected" };
|
|
24352
25203
|
}
|
|
@@ -24358,7 +25209,7 @@ async function checkCodexDesktopProject() {
|
|
|
24358
25209
|
} catch {
|
|
24359
25210
|
return { name, status: "warn", message: "could not read Codex desktop state" };
|
|
24360
25211
|
}
|
|
24361
|
-
const runworkDir =
|
|
25212
|
+
const runworkDir = join52(homedir31(), ".runwork");
|
|
24362
25213
|
if (savedRoots.includes(runworkDir)) {
|
|
24363
25214
|
return { name, status: "pass", message: "Runwork project added to Codex desktop sidebar" };
|
|
24364
25215
|
}
|
|
@@ -24436,7 +25287,7 @@ async function checkAgentSetup() {
|
|
|
24436
25287
|
if (!skillsDir)
|
|
24437
25288
|
continue;
|
|
24438
25289
|
const missingSkills = state.skills.filter((name) => {
|
|
24439
|
-
const skillPath =
|
|
25290
|
+
const skillPath = join52(skillsDir, name, "SKILL.md");
|
|
24440
25291
|
return !existsSync55(skillPath);
|
|
24441
25292
|
});
|
|
24442
25293
|
if (missingSkills.length > 0) {
|
|
@@ -24462,25 +25313,25 @@ async function checkAgentSetup() {
|
|
|
24462
25313
|
};
|
|
24463
25314
|
}
|
|
24464
25315
|
function getMcpConfigPath2(slug, scope) {
|
|
24465
|
-
const home =
|
|
25316
|
+
const home = homedir31();
|
|
24466
25317
|
switch (slug) {
|
|
24467
25318
|
case "claude-code":
|
|
24468
|
-
return scope === "project" ?
|
|
25319
|
+
return scope === "project" ? join52(process.cwd(), ".mcp.json") : join52(home, ".claude", "settings.json");
|
|
24469
25320
|
case "cursor":
|
|
24470
|
-
return scope === "project" ?
|
|
25321
|
+
return scope === "project" ? join52(process.cwd(), ".cursor", "mcp.json") : join52(home, ".cursor", "mcp.json");
|
|
24471
25322
|
case "windsurf":
|
|
24472
|
-
return scope === "project" ?
|
|
25323
|
+
return scope === "project" ? join52(process.cwd(), ".windsurf", "mcp.json") : join52(home, ".windsurf", "mcp.json");
|
|
24473
25324
|
case "codex":
|
|
24474
25325
|
case "codex-app":
|
|
24475
|
-
return scope === "user" ?
|
|
25326
|
+
return scope === "user" ? join52(home, ".codex", "config.toml") : null;
|
|
24476
25327
|
case "gemini":
|
|
24477
|
-
return scope === "user" ?
|
|
25328
|
+
return scope === "user" ? join52(home, ".gemini", "settings.json") : null;
|
|
24478
25329
|
default:
|
|
24479
25330
|
return null;
|
|
24480
25331
|
}
|
|
24481
25332
|
}
|
|
24482
25333
|
async function checkWorkspacePointers() {
|
|
24483
|
-
const userStatePath =
|
|
25334
|
+
const userStatePath = join52(homedir31(), ".runwork", "setup.json");
|
|
24484
25335
|
const state = existsSync55(userStatePath) ? (() => {
|
|
24485
25336
|
try {
|
|
24486
25337
|
return JSON.parse(readFileSync45(userStatePath, "utf-8"));
|
|
@@ -24513,15 +25364,15 @@ async function checkWorkspacePointers() {
|
|
|
24513
25364
|
};
|
|
24514
25365
|
}
|
|
24515
25366
|
function getSkillsDir(slug, scope) {
|
|
24516
|
-
const home =
|
|
25367
|
+
const home = homedir31();
|
|
24517
25368
|
switch (slug) {
|
|
24518
25369
|
case "claude-code":
|
|
24519
|
-
return scope === "project" ?
|
|
25370
|
+
return scope === "project" ? join52(process.cwd(), ".claude", "skills") : join52(home, ".claude", "skills");
|
|
24520
25371
|
case "codex":
|
|
24521
25372
|
case "codex-app":
|
|
24522
|
-
return scope === "project" ?
|
|
25373
|
+
return scope === "project" ? join52(process.cwd(), ".agents", "skills") : join52(home, ".agents", "skills");
|
|
24523
25374
|
case "gemini":
|
|
24524
|
-
return scope === "project" ?
|
|
25375
|
+
return scope === "project" ? join52(process.cwd(), ".gemini", "skills") : join52(home, ".gemini", "skills");
|
|
24525
25376
|
default:
|
|
24526
25377
|
return null;
|
|
24527
25378
|
}
|
|
@@ -24575,7 +25426,7 @@ async function runAllChecks(options) {
|
|
|
24575
25426
|
init_credentials();
|
|
24576
25427
|
init_remote();
|
|
24577
25428
|
import { existsSync as existsSync56 } from "fs";
|
|
24578
|
-
import { join as
|
|
25429
|
+
import { join as join53 } from "path";
|
|
24579
25430
|
async function applyDoctorFixes(ctx, failingNames) {
|
|
24580
25431
|
const failing = new Set(failingNames);
|
|
24581
25432
|
const outcomes = [];
|
|
@@ -24602,7 +25453,7 @@ async function applyDoctorFixes(ctx, failingNames) {
|
|
|
24602
25453
|
applied: false,
|
|
24603
25454
|
message: "no project config -- run inside an app directory"
|
|
24604
25455
|
});
|
|
24605
|
-
} else if (!existsSync56(
|
|
25456
|
+
} else if (!existsSync56(join53(ctx.cwd, ".git"))) {
|
|
24606
25457
|
outcomes.push({
|
|
24607
25458
|
name: "git-remote",
|
|
24608
25459
|
applied: false,
|
|
@@ -24622,9 +25473,9 @@ async function applyDoctorFixes(ctx, failingNames) {
|
|
|
24622
25473
|
|
|
24623
25474
|
// src/agents/runtime-detection.ts
|
|
24624
25475
|
import { existsSync as existsSync57, readFileSync as readFileSync46, statSync as statSync10, readdirSync as readdirSync16 } from "fs";
|
|
24625
|
-
import { homedir as
|
|
24626
|
-
import { join as
|
|
24627
|
-
var RUNWORK_SESSIONS_DIR =
|
|
25476
|
+
import { homedir as homedir32 } from "os";
|
|
25477
|
+
import { join as join54 } from "path";
|
|
25478
|
+
var RUNWORK_SESSIONS_DIR = join54(homedir32(), ".runwork", "sessions");
|
|
24628
25479
|
function detectCurrentAgent() {
|
|
24629
25480
|
const claudeCodeSessionId = process.env.CLAUDE_CODE_SESSION_ID;
|
|
24630
25481
|
if (claudeCodeSessionId) {
|
|
@@ -24687,7 +25538,7 @@ function detectCurrentAgent() {
|
|
|
24687
25538
|
return null;
|
|
24688
25539
|
}
|
|
24689
25540
|
function readHookSessionInfo(sessionId) {
|
|
24690
|
-
const path4 =
|
|
25541
|
+
const path4 = join54(RUNWORK_SESSIONS_DIR, `${sessionId}.json`);
|
|
24691
25542
|
if (!existsSync57(path4))
|
|
24692
25543
|
return null;
|
|
24693
25544
|
try {
|
|
@@ -24699,7 +25550,7 @@ function readHookSessionInfo(sessionId) {
|
|
|
24699
25550
|
}
|
|
24700
25551
|
}
|
|
24701
25552
|
function findClaudeCodeSessionFile(sessionId) {
|
|
24702
|
-
const root =
|
|
25553
|
+
const root = join54(homedir32(), ".claude", "projects");
|
|
24703
25554
|
if (!existsSync57(root))
|
|
24704
25555
|
return null;
|
|
24705
25556
|
let projectDirs;
|
|
@@ -24709,14 +25560,14 @@ function findClaudeCodeSessionFile(sessionId) {
|
|
|
24709
25560
|
return null;
|
|
24710
25561
|
}
|
|
24711
25562
|
for (const dir of projectDirs) {
|
|
24712
|
-
const candidate =
|
|
25563
|
+
const candidate = join54(root, dir, `${sessionId}.jsonl`);
|
|
24713
25564
|
if (existsSync57(candidate))
|
|
24714
25565
|
return candidate;
|
|
24715
25566
|
}
|
|
24716
25567
|
return null;
|
|
24717
25568
|
}
|
|
24718
25569
|
function findCodexRolloutFile(threadId) {
|
|
24719
|
-
const root =
|
|
25570
|
+
const root = join54(homedir32(), ".codex", "sessions");
|
|
24720
25571
|
if (!existsSync57(root))
|
|
24721
25572
|
return null;
|
|
24722
25573
|
const stack = [root];
|
|
@@ -24729,7 +25580,7 @@ function findCodexRolloutFile(threadId) {
|
|
|
24729
25580
|
continue;
|
|
24730
25581
|
}
|
|
24731
25582
|
for (const entry of entries) {
|
|
24732
|
-
const full =
|
|
25583
|
+
const full = join54(dir, entry);
|
|
24733
25584
|
let s;
|
|
24734
25585
|
try {
|
|
24735
25586
|
s = statSync10(full);
|
|
@@ -24746,7 +25597,7 @@ function findCodexRolloutFile(threadId) {
|
|
|
24746
25597
|
return null;
|
|
24747
25598
|
}
|
|
24748
25599
|
function findNewestClaudeCodeSession() {
|
|
24749
|
-
const root =
|
|
25600
|
+
const root = join54(homedir32(), ".claude", "projects");
|
|
24750
25601
|
if (!existsSync57(root))
|
|
24751
25602
|
return null;
|
|
24752
25603
|
let projectDirs;
|
|
@@ -24757,7 +25608,7 @@ function findNewestClaudeCodeSession() {
|
|
|
24757
25608
|
}
|
|
24758
25609
|
let best = null;
|
|
24759
25610
|
for (const dir of projectDirs) {
|
|
24760
|
-
const projectPath =
|
|
25611
|
+
const projectPath = join54(root, dir);
|
|
24761
25612
|
let files;
|
|
24762
25613
|
try {
|
|
24763
25614
|
files = readdirSync16(projectPath);
|
|
@@ -24767,7 +25618,7 @@ function findNewestClaudeCodeSession() {
|
|
|
24767
25618
|
for (const file of files) {
|
|
24768
25619
|
if (!file.endsWith(".jsonl"))
|
|
24769
25620
|
continue;
|
|
24770
|
-
const full =
|
|
25621
|
+
const full = join54(projectPath, file);
|
|
24771
25622
|
try {
|
|
24772
25623
|
const s = statSync10(full);
|
|
24773
25624
|
if (!best || s.mtimeMs > best.mtime) {
|
|
@@ -24785,7 +25636,7 @@ function findNewestClaudeCodeSession() {
|
|
|
24785
25636
|
return best ? { sessionId: best.sessionId, path: best.path } : null;
|
|
24786
25637
|
}
|
|
24787
25638
|
function findNewestCodexRollout() {
|
|
24788
|
-
const root =
|
|
25639
|
+
const root = join54(homedir32(), ".codex", "sessions");
|
|
24789
25640
|
if (!existsSync57(root))
|
|
24790
25641
|
return null;
|
|
24791
25642
|
const stack = [root];
|
|
@@ -24799,7 +25650,7 @@ function findNewestCodexRollout() {
|
|
|
24799
25650
|
continue;
|
|
24800
25651
|
}
|
|
24801
25652
|
for (const entry of entries) {
|
|
24802
|
-
const full =
|
|
25653
|
+
const full = join54(dir, entry);
|
|
24803
25654
|
let s;
|
|
24804
25655
|
try {
|
|
24805
25656
|
s = statSync10(full);
|
|
@@ -25032,9 +25883,9 @@ init_client();
|
|
|
25032
25883
|
init_resolve();
|
|
25033
25884
|
import { Command as Command35 } from "commander";
|
|
25034
25885
|
import { readFileSync as readFileSync47, writeFileSync as writeFileSync31, existsSync as existsSync58, mkdtempSync as mkdtempSync4 } from "fs";
|
|
25035
|
-
import { join as
|
|
25886
|
+
import { join as join55 } from "path";
|
|
25036
25887
|
import { tmpdir as tmpdir4 } from "os";
|
|
25037
|
-
import { createHash as
|
|
25888
|
+
import { createHash as createHash6 } from "crypto";
|
|
25038
25889
|
|
|
25039
25890
|
// src/agents/utils/transcript-render.ts
|
|
25040
25891
|
init_session_digest();
|
|
@@ -25159,8 +26010,8 @@ function resolveLocalSessionShare(opts, conversation) {
|
|
|
25159
26010
|
console.error("Error: this conversation has no shareable content.");
|
|
25160
26011
|
process.exit(1);
|
|
25161
26012
|
}
|
|
25162
|
-
const tempDir = mkdtempSync4(
|
|
25163
|
-
const transcriptFile =
|
|
26013
|
+
const tempDir = mkdtempSync4(join55(tmpdir4(), "runwork-share-"));
|
|
26014
|
+
const transcriptFile = join55(tempDir, "transcript.md");
|
|
25164
26015
|
writeFileSync31(transcriptFile, markdown);
|
|
25165
26016
|
opts.transcriptFile = transcriptFile;
|
|
25166
26017
|
opts.nativeFile = opts.nativeFile ?? conversation.transcriptPath;
|
|
@@ -25178,7 +26029,7 @@ function nativeBundleFormatForAgent(slug) {
|
|
|
25178
26029
|
return null;
|
|
25179
26030
|
}
|
|
25180
26031
|
function sha256Hex(content) {
|
|
25181
|
-
return
|
|
26032
|
+
return createHash6("sha256").update(content, "utf8").digest("hex");
|
|
25182
26033
|
}
|
|
25183
26034
|
function utf8ByteLength(content) {
|
|
25184
26035
|
return Buffer.byteLength(content, "utf8");
|
|
@@ -25370,8 +26221,8 @@ init_resolve();
|
|
|
25370
26221
|
init_registry_data();
|
|
25371
26222
|
import { Command as Command38 } from "commander";
|
|
25372
26223
|
import { writeFileSync as writeFileSync32, mkdirSync as mkdirSync28, realpathSync } from "fs";
|
|
25373
|
-
import { homedir as
|
|
25374
|
-
import { join as
|
|
26224
|
+
import { homedir as homedir33 } from "os";
|
|
26225
|
+
import { join as join56 } from "path";
|
|
25375
26226
|
import { spawn as spawn5 } from "child_process";
|
|
25376
26227
|
init_registry();
|
|
25377
26228
|
init_which();
|
|
@@ -25410,9 +26261,9 @@ function extractCodexUuid(rolloutContent) {
|
|
|
25410
26261
|
}
|
|
25411
26262
|
function placeClaudeJsonl(uuid, content, recipientCwd) {
|
|
25412
26263
|
const encoded = encodeClaudeCodeCwd(recipientCwd);
|
|
25413
|
-
const projectDir =
|
|
26264
|
+
const projectDir = join56(homedir33(), ".claude", "projects", encoded);
|
|
25414
26265
|
mkdirSync28(projectDir, { recursive: true });
|
|
25415
|
-
const placedAt =
|
|
26266
|
+
const placedAt = join56(projectDir, `${uuid}.jsonl`);
|
|
25416
26267
|
writeFileSync32(placedAt, content);
|
|
25417
26268
|
return { placedAt, runFromCwd: recipientCwd };
|
|
25418
26269
|
}
|
|
@@ -25421,10 +26272,10 @@ function placeCodexRollout(uuid, content) {
|
|
|
25421
26272
|
const yyyy = String(now.getUTCFullYear());
|
|
25422
26273
|
const mm = String(now.getUTCMonth() + 1).padStart(2, "0");
|
|
25423
26274
|
const dd = String(now.getUTCDate()).padStart(2, "0");
|
|
25424
|
-
const dir =
|
|
26275
|
+
const dir = join56(homedir33(), ".codex", "sessions", yyyy, mm, dd);
|
|
25425
26276
|
mkdirSync28(dir, { recursive: true });
|
|
25426
26277
|
const ts = now.toISOString().replace(/\.\d+Z$/, "").replace(/:/g, "-");
|
|
25427
|
-
const placedAt =
|
|
26278
|
+
const placedAt = join56(dir, `rollout-${ts}-${uuid}.jsonl`);
|
|
25428
26279
|
writeFileSync32(placedAt, content);
|
|
25429
26280
|
return { placedAt };
|
|
25430
26281
|
}
|