exponential-sdk 1.17.0 → 1.20.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.d.ts CHANGED
@@ -221,6 +221,24 @@ type DecisionUpdateProcedureInput = {
221
221
  keyResultId?: string | null;
222
222
  adrDocumentId?: string | null;
223
223
  };
224
+ type ActionUpsertBySourceInput$1 = {
225
+ sourceType: string;
226
+ sourceId: string;
227
+ name: string;
228
+ workspaceId: string;
229
+ description?: string;
230
+ projectId?: string | null;
231
+ ticketId?: string | null;
232
+ };
233
+ type TimeEntryCreateInput = {
234
+ actionId: string;
235
+ startedAt: Date;
236
+ endedAt: Date;
237
+ source?: 'manual' | 'claude-desktop' | 'agent-run';
238
+ status?: 'PROPOSED' | 'CONFIRMED';
239
+ sourceRef?: string;
240
+ note?: string;
241
+ };
224
242
  interface TrpcClient {
225
243
  transcription: {
226
244
  getAllTranscriptions: {
@@ -298,6 +316,10 @@ interface TrpcClient {
298
316
  update: {
299
317
  mutate: (input: ActionUpdateInput$1) => Promise<unknown>;
300
318
  };
319
+ /** Idempotent create keyed on (workspaceId, sourceType, sourceId). */
320
+ upsertBySource: {
321
+ mutate: (input: ActionUpsertBySourceInput$1) => Promise<unknown>;
322
+ };
301
323
  bulkReschedule: {
302
324
  mutate: (input: {
303
325
  actionIds: string[];
@@ -521,6 +543,38 @@ interface TrpcClient {
521
543
  }) => Promise<unknown>;
522
544
  };
523
545
  };
546
+ timeEntry: {
547
+ /** Explicit-bounds entry; never touches the running Timer (ADR-0061). */
548
+ create: {
549
+ mutate: (input: TimeEntryCreateInput) => Promise<unknown>;
550
+ };
551
+ upsertBySourceRef: {
552
+ mutate: (input: TimeEntryCreateInput & {
553
+ sourceRef: string;
554
+ }) => Promise<unknown>;
555
+ };
556
+ listByDateRange: {
557
+ query: (input: {
558
+ startDate: Date;
559
+ endDate: Date;
560
+ workspaceId?: string | null;
561
+ }) => Promise<unknown[]>;
562
+ };
563
+ /** Human-only; `date` is the start of the day in the caller's timezone. */
564
+ confirmDay: {
565
+ mutate: (input: {
566
+ date: Date;
567
+ workspaceId?: string | null;
568
+ }) => Promise<unknown>;
569
+ };
570
+ /** `date` is the start of the day in the caller's timezone; an agent key reads its owner's day. */
571
+ dayReport: {
572
+ query: (input: {
573
+ date: Date;
574
+ workspaceId?: string | null;
575
+ }) => Promise<unknown>;
576
+ };
577
+ };
524
578
  decision: {
525
579
  list: {
526
580
  query: (input: {
@@ -1280,7 +1334,9 @@ interface Action {
1280
1334
  name: string;
1281
1335
  } | null;
1282
1336
  assignees?: Array<{
1283
- user: {
1337
+ /** Bare form returned by `action.upsertBySource`. */
1338
+ userId?: string;
1339
+ user?: {
1284
1340
  id: string;
1285
1341
  name: string | null;
1286
1342
  email: string | null;
@@ -1368,6 +1424,30 @@ interface ActionUpdateInput {
1368
1424
  effortEstimate?: number | null;
1369
1425
  blockedByIds?: string[];
1370
1426
  }
1427
+ interface ActionUpsertBySourceInput {
1428
+ /** Convention for the Daily worklog: `claude-session`. */
1429
+ sourceType: string;
1430
+ /** The conversation id — re-running with the same pair updates, never duplicates. */
1431
+ sourceId: string;
1432
+ name: string;
1433
+ workspaceId: string;
1434
+ description?: string;
1435
+ /** Must live in `workspaceId`, or the call is NOT_FOUND. */
1436
+ projectId?: string | null;
1437
+ /** Must belong to a product in `workspaceId`, or the call is NOT_FOUND. */
1438
+ ticketId?: string | null;
1439
+ }
1440
+ interface ActionUpsertBySourceResult {
1441
+ action: Action & {
1442
+ ticket?: {
1443
+ id: string;
1444
+ number: number;
1445
+ shortId: string | null;
1446
+ productId: string;
1447
+ } | null;
1448
+ };
1449
+ outcome: 'created' | 'updated';
1450
+ }
1371
1451
  /** One action as returned inside a {@link TodaysActions} group. */
1372
1452
  interface TodaysActionRow {
1373
1453
  id: string;
@@ -1470,6 +1550,13 @@ declare class ActionsApi {
1470
1550
  getProjectActions(projectId: string, assigneeId?: string): Promise<Action[]>;
1471
1551
  create(input: ActionCreateInput): Promise<Action>;
1472
1552
  update(input: ActionUpdateInput): Promise<Action>;
1553
+ /**
1554
+ * One Action per external source: find by (workspaceId, sourceType,
1555
+ * sourceId), refresh its name and links, else create it. `outcome` says
1556
+ * which happened. Agents may call this; the created Action records the
1557
+ * agent's shadow user as creator with `source: "agent"` (ADR-0049).
1558
+ */
1559
+ upsertBySource(input: ActionUpsertBySourceInput): Promise<ActionUpsertBySourceResult>;
1473
1560
  }
1474
1561
 
1475
1562
  interface CommentAuthor {
@@ -4061,6 +4148,207 @@ declare class SearchApi {
4061
4148
  global(options: GlobalSearchOptions): Promise<GlobalSearchOutput>;
4062
4149
  }
4063
4150
 
4151
+ type TimeEntryStatus = 'PROPOSED' | 'CONFIRMED';
4152
+ /**
4153
+ * Where an entry came from. `plugin` is the legacy stamp of the in-app Timer
4154
+ * and calendar grids; new writes use the other three. `claude-desktop` is
4155
+ * Proposed time from the Daily worklog, `agent-run` is Agent-run time (an
4156
+ * agent working with no human turns, kept off the owner's attention totals).
4157
+ */
4158
+ type TimeEntrySource = 'plugin' | 'manual' | 'claude-desktop' | 'agent-run';
4159
+ interface TimeEntry {
4160
+ id: string;
4161
+ /** Whose time it is. Under an agent key this is the agent's OWNER (ADR-0061). */
4162
+ userId: string;
4163
+ actionId: string;
4164
+ workspaceId: string | null;
4165
+ startedAt: Date;
4166
+ endedAt: Date | null;
4167
+ source: string;
4168
+ status: TimeEntryStatus;
4169
+ /** Idempotency key, e.g. `claude-session:<sessionId>#<segmentIndex>`. */
4170
+ sourceRef: string | null;
4171
+ note: string | null;
4172
+ /** The external agent that wrote the row; null for human-made entries. */
4173
+ createdByAgentId: string | null;
4174
+ action: {
4175
+ id: string;
4176
+ name: string;
4177
+ projectId: string | null;
4178
+ workspaceId: string | null;
4179
+ };
4180
+ }
4181
+ interface TimeLogInput {
4182
+ actionId: string;
4183
+ startedAt: Date;
4184
+ endedAt: Date;
4185
+ /** Defaults to `manual` server-side. */
4186
+ source?: Exclude<TimeEntrySource, 'plugin'>;
4187
+ /**
4188
+ * Ignored under an agent key: agent-written entries are always PROPOSED.
4189
+ * A human's entry defaults to CONFIRMED.
4190
+ */
4191
+ status?: TimeEntryStatus;
4192
+ /**
4193
+ * With a `sourceRef` the write is idempotent per owner: a re-run updates a
4194
+ * PROPOSED entry in place and leaves a CONFIRMED one alone.
4195
+ */
4196
+ sourceRef?: string;
4197
+ /** One line shown in the day view and list row (max 1000 chars). */
4198
+ note?: string;
4199
+ }
4200
+ /**
4201
+ * `created` / `updated` — proposed pieces were written; `left` — a CONFIRMED
4202
+ * row already carries this ref and was not touched; `merged` — manual time
4203
+ * on the same Action covers it, so the manual entries were annotated instead
4204
+ * (V2, manual wins); `dropped` — manual time on other Actions covered every
4205
+ * minute, nothing written.
4206
+ */
4207
+ type TimeLogOutcome = 'created' | 'updated' | 'left' | 'merged' | 'dropped';
4208
+ interface TimeLogResult {
4209
+ /** The first written piece, or the untouched CONFIRMED row; null for merged/dropped. */
4210
+ entry: TimeEntry | null;
4211
+ outcome: TimeLogOutcome;
4212
+ /** Every piece written — several when manual time on another Action split the proposal. */
4213
+ pieces?: TimeEntry[];
4214
+ /** Manual entry ids whose note now carries the conversation reference. */
4215
+ mergedInto?: string[];
4216
+ }
4217
+ interface DayReportEntry extends TimeEntry {
4218
+ /** Whole minutes inside the day window (a running entry ends "now"). */
4219
+ minutes: number;
4220
+ /** `ticket.productId ?? project.productId ?? null`. */
4221
+ productId: string | null;
4222
+ productName: string | null;
4223
+ isAgentRun: boolean;
4224
+ flags: Array<'forgotten-timer'>;
4225
+ action: TimeEntry['action'] & {
4226
+ ticketId: string | null;
4227
+ project: {
4228
+ id: string;
4229
+ name: string;
4230
+ productId: string | null;
4231
+ } | null;
4232
+ ticket: {
4233
+ id: string;
4234
+ number: number;
4235
+ shortId: string | null;
4236
+ title: string;
4237
+ productId: string;
4238
+ } | null;
4239
+ };
4240
+ }
4241
+ interface DayReportProductRow {
4242
+ /** Null is Unassigned time. */
4243
+ productId: string | null;
4244
+ name: string;
4245
+ /** Overlap-split minutes; the rows sum to `attentionMinutes`. */
4246
+ minutes: number;
4247
+ }
4248
+ interface DayReportActionRow {
4249
+ actionId: string;
4250
+ name: string;
4251
+ /** Overlap-split minutes; the rows sum to `attentionMinutes`. */
4252
+ minutes: number;
4253
+ /** Plain sum of the Action's non-agent-run entries. */
4254
+ sessionMinutes: number;
4255
+ agentRunMinutes: number;
4256
+ productId: string | null;
4257
+ productName: string | null;
4258
+ projectId: string | null;
4259
+ projectName: string | null;
4260
+ ticket: {
4261
+ id: string;
4262
+ number: number;
4263
+ shortId: string | null;
4264
+ title: string;
4265
+ } | null;
4266
+ proposedCount: number;
4267
+ }
4268
+ /**
4269
+ * One day of a person's time — the same numbers the `/time` Day tab shows.
4270
+ * Attention minutes count each covered minute once; session minutes are the
4271
+ * plain sum; Agent-run time is kept off both and reported on its own.
4272
+ */
4273
+ interface DayReport {
4274
+ dayStart: Date;
4275
+ dayEnd: Date;
4276
+ entries: DayReportEntry[];
4277
+ attentionMinutes: number;
4278
+ sessionMinutes: number;
4279
+ agentRunMinutes: number;
4280
+ byProduct: DayReportProductRow[];
4281
+ byAction: DayReportActionRow[];
4282
+ /** Non-agent-run entries whose Action has neither Ticket nor Project. */
4283
+ unassignedCount: number;
4284
+ proposedCount: number;
4285
+ flags: Array<{
4286
+ entryId: string;
4287
+ flag: 'forgotten-timer';
4288
+ }>;
4289
+ }
4290
+ interface TimeConfirmDayResult {
4291
+ /** Proposed entries flipped to CONFIRMED. */
4292
+ confirmed: number;
4293
+ }
4294
+ interface TimeLogBatchResult {
4295
+ index: number;
4296
+ success: boolean;
4297
+ sourceRef?: string;
4298
+ /** Null when the outcome is `merged` or `dropped`. */
4299
+ entry?: TimeEntry | null;
4300
+ outcome?: TimeLogOutcome;
4301
+ pieces?: TimeEntry[];
4302
+ mergedInto?: string[];
4303
+ error?: string;
4304
+ }
4305
+ /**
4306
+ * Time entries — the Daily worklog's write path (ADR-0061).
4307
+ *
4308
+ * The thing to know: an entry always belongs to the person whose time it
4309
+ * was. Called with an external-agent key, {@link log} writes an entry OWNED
4310
+ * by the agent's owner, records the agent as author and forces it to
4311
+ * `PROPOSED`; it shows on the owner's `/time` with a "proposed" chip and
4312
+ * stays out of `Action.timeSpentMins` until the owner confirms the day.
4313
+ * Manual time is never altered by these calls.
4314
+ */
4315
+ declare class TimeApi {
4316
+ private client;
4317
+ constructor(client: TrpcClient);
4318
+ /**
4319
+ * Log one completed entry with explicit bounds. Never starts, stops or
4320
+ * alters the running Timer. With `sourceRef` the call is an upsert keyed
4321
+ * on (owner, sourceRef); without it every call creates a row.
4322
+ */
4323
+ log(input: TimeLogInput): Promise<TimeLogResult>;
4324
+ /**
4325
+ * Log many entries, one call each and in order, so a failure names the
4326
+ * entry it belonged to instead of rejecting the whole batch. `defaults`
4327
+ * fill any field an entry leaves out.
4328
+ */
4329
+ logBatch(entries: Partial<TimeLogInput>[], defaults?: Partial<TimeLogInput>): Promise<TimeLogBatchResult[]>;
4330
+ /**
4331
+ * Confirm a day: every PROPOSED entry of yours that starts on that local
4332
+ * calendar day becomes CONFIRMED and the Actions' spent time moves. Human
4333
+ * only — the server refuses agent keys, so run this with personal
4334
+ * credentials. A day with nothing proposed returns `{ confirmed: 0 }`.
4335
+ */
4336
+ confirmDay(date: Date | string, workspaceId?: string): Promise<TimeConfirmDayResult>;
4337
+ /**
4338
+ * The day report for one local calendar day: entries joined to Action,
4339
+ * Ticket, Project and Product, attention / session / agent-run minutes,
4340
+ * the overlap-split roll-ups and the counts — exactly what the `/time` Day
4341
+ * tab and the Daily summary's Yesterday line read. Under an agent key the
4342
+ * report is the OWNER's day.
4343
+ */
4344
+ dayReport(date: Date | string, workspaceId?: string): Promise<DayReport>;
4345
+ /**
4346
+ * The caller's entries that touch one calendar day (local time), oldest
4347
+ * first, each with its `status`. Pass `YYYY-MM-DD` or a Date.
4348
+ */
4349
+ list(date: Date | string, workspaceId?: string): Promise<TimeEntry[]>;
4350
+ }
4351
+
4064
4352
  interface ExponentialConfig {
4065
4353
  apiUrl: string;
4066
4354
  token: string;
@@ -4083,6 +4371,42 @@ declare function clearConfig(): void;
4083
4371
  declare function isAuthenticated(): boolean;
4084
4372
  declare function getConfigPath(): string;
4085
4373
 
4374
+ /**
4375
+ * Worklog segments (CONTEXT.md "Worklog segment"): cut a conversation into
4376
+ * the stretches of a person's attention it actually held. Pure — it lives in
4377
+ * the SDK because the Daily worklog that needs it runs outside the app
4378
+ * (decision 2026-09-12).
4379
+ */
4380
+ interface ConversationMessage {
4381
+ at: Date | string;
4382
+ role: 'user' | 'assistant';
4383
+ }
4384
+ interface WorklogSegment {
4385
+ start: Date;
4386
+ end: Date;
4387
+ /** Number of the person's messages inside. `0` marks an Agent-run stretch. */
4388
+ humanTurns: number;
4389
+ }
4390
+ /** Silence between the person's messages that ends a segment. */
4391
+ declare const DEFAULT_GAP_MINUTES = 30;
4392
+ /** Segment bounds are rounded outward to this grain. */
4393
+ declare const ROUND_MINUTES = 5;
4394
+ /**
4395
+ * Rules:
4396
+ * - a segment opens at a user message and, as long as the person keeps
4397
+ * replying within `gapMinutes` of their previous message, runs to the
4398
+ * last reply before the next gap (CONTEXT.md: "starts at the person's
4399
+ * first message and ends at the last reply");
4400
+ * - the exception is unattended work: once the assistant has been talking
4401
+ * alone for longer than `gapMinutes` since the person's last message, the
4402
+ * rest of that run — when it lasts longer than `gapMinutes` itself — is
4403
+ * a separate segment with `humanTurns: 0`, and the person's segment ends
4404
+ * at the last message inside the window. A shorter tail is just the reply
4405
+ * and stays in the person's segment;
4406
+ * - every segment is rounded outward to 5 minutes.
4407
+ */
4408
+ declare function segmentConversation(messages: ConversationMessage[], gapMinutes?: number): WorklogSegment[];
4409
+
4086
4410
  declare class ExponentialClient {
4087
4411
  private config;
4088
4412
  private client;
@@ -4112,10 +4436,12 @@ declare class ExponentialClient {
4112
4436
  requirements: RequirementsApi;
4113
4437
  scopes: ScopesApi;
4114
4438
  search: SearchApi;
4439
+ /** Time entries — the Daily worklog's write path (ADR-0061). */
4440
+ time: TimeApi;
4115
4441
  constructor(config: {
4116
4442
  token: string;
4117
4443
  apiUrl: string;
4118
4444
  });
4119
4445
  }
4120
4446
 
4121
- export { type Action, type ActionComment, type ActionCommentAddInput, type ActionCommentUpdateInput, type ActionCreateInput, type ActionOutput, type ActionStatus, type ActionUpdateInput, type ActionsListOutput, type AddInteractionInput, type Area, type AreaCreateInput, type AreaListOptions, type CommentAuthor, type Contact, type ContactCreateInput, type ContactInteraction, type ContactListOutput, type ContactUpdateInput, type ContactsListOptions, type Deal, type DealActivity, type DealCreateInput, type DealMoveInput, type DealUpdateInput, type Decision, type DecisionAdrRef, type DecisionChainRef, type DecisionCreateInput, type DecisionDecider, type DecisionDeciderInput, type DecisionEvidenceTurn, type DecisionFeatureRef, type DecisionLink, type DecisionListOptions, type DecisionListRow, type DecisionMeetingRef, type DecisionOccurrenceRef, type DecisionProductRef, type DecisionProjectRef, type DecisionReviewState, type DecisionSetStatusInput, type DecisionSource, type DecisionStatus, type DecisionTicketRef, type DecisionUpdateInput, type DecisionUserRef, type DraftDecisionsResult, type EnrichContactResult, type Epic, type EpicCreateInput, type EpicListOptions, type EpicPriority, type EpicStatus, type EpicUpdateInput, ExponentialClient, type ExponentialConfig, type Feature, type FeatureComment, type FeatureCommentCreateInput, type FeatureCommentReplyInput, type FeatureCommentThreadInput, type FeatureCommentUpdateInput, type FeatureCreateInput, type FeatureKeyResultLink, type FeatureLinkPageInput, type FeatureListOptions, type FeatureScope, type FeatureScopeStatus, type FeatureStatus, type FeatureUpdateInput, type GlobalSearchOptions, type GlobalSearchOutput, type Goal, type GoalChildSummary, type GoalComment, type GoalCommentAddInput, type GoalCommentUpdateInput, type GoalCreateInput, type GoalHealth, type GoalKeyResultSummary, type GoalLifeDomain, type GoalListOptions, type GoalPeriod, type GoalProjectSummary, type GoalSetParentInput, type GoalSetStatusInput, type GoalStats, type GoalStatsOptions, type GoalStatus, type GoalTreeNode, type GoalTreeOptions, type GoalUpdateInput, type GoalUserSummary, type GoalWritableStatus, type InteractionDirection, type InteractionType, type KanbanStatus, type KeyResult, type KeyResultByObjectiveOptions, type KeyResultCheckIn, type KeyResultCheckInInput, type KeyResultCreateInput, type KeyResultFeatureLink, type KeyResultListOptions, type KeyResultProjectLink, type KeyResultStatus, type KeyResultUnit, type KeyResultUpdateInput, type KnowledgePage, type LabelCreateInput, type LabelListOptions, type ListForEntityInput, type Meeting, type MeetingActionSummary, type MeetingCreateInput, type MeetingDecisions, type MeetingListOptions, type MeetingParticipant, type MeetingParticipantInput, type MeetingProjectRef, type MeetingSourceIntegration, type MeetingTypeFilter, type MeetingUpdateInput, type MeetingWorkspaceRef, type ObjectiveWithKeyResults, type Organization, type OrganizationCreateInput, type OrganizationListOutput, type OrganizationSize, type OrganizationsListOptions, type OverdueCohort, type OverdueTriage, type OverdueTriageRow, type PageComment, type PageCommentCreateInput, type PageCommentUpdateInput, type PageCreateInput, type PageListOptions, type PageUpdateInput, type Pipeline, type PipelineStage, type Priority, type Product, type ProductCreateInput, type ProductUpdateInput, type Project, type ProjectDetail, type ProjectKeyResultLink, type ProjectOutput, type ProjectPriority, type ProjectStatus, type ProjectUpdateInput, type ProjectsListOptions, type ProjectsListOutput, type Requirement, type RequirementCreateInput, type RequirementKind, type RequirementListOptions, type ScopeCreateInput, type ScopeListOptions, type ScopeUpdateInput, type SearchResult, type SearchResultType, type SearchResultWorkspace, type SetEntityTagsInput, type SetEntityTagsResult, type Tag, type TagEntityType, type TagListResult, type Ticket, type TicketAssignee, type TicketComment, type TicketCommentAddInput, type TicketCommentUpdateInput, type TicketCreateInput, type TicketDependencyEdge, type TicketDetail, type TicketListOptions, type TicketSearchOptions, type TicketStatus, type TicketType, type TicketUpdateInput, type TodaysActionRow, type TodaysActions, type TodaysActionsGroup, type UserStory, type UserStoryCreateInput, type UserStoryListOptions, type UserStoryUpdateInput, type Workspace, type WorkspaceMember, type WorkspaceOutput, type WorkspacesListOutput, clearConfig, createConfigStore, getConfigPath, isAuthenticated, isTRPCError, loadConfig, saveConfig };
4447
+ export { type Action, type ActionComment, type ActionCommentAddInput, type ActionCommentUpdateInput, type ActionCreateInput, type ActionOutput, type ActionStatus, type ActionUpdateInput, type ActionUpsertBySourceInput, type ActionUpsertBySourceResult, type ActionsListOutput, type AddInteractionInput, type Area, type AreaCreateInput, type AreaListOptions, type CommentAuthor, type Contact, type ContactCreateInput, type ContactInteraction, type ContactListOutput, type ContactUpdateInput, type ContactsListOptions, type ConversationMessage, DEFAULT_GAP_MINUTES, type DayReport, type DayReportActionRow, type DayReportEntry, type DayReportProductRow, type Deal, type DealActivity, type DealCreateInput, type DealMoveInput, type DealUpdateInput, type Decision, type DecisionAdrRef, type DecisionChainRef, type DecisionCreateInput, type DecisionDecider, type DecisionDeciderInput, type DecisionEvidenceTurn, type DecisionFeatureRef, type DecisionLink, type DecisionListOptions, type DecisionListRow, type DecisionMeetingRef, type DecisionOccurrenceRef, type DecisionProductRef, type DecisionProjectRef, type DecisionReviewState, type DecisionSetStatusInput, type DecisionSource, type DecisionStatus, type DecisionTicketRef, type DecisionUpdateInput, type DecisionUserRef, type DraftDecisionsResult, type EnrichContactResult, type Epic, type EpicCreateInput, type EpicListOptions, type EpicPriority, type EpicStatus, type EpicUpdateInput, ExponentialClient, type ExponentialConfig, type Feature, type FeatureComment, type FeatureCommentCreateInput, type FeatureCommentReplyInput, type FeatureCommentThreadInput, type FeatureCommentUpdateInput, type FeatureCreateInput, type FeatureKeyResultLink, type FeatureLinkPageInput, type FeatureListOptions, type FeatureScope, type FeatureScopeStatus, type FeatureStatus, type FeatureUpdateInput, type GlobalSearchOptions, type GlobalSearchOutput, type Goal, type GoalChildSummary, type GoalComment, type GoalCommentAddInput, type GoalCommentUpdateInput, type GoalCreateInput, type GoalHealth, type GoalKeyResultSummary, type GoalLifeDomain, type GoalListOptions, type GoalPeriod, type GoalProjectSummary, type GoalSetParentInput, type GoalSetStatusInput, type GoalStats, type GoalStatsOptions, type GoalStatus, type GoalTreeNode, type GoalTreeOptions, type GoalUpdateInput, type GoalUserSummary, type GoalWritableStatus, type InteractionDirection, type InteractionType, type KanbanStatus, type KeyResult, type KeyResultByObjectiveOptions, type KeyResultCheckIn, type KeyResultCheckInInput, type KeyResultCreateInput, type KeyResultFeatureLink, type KeyResultListOptions, type KeyResultProjectLink, type KeyResultStatus, type KeyResultUnit, type KeyResultUpdateInput, type KnowledgePage, type LabelCreateInput, type LabelListOptions, type ListForEntityInput, type Meeting, type MeetingActionSummary, type MeetingCreateInput, type MeetingDecisions, type MeetingListOptions, type MeetingParticipant, type MeetingParticipantInput, type MeetingProjectRef, type MeetingSourceIntegration, type MeetingTypeFilter, type MeetingUpdateInput, type MeetingWorkspaceRef, type ObjectiveWithKeyResults, type Organization, type OrganizationCreateInput, type OrganizationListOutput, type OrganizationSize, type OrganizationsListOptions, type OverdueCohort, type OverdueTriage, type OverdueTriageRow, type PageComment, type PageCommentCreateInput, type PageCommentUpdateInput, type PageCreateInput, type PageListOptions, type PageUpdateInput, type Pipeline, type PipelineStage, type Priority, type Product, type ProductCreateInput, type ProductUpdateInput, type Project, type ProjectDetail, type ProjectKeyResultLink, type ProjectOutput, type ProjectPriority, type ProjectStatus, type ProjectUpdateInput, type ProjectsListOptions, type ProjectsListOutput, ROUND_MINUTES, type Requirement, type RequirementCreateInput, type RequirementKind, type RequirementListOptions, type ScopeCreateInput, type ScopeListOptions, type ScopeUpdateInput, type SearchResult, type SearchResultType, type SearchResultWorkspace, type SetEntityTagsInput, type SetEntityTagsResult, type Tag, type TagEntityType, type TagListResult, type Ticket, type TicketAssignee, type TicketComment, type TicketCommentAddInput, type TicketCommentUpdateInput, type TicketCreateInput, type TicketDependencyEdge, type TicketDetail, type TicketListOptions, type TicketSearchOptions, type TicketStatus, type TicketType, type TicketUpdateInput, TimeApi, type TimeConfirmDayResult, type TimeEntry, type TimeEntrySource, type TimeEntryStatus, type TimeLogBatchResult, type TimeLogInput, type TimeLogOutcome, type TimeLogResult, type TodaysActionRow, type TodaysActions, type TodaysActionsGroup, type UserStory, type UserStoryCreateInput, type UserStoryListOptions, type UserStoryUpdateInput, type WorklogSegment, type Workspace, type WorkspaceMember, type WorkspaceOutput, type WorkspacesListOutput, clearConfig, createConfigStore, getConfigPath, isAuthenticated, isTRPCError, loadConfig, saveConfig, segmentConversation };
package/dist/index.js CHANGED
@@ -144,6 +144,15 @@ var ActionsApi = class {
144
144
  async update(input) {
145
145
  return await this.client.action.update.mutate(input);
146
146
  }
147
+ /**
148
+ * One Action per external source: find by (workspaceId, sourceType,
149
+ * sourceId), refresh its name and links, else create it. `outcome` says
150
+ * which happened. Agents may call this; the created Action records the
151
+ * agent's shadow user as creator with `source: "agent"` (ADR-0049).
152
+ */
153
+ async upsertBySource(input) {
154
+ return await this.client.action.upsertBySource.mutate(input);
155
+ }
147
156
  };
148
157
 
149
158
  // src/actionComments.ts
@@ -1152,6 +1161,101 @@ var SearchApi = class {
1152
1161
  }
1153
1162
  };
1154
1163
 
1164
+ // src/time.ts
1165
+ function startOfLocalDay(date) {
1166
+ const d = typeof date === "string" && /^\d{4}-\d{2}-\d{2}$/.test(date) ? /* @__PURE__ */ new Date(`${date}T00:00:00`) : new Date(date);
1167
+ if (Number.isNaN(d.getTime())) {
1168
+ throw new Error(`Invalid date: "${String(date)}". Use YYYY-MM-DD or an ISO timestamp.`);
1169
+ }
1170
+ return new Date(d.getFullYear(), d.getMonth(), d.getDate());
1171
+ }
1172
+ var TimeApi = class {
1173
+ constructor(client) {
1174
+ this.client = client;
1175
+ }
1176
+ /**
1177
+ * Log one completed entry with explicit bounds. Never starts, stops or
1178
+ * alters the running Timer. With `sourceRef` the call is an upsert keyed
1179
+ * on (owner, sourceRef); without it every call creates a row.
1180
+ */
1181
+ async log(input) {
1182
+ if (input.sourceRef) {
1183
+ return await this.client.timeEntry.upsertBySourceRef.mutate({
1184
+ ...input,
1185
+ sourceRef: input.sourceRef
1186
+ });
1187
+ }
1188
+ const entry = await this.client.timeEntry.create.mutate(input);
1189
+ return { entry, outcome: "created", pieces: [entry], mergedInto: [] };
1190
+ }
1191
+ /**
1192
+ * Log many entries, one call each and in order, so a failure names the
1193
+ * entry it belonged to instead of rejecting the whole batch. `defaults`
1194
+ * fill any field an entry leaves out.
1195
+ */
1196
+ async logBatch(entries, defaults = {}) {
1197
+ const results = [];
1198
+ for (const [index, item] of entries.entries()) {
1199
+ const merged = { ...defaults, ...item };
1200
+ const sourceRef = merged.sourceRef;
1201
+ try {
1202
+ if (!merged.actionId) throw new Error("actionId is required");
1203
+ if (!merged.startedAt || !merged.endedAt) {
1204
+ throw new Error("startedAt and endedAt are required");
1205
+ }
1206
+ const result = await this.log(merged);
1207
+ results.push({ index, success: true, sourceRef, ...result });
1208
+ } catch (error) {
1209
+ results.push({
1210
+ index,
1211
+ success: false,
1212
+ sourceRef,
1213
+ error: error instanceof Error ? error.message : String(error)
1214
+ });
1215
+ }
1216
+ }
1217
+ return results;
1218
+ }
1219
+ /**
1220
+ * Confirm a day: every PROPOSED entry of yours that starts on that local
1221
+ * calendar day becomes CONFIRMED and the Actions' spent time moves. Human
1222
+ * only — the server refuses agent keys, so run this with personal
1223
+ * credentials. A day with nothing proposed returns `{ confirmed: 0 }`.
1224
+ */
1225
+ async confirmDay(date, workspaceId) {
1226
+ return await this.client.timeEntry.confirmDay.mutate({
1227
+ date: startOfLocalDay(date),
1228
+ workspaceId
1229
+ });
1230
+ }
1231
+ /**
1232
+ * The day report for one local calendar day: entries joined to Action,
1233
+ * Ticket, Project and Product, attention / session / agent-run minutes,
1234
+ * the overlap-split roll-ups and the counts — exactly what the `/time` Day
1235
+ * tab and the Daily summary's Yesterday line read. Under an agent key the
1236
+ * report is the OWNER's day.
1237
+ */
1238
+ async dayReport(date, workspaceId) {
1239
+ return await this.client.timeEntry.dayReport.query({
1240
+ date: startOfLocalDay(date),
1241
+ workspaceId
1242
+ });
1243
+ }
1244
+ /**
1245
+ * The caller's entries that touch one calendar day (local time), oldest
1246
+ * first, each with its `status`. Pass `YYYY-MM-DD` or a Date.
1247
+ */
1248
+ async list(date, workspaceId) {
1249
+ const startDate = startOfLocalDay(date);
1250
+ const endDate = new Date(startDate.getTime() + 24 * 60 * 60 * 1e3);
1251
+ return await this.client.timeEntry.listByDateRange.query({
1252
+ startDate,
1253
+ endDate,
1254
+ workspaceId
1255
+ });
1256
+ }
1257
+ };
1258
+
1155
1259
  // src/config.ts
1156
1260
  import Conf from "conf";
1157
1261
  var schema = {
@@ -1229,6 +1333,60 @@ function getConfigPath() {
1229
1333
  return defaultStore.path;
1230
1334
  }
1231
1335
 
1336
+ // src/segment.ts
1337
+ var DEFAULT_GAP_MINUTES = 30;
1338
+ var ROUND_MINUTES = 5;
1339
+ function roundDown(ms) {
1340
+ const grain = ROUND_MINUTES * 6e4;
1341
+ return new Date(Math.floor(ms / grain) * grain);
1342
+ }
1343
+ function roundUp(ms) {
1344
+ const grain = ROUND_MINUTES * 6e4;
1345
+ return new Date(Math.ceil(ms / grain) * grain);
1346
+ }
1347
+ function segmentConversation(messages, gapMinutes = DEFAULT_GAP_MINUTES) {
1348
+ const gapMs = gapMinutes * 6e4;
1349
+ const sorted = messages.map((m) => ({ at: new Date(m.at).getTime(), role: m.role })).filter((m) => !Number.isNaN(m.at)).sort((a, b) => a.at - b.at);
1350
+ const out = [];
1351
+ let seg = null;
1352
+ let run = null;
1353
+ const closeSegment = () => {
1354
+ if (seg) out.push({ start: roundDown(seg.start), end: roundUp(seg.end), humanTurns: seg.humanTurns });
1355
+ seg = null;
1356
+ };
1357
+ const resolveRun = () => {
1358
+ if (!run) return;
1359
+ if (run.end - run.start > gapMs) {
1360
+ closeSegment();
1361
+ out.push({ start: roundDown(run.start), end: roundUp(run.end), humanTurns: 0 });
1362
+ } else if (seg) {
1363
+ seg.end = Math.max(seg.end, run.end);
1364
+ }
1365
+ run = null;
1366
+ };
1367
+ for (const m of sorted) {
1368
+ if (m.role === "user") {
1369
+ resolveRun();
1370
+ if (seg && m.at - seg.lastUserAt > gapMs) closeSegment();
1371
+ if (!seg) seg = { start: m.at, end: m.at, humanTurns: 0, lastUserAt: m.at };
1372
+ seg.end = Math.max(seg.end, m.at);
1373
+ seg.lastUserAt = m.at;
1374
+ seg.humanTurns += 1;
1375
+ continue;
1376
+ }
1377
+ if (run) {
1378
+ run.end = m.at;
1379
+ } else if (!seg || m.at - seg.lastUserAt > gapMs) {
1380
+ run = { start: m.at, end: m.at };
1381
+ } else {
1382
+ seg.end = Math.max(seg.end, m.at);
1383
+ }
1384
+ }
1385
+ resolveRun();
1386
+ closeSegment();
1387
+ return out;
1388
+ }
1389
+
1232
1390
  // src/index.ts
1233
1391
  var ExponentialClient = class {
1234
1392
  constructor(config) {
@@ -1259,6 +1417,7 @@ var ExponentialClient = class {
1259
1417
  this.requirements = new RequirementsApi(this.client);
1260
1418
  this.scopes = new ScopesApi(this.client);
1261
1419
  this.search = new SearchApi(this.client);
1420
+ this.time = new TimeApi(this.client);
1262
1421
  }
1263
1422
  client;
1264
1423
  actions;
@@ -1287,16 +1446,22 @@ var ExponentialClient = class {
1287
1446
  requirements;
1288
1447
  scopes;
1289
1448
  search;
1449
+ /** Time entries — the Daily worklog's write path (ADR-0061). */
1450
+ time;
1290
1451
  };
1291
1452
  export {
1453
+ DEFAULT_GAP_MINUTES,
1292
1454
  ExponentialClient,
1455
+ ROUND_MINUTES,
1293
1456
  TRPCClientError,
1457
+ TimeApi,
1294
1458
  clearConfig,
1295
1459
  createConfigStore,
1296
1460
  getConfigPath,
1297
1461
  isAuthenticated,
1298
1462
  isTRPCError,
1299
1463
  loadConfig,
1300
- saveConfig
1464
+ saveConfig,
1465
+ segmentConversation
1301
1466
  };
1302
1467
  //# sourceMappingURL=index.js.map