runwork 0.24.0 → 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
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
|
});
|
|
@@ -5100,8 +5101,8 @@ export declare function componentRoutes(app: Hono<{
|
|
|
5100
5101
|
Bindings: Env;
|
|
5101
5102
|
}>): void;
|
|
5102
5103
|
`,
|
|
5103
|
-
"workspace.d.ts": `export { WorkspaceContext, getWorkspaceContext, listWorkspaceEntity, getWorkspaceEntity, createWorkspaceEntity, updateWorkspaceEntity, deleteWorkspaceEntity, initializeWorkspace, } from './core-workspace';
|
|
5104
|
-
export type { WorkspaceUser, ListUsersOptions, ListUsersResponse, NotifyUserParams, NotifyUserResult, ListEntityRequest, ListEntityResponse, GetEntityRequest, CreateEntityRequest, UpdateEntityRequest, DeleteEntityRequest, RegisterEntityRequest, RegisterAppRequest, RegisterComponentRequest, RegisterScheduleRequest, RegisterWorkflowRequest, RegisterEndpointRequest, RegisterIntegrationRequest, RegisterAgentRequest as WorkspaceRegisterAgentRequest, } from './core-workspace';
|
|
5104
|
+
"workspace.d.ts": `export { WorkspaceContext, getWorkspaceContext, listWorkspaceEntity, getWorkspaceEntity, createWorkspaceEntity, updateWorkspaceEntity, deleteWorkspaceEntity, callAppEndpoint, initializeWorkspace, } from './core-workspace';
|
|
5105
|
+
export type { WorkspaceUser, ListUsersOptions, ListUsersResponse, NotifyUserParams, NotifyUserResult, ListEntityRequest, ListEntityResponse, GetEntityRequest, CreateEntityRequest, UpdateEntityRequest, DeleteEntityRequest, RegisterEntityRequest, RegisterAppRequest, RegisterComponentRequest, RegisterScheduleRequest, RegisterWorkflowRequest, RegisterEndpointRequest, RegisterIntegrationRequest, CallAppEndpointOptions, CallAppEndpointResult, RegisterAgentRequest as WorkspaceRegisterAgentRequest, } from './core-workspace';
|
|
5105
5106
|
`,
|
|
5106
5107
|
"core-scheduler.d.ts": `/**
|
|
5107
5108
|
* Core Scheduled Jobs Framework
|
|
@@ -5443,11 +5444,16 @@ type ZodSchema = z.ZodType<any, any, any>;
|
|
|
5443
5444
|
export type EndpointAuthType = 'apiKey' | 'public';
|
|
5444
5445
|
/**
|
|
5445
5446
|
* Caller principal type resolved by the platform and passed via the
|
|
5446
|
-
* \`X-Public-Endpoint-Auth-Type\` header. \`user\` and \`
|
|
5447
|
+
* \`X-Public-Endpoint-Auth-Type\` header. \`user\`, \`workspace_key\` and \`app\` are
|
|
5447
5448
|
* authenticated principals the platform has already authorized; \`api_key\`
|
|
5448
5449
|
* additionally carries a validated key id; \`none\` is unauthenticated.
|
|
5450
|
+
*
|
|
5451
|
+
* \`app\` means a sibling app in the same workspace called this endpoint through
|
|
5452
|
+
* the platform (see \`callAppEndpoint\`). The platform records which app made the
|
|
5453
|
+
* call, but does not forward that id here: it is self-asserted by the caller and
|
|
5454
|
+
* must not be used for authorization until app identity is verifiable.
|
|
5449
5455
|
*/
|
|
5450
|
-
export type EndpointPrincipalType = 'none' | 'api_key' | 'workspace_key' | 'user';
|
|
5456
|
+
export type EndpointPrincipalType = 'none' | 'api_key' | 'workspace_key' | 'user' | 'app';
|
|
5451
5457
|
/**
|
|
5452
5458
|
* HTTP methods supported by endpoints
|
|
5453
5459
|
*/
|
|
@@ -5490,7 +5496,7 @@ export interface EndpointContext<TQuery = Record<string, unknown>, TBody = unkno
|
|
|
5490
5496
|
export interface EndpointAuthInfo {
|
|
5491
5497
|
/** Authentication requirement declared by the endpoint */
|
|
5492
5498
|
type: EndpointAuthType;
|
|
5493
|
-
/** Resolved caller principal type (api_key, workspace_key, user, none) */
|
|
5499
|
+
/** Resolved caller principal type (api_key, workspace_key, user, app, none) */
|
|
5494
5500
|
principalType?: EndpointPrincipalType;
|
|
5495
5501
|
/** API key ID (if authenticated with an API key) */
|
|
5496
5502
|
apiKeyId?: string;
|
|
@@ -6319,6 +6325,34 @@ export interface RegisterAgentRequest extends Record<string, unknown> {
|
|
|
6319
6325
|
* WorkspaceContext - Helper for cross-app data queries
|
|
6320
6326
|
* Provides methods to query entities from other apps in the same workspace
|
|
6321
6327
|
*/
|
|
6328
|
+
/**
|
|
6329
|
+
* Options for calling a public endpoint on another app in the workspace.
|
|
6330
|
+
*/
|
|
6331
|
+
export interface CallAppEndpointOptions {
|
|
6332
|
+
/**
|
|
6333
|
+
* Target app, normally its slug as shown in the dashboard (e.g. 'labcrm').
|
|
6334
|
+
* An app id or worker name also resolves. If the slug is wrong, the error
|
|
6335
|
+
* response lists the slugs available in this workspace.
|
|
6336
|
+
*/
|
|
6337
|
+
app: string;
|
|
6338
|
+
method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
6339
|
+
/** Endpoint path exactly as registered, path params included: '/v1/orders/:id'. */
|
|
6340
|
+
path: string;
|
|
6341
|
+
/** Values for the \`:param\` segments in \`path\`. */
|
|
6342
|
+
pathParams?: Record<string, string>;
|
|
6343
|
+
queryParams?: Record<string, string>;
|
|
6344
|
+
/** JSON body. Ignored for GET and HEAD. */
|
|
6345
|
+
body?: unknown;
|
|
6346
|
+
}
|
|
6347
|
+
/**
|
|
6348
|
+
* Result of a cross-app endpoint call. \`ok\` and \`status\` are the target
|
|
6349
|
+
* endpoint's own response status, not the transport's.
|
|
6350
|
+
*/
|
|
6351
|
+
export interface CallAppEndpointResult<T = unknown> {
|
|
6352
|
+
ok: boolean;
|
|
6353
|
+
status: number;
|
|
6354
|
+
data: T;
|
|
6355
|
+
}
|
|
6322
6356
|
export declare class WorkspaceContext {
|
|
6323
6357
|
private env;
|
|
6324
6358
|
private workspaceId;
|
|
@@ -6355,6 +6389,45 @@ export declare class WorkspaceContext {
|
|
|
6355
6389
|
* Delete an entity in another app in the workspace
|
|
6356
6390
|
*/
|
|
6357
6391
|
deleteEntity(entityName: string, id: string): Promise<void>;
|
|
6392
|
+
/**
|
|
6393
|
+
* Transport for cross-app endpoint calls.
|
|
6394
|
+
*
|
|
6395
|
+
* Deliberately NOT \`workspaceFetch\`, for two reasons:
|
|
6396
|
+
*
|
|
6397
|
+
* 1. \`workspaceFetch\` retries over HTTP whenever the Durable Object returns a
|
|
6398
|
+
* non-2xx. That is right for entity CRUD, where a non-2xx means the DO
|
|
6399
|
+
* itself failed. Here a non-2xx is the TARGET ENDPOINT'S OWN ANSWER, so
|
|
6400
|
+
* retrying would re-send the request — duplicating POSTs — and in production
|
|
6401
|
+
* the retry goes to the platform's own domain, which a Workers for Platforms
|
|
6402
|
+
* worker cannot fetch: it hangs until timeout, the very failure this function
|
|
6403
|
+
* exists to avoid. A status is a result, never a transport error.
|
|
6404
|
+
*
|
|
6405
|
+
* 2. Production therefore uses the DO binding ONLY, mirroring \`workspaceApiFetch\`
|
|
6406
|
+
* in core-utils.ts, which is the proven path for every other platform call
|
|
6407
|
+
* made from a deployed app. Preview and sandbox keep the HTTP path, where the
|
|
6408
|
+
* app is reached over a tunnel rather than the dispatch namespace.
|
|
6409
|
+
*/
|
|
6410
|
+
private endpointCallFetch;
|
|
6411
|
+
/**
|
|
6412
|
+
* Call a public endpoint on another app in this workspace.
|
|
6413
|
+
*
|
|
6414
|
+
* Production apps run in a Workers for Platforms dispatch namespace and cannot
|
|
6415
|
+
* HTTP-fetch the platform's app domain, so a direct \`fetch()\` to a sibling app's
|
|
6416
|
+
* public URL hangs until it times out. This routes through the workspace instead,
|
|
6417
|
+
* which reaches the target app via the dispatcher.
|
|
6418
|
+
*
|
|
6419
|
+
* The call arrives at the target as an \`app\` principal, which satisfies endpoints
|
|
6420
|
+
* declared \`auth: 'apiKey'\`. No API key is needed or sent.
|
|
6421
|
+
*
|
|
6422
|
+
* @example
|
|
6423
|
+
* const invite = await callAppEndpoint(env, {
|
|
6424
|
+
* app: 'labcrm',
|
|
6425
|
+
* method: 'GET',
|
|
6426
|
+
* path: '/v1/portal/invite/:token',
|
|
6427
|
+
* pathParams: { token },
|
|
6428
|
+
* });
|
|
6429
|
+
*/
|
|
6430
|
+
callAppEndpoint<T = unknown>(options: CallAppEndpointOptions): Promise<CallAppEndpointResult<T>>;
|
|
6358
6431
|
/**
|
|
6359
6432
|
* Register an entity with the workspace so other apps can query it
|
|
6360
6433
|
*/
|
|
@@ -6587,6 +6660,22 @@ export declare function listWorkspaceEntity<T = unknown>(env: Env, entityName: s
|
|
|
6587
6660
|
* @example
|
|
6588
6661
|
* const contact = await getWorkspaceEntity<Contact>(env, 'Contact', 'contact-123');
|
|
6589
6662
|
*/
|
|
6663
|
+
/**
|
|
6664
|
+
* Call a public endpoint on ANOTHER app in this workspace.
|
|
6665
|
+
*
|
|
6666
|
+
* Use this instead of \`fetch()\` against a sibling app's public URL: production apps
|
|
6667
|
+
* cannot HTTP-fetch the platform's app domain and such a fetch hangs until timeout.
|
|
6668
|
+
*
|
|
6669
|
+
* @example
|
|
6670
|
+
* const result = await callAppEndpoint(env, {
|
|
6671
|
+
* app: 'labcrm',
|
|
6672
|
+
* method: 'POST',
|
|
6673
|
+
* path: '/v1/portal/orders',
|
|
6674
|
+
* body: { items },
|
|
6675
|
+
* });
|
|
6676
|
+
* if (!result.ok) throw new Error(\`CRM rejected the order: \${result.status}\`);
|
|
6677
|
+
*/
|
|
6678
|
+
export declare function callAppEndpoint<T = unknown>(env: Env, options: CallAppEndpointOptions): Promise<CallAppEndpointResult<T>>;
|
|
6590
6679
|
export declare function getWorkspaceEntity<T = unknown>(env: Env, entityName: string, id: string): Promise<T | null>;
|
|
6591
6680
|
/**
|
|
6592
6681
|
* Create an entity in ANOTHER app in the workspace (cross-app ONLY)
|
|
@@ -6914,6 +7003,13 @@ export declare class BaseAgent extends AIChatAgent<Env> {
|
|
|
6914
7003
|
* Build tools for accessing entities using AI SDK tool() helper
|
|
6915
7004
|
* Uses entity classes directly with EntityContext
|
|
6916
7005
|
*/
|
|
7006
|
+
/**
|
|
7007
|
+
* One tool per endpoint the agent is allowed to call on another app.
|
|
7008
|
+
*
|
|
7009
|
+
* Routed through \`callAppEndpoint\`, so it works in preview and production
|
|
7010
|
+
* alike: a production app cannot HTTP-fetch a sibling's public URL.
|
|
7011
|
+
*/
|
|
7012
|
+
private buildAppEndpointTools;
|
|
6917
7013
|
private buildEntityTools;
|
|
6918
7014
|
/**
|
|
6919
7015
|
* Build tools for running complex tasks via a sandboxed AI sub-agent
|
|
@@ -7353,6 +7449,16 @@ export interface AgentDefinition {
|
|
|
7353
7449
|
integrations?: string[];
|
|
7354
7450
|
/** Entity names the agent can read/write */
|
|
7355
7451
|
entities?: string[];
|
|
7452
|
+
/**
|
|
7453
|
+
* Endpoints on OTHER apps in this workspace that the agent may call, as
|
|
7454
|
+
* \`'<app>:<METHOD>:<path>'\` — e.g. \`'labcrm:GET:/v1/portal/catalog'\`.
|
|
7455
|
+
* \`<app>\` is the target app's slug; \`<path>\` is the path exactly as registered,
|
|
7456
|
+
* \`:param\` placeholders included.
|
|
7457
|
+
*
|
|
7458
|
+
* Declared explicitly, like \`entities\` and \`integrations\`: an agent can only
|
|
7459
|
+
* call what the app author listed, never an arbitrary endpoint.
|
|
7460
|
+
*/
|
|
7461
|
+
appEndpoints?: string[];
|
|
7356
7462
|
/** Memory strategy */
|
|
7357
7463
|
memoryStrategy?: MemoryStrategy;
|
|
7358
7464
|
/** Custom tools for this agent */
|
|
@@ -7362,6 +7468,27 @@ export interface AgentDefinition {
|
|
|
7362
7468
|
/** Cost/time limits for task agent sandbox execution. Optional overrides. */
|
|
7363
7469
|
executionLimits?: ExecutionLimits;
|
|
7364
7470
|
}
|
|
7471
|
+
/** A parsed \`'<app>:<METHOD>:<path>'\` agent endpoint reference. */
|
|
7472
|
+
export interface AppEndpointRef {
|
|
7473
|
+
app: string;
|
|
7474
|
+
method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
7475
|
+
path: string;
|
|
7476
|
+
}
|
|
7477
|
+
/**
|
|
7478
|
+
* Parse \`'labcrm:GET:/v1/portal/invite/:token'\`.
|
|
7479
|
+
*
|
|
7480
|
+
* Split on the first two colons only: the path itself contains colons for its
|
|
7481
|
+
* \`:param\` segments, so a naive split would mangle every parameterized endpoint.
|
|
7482
|
+
* Returns null for anything malformed rather than throwing, so one bad entry in an
|
|
7483
|
+
* agent definition costs that one tool instead of the whole agent.
|
|
7484
|
+
*/
|
|
7485
|
+
export declare function parseAppEndpointRef(ref: string): AppEndpointRef | null;
|
|
7486
|
+
/**
|
|
7487
|
+
* Stable, model-friendly tool name for an endpoint reference, e.g.
|
|
7488
|
+
* \`call_labcrm_get_v1_portal_invite\`. Param segments are dropped so the name
|
|
7489
|
+
* stays readable; the full path is in the tool description.
|
|
7490
|
+
*/
|
|
7491
|
+
export declare function appEndpointToolName(ref: AppEndpointRef): string;
|
|
7365
7492
|
/**
|
|
7366
7493
|
* Custom tool definition for agents
|
|
7367
7494
|
*/
|
|
@@ -7713,7 +7840,7 @@ function createKeyboardListener() {
|
|
|
7713
7840
|
}
|
|
7714
7841
|
|
|
7715
7842
|
// src/generated/version.ts
|
|
7716
|
-
var VERSION = "0.
|
|
7843
|
+
var VERSION = "0.25.0";
|
|
7717
7844
|
|
|
7718
7845
|
// src/commands/dev.ts
|
|
7719
7846
|
var exports_dev = {};
|
|
@@ -8762,10 +8889,19 @@ function saveDefaultWorkspace(workspaceId, workspaceName) {
|
|
|
8762
8889
|
function hasProjectConfig() {
|
|
8763
8890
|
return existsSync21(".runwork.json");
|
|
8764
8891
|
}
|
|
8892
|
+
function matchAppRef(apps, ref) {
|
|
8893
|
+
return apps.find((a) => a.id === ref || a.name === ref || a.slug === ref);
|
|
8894
|
+
}
|
|
8895
|
+
function filterRowsByApp(rows, appRef, apps) {
|
|
8896
|
+
const match = matchAppRef(apps, appRef);
|
|
8897
|
+
if (!match)
|
|
8898
|
+
return null;
|
|
8899
|
+
return rows.filter((row) => row.appId === match.id);
|
|
8900
|
+
}
|
|
8765
8901
|
async function resolveApp2(client, workspaceId, options = {}) {
|
|
8766
8902
|
if (options.app) {
|
|
8767
8903
|
const apps = await client.listApps(workspaceId);
|
|
8768
|
-
const match = apps
|
|
8904
|
+
const match = matchAppRef(apps, options.app);
|
|
8769
8905
|
if (!match) {
|
|
8770
8906
|
console.error(`App "${options.app}" not found in workspace.`);
|
|
8771
8907
|
process.exit(1);
|
|
@@ -9612,6 +9748,187 @@ var init_registry = __esm(() => {
|
|
|
9612
9748
|
});
|
|
9613
9749
|
|
|
9614
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
|
+
}
|
|
9615
9932
|
function extractClaudeJsonlSession(raw, agentSlug, project) {
|
|
9616
9933
|
const digest = {
|
|
9617
9934
|
agentSlug,
|
|
@@ -9625,6 +9942,21 @@ function extractClaudeJsonlSession(raw, agentSlug, project) {
|
|
|
9625
9942
|
errorCount: 0,
|
|
9626
9943
|
assistantTurns: 0
|
|
9627
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;
|
|
9628
9960
|
for (const line of raw.split(`
|
|
9629
9961
|
`)) {
|
|
9630
9962
|
if (!line.trim())
|
|
@@ -9640,12 +9972,29 @@ function extractClaudeJsonlSession(raw, agentSlug, project) {
|
|
|
9640
9972
|
if (!digest.start)
|
|
9641
9973
|
digest.start = ts;
|
|
9642
9974
|
digest.end = ts;
|
|
9975
|
+
const ms = Date.parse(ts);
|
|
9976
|
+
if (Number.isFinite(ms))
|
|
9977
|
+
epochMs.push(ms);
|
|
9643
9978
|
}
|
|
9644
9979
|
const type = obj.type;
|
|
9980
|
+
if (type === "permission-mode" && typeof obj.permissionMode === "string") {
|
|
9981
|
+
modeStream.push(obj.permissionMode);
|
|
9982
|
+
continue;
|
|
9983
|
+
}
|
|
9645
9984
|
const message = obj.message;
|
|
9646
9985
|
if (!message || typeof message !== "object")
|
|
9647
9986
|
continue;
|
|
9648
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");
|
|
9649
9998
|
const texts = [];
|
|
9650
9999
|
if (typeof message.content === "string") {
|
|
9651
10000
|
texts.push(message.content);
|
|
@@ -9655,15 +10004,22 @@ function extractClaudeJsonlSession(raw, agentSlug, project) {
|
|
|
9655
10004
|
const it = item;
|
|
9656
10005
|
if (it.type === "text" && typeof it.text === "string")
|
|
9657
10006
|
texts.push(it.text);
|
|
9658
|
-
if (it.type === "tool_result"
|
|
9659
|
-
|
|
10007
|
+
if (it.type === "tool_result") {
|
|
10008
|
+
if (it.is_error === true)
|
|
10009
|
+
digest.errorCount++;
|
|
10010
|
+
friction.result(it.is_error === true);
|
|
10011
|
+
}
|
|
9660
10012
|
}
|
|
9661
10013
|
}
|
|
9662
10014
|
}
|
|
9663
10015
|
for (const text2 of texts) {
|
|
9664
10016
|
const trimmed = text2.trim();
|
|
10017
|
+
if (trimmed.startsWith("<command-name>"))
|
|
10018
|
+
slashCommandCount++;
|
|
9665
10019
|
if (!trimmed || SKIP_PREFIXES.some((p) => trimmed.startsWith(p)))
|
|
9666
10020
|
continue;
|
|
10021
|
+
if (humanLine)
|
|
10022
|
+
promptBuckets[promptLengthBucket(trimmed.length)]++;
|
|
9667
10023
|
if (digest.userMessages.length >= MAX_MSGS_PER_SESSION) {
|
|
9668
10024
|
digest.droppedUserMessages++;
|
|
9669
10025
|
continue;
|
|
@@ -9671,15 +10027,47 @@ function extractClaudeJsonlSession(raw, agentSlug, project) {
|
|
|
9671
10027
|
digest.userMessages.push(trimmed.length > MAX_MSG_CHARS ? trimmed.slice(0, MAX_MSG_CHARS) + " [...]" : trimmed);
|
|
9672
10028
|
}
|
|
9673
10029
|
}
|
|
9674
|
-
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;
|
|
9675
10050
|
digest.assistantTurns++;
|
|
9676
10051
|
for (const item of message.content) {
|
|
9677
10052
|
if (item && typeof item === "object") {
|
|
9678
10053
|
const it = item;
|
|
9679
10054
|
if (it.type === "tool_use" && typeof it.name === "string") {
|
|
9680
10055
|
digest.toolCounts[it.name] = (digest.toolCounts[it.name] ?? 0) + 1;
|
|
9681
|
-
|
|
9682
|
-
|
|
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++;
|
|
9683
10071
|
}
|
|
9684
10072
|
}
|
|
9685
10073
|
}
|
|
@@ -9687,11 +10075,40 @@ function extractClaudeJsonlSession(raw, agentSlug, project) {
|
|
|
9687
10075
|
}
|
|
9688
10076
|
if (digest.userMessages.length === 0)
|
|
9689
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);
|
|
9690
10094
|
return digest;
|
|
9691
10095
|
}
|
|
9692
10096
|
function decodeProjectDir(encoded) {
|
|
9693
10097
|
return encoded.replace(/^-/, "/").replace(/-/g, "/");
|
|
9694
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
|
+
}
|
|
9695
10112
|
function geminiMessageText(content) {
|
|
9696
10113
|
if (typeof content === "string")
|
|
9697
10114
|
return content;
|
|
@@ -9727,6 +10144,9 @@ function extractGeminiSession(raw, agentSlug, project) {
|
|
|
9727
10144
|
digest.start = session.startTime;
|
|
9728
10145
|
if (typeof session.lastUpdated === "string")
|
|
9729
10146
|
digest.end = session.lastUpdated;
|
|
10147
|
+
const signals = emptySessionSignals();
|
|
10148
|
+
const epochMs = [];
|
|
10149
|
+
const tokensByModel = new Map;
|
|
9730
10150
|
for (const raw2 of session.messages) {
|
|
9731
10151
|
const m = raw2;
|
|
9732
10152
|
if (typeof m.timestamp === "string") {
|
|
@@ -9734,9 +10154,21 @@ function extractGeminiSession(raw, agentSlug, project) {
|
|
|
9734
10154
|
digest.start = m.timestamp;
|
|
9735
10155
|
if (!digest.end || m.timestamp > digest.end)
|
|
9736
10156
|
digest.end = m.timestamp;
|
|
10157
|
+
const ms = Date.parse(m.timestamp);
|
|
10158
|
+
if (Number.isFinite(ms))
|
|
10159
|
+
epochMs.push(ms);
|
|
9737
10160
|
}
|
|
9738
10161
|
if (m.type === "gemini") {
|
|
9739
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
|
+
}
|
|
9740
10172
|
continue;
|
|
9741
10173
|
}
|
|
9742
10174
|
if (m.type === "error") {
|
|
@@ -9754,7 +10186,12 @@ function extractGeminiSession(raw, agentSlug, project) {
|
|
|
9754
10186
|
}
|
|
9755
10187
|
digest.userMessages.push(text2.length > MAX_MSG_CHARS ? `${text2.slice(0, MAX_MSG_CHARS)}...` : text2);
|
|
9756
10188
|
}
|
|
9757
|
-
|
|
10189
|
+
if (digest.userMessages.length === 0)
|
|
10190
|
+
return null;
|
|
10191
|
+
applyGapHistogram(signals, epochMs);
|
|
10192
|
+
applyTokenMap(signals, tokensByModel);
|
|
10193
|
+
digest.signals = signals;
|
|
10194
|
+
return digest;
|
|
9758
10195
|
}
|
|
9759
10196
|
function extractCodexRolloutSession(raw, agentSlug) {
|
|
9760
10197
|
const digest = {
|
|
@@ -9769,6 +10206,14 @@ function extractCodexRolloutSession(raw, agentSlug) {
|
|
|
9769
10206
|
errorCount: 0,
|
|
9770
10207
|
assistantTurns: 0
|
|
9771
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;
|
|
9772
10217
|
for (const line of raw.split(`
|
|
9773
10218
|
`)) {
|
|
9774
10219
|
if (!line.trim())
|
|
@@ -9784,12 +10229,49 @@ function extractCodexRolloutSession(raw, agentSlug) {
|
|
|
9784
10229
|
if (!digest.start)
|
|
9785
10230
|
digest.start = ts;
|
|
9786
10231
|
digest.end = ts;
|
|
10232
|
+
const ms = Date.parse(ts);
|
|
10233
|
+
if (Number.isFinite(ms))
|
|
10234
|
+
epochMs.push(ms);
|
|
9787
10235
|
}
|
|
9788
10236
|
const p = o.payload;
|
|
9789
10237
|
if (!p || typeof p !== "object")
|
|
9790
10238
|
continue;
|
|
9791
|
-
if (o.type === "session_meta"
|
|
9792
|
-
|
|
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
|
+
}
|
|
9793
10275
|
continue;
|
|
9794
10276
|
}
|
|
9795
10277
|
if (o.type !== "response_item")
|
|
@@ -9815,6 +10297,17 @@ function extractCodexRolloutSession(raw, agentSlug) {
|
|
|
9815
10297
|
}
|
|
9816
10298
|
} else if (pt === "function_call" && typeof p.name === "string") {
|
|
9817
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++;
|
|
9818
10311
|
} else if (pt === "tool_search_call") {
|
|
9819
10312
|
digest.toolCounts["tool_search"] = (digest.toolCounts["tool_search"] ?? 0) + 1;
|
|
9820
10313
|
}
|
|
@@ -9823,6 +10316,21 @@ function extractCodexRolloutSession(raw, agentSlug) {
|
|
|
9823
10316
|
return null;
|
|
9824
10317
|
if (!digest.project)
|
|
9825
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);
|
|
9826
10334
|
return digest;
|
|
9827
10335
|
}
|
|
9828
10336
|
function extractCursorSessions(composerRows, bubbleRows, sinceISO) {
|
|
@@ -9871,6 +10379,7 @@ function extractCursorSessions(composerRows, bubbleRows, sinceISO) {
|
|
|
9871
10379
|
errorCount: 0,
|
|
9872
10380
|
assistantTurns: 0
|
|
9873
10381
|
};
|
|
10382
|
+
const friction = new FrictionCounters;
|
|
9874
10383
|
for (const b of bubbles) {
|
|
9875
10384
|
if (b.ts) {
|
|
9876
10385
|
if (!digest.start)
|
|
@@ -9881,6 +10390,7 @@ function extractCursorSessions(composerRows, bubbleRows, sinceISO) {
|
|
|
9881
10390
|
digest.toolCounts[b.tool] = (digest.toolCounts[b.tool] ?? 0) + 1;
|
|
9882
10391
|
if (b.status === "error")
|
|
9883
10392
|
digest.errorCount++;
|
|
10393
|
+
friction.result(b.status === "error");
|
|
9884
10394
|
} else if (b.type === 2) {
|
|
9885
10395
|
digest.assistantTurns++;
|
|
9886
10396
|
}
|
|
@@ -9907,6 +10417,9 @@ function extractCursorSessions(composerRows, bubbleRows, sinceISO) {
|
|
|
9907
10417
|
continue;
|
|
9908
10418
|
if (sinceISO && digest.end && digest.end < sinceISO)
|
|
9909
10419
|
continue;
|
|
10420
|
+
const signals = emptySessionSignals();
|
|
10421
|
+
signals.toolFailureStreakMax = friction.failStreakMax;
|
|
10422
|
+
digest.signals = signals;
|
|
9910
10423
|
digests.push(digest);
|
|
9911
10424
|
}
|
|
9912
10425
|
return digests;
|
|
@@ -9950,7 +10463,7 @@ function formatCombinedDigest(sessions, opts = { days: 7 }) {
|
|
|
9950
10463
|
return lines.join(`
|
|
9951
10464
|
`);
|
|
9952
10465
|
}
|
|
9953
|
-
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;
|
|
9954
10467
|
var init_session_digest = __esm(() => {
|
|
9955
10468
|
SKIP_PREFIXES = [
|
|
9956
10469
|
"<system-reminder",
|
|
@@ -10289,7 +10802,7 @@ import { createHash as createHash2 } from "crypto";
|
|
|
10289
10802
|
function contentHash(content) {
|
|
10290
10803
|
return "sha256:" + createHash2("sha256").update(content).digest("hex");
|
|
10291
10804
|
}
|
|
10292
|
-
function
|
|
10805
|
+
function stableStringify2(value) {
|
|
10293
10806
|
return JSON.stringify(sortValue(value));
|
|
10294
10807
|
}
|
|
10295
10808
|
function sortValue(value) {
|
|
@@ -10306,7 +10819,7 @@ function sortValue(value) {
|
|
|
10306
10819
|
return value;
|
|
10307
10820
|
}
|
|
10308
10821
|
function configHash(value) {
|
|
10309
|
-
return contentHash(
|
|
10822
|
+
return contentHash(stableStringify2(value));
|
|
10310
10823
|
}
|
|
10311
10824
|
var init_hash = () => {};
|
|
10312
10825
|
|
|
@@ -15720,10 +16233,104 @@ var init_conversation_registry = __esm(async () => {
|
|
|
15720
16233
|
|
|
15721
16234
|
// src/reflect/session-summary.ts
|
|
15722
16235
|
import { createHash as createHash4 } from "crypto";
|
|
16236
|
+
import { execFileSync as execFileSync3 } from "child_process";
|
|
15723
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
|
+
}
|
|
15724
16283
|
function hashSessionKey(key) {
|
|
15725
16284
|
return createHash4("sha256").update(key).digest("hex").slice(0, 32);
|
|
15726
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
|
+
}
|
|
15727
16334
|
function digestForEntry(entry) {
|
|
15728
16335
|
if (!entry.transcriptPath)
|
|
15729
16336
|
return null;
|
|
@@ -15757,9 +16364,10 @@ function deriveMcpUsage(toolCounts) {
|
|
|
15757
16364
|
}
|
|
15758
16365
|
return { mcpCallCount, mcpServers };
|
|
15759
16366
|
}
|
|
15760
|
-
function buildSessionSummary(key, entry, digest) {
|
|
16367
|
+
function buildSessionSummary(key, entry, digest, opts = {}) {
|
|
15761
16368
|
const toolCallCount = digest ? Object.values(digest.toolCounts).reduce((sum, n) => sum + n, 0) : null;
|
|
15762
16369
|
const userMessageCount = digest ? digest.userMessages.length + digest.droppedUserMessages : null;
|
|
16370
|
+
const sig = digest?.signals;
|
|
15763
16371
|
return {
|
|
15764
16372
|
agentSlug: entry.agentSlug,
|
|
15765
16373
|
sessionIdHash: hashSessionKey(key),
|
|
@@ -15772,7 +16380,32 @@ function buildSessionSummary(key, entry, digest) {
|
|
|
15772
16380
|
toolCounts: digest ? digest.toolCounts : null,
|
|
15773
16381
|
...deriveMcpUsage(digest ? digest.toolCounts : null),
|
|
15774
16382
|
errorCount: digest?.errorCount ?? null,
|
|
15775
|
-
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)
|
|
15776
16409
|
};
|
|
15777
16410
|
}
|
|
15778
16411
|
function buildSessionSummaryEvent(summary, nowISO) {
|
|
@@ -15793,14 +16426,41 @@ function buildSessionSummaryEvent(summary, nowISO) {
|
|
|
15793
16426
|
mcpCallCount: summary.mcpCallCount,
|
|
15794
16427
|
mcpServers: summary.mcpServers,
|
|
15795
16428
|
errorCount: summary.errorCount,
|
|
15796
|
-
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
|
|
15797
16455
|
},
|
|
15798
16456
|
timestamp: nowISO
|
|
15799
16457
|
}
|
|
15800
16458
|
};
|
|
15801
16459
|
}
|
|
16460
|
+
var VENDOR_STRING_MAX_CHARS = 64, VENDOR_LIST_MAX_ITEMS = 12, PROJECT_PLACEHOLDERS;
|
|
15802
16461
|
var init_session_summary = __esm(() => {
|
|
15803
16462
|
init_session_digest();
|
|
16463
|
+
PROJECT_PLACEHOLDERS = new Set(["codex", "cowork", "cursor"]);
|
|
15804
16464
|
});
|
|
15805
16465
|
|
|
15806
16466
|
// src/reflect/telemetry-outbox.ts
|
|
@@ -15842,6 +16502,222 @@ var init_telemetry_outbox = __esm(() => {
|
|
|
15842
16502
|
init_atomic_json();
|
|
15843
16503
|
});
|
|
15844
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
|
+
|
|
15845
16721
|
// src/reflect/triage.ts
|
|
15846
16722
|
var exports_triage = {};
|
|
15847
16723
|
__export(exports_triage, {
|
|
@@ -15852,7 +16728,8 @@ __export(exports_triage, {
|
|
|
15852
16728
|
packTriageBatches: () => packTriageBatches,
|
|
15853
16729
|
buildTriagePrompt: () => buildTriagePrompt,
|
|
15854
16730
|
TRIAGE_ITEM_CHAR_CAP: () => TRIAGE_ITEM_CHAR_CAP,
|
|
15855
|
-
TRIAGE_BATCH_CHAR_BUDGET: () => TRIAGE_BATCH_CHAR_BUDGET
|
|
16731
|
+
TRIAGE_BATCH_CHAR_BUDGET: () => TRIAGE_BATCH_CHAR_BUDGET,
|
|
16732
|
+
TASK_TYPES: () => TASK_TYPES
|
|
15856
16733
|
});
|
|
15857
16734
|
function renderTriageItem(id, candidate) {
|
|
15858
16735
|
const d = candidate.digest;
|
|
@@ -15901,12 +16778,17 @@ You are the cheap triage tier of Runwork's reflection engine. At the end of this
|
|
|
15901
16778
|
|
|
15902
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.
|
|
15903
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
|
+
|
|
15904
16786
|
Reply with ONLY a single fenced \`json\` block:
|
|
15905
16787
|
|
|
15906
16788
|
\`\`\`json
|
|
15907
16789
|
{
|
|
15908
16790
|
"verdicts": [
|
|
15909
|
-
{ "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>"] }
|
|
15910
16792
|
]
|
|
15911
16793
|
}
|
|
15912
16794
|
\`\`\`
|
|
@@ -15936,9 +16818,13 @@ function parseTriageVerdicts(text2) {
|
|
|
15936
16818
|
const v = raw;
|
|
15937
16819
|
if (typeof v.id !== "string" || typeof v.worthy !== "boolean")
|
|
15938
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()) : [];
|
|
15939
16823
|
out.set(v.id, {
|
|
15940
16824
|
worthy: v.worthy,
|
|
15941
|
-
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 } : {}
|
|
15942
16828
|
});
|
|
15943
16829
|
}
|
|
15944
16830
|
return out.size > 0 ? out : null;
|
|
@@ -16009,7 +16895,7 @@ async function runTriagePass(candidates, opts) {
|
|
|
16009
16895
|
}
|
|
16010
16896
|
return { verdicts, summary };
|
|
16011
16897
|
}
|
|
16012
|
-
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;
|
|
16013
16899
|
var init_triage = __esm(async () => {
|
|
16014
16900
|
init_which();
|
|
16015
16901
|
init_session_listing();
|
|
@@ -16018,6 +16904,7 @@ var init_triage = __esm(async () => {
|
|
|
16018
16904
|
init_conversation_analysis()
|
|
16019
16905
|
]);
|
|
16020
16906
|
TRIAGE_TIMEOUT_MS = 2 * 60 * 1000;
|
|
16907
|
+
TASK_TYPES = ["build", "automate", "analyze", "write", "research", "admin", "debug"];
|
|
16021
16908
|
});
|
|
16022
16909
|
|
|
16023
16910
|
// src/reflect/conversation-analysis.ts
|
|
@@ -16182,6 +17069,48 @@ async function analyzeConversations(opts = {}) {
|
|
|
16182
17069
|
releaseRunLock();
|
|
16183
17070
|
}
|
|
16184
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
|
+
}
|
|
16185
17114
|
async function analyzeConversationsLocked(opts, outcome, gates) {
|
|
16186
17115
|
const { cadence, explicitSession, manual } = gates;
|
|
16187
17116
|
if (!cadence.enabled && !manual && !explicitSession) {
|
|
@@ -16318,9 +17247,11 @@ async function analyzeConversationsLocked(opts, outcome, gates) {
|
|
|
16318
17247
|
for (const [key, verdict] of pass.verdicts) {
|
|
16319
17248
|
const entry = state.entries[key];
|
|
16320
17249
|
if (entry) {
|
|
16321
|
-
|
|
17250
|
+
const { keyEntities: _entities, ...cacheable } = verdict;
|
|
17251
|
+
state = { entries: { ...state.entries, [key]: { ...entry, triage: { ...cacheable, at: entry.lastActivityAt } } } };
|
|
16322
17252
|
}
|
|
16323
17253
|
}
|
|
17254
|
+
recordPatternObservations(pass.verdicts, state, progress);
|
|
16324
17255
|
}
|
|
16325
17256
|
}
|
|
16326
17257
|
const ranked = [];
|
|
@@ -16540,6 +17471,7 @@ var init_conversation_analysis = __esm(async () => {
|
|
|
16540
17471
|
init_session_listing();
|
|
16541
17472
|
init_insight_store();
|
|
16542
17473
|
init_telemetry_outbox();
|
|
17474
|
+
init_pattern_store();
|
|
16543
17475
|
init_run_log();
|
|
16544
17476
|
init_insight_store();
|
|
16545
17477
|
await __promiseAll([
|
|
@@ -19036,14 +19968,18 @@ init_session_summary();
|
|
|
19036
19968
|
init_telemetry_outbox();
|
|
19037
19969
|
init_insight_store();
|
|
19038
19970
|
init_atomic_json();
|
|
19971
|
+
init_store();
|
|
19972
|
+
init_client();
|
|
19973
|
+
init_resolve();
|
|
19974
|
+
init_workspace_state();
|
|
19039
19975
|
init_colors();
|
|
19040
19976
|
await __promiseAll([
|
|
19041
19977
|
init_conversation_registry(),
|
|
19042
19978
|
init_conversation_analysis()
|
|
19043
19979
|
]);
|
|
19044
19980
|
import { Command as Command15 } from "commander";
|
|
19045
|
-
import { join as
|
|
19046
|
-
import { homedir as
|
|
19981
|
+
import { join as join43 } from "path";
|
|
19982
|
+
import { homedir as homedir24 } from "os";
|
|
19047
19983
|
var DEFAULT_LOOKBACK_DAYS = 30;
|
|
19048
19984
|
function sinceFromDays(days) {
|
|
19049
19985
|
if (days <= 0)
|
|
@@ -19056,6 +19992,46 @@ function parseDays(raw) {
|
|
|
19056
19992
|
return DEFAULT_LOOKBACK_DAYS;
|
|
19057
19993
|
return Math.floor(n);
|
|
19058
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
|
+
}
|
|
19059
20035
|
function projectLabel(project) {
|
|
19060
20036
|
const trimmed = project.replace(/[/\\]+$/, "");
|
|
19061
20037
|
const idx = Math.max(trimmed.lastIndexOf("/"), trimmed.lastIndexOf("\\"));
|
|
@@ -19112,11 +20088,21 @@ conversationsCommand.command("scan").description("Detect finished conversations
|
|
|
19112
20088
|
const nowISO = new Date().toISOString();
|
|
19113
20089
|
const summaryEvents = [];
|
|
19114
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
|
+
};
|
|
19115
20098
|
let statsWritten = 0;
|
|
19116
20099
|
for (const [key, entry] of Object.entries(scan.state.entries)) {
|
|
19117
20100
|
if (!newlyPending.has(key) && entry.stats)
|
|
19118
20101
|
continue;
|
|
19119
|
-
const summary = buildSessionSummary(key, entry, digestForEntry(entry)
|
|
20102
|
+
const summary = buildSessionSummary(key, entry, digestForEntry(entry), {
|
|
20103
|
+
knownSkills,
|
|
20104
|
+
repoHash: repoHashFor(entry.project)
|
|
20105
|
+
});
|
|
19120
20106
|
scan.state.entries[key] = { ...entry, stats: summary };
|
|
19121
20107
|
statsWritten++;
|
|
19122
20108
|
if (newlyPending.has(key))
|
|
@@ -19155,7 +20141,7 @@ conversationsCommand.command("scan").description("Detect finished conversations
|
|
|
19155
20141
|
idleMinutes,
|
|
19156
20142
|
conversations: [...listed, ...rescued].sort((a, b) => b.lastActivityAt.localeCompare(a.lastActivityAt))
|
|
19157
20143
|
};
|
|
19158
|
-
writeJsonAtomic(
|
|
20144
|
+
writeJsonAtomic(join43(homedir24(), ".runwork", "conversations.json"), snapshot);
|
|
19159
20145
|
if (json) {
|
|
19160
20146
|
jsonOut({ queued: scan.queued, reopened: scan.reopened, pruned: scan.pruned, pending, finished: finished.length });
|
|
19161
20147
|
return;
|
|
@@ -19185,8 +20171,8 @@ init_client();
|
|
|
19185
20171
|
init_resolve();
|
|
19186
20172
|
import { Command as Command16 } from "commander";
|
|
19187
20173
|
import { readFileSync as readFileSync37, existsSync as existsSync48 } from "fs";
|
|
19188
|
-
import { join as
|
|
19189
|
-
import { homedir as
|
|
20174
|
+
import { join as join44 } from "path";
|
|
20175
|
+
import { homedir as homedir25 } from "os";
|
|
19190
20176
|
|
|
19191
20177
|
// ../../shared/agent-instructions/runwork-instructions.ts
|
|
19192
20178
|
function formatList(items, max = 8) {
|
|
@@ -19327,17 +20313,22 @@ function toRunworkInventory(ctx) {
|
|
|
19327
20313
|
mcpServerCount: ctx.mcpServerCount
|
|
19328
20314
|
};
|
|
19329
20315
|
}
|
|
19330
|
-
function buildAppSkillDescription(
|
|
20316
|
+
function buildAppSkillDescription(skill, registries) {
|
|
20317
|
+
const appName = skill.name;
|
|
20318
|
+
const fallback = `${appName} - Runwork workspace application`;
|
|
19331
20319
|
if (!registries)
|
|
19332
|
-
return
|
|
20320
|
+
return fallback;
|
|
20321
|
+
if (!skill.appId)
|
|
20322
|
+
return fallback;
|
|
20323
|
+
const appId = skill.appId;
|
|
19333
20324
|
const parts = [];
|
|
19334
|
-
const appEntities = registries.entities.filter((e) => e.
|
|
19335
|
-
const appSchedules = registries.schedules.filter((s) => s.
|
|
19336
|
-
const appWorkflows = registries.workflows.filter((w) => w.
|
|
19337
|
-
const appAgents = registries.agents.filter((a) => a.
|
|
19338
|
-
const appEndpoints = registries.endpoints.filter((e) => e.
|
|
19339
|
-
const appFileStorages = registries.fileStorages.filter((f) => f.
|
|
19340
|
-
const appComponents = registries.components.filter((c) => c.
|
|
20325
|
+
const appEntities = registries.entities.filter((e) => e.appId === appId);
|
|
20326
|
+
const appSchedules = registries.schedules.filter((s) => s.appId === appId);
|
|
20327
|
+
const appWorkflows = registries.workflows.filter((w) => w.appId === appId);
|
|
20328
|
+
const appAgents = registries.agents.filter((a) => a.appId === appId);
|
|
20329
|
+
const appEndpoints = registries.endpoints.filter((e) => e.appId === appId);
|
|
20330
|
+
const appFileStorages = registries.fileStorages.filter((f) => f.appId === appId);
|
|
20331
|
+
const appComponents = registries.components.filter((c) => c.appId === appId);
|
|
19341
20332
|
if (appEntities.length > 0)
|
|
19342
20333
|
parts.push(`entities (${appEntities.map((e) => e.entityName).join(", ")})`);
|
|
19343
20334
|
if (appFileStorages.length > 0)
|
|
@@ -19353,7 +20344,7 @@ function buildAppSkillDescription(appName, registries) {
|
|
|
19353
20344
|
if (appComponents.length > 0)
|
|
19354
20345
|
parts.push(`components (${appComponents.map((c) => c.componentName).join(", ")})`);
|
|
19355
20346
|
if (parts.length === 0)
|
|
19356
|
-
return
|
|
20347
|
+
return fallback;
|
|
19357
20348
|
return `${appName} - Runwork workspace app with ${parts.join(", ")}. Use this skill when you need data or capabilities from this app.`;
|
|
19358
20349
|
}
|
|
19359
20350
|
function generateIntroSkill(ctx) {
|
|
@@ -19661,8 +20652,8 @@ async function buildInstructionContext(client, workspace) {
|
|
|
19661
20652
|
// src/commands/instructions.ts
|
|
19662
20653
|
function readSetupExtras(workspaceId) {
|
|
19663
20654
|
for (const path2 of [
|
|
19664
|
-
|
|
19665
|
-
|
|
20655
|
+
join44(process.cwd(), ".runwork", "setup.json"),
|
|
20656
|
+
join44(homedir25(), ".runwork", "setup.json")
|
|
19666
20657
|
]) {
|
|
19667
20658
|
if (!existsSync48(path2))
|
|
19668
20659
|
continue;
|
|
@@ -19877,7 +20868,12 @@ var listCommand3 = new Command17("list").description("List entities registered i
|
|
|
19877
20868
|
try {
|
|
19878
20869
|
let entities = await client.listEntities(workspaceId);
|
|
19879
20870
|
if (opts.app) {
|
|
19880
|
-
|
|
20871
|
+
const filtered = filterRowsByApp(entities, opts.app, await client.listApps(workspaceId));
|
|
20872
|
+
if (filtered === null) {
|
|
20873
|
+
console.error(`App "${opts.app}" not found in this workspace.`);
|
|
20874
|
+
process.exit(1);
|
|
20875
|
+
}
|
|
20876
|
+
entities = filtered;
|
|
19881
20877
|
}
|
|
19882
20878
|
if (opts.preview) {
|
|
19883
20879
|
entities = entities.filter((e) => e.deploymentMode === "preview");
|
|
@@ -20226,7 +21222,12 @@ var listCommand4 = new Command18("list").description("List workflows in a worksp
|
|
|
20226
21222
|
try {
|
|
20227
21223
|
let workflows = await client.listWorkflows(workspaceId);
|
|
20228
21224
|
if (opts.app) {
|
|
20229
|
-
|
|
21225
|
+
const filtered = filterRowsByApp(workflows, opts.app, await client.listApps(workspaceId));
|
|
21226
|
+
if (filtered === null) {
|
|
21227
|
+
console.error(`App "${opts.app}" not found in this workspace.`);
|
|
21228
|
+
process.exit(1);
|
|
21229
|
+
}
|
|
21230
|
+
workflows = filtered;
|
|
20230
21231
|
}
|
|
20231
21232
|
if (useJson) {
|
|
20232
21233
|
jsonOut({ workflows });
|
|
@@ -20585,7 +21586,10 @@ function truncate3(text2, max) {
|
|
|
20585
21586
|
return first;
|
|
20586
21587
|
return first.slice(0, max - 3) + "...";
|
|
20587
21588
|
}
|
|
20588
|
-
|
|
21589
|
+
function filterEndpointsByApp(endpoints, appRef, apps) {
|
|
21590
|
+
return filterRowsByApp(endpoints, appRef, apps);
|
|
21591
|
+
}
|
|
21592
|
+
var listCommand6 = new Command20("list").description("List workspace public endpoints").option("--workspace <name-or-id>", "Workspace name or ID").option("--app <slug>", "Filter by app slug, name, or ID").action(async (opts, command) => {
|
|
20589
21593
|
const useJson = shouldOutputJson(command.optsWithGlobals().json);
|
|
20590
21594
|
const credentials = requireAuth();
|
|
20591
21595
|
const client = new ApiClient(credentials);
|
|
@@ -20593,7 +21597,12 @@ var listCommand6 = new Command20("list").description("List workspace public endp
|
|
|
20593
21597
|
try {
|
|
20594
21598
|
let endpoints = await client.listEndpoints(workspaceId);
|
|
20595
21599
|
if (opts.app) {
|
|
20596
|
-
|
|
21600
|
+
const filtered = filterEndpointsByApp(endpoints, opts.app, await client.listApps(workspaceId));
|
|
21601
|
+
if (filtered === null) {
|
|
21602
|
+
console.error(`App "${opts.app}" not found in this workspace.`);
|
|
21603
|
+
process.exit(1);
|
|
21604
|
+
}
|
|
21605
|
+
endpoints = filtered;
|
|
20597
21606
|
}
|
|
20598
21607
|
if (useJson) {
|
|
20599
21608
|
jsonOut({ endpoints });
|
|
@@ -20623,7 +21632,11 @@ Workspace: ${workspaceName || workspaceId}
|
|
|
20623
21632
|
process.exit(1);
|
|
20624
21633
|
}
|
|
20625
21634
|
});
|
|
20626
|
-
|
|
21635
|
+
function methodsForPath(endpoints, path4) {
|
|
21636
|
+
const methods = endpoints.filter((e) => e.endpointPath === path4).map((e) => e.method.toUpperCase());
|
|
21637
|
+
return [...new Set(methods)].sort();
|
|
21638
|
+
}
|
|
21639
|
+
var callCommand2 = new Command20("call").description("Call a workspace endpoint directly").argument("<method>", "HTTP method (GET, POST, PUT, DELETE, etc.)").argument("<path>", "Endpoint path (e.g. /my-endpoint)").option("--workspace <name-or-id>", "Workspace name or ID").option("--app <slug>", "App slug, name, or ID (narrows endpoint lookup)").option("--body <json>", "Request body as JSON string").option("--header <header>", 'Request header (format: "Key: Value", repeatable)', (val, prev) => [...prev, val], []).option("--query <string>", 'Query string to append to the URL (e.g. "foo=bar&baz=1")').option("--api-key <key>", "API key for Authorization: Bearer header").action(async (method, path4, opts, command) => {
|
|
20627
21640
|
const useJson = shouldOutputJson(command.optsWithGlobals().json);
|
|
20628
21641
|
const credentials = requireAuth();
|
|
20629
21642
|
const client = new ApiClient(credentials);
|
|
@@ -20631,13 +21644,23 @@ var callCommand2 = new Command20("call").description("Call a workspace endpoint
|
|
|
20631
21644
|
try {
|
|
20632
21645
|
let endpoints = await client.listEndpoints(workspaceId);
|
|
20633
21646
|
if (opts.app) {
|
|
20634
|
-
|
|
21647
|
+
const filtered = filterEndpointsByApp(endpoints, opts.app, await client.listApps(workspaceId));
|
|
21648
|
+
if (filtered === null) {
|
|
21649
|
+
console.error(`App "${opts.app}" not found in this workspace.`);
|
|
21650
|
+
process.exit(1);
|
|
21651
|
+
}
|
|
21652
|
+
endpoints = filtered;
|
|
20635
21653
|
}
|
|
20636
21654
|
const targetMethod = method.toUpperCase();
|
|
20637
21655
|
const endpoint = endpoints.find((e) => e.method.toUpperCase() === targetMethod && e.endpointPath === path4);
|
|
20638
21656
|
if (!endpoint) {
|
|
20639
21657
|
console.error(`Endpoint not found: ${targetMethod} ${path4}`);
|
|
20640
|
-
|
|
21658
|
+
const otherMethods = methodsForPath(endpoints, path4);
|
|
21659
|
+
if (otherMethods.length > 0) {
|
|
21660
|
+
console.error(`
|
|
21661
|
+
${path4} is registered, but not for ${targetMethod}. Try: ${otherMethods.join(", ")}`);
|
|
21662
|
+
console.error(` runwork endpoints call ${otherMethods[0]} ${path4}`);
|
|
21663
|
+
} else if (endpoints.length > 0) {
|
|
20641
21664
|
console.error(`
|
|
20642
21665
|
Available endpoints:`);
|
|
20643
21666
|
for (const e of endpoints) {
|
|
@@ -20907,7 +21930,12 @@ var listCommand8 = new Command22("list").description("List components registered
|
|
|
20907
21930
|
try {
|
|
20908
21931
|
let components = await client.listComponents(workspaceId);
|
|
20909
21932
|
if (opts.app) {
|
|
20910
|
-
|
|
21933
|
+
const filtered = filterRowsByApp(components, opts.app, await client.listApps(workspaceId));
|
|
21934
|
+
if (filtered === null) {
|
|
21935
|
+
console.error(`App "${opts.app}" not found in this workspace.`);
|
|
21936
|
+
process.exit(1);
|
|
21937
|
+
}
|
|
21938
|
+
components = filtered;
|
|
20911
21939
|
}
|
|
20912
21940
|
if (useJson) {
|
|
20913
21941
|
jsonOut({ components });
|
|
@@ -21040,7 +22068,12 @@ var listCommand10 = new Command24("list").description("List agents in workspace"
|
|
|
21040
22068
|
try {
|
|
21041
22069
|
let agents = await client.listAgents(workspaceId);
|
|
21042
22070
|
if (opts.app) {
|
|
21043
|
-
|
|
22071
|
+
const filtered = filterRowsByApp(agents, opts.app, await client.listApps(workspaceId));
|
|
22072
|
+
if (filtered === null) {
|
|
22073
|
+
console.error(`App "${opts.app}" not found in this workspace.`);
|
|
22074
|
+
process.exit(1);
|
|
22075
|
+
}
|
|
22076
|
+
agents = filtered;
|
|
21044
22077
|
}
|
|
21045
22078
|
if (useJson) {
|
|
21046
22079
|
jsonOut({ agents });
|
|
@@ -21359,8 +22392,8 @@ init_resolve();
|
|
|
21359
22392
|
init_prompt();
|
|
21360
22393
|
await init_detect();
|
|
21361
22394
|
import { Command as Command27 } from "commander";
|
|
21362
|
-
import { join as
|
|
21363
|
-
import { homedir as
|
|
22395
|
+
import { join as join48 } from "path";
|
|
22396
|
+
import { homedir as homedir27 } from "os";
|
|
21364
22397
|
|
|
21365
22398
|
// src/commands/sync.ts
|
|
21366
22399
|
init_store();
|
|
@@ -21372,8 +22405,8 @@ await __promiseAll([
|
|
|
21372
22405
|
]);
|
|
21373
22406
|
import { Command as Command26 } from "commander";
|
|
21374
22407
|
import { readFileSync as readFileSync40, existsSync as existsSync51 } from "fs";
|
|
21375
|
-
import { join as
|
|
21376
|
-
import { homedir as
|
|
22408
|
+
import { join as join47 } from "path";
|
|
22409
|
+
import { homedir as homedir26 } from "os";
|
|
21377
22410
|
|
|
21378
22411
|
// src/commands/mcp-entries.ts
|
|
21379
22412
|
init_types();
|
|
@@ -22272,13 +23305,13 @@ function readLocalSkills(state) {
|
|
|
22272
23305
|
if (!baseDir)
|
|
22273
23306
|
continue;
|
|
22274
23307
|
for (const skillName of state.skills) {
|
|
22275
|
-
const skillMdPath =
|
|
23308
|
+
const skillMdPath = join47(baseDir, skillName, "SKILL.md");
|
|
22276
23309
|
if (existsSync51(skillMdPath)) {
|
|
22277
23310
|
results.push({ name: skillName, content: readFileSync40(skillMdPath, "utf-8") });
|
|
22278
23311
|
continue;
|
|
22279
23312
|
}
|
|
22280
23313
|
const filename = skillName.toLowerCase().replace(/[^a-z0-9]+/g, "-");
|
|
22281
|
-
const flatPath =
|
|
23314
|
+
const flatPath = join47(baseDir, `${filename}.md`);
|
|
22282
23315
|
if (existsSync51(flatPath)) {
|
|
22283
23316
|
results.push({ name: skillName, content: readFileSync40(flatPath, "utf-8") });
|
|
22284
23317
|
}
|
|
@@ -22574,7 +23607,7 @@ This account is not a member of "${state.workspaceName || state.workspaceId}".`)
|
|
|
22574
23607
|
name: s.name,
|
|
22575
23608
|
filename: s.name.toLowerCase().replace(/[^a-z0-9]+/g, "-"),
|
|
22576
23609
|
content: s.content,
|
|
22577
|
-
description: s.source === "app" ? buildAppSkillDescription(s
|
|
23610
|
+
description: s.source === "app" ? buildAppSkillDescription(s, registries) || `${s.name} - Runwork workspace application` : `${s.name} - Runwork workspace skill`
|
|
22578
23611
|
}));
|
|
22579
23612
|
const allSkillFiles = [...builtInSkills, ...workspaceSkillFiles];
|
|
22580
23613
|
const written = await adapter2.writeSkills(allSkillFiles, scope);
|
|
@@ -22760,7 +23793,7 @@ This account is not a member of "${state.workspaceName || state.workspaceId}".`)
|
|
|
22760
23793
|
}
|
|
22761
23794
|
for (const adapter2 of adapters) {
|
|
22762
23795
|
if (adapter2 instanceof CodexAdapter) {
|
|
22763
|
-
const runworkDir =
|
|
23796
|
+
const runworkDir = join47(homedir26(), ".runwork");
|
|
22764
23797
|
const result = adapter2.registerDesktopWorkspace(runworkDir, "Runwork");
|
|
22765
23798
|
if (result === "written") {
|
|
22766
23799
|
vlog(` [${adapter2.name}] Registered workspace in Codex desktop app`);
|
|
@@ -22902,8 +23935,8 @@ var syncCommand = new Command26("sync").description("Sync skills bidirectionally
|
|
|
22902
23935
|
verbose: !!opts.verbose,
|
|
22903
23936
|
redetect: !!opts.redetect
|
|
22904
23937
|
};
|
|
22905
|
-
const projectStatePath =
|
|
22906
|
-
const userStatePath =
|
|
23938
|
+
const projectStatePath = join47(process.cwd(), ".runwork", "setup.json");
|
|
23939
|
+
const userStatePath = join47(homedir26(), ".runwork", "setup.json");
|
|
22907
23940
|
const projectState = loadSetupState(projectStatePath);
|
|
22908
23941
|
const userState = loadSetupState(userStatePath);
|
|
22909
23942
|
if (!projectState && !userState) {
|
|
@@ -22972,7 +24005,7 @@ function toSkillFilename(name) {
|
|
|
22972
24005
|
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-");
|
|
22973
24006
|
}
|
|
22974
24007
|
function loadSetupStateForScope(scope) {
|
|
22975
|
-
const path4 = scope === "project" ?
|
|
24008
|
+
const path4 = scope === "project" ? join48(process.cwd(), ".runwork", "setup.json") : join48(homedir27(), ".runwork", "setup.json");
|
|
22976
24009
|
return readJsonOrNull(path4);
|
|
22977
24010
|
}
|
|
22978
24011
|
async function parkAndTeardownWorkspace(previous, scopes) {
|
|
@@ -23141,8 +24174,8 @@ Re-run without --dry-run to sync workspace data.`);
|
|
|
23141
24174
|
}
|
|
23142
24175
|
persistDefaultWorkspace(workspaceId, workspaceName);
|
|
23143
24176
|
for (const s of scopes) {
|
|
23144
|
-
const dir = s === "project" ? ".runwork" :
|
|
23145
|
-
writeJsonAtomic(
|
|
24177
|
+
const dir = s === "project" ? ".runwork" : join48(homedir27(), ".runwork");
|
|
24178
|
+
writeJsonAtomic(join48(dir, "setup.json"), state);
|
|
23146
24179
|
}
|
|
23147
24180
|
if (restored)
|
|
23148
24181
|
clearParkedState(workspaceId);
|
|
@@ -23150,7 +24183,7 @@ Re-run without --dry-run to sync workspace data.`);
|
|
|
23150
24183
|
Syncing workspace data...
|
|
23151
24184
|
`);
|
|
23152
24185
|
for (const s of scopes) {
|
|
23153
|
-
const statePath2 = s === "project" ?
|
|
24186
|
+
const statePath2 = s === "project" ? join48(process.cwd(), ".runwork", "setup.json") : join48(homedir27(), ".runwork", "setup.json");
|
|
23154
24187
|
await syncFromState(state, statePath2, credentials, {
|
|
23155
24188
|
dryRun: false,
|
|
23156
24189
|
pullOnly: true,
|
|
@@ -23171,11 +24204,11 @@ import { Command as Command28 } from "commander";
|
|
|
23171
24204
|
|
|
23172
24205
|
// src/utils/setup-state.ts
|
|
23173
24206
|
import { existsSync as existsSync52, readFileSync as readFileSync41 } from "fs";
|
|
23174
|
-
import { join as
|
|
23175
|
-
import { homedir as
|
|
24207
|
+
import { join as join49 } from "path";
|
|
24208
|
+
import { homedir as homedir28 } from "os";
|
|
23176
24209
|
function loadSetupState2() {
|
|
23177
|
-
const projectPath =
|
|
23178
|
-
const userPath =
|
|
24210
|
+
const projectPath = join49(process.cwd(), ".runwork", "setup.json");
|
|
24211
|
+
const userPath = join49(homedir28(), ".runwork", "setup.json");
|
|
23179
24212
|
for (const p of [projectPath, userPath]) {
|
|
23180
24213
|
if (existsSync52(p)) {
|
|
23181
24214
|
try {
|
|
@@ -23239,8 +24272,8 @@ init_types();
|
|
|
23239
24272
|
await init_detect();
|
|
23240
24273
|
import { Command as Command29 } from "commander";
|
|
23241
24274
|
import { existsSync as existsSync53, readFileSync as readFileSync42 } from "fs";
|
|
23242
|
-
import { resolve as resolve3, join as
|
|
23243
|
-
import { homedir as
|
|
24275
|
+
import { resolve as resolve3, join as join50 } from "path";
|
|
24276
|
+
import { homedir as homedir29 } from "os";
|
|
23244
24277
|
function loadSetupState3(filePath) {
|
|
23245
24278
|
if (!existsSync53(filePath))
|
|
23246
24279
|
return null;
|
|
@@ -23261,8 +24294,8 @@ var buildPluginCommand = new Command29("build-plugin").description("Build an ins
|
|
|
23261
24294
|
process.exit(1);
|
|
23262
24295
|
}
|
|
23263
24296
|
const credentials = requireAuth();
|
|
23264
|
-
const projectStatePath =
|
|
23265
|
-
const userStatePath =
|
|
24297
|
+
const projectStatePath = join50(process.cwd(), ".runwork", "setup.json");
|
|
24298
|
+
const userStatePath = join50(homedir29(), ".runwork", "setup.json");
|
|
23266
24299
|
const state = loadSetupState3(projectStatePath) ?? loadSetupState3(userStatePath);
|
|
23267
24300
|
if (!state) {
|
|
23268
24301
|
console.error("No setup state found. Run `runwork setup` first.");
|
|
@@ -23337,7 +24370,7 @@ var buildPluginCommand = new Command29("build-plugin").description("Build an ins
|
|
|
23337
24370
|
name: s.name,
|
|
23338
24371
|
filename: s.name.toLowerCase().replace(/[^a-z0-9]+/g, "-"),
|
|
23339
24372
|
content: s.content,
|
|
23340
|
-
description: s.source === "app" ? buildAppSkillDescription(s
|
|
24373
|
+
description: s.source === "app" ? buildAppSkillDescription(s, registries) || `${s.name} - Runwork workspace application` : `${s.name} - Runwork workspace skill`
|
|
23341
24374
|
}))
|
|
23342
24375
|
];
|
|
23343
24376
|
const teamInstructions = onboardingConfig?.config?.teamInstructions;
|
|
@@ -23355,8 +24388,8 @@ init_prompt();
|
|
|
23355
24388
|
await init_detect();
|
|
23356
24389
|
import { Command as Command30 } from "commander";
|
|
23357
24390
|
import { existsSync as existsSync54, readFileSync as readFileSync43, rmSync as rmSync13, unlinkSync as unlinkSync8 } from "fs";
|
|
23358
|
-
import { join as
|
|
23359
|
-
import { homedir as
|
|
24391
|
+
import { join as join51 } from "path";
|
|
24392
|
+
import { homedir as homedir30 } from "os";
|
|
23360
24393
|
function loadSetupState4(filePath) {
|
|
23361
24394
|
if (!existsSync54(filePath))
|
|
23362
24395
|
return null;
|
|
@@ -23367,8 +24400,8 @@ function loadSetupState4(filePath) {
|
|
|
23367
24400
|
}
|
|
23368
24401
|
}
|
|
23369
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) => {
|
|
23370
|
-
const projectStatePath =
|
|
23371
|
-
const userStatePath =
|
|
24403
|
+
const projectStatePath = join51(process.cwd(), ".runwork", "setup.json");
|
|
24404
|
+
const userStatePath = join51(homedir30(), ".runwork", "setup.json");
|
|
23372
24405
|
const projectState = loadSetupState4(projectStatePath);
|
|
23373
24406
|
const userState = loadSetupState4(userStatePath);
|
|
23374
24407
|
if (!projectState && !userState) {
|
|
@@ -23448,9 +24481,9 @@ This will remove all Runwork configuration from your local agents:
|
|
|
23448
24481
|
}
|
|
23449
24482
|
}
|
|
23450
24483
|
}
|
|
23451
|
-
const stateDir = label === "project" ?
|
|
24484
|
+
const stateDir = label === "project" ? join51(process.cwd(), ".runwork") : join51(homedir30(), ".runwork");
|
|
23452
24485
|
if (opts.keepAuth && label === "user") {
|
|
23453
|
-
const setupFile =
|
|
24486
|
+
const setupFile = join51(stateDir, "setup.json");
|
|
23454
24487
|
if (existsSync54(setupFile)) {
|
|
23455
24488
|
try {
|
|
23456
24489
|
unlinkSync8(setupFile);
|
|
@@ -23701,8 +24734,8 @@ init_credentials();
|
|
|
23701
24734
|
await init_detect();
|
|
23702
24735
|
import { parse as parse2 } from "smol-toml";
|
|
23703
24736
|
import { existsSync as existsSync55, readFileSync as readFileSync45 } from "fs";
|
|
23704
|
-
import { join as
|
|
23705
|
-
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";
|
|
23706
24739
|
var BASE_URL2 = process.env.RUNWORK_DOWNLOAD_BASE_URL || "https://runwork.ai";
|
|
23707
24740
|
var LATEST_JSON_URL2 = `${BASE_URL2}/cli/latest.json`;
|
|
23708
24741
|
function detectPlatform() {
|
|
@@ -23730,7 +24763,7 @@ function buildContext() {
|
|
|
23730
24763
|
const credentials = getCredentials();
|
|
23731
24764
|
const client = credentials ? new ApiClient(credentials) : null;
|
|
23732
24765
|
let config = null;
|
|
23733
|
-
const configPath =
|
|
24766
|
+
const configPath = join52(process.cwd(), ".runwork.json");
|
|
23734
24767
|
if (existsSync55(configPath)) {
|
|
23735
24768
|
try {
|
|
23736
24769
|
config = JSON.parse(readFileSync45(configPath, "utf-8"));
|
|
@@ -23834,9 +24867,9 @@ async function checkCliArtifactReachable() {
|
|
|
23834
24867
|
}
|
|
23835
24868
|
async function checkCliInstallLocation() {
|
|
23836
24869
|
const isWindows2 = osPlatform2() === "win32";
|
|
23837
|
-
const home =
|
|
23838
|
-
const canonicalDir =
|
|
23839
|
-
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");
|
|
23840
24873
|
const candidates = [process.execPath, process.argv[1] || ""].filter(Boolean);
|
|
23841
24874
|
const runsFromCanonical = candidates.some((p) => normalizePath(p) === normalizePath(canonicalBinary));
|
|
23842
24875
|
if (runsFromCanonical) {
|
|
@@ -23968,7 +25001,7 @@ async function checkGitCredentialHelper(ctx) {
|
|
|
23968
25001
|
};
|
|
23969
25002
|
}
|
|
23970
25003
|
async function checkProjectConfig(ctx) {
|
|
23971
|
-
const configPath =
|
|
25004
|
+
const configPath = join52(ctx.cwd, ".runwork.json");
|
|
23972
25005
|
if (!existsSync55(configPath)) {
|
|
23973
25006
|
if (!ctx.credentials) {
|
|
23974
25007
|
return { name: "project-config", status: "skip", message: "no project (not logged in)" };
|
|
@@ -24031,7 +25064,7 @@ async function checkGitRemote(ctx) {
|
|
|
24031
25064
|
if (!ctx.config) {
|
|
24032
25065
|
return { name: "git-remote", status: "skip", message: "skipped (no project)" };
|
|
24033
25066
|
}
|
|
24034
|
-
if (!existsSync55(
|
|
25067
|
+
if (!existsSync55(join52(ctx.cwd, ".git"))) {
|
|
24035
25068
|
return {
|
|
24036
25069
|
name: "git-remote",
|
|
24037
25070
|
status: "fail",
|
|
@@ -24085,8 +25118,8 @@ async function checkDeployFreshness(ctx) {
|
|
|
24085
25118
|
return { name: "deploy-freshness", status: "skip", message: "local HEAD unknown" };
|
|
24086
25119
|
}
|
|
24087
25120
|
function loadSetupState5() {
|
|
24088
|
-
const projectPath =
|
|
24089
|
-
const userPath =
|
|
25121
|
+
const projectPath = join52(process.cwd(), ".runwork", "setup.json");
|
|
25122
|
+
const userPath = join52(homedir31(), ".runwork", "setup.json");
|
|
24090
25123
|
for (const p of [projectPath, userPath]) {
|
|
24091
25124
|
if (existsSync55(p)) {
|
|
24092
25125
|
try {
|
|
@@ -24105,7 +25138,7 @@ async function checkCodexNetwork() {
|
|
|
24105
25138
|
if (!state || !state.configuredAgents.includes("codex")) {
|
|
24106
25139
|
return { name, status: "skip", message: "Codex not configured for Runwork" };
|
|
24107
25140
|
}
|
|
24108
|
-
const configPath =
|
|
25141
|
+
const configPath = join52(homedir31(), ".codex", "config.toml");
|
|
24109
25142
|
if (!existsSync55(configPath)) {
|
|
24110
25143
|
return { name, status: "skip", message: "no Codex config found" };
|
|
24111
25144
|
}
|
|
@@ -24164,7 +25197,7 @@ async function checkCodexDesktopProject() {
|
|
|
24164
25197
|
if (!usesCodex) {
|
|
24165
25198
|
return { name, status: "skip", message: "Codex not configured for Runwork" };
|
|
24166
25199
|
}
|
|
24167
|
-
const statePath2 =
|
|
25200
|
+
const statePath2 = join52(homedir31(), ".codex", ".codex-global-state.json");
|
|
24168
25201
|
if (!existsSync55(statePath2)) {
|
|
24169
25202
|
return { name, status: "skip", message: "Codex desktop app not detected" };
|
|
24170
25203
|
}
|
|
@@ -24176,7 +25209,7 @@ async function checkCodexDesktopProject() {
|
|
|
24176
25209
|
} catch {
|
|
24177
25210
|
return { name, status: "warn", message: "could not read Codex desktop state" };
|
|
24178
25211
|
}
|
|
24179
|
-
const runworkDir =
|
|
25212
|
+
const runworkDir = join52(homedir31(), ".runwork");
|
|
24180
25213
|
if (savedRoots.includes(runworkDir)) {
|
|
24181
25214
|
return { name, status: "pass", message: "Runwork project added to Codex desktop sidebar" };
|
|
24182
25215
|
}
|
|
@@ -24254,7 +25287,7 @@ async function checkAgentSetup() {
|
|
|
24254
25287
|
if (!skillsDir)
|
|
24255
25288
|
continue;
|
|
24256
25289
|
const missingSkills = state.skills.filter((name) => {
|
|
24257
|
-
const skillPath =
|
|
25290
|
+
const skillPath = join52(skillsDir, name, "SKILL.md");
|
|
24258
25291
|
return !existsSync55(skillPath);
|
|
24259
25292
|
});
|
|
24260
25293
|
if (missingSkills.length > 0) {
|
|
@@ -24280,25 +25313,25 @@ async function checkAgentSetup() {
|
|
|
24280
25313
|
};
|
|
24281
25314
|
}
|
|
24282
25315
|
function getMcpConfigPath2(slug, scope) {
|
|
24283
|
-
const home =
|
|
25316
|
+
const home = homedir31();
|
|
24284
25317
|
switch (slug) {
|
|
24285
25318
|
case "claude-code":
|
|
24286
|
-
return scope === "project" ?
|
|
25319
|
+
return scope === "project" ? join52(process.cwd(), ".mcp.json") : join52(home, ".claude", "settings.json");
|
|
24287
25320
|
case "cursor":
|
|
24288
|
-
return scope === "project" ?
|
|
25321
|
+
return scope === "project" ? join52(process.cwd(), ".cursor", "mcp.json") : join52(home, ".cursor", "mcp.json");
|
|
24289
25322
|
case "windsurf":
|
|
24290
|
-
return scope === "project" ?
|
|
25323
|
+
return scope === "project" ? join52(process.cwd(), ".windsurf", "mcp.json") : join52(home, ".windsurf", "mcp.json");
|
|
24291
25324
|
case "codex":
|
|
24292
25325
|
case "codex-app":
|
|
24293
|
-
return scope === "user" ?
|
|
25326
|
+
return scope === "user" ? join52(home, ".codex", "config.toml") : null;
|
|
24294
25327
|
case "gemini":
|
|
24295
|
-
return scope === "user" ?
|
|
25328
|
+
return scope === "user" ? join52(home, ".gemini", "settings.json") : null;
|
|
24296
25329
|
default:
|
|
24297
25330
|
return null;
|
|
24298
25331
|
}
|
|
24299
25332
|
}
|
|
24300
25333
|
async function checkWorkspacePointers() {
|
|
24301
|
-
const userStatePath =
|
|
25334
|
+
const userStatePath = join52(homedir31(), ".runwork", "setup.json");
|
|
24302
25335
|
const state = existsSync55(userStatePath) ? (() => {
|
|
24303
25336
|
try {
|
|
24304
25337
|
return JSON.parse(readFileSync45(userStatePath, "utf-8"));
|
|
@@ -24331,15 +25364,15 @@ async function checkWorkspacePointers() {
|
|
|
24331
25364
|
};
|
|
24332
25365
|
}
|
|
24333
25366
|
function getSkillsDir(slug, scope) {
|
|
24334
|
-
const home =
|
|
25367
|
+
const home = homedir31();
|
|
24335
25368
|
switch (slug) {
|
|
24336
25369
|
case "claude-code":
|
|
24337
|
-
return scope === "project" ?
|
|
25370
|
+
return scope === "project" ? join52(process.cwd(), ".claude", "skills") : join52(home, ".claude", "skills");
|
|
24338
25371
|
case "codex":
|
|
24339
25372
|
case "codex-app":
|
|
24340
|
-
return scope === "project" ?
|
|
25373
|
+
return scope === "project" ? join52(process.cwd(), ".agents", "skills") : join52(home, ".agents", "skills");
|
|
24341
25374
|
case "gemini":
|
|
24342
|
-
return scope === "project" ?
|
|
25375
|
+
return scope === "project" ? join52(process.cwd(), ".gemini", "skills") : join52(home, ".gemini", "skills");
|
|
24343
25376
|
default:
|
|
24344
25377
|
return null;
|
|
24345
25378
|
}
|
|
@@ -24393,7 +25426,7 @@ async function runAllChecks(options) {
|
|
|
24393
25426
|
init_credentials();
|
|
24394
25427
|
init_remote();
|
|
24395
25428
|
import { existsSync as existsSync56 } from "fs";
|
|
24396
|
-
import { join as
|
|
25429
|
+
import { join as join53 } from "path";
|
|
24397
25430
|
async function applyDoctorFixes(ctx, failingNames) {
|
|
24398
25431
|
const failing = new Set(failingNames);
|
|
24399
25432
|
const outcomes = [];
|
|
@@ -24420,7 +25453,7 @@ async function applyDoctorFixes(ctx, failingNames) {
|
|
|
24420
25453
|
applied: false,
|
|
24421
25454
|
message: "no project config -- run inside an app directory"
|
|
24422
25455
|
});
|
|
24423
|
-
} else if (!existsSync56(
|
|
25456
|
+
} else if (!existsSync56(join53(ctx.cwd, ".git"))) {
|
|
24424
25457
|
outcomes.push({
|
|
24425
25458
|
name: "git-remote",
|
|
24426
25459
|
applied: false,
|
|
@@ -24440,9 +25473,9 @@ async function applyDoctorFixes(ctx, failingNames) {
|
|
|
24440
25473
|
|
|
24441
25474
|
// src/agents/runtime-detection.ts
|
|
24442
25475
|
import { existsSync as existsSync57, readFileSync as readFileSync46, statSync as statSync10, readdirSync as readdirSync16 } from "fs";
|
|
24443
|
-
import { homedir as
|
|
24444
|
-
import { join as
|
|
24445
|
-
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");
|
|
24446
25479
|
function detectCurrentAgent() {
|
|
24447
25480
|
const claudeCodeSessionId = process.env.CLAUDE_CODE_SESSION_ID;
|
|
24448
25481
|
if (claudeCodeSessionId) {
|
|
@@ -24505,7 +25538,7 @@ function detectCurrentAgent() {
|
|
|
24505
25538
|
return null;
|
|
24506
25539
|
}
|
|
24507
25540
|
function readHookSessionInfo(sessionId) {
|
|
24508
|
-
const path4 =
|
|
25541
|
+
const path4 = join54(RUNWORK_SESSIONS_DIR, `${sessionId}.json`);
|
|
24509
25542
|
if (!existsSync57(path4))
|
|
24510
25543
|
return null;
|
|
24511
25544
|
try {
|
|
@@ -24517,7 +25550,7 @@ function readHookSessionInfo(sessionId) {
|
|
|
24517
25550
|
}
|
|
24518
25551
|
}
|
|
24519
25552
|
function findClaudeCodeSessionFile(sessionId) {
|
|
24520
|
-
const root =
|
|
25553
|
+
const root = join54(homedir32(), ".claude", "projects");
|
|
24521
25554
|
if (!existsSync57(root))
|
|
24522
25555
|
return null;
|
|
24523
25556
|
let projectDirs;
|
|
@@ -24527,14 +25560,14 @@ function findClaudeCodeSessionFile(sessionId) {
|
|
|
24527
25560
|
return null;
|
|
24528
25561
|
}
|
|
24529
25562
|
for (const dir of projectDirs) {
|
|
24530
|
-
const candidate =
|
|
25563
|
+
const candidate = join54(root, dir, `${sessionId}.jsonl`);
|
|
24531
25564
|
if (existsSync57(candidate))
|
|
24532
25565
|
return candidate;
|
|
24533
25566
|
}
|
|
24534
25567
|
return null;
|
|
24535
25568
|
}
|
|
24536
25569
|
function findCodexRolloutFile(threadId) {
|
|
24537
|
-
const root =
|
|
25570
|
+
const root = join54(homedir32(), ".codex", "sessions");
|
|
24538
25571
|
if (!existsSync57(root))
|
|
24539
25572
|
return null;
|
|
24540
25573
|
const stack = [root];
|
|
@@ -24547,7 +25580,7 @@ function findCodexRolloutFile(threadId) {
|
|
|
24547
25580
|
continue;
|
|
24548
25581
|
}
|
|
24549
25582
|
for (const entry of entries) {
|
|
24550
|
-
const full =
|
|
25583
|
+
const full = join54(dir, entry);
|
|
24551
25584
|
let s;
|
|
24552
25585
|
try {
|
|
24553
25586
|
s = statSync10(full);
|
|
@@ -24564,7 +25597,7 @@ function findCodexRolloutFile(threadId) {
|
|
|
24564
25597
|
return null;
|
|
24565
25598
|
}
|
|
24566
25599
|
function findNewestClaudeCodeSession() {
|
|
24567
|
-
const root =
|
|
25600
|
+
const root = join54(homedir32(), ".claude", "projects");
|
|
24568
25601
|
if (!existsSync57(root))
|
|
24569
25602
|
return null;
|
|
24570
25603
|
let projectDirs;
|
|
@@ -24575,7 +25608,7 @@ function findNewestClaudeCodeSession() {
|
|
|
24575
25608
|
}
|
|
24576
25609
|
let best = null;
|
|
24577
25610
|
for (const dir of projectDirs) {
|
|
24578
|
-
const projectPath =
|
|
25611
|
+
const projectPath = join54(root, dir);
|
|
24579
25612
|
let files;
|
|
24580
25613
|
try {
|
|
24581
25614
|
files = readdirSync16(projectPath);
|
|
@@ -24585,7 +25618,7 @@ function findNewestClaudeCodeSession() {
|
|
|
24585
25618
|
for (const file of files) {
|
|
24586
25619
|
if (!file.endsWith(".jsonl"))
|
|
24587
25620
|
continue;
|
|
24588
|
-
const full =
|
|
25621
|
+
const full = join54(projectPath, file);
|
|
24589
25622
|
try {
|
|
24590
25623
|
const s = statSync10(full);
|
|
24591
25624
|
if (!best || s.mtimeMs > best.mtime) {
|
|
@@ -24603,7 +25636,7 @@ function findNewestClaudeCodeSession() {
|
|
|
24603
25636
|
return best ? { sessionId: best.sessionId, path: best.path } : null;
|
|
24604
25637
|
}
|
|
24605
25638
|
function findNewestCodexRollout() {
|
|
24606
|
-
const root =
|
|
25639
|
+
const root = join54(homedir32(), ".codex", "sessions");
|
|
24607
25640
|
if (!existsSync57(root))
|
|
24608
25641
|
return null;
|
|
24609
25642
|
const stack = [root];
|
|
@@ -24617,7 +25650,7 @@ function findNewestCodexRollout() {
|
|
|
24617
25650
|
continue;
|
|
24618
25651
|
}
|
|
24619
25652
|
for (const entry of entries) {
|
|
24620
|
-
const full =
|
|
25653
|
+
const full = join54(dir, entry);
|
|
24621
25654
|
let s;
|
|
24622
25655
|
try {
|
|
24623
25656
|
s = statSync10(full);
|
|
@@ -24850,9 +25883,9 @@ init_client();
|
|
|
24850
25883
|
init_resolve();
|
|
24851
25884
|
import { Command as Command35 } from "commander";
|
|
24852
25885
|
import { readFileSync as readFileSync47, writeFileSync as writeFileSync31, existsSync as existsSync58, mkdtempSync as mkdtempSync4 } from "fs";
|
|
24853
|
-
import { join as
|
|
25886
|
+
import { join as join55 } from "path";
|
|
24854
25887
|
import { tmpdir as tmpdir4 } from "os";
|
|
24855
|
-
import { createHash as
|
|
25888
|
+
import { createHash as createHash6 } from "crypto";
|
|
24856
25889
|
|
|
24857
25890
|
// src/agents/utils/transcript-render.ts
|
|
24858
25891
|
init_session_digest();
|
|
@@ -24977,8 +26010,8 @@ function resolveLocalSessionShare(opts, conversation) {
|
|
|
24977
26010
|
console.error("Error: this conversation has no shareable content.");
|
|
24978
26011
|
process.exit(1);
|
|
24979
26012
|
}
|
|
24980
|
-
const tempDir = mkdtempSync4(
|
|
24981
|
-
const transcriptFile =
|
|
26013
|
+
const tempDir = mkdtempSync4(join55(tmpdir4(), "runwork-share-"));
|
|
26014
|
+
const transcriptFile = join55(tempDir, "transcript.md");
|
|
24982
26015
|
writeFileSync31(transcriptFile, markdown);
|
|
24983
26016
|
opts.transcriptFile = transcriptFile;
|
|
24984
26017
|
opts.nativeFile = opts.nativeFile ?? conversation.transcriptPath;
|
|
@@ -24996,7 +26029,7 @@ function nativeBundleFormatForAgent(slug) {
|
|
|
24996
26029
|
return null;
|
|
24997
26030
|
}
|
|
24998
26031
|
function sha256Hex(content) {
|
|
24999
|
-
return
|
|
26032
|
+
return createHash6("sha256").update(content, "utf8").digest("hex");
|
|
25000
26033
|
}
|
|
25001
26034
|
function utf8ByteLength(content) {
|
|
25002
26035
|
return Buffer.byteLength(content, "utf8");
|
|
@@ -25188,8 +26221,8 @@ init_resolve();
|
|
|
25188
26221
|
init_registry_data();
|
|
25189
26222
|
import { Command as Command38 } from "commander";
|
|
25190
26223
|
import { writeFileSync as writeFileSync32, mkdirSync as mkdirSync28, realpathSync } from "fs";
|
|
25191
|
-
import { homedir as
|
|
25192
|
-
import { join as
|
|
26224
|
+
import { homedir as homedir33 } from "os";
|
|
26225
|
+
import { join as join56 } from "path";
|
|
25193
26226
|
import { spawn as spawn5 } from "child_process";
|
|
25194
26227
|
init_registry();
|
|
25195
26228
|
init_which();
|
|
@@ -25228,9 +26261,9 @@ function extractCodexUuid(rolloutContent) {
|
|
|
25228
26261
|
}
|
|
25229
26262
|
function placeClaudeJsonl(uuid, content, recipientCwd) {
|
|
25230
26263
|
const encoded = encodeClaudeCodeCwd(recipientCwd);
|
|
25231
|
-
const projectDir =
|
|
26264
|
+
const projectDir = join56(homedir33(), ".claude", "projects", encoded);
|
|
25232
26265
|
mkdirSync28(projectDir, { recursive: true });
|
|
25233
|
-
const placedAt =
|
|
26266
|
+
const placedAt = join56(projectDir, `${uuid}.jsonl`);
|
|
25234
26267
|
writeFileSync32(placedAt, content);
|
|
25235
26268
|
return { placedAt, runFromCwd: recipientCwd };
|
|
25236
26269
|
}
|
|
@@ -25239,10 +26272,10 @@ function placeCodexRollout(uuid, content) {
|
|
|
25239
26272
|
const yyyy = String(now.getUTCFullYear());
|
|
25240
26273
|
const mm = String(now.getUTCMonth() + 1).padStart(2, "0");
|
|
25241
26274
|
const dd = String(now.getUTCDate()).padStart(2, "0");
|
|
25242
|
-
const dir =
|
|
26275
|
+
const dir = join56(homedir33(), ".codex", "sessions", yyyy, mm, dd);
|
|
25243
26276
|
mkdirSync28(dir, { recursive: true });
|
|
25244
26277
|
const ts = now.toISOString().replace(/\.\d+Z$/, "").replace(/:/g, "-");
|
|
25245
|
-
const placedAt =
|
|
26278
|
+
const placedAt = join56(dir, `rollout-${ts}-${uuid}.jsonl`);
|
|
25246
26279
|
writeFileSync32(placedAt, content);
|
|
25247
26280
|
return { placedAt };
|
|
25248
26281
|
}
|