dsh-plugin-jules 0.1.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/types.d.ts ADDED
@@ -0,0 +1,230 @@
1
+ /**
2
+ * Wire types for the Jules v1alpha REST API, plus the small amount of
3
+ * normalization the tools do before they call it.
4
+ *
5
+ * Everything here describes what the service sends: Jules omits optional
6
+ * members rather than sending nulls, so every field a response may omit is
7
+ * optional. Nothing in this module imports a harness package, which keeps the
8
+ * API surface testable on its own.
9
+ *
10
+ * @module dsh-plugin-jules/types
11
+ */
12
+ /** Lifecycle state of one Jules session, as the service reports it. */
13
+ export type SessionState = 'STATE_UNSPECIFIED' | 'QUEUED' | 'PLANNING' | 'AWAITING_PLAN_APPROVAL' | 'AWAITING_USER_FEEDBACK' | 'IN_PROGRESS' | 'PAUSED' | 'FAILED' | 'COMPLETED';
14
+ /** States a session reaches before it starts or after it stops working. */
15
+ export declare const SETTLED_STATES: readonly SessionState[];
16
+ /** States that stop the agent until a human or a model answers it. */
17
+ export declare const ATTENTION_STATES: readonly SessionState[];
18
+ /**
19
+ * States a caller may reasonably wait for; the default `jules_wait` target.
20
+ *
21
+ * `PAUSED` is deliberately absent. The reference lists it in the enum and
22
+ * documents nothing about what a paused session is waiting for or whether
23
+ * anything resumes it, so treating it as an end to the wait would invent a
24
+ * contract. A caller who knows better can name it in `until`, and until then a
25
+ * paused session reports as a wait that ran out rather than one that finished.
26
+ *
27
+ * `QUEUED`, `PLANNING` and `IN_PROGRESS` are absent for the obvious reason:
28
+ * they are the states a wait exists to sit through.
29
+ */
30
+ export declare const DEFAULT_WAIT_STATES: readonly SessionState[];
31
+ /**
32
+ * Report whether a session has stopped for good.
33
+ * @param state - the state the service reported.
34
+ * @returns whether the session reached a terminal state.
35
+ */
36
+ export declare function settledState(state: string): boolean;
37
+ /**
38
+ * Report whether a session is blocked on an answer.
39
+ * @param state - the state the service reported.
40
+ * @returns whether the session waits for a human or model decision.
41
+ */
42
+ export declare function needsAttentionState(state: string): boolean;
43
+ /** One file's worth of output in a `changeSet` artifact. */
44
+ export interface GitPatch {
45
+ baseCommitId?: string;
46
+ unidiffPatch?: string;
47
+ suggestedCommitMessage?: string;
48
+ }
49
+ /** A code change the agent produced. */
50
+ export interface ChangeSet {
51
+ source?: string;
52
+ gitPatch?: GitPatch;
53
+ }
54
+ /** A shell command the agent ran and what it printed. */
55
+ export interface BashOutput {
56
+ command?: string;
57
+ output?: string;
58
+ exitCode?: number;
59
+ }
60
+ /** Base64 media the agent attached, such as a screenshot. */
61
+ export interface Media {
62
+ mimeType?: string;
63
+ data?: string;
64
+ }
65
+ /** One thing an activity produced; exactly one member is present. */
66
+ export interface Artifact {
67
+ changeSet?: ChangeSet;
68
+ bashOutput?: BashOutput;
69
+ media?: Media;
70
+ }
71
+ /** One step of a generated plan. */
72
+ export interface PlanStep {
73
+ id?: string;
74
+ index?: number;
75
+ title?: string;
76
+ description?: string;
77
+ }
78
+ /** The plan a session is executing. */
79
+ export interface Plan {
80
+ id?: string;
81
+ steps?: PlanStep[];
82
+ createTime?: string;
83
+ }
84
+ /** One entry in a session's event log; exactly one event member is present. */
85
+ export interface Activity {
86
+ name?: string;
87
+ id?: string;
88
+ originator?: string;
89
+ description?: string;
90
+ createTime?: string;
91
+ artifacts?: Artifact[];
92
+ planGenerated?: {
93
+ plan?: Plan;
94
+ };
95
+ planApproved?: {
96
+ planId?: string;
97
+ };
98
+ userMessaged?: {
99
+ userMessage?: string;
100
+ };
101
+ agentMessaged?: {
102
+ agentMessage?: string;
103
+ };
104
+ progressUpdated?: {
105
+ title?: string;
106
+ description?: string;
107
+ };
108
+ sessionCompleted?: Record<string, never>;
109
+ sessionFailed?: {
110
+ reason?: string;
111
+ };
112
+ }
113
+ /** A pull request the session opened. */
114
+ export interface PullRequest {
115
+ url?: string;
116
+ title?: string;
117
+ description?: string;
118
+ baseRef?: string;
119
+ headRef?: string;
120
+ }
121
+ /** One deliverable of a finished session. */
122
+ export interface SessionOutput {
123
+ pullRequest?: PullRequest;
124
+ }
125
+ /** A file the session produced, in full or as added lines. */
126
+ export interface GeneratedFile {
127
+ path?: string;
128
+ changeType?: string;
129
+ content?: string;
130
+ }
131
+ /** The repository a source points at. */
132
+ export interface GithubRepo {
133
+ owner?: string;
134
+ repo?: string;
135
+ isPrivate?: boolean;
136
+ defaultBranch?: {
137
+ displayName?: string;
138
+ };
139
+ branches?: {
140
+ displayName?: string;
141
+ }[];
142
+ }
143
+ /** A repository connected to Jules through the GitHub App. */
144
+ export interface Source {
145
+ name?: string;
146
+ id?: string;
147
+ githubRepo?: GithubRepo;
148
+ }
149
+ /** One session, as `/sessions/{id}` returns it. */
150
+ export interface Session {
151
+ name?: string;
152
+ id?: string;
153
+ prompt?: string;
154
+ title?: string;
155
+ state?: SessionState;
156
+ url?: string;
157
+ sourceContext?: {
158
+ source?: string;
159
+ githubRepoContext?: {
160
+ startingBranch?: string;
161
+ workingBranch?: string;
162
+ };
163
+ };
164
+ requirePlanApproval?: boolean;
165
+ automationMode?: string;
166
+ outputs?: SessionOutput[];
167
+ createTime?: string;
168
+ updateTime?: string;
169
+ generatedFiles?: GeneratedFile[];
170
+ archived?: boolean;
171
+ }
172
+ /** A page of sources. */
173
+ export interface ListSourcesResponse {
174
+ sources?: Source[];
175
+ nextPageToken?: string;
176
+ }
177
+ /** A page of sessions. */
178
+ export interface ListSessionsResponse {
179
+ sessions?: Session[];
180
+ nextPageToken?: string;
181
+ }
182
+ /** A page of activities. */
183
+ export interface ListActivitiesResponse {
184
+ activities?: Activity[];
185
+ nextPageToken?: string;
186
+ }
187
+ /** What `jules_create` sends. `sourceContext` is omitted for a repoless session. */
188
+ export interface CreateSessionRequest {
189
+ prompt: string;
190
+ title?: string;
191
+ sourceContext?: {
192
+ source: string;
193
+ githubRepoContext?: {
194
+ startingBranch?: string;
195
+ };
196
+ };
197
+ requirePlanApproval?: boolean;
198
+ automationMode?: string;
199
+ }
200
+ /**
201
+ * Reduce any session reference a model may produce to the bare id the API
202
+ * paths use: `123`, `sessions/123`, or the `jules.google.com/session/123`
203
+ * URL all become `123`.
204
+ * @param reference - session id, resource name, or session URL.
205
+ * @returns the bare session id.
206
+ * @throws {TypeError} when the reference carries no id.
207
+ */
208
+ export declare function sessionIdOf(reference: string): string;
209
+ /**
210
+ * Accept the three spellings of a connected repository and produce the
211
+ * resource name the API expects: `owner/repo` and `github/owner/repo` both
212
+ * become `sources/github/owner/repo`.
213
+ * @param reference - `owner/repo`, `github/owner/repo`, or the full resource name.
214
+ * @returns the canonical source resource name.
215
+ * @throws {TypeError} when the reference is not a two-part repository id.
216
+ */
217
+ export declare function sourceNameOf(reference: string): string;
218
+ /**
219
+ * Render a source resource name back as `owner/repo` for display, leaving an
220
+ * unrecognized name untouched.
221
+ * @param name - the source resource name.
222
+ * @returns the short repository reference.
223
+ */
224
+ export declare function shortSourceName(name: string | undefined): string;
225
+ /**
226
+ * Identify which event an activity carries.
227
+ * @param activity - one activity from the log.
228
+ * @returns the event member name, or `'unknown'` when none is present.
229
+ */
230
+ export declare function activityKind(activity: Activity): string;
package/lib/types.js ADDED
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Wire types for the Jules v1alpha REST API, plus the small amount of
3
+ * normalization the tools do before they call it.
4
+ *
5
+ * Everything here describes what the service sends: Jules omits optional
6
+ * members rather than sending nulls, so every field a response may omit is
7
+ * optional. Nothing in this module imports a harness package, which keeps the
8
+ * API surface testable on its own.
9
+ *
10
+ * @module dsh-plugin-jules/types
11
+ */
12
+ /** States a session reaches before it starts or after it stops working. */
13
+ export const SETTLED_STATES = ['COMPLETED', 'FAILED'];
14
+ /** States that stop the agent until a human or a model answers it. */
15
+ export const ATTENTION_STATES = ['AWAITING_PLAN_APPROVAL', 'AWAITING_USER_FEEDBACK'];
16
+ /**
17
+ * States a caller may reasonably wait for; the default `jules_wait` target.
18
+ *
19
+ * `PAUSED` is deliberately absent. The reference lists it in the enum and
20
+ * documents nothing about what a paused session is waiting for or whether
21
+ * anything resumes it, so treating it as an end to the wait would invent a
22
+ * contract. A caller who knows better can name it in `until`, and until then a
23
+ * paused session reports as a wait that ran out rather than one that finished.
24
+ *
25
+ * `QUEUED`, `PLANNING` and `IN_PROGRESS` are absent for the obvious reason:
26
+ * they are the states a wait exists to sit through.
27
+ */
28
+ export const DEFAULT_WAIT_STATES = [...SETTLED_STATES, ...ATTENTION_STATES];
29
+ /**
30
+ * Report whether a session has stopped for good.
31
+ * @param state - the state the service reported.
32
+ * @returns whether the session reached a terminal state.
33
+ */
34
+ export function settledState(state) {
35
+ return SETTLED_STATES.includes(state);
36
+ }
37
+ /**
38
+ * Report whether a session is blocked on an answer.
39
+ * @param state - the state the service reported.
40
+ * @returns whether the session waits for a human or model decision.
41
+ */
42
+ export function needsAttentionState(state) {
43
+ return ATTENTION_STATES.includes(state);
44
+ }
45
+ /** The identifying part of a session reference, accepted in any of its forms. */
46
+ const SESSION_ID_PATTERN = /^sessions\/([^/]+)$/;
47
+ /** A reference that points at an activity rather than at its session. */
48
+ const ACTIVITY_REFERENCE_PATTERN = /^sessions\/[^/]+\/activities/;
49
+ /**
50
+ * Reduce any session reference a model may produce to the bare id the API
51
+ * paths use: `123`, `sessions/123`, or the `jules.google.com/session/123`
52
+ * URL all become `123`.
53
+ * @param reference - session id, resource name, or session URL.
54
+ * @returns the bare session id.
55
+ * @throws {TypeError} when the reference carries no id.
56
+ */
57
+ export function sessionIdOf(reference) {
58
+ const trimmed = reference.trim().replace(/\/+$/, '');
59
+ if (trimmed.length === 0)
60
+ throw new TypeError('a Jules session reference must not be empty');
61
+ // Checked before the session forms: \`sessions/1/activities/2\` would otherwise
62
+ // read as the session id \`1/activities/2\` and reach the API as a 404.
63
+ if (ACTIVITY_REFERENCE_PATTERN.test(trimmed)) {
64
+ throw new TypeError(`${JSON.stringify(reference)} names an activity, not a session`);
65
+ }
66
+ const fromUrl = /jules\.google\.com\/session\/([^/?#]+)/.exec(trimmed);
67
+ if (fromUrl?.[1] !== undefined)
68
+ return fromUrl[1];
69
+ const fromName = SESSION_ID_PATTERN.exec(trimmed);
70
+ if (fromName?.[1] !== undefined && fromName[1].length > 0)
71
+ return fromName[1];
72
+ return trimmed;
73
+ }
74
+ /**
75
+ * Accept the three spellings of a connected repository and produce the
76
+ * resource name the API expects: `owner/repo` and `github/owner/repo` both
77
+ * become `sources/github/owner/repo`.
78
+ * @param reference - `owner/repo`, `github/owner/repo`, or the full resource name.
79
+ * @returns the canonical source resource name.
80
+ * @throws {TypeError} when the reference is not a two-part repository id.
81
+ */
82
+ export function sourceNameOf(reference) {
83
+ const trimmed = reference.trim();
84
+ if (trimmed.startsWith('sources/')) {
85
+ const rest = trimmed.slice('sources/'.length);
86
+ return rest.startsWith('github/') ? trimmed : `sources/github/${rest}`;
87
+ }
88
+ const parts = trimmed.replace(/^github\//, '').split('/');
89
+ if (parts.length !== 2 || parts[0] === undefined || parts[1] === undefined
90
+ || parts[0].length === 0 || parts[1].length === 0) {
91
+ throw new TypeError(`${JSON.stringify(reference)} is not a repository reference; use "owner/repo" or "sources/github/owner/repo"`);
92
+ }
93
+ return `sources/github/${parts[0]}/${parts[1]}`;
94
+ }
95
+ /**
96
+ * Render a source resource name back as `owner/repo` for display, leaving an
97
+ * unrecognized name untouched.
98
+ * @param name - the source resource name.
99
+ * @returns the short repository reference.
100
+ */
101
+ export function shortSourceName(name) {
102
+ if (name === undefined)
103
+ return '';
104
+ const match = /^sources\/github\/(.+)$/.exec(name);
105
+ return match?.[1] ?? name;
106
+ }
107
+ /**
108
+ * Identify which event an activity carries.
109
+ * @param activity - one activity from the log.
110
+ * @returns the event member name, or `'unknown'` when none is present.
111
+ */
112
+ export function activityKind(activity) {
113
+ if (activity.planGenerated !== undefined)
114
+ return 'planGenerated';
115
+ if (activity.planApproved !== undefined)
116
+ return 'planApproved';
117
+ if (activity.userMessaged !== undefined)
118
+ return 'userMessaged';
119
+ if (activity.agentMessaged !== undefined)
120
+ return 'agentMessaged';
121
+ if (activity.progressUpdated !== undefined)
122
+ return 'progressUpdated';
123
+ if (activity.sessionCompleted !== undefined)
124
+ return 'sessionCompleted';
125
+ if (activity.sessionFailed !== undefined)
126
+ return 'sessionFailed';
127
+ return 'unknown';
128
+ }
package/lib/views.d.ts ADDED
@@ -0,0 +1,240 @@
1
+ /**
2
+ * Projections from Jules wire objects to the canonical values the tools return,
3
+ * and the text those values render to.
4
+ *
5
+ * Every projected object carries a value for each declared member: an absent
6
+ * optional scalar becomes `''` and an absent list becomes `[]`. The canonical
7
+ * value therefore stays lossless JSON with a fixed shape, and the renderer — not
8
+ * the model — decides what an empty value means.
9
+ *
10
+ * @module dsh-plugin-jules/views
11
+ */
12
+ import type { Activity, GitPatch, Session, Source } from './types.ts';
13
+ /** One row of `jules_sources`. */
14
+ export interface SourceView {
15
+ name: string;
16
+ owner: string;
17
+ repo: string;
18
+ isPrivate: boolean;
19
+ defaultBranch: string;
20
+ branches: string[];
21
+ }
22
+ /** One row of `jules_list`. */
23
+ export interface SessionRow {
24
+ id: string;
25
+ title: string;
26
+ state: string;
27
+ url: string;
28
+ source: string;
29
+ branch: string;
30
+ createTime: string;
31
+ updateTime: string;
32
+ pullRequests: string[];
33
+ }
34
+ /** The full picture `jules_status` returns. */
35
+ export interface SessionDetail {
36
+ id: string;
37
+ title: string;
38
+ state: string;
39
+ url: string;
40
+ prompt: string;
41
+ source: string;
42
+ branch: string;
43
+ /**
44
+ * Whether the plan gate was in force, as `yes`, `no`, or `unknown`.
45
+ *
46
+ * The service treats this as a create-time *input* and does not echo it back,
47
+ * so a plain boolean here reported `false` for sessions that clearly
48
+ * honoured it. `unknown` is the honest answer when neither the session nor the
49
+ * log carries evidence either way.
50
+ */
51
+ requirePlanApproval: string;
52
+ /** Whether a PR is opened automatically, on the same yes/no/unknown terms. */
53
+ autoCreatePr: string;
54
+ archived: boolean;
55
+ createTime: string;
56
+ updateTime: string;
57
+ planId: string;
58
+ planSteps: string[];
59
+ /** A generated plan with no approval after it, which is what blocks the session. */
60
+ planPending: boolean;
61
+ planApproved: boolean;
62
+ pullRequests: string[];
63
+ generatedFiles: GeneratedFileView[];
64
+ /** The most recent shell command the agent ran, for diagnosing a stall. */
65
+ latestCommand: string;
66
+ /**
67
+ * True only when the call performing the projection had just approved a plan
68
+ * and then observed its `planApproved` event. False on every other call,
69
+ * including one that approved but could not confirm in time — the service is
70
+ * eventually consistent about its own actions, so this is the answer to "did
71
+ * my approval land", which the state field cannot give.
72
+ */
73
+ approvalConfirmed: boolean;
74
+ /** False only when a log read was attempted and failed, making its derived fields unknown. */
75
+ logRead: boolean;
76
+ /** The most recent message the agent sent, which is where a question or a blocker appears. */
77
+ lastMessage: string;
78
+ lastProgress: string;
79
+ /**
80
+ * Milliseconds since this same observation was last returned, or 0 when it is
81
+ * new. Non-zero means the caller is looking at a session that has not moved,
82
+ * which the renderer turns into a nudge to stop polling.
83
+ */
84
+ unchangedForMs: number;
85
+ }
86
+ /** Per-call facts a projection cannot derive from the session and its log. */
87
+ export interface DetailOptions {
88
+ /** Milliseconds since this same observation was last returned; 0 when new. */
89
+ unchangedForMs?: number;
90
+ /** Whether this call approved a plan and then observed the approval. */
91
+ approvalConfirmed?: boolean;
92
+ /**
93
+ * Set false only when a read was attempted and failed. It then means every
94
+ * log-derived field below is *unknown*, not empty — a distinction that matters
95
+ * because "no plan pending" and "we could not look" call for opposite actions.
96
+ * Omitting it says nothing failed, which is also true of a projection that
97
+ * never looked.
98
+ */
99
+ logRead?: boolean;
100
+ }
101
+ /** One entry of `SessionDetail.generatedFiles`. */
102
+ export interface GeneratedFileView {
103
+ path: string;
104
+ changeType: string;
105
+ /** Length of the file content the service reported. */
106
+ bytes: number;
107
+ }
108
+ /** One row of `jules_activities`. */
109
+ export interface ActivityView {
110
+ id: string;
111
+ time: string;
112
+ originator: string;
113
+ kind: string;
114
+ summary: string;
115
+ artifacts: string[];
116
+ }
117
+ /** The canonical value of `jules_wait`. */
118
+ export interface WaitResult {
119
+ id: string;
120
+ title: string;
121
+ state: string;
122
+ url: string;
123
+ settled: boolean;
124
+ timedOut: boolean;
125
+ needsAttention: boolean;
126
+ waitedMs: number;
127
+ polls: number;
128
+ planId: string;
129
+ planSteps: string[];
130
+ pullRequests: string[];
131
+ lastMessage: string;
132
+ lastProgress: string;
133
+ }
134
+ /** The canonical value of `jules_patch`. */
135
+ export interface PatchResult {
136
+ id: string;
137
+ found: boolean;
138
+ /** The requested slice of the unified diff. */
139
+ patch: string;
140
+ /** Size of the whole diff, not of the slice. */
141
+ bytes: number;
142
+ /** Whether more of the diff remains after this slice. */
143
+ truncated: boolean;
144
+ /** Byte offset this slice started at. */
145
+ offset: number;
146
+ /** Offset to pass to continue reading, or 0 when this slice is the last. */
147
+ nextOffset: number;
148
+ /** Every path the whole diff touches, so a large patch is still reviewable at a glance. */
149
+ files: string[];
150
+ baseCommitId: string;
151
+ suggestedCommitMessage: string;
152
+ source: string;
153
+ }
154
+ /**
155
+ * Read the one human-readable line an activity carries.
156
+ * @param activity - one activity from the log.
157
+ * @returns the message, progress text, or failure reason.
158
+ */
159
+ export declare function activitySummary(activity: Activity): string;
160
+ /**
161
+ * Project one session into a list row.
162
+ * @param session - the wire session.
163
+ * @returns the row value.
164
+ */
165
+ export declare function sessionRow(session: Session): SessionRow;
166
+ /**
167
+ * Project one session into the full detail view.
168
+ * @param session - the wire session.
169
+ * @param activities - the session's event log, when it was read.
170
+ * @returns the detail value.
171
+ */
172
+ export declare function sessionDetail(session: Session, activities?: readonly Activity[], options?: DetailOptions): SessionDetail;
173
+ /**
174
+ * Project one source into a row.
175
+ * @param source - the wire source.
176
+ * @returns the row value.
177
+ */
178
+ export declare function sourceRow(source: Source): SourceView;
179
+ /**
180
+ * Project one activity into a row.
181
+ * @param activity - the wire activity.
182
+ * @returns the row value.
183
+ */
184
+ export declare function activityRow(activity: Activity): ActivityView;
185
+ /**
186
+ * Find the newest unified diff in an activity log.
187
+ *
188
+ * Activities are append-only, so later pages hold later patches; the last
189
+ * `changeSet` artifact is the state the session finished in.
190
+ * @param activities - the session's event log, oldest first.
191
+ * @returns the newest patch and its provenance, or undefined.
192
+ */
193
+ export declare function latestPatch(activities: readonly Activity[]): GitPatch | undefined;
194
+ /**
195
+ * List the paths a unified diff touches.
196
+ *
197
+ * The service's `generatedFiles` manifest was empty on every session observed,
198
+ * so the diff itself is the only reliable statement of what changed.
199
+ * @param unidiff - the complete unified diff.
200
+ * @returns one entry per touched path, in diff order.
201
+ */
202
+ export declare function patchFiles(unidiff: string): string[];
203
+ /**
204
+ * Render the source list.
205
+ * @param sources - projected rows.
206
+ * @returns the model-facing text.
207
+ */
208
+ export declare function renderSources(sources: readonly SourceView[]): string;
209
+ /**
210
+ * Render session rows as one line each.
211
+ * @param rows - projected rows.
212
+ * @param nextPageToken - continuation token, when the page was truncated.
213
+ * @returns the model-facing text.
214
+ */
215
+ export declare function renderSessionRows(rows: readonly SessionRow[], nextPageToken: string): string;
216
+ /**
217
+ * Render one session in full.
218
+ * @param detail - the projected detail.
219
+ * @returns the model-facing text.
220
+ */
221
+ export declare function renderSessionDetail(detail: SessionDetail): string;
222
+ /**
223
+ * Render an activity page.
224
+ * @param rows - projected rows.
225
+ * @param nextPageToken - continuation token, when the page was truncated.
226
+ * @returns the model-facing text.
227
+ */
228
+ export declare function renderActivities(rows: readonly ActivityView[], nextPageToken: string): string;
229
+ /**
230
+ * Render a wait result.
231
+ * @param result - the canonical wait value.
232
+ * @returns the model-facing text.
233
+ */
234
+ export declare function renderWait(result: WaitResult): string;
235
+ /**
236
+ * Render the newest patch, or explain why there is none.
237
+ * @param result - the canonical patch value.
238
+ * @returns the model-facing text.
239
+ */
240
+ export declare function renderPatch(result: PatchResult): string;