@zhangj1164/dsh-github-issue 0.2.0 → 0.4.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/lib/index.d.ts CHANGED
@@ -1,8 +1,9 @@
1
- import { GithubIssueAnalysisWindow, GithubIssueFailureGroupInput, GithubIssueFailureRoute, GithubIssueGenerateReportRequest, GithubIssueGenerateReportResult, GithubIssueOptimizeRequest, GithubIssueOptimizeResult, GithubIssuePrefillRequest, GithubIssuePrefillResult, GithubIssueReport } from "./types.js";
1
+ import { GithubIssueAnalysisWindow, GithubIssueCreateRequest, GithubIssueCreateResult, GithubIssueCreated, GithubIssueDispatchRequest, GithubIssueDispatchResult, GithubIssueDispatched, GithubIssueFailure, GithubIssueFailureCode, GithubIssueFailureGroupInput, GithubIssueFailureRoute, GithubIssueGenerateReportRequest, GithubIssueGenerateReportResult, GithubIssueIntakeFailure, GithubIssueIntakeRequest, GithubIssueIntakeResult, GithubIssueIntakeValue, GithubIssueListRequest, GithubIssueListResult, GithubIssueOptimizeRequest, GithubIssueOptimizeResult, GithubIssuePrefillRequest, GithubIssuePrefillResult, GithubIssueReport, GithubIssueTask } from "./types.js";
2
+ import { Context, Service } from "@deepseek-ai/cordis";
2
3
  import s from "@deepseek-ai/schemastery";
3
4
  import { TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
4
5
  import { GenerateOptions, StreamChunk } from "@deepseek-ai/dsh-llm";
5
- import { Context } from "@deepseek-ai/cordis";
6
+ import { Volatile } from "@deepseek-ai/cosmokit";
6
7
  //#region src/llm-text.d.ts
7
8
  /**
8
9
  * Minimal shape of the DSH `llm` service this helper needs. Declared
@@ -40,14 +41,22 @@ type LlmTextResult = {
40
41
  };
41
42
  //#endregion
42
43
  //#region src/index.d.ts
43
- /** Deployment configuration for the GitHub-issue service. */
44
+ /**
45
+ * Deployment configuration for the GitHub-issue service.
46
+ *
47
+ * The fields a settings surface may rewrite (`repoUrl`, `claimLabel`,
48
+ * `maxIssuesPerRun`, and the five autopilot fields) are {@link Volatile}: the
49
+ * Loader resolves each into a ref it updates in place, so an accepted settings
50
+ * write reaches the running service without a restart. The remaining fields
51
+ * are deployment-only and stay in `cordis.yml`.
52
+ */
44
53
  interface Config {
45
54
  /**
46
55
  * The GitHub repository URL for issue prefill (e.g.
47
56
  * `https://github.com/owner/repo`). Used as the default when a request omits
48
57
  * `repoUrl`. The deployment sets this to the real project repository.
49
58
  */
50
- readonly repoUrl: string;
59
+ readonly repoUrl: Volatile<string>;
51
60
  /**
52
61
  * Longest pre-filled issue URL to produce, counted in characters.
53
62
  *
@@ -73,6 +82,76 @@ interface Config {
73
82
  * `agentDefaultModel` selection.
74
83
  */
75
84
  readonly model?: string;
85
+ /**
86
+ * Environment variable that carries the GitHub token. Read only when
87
+ * {@link Config.token} is unset, and only when no token command is configured
88
+ * or the command produced nothing.
89
+ */
90
+ readonly tokenEnv: string;
91
+ /**
92
+ * Explicit GitHub token. A deployment that manages secrets itself sets this;
93
+ * a token stored in the environment or produced by {@link Config.tokenCommand}
94
+ * is read when this field is absent.
95
+ */
96
+ readonly token?: string;
97
+ /**
98
+ * Command whose standard output is a GitHub token, run only when neither
99
+ * {@link Config.token} nor {@link Config.tokenEnv} supplies one. The default
100
+ * reads the credential the `gh` CLI already stores, so a machine that is
101
+ * logged in to GitHub needs no extra secret; set it to an empty string to
102
+ * disable the lookup.
103
+ */
104
+ readonly tokenCommand: string;
105
+ /** Deadline for {@link Config.tokenCommand}, in milliseconds. */
106
+ readonly tokenCommandTimeoutMs: number;
107
+ /**
108
+ * GitHub REST origin. `https://api.github.com` serves github.com; an
109
+ * Enterprise deployment sets its own `https://host/api/v3`.
110
+ */
111
+ readonly apiBaseUrl: string;
112
+ /** Deadline for one GitHub REST request, in milliseconds. */
113
+ readonly requestTimeoutMs: number;
114
+ /**
115
+ * Label that marks an issue claimable by the intake loop. The repository's
116
+ * triage vocabulary owns the value; this field only names which label counts.
117
+ */
118
+ readonly claimLabel: Volatile<string>;
119
+ /** Largest number of issues one intake pass may dispatch. */
120
+ readonly maxIssuesPerRun: Volatile<number>;
121
+ /**
122
+ * Whether the host checks for claimable issues on its first startup of the
123
+ * day. Off by default: opening a Session without being asked is a deployment
124
+ * decision, never a surprise a plugin installs on its own.
125
+ */
126
+ readonly autopilotEnabled: Volatile<boolean>;
127
+ /**
128
+ * Working directory the dispatched Sessions own. Empty follows the host's
129
+ * default directory, which is the deployment's current working directory.
130
+ */
131
+ readonly autopilotCwd: Volatile<string>;
132
+ /** Agent preset the dispatched Sessions run; empty follows the deployment default. */
133
+ readonly autopilotPreset: Volatile<string>;
134
+ /**
135
+ * IANA zone "today" is measured in. Empty follows the host's own zone, so a
136
+ * user's local midnight is what starts a new day.
137
+ */
138
+ readonly autopilotTimeZone: Volatile<string>;
139
+ /**
140
+ * Prompt template handed to every dispatched Session. Placeholders are
141
+ * documented on `renderDispatchPrompt`; the default states the issue
142
+ * location and points at the repository workflow.
143
+ */
144
+ readonly autopilotPrompt: Volatile<string>;
145
+ /**
146
+ * Seconds between registering a dispatch's automation task and its delivery.
147
+ *
148
+ * The Schedule service refuses a non-positive delay, so this is at least one
149
+ * second. It stays a deployment-level value instead of a constant because a
150
+ * deployment may want the task to sit visibly on the board before it fires,
151
+ * and it stays out of the settings surface: it times a dispatch, it does not
152
+ * describe one.
153
+ */
154
+ readonly dispatchTaskDelaySeconds: number;
76
155
  }
77
156
  /** Schemastery configuration for the GitHub-issue service. */
78
157
  declare const Config: s<Config>;
@@ -83,22 +162,62 @@ declare module '@deepseek-ai/cordis' {
83
162
  }
84
163
  /**
85
164
  * GitHub-issue generation and optimization service over the configured model.
165
+ *
166
+ * Beyond the model-backed report methods, the service owns the issue intake
167
+ * loop: it files an optimized report through the GitHub REST API, lists the
168
+ * issues that carry the claim label, and hands one of them to a fresh Session.
86
169
  */
87
170
  declare class GithubIssueService extends TypertRemoteService {
88
171
  static inject: string[];
89
172
  static Config: s<Config>;
90
- private readonly repoUrl;
91
173
  private readonly maxPrefillUrlLength;
92
174
  private readonly prefillTruncationNote;
93
175
  /** Configured provider route, or `undefined` to follow `agentDefaultModel`. */
94
176
  private readonly provider?;
95
177
  /** Configured model id, or `undefined` to follow `agentDefaultModel`. */
96
178
  private readonly model?;
179
+ private readonly tokenEnv;
180
+ private readonly token?;
181
+ private readonly tokenCommand;
182
+ private readonly tokenCommandTimeoutMs;
183
+ private readonly apiBaseUrl;
184
+ private readonly requestTimeoutMs;
185
+ /** The persisted state, or the empty state when this deployment has no storage. */
186
+ private intakeState;
187
+ /** The durable table, absent when this deployment mounts no storage domain. */
188
+ private intakeTable?;
189
+ /** Whether the state is durable; the startup check refuses to run without it. */
190
+ private intakePersistent;
191
+ /** The resolved token, cached so a token command runs at most once. */
192
+ private resolvedToken?;
193
+ /** Whether token resolution finished; a missing credential is a result, not a retry. */
194
+ private tokenResolved;
195
+ /** The last invalid time zone already reported, so a warning is not repeated per call. */
196
+ private warnedTimeZone;
97
197
  /**
98
198
  * @param ctx - Host context carrying the llm service.
99
199
  * @param config - Validated deployment configuration.
100
200
  */
101
201
  constructor(ctx: Context, config: Config);
202
+ /**
203
+ * The IANA zone the intake day is measured in.
204
+ *
205
+ * Read per call: a settings surface can rewrite it while the service runs. An
206
+ * invalid zone must not break the startup check or a dispatch, so it falls
207
+ * back to the host zone and is reported once per changed value.
208
+ * @returns the configured zone, or an empty string for the host zone.
209
+ */
210
+ private intakeTimeZone;
211
+ /**
212
+ * Open the durable intake state and, when enabled, run the first-startup
213
+ * check.
214
+ *
215
+ * The check is deliberately not awaited: opening a Session is useful work
216
+ * but not a prerequisite for the service, and a slow or unreachable GitHub
217
+ * must not hold up the host's boot. A failure is logged and retried on the
218
+ * next startup, because the day key is only committed after a completed pass.
219
+ */
220
+ protected [Service.init](): Promise<void>;
102
221
  /**
103
222
  * Resolve the model route for one model call.
104
223
  *
@@ -140,6 +259,130 @@ declare class GithubIssueService extends TypertRemoteService {
140
259
  * @returns the structured report or an LLM failure.
141
260
  */
142
261
  optimizeIssue(request: GithubIssueOptimizeRequest): Promise<GithubIssueOptimizeResult>;
262
+ /**
263
+ * File one issue in the configured repository.
264
+ *
265
+ * The body is filed without its leading title heading: the optimizer repeats
266
+ * the title there so one text can drive both the pre-filled URL form and a
267
+ * direct filing, and GitHub takes the title as its own field.
268
+ *
269
+ * @param request - repository override and the report to file.
270
+ * @returns the created issue, or a stable failure.
271
+ */
272
+ createIssue(request: GithubIssueCreateRequest): Promise<GithubIssueCreateResult>;
273
+ /**
274
+ * List the open issues that carry the claim label.
275
+ * @param request - repository, label, and count overrides.
276
+ * @returns the claimable issues, or a stable failure.
277
+ */
278
+ listClaimableIssues(request?: GithubIssueListRequest): Promise<GithubIssueListResult>;
279
+ /**
280
+ * Hand one issue to a fresh Session.
281
+ * @param request - the issue and optional Session placement.
282
+ * @returns the created Session, or a stable failure.
283
+ */
284
+ dispatchIssue(request: GithubIssueDispatchRequest): Promise<GithubIssueDispatchResult>;
285
+ /**
286
+ * Run one intake pass: list the claimable issues, skip the ones an earlier
287
+ * pass already dispatched, and hand the rest to a fresh Session.
288
+ *
289
+ * The local day is committed only after the listing succeeded, so a pass that
290
+ * could not reach GitHub is retried on the next startup instead of being
291
+ * remembered as done. An issue that was already dispatched is never
292
+ * dispatched again, even on a later day: its Session exists, and a second one
293
+ * would duplicate the work.
294
+ *
295
+ * @param request - repository, label, count, and Session placement overrides.
296
+ * @returns what the pass read and did.
297
+ */
298
+ runIntake(request?: GithubIssueIntakeRequest): Promise<GithubIssueIntakeResult>;
299
+ /**
300
+ * Create one Session, register its automation task, and name the Session.
301
+ *
302
+ * A dispatch is two steps. The Session is created through the controller, so
303
+ * the deployment's preset and workspace policy apply. The work is then handed
304
+ * over as a one-shot automation task on the Schedule service: the task shows
305
+ * up in the Web's automation board, and the service delivers its prompt into
306
+ * that Session when the task becomes due. A task is always bound to an
307
+ * existing Session — the board never opens one by itself — so the Session has
308
+ * to exist first, and registering the task is what makes the dispatch visible
309
+ * beyond the sidebar.
310
+ *
311
+ * A deployment that mounts no Schedule service (a non-Web composition) falls
312
+ * back to admitting the prompt directly, the same durable admission the Web
313
+ * prompt uses. Both paths fail the dispatch when the host refuses, so a
314
+ * refusal never leaves the day marked as checked.
315
+ *
316
+ * @param request - the issue and optional Session placement.
317
+ * @returns the created Session, or a stable failure.
318
+ */
319
+ private dispatch;
320
+ /**
321
+ * Name a dispatched Session so the user can recognise it in the sidebar.
322
+ *
323
+ * A title is presentation, not part of the dispatch contract: a deployment
324
+ * that mounts no title service still gets a working Session, so a failure
325
+ * here is logged and never fails the dispatch.
326
+ *
327
+ * @param controller - the Session controller.
328
+ * @param sessionId - the Session to name.
329
+ * @param title - the proposed title.
330
+ */
331
+ private renameSession;
332
+ /**
333
+ * Run the first-startup check once per local day.
334
+ *
335
+ * The day is read from durable state, so a restart later the same day does
336
+ * nothing; the check is skipped until the local calendar moves on.
337
+ */
338
+ private startupIntake;
339
+ /**
340
+ * Open the durable intake state when this deployment mounts storage.
341
+ *
342
+ * Storage is optional so the service still installs on its own bundle; the
343
+ * startup check then stays off rather than running without a memory of what
344
+ * it already dispatched.
345
+ */
346
+ private openIntakeState;
347
+ /**
348
+ * Commit one completed pass to durable state.
349
+ *
350
+ * The in-memory state is updated first so the next pass in this process sees
351
+ * the dispatch even when the durable write fails; the warning then names the
352
+ * consequence instead of pretending the write succeeded.
353
+ *
354
+ * @param day - local day the pass completed.
355
+ * @param dispatched - issues handed to a Session in this pass.
356
+ */
357
+ private commitIntakeState;
358
+ /**
359
+ * Build one GitHub client for a call.
360
+ * @param repoUrl - repository override, or the configured repository.
361
+ * @returns the client, carrying the resolved credential.
362
+ */
363
+ private createClient;
364
+ /**
365
+ * Resolve the GitHub credential once.
366
+ *
367
+ * The order is explicit config, then the configured environment variable,
368
+ * then the configured command. The command runs at most once per process so
369
+ * a keyring-backed credential is not fetched on every request, and its output
370
+ * is never logged.
371
+ *
372
+ * @returns the token, or undefined when the deployment has none.
373
+ */
374
+ private resolveToken;
375
+ /**
376
+ * The telemetry feature anchor for one intake action.
377
+ * @param action - the method the event belongs to.
378
+ * @returns the stable feature-code reference.
379
+ */
380
+ private featureCode;
381
+ /**
382
+ * Log one non-fatal intake problem.
383
+ * @param message - what went wrong and what it costs.
384
+ */
385
+ private warn;
143
386
  /**
144
387
  * Report one model failure and return the business failure.
145
388
  *
@@ -195,4 +438,4 @@ declare class GithubIssueService extends TypertRemoteService {
195
438
  private trackError;
196
439
  }
197
440
  //#endregion
198
- export { Config, type GithubIssueAnalysisWindow, type GithubIssueFailureGroupInput, type GithubIssueFailureRoute, type GithubIssueGenerateReportRequest, type GithubIssueGenerateReportResult, type GithubIssueOptimizeRequest, type GithubIssueOptimizeResult, type GithubIssuePrefillRequest, type GithubIssuePrefillResult, type GithubIssueReport, GithubIssueService, GithubIssueService as default, type LlmRoute, type LlmTextFailure, type LlmTextResult, type LlmTextSource };
441
+ export { Config, type GithubIssueAnalysisWindow, type GithubIssueCreateRequest, type GithubIssueCreateResult, type GithubIssueCreated, type GithubIssueDispatchRequest, type GithubIssueDispatchResult, type GithubIssueDispatched, type GithubIssueFailure, type GithubIssueFailureCode, type GithubIssueFailureGroupInput, type GithubIssueFailureRoute, type GithubIssueGenerateReportRequest, type GithubIssueGenerateReportResult, type GithubIssueIntakeFailure, type GithubIssueIntakeRequest, type GithubIssueIntakeResult, type GithubIssueIntakeValue, type GithubIssueListRequest, type GithubIssueListResult, type GithubIssueOptimizeRequest, type GithubIssueOptimizeResult, type GithubIssuePrefillRequest, type GithubIssuePrefillResult, type GithubIssueReport, GithubIssueService, GithubIssueService as default, type GithubIssueTask, type LlmRoute, type LlmTextFailure, type LlmTextResult, type LlmTextSource };