runwork 0.17.1 → 0.18.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/LICENSE +3 -3
- package/bundled-types/core-scheduler.d.ts +10 -1
- package/dist/index.js +424 -188
- package/package.json +2 -2
package/LICENSE
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
Copyright (c) 2025-2026 Runwork All rights reserved.
|
|
1
|
+
Copyright (c) 2025-2026 Runwork, Inc. All rights reserved.
|
|
2
2
|
|
|
3
3
|
This software and associated documentation files (the "Software") are the
|
|
4
|
-
proprietary and confidential property of Runwork
|
|
4
|
+
proprietary and confidential property of Runwork, Inc.
|
|
5
5
|
|
|
6
6
|
No part of the Software may be copied, modified, distributed, sublicensed,
|
|
7
7
|
sold, or otherwise made available to any third party without the prior
|
|
8
|
-
written consent of Runwork
|
|
8
|
+
written consent of Runwork, Inc.
|
|
9
9
|
|
|
10
10
|
You may use the Software solely as a tool to interact with the Runwork
|
|
11
11
|
platform in accordance with the Runwork Terms of Service.
|
|
@@ -44,6 +44,15 @@ export interface JobResult {
|
|
|
44
44
|
/** Additional result data (varies by job) */
|
|
45
45
|
[key: string]: unknown;
|
|
46
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* Wire-safe schedule definition: what crosses the RPC boundary into
|
|
49
|
+
* SchedulerDO. Deliberately excludes the handler function; over DO RPC a
|
|
50
|
+
* function arrives as a callback stub, and invoking a stubbed handler
|
|
51
|
+
* serializes JobContext back to the calling isolate, where env bindings (R2
|
|
52
|
+
* buckets, DO namespaces) cannot be serialized (AGENTCLI-6). Handlers are
|
|
53
|
+
* bound inside the DO from its own isolate's module registry.
|
|
54
|
+
*/
|
|
55
|
+
export type ScheduledJobDefinition = Omit<ScheduledJob, 'handler'> & Partial<Pick<ScheduledJob, 'handler'>>;
|
|
47
56
|
export interface JobLogger {
|
|
48
57
|
info(message: string, data?: Record<string, unknown>): void;
|
|
49
58
|
warn(message: string, data?: Record<string, unknown>): void;
|
|
@@ -134,7 +143,7 @@ export declare class SchedulerDO extends DurableObject<Env> {
|
|
|
134
143
|
* Initialize the job scheduler with job definitions.
|
|
135
144
|
* Can be called again after HMR to sync new/changed job definitions.
|
|
136
145
|
*/
|
|
137
|
-
initializeJobScheduler(jobs:
|
|
146
|
+
initializeJobScheduler(jobs: ScheduledJobDefinition[]): Promise<void>;
|
|
138
147
|
/**
|
|
139
148
|
* Handle alarm - execute due jobs
|
|
140
149
|
*/
|
package/dist/index.js
CHANGED
|
@@ -5811,6 +5811,15 @@ export interface JobResult {
|
|
|
5811
5811
|
/** Additional result data (varies by job) */
|
|
5812
5812
|
[key: string]: unknown;
|
|
5813
5813
|
}
|
|
5814
|
+
/**
|
|
5815
|
+
* Wire-safe schedule definition: what crosses the RPC boundary into
|
|
5816
|
+
* SchedulerDO. Deliberately excludes the handler function; over DO RPC a
|
|
5817
|
+
* function arrives as a callback stub, and invoking a stubbed handler
|
|
5818
|
+
* serializes JobContext back to the calling isolate, where env bindings (R2
|
|
5819
|
+
* buckets, DO namespaces) cannot be serialized (AGENTCLI-6). Handlers are
|
|
5820
|
+
* bound inside the DO from its own isolate's module registry.
|
|
5821
|
+
*/
|
|
5822
|
+
export type ScheduledJobDefinition = Omit<ScheduledJob, 'handler'> & Partial<Pick<ScheduledJob, 'handler'>>;
|
|
5814
5823
|
export interface JobLogger {
|
|
5815
5824
|
info(message: string, data?: Record<string, unknown>): void;
|
|
5816
5825
|
warn(message: string, data?: Record<string, unknown>): void;
|
|
@@ -5901,7 +5910,7 @@ export declare class SchedulerDO extends DurableObject<Env> {
|
|
|
5901
5910
|
* Initialize the job scheduler with job definitions.
|
|
5902
5911
|
* Can be called again after HMR to sync new/changed job definitions.
|
|
5903
5912
|
*/
|
|
5904
|
-
initializeJobScheduler(jobs:
|
|
5913
|
+
initializeJobScheduler(jobs: ScheduledJobDefinition[]): Promise<void>;
|
|
5905
5914
|
/**
|
|
5906
5915
|
* Handle alarm - execute due jobs
|
|
5907
5916
|
*/
|
|
@@ -7561,7 +7570,7 @@ function createKeyboardListener() {
|
|
|
7561
7570
|
}
|
|
7562
7571
|
|
|
7563
7572
|
// src/generated/version.ts
|
|
7564
|
-
var VERSION = "0.
|
|
7573
|
+
var VERSION = "0.18.0";
|
|
7565
7574
|
|
|
7566
7575
|
// src/commands/dev.ts
|
|
7567
7576
|
var exports_dev = {};
|
|
@@ -8921,6 +8930,21 @@ var deployCommand = new Command5("deploy").description("Deploy the current app t
|
|
|
8921
8930
|
}
|
|
8922
8931
|
const deployedSha = getHeadSha(cwd);
|
|
8923
8932
|
const finishedAt = new Date().toISOString();
|
|
8933
|
+
const shaVerdict = assessBuiltSha(deployedSha, result.builtFromSha);
|
|
8934
|
+
if (shaVerdict === "mismatch") {
|
|
8935
|
+
const detail = `The platform built this deploy from ${result.builtFromSha.slice(0, 7)} but your pushed HEAD is ${deployedSha.slice(0, 7)}. Production is NOT running your latest code.`;
|
|
8936
|
+
writeDeployStatus(cwd, { state: "failed", startedAt: childStartedAt, finishedAt, error: detail });
|
|
8937
|
+
deployFinalized = true;
|
|
8938
|
+
if (useJson) {
|
|
8939
|
+
jsonOut(buildErrorResponse("deploy", "Deployed artifact does not match pushed commit", detail, [
|
|
8940
|
+
"Run `runwork dev --restart --detach` to resync the build sandbox, then retry `runwork deploy`",
|
|
8941
|
+
"Verify with `runwork logs --production --once` which version is actually serving"
|
|
8942
|
+
]));
|
|
8943
|
+
process.exit(1);
|
|
8944
|
+
}
|
|
8945
|
+
console.error(`Deploy verification failed: ${detail}`);
|
|
8946
|
+
process.exit(1);
|
|
8947
|
+
}
|
|
8924
8948
|
if (deployedSha) {
|
|
8925
8949
|
writeDeployState(cwd, { sha: deployedSha, deployedAt: finishedAt, url: deploymentUrl });
|
|
8926
8950
|
}
|
|
@@ -8937,7 +8961,9 @@ var deployCommand = new Command5("deploy").description("Deploy the current app t
|
|
|
8937
8961
|
deployed,
|
|
8938
8962
|
url: deploymentUrl,
|
|
8939
8963
|
appName: config.appName,
|
|
8940
|
-
deployedSha
|
|
8964
|
+
deployedSha,
|
|
8965
|
+
builtFromSha: result.builtFromSha,
|
|
8966
|
+
shaVerified: shaVerdict === "match"
|
|
8941
8967
|
},
|
|
8942
8968
|
guide: buildDeployGuide(),
|
|
8943
8969
|
...deployGuardWarning ? { warning: deployGuardWarning } : {}
|
|
@@ -8947,8 +8973,13 @@ var deployCommand = new Command5("deploy").description("Deploy the current app t
|
|
|
8947
8973
|
}
|
|
8948
8974
|
console.log(`Deployed: ${deploymentUrl}`);
|
|
8949
8975
|
if (deployedSha)
|
|
8950
|
-
console.log(dim(`Commit: ${deployedSha.slice(0, 7)}`));
|
|
8976
|
+
console.log(dim(`Commit: ${deployedSha.slice(0, 7)}${shaVerdict === "match" ? " (verified)" : shaVerdict === "unverified" ? " (provenance unverified by server)" : ""}`));
|
|
8951
8977
|
});
|
|
8978
|
+
function assessBuiltSha(localSha, builtFromSha) {
|
|
8979
|
+
if (!localSha || !builtFromSha)
|
|
8980
|
+
return "unverified";
|
|
8981
|
+
return builtFromSha === localSha ? "match" : "mismatch";
|
|
8982
|
+
}
|
|
8952
8983
|
function printDeployStatus(cwd, useJson) {
|
|
8953
8984
|
const status = readDeployStatus(cwd);
|
|
8954
8985
|
const summary = getDeploySummary(cwd);
|
|
@@ -9950,7 +9981,7 @@ Used by your team (${teamOnly.length} more):
|
|
|
9950
9981
|
console.log(` ${t.canonicalId} - Used by ${display}`);
|
|
9951
9982
|
}
|
|
9952
9983
|
console.log(`
|
|
9953
|
-
Connect at https://runwork.ai/
|
|
9984
|
+
Connect at https://runwork.ai/integrations`);
|
|
9954
9985
|
}
|
|
9955
9986
|
} catch (err) {
|
|
9956
9987
|
console.error("Failed to list integrations:", err instanceof Error ? err.message : err);
|
|
@@ -11415,6 +11446,22 @@ function formatCombinedDigest(sessions, opts = { days: 7 }) {
|
|
|
11415
11446
|
`);
|
|
11416
11447
|
}
|
|
11417
11448
|
|
|
11449
|
+
// src/agents/utils/skill-name.ts
|
|
11450
|
+
function canonicalSkillName(raw) {
|
|
11451
|
+
const afterPrefix = raw.includes(":") ? raw.slice(raw.lastIndexOf(":") + 1) : raw;
|
|
11452
|
+
return afterPrefix.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
11453
|
+
}
|
|
11454
|
+
function skillNameFromPath(path2) {
|
|
11455
|
+
const trimmed = path2.trim().replace(/\/SKILL\.md$/i, "");
|
|
11456
|
+
if (!trimmed.includes("/") || /[<>]/.test(trimmed))
|
|
11457
|
+
return null;
|
|
11458
|
+
const segment = trimmed.replace(/\/+$/, "").split("/").pop();
|
|
11459
|
+
if (!segment)
|
|
11460
|
+
return null;
|
|
11461
|
+
const canonical = canonicalSkillName(segment);
|
|
11462
|
+
return canonical || null;
|
|
11463
|
+
}
|
|
11464
|
+
|
|
11418
11465
|
// src/agents/utils/json-config.ts
|
|
11419
11466
|
import { readFileSync as readFileSync22, writeFileSync as writeFileSync14, mkdirSync as mkdirSync13, existsSync as existsSync25 } from "fs";
|
|
11420
11467
|
import { dirname as dirname5 } from "path";
|
|
@@ -12138,6 +12185,7 @@ ${instructions}`;
|
|
|
12138
12185
|
let outputTokens = 0;
|
|
12139
12186
|
let cacheReadTokens = 0;
|
|
12140
12187
|
let latestActivityMs = 0;
|
|
12188
|
+
const activeDays = new Set;
|
|
12141
12189
|
const seenEntries = new Set;
|
|
12142
12190
|
let cwdEntries;
|
|
12143
12191
|
try {
|
|
@@ -12191,6 +12239,7 @@ ${instructions}`;
|
|
|
12191
12239
|
sessionHadActivity = true;
|
|
12192
12240
|
if (ts > latestActivityMs)
|
|
12193
12241
|
latestActivityMs = ts;
|
|
12242
|
+
activeDays.add(new Date(ts).toISOString().slice(0, 10));
|
|
12194
12243
|
const type = entry.type;
|
|
12195
12244
|
if (type === "user" || type === "assistant") {
|
|
12196
12245
|
const msg = entry.message;
|
|
@@ -12243,6 +12292,7 @@ ${instructions}`;
|
|
|
12243
12292
|
aiLinesAdded: 0,
|
|
12244
12293
|
aiLinesRemoved: 0,
|
|
12245
12294
|
lastActiveAt: latestActivityMs > 0 ? new Date(latestActivityMs).toISOString() : null,
|
|
12295
|
+
activeDays: [...activeDays].sort(),
|
|
12246
12296
|
extra: { inputTokens, outputTokens, cacheReadTokens }
|
|
12247
12297
|
};
|
|
12248
12298
|
} catch {
|
|
@@ -12347,7 +12397,7 @@ ${instructions}`;
|
|
|
12347
12397
|
} catch {
|
|
12348
12398
|
continue;
|
|
12349
12399
|
}
|
|
12350
|
-
|
|
12400
|
+
const pendingToolUses = [];
|
|
12351
12401
|
for (const line of content.split(`
|
|
12352
12402
|
`)) {
|
|
12353
12403
|
if (!line)
|
|
@@ -12365,39 +12415,48 @@ ${instructions}`;
|
|
|
12365
12415
|
if (!Number.isFinite(ts) || ts <= sinceMs)
|
|
12366
12416
|
continue;
|
|
12367
12417
|
const type = entry.type;
|
|
12368
|
-
|
|
12369
|
-
|
|
12370
|
-
|
|
12371
|
-
|
|
12372
|
-
|
|
12373
|
-
|
|
12374
|
-
|
|
12375
|
-
|
|
12376
|
-
|
|
12377
|
-
|
|
12418
|
+
const msg = entry.message;
|
|
12419
|
+
if (type === "assistant" && msg && typeof msg === "object") {
|
|
12420
|
+
const blocks = Array.isArray(msg.content) ? msg.content : [];
|
|
12421
|
+
for (const block of blocks) {
|
|
12422
|
+
if (block && block.type === "tool_use" && block.name === "Skill") {
|
|
12423
|
+
const input = block.input;
|
|
12424
|
+
if (input && typeof input.skill === "string") {
|
|
12425
|
+
const name = canonicalSkillName(input.skill);
|
|
12426
|
+
if (name)
|
|
12427
|
+
pendingToolUses.push({ name, ts });
|
|
12378
12428
|
}
|
|
12379
12429
|
}
|
|
12380
12430
|
}
|
|
12381
12431
|
}
|
|
12382
|
-
if (type === "user") {
|
|
12383
|
-
const
|
|
12384
|
-
|
|
12385
|
-
|
|
12386
|
-
if (
|
|
12387
|
-
|
|
12388
|
-
|
|
12389
|
-
|
|
12390
|
-
|
|
12391
|
-
|
|
12392
|
-
pendingCommand = null;
|
|
12432
|
+
if (type === "user" && msg && typeof msg === "object") {
|
|
12433
|
+
const texts = [];
|
|
12434
|
+
if (typeof msg.content === "string") {
|
|
12435
|
+
texts.push(msg.content);
|
|
12436
|
+
} else if (Array.isArray(msg.content)) {
|
|
12437
|
+
for (const block of msg.content) {
|
|
12438
|
+
if (block && typeof block === "object" && block.type === "text" && typeof block.text === "string") {
|
|
12439
|
+
texts.push(block.text);
|
|
12440
|
+
}
|
|
12441
|
+
}
|
|
12393
12442
|
}
|
|
12394
|
-
const
|
|
12395
|
-
|
|
12396
|
-
|
|
12397
|
-
|
|
12443
|
+
for (const text2 of texts) {
|
|
12444
|
+
const m = text2.match(/^Base directory for this skill: (.+)$/m);
|
|
12445
|
+
if (!m)
|
|
12446
|
+
continue;
|
|
12447
|
+
const name = skillNameFromPath(m[1]);
|
|
12448
|
+
if (!name)
|
|
12449
|
+
continue;
|
|
12450
|
+
const pendingIdx = pendingToolUses.findIndex((p) => p.name === name);
|
|
12451
|
+
if (pendingIdx >= 0)
|
|
12452
|
+
pendingToolUses.splice(pendingIdx, 1);
|
|
12453
|
+
recordSkill(name, ts);
|
|
12398
12454
|
}
|
|
12399
12455
|
}
|
|
12400
12456
|
}
|
|
12457
|
+
for (const pending of pendingToolUses) {
|
|
12458
|
+
recordSkill(pending.name, pending.ts);
|
|
12459
|
+
}
|
|
12401
12460
|
}
|
|
12402
12461
|
}
|
|
12403
12462
|
if (skillCounts.size === 0)
|
|
@@ -12971,11 +13030,14 @@ class ClaudeDesktopAdapter {
|
|
|
12971
13030
|
const modelsUsed = new Set;
|
|
12972
13031
|
let maxMcpTools = 0;
|
|
12973
13032
|
const agentSessionsDir = join23(claudeAppDir, "local-agent-mode-sessions");
|
|
13033
|
+
const activeDays = new Set;
|
|
12974
13034
|
if (existsSync29(agentSessionsDir)) {
|
|
12975
13035
|
this.walkSessionDirs(agentSessionsDir, sinceMs, (session) => {
|
|
12976
13036
|
const sessionTime = session.lastActivityAt ?? session.createdAt ?? 0;
|
|
12977
|
-
if (sessionTime > sinceMs)
|
|
13037
|
+
if (sessionTime > sinceMs) {
|
|
12978
13038
|
sessionCount++;
|
|
13039
|
+
activeDays.add(new Date(sessionTime).toISOString().slice(0, 10));
|
|
13040
|
+
}
|
|
12979
13041
|
if (sessionTime > latestActivity)
|
|
12980
13042
|
latestActivity = sessionTime;
|
|
12981
13043
|
if (session.model)
|
|
@@ -12991,8 +13053,10 @@ class ClaudeDesktopAdapter {
|
|
|
12991
13053
|
if (existsSync29(codeSessionsDir)) {
|
|
12992
13054
|
this.walkSessionDirs(codeSessionsDir, sinceMs, (session) => {
|
|
12993
13055
|
const sessionTime = session.lastActivityAt ?? session.createdAt ?? 0;
|
|
12994
|
-
if (sessionTime > sinceMs)
|
|
13056
|
+
if (sessionTime > sinceMs) {
|
|
12995
13057
|
sessionCount++;
|
|
13058
|
+
activeDays.add(new Date(sessionTime).toISOString().slice(0, 10));
|
|
13059
|
+
}
|
|
12996
13060
|
if (sessionTime > latestActivity)
|
|
12997
13061
|
latestActivity = sessionTime;
|
|
12998
13062
|
if (session.model)
|
|
@@ -13025,6 +13089,7 @@ class ClaudeDesktopAdapter {
|
|
|
13025
13089
|
aiLinesAdded: 0,
|
|
13026
13090
|
aiLinesRemoved: 0,
|
|
13027
13091
|
lastActiveAt: latestActivity > 0 ? new Date(latestActivity).toISOString() : null,
|
|
13092
|
+
activeDays: activeDays.size > 0 ? [...activeDays].sort() : undefined,
|
|
13028
13093
|
modelsUsed: Array.from(modelsUsed),
|
|
13029
13094
|
mcpToolCount: maxMcpTools > 0 ? maxMcpTools : undefined,
|
|
13030
13095
|
extra: scheduledTaskRuns > 0 ? { scheduledTaskRuns } : undefined
|
|
@@ -13033,46 +13098,6 @@ class ClaudeDesktopAdapter {
|
|
|
13033
13098
|
return null;
|
|
13034
13099
|
}
|
|
13035
13100
|
}
|
|
13036
|
-
async readSessionDigests(sinceISO) {
|
|
13037
|
-
try {
|
|
13038
|
-
const os2 = platform3();
|
|
13039
|
-
const claudeAppDir = os2 === "darwin" ? join23(homedir6(), "Library", "Application Support", "Claude") : os2 === "win32" ? join23(process.env.APPDATA || join23(homedir6(), "AppData", "Roaming"), "Claude") : join23(homedir6(), ".config", "Claude");
|
|
13040
|
-
const sessionsDir = join23(claudeAppDir, "claude-code-sessions");
|
|
13041
|
-
if (!existsSync29(sessionsDir))
|
|
13042
|
-
return null;
|
|
13043
|
-
const sinceMs = sinceISO ? new Date(sinceISO).getTime() : 0;
|
|
13044
|
-
let files;
|
|
13045
|
-
try {
|
|
13046
|
-
files = readdirSync6(sessionsDir).filter((f) => f.endsWith(".jsonl"));
|
|
13047
|
-
} catch {
|
|
13048
|
-
return null;
|
|
13049
|
-
}
|
|
13050
|
-
const digests = [];
|
|
13051
|
-
for (const file of files) {
|
|
13052
|
-
const filePath = join23(sessionsDir, file);
|
|
13053
|
-
let stat;
|
|
13054
|
-
try {
|
|
13055
|
-
stat = statSync4(filePath);
|
|
13056
|
-
} catch {
|
|
13057
|
-
continue;
|
|
13058
|
-
}
|
|
13059
|
-
if (stat.mtimeMs <= sinceMs)
|
|
13060
|
-
continue;
|
|
13061
|
-
let content;
|
|
13062
|
-
try {
|
|
13063
|
-
content = readFileSync25(filePath, "utf-8");
|
|
13064
|
-
} catch {
|
|
13065
|
-
continue;
|
|
13066
|
-
}
|
|
13067
|
-
const digest = extractClaudeJsonlSession(content, this.slug, file.replace(/\.jsonl$/, ""));
|
|
13068
|
-
if (digest)
|
|
13069
|
-
digests.push(digest);
|
|
13070
|
-
}
|
|
13071
|
-
return digests;
|
|
13072
|
-
} catch {
|
|
13073
|
-
return null;
|
|
13074
|
-
}
|
|
13075
|
-
}
|
|
13076
13101
|
async readVersion() {
|
|
13077
13102
|
try {
|
|
13078
13103
|
const os2 = platform3();
|
|
@@ -13093,53 +13118,6 @@ class ClaudeDesktopAdapter {
|
|
|
13093
13118
|
return null;
|
|
13094
13119
|
}
|
|
13095
13120
|
}
|
|
13096
|
-
async readSkillUsage(lastSyncAt) {
|
|
13097
|
-
try {
|
|
13098
|
-
const baseDir = getCoworkBaseDir();
|
|
13099
|
-
if (!existsSync29(baseDir))
|
|
13100
|
-
return null;
|
|
13101
|
-
const sinceMs = lastSyncAt ? new Date(lastSyncAt).getTime() : 0;
|
|
13102
|
-
const skillCounts = new Map;
|
|
13103
|
-
const agentSessionsDir = baseDir;
|
|
13104
|
-
this.walkSessionDirs(agentSessionsDir, sinceMs, (session) => {
|
|
13105
|
-
const sessionTime = session.lastActivityAt ?? session.createdAt ?? 0;
|
|
13106
|
-
if (sessionTime <= sinceMs)
|
|
13107
|
-
return;
|
|
13108
|
-
const mcpTools = session.enabledMcpTools;
|
|
13109
|
-
if (!mcpTools || typeof mcpTools !== "object")
|
|
13110
|
-
return;
|
|
13111
|
-
for (const toolName of Object.keys(mcpTools)) {
|
|
13112
|
-
const skillMatch = toolName.match(/skill_([A-Za-z0-9_]+)/);
|
|
13113
|
-
if (!skillMatch)
|
|
13114
|
-
continue;
|
|
13115
|
-
const rawName = skillMatch[1].replace(/_/g, " ").trim();
|
|
13116
|
-
if (!rawName)
|
|
13117
|
-
continue;
|
|
13118
|
-
const existing = skillCounts.get(rawName);
|
|
13119
|
-
if (existing) {
|
|
13120
|
-
existing.count++;
|
|
13121
|
-
if (sessionTime > existing.lastTs)
|
|
13122
|
-
existing.lastTs = sessionTime;
|
|
13123
|
-
} else {
|
|
13124
|
-
skillCounts.set(rawName, { count: 1, lastTs: sessionTime });
|
|
13125
|
-
}
|
|
13126
|
-
}
|
|
13127
|
-
});
|
|
13128
|
-
if (skillCounts.size === 0)
|
|
13129
|
-
return null;
|
|
13130
|
-
const results = [];
|
|
13131
|
-
for (const [skillName, { count, lastTs }] of skillCounts) {
|
|
13132
|
-
results.push({
|
|
13133
|
-
skillName,
|
|
13134
|
-
count,
|
|
13135
|
-
lastUsedAt: new Date(lastTs).toISOString()
|
|
13136
|
-
});
|
|
13137
|
-
}
|
|
13138
|
-
return results;
|
|
13139
|
-
} catch {
|
|
13140
|
-
return null;
|
|
13141
|
-
}
|
|
13142
|
-
}
|
|
13143
13121
|
walkSessionDirs(baseDir, _sinceMs, onSession) {
|
|
13144
13122
|
try {
|
|
13145
13123
|
for (const orgDir of readdirSync6(baseDir)) {
|
|
@@ -13420,14 +13398,18 @@ ${instructions}`;
|
|
|
13420
13398
|
try {
|
|
13421
13399
|
const sinceMs = lastSyncAt ? new Date(lastSyncAt).getTime() : 0;
|
|
13422
13400
|
const globalDbPath = this.globalStorageDbPath();
|
|
13423
|
-
let
|
|
13401
|
+
let newComposersWithoutId = 0;
|
|
13402
|
+
const activeComposerIds = new Set;
|
|
13403
|
+
const activeDays = new Set;
|
|
13404
|
+
let messageCount = 0;
|
|
13405
|
+
let toolCallCount = 0;
|
|
13424
13406
|
let totalLinesAdded = 0;
|
|
13425
13407
|
let totalLinesRemoved = 0;
|
|
13426
13408
|
let latestActivity = 0;
|
|
13427
13409
|
let agenticSessions = 0;
|
|
13428
13410
|
let chatSessions = 0;
|
|
13429
13411
|
if (existsSync30(globalDbPath)) {
|
|
13430
|
-
const composerResult = queryReadonlySqlite(globalDbPath, `SELECT value FROM cursorDiskKV WHERE key LIKE 'composerData
|
|
13412
|
+
const composerResult = queryReadonlySqlite(globalDbPath, `SELECT value FROM cursorDiskKV WHERE key LIKE 'composerData:%'`);
|
|
13431
13413
|
if (composerResult) {
|
|
13432
13414
|
for (const line of composerResult.split(`
|
|
13433
13415
|
`)) {
|
|
@@ -13437,7 +13419,11 @@ ${instructions}`;
|
|
|
13437
13419
|
const data = JSON.parse(line);
|
|
13438
13420
|
const createdAt = data.createdAt ?? 0;
|
|
13439
13421
|
if (createdAt > sinceMs) {
|
|
13440
|
-
|
|
13422
|
+
if (typeof data.composerId === "string")
|
|
13423
|
+
activeComposerIds.add(data.composerId);
|
|
13424
|
+
else
|
|
13425
|
+
newComposersWithoutId++;
|
|
13426
|
+
activeDays.add(new Date(createdAt).toISOString().slice(0, 10));
|
|
13441
13427
|
totalLinesAdded += data.totalLinesAdded ?? 0;
|
|
13442
13428
|
totalLinesRemoved += data.totalLinesRemoved ?? 0;
|
|
13443
13429
|
if (data.unifiedMode === "agent" || data.isAgentic)
|
|
@@ -13452,7 +13438,34 @@ ${instructions}`;
|
|
|
13452
13438
|
}
|
|
13453
13439
|
}
|
|
13454
13440
|
}
|
|
13441
|
+
const bubbleResult = queryReadonlySqlite(globalDbPath, `SELECT json_object('k', key, 'type', json_extract(value,'$.type'), 'ts', json_extract(value,'$.createdAt'), 'tool', json_extract(value,'$.toolFormerData.name')) FROM cursorDiskKV WHERE key LIKE 'bubbleId:%'`);
|
|
13442
|
+
if (bubbleResult) {
|
|
13443
|
+
for (const line of bubbleResult.split(`
|
|
13444
|
+
`)) {
|
|
13445
|
+
if (!line.trim())
|
|
13446
|
+
continue;
|
|
13447
|
+
try {
|
|
13448
|
+
const b = JSON.parse(line);
|
|
13449
|
+
const ts = typeof b.ts === "string" ? Date.parse(b.ts) : NaN;
|
|
13450
|
+
if (!Number.isFinite(ts) || ts <= sinceMs)
|
|
13451
|
+
continue;
|
|
13452
|
+
if (ts > latestActivity)
|
|
13453
|
+
latestActivity = ts;
|
|
13454
|
+
activeDays.add(new Date(ts).toISOString().slice(0, 10));
|
|
13455
|
+
const parts = (b.k ?? "").split(":");
|
|
13456
|
+
if (parts.length >= 3)
|
|
13457
|
+
activeComposerIds.add(parts[1]);
|
|
13458
|
+
if (b.tool)
|
|
13459
|
+
toolCallCount++;
|
|
13460
|
+
else if (b.type === 1 || b.type === 2)
|
|
13461
|
+
messageCount++;
|
|
13462
|
+
} catch {
|
|
13463
|
+
continue;
|
|
13464
|
+
}
|
|
13465
|
+
}
|
|
13466
|
+
}
|
|
13455
13467
|
}
|
|
13468
|
+
const sessionCount = newComposersWithoutId + activeComposerIds.size;
|
|
13456
13469
|
const trackingDbPath = join24(homedir7(), ".cursor", "ai-tracking", "ai-code-tracking.db");
|
|
13457
13470
|
let aiCommitCount = 0;
|
|
13458
13471
|
let avgAiPercent = 0;
|
|
@@ -13465,17 +13478,19 @@ ${instructions}`;
|
|
|
13465
13478
|
avgAiPercent = parseFloat(parts[1]) || 0;
|
|
13466
13479
|
}
|
|
13467
13480
|
}
|
|
13468
|
-
|
|
13481
|
+
const hasNewActivity = sessionCount > 0 || messageCount > 0;
|
|
13482
|
+
if (!hasNewActivity && latestActivity <= sinceMs)
|
|
13469
13483
|
return null;
|
|
13470
13484
|
return {
|
|
13471
|
-
hasNewActivity
|
|
13485
|
+
hasNewActivity,
|
|
13472
13486
|
sessionCount,
|
|
13473
|
-
messageCount
|
|
13474
|
-
toolCallCount
|
|
13487
|
+
messageCount,
|
|
13488
|
+
toolCallCount,
|
|
13475
13489
|
tokensUsed: 0,
|
|
13476
13490
|
aiLinesAdded: totalLinesAdded,
|
|
13477
13491
|
aiLinesRemoved: totalLinesRemoved,
|
|
13478
13492
|
lastActiveAt: latestActivity > 0 ? new Date(latestActivity).toISOString() : null,
|
|
13493
|
+
activeDays: activeDays.size > 0 ? [...activeDays].sort() : undefined,
|
|
13479
13494
|
extra: {
|
|
13480
13495
|
agenticSessions,
|
|
13481
13496
|
chatSessions,
|
|
@@ -13710,6 +13725,8 @@ var AGENT_REGISTRY = [
|
|
|
13710
13725
|
},
|
|
13711
13726
|
manualSetup: {
|
|
13712
13727
|
title: "Install Runwork plugin",
|
|
13728
|
+
action: "Install Runwork plugin",
|
|
13729
|
+
preposition: "for",
|
|
13713
13730
|
showAfter: "connecting",
|
|
13714
13731
|
downloadArtifact: {
|
|
13715
13732
|
label: "Download plugin",
|
|
@@ -13717,7 +13734,7 @@ var AGENT_REGISTRY = [
|
|
|
13717
13734
|
command: "build-plugin"
|
|
13718
13735
|
},
|
|
13719
13736
|
steps: [
|
|
13720
|
-
{ id: "download", label: "
|
|
13737
|
+
{ id: "download", label: "Download the plugin. It saves runwork-plugin.zip to your Downloads folder.", control: "download-artifact" },
|
|
13721
13738
|
{ id: "open", label: "Open Claude Desktop and sign in if you haven't already" },
|
|
13722
13739
|
{ id: "cowork", label: "Go to the Cowork tab (top of the window)" },
|
|
13723
13740
|
{ id: "customize", label: "Click Customize in the sidebar" },
|
|
@@ -13891,6 +13908,8 @@ var AGENT_REGISTRY = [
|
|
|
13891
13908
|
},
|
|
13892
13909
|
manualSetup: {
|
|
13893
13910
|
title: "Connect Runwork in ChatGPT",
|
|
13911
|
+
action: "Create Runwork connector",
|
|
13912
|
+
preposition: "in",
|
|
13894
13913
|
showAfter: "connecting",
|
|
13895
13914
|
copyValue: { label: "Workspace MCP URL", token: "{mcpUrl}" },
|
|
13896
13915
|
steps: [
|
|
@@ -13898,7 +13917,7 @@ var AGENT_REGISTRY = [
|
|
|
13898
13917
|
{ id: "settings-apps", label: "Open the profile menu (bottom-left), then Settings, then Apps" },
|
|
13899
13918
|
{ id: "dev-mode", label: 'Click "Advanced settings" and turn on "Developer mode" (skip if it is already on)' },
|
|
13900
13919
|
{ id: "create-app", label: 'Go back to Apps and click "Create app"' },
|
|
13901
|
-
{ id: "fill", label: 'In the New App dialog, set Name to "Runwork", keep Connection on "Server URL",
|
|
13920
|
+
{ id: "fill", label: 'In the New App dialog, set Name to "Runwork", keep Connection on "Server URL", then paste this Workspace MCP URL into the Server URL field:', control: "copy-value" },
|
|
13902
13921
|
{ id: "oauth", label: 'Leave Authentication set to "OAuth", check "I understand and want to continue", then click Create' },
|
|
13903
13922
|
{ id: "authorize", label: "Complete the OAuth sign-in to authorize Runwork for your account" },
|
|
13904
13923
|
{ id: "confirm", label: 'In a new chat, ask "Do you have access to Runwork tools?" to confirm (or check Settings, then Apps, for Runwork)' }
|
|
@@ -13908,6 +13927,8 @@ var AGENT_REGISTRY = [
|
|
|
13908
13927
|
key: "team",
|
|
13909
13928
|
label: "Team / Enterprise",
|
|
13910
13929
|
title: "Connect the Runwork app in ChatGPT",
|
|
13930
|
+
action: "Connect the Runwork app",
|
|
13931
|
+
preposition: "in",
|
|
13911
13932
|
steps: [
|
|
13912
13933
|
{ id: "open", label: "Open chatgpt.com and sign in" },
|
|
13913
13934
|
{ id: "settings-apps", label: "Open the profile menu (bottom-left), then Settings, then Apps" },
|
|
@@ -13920,12 +13941,14 @@ var AGENT_REGISTRY = [
|
|
|
13920
13941
|
key: "personal",
|
|
13921
13942
|
label: "Personal",
|
|
13922
13943
|
title: "Create the Runwork connector in ChatGPT",
|
|
13944
|
+
action: "Create Runwork connector",
|
|
13945
|
+
preposition: "in",
|
|
13923
13946
|
steps: [
|
|
13924
13947
|
{ id: "open", label: "Open chatgpt.com and sign in" },
|
|
13925
13948
|
{ id: "settings-apps", label: "Open the profile menu (bottom-left), then Settings, then Apps" },
|
|
13926
13949
|
{ id: "dev-mode", label: 'Click "Advanced settings" and turn on "Developer mode" (skip if it is already on)' },
|
|
13927
13950
|
{ id: "create-app", label: 'Go back to Apps and click "Create app"' },
|
|
13928
|
-
{ id: "fill", label: 'In the New App dialog, set Name to "Runwork", keep Connection on "Server URL",
|
|
13951
|
+
{ id: "fill", label: 'In the New App dialog, set Name to "Runwork", keep Connection on "Server URL", then paste this Workspace MCP URL into the Server URL field:', control: "copy-value" },
|
|
13929
13952
|
{ id: "oauth", label: 'Leave Authentication set to "OAuth", check "I understand and want to continue", then click Create' },
|
|
13930
13953
|
{ id: "authorize", label: "Complete the OAuth sign-in to authorize Runwork for your account" },
|
|
13931
13954
|
{ id: "confirm", label: 'In a new chat, ask "Do you have access to Runwork tools?" to confirm (or check Settings, then Apps, for Runwork)' }
|
|
@@ -13965,6 +13988,8 @@ var AGENT_REGISTRY = [
|
|
|
13965
13988
|
},
|
|
13966
13989
|
manualSetup: {
|
|
13967
13990
|
title: "Connect Runwork in ChatGPT",
|
|
13991
|
+
action: "Create Runwork connector",
|
|
13992
|
+
preposition: "in",
|
|
13968
13993
|
showAfter: "connecting",
|
|
13969
13994
|
copyValue: { label: "Workspace MCP URL", token: "{mcpUrl}" },
|
|
13970
13995
|
steps: [
|
|
@@ -13973,7 +13998,7 @@ var AGENT_REGISTRY = [
|
|
|
13973
13998
|
{ id: "settings-apps", label: "Open the profile menu (bottom-left), then Settings, then Apps" },
|
|
13974
13999
|
{ id: "dev-mode", label: 'Click "Advanced settings" and turn on "Developer mode" (skip if it is already on)' },
|
|
13975
14000
|
{ id: "create-app", label: 'Go back to Apps and click "Create app"' },
|
|
13976
|
-
{ id: "fill", label: 'In the New App dialog, set Name to "Runwork", keep Connection on "Server URL",
|
|
14001
|
+
{ id: "fill", label: 'In the New App dialog, set Name to "Runwork", keep Connection on "Server URL", then paste this Workspace MCP URL into the Server URL field:', control: "copy-value" },
|
|
13977
14002
|
{ id: "oauth", label: 'Leave Authentication set to "OAuth", check "I understand and want to continue", then click Create' },
|
|
13978
14003
|
{ id: "authorize", label: "Complete the OAuth sign-in to authorize Runwork for your account" },
|
|
13979
14004
|
{ id: "confirm", label: 'Open the ChatGPT desktop app and, in a new chat, ask "Do you have access to Runwork tools?" to confirm' }
|
|
@@ -13983,6 +14008,8 @@ var AGENT_REGISTRY = [
|
|
|
13983
14008
|
key: "team",
|
|
13984
14009
|
label: "Team / Enterprise",
|
|
13985
14010
|
title: "Connect the Runwork app in ChatGPT",
|
|
14011
|
+
action: "Connect the Runwork app",
|
|
14012
|
+
preposition: "in",
|
|
13986
14013
|
steps: [
|
|
13987
14014
|
{ id: "install", label: "Install the ChatGPT desktop app from chatgpt.com/download if you have not already" },
|
|
13988
14015
|
{ id: "open", label: "Open the ChatGPT desktop app and sign in" },
|
|
@@ -13996,13 +14023,15 @@ var AGENT_REGISTRY = [
|
|
|
13996
14023
|
key: "personal",
|
|
13997
14024
|
label: "Personal",
|
|
13998
14025
|
title: "Create the Runwork connector in ChatGPT",
|
|
14026
|
+
action: "Create Runwork connector",
|
|
14027
|
+
preposition: "in",
|
|
13999
14028
|
steps: [
|
|
14000
14029
|
{ id: "install", label: "Install the ChatGPT desktop app from chatgpt.com/download if you have not already" },
|
|
14001
14030
|
{ id: "open-web", label: "Connectors are created on chatgpt.com: open it in your browser and sign in" },
|
|
14002
14031
|
{ id: "settings-apps", label: "Open the profile menu (bottom-left), then Settings, then Apps" },
|
|
14003
14032
|
{ id: "dev-mode", label: 'Click "Advanced settings" and turn on "Developer mode" (skip if it is already on)' },
|
|
14004
14033
|
{ id: "create-app", label: 'Go back to Apps and click "Create app"' },
|
|
14005
|
-
{ id: "fill", label: 'In the New App dialog, set Name to "Runwork", keep Connection on "Server URL",
|
|
14034
|
+
{ id: "fill", label: 'In the New App dialog, set Name to "Runwork", keep Connection on "Server URL", then paste this Workspace MCP URL into the Server URL field:', control: "copy-value" },
|
|
14006
14035
|
{ id: "oauth", label: 'Leave Authentication set to "OAuth", check "I understand and want to continue", then click Create' },
|
|
14007
14036
|
{ id: "authorize", label: "Complete the OAuth sign-in to authorize Runwork for your account" },
|
|
14008
14037
|
{ id: "confirm", label: 'Open the ChatGPT desktop app and, in a new chat, ask "Do you have access to Runwork tools?" to confirm' }
|
|
@@ -14492,50 +14521,162 @@ class CodexAdapter {
|
|
|
14492
14521
|
return null;
|
|
14493
14522
|
const sinceMs = lastSyncAt ? new Date(lastSyncAt).getTime() : 0;
|
|
14494
14523
|
const sinceSec = Math.floor(sinceMs / 1000);
|
|
14524
|
+
const rollout = this.scanRolloutActivity(sinceMs);
|
|
14525
|
+
let sessionCount = rollout.sessionCount;
|
|
14526
|
+
let tokensUsed = rollout.tokensUsed;
|
|
14527
|
+
let latestMs = rollout.latestMs;
|
|
14528
|
+
let versions = [];
|
|
14495
14529
|
const dbPath = join28(codexDir, "state_5.sqlite");
|
|
14496
|
-
if (
|
|
14497
|
-
|
|
14498
|
-
|
|
14499
|
-
|
|
14500
|
-
|
|
14501
|
-
|
|
14502
|
-
|
|
14503
|
-
|
|
14504
|
-
|
|
14505
|
-
|
|
14506
|
-
|
|
14507
|
-
|
|
14508
|
-
|
|
14509
|
-
|
|
14510
|
-
|
|
14511
|
-
|
|
14512
|
-
|
|
14513
|
-
|
|
14514
|
-
|
|
14515
|
-
|
|
14516
|
-
|
|
14517
|
-
|
|
14530
|
+
if (existsSync33(dbPath)) {
|
|
14531
|
+
const countResult = queryReadonlySqlite(dbPath, `SELECT count(*) FROM threads WHERE updated_at > ${sinceSec}`);
|
|
14532
|
+
const dbSessionCount = parseInt(countResult) || 0;
|
|
14533
|
+
const tokensResult = queryReadonlySqlite(dbPath, `SELECT coalesce(sum(tokens_used), 0) FROM threads WHERE updated_at > ${sinceSec}`);
|
|
14534
|
+
const dbTokens = parseInt(tokensResult) || 0;
|
|
14535
|
+
const latestResult = queryReadonlySqlite(dbPath, `SELECT max(updated_at) FROM threads`);
|
|
14536
|
+
const latestSec = parseInt(latestResult) || 0;
|
|
14537
|
+
const versionsResult = queryReadonlySqlite(dbPath, `SELECT DISTINCT cli_version FROM threads WHERE cli_version IS NOT NULL ORDER BY created_at DESC LIMIT 3`);
|
|
14538
|
+
versions = versionsResult ? versionsResult.split(/[\r\n]+/).filter(Boolean) : [];
|
|
14539
|
+
sessionCount = Math.max(dbSessionCount, rollout.sessionCount);
|
|
14540
|
+
if (dbTokens > 0)
|
|
14541
|
+
tokensUsed = dbTokens;
|
|
14542
|
+
if (latestSec * 1000 > latestMs)
|
|
14543
|
+
latestMs = latestSec * 1000;
|
|
14544
|
+
}
|
|
14545
|
+
let messageCount = rollout.messageCount;
|
|
14546
|
+
if (messageCount === 0) {
|
|
14547
|
+
const historyPath = join28(codexDir, "history.jsonl");
|
|
14548
|
+
if (existsSync33(historyPath)) {
|
|
14549
|
+
const content = readFileSync26(historyPath, "utf-8").trim();
|
|
14550
|
+
if (content) {
|
|
14551
|
+
for (const line of content.split(/[\r\n]+/)) {
|
|
14552
|
+
try {
|
|
14553
|
+
const entry = JSON.parse(line);
|
|
14554
|
+
if (entry.ts && entry.ts > sinceSec)
|
|
14555
|
+
messageCount++;
|
|
14556
|
+
} catch {
|
|
14557
|
+
continue;
|
|
14558
|
+
}
|
|
14518
14559
|
}
|
|
14519
14560
|
}
|
|
14520
14561
|
}
|
|
14521
14562
|
}
|
|
14522
|
-
|
|
14563
|
+
const hasNewActivity = sessionCount > 0 || messageCount > 0;
|
|
14564
|
+
if (!hasNewActivity && latestMs <= sinceMs)
|
|
14523
14565
|
return null;
|
|
14524
14566
|
return {
|
|
14525
|
-
hasNewActivity
|
|
14567
|
+
hasNewActivity,
|
|
14526
14568
|
sessionCount,
|
|
14527
14569
|
messageCount,
|
|
14528
|
-
toolCallCount:
|
|
14570
|
+
toolCallCount: rollout.toolCallCount,
|
|
14529
14571
|
tokensUsed,
|
|
14530
14572
|
aiLinesAdded: 0,
|
|
14531
14573
|
aiLinesRemoved: 0,
|
|
14532
|
-
lastActiveAt:
|
|
14574
|
+
lastActiveAt: latestMs > 0 ? new Date(latestMs).toISOString() : null,
|
|
14575
|
+
activeDays: rollout.activeDays.length > 0 ? rollout.activeDays : undefined,
|
|
14533
14576
|
extra: versions.length > 0 ? { cliVersions: versions } : undefined
|
|
14534
14577
|
};
|
|
14535
14578
|
} catch {
|
|
14536
14579
|
return null;
|
|
14537
14580
|
}
|
|
14538
14581
|
}
|
|
14582
|
+
scanRolloutActivity(sinceMs) {
|
|
14583
|
+
const days = new Set;
|
|
14584
|
+
const result = {
|
|
14585
|
+
sessionCount: 0,
|
|
14586
|
+
messageCount: 0,
|
|
14587
|
+
toolCallCount: 0,
|
|
14588
|
+
tokensUsed: 0,
|
|
14589
|
+
latestMs: 0,
|
|
14590
|
+
activeDays: []
|
|
14591
|
+
};
|
|
14592
|
+
const sessionsDir = join28(homedir11(), ".codex", "sessions");
|
|
14593
|
+
if (!existsSync33(sessionsDir))
|
|
14594
|
+
return result;
|
|
14595
|
+
const files = [];
|
|
14596
|
+
const walk = (dir) => {
|
|
14597
|
+
let entries;
|
|
14598
|
+
try {
|
|
14599
|
+
entries = readdirSync9(dir, { withFileTypes: true });
|
|
14600
|
+
} catch {
|
|
14601
|
+
return;
|
|
14602
|
+
}
|
|
14603
|
+
for (const e of entries) {
|
|
14604
|
+
const full = join28(dir, e.name);
|
|
14605
|
+
if (e.isDirectory())
|
|
14606
|
+
walk(full);
|
|
14607
|
+
else if (e.name.startsWith("rollout-") && e.name.endsWith(".jsonl"))
|
|
14608
|
+
files.push(full);
|
|
14609
|
+
}
|
|
14610
|
+
};
|
|
14611
|
+
walk(sessionsDir);
|
|
14612
|
+
for (const file of files) {
|
|
14613
|
+
let stat;
|
|
14614
|
+
try {
|
|
14615
|
+
stat = statSync5(file);
|
|
14616
|
+
} catch {
|
|
14617
|
+
continue;
|
|
14618
|
+
}
|
|
14619
|
+
if (stat.mtimeMs <= sinceMs)
|
|
14620
|
+
continue;
|
|
14621
|
+
let content;
|
|
14622
|
+
try {
|
|
14623
|
+
content = readFileSync26(file, "utf-8");
|
|
14624
|
+
} catch {
|
|
14625
|
+
continue;
|
|
14626
|
+
}
|
|
14627
|
+
let fileHadActivity = false;
|
|
14628
|
+
let fileTokens = 0;
|
|
14629
|
+
for (const line of content.split(`
|
|
14630
|
+
`)) {
|
|
14631
|
+
if (!line.trim())
|
|
14632
|
+
continue;
|
|
14633
|
+
let o;
|
|
14634
|
+
try {
|
|
14635
|
+
o = JSON.parse(line);
|
|
14636
|
+
} catch {
|
|
14637
|
+
continue;
|
|
14638
|
+
}
|
|
14639
|
+
const ts = typeof o.timestamp === "string" ? Date.parse(o.timestamp) : NaN;
|
|
14640
|
+
if (!Number.isFinite(ts) || ts <= sinceMs)
|
|
14641
|
+
continue;
|
|
14642
|
+
const p = o.payload;
|
|
14643
|
+
if (!p || typeof p !== "object")
|
|
14644
|
+
continue;
|
|
14645
|
+
const pt = p.type;
|
|
14646
|
+
if (o.type === "event_msg") {
|
|
14647
|
+
if (pt === "user_message" || pt === "agent_message") {
|
|
14648
|
+
result.messageCount++;
|
|
14649
|
+
fileHadActivity = true;
|
|
14650
|
+
if (ts > result.latestMs)
|
|
14651
|
+
result.latestMs = ts;
|
|
14652
|
+
days.add(new Date(ts).toISOString().slice(0, 10));
|
|
14653
|
+
} else if (pt === "token_count") {
|
|
14654
|
+
const info = p.info;
|
|
14655
|
+
const usage = info?.total_token_usage;
|
|
14656
|
+
if (usage) {
|
|
14657
|
+
const total = (usage.input_tokens ?? 0) + (usage.output_tokens ?? 0);
|
|
14658
|
+
if (total > fileTokens)
|
|
14659
|
+
fileTokens = total;
|
|
14660
|
+
}
|
|
14661
|
+
if (ts > result.latestMs)
|
|
14662
|
+
result.latestMs = ts;
|
|
14663
|
+
}
|
|
14664
|
+
} else if (o.type === "response_item" && (pt === "function_call" || pt === "tool_search_call")) {
|
|
14665
|
+
result.toolCallCount++;
|
|
14666
|
+
fileHadActivity = true;
|
|
14667
|
+
if (ts > result.latestMs)
|
|
14668
|
+
result.latestMs = ts;
|
|
14669
|
+
days.add(new Date(ts).toISOString().slice(0, 10));
|
|
14670
|
+
}
|
|
14671
|
+
}
|
|
14672
|
+
if (fileHadActivity) {
|
|
14673
|
+
result.sessionCount++;
|
|
14674
|
+
result.tokensUsed += fileTokens;
|
|
14675
|
+
}
|
|
14676
|
+
}
|
|
14677
|
+
result.activeDays = [...days].sort();
|
|
14678
|
+
return result;
|
|
14679
|
+
}
|
|
14539
14680
|
async readSessionDigests(sinceISO) {
|
|
14540
14681
|
try {
|
|
14541
14682
|
const sessionsDir = join28(homedir11(), ".codex", "sessions");
|
|
@@ -14653,9 +14794,10 @@ class CodexAdapter {
|
|
|
14653
14794
|
} catch {
|
|
14654
14795
|
return;
|
|
14655
14796
|
}
|
|
14797
|
+
const seenInFile = new Set;
|
|
14656
14798
|
for (const line of content.split(`
|
|
14657
14799
|
`)) {
|
|
14658
|
-
if (!line)
|
|
14800
|
+
if (!line || !line.includes("SKILL.md"))
|
|
14659
14801
|
continue;
|
|
14660
14802
|
let entry;
|
|
14661
14803
|
try {
|
|
@@ -14663,35 +14805,37 @@ class CodexAdapter {
|
|
|
14663
14805
|
} catch {
|
|
14664
14806
|
continue;
|
|
14665
14807
|
}
|
|
14666
|
-
if (entry.type !== "
|
|
14808
|
+
if (entry.type !== "response_item")
|
|
14667
14809
|
continue;
|
|
14668
14810
|
const payload = entry.payload;
|
|
14669
14811
|
if (!payload)
|
|
14670
14812
|
continue;
|
|
14813
|
+
const pt = payload.type;
|
|
14814
|
+
if (pt !== "function_call" && pt !== "custom_tool_call")
|
|
14815
|
+
continue;
|
|
14671
14816
|
const tsRaw = entry.timestamp;
|
|
14672
14817
|
const ts = typeof tsRaw === "string" ? Date.parse(tsRaw) : 0;
|
|
14673
|
-
if (ts <= sinceMs)
|
|
14818
|
+
if (!Number.isFinite(ts) || ts <= sinceMs)
|
|
14674
14819
|
continue;
|
|
14675
|
-
const
|
|
14676
|
-
|
|
14820
|
+
const args = typeof payload.arguments === "string" ? payload.arguments : JSON.stringify(payload.arguments ?? "");
|
|
14821
|
+
const m = args.match(/([^\s"']*\/skills\/[^\s"']*\/)SKILL\.md/);
|
|
14822
|
+
if (!m)
|
|
14677
14823
|
continue;
|
|
14678
|
-
const
|
|
14679
|
-
|
|
14680
|
-
|
|
14681
|
-
|
|
14682
|
-
|
|
14683
|
-
|
|
14684
|
-
|
|
14685
|
-
|
|
14686
|
-
|
|
14687
|
-
|
|
14688
|
-
|
|
14689
|
-
|
|
14690
|
-
|
|
14691
|
-
|
|
14692
|
-
}
|
|
14824
|
+
const skillDir = m[1];
|
|
14825
|
+
if (skillDir.includes("/plugins/cache/openai-") || skillDir.includes("/.system/"))
|
|
14826
|
+
continue;
|
|
14827
|
+
const skillName = skillNameFromPath(skillDir);
|
|
14828
|
+
if (!skillName || seenInFile.has(skillName))
|
|
14829
|
+
continue;
|
|
14830
|
+
seenInFile.add(skillName);
|
|
14831
|
+
const existing = skillCounts.get(skillName);
|
|
14832
|
+
if (existing) {
|
|
14833
|
+
existing.count++;
|
|
14834
|
+
if (ts > existing.lastTs)
|
|
14835
|
+
existing.lastTs = ts;
|
|
14836
|
+
} else {
|
|
14837
|
+
skillCounts.set(skillName, { count: 1, lastTs: ts });
|
|
14693
14838
|
}
|
|
14694
|
-
break;
|
|
14695
14839
|
}
|
|
14696
14840
|
}
|
|
14697
14841
|
registerDesktopWorkspace(workspacePath, label) {
|
|
@@ -14865,7 +15009,7 @@ class ClineAdapter {
|
|
|
14865
15009
|
}
|
|
14866
15010
|
|
|
14867
15011
|
// src/agents/gemini.ts
|
|
14868
|
-
import { existsSync as existsSync35, mkdirSync as mkdirSync22, readdirSync as readdirSync11, readFileSync as readFileSync28, rmSync as rmSync10, writeFileSync as writeFileSync23 } from "fs";
|
|
15012
|
+
import { existsSync as existsSync35, mkdirSync as mkdirSync22, readdirSync as readdirSync11, readFileSync as readFileSync28, rmSync as rmSync10, statSync as statSync6, writeFileSync as writeFileSync23 } from "fs";
|
|
14869
15013
|
import { join as join30 } from "path";
|
|
14870
15014
|
import { homedir as homedir13 } from "os";
|
|
14871
15015
|
class GeminiAdapter {
|
|
@@ -14943,6 +15087,86 @@ class GeminiAdapter {
|
|
|
14943
15087
|
mkdirSync22(join30(settingsPath, ".."), { recursive: true });
|
|
14944
15088
|
writeFileSync23(settingsPath, JSON.stringify(settings, null, 2));
|
|
14945
15089
|
}
|
|
15090
|
+
async readUsageStats(lastSyncAt) {
|
|
15091
|
+
try {
|
|
15092
|
+
const tmpDir = join30(homedir13(), ".gemini", "tmp");
|
|
15093
|
+
if (!existsSync35(tmpDir))
|
|
15094
|
+
return null;
|
|
15095
|
+
const sinceMs = lastSyncAt ? new Date(lastSyncAt).getTime() : 0;
|
|
15096
|
+
let sessionCount = 0;
|
|
15097
|
+
let messageCount = 0;
|
|
15098
|
+
let latestMs = 0;
|
|
15099
|
+
const activeDays = new Set;
|
|
15100
|
+
let projects;
|
|
15101
|
+
try {
|
|
15102
|
+
projects = readdirSync11(tmpDir, { withFileTypes: true });
|
|
15103
|
+
} catch {
|
|
15104
|
+
return null;
|
|
15105
|
+
}
|
|
15106
|
+
for (const project of projects) {
|
|
15107
|
+
if (!project.isDirectory())
|
|
15108
|
+
continue;
|
|
15109
|
+
const chatsDir = join30(tmpDir, project.name, "chats");
|
|
15110
|
+
let files;
|
|
15111
|
+
try {
|
|
15112
|
+
files = readdirSync11(chatsDir, { withFileTypes: true });
|
|
15113
|
+
} catch {
|
|
15114
|
+
continue;
|
|
15115
|
+
}
|
|
15116
|
+
for (const file of files) {
|
|
15117
|
+
if (!file.name.startsWith("session-") || !file.name.endsWith(".json"))
|
|
15118
|
+
continue;
|
|
15119
|
+
const filePath = join30(chatsDir, file.name);
|
|
15120
|
+
let stat;
|
|
15121
|
+
try {
|
|
15122
|
+
stat = statSync6(filePath);
|
|
15123
|
+
} catch {
|
|
15124
|
+
continue;
|
|
15125
|
+
}
|
|
15126
|
+
if (stat.mtimeMs <= sinceMs)
|
|
15127
|
+
continue;
|
|
15128
|
+
let session;
|
|
15129
|
+
try {
|
|
15130
|
+
session = JSON.parse(readFileSync28(filePath, "utf-8"));
|
|
15131
|
+
} catch {
|
|
15132
|
+
continue;
|
|
15133
|
+
}
|
|
15134
|
+
if (!Array.isArray(session.messages))
|
|
15135
|
+
continue;
|
|
15136
|
+
let sessionHadActivity = false;
|
|
15137
|
+
for (const m of session.messages) {
|
|
15138
|
+
const ts = typeof m.timestamp === "string" ? Date.parse(m.timestamp) : NaN;
|
|
15139
|
+
if (!Number.isFinite(ts) || ts <= sinceMs)
|
|
15140
|
+
continue;
|
|
15141
|
+
if (m.type === "user" || m.type === "gemini") {
|
|
15142
|
+
messageCount++;
|
|
15143
|
+
sessionHadActivity = true;
|
|
15144
|
+
if (ts > latestMs)
|
|
15145
|
+
latestMs = ts;
|
|
15146
|
+
activeDays.add(new Date(ts).toISOString().slice(0, 10));
|
|
15147
|
+
}
|
|
15148
|
+
}
|
|
15149
|
+
if (sessionHadActivity)
|
|
15150
|
+
sessionCount++;
|
|
15151
|
+
}
|
|
15152
|
+
}
|
|
15153
|
+
if (sessionCount === 0)
|
|
15154
|
+
return null;
|
|
15155
|
+
return {
|
|
15156
|
+
hasNewActivity: true,
|
|
15157
|
+
sessionCount,
|
|
15158
|
+
messageCount,
|
|
15159
|
+
toolCallCount: 0,
|
|
15160
|
+
tokensUsed: 0,
|
|
15161
|
+
aiLinesAdded: 0,
|
|
15162
|
+
aiLinesRemoved: 0,
|
|
15163
|
+
lastActiveAt: latestMs > 0 ? new Date(latestMs).toISOString() : null,
|
|
15164
|
+
activeDays: [...activeDays].sort()
|
|
15165
|
+
};
|
|
15166
|
+
} catch {
|
|
15167
|
+
return null;
|
|
15168
|
+
}
|
|
15169
|
+
}
|
|
14946
15170
|
async cleanup(scope, manifest) {
|
|
14947
15171
|
if (scope === "user") {
|
|
14948
15172
|
removeRunworkMcpServers(join30(homedir13(), ".gemini", "settings.json"), "mcpServers");
|
|
@@ -17119,6 +17343,12 @@ var RUNWORK_AGENT_DEFAULTS = {
|
|
|
17119
17343
|
var AGENT_DEFAULTS_SCHEMA_VERSION = 1;
|
|
17120
17344
|
|
|
17121
17345
|
// src/commands/sync-telemetry.ts
|
|
17346
|
+
var TELEMETRY_BACKFILL_MS = 30 * 24 * 60 * 60 * 1000;
|
|
17347
|
+
function resolveTelemetrySince(state, nowMs = Date.now()) {
|
|
17348
|
+
if (state.lastTelemetryAt)
|
|
17349
|
+
return state.lastTelemetryAt;
|
|
17350
|
+
return new Date(nowMs - TELEMETRY_BACKFILL_MS).toISOString();
|
|
17351
|
+
}
|
|
17122
17352
|
async function collectTelemetryEvents(params) {
|
|
17123
17353
|
const now = params.now ?? new Date().toISOString();
|
|
17124
17354
|
const events = [];
|
|
@@ -17129,7 +17359,7 @@ async function collectTelemetryEvents(params) {
|
|
|
17129
17359
|
let error;
|
|
17130
17360
|
if (adapter2.readUsageStats) {
|
|
17131
17361
|
try {
|
|
17132
|
-
stats = await adapter2.readUsageStats(params.
|
|
17362
|
+
stats = await adapter2.readUsageStats(params.since);
|
|
17133
17363
|
if (stats && stats.hasNewActivity) {
|
|
17134
17364
|
events.push({
|
|
17135
17365
|
eventType: "local_agent.usage",
|
|
@@ -17142,6 +17372,7 @@ async function collectTelemetryEvents(params) {
|
|
|
17142
17372
|
aiLinesAdded: stats.aiLinesAdded,
|
|
17143
17373
|
aiLinesRemoved: stats.aiLinesRemoved,
|
|
17144
17374
|
lastActiveAt: stats.lastActiveAt,
|
|
17375
|
+
activeDays: stats.activeDays,
|
|
17145
17376
|
modelsUsed: stats.modelsUsed,
|
|
17146
17377
|
mcpToolCount: stats.mcpToolCount,
|
|
17147
17378
|
...stats.extra
|
|
@@ -17156,7 +17387,7 @@ async function collectTelemetryEvents(params) {
|
|
|
17156
17387
|
}
|
|
17157
17388
|
if (adapter2.readSkillUsage) {
|
|
17158
17389
|
try {
|
|
17159
|
-
skills = await adapter2.readSkillUsage(params.
|
|
17390
|
+
skills = await adapter2.readSkillUsage(params.since);
|
|
17160
17391
|
if (skills && skills.length > 0) {
|
|
17161
17392
|
for (const entry of skills) {
|
|
17162
17393
|
events.push({
|
|
@@ -18481,6 +18712,7 @@ async function syncFromState(state, statePath2, credentials, opts) {
|
|
|
18481
18712
|
const telemetry = await collectTelemetryEvents({
|
|
18482
18713
|
adapters,
|
|
18483
18714
|
state,
|
|
18715
|
+
since: resolveTelemetrySince(state),
|
|
18484
18716
|
syncedSkillsCount: remoteSkills.length,
|
|
18485
18717
|
syncedMcpServersCount: mcpEntries.length,
|
|
18486
18718
|
teamInstructionsApplied: false,
|
|
@@ -18860,21 +19092,25 @@ async function syncFromState(state, statePath2, credentials, opts) {
|
|
|
18860
19092
|
state.skillHashes = mergedHashes;
|
|
18861
19093
|
writeFileSync29(statePath2, JSON.stringify(state, null, 2));
|
|
18862
19094
|
try {
|
|
19095
|
+
const telemetryNow = new Date().toISOString();
|
|
18863
19096
|
const telemetry = await collectTelemetryEvents({
|
|
18864
19097
|
adapters,
|
|
18865
19098
|
state,
|
|
19099
|
+
since: resolveTelemetrySince(state, Date.parse(telemetryNow)),
|
|
18866
19100
|
syncedSkillsCount: remoteSkills.length,
|
|
18867
19101
|
syncedMcpServersCount: mcpEntries.length,
|
|
18868
19102
|
teamInstructionsApplied,
|
|
18869
|
-
agentConfigsApplied
|
|
19103
|
+
agentConfigsApplied,
|
|
19104
|
+
now: telemetryNow
|
|
18870
19105
|
});
|
|
18871
19106
|
if (opts.verbose)
|
|
18872
19107
|
printTelemetryVerbose(telemetry);
|
|
18873
19108
|
await client.reportTelemetry(state.workspaceId, telemetry.events);
|
|
19109
|
+
state.lastTelemetryAt = telemetryNow;
|
|
18874
19110
|
if (telemetry.healthReported) {
|
|
18875
19111
|
state.lastHealthReportAt = new Date().toISOString();
|
|
18876
|
-
writeFileSync29(statePath2, JSON.stringify(state, null, 2));
|
|
18877
19112
|
}
|
|
19113
|
+
writeFileSync29(statePath2, JSON.stringify(state, null, 2));
|
|
18878
19114
|
} catch {}
|
|
18879
19115
|
const failedNote = summary.adaptersFailed > 0 ? ` (${summary.adaptersFailed} failed)` : "";
|
|
18880
19116
|
if (isVerbose()) {
|
|
@@ -20261,7 +20497,7 @@ async function applyDoctorFixes(ctx, failingNames) {
|
|
|
20261
20497
|
}
|
|
20262
20498
|
|
|
20263
20499
|
// src/agents/runtime-detection.ts
|
|
20264
|
-
import { existsSync as existsSync46, readFileSync as readFileSync39, statSync as
|
|
20500
|
+
import { existsSync as existsSync46, readFileSync as readFileSync39, statSync as statSync7, readdirSync as readdirSync13 } from "fs";
|
|
20265
20501
|
import { homedir as homedir23 } from "os";
|
|
20266
20502
|
import { join as join42 } from "path";
|
|
20267
20503
|
var RUNWORK_SESSIONS_DIR = join42(homedir23(), ".runwork", "sessions");
|
|
@@ -20372,7 +20608,7 @@ function findCodexRolloutFile(threadId) {
|
|
|
20372
20608
|
const full = join42(dir, entry);
|
|
20373
20609
|
let s;
|
|
20374
20610
|
try {
|
|
20375
|
-
s =
|
|
20611
|
+
s = statSync7(full);
|
|
20376
20612
|
} catch {
|
|
20377
20613
|
continue;
|
|
20378
20614
|
}
|
|
@@ -20409,7 +20645,7 @@ function findNewestClaudeCodeSession() {
|
|
|
20409
20645
|
continue;
|
|
20410
20646
|
const full = join42(projectPath, file);
|
|
20411
20647
|
try {
|
|
20412
|
-
const s =
|
|
20648
|
+
const s = statSync7(full);
|
|
20413
20649
|
if (!best || s.mtimeMs > best.mtime) {
|
|
20414
20650
|
best = {
|
|
20415
20651
|
sessionId: file.replace(/\.jsonl$/, ""),
|
|
@@ -20442,7 +20678,7 @@ function findNewestCodexRollout() {
|
|
|
20442
20678
|
const full = join42(dir, entry);
|
|
20443
20679
|
let s;
|
|
20444
20680
|
try {
|
|
20445
|
-
s =
|
|
20681
|
+
s = statSync7(full);
|
|
20446
20682
|
} catch {
|
|
20447
20683
|
continue;
|
|
20448
20684
|
}
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "runwork",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"description": "CLI for Runwork: develop, preview, and deploy Runwork apps from your local machine.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
|
-
"author": "Runwork <info@runwork.ai> (https://www.runwork.ai)",
|
|
6
|
+
"author": "Runwork, Inc. <info@runwork.ai> (https://www.runwork.ai)",
|
|
7
7
|
"homepage": "https://www.runwork.ai",
|
|
8
8
|
"keywords": [
|
|
9
9
|
"runwork",
|