@p4code/cli 0.1.34 → 0.1.35
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/bin.mjs +566 -526
- package/dist/client/assets/{DiffPanel-lrTRyWQ8.js → DiffPanel-CEw1CywA.js} +2 -2
- package/dist/client/assets/{FilePreviewPanel-0DO5ToK5.js → FilePreviewPanel-CBURMvGc.js} +2 -2
- package/dist/client/assets/{PreviewPanel-WGzNU2y8.js → PreviewPanel-B1DkkA0M.js} +2 -2
- package/dist/client/assets/arrow-right-DTy7s06V.js +2 -0
- package/dist/client/assets/{fileCommentAnnotations-C5v2Y3k-.js → fileCommentAnnotations-df0mYZSy.js} +2 -2
- package/dist/client/assets/index-D5-EytSK.js +941 -0
- package/dist/client/assets/terminal-links-B1K-cj6g.js +46 -0
- package/dist/client/index.html +2 -2
- package/package.json +1 -1
- package/dist/client/assets/arrow-right-B97X3eK-.js +0 -2
- package/dist/client/assets/index-BzCS1mQx.js +0 -936
- package/dist/client/assets/terminal-links-DlQTO-Wn.js +0 -46
package/dist/bin.mjs
CHANGED
|
@@ -236,7 +236,7 @@ const make$76 = () => {
|
|
|
236
236
|
const layer$72 = Layer.sync(NetService, make$76);
|
|
237
237
|
//#endregion
|
|
238
238
|
//#region package.json
|
|
239
|
-
var version = "0.1.
|
|
239
|
+
var version = "0.1.35";
|
|
240
240
|
//#endregion
|
|
241
241
|
//#region src/config.ts
|
|
242
242
|
/**
|
|
@@ -5664,6 +5664,509 @@ var KeybindingsConfigError = class extends Schema$1.TaggedErrorClass()("Keybindi
|
|
|
5664
5664
|
}
|
|
5665
5665
|
};
|
|
5666
5666
|
//#endregion
|
|
5667
|
+
//#region ../../packages/contracts/src/task.ts
|
|
5668
|
+
const TASK_TITLE_MAX_LENGTH = 512;
|
|
5669
|
+
const TASK_LABEL_MAX_LENGTH = 64;
|
|
5670
|
+
const TASK_LABELS_MAX_COUNT = 32;
|
|
5671
|
+
const TASK_ASSIGNEE_MAX_LENGTH = 128;
|
|
5672
|
+
/**
|
|
5673
|
+
* Board columns, in board order. `backlog` is the default for newly created
|
|
5674
|
+
* tasks — an agent filing a task mid-session is recording work that exists, not
|
|
5675
|
+
* committing to doing it next.
|
|
5676
|
+
*
|
|
5677
|
+
* `cancelled` is the other way a task closes: decided against, superseded or
|
|
5678
|
+
* merged into another task. Marking such a task `done` is the workaround it
|
|
5679
|
+
* replaces, and it lies - a board that says five things finished when two of
|
|
5680
|
+
* them were dropped is not worth reading.
|
|
5681
|
+
*/
|
|
5682
|
+
const TaskStatus = Schema$1.Literals([
|
|
5683
|
+
"backlog",
|
|
5684
|
+
"todo",
|
|
5685
|
+
"in_progress",
|
|
5686
|
+
"in_review",
|
|
5687
|
+
"done",
|
|
5688
|
+
"cancelled"
|
|
5689
|
+
]);
|
|
5690
|
+
/**
|
|
5691
|
+
* What a status this build has never heard of decodes to when read back.
|
|
5692
|
+
*
|
|
5693
|
+
* `backlog` is the least dishonest guess: it says "recorded, nothing claimed
|
|
5694
|
+
* about it", which is true of a status we cannot interpret. Guessing `done` or
|
|
5695
|
+
* `cancelled` would claim the task is closed.
|
|
5696
|
+
*/
|
|
5697
|
+
const UNKNOWN_TASK_STATUS_FALLBACK = "backlog";
|
|
5698
|
+
const TaskPriority = Schema$1.Literals([
|
|
5699
|
+
"none",
|
|
5700
|
+
"low",
|
|
5701
|
+
"medium",
|
|
5702
|
+
"high",
|
|
5703
|
+
"urgent"
|
|
5704
|
+
]);
|
|
5705
|
+
/**
|
|
5706
|
+
* Which board a call is about.
|
|
5707
|
+
*
|
|
5708
|
+
* `board` is p4code's own: this machine's rows, mirrored to the hub when one is
|
|
5709
|
+
* linked. It is one board rather than two because the local store is a replica
|
|
5710
|
+
* of the hub and not a rival copy of it, which is why there is no `hub` member
|
|
5711
|
+
* here — asking for "the hub board" and "the local board" separately would be
|
|
5712
|
+
* asking for the same rows twice.
|
|
5713
|
+
*
|
|
5714
|
+
* `linear` is the user's Linear workspace, read and written through the Linear
|
|
5715
|
+
* MCP server they already registered. p4code holds no Linear credential of its
|
|
5716
|
+
* own; see `mcp/TicketResolver` for why that is the only way in.
|
|
5717
|
+
*
|
|
5718
|
+
* A per-call parameter rather than a setting: a person switches boards to look
|
|
5719
|
+
* at something and switches back, and a server-wide mode would make that a
|
|
5720
|
+
* configuration change with a blast radius across every open client.
|
|
5721
|
+
*/
|
|
5722
|
+
const TaskSource = Schema$1.Literals(["board", "linear"]);
|
|
5723
|
+
/**
|
|
5724
|
+
* The board a call means when it does not say. Every existing caller predates
|
|
5725
|
+
* the parameter and means p4code's own board, so the default has to be `board`
|
|
5726
|
+
* for them to keep working unchanged.
|
|
5727
|
+
*/
|
|
5728
|
+
const DEFAULT_TASK_SOURCE = "board";
|
|
5729
|
+
const TaskSourceField = TaskSource.pipe(Schema$1.annotateEncoded({ description: "Which board to act on: 'board' for p4code's own (the default), 'linear' for the linked Linear workspace." }));
|
|
5730
|
+
/**
|
|
5731
|
+
* Mixed into the RPC payloads rather than into `TaskCreateInput` and friends.
|
|
5732
|
+
*
|
|
5733
|
+
* Those inputs are also the hub's wire format, and a source does not belong
|
|
5734
|
+
* there because the hub *is* the board. Client RPCs and the task toolkit mix
|
|
5735
|
+
* this selector into their own request schemas explicitly.
|
|
5736
|
+
*/
|
|
5737
|
+
const TaskSourceSelector = Schema$1.Struct({ source: Schema$1.optional(TaskSourceField) });
|
|
5738
|
+
/**
|
|
5739
|
+
* A per-prefix sequence prefix, e.g. `T` or `P4`. Declared by a project in its
|
|
5740
|
+
* `p4.json` (`taskPrefix`); always stored and compared uppercase.
|
|
5741
|
+
*/
|
|
5742
|
+
const TASK_READABLE_ID_PREFIX_PATTERN = /^[A-Za-z][A-Za-z0-9]{0,9}$/;
|
|
5743
|
+
/** A validated task-id prefix such as `T`, `P4`, or `MOBILE`. */
|
|
5744
|
+
const TaskReadableIdPrefix = TrimmedNonEmptyString.check(Schema$1.isPattern(TASK_READABLE_ID_PREFIX_PATTERN));
|
|
5745
|
+
/**
|
|
5746
|
+
* The shape of a readable task id: prefix, dash, 1-based sequence number.
|
|
5747
|
+
* Deliberately disjoint from the UUID `taskId`, which is what lets a lookup
|
|
5748
|
+
* accept either and tell them apart by shape alone.
|
|
5749
|
+
*/
|
|
5750
|
+
const TASK_READABLE_ID_PATTERN = /^[A-Za-z][A-Za-z0-9]{0,9}-[1-9][0-9]*$/;
|
|
5751
|
+
/** Whether a task reference is a readable id (`P4-12`) rather than a UUID. */
|
|
5752
|
+
const isReadableTaskId = (reference) => TASK_READABLE_ID_PATTERN.test(reference);
|
|
5753
|
+
const TaskLabel = TrimmedNonEmptyString.check(Schema$1.isMaxLength(TASK_LABEL_MAX_LENGTH));
|
|
5754
|
+
/**
|
|
5755
|
+
* Agent-facing field descriptions. These ride on the *encoded* side because
|
|
5756
|
+
* every one of these schemas is a brand or a check over a transformation, and
|
|
5757
|
+
* the JSON Schema an agent actually reads is generated from the encoded schema
|
|
5758
|
+
* — a plain `.annotate` on the decoded side is silently dropped.
|
|
5759
|
+
*/
|
|
5760
|
+
const ProjectIdField = ProjectId.pipe(Schema$1.annotateEncoded({ description: "Id of the project this task belongs to." }));
|
|
5761
|
+
const ThreadIdField = ThreadId.pipe(Schema$1.annotateEncoded({ description: "Id of the agent thread working this task." }));
|
|
5762
|
+
const TitleField = TrimmedNonEmptyString.check(Schema$1.isMaxLength(TASK_TITLE_MAX_LENGTH)).pipe(Schema$1.annotateEncoded({ description: "One-line summary of the work. Required." }));
|
|
5763
|
+
const BodyField = Schema$1.String.pipe(Schema$1.annotateEncoded({ description: "Markdown detail: context, acceptance criteria, links." }));
|
|
5764
|
+
const StatusField = TaskStatus.pipe(Schema$1.annotateEncoded({ description: "Board column: backlog (recorded, not queued), todo (queued next), in_progress, in_review, done, cancelled (closed without doing the work)." }));
|
|
5765
|
+
/**
|
|
5766
|
+
* The status as read back from a store, tolerant of values this build predates.
|
|
5767
|
+
*
|
|
5768
|
+
* A board can be shared between machines running different versions, and a
|
|
5769
|
+
* newer peer may write a status added after this client shipped. A strict
|
|
5770
|
+
* literal would fail the decode of the whole page, so one unknown row would
|
|
5771
|
+
* blank the board behind a "hub is unavailable" error rather than degrade. An
|
|
5772
|
+
* unrecognized status therefore decodes to {@link UNKNOWN_TASK_STATUS_FALLBACK},
|
|
5773
|
+
* same spirit as the nullable `readableId` and `orderKey` below. Encoding stays
|
|
5774
|
+
* strict: this client can only ever write a status it knows.
|
|
5775
|
+
*/
|
|
5776
|
+
const StoredStatusField = TaskStatus.pipe(Schema$1.catchDecoding(() => Effect.succeedSome(UNKNOWN_TASK_STATUS_FALLBACK)));
|
|
5777
|
+
const PriorityField = TaskPriority.pipe(Schema$1.annotateEncoded({ description: "Relative urgency: none, low, medium, high or urgent." }));
|
|
5778
|
+
const AssigneeField = TrimmedNonEmptyString.check(Schema$1.isMaxLength(TASK_ASSIGNEE_MAX_LENGTH)).pipe(Schema$1.annotateEncoded({ description: "Who owns the task — a person's handle or an agent name." }));
|
|
5779
|
+
/**
|
|
5780
|
+
* The portable half of "which repository is this task about".
|
|
5781
|
+
*
|
|
5782
|
+
* `projectId` cannot answer that across machines: it is a UUID minted when a
|
|
5783
|
+
* project is added, and the same checkout added on two servers gets two
|
|
5784
|
+
* different ids. A canonical repository key — the normalized primary git remote
|
|
5785
|
+
* — is the same string wherever the repo is cloned, so it survives the trip
|
|
5786
|
+
* through a shared board.
|
|
5787
|
+
*
|
|
5788
|
+
* Null for a task that is not about a repository, and also for a checkout with
|
|
5789
|
+
* no remote, where there is nothing stable to key on.
|
|
5790
|
+
*/
|
|
5791
|
+
const RepositoryKeyField = TrimmedNonEmptyString.pipe(Schema$1.annotateEncoded({ description: "Canonical repository key (normalized git remote, e.g. github.com/owner/repo). Portable across machines, unlike projectId." }));
|
|
5792
|
+
/**
|
|
5793
|
+
* Manual board position within a column, as a lexicographic fractional key.
|
|
5794
|
+
*
|
|
5795
|
+
* Compared as a plain string: smaller sorts higher. Keys are minted by the
|
|
5796
|
+
* board between the neighbours of a drop, so moving one card writes one row
|
|
5797
|
+
* rather than renumbering the column. `null` means the task has never been
|
|
5798
|
+
* placed by hand and falls back to the priority/recency order.
|
|
5799
|
+
*/
|
|
5800
|
+
const OrderKeyField = TrimmedNonEmptyString.pipe(Schema$1.annotateEncoded({ description: "Manual board position key. Lexicographic: smaller sorts higher in the column. Null means no manual position." }));
|
|
5801
|
+
/**
|
|
5802
|
+
* Which tracker issue a row *is*, for a row read out of a tracker.
|
|
5803
|
+
*
|
|
5804
|
+
* The identifier is the tracker's own, not ours: `MOBILE-12262` reads the same
|
|
5805
|
+
* in p4code as it does in Linear, in a commit message, and in conversation,
|
|
5806
|
+
* which is the whole point of carrying it. `url` is what the tracker itself
|
|
5807
|
+
* reported, never a guess assembled from the identifier - a Linear URL embeds
|
|
5808
|
+
* the workspace slug, so a constructed one would 404.
|
|
5809
|
+
*
|
|
5810
|
+
* Read-only, and only ever set by the `linear` source. p4code's own board does
|
|
5811
|
+
* not copy tracker issues into rows of its own: a ticket is read where it lives
|
|
5812
|
+
* and shown under the id its tracker gave it, which is why nothing here appears
|
|
5813
|
+
* in a create or update input.
|
|
5814
|
+
*/
|
|
5815
|
+
const TaskExternalRef = Schema$1.Struct({
|
|
5816
|
+
source: Schema$1.Literals(["linear"]),
|
|
5817
|
+
identifier: TrimmedNonEmptyString,
|
|
5818
|
+
url: TrimmedNonEmptyString
|
|
5819
|
+
});
|
|
5820
|
+
/**
|
|
5821
|
+
* The task this one was split out of.
|
|
5822
|
+
*
|
|
5823
|
+
* Carried as a field rather than as a sentence in the body because the plan
|
|
5824
|
+
* phase writes it and the board reads it: a parent chip, a subtask list and
|
|
5825
|
+
* "does this still have unfinished children" are all questions about the graph,
|
|
5826
|
+
* and prose cannot be asked them. Stored as the parent's UUID, but accepted as
|
|
5827
|
+
* either a UUID or a readable id on the way in - a planning agent works in
|
|
5828
|
+
* `P4-12`, and making it look up a UUID first is a round trip for nothing.
|
|
5829
|
+
*
|
|
5830
|
+
* One level is all this promises. Nothing forbids a subtask of a subtask, and
|
|
5831
|
+
* nothing walks the chain either; the board renders the direct children of a
|
|
5832
|
+
* task and stops.
|
|
5833
|
+
*/
|
|
5834
|
+
const ParentTaskIdField = TaskId.pipe(Schema$1.annotateEncoded({ description: "The task this one is a subtask of - the parent's UUID or readable id (e.g. P4-12). Null for a task that is not part of a larger one." }));
|
|
5835
|
+
const LabelsField = Schema$1.Array(TaskLabel).check(Schema$1.isMaxLength(TASK_LABELS_MAX_COUNT)).pipe(Schema$1.annotateEncoded({ description: "Free-form tags used to group and filter tasks." }));
|
|
5836
|
+
const TaskIdField = TaskId.pipe(Schema$1.annotateEncoded({ description: "Id of the task to act on - the UUID or the readable id (e.g. P4-12)." }));
|
|
5837
|
+
const Task = Schema$1.Struct({
|
|
5838
|
+
taskId: TaskId,
|
|
5839
|
+
/**
|
|
5840
|
+
* The human-readable id, e.g. `T-1` or `P4-12`: a per-prefix sequence
|
|
5841
|
+
* allocated by the store that owns the row, never reused, with gaps where
|
|
5842
|
+
* tasks were deleted. Nullable only for version skew - a row read from a
|
|
5843
|
+
* store that has not run the backfill migration decodes to `null` rather
|
|
5844
|
+
* than failing the whole board.
|
|
5845
|
+
*/
|
|
5846
|
+
readableId: Schema$1.NullOr(TrimmedNonEmptyString).pipe(Schema$1.withDecodingDefault(Effect.succeed(null))),
|
|
5847
|
+
/**
|
|
5848
|
+
* Manual board position: a lexicographic fractional key, compared as a plain
|
|
5849
|
+
* string, smaller first. Written only when a card is dragged to a position;
|
|
5850
|
+
* `null` means "never placed by hand" and the board falls back to its
|
|
5851
|
+
* priority/recency order. Nullable-with-default for the same version-skew
|
|
5852
|
+
* reason as `readableId`: a row from a store without the migration decodes to
|
|
5853
|
+
* `null` rather than failing the whole board.
|
|
5854
|
+
*/
|
|
5855
|
+
orderKey: Schema$1.NullOr(TrimmedNonEmptyString).pipe(Schema$1.withDecodingDefault(Effect.succeed(null))),
|
|
5856
|
+
/**
|
|
5857
|
+
* The project this task belongs to, or `null` for a task that is not scoped
|
|
5858
|
+
* to one. This references the existing workspace project registry rather than
|
|
5859
|
+
* a task-board-private notion of a project.
|
|
5860
|
+
*/
|
|
5861
|
+
projectId: Schema$1.NullOr(ProjectId),
|
|
5862
|
+
/**
|
|
5863
|
+
* Canonical repository key, resolved server-side from `projectId` when the
|
|
5864
|
+
* task is written. This is the field that survives a move to another machine;
|
|
5865
|
+
* `projectId` above is a local pointer and is meaningless on a server that
|
|
5866
|
+
* did not mint it. A board reading a shared task matches on this and looks up
|
|
5867
|
+
* whatever local project shares the key.
|
|
5868
|
+
*/
|
|
5869
|
+
repositoryKey: Schema$1.NullOr(TrimmedNonEmptyString),
|
|
5870
|
+
/**
|
|
5871
|
+
* The thread currently working this task, or `null`. A task links to at most
|
|
5872
|
+
* one thread at a time; re-running a task repoints this rather than appending
|
|
5873
|
+
* to a history.
|
|
5874
|
+
*/
|
|
5875
|
+
threadId: Schema$1.NullOr(ThreadId),
|
|
5876
|
+
/**
|
|
5877
|
+
* The tracker issue this row is, for a row read from a tracker, and `null`
|
|
5878
|
+
* for every row on p4code's own board. Nullable-with-default because the
|
|
5879
|
+
* board's own store has no such column at all - a row read from SQLite
|
|
5880
|
+
* decodes to `null` rather than failing the whole board.
|
|
5881
|
+
*/
|
|
5882
|
+
externalRef: Schema$1.NullOr(TaskExternalRef).pipe(Schema$1.withDecodingDefault(Effect.succeed(null))),
|
|
5883
|
+
/**
|
|
5884
|
+
* The task this one was split out of, always as a UUID once stored - a
|
|
5885
|
+
* readable id given on the way in is resolved before the row is written, so
|
|
5886
|
+
* the link survives a board where readable ids were allocated elsewhere.
|
|
5887
|
+
* Nullable-with-default for the same version-skew reason as `readableId`.
|
|
5888
|
+
*/
|
|
5889
|
+
parentTaskId: Schema$1.NullOr(TaskId).pipe(Schema$1.withDecodingDefault(Effect.succeed(null))),
|
|
5890
|
+
title: TrimmedNonEmptyString.check(Schema$1.isMaxLength(TASK_TITLE_MAX_LENGTH)),
|
|
5891
|
+
body: Schema$1.String,
|
|
5892
|
+
status: StoredStatusField,
|
|
5893
|
+
priority: TaskPriority,
|
|
5894
|
+
assignee: Schema$1.NullOr(TrimmedNonEmptyString.check(Schema$1.isMaxLength(TASK_ASSIGNEE_MAX_LENGTH))),
|
|
5895
|
+
labels: Schema$1.Array(TaskLabel).check(Schema$1.isMaxLength(TASK_LABELS_MAX_COUNT)),
|
|
5896
|
+
createdAt: IsoDateTime,
|
|
5897
|
+
updatedAt: IsoDateTime
|
|
5898
|
+
});
|
|
5899
|
+
const TaskCreateInput = Schema$1.Struct({
|
|
5900
|
+
projectId: Schema$1.optional(ProjectIdField),
|
|
5901
|
+
repositoryKey: Schema$1.optional(RepositoryKeyField),
|
|
5902
|
+
threadId: Schema$1.optional(ThreadIdField),
|
|
5903
|
+
parentTaskId: Schema$1.optional(ParentTaskIdField),
|
|
5904
|
+
title: TitleField,
|
|
5905
|
+
body: Schema$1.optional(BodyField),
|
|
5906
|
+
status: Schema$1.optional(StatusField),
|
|
5907
|
+
priority: Schema$1.optional(PriorityField),
|
|
5908
|
+
assignee: Schema$1.optional(AssigneeField),
|
|
5909
|
+
labels: Schema$1.optional(LabelsField)
|
|
5910
|
+
});
|
|
5911
|
+
/**
|
|
5912
|
+
* What an environment server sends the hub to file a task.
|
|
5913
|
+
*
|
|
5914
|
+
* Extends the public create input with the readable-id prefix, because the two
|
|
5915
|
+
* halves of the answer live on different machines: the prefix comes from the
|
|
5916
|
+
* project's checked-in `p4.json`, which only the submitting server can read,
|
|
5917
|
+
* while the sequence number must be allocated by the hub, which is the only
|
|
5918
|
+
* party that sees every machine's writes. Deliberately not part of
|
|
5919
|
+
* `TaskCreateInput` itself so an agent filing over MCP cannot pick a prefix -
|
|
5920
|
+
* the prefix belongs to the project, not to the caller.
|
|
5921
|
+
*/
|
|
5922
|
+
const HubTaskCreateInput = Schema$1.Struct({
|
|
5923
|
+
...TaskCreateInput.fields,
|
|
5924
|
+
readableIdPrefix: Schema$1.optional(TrimmedNonEmptyString),
|
|
5925
|
+
/**
|
|
5926
|
+
* Carried on the hub's create route (but not the public one) so a server
|
|
5927
|
+
* mirroring an already-placed row to the hub does not lose its position.
|
|
5928
|
+
* An agent filing a new task has no business picking a slot.
|
|
5929
|
+
*/
|
|
5930
|
+
orderKey: Schema$1.optional(OrderKeyField)
|
|
5931
|
+
});
|
|
5932
|
+
/**
|
|
5933
|
+
* A whole row, written to the hub at an id the sending machine chose.
|
|
5934
|
+
*
|
|
5935
|
+
* Every field is present and nullable rather than optional, because this is a
|
|
5936
|
+
* replace and not a patch: a task whose assignee was cleared locally has to
|
|
5937
|
+
* arrive as an explicit `null`, and under an optional field it would arrive as
|
|
5938
|
+
* an absence indistinguishable from "unchanged" — so the clear would never
|
|
5939
|
+
* propagate and the two machines would disagree forever about who owns it.
|
|
5940
|
+
*
|
|
5941
|
+
* `readableId` and `updatedAt` are absent for the opposite reason: they are
|
|
5942
|
+
* the hub's to assign. The hub allocates the readable id the first time it
|
|
5943
|
+
* sees an id and preserves it afterwards, which is what lets a task created
|
|
5944
|
+
* on an offline machine get its `P4-12` on the first push and keep it.
|
|
5945
|
+
*
|
|
5946
|
+
* `createdAt` does come from the sender. It is a fact about when the person
|
|
5947
|
+
* filed the task, and the hub learning about it late does not make it newer.
|
|
5948
|
+
*/
|
|
5949
|
+
const HubTaskPutInput = Schema$1.Struct({
|
|
5950
|
+
projectId: Schema$1.NullOr(ProjectIdField),
|
|
5951
|
+
repositoryKey: Schema$1.NullOr(RepositoryKeyField),
|
|
5952
|
+
threadId: Schema$1.NullOr(ThreadIdField),
|
|
5953
|
+
parentTaskId: Schema$1.NullOr(ParentTaskIdField),
|
|
5954
|
+
title: TitleField,
|
|
5955
|
+
body: BodyField,
|
|
5956
|
+
status: StatusField,
|
|
5957
|
+
priority: PriorityField,
|
|
5958
|
+
assignee: Schema$1.NullOr(AssigneeField),
|
|
5959
|
+
labels: LabelsField,
|
|
5960
|
+
orderKey: Schema$1.NullOr(OrderKeyField),
|
|
5961
|
+
createdAt: IsoDateTime,
|
|
5962
|
+
readableIdPrefix: Schema$1.optional(TrimmedNonEmptyString)
|
|
5963
|
+
});
|
|
5964
|
+
/**
|
|
5965
|
+
* Every field is optional and an omitted field is left untouched. The nullable
|
|
5966
|
+
* fields accept an explicit `null` to clear them, which is why they are
|
|
5967
|
+
* `NullOr` inside `optional` rather than merely optional.
|
|
5968
|
+
*/
|
|
5969
|
+
const TaskUpdateInput = Schema$1.Struct({
|
|
5970
|
+
taskId: TaskIdField,
|
|
5971
|
+
projectId: Schema$1.optional(Schema$1.NullOr(ProjectIdField)),
|
|
5972
|
+
repositoryKey: Schema$1.optional(Schema$1.NullOr(RepositoryKeyField)),
|
|
5973
|
+
threadId: Schema$1.optional(Schema$1.NullOr(ThreadIdField)),
|
|
5974
|
+
parentTaskId: Schema$1.optional(Schema$1.NullOr(ParentTaskIdField)),
|
|
5975
|
+
title: Schema$1.optional(TitleField),
|
|
5976
|
+
body: Schema$1.optional(BodyField),
|
|
5977
|
+
status: Schema$1.optional(StatusField),
|
|
5978
|
+
priority: Schema$1.optional(PriorityField),
|
|
5979
|
+
assignee: Schema$1.optional(Schema$1.NullOr(AssigneeField)),
|
|
5980
|
+
labels: Schema$1.optional(LabelsField),
|
|
5981
|
+
orderKey: Schema$1.optional(Schema$1.NullOr(OrderKeyField))
|
|
5982
|
+
});
|
|
5983
|
+
/**
|
|
5984
|
+
* A task an agent has drafted but has not filed.
|
|
5985
|
+
*
|
|
5986
|
+
* Mirrors `TaskCreateInput` minus the fields the proposer has no business
|
|
5987
|
+
* setting. `status` is absent on purpose: a confirmed proposal always lands in
|
|
5988
|
+
* `backlog`, so an interviewing agent cannot queue its own work — the property
|
|
5989
|
+
* the board's pick-up relies on. `taskId`, `threadId` and `repositoryKey` are
|
|
5990
|
+
* absent because they are minted or derived when the proposal is accepted, not
|
|
5991
|
+
* offered by the model.
|
|
5992
|
+
*/
|
|
5993
|
+
const TaskProposeInput = Schema$1.Struct({
|
|
5994
|
+
projectId: Schema$1.optional(ProjectIdField),
|
|
5995
|
+
title: TitleField,
|
|
5996
|
+
body: Schema$1.optional(BodyField),
|
|
5997
|
+
priority: Schema$1.optional(PriorityField),
|
|
5998
|
+
assignee: Schema$1.optional(AssigneeField),
|
|
5999
|
+
labels: Schema$1.optional(LabelsField)
|
|
6000
|
+
});
|
|
6001
|
+
/**
|
|
6002
|
+
* What `task_propose` returns. The tool writes nothing; its only effect is the
|
|
6003
|
+
* card the client renders from the call itself, so the result exists to tell
|
|
6004
|
+
* the model that nothing has been filed yet and it should stop and wait.
|
|
6005
|
+
*/
|
|
6006
|
+
const TaskProposeResult = Schema$1.Struct({ awaitingConfirmation: Schema$1.Literal(true) });
|
|
6007
|
+
/**
|
|
6008
|
+
* Parameters for `task_current`. The thread defaults to the caller's own, which
|
|
6009
|
+
* is the point of the tool — an agent does not know its own thread id. It is
|
|
6010
|
+
* still a real parameter rather than an empty struct because an empty struct
|
|
6011
|
+
* generates a root `anyOf` JSON Schema that some providers reject outright.
|
|
6012
|
+
*/
|
|
6013
|
+
const TaskCurrentInput = Schema$1.Struct({ threadId: Schema$1.optional(ThreadId.pipe(Schema$1.annotateEncoded({ description: "Thread whose linked task to read. Defaults to this agent session's own thread." }))) });
|
|
6014
|
+
/** Parameters for reading or targeting a single task by id. */
|
|
6015
|
+
const TaskGetInput = Schema$1.Struct({ taskId: TaskIdField });
|
|
6016
|
+
const TaskListFilter = Schema$1.Struct({
|
|
6017
|
+
projectId: Schema$1.optional(ProjectIdField),
|
|
6018
|
+
repositoryKey: Schema$1.optional(RepositoryKeyField),
|
|
6019
|
+
threadId: Schema$1.optional(ThreadIdField),
|
|
6020
|
+
status: Schema$1.optional(StatusField),
|
|
6021
|
+
assignee: Schema$1.optional(AssigneeField)
|
|
6022
|
+
});
|
|
6023
|
+
Schema$1.Union([Schema$1.Struct({
|
|
6024
|
+
type: Schema$1.Literal("upserted"),
|
|
6025
|
+
task: Task
|
|
6026
|
+
}), Schema$1.Struct({
|
|
6027
|
+
type: Schema$1.Literal("deleted"),
|
|
6028
|
+
taskId: TaskId
|
|
6029
|
+
})]);
|
|
6030
|
+
/**
|
|
6031
|
+
* What a board subscription carries: the current board, then every change.
|
|
6032
|
+
*
|
|
6033
|
+
* The snapshot rides on the stream rather than being fetched separately
|
|
6034
|
+
* because a list call followed by a subscribe has a gap — anything committed
|
|
6035
|
+
* between the two is lost, and the board then shows stale rows until something
|
|
6036
|
+
* else happens to touch them. The server takes the snapshot *after* it has
|
|
6037
|
+
* subscribed, so the worst case is an event the subscriber already has, which
|
|
6038
|
+
* is idempotent, rather than one it never sees.
|
|
6039
|
+
*/
|
|
6040
|
+
const TaskStreamEvent = Schema$1.Union([
|
|
6041
|
+
Schema$1.Struct({
|
|
6042
|
+
type: Schema$1.Literal("snapshot"),
|
|
6043
|
+
tasks: Schema$1.Array(Task)
|
|
6044
|
+
}),
|
|
6045
|
+
Schema$1.Struct({
|
|
6046
|
+
type: Schema$1.Literal("upserted"),
|
|
6047
|
+
task: Task
|
|
6048
|
+
}),
|
|
6049
|
+
Schema$1.Struct({
|
|
6050
|
+
type: Schema$1.Literal("deleted"),
|
|
6051
|
+
taskId: TaskId
|
|
6052
|
+
})
|
|
6053
|
+
]);
|
|
6054
|
+
Schema$1.Struct({
|
|
6055
|
+
patch: TaskUpdateInput,
|
|
6056
|
+
/** The status the caller believes the task is in. */
|
|
6057
|
+
expectedStatus: TaskStatus
|
|
6058
|
+
});
|
|
6059
|
+
Schema$1.Union([
|
|
6060
|
+
Schema$1.Struct({
|
|
6061
|
+
outcome: Schema$1.Literal("updated"),
|
|
6062
|
+
task: Task
|
|
6063
|
+
}),
|
|
6064
|
+
Schema$1.Struct({ outcome: Schema$1.Literal("missing") }),
|
|
6065
|
+
Schema$1.Struct({
|
|
6066
|
+
outcome: Schema$1.Literal("conflict"),
|
|
6067
|
+
current: Task
|
|
6068
|
+
})
|
|
6069
|
+
]);
|
|
6070
|
+
/**
|
|
6071
|
+
* Start an agent on a task.
|
|
6072
|
+
*
|
|
6073
|
+
* The input is only the task, deliberately. Everything else the thread needs —
|
|
6074
|
+
* which project, which model, what prompt — is resolved server-side from the
|
|
6075
|
+
* task's own project, because the caller that matters next is not a person
|
|
6076
|
+
* clicking a button but Phase 12's orchestrator, and a client that supplies the
|
|
6077
|
+
* model is a client that can start a task on a provider the user does not use.
|
|
6078
|
+
*
|
|
6079
|
+
* Worktrees are absent for a harder reason: `prepareWorktree` needs a base
|
|
6080
|
+
* branch, a task carries none, and nothing resolves one server-side. A thread
|
|
6081
|
+
* started from a task therefore runs in the project's own checkout.
|
|
6082
|
+
*/
|
|
6083
|
+
const TaskStartThreadInput = Schema$1.Struct({ taskId: TaskIdField });
|
|
6084
|
+
/**
|
|
6085
|
+
* The started thread and the task as it now stands.
|
|
6086
|
+
*
|
|
6087
|
+
* Both are returned because both changed: the caller navigates to the thread,
|
|
6088
|
+
* and the board would otherwise show the old status until the change event
|
|
6089
|
+
* arrived.
|
|
6090
|
+
*/
|
|
6091
|
+
const TaskStartThreadResult = Schema$1.Struct({
|
|
6092
|
+
task: Task,
|
|
6093
|
+
threadId: ThreadId
|
|
6094
|
+
});
|
|
6095
|
+
/**
|
|
6096
|
+
* A task cannot be started, and the reason is a property of the task or the
|
|
6097
|
+
* server rather than a failure.
|
|
6098
|
+
*
|
|
6099
|
+
* Each reason is a different thing for the user to fix, which is why this is a
|
|
6100
|
+
* union and not one message. `no-model` is the one worth explaining: a task
|
|
6101
|
+
* carries no model selection, so the thread runs on the project's default. The
|
|
6102
|
+
* server-side fallback that exists for first-run bootstrap is deliberately not
|
|
6103
|
+
* used here — it hardcodes a provider, so reaching for it would silently start
|
|
6104
|
+
* someone who works in Claude on Codex. Failing is the honest answer.
|
|
6105
|
+
*/
|
|
6106
|
+
const TaskNotStartableReason = Schema$1.Literals([
|
|
6107
|
+
"no-project",
|
|
6108
|
+
"project-missing",
|
|
6109
|
+
"no-model"
|
|
6110
|
+
]);
|
|
6111
|
+
var TaskNotStartableError = class extends Schema$1.TaggedErrorClass()("TaskNotStartableError", {
|
|
6112
|
+
taskId: TaskId,
|
|
6113
|
+
reason: TaskNotStartableReason
|
|
6114
|
+
}) {
|
|
6115
|
+
get message() {
|
|
6116
|
+
switch (this.reason) {
|
|
6117
|
+
case "no-project": return "This task has no project, so there is nowhere to run it. Set a project on the task first.";
|
|
6118
|
+
case "project-missing": return "This task's project is not registered on this server. Point it at a project that is.";
|
|
6119
|
+
case "no-model": return "This task's project has no default model. Choose one in the project's settings, then start the task again.";
|
|
6120
|
+
}
|
|
6121
|
+
}
|
|
6122
|
+
};
|
|
6123
|
+
/** Result shape for the board's list RPC. */
|
|
6124
|
+
const TaskListResult$1 = Schema$1.Struct({ tasks: Schema$1.Array(Task) });
|
|
6125
|
+
/** Result shape for every RPC that resolves to a single task. */
|
|
6126
|
+
const TaskResult = Schema$1.Struct({ task: Task });
|
|
6127
|
+
/** `get` distinguishes "no such task" from an error, so the task is nullable. */
|
|
6128
|
+
const TaskGetResult = Schema$1.Struct({ task: Schema$1.NullOr(Task) });
|
|
6129
|
+
var TaskToolUnavailableError = class extends Schema$1.TaggedErrorClass()("TaskToolUnavailableError", {
|
|
6130
|
+
capability: Schema$1.Literal("tasks"),
|
|
6131
|
+
environmentId: EnvironmentId,
|
|
6132
|
+
threadId: ThreadId,
|
|
6133
|
+
providerSessionId: TrimmedNonEmptyString,
|
|
6134
|
+
providerInstanceId: ProviderInstanceId
|
|
6135
|
+
}) {
|
|
6136
|
+
get message() {
|
|
6137
|
+
return `MCP credential does not grant the ${this.capability} capability.`;
|
|
6138
|
+
}
|
|
6139
|
+
};
|
|
6140
|
+
var TaskNotFoundError = class extends Schema$1.TaggedErrorClass()("TaskNotFoundError", { taskId: TaskId }) {
|
|
6141
|
+
get message() {
|
|
6142
|
+
return `No task exists with id ${this.taskId}.`;
|
|
6143
|
+
}
|
|
6144
|
+
};
|
|
6145
|
+
/**
|
|
6146
|
+
* A task tool hit the store and the store failed. The underlying persistence
|
|
6147
|
+
* error is deliberately not surfaced to the agent — it carries SQL detail that
|
|
6148
|
+
* is noise in a tool result — but it is logged server-side.
|
|
6149
|
+
*/
|
|
6150
|
+
var TaskStoreError = class extends Schema$1.TaggedErrorClass()("TaskStoreError", { operation: TrimmedNonEmptyString }) {
|
|
6151
|
+
get message() {
|
|
6152
|
+
return `The task store failed during ${this.operation}.`;
|
|
6153
|
+
}
|
|
6154
|
+
};
|
|
6155
|
+
/**
|
|
6156
|
+
* No task is linked to the calling agent's thread.
|
|
6157
|
+
*/
|
|
6158
|
+
var TaskNotLinkedError = class extends Schema$1.TaggedErrorClass()("TaskNotLinkedError", { threadId: ThreadId }) {
|
|
6159
|
+
get message() {
|
|
6160
|
+
return `No task is linked to thread ${this.threadId}.`;
|
|
6161
|
+
}
|
|
6162
|
+
};
|
|
6163
|
+
const TaskToolError = Schema$1.Union([
|
|
6164
|
+
TaskToolUnavailableError,
|
|
6165
|
+
TaskNotFoundError,
|
|
6166
|
+
TaskNotLinkedError,
|
|
6167
|
+
TaskStoreError
|
|
6168
|
+
]);
|
|
6169
|
+
//#endregion
|
|
5667
6170
|
//#region ../../packages/contracts/src/settings.ts
|
|
5668
6171
|
const TimestampFormat = Schema$1.Literals([
|
|
5669
6172
|
"locale",
|
|
@@ -5978,6 +6481,12 @@ const ServerSettings = Schema$1.Struct({
|
|
|
5978
6481
|
}))),
|
|
5979
6482
|
sourceControlWritingStyle: SourceControlWritingStyleSettings.pipe(Schema$1.withDecodingDefault(Effect.succeed({}))),
|
|
5980
6483
|
sourceControlWriterModelSelection: Schema$1.NullOr(ModelSelection).pipe(Schema$1.withDecodingDefault(Effect.succeed(null))),
|
|
6484
|
+
/** Prefix used for tasks without a project or project-specific override. */
|
|
6485
|
+
defaultTaskPrefix: TaskReadableIdPrefix.pipe(Schema$1.withDecodingDefault(Effect.succeed("T"))),
|
|
6486
|
+
/** Linear issue identifier prefixes. Empty leaves Linear references unclaimed. */
|
|
6487
|
+
linearTaskPrefixes: Schema$1.Array(TaskReadableIdPrefix).pipe(Schema$1.withDecodingDefault(Effect.succeed([]))),
|
|
6488
|
+
/** Local task-id prefix overrides keyed by this environment's project id. */
|
|
6489
|
+
projectTaskPrefixes: Schema$1.Record(ProjectId, TaskReadableIdPrefix).pipe(Schema$1.withDecodingDefault(Effect.succeed({}))),
|
|
5981
6490
|
/**
|
|
5982
6491
|
* The Linear team new issues are filed into, by name or id.
|
|
5983
6492
|
*
|
|
@@ -6113,6 +6622,9 @@ const ServerSettingsPatch = Schema$1.Struct({
|
|
|
6113
6622
|
followChangeRequestTemplates: Schema$1.optionalKey(Schema$1.Boolean)
|
|
6114
6623
|
})),
|
|
6115
6624
|
sourceControlWriterModelSelection: Schema$1.optionalKey(Schema$1.NullOr(ModelSelection)),
|
|
6625
|
+
defaultTaskPrefix: Schema$1.optionalKey(TaskReadableIdPrefix),
|
|
6626
|
+
linearTaskPrefixes: Schema$1.optionalKey(Schema$1.Array(TaskReadableIdPrefix)),
|
|
6627
|
+
projectTaskPrefixes: Schema$1.optionalKey(Schema$1.Record(ProjectId, TaskReadableIdPrefix)),
|
|
6116
6628
|
disabledSkills: Schema$1.optionalKey(Schema$1.Array(TrimmedNonEmptyString)),
|
|
6117
6629
|
linearTeam: Schema$1.optionalKey(TrimmedString),
|
|
6118
6630
|
observability: Schema$1.optionalKey(Schema$1.Struct({
|
|
@@ -7263,509 +7775,6 @@ const GitActionProgressEvent = Schema$1.Union([
|
|
|
7263
7775
|
GitActionFailedEvent
|
|
7264
7776
|
]);
|
|
7265
7777
|
//#endregion
|
|
7266
|
-
//#region ../../packages/contracts/src/task.ts
|
|
7267
|
-
const TASK_TITLE_MAX_LENGTH = 512;
|
|
7268
|
-
const TASK_LABEL_MAX_LENGTH = 64;
|
|
7269
|
-
const TASK_LABELS_MAX_COUNT = 32;
|
|
7270
|
-
const TASK_ASSIGNEE_MAX_LENGTH = 128;
|
|
7271
|
-
/**
|
|
7272
|
-
* Board columns, in board order. `backlog` is the default for newly created
|
|
7273
|
-
* tasks — an agent filing a task mid-session is recording work that exists, not
|
|
7274
|
-
* committing to doing it next.
|
|
7275
|
-
*
|
|
7276
|
-
* `cancelled` is the other way a task closes: decided against, superseded or
|
|
7277
|
-
* merged into another task. Marking such a task `done` is the workaround it
|
|
7278
|
-
* replaces, and it lies - a board that says five things finished when two of
|
|
7279
|
-
* them were dropped is not worth reading.
|
|
7280
|
-
*/
|
|
7281
|
-
const TaskStatus = Schema$1.Literals([
|
|
7282
|
-
"backlog",
|
|
7283
|
-
"todo",
|
|
7284
|
-
"in_progress",
|
|
7285
|
-
"in_review",
|
|
7286
|
-
"done",
|
|
7287
|
-
"cancelled"
|
|
7288
|
-
]);
|
|
7289
|
-
/**
|
|
7290
|
-
* What a status this build has never heard of decodes to when read back.
|
|
7291
|
-
*
|
|
7292
|
-
* `backlog` is the least dishonest guess: it says "recorded, nothing claimed
|
|
7293
|
-
* about it", which is true of a status we cannot interpret. Guessing `done` or
|
|
7294
|
-
* `cancelled` would claim the task is closed.
|
|
7295
|
-
*/
|
|
7296
|
-
const UNKNOWN_TASK_STATUS_FALLBACK = "backlog";
|
|
7297
|
-
const TaskPriority = Schema$1.Literals([
|
|
7298
|
-
"none",
|
|
7299
|
-
"low",
|
|
7300
|
-
"medium",
|
|
7301
|
-
"high",
|
|
7302
|
-
"urgent"
|
|
7303
|
-
]);
|
|
7304
|
-
/**
|
|
7305
|
-
* Which board a call is about.
|
|
7306
|
-
*
|
|
7307
|
-
* `board` is p4code's own: this machine's rows, mirrored to the hub when one is
|
|
7308
|
-
* linked. It is one board rather than two because the local store is a replica
|
|
7309
|
-
* of the hub and not a rival copy of it, which is why there is no `hub` member
|
|
7310
|
-
* here — asking for "the hub board" and "the local board" separately would be
|
|
7311
|
-
* asking for the same rows twice.
|
|
7312
|
-
*
|
|
7313
|
-
* `linear` is the user's Linear workspace, read and written through the Linear
|
|
7314
|
-
* MCP server they already registered. p4code holds no Linear credential of its
|
|
7315
|
-
* own; see `mcp/TicketResolver` for why that is the only way in.
|
|
7316
|
-
*
|
|
7317
|
-
* A per-call parameter rather than a setting: a person switches boards to look
|
|
7318
|
-
* at something and switches back, and a server-wide mode would make that a
|
|
7319
|
-
* configuration change with a blast radius across every open client.
|
|
7320
|
-
*/
|
|
7321
|
-
const TaskSource = Schema$1.Literals(["board", "linear"]);
|
|
7322
|
-
/**
|
|
7323
|
-
* The board a call means when it does not say. Every existing caller predates
|
|
7324
|
-
* the parameter and means p4code's own board, so the default has to be `board`
|
|
7325
|
-
* for them to keep working unchanged.
|
|
7326
|
-
*/
|
|
7327
|
-
const DEFAULT_TASK_SOURCE = "board";
|
|
7328
|
-
const TaskSourceField = TaskSource.pipe(Schema$1.annotateEncoded({ description: "Which board to act on: 'board' for p4code's own (the default), 'linear' for the linked Linear workspace." }));
|
|
7329
|
-
/**
|
|
7330
|
-
* Mixed into the RPC payloads rather than into `TaskCreateInput` and friends.
|
|
7331
|
-
*
|
|
7332
|
-
* Those inputs are also the hub's wire format and the MCP tools' argument
|
|
7333
|
-
* schemas, and a source belongs to neither: the hub *is* the board, so a
|
|
7334
|
-
* `source` field on its create route would be a field it must reject, and an
|
|
7335
|
-
* agent picking a tracker per call is a capability to add deliberately rather
|
|
7336
|
-
* than one to leak in by inheritance.
|
|
7337
|
-
*/
|
|
7338
|
-
const TaskSourceSelector = Schema$1.Struct({ source: Schema$1.optional(TaskSourceField) });
|
|
7339
|
-
/**
|
|
7340
|
-
* A per-prefix sequence prefix, e.g. `T` or `P4`. Declared by a project in its
|
|
7341
|
-
* `p4.json` (`taskPrefix`); always stored and compared uppercase.
|
|
7342
|
-
*/
|
|
7343
|
-
const TASK_READABLE_ID_PREFIX_PATTERN = /^[A-Za-z][A-Za-z0-9]{0,9}$/;
|
|
7344
|
-
/**
|
|
7345
|
-
* The shape of a readable task id: prefix, dash, 1-based sequence number.
|
|
7346
|
-
* Deliberately disjoint from the UUID `taskId`, which is what lets a lookup
|
|
7347
|
-
* accept either and tell them apart by shape alone.
|
|
7348
|
-
*/
|
|
7349
|
-
const TASK_READABLE_ID_PATTERN = /^[A-Za-z][A-Za-z0-9]{0,9}-[1-9][0-9]*$/;
|
|
7350
|
-
/** Whether a task reference is a readable id (`P4-12`) rather than a UUID. */
|
|
7351
|
-
const isReadableTaskId = (reference) => TASK_READABLE_ID_PATTERN.test(reference);
|
|
7352
|
-
const TaskLabel = TrimmedNonEmptyString.check(Schema$1.isMaxLength(TASK_LABEL_MAX_LENGTH));
|
|
7353
|
-
/**
|
|
7354
|
-
* Agent-facing field descriptions. These ride on the *encoded* side because
|
|
7355
|
-
* every one of these schemas is a brand or a check over a transformation, and
|
|
7356
|
-
* the JSON Schema an agent actually reads is generated from the encoded schema
|
|
7357
|
-
* — a plain `.annotate` on the decoded side is silently dropped.
|
|
7358
|
-
*/
|
|
7359
|
-
const ProjectIdField = ProjectId.pipe(Schema$1.annotateEncoded({ description: "Id of the project this task belongs to." }));
|
|
7360
|
-
const ThreadIdField = ThreadId.pipe(Schema$1.annotateEncoded({ description: "Id of the agent thread working this task." }));
|
|
7361
|
-
const TitleField = TrimmedNonEmptyString.check(Schema$1.isMaxLength(TASK_TITLE_MAX_LENGTH)).pipe(Schema$1.annotateEncoded({ description: "One-line summary of the work. Required." }));
|
|
7362
|
-
const BodyField = Schema$1.String.pipe(Schema$1.annotateEncoded({ description: "Markdown detail: context, acceptance criteria, links." }));
|
|
7363
|
-
const StatusField = TaskStatus.pipe(Schema$1.annotateEncoded({ description: "Board column: backlog (recorded, not queued), todo (queued next), in_progress, in_review, done, cancelled (closed without doing the work)." }));
|
|
7364
|
-
/**
|
|
7365
|
-
* The status as read back from a store, tolerant of values this build predates.
|
|
7366
|
-
*
|
|
7367
|
-
* A board can be shared between machines running different versions, and a
|
|
7368
|
-
* newer peer may write a status added after this client shipped. A strict
|
|
7369
|
-
* literal would fail the decode of the whole page, so one unknown row would
|
|
7370
|
-
* blank the board behind a "hub is unavailable" error rather than degrade. An
|
|
7371
|
-
* unrecognized status therefore decodes to {@link UNKNOWN_TASK_STATUS_FALLBACK},
|
|
7372
|
-
* same spirit as the nullable `readableId` and `orderKey` below. Encoding stays
|
|
7373
|
-
* strict: this client can only ever write a status it knows.
|
|
7374
|
-
*/
|
|
7375
|
-
const StoredStatusField = TaskStatus.pipe(Schema$1.catchDecoding(() => Effect.succeedSome(UNKNOWN_TASK_STATUS_FALLBACK)));
|
|
7376
|
-
const PriorityField = TaskPriority.pipe(Schema$1.annotateEncoded({ description: "Relative urgency: none, low, medium, high or urgent." }));
|
|
7377
|
-
const AssigneeField = TrimmedNonEmptyString.check(Schema$1.isMaxLength(TASK_ASSIGNEE_MAX_LENGTH)).pipe(Schema$1.annotateEncoded({ description: "Who owns the task — a person's handle or an agent name." }));
|
|
7378
|
-
/**
|
|
7379
|
-
* The portable half of "which repository is this task about".
|
|
7380
|
-
*
|
|
7381
|
-
* `projectId` cannot answer that across machines: it is a UUID minted when a
|
|
7382
|
-
* project is added, and the same checkout added on two servers gets two
|
|
7383
|
-
* different ids. A canonical repository key — the normalized primary git remote
|
|
7384
|
-
* — is the same string wherever the repo is cloned, so it survives the trip
|
|
7385
|
-
* through a shared board.
|
|
7386
|
-
*
|
|
7387
|
-
* Null for a task that is not about a repository, and also for a checkout with
|
|
7388
|
-
* no remote, where there is nothing stable to key on.
|
|
7389
|
-
*/
|
|
7390
|
-
const RepositoryKeyField = TrimmedNonEmptyString.pipe(Schema$1.annotateEncoded({ description: "Canonical repository key (normalized git remote, e.g. github.com/owner/repo). Portable across machines, unlike projectId." }));
|
|
7391
|
-
/**
|
|
7392
|
-
* Manual board position within a column, as a lexicographic fractional key.
|
|
7393
|
-
*
|
|
7394
|
-
* Compared as a plain string: smaller sorts higher. Keys are minted by the
|
|
7395
|
-
* board between the neighbours of a drop, so moving one card writes one row
|
|
7396
|
-
* rather than renumbering the column. `null` means the task has never been
|
|
7397
|
-
* placed by hand and falls back to the priority/recency order.
|
|
7398
|
-
*/
|
|
7399
|
-
const OrderKeyField = TrimmedNonEmptyString.pipe(Schema$1.annotateEncoded({ description: "Manual board position key. Lexicographic: smaller sorts higher in the column. Null means no manual position." }));
|
|
7400
|
-
/**
|
|
7401
|
-
* Which tracker issue a row *is*, for a row read out of a tracker.
|
|
7402
|
-
*
|
|
7403
|
-
* The identifier is the tracker's own, not ours: `MOBILE-12262` reads the same
|
|
7404
|
-
* in p4code as it does in Linear, in a commit message, and in conversation,
|
|
7405
|
-
* which is the whole point of carrying it. `url` is what the tracker itself
|
|
7406
|
-
* reported, never a guess assembled from the identifier - a Linear URL embeds
|
|
7407
|
-
* the workspace slug, so a constructed one would 404.
|
|
7408
|
-
*
|
|
7409
|
-
* Read-only, and only ever set by the `linear` source. p4code's own board does
|
|
7410
|
-
* not copy tracker issues into rows of its own: a ticket is read where it lives
|
|
7411
|
-
* and shown under the id its tracker gave it, which is why nothing here appears
|
|
7412
|
-
* in a create or update input.
|
|
7413
|
-
*/
|
|
7414
|
-
const TaskExternalRef = Schema$1.Struct({
|
|
7415
|
-
source: Schema$1.Literals(["linear"]),
|
|
7416
|
-
identifier: TrimmedNonEmptyString,
|
|
7417
|
-
url: TrimmedNonEmptyString
|
|
7418
|
-
});
|
|
7419
|
-
/**
|
|
7420
|
-
* The task this one was split out of.
|
|
7421
|
-
*
|
|
7422
|
-
* Carried as a field rather than as a sentence in the body because the plan
|
|
7423
|
-
* phase writes it and the board reads it: a parent chip, a subtask list and
|
|
7424
|
-
* "does this still have unfinished children" are all questions about the graph,
|
|
7425
|
-
* and prose cannot be asked them. Stored as the parent's UUID, but accepted as
|
|
7426
|
-
* either a UUID or a readable id on the way in - a planning agent works in
|
|
7427
|
-
* `P4-12`, and making it look up a UUID first is a round trip for nothing.
|
|
7428
|
-
*
|
|
7429
|
-
* One level is all this promises. Nothing forbids a subtask of a subtask, and
|
|
7430
|
-
* nothing walks the chain either; the board renders the direct children of a
|
|
7431
|
-
* task and stops.
|
|
7432
|
-
*/
|
|
7433
|
-
const ParentTaskIdField = TaskId.pipe(Schema$1.annotateEncoded({ description: "The task this one is a subtask of - the parent's UUID or readable id (e.g. P4-12). Null for a task that is not part of a larger one." }));
|
|
7434
|
-
const LabelsField = Schema$1.Array(TaskLabel).check(Schema$1.isMaxLength(TASK_LABELS_MAX_COUNT)).pipe(Schema$1.annotateEncoded({ description: "Free-form tags used to group and filter tasks." }));
|
|
7435
|
-
const TaskIdField = TaskId.pipe(Schema$1.annotateEncoded({ description: "Id of the task to act on - the UUID or the readable id (e.g. P4-12)." }));
|
|
7436
|
-
const Task = Schema$1.Struct({
|
|
7437
|
-
taskId: TaskId,
|
|
7438
|
-
/**
|
|
7439
|
-
* The human-readable id, e.g. `T-1` or `P4-12`: a per-prefix sequence
|
|
7440
|
-
* allocated by the store that owns the row, never reused, with gaps where
|
|
7441
|
-
* tasks were deleted. Nullable only for version skew - a row read from a
|
|
7442
|
-
* store that has not run the backfill migration decodes to `null` rather
|
|
7443
|
-
* than failing the whole board.
|
|
7444
|
-
*/
|
|
7445
|
-
readableId: Schema$1.NullOr(TrimmedNonEmptyString).pipe(Schema$1.withDecodingDefault(Effect.succeed(null))),
|
|
7446
|
-
/**
|
|
7447
|
-
* Manual board position: a lexicographic fractional key, compared as a plain
|
|
7448
|
-
* string, smaller first. Written only when a card is dragged to a position;
|
|
7449
|
-
* `null` means "never placed by hand" and the board falls back to its
|
|
7450
|
-
* priority/recency order. Nullable-with-default for the same version-skew
|
|
7451
|
-
* reason as `readableId`: a row from a store without the migration decodes to
|
|
7452
|
-
* `null` rather than failing the whole board.
|
|
7453
|
-
*/
|
|
7454
|
-
orderKey: Schema$1.NullOr(TrimmedNonEmptyString).pipe(Schema$1.withDecodingDefault(Effect.succeed(null))),
|
|
7455
|
-
/**
|
|
7456
|
-
* The project this task belongs to, or `null` for a task that is not scoped
|
|
7457
|
-
* to one. This references the existing workspace project registry rather than
|
|
7458
|
-
* a task-board-private notion of a project.
|
|
7459
|
-
*/
|
|
7460
|
-
projectId: Schema$1.NullOr(ProjectId),
|
|
7461
|
-
/**
|
|
7462
|
-
* Canonical repository key, resolved server-side from `projectId` when the
|
|
7463
|
-
* task is written. This is the field that survives a move to another machine;
|
|
7464
|
-
* `projectId` above is a local pointer and is meaningless on a server that
|
|
7465
|
-
* did not mint it. A board reading a shared task matches on this and looks up
|
|
7466
|
-
* whatever local project shares the key.
|
|
7467
|
-
*/
|
|
7468
|
-
repositoryKey: Schema$1.NullOr(TrimmedNonEmptyString),
|
|
7469
|
-
/**
|
|
7470
|
-
* The thread currently working this task, or `null`. A task links to at most
|
|
7471
|
-
* one thread at a time; re-running a task repoints this rather than appending
|
|
7472
|
-
* to a history.
|
|
7473
|
-
*/
|
|
7474
|
-
threadId: Schema$1.NullOr(ThreadId),
|
|
7475
|
-
/**
|
|
7476
|
-
* The tracker issue this row is, for a row read from a tracker, and `null`
|
|
7477
|
-
* for every row on p4code's own board. Nullable-with-default because the
|
|
7478
|
-
* board's own store has no such column at all - a row read from SQLite
|
|
7479
|
-
* decodes to `null` rather than failing the whole board.
|
|
7480
|
-
*/
|
|
7481
|
-
externalRef: Schema$1.NullOr(TaskExternalRef).pipe(Schema$1.withDecodingDefault(Effect.succeed(null))),
|
|
7482
|
-
/**
|
|
7483
|
-
* The task this one was split out of, always as a UUID once stored - a
|
|
7484
|
-
* readable id given on the way in is resolved before the row is written, so
|
|
7485
|
-
* the link survives a board where readable ids were allocated elsewhere.
|
|
7486
|
-
* Nullable-with-default for the same version-skew reason as `readableId`.
|
|
7487
|
-
*/
|
|
7488
|
-
parentTaskId: Schema$1.NullOr(TaskId).pipe(Schema$1.withDecodingDefault(Effect.succeed(null))),
|
|
7489
|
-
title: TrimmedNonEmptyString.check(Schema$1.isMaxLength(TASK_TITLE_MAX_LENGTH)),
|
|
7490
|
-
body: Schema$1.String,
|
|
7491
|
-
status: StoredStatusField,
|
|
7492
|
-
priority: TaskPriority,
|
|
7493
|
-
assignee: Schema$1.NullOr(TrimmedNonEmptyString.check(Schema$1.isMaxLength(TASK_ASSIGNEE_MAX_LENGTH))),
|
|
7494
|
-
labels: Schema$1.Array(TaskLabel).check(Schema$1.isMaxLength(TASK_LABELS_MAX_COUNT)),
|
|
7495
|
-
createdAt: IsoDateTime,
|
|
7496
|
-
updatedAt: IsoDateTime
|
|
7497
|
-
});
|
|
7498
|
-
const TaskCreateInput = Schema$1.Struct({
|
|
7499
|
-
projectId: Schema$1.optional(ProjectIdField),
|
|
7500
|
-
repositoryKey: Schema$1.optional(RepositoryKeyField),
|
|
7501
|
-
threadId: Schema$1.optional(ThreadIdField),
|
|
7502
|
-
parentTaskId: Schema$1.optional(ParentTaskIdField),
|
|
7503
|
-
title: TitleField,
|
|
7504
|
-
body: Schema$1.optional(BodyField),
|
|
7505
|
-
status: Schema$1.optional(StatusField),
|
|
7506
|
-
priority: Schema$1.optional(PriorityField),
|
|
7507
|
-
assignee: Schema$1.optional(AssigneeField),
|
|
7508
|
-
labels: Schema$1.optional(LabelsField)
|
|
7509
|
-
});
|
|
7510
|
-
/**
|
|
7511
|
-
* What an environment server sends the hub to file a task.
|
|
7512
|
-
*
|
|
7513
|
-
* Extends the public create input with the readable-id prefix, because the two
|
|
7514
|
-
* halves of the answer live on different machines: the prefix comes from the
|
|
7515
|
-
* project's checked-in `p4.json`, which only the submitting server can read,
|
|
7516
|
-
* while the sequence number must be allocated by the hub, which is the only
|
|
7517
|
-
* party that sees every machine's writes. Deliberately not part of
|
|
7518
|
-
* `TaskCreateInput` itself so an agent filing over MCP cannot pick a prefix -
|
|
7519
|
-
* the prefix belongs to the project, not to the caller.
|
|
7520
|
-
*/
|
|
7521
|
-
const HubTaskCreateInput = Schema$1.Struct({
|
|
7522
|
-
...TaskCreateInput.fields,
|
|
7523
|
-
readableIdPrefix: Schema$1.optional(TrimmedNonEmptyString),
|
|
7524
|
-
/**
|
|
7525
|
-
* Carried on the hub's create route (but not the public one) so a server
|
|
7526
|
-
* mirroring an already-placed row to the hub does not lose its position.
|
|
7527
|
-
* An agent filing a new task has no business picking a slot.
|
|
7528
|
-
*/
|
|
7529
|
-
orderKey: Schema$1.optional(OrderKeyField)
|
|
7530
|
-
});
|
|
7531
|
-
/**
|
|
7532
|
-
* A whole row, written to the hub at an id the sending machine chose.
|
|
7533
|
-
*
|
|
7534
|
-
* Every field is present and nullable rather than optional, because this is a
|
|
7535
|
-
* replace and not a patch: a task whose assignee was cleared locally has to
|
|
7536
|
-
* arrive as an explicit `null`, and under an optional field it would arrive as
|
|
7537
|
-
* an absence indistinguishable from "unchanged" — so the clear would never
|
|
7538
|
-
* propagate and the two machines would disagree forever about who owns it.
|
|
7539
|
-
*
|
|
7540
|
-
* `readableId` and `updatedAt` are absent for the opposite reason: they are
|
|
7541
|
-
* the hub's to assign. The hub allocates the readable id the first time it
|
|
7542
|
-
* sees an id and preserves it afterwards, which is what lets a task created
|
|
7543
|
-
* on an offline machine get its `P4-12` on the first push and keep it.
|
|
7544
|
-
*
|
|
7545
|
-
* `createdAt` does come from the sender. It is a fact about when the person
|
|
7546
|
-
* filed the task, and the hub learning about it late does not make it newer.
|
|
7547
|
-
*/
|
|
7548
|
-
const HubTaskPutInput = Schema$1.Struct({
|
|
7549
|
-
projectId: Schema$1.NullOr(ProjectIdField),
|
|
7550
|
-
repositoryKey: Schema$1.NullOr(RepositoryKeyField),
|
|
7551
|
-
threadId: Schema$1.NullOr(ThreadIdField),
|
|
7552
|
-
parentTaskId: Schema$1.NullOr(ParentTaskIdField),
|
|
7553
|
-
title: TitleField,
|
|
7554
|
-
body: BodyField,
|
|
7555
|
-
status: StatusField,
|
|
7556
|
-
priority: PriorityField,
|
|
7557
|
-
assignee: Schema$1.NullOr(AssigneeField),
|
|
7558
|
-
labels: LabelsField,
|
|
7559
|
-
orderKey: Schema$1.NullOr(OrderKeyField),
|
|
7560
|
-
createdAt: IsoDateTime,
|
|
7561
|
-
readableIdPrefix: Schema$1.optional(TrimmedNonEmptyString)
|
|
7562
|
-
});
|
|
7563
|
-
/**
|
|
7564
|
-
* Every field is optional and an omitted field is left untouched. The nullable
|
|
7565
|
-
* fields accept an explicit `null` to clear them, which is why they are
|
|
7566
|
-
* `NullOr` inside `optional` rather than merely optional.
|
|
7567
|
-
*/
|
|
7568
|
-
const TaskUpdateInput = Schema$1.Struct({
|
|
7569
|
-
taskId: TaskIdField,
|
|
7570
|
-
projectId: Schema$1.optional(Schema$1.NullOr(ProjectIdField)),
|
|
7571
|
-
repositoryKey: Schema$1.optional(Schema$1.NullOr(RepositoryKeyField)),
|
|
7572
|
-
threadId: Schema$1.optional(Schema$1.NullOr(ThreadIdField)),
|
|
7573
|
-
parentTaskId: Schema$1.optional(Schema$1.NullOr(ParentTaskIdField)),
|
|
7574
|
-
title: Schema$1.optional(TitleField),
|
|
7575
|
-
body: Schema$1.optional(BodyField),
|
|
7576
|
-
status: Schema$1.optional(StatusField),
|
|
7577
|
-
priority: Schema$1.optional(PriorityField),
|
|
7578
|
-
assignee: Schema$1.optional(Schema$1.NullOr(AssigneeField)),
|
|
7579
|
-
labels: Schema$1.optional(LabelsField),
|
|
7580
|
-
orderKey: Schema$1.optional(Schema$1.NullOr(OrderKeyField))
|
|
7581
|
-
});
|
|
7582
|
-
/**
|
|
7583
|
-
* A task an agent has drafted but has not filed.
|
|
7584
|
-
*
|
|
7585
|
-
* Mirrors `TaskCreateInput` minus the fields the proposer has no business
|
|
7586
|
-
* setting. `status` is absent on purpose: a confirmed proposal always lands in
|
|
7587
|
-
* `backlog`, so an interviewing agent cannot queue its own work — the property
|
|
7588
|
-
* the board's pick-up relies on. `taskId`, `threadId` and `repositoryKey` are
|
|
7589
|
-
* absent because they are minted or derived when the proposal is accepted, not
|
|
7590
|
-
* offered by the model.
|
|
7591
|
-
*/
|
|
7592
|
-
const TaskProposeInput = Schema$1.Struct({
|
|
7593
|
-
projectId: Schema$1.optional(ProjectIdField),
|
|
7594
|
-
title: TitleField,
|
|
7595
|
-
body: Schema$1.optional(BodyField),
|
|
7596
|
-
priority: Schema$1.optional(PriorityField),
|
|
7597
|
-
assignee: Schema$1.optional(AssigneeField),
|
|
7598
|
-
labels: Schema$1.optional(LabelsField)
|
|
7599
|
-
});
|
|
7600
|
-
/**
|
|
7601
|
-
* What `task_propose` returns. The tool writes nothing; its only effect is the
|
|
7602
|
-
* card the client renders from the call itself, so the result exists to tell
|
|
7603
|
-
* the model that nothing has been filed yet and it should stop and wait.
|
|
7604
|
-
*/
|
|
7605
|
-
const TaskProposeResult = Schema$1.Struct({ awaitingConfirmation: Schema$1.Literal(true) });
|
|
7606
|
-
/**
|
|
7607
|
-
* Parameters for `task_current`. The thread defaults to the caller's own, which
|
|
7608
|
-
* is the point of the tool — an agent does not know its own thread id. It is
|
|
7609
|
-
* still a real parameter rather than an empty struct because an empty struct
|
|
7610
|
-
* generates a root `anyOf` JSON Schema that some providers reject outright.
|
|
7611
|
-
*/
|
|
7612
|
-
const TaskCurrentInput = Schema$1.Struct({ threadId: Schema$1.optional(ThreadId.pipe(Schema$1.annotateEncoded({ description: "Thread whose linked task to read. Defaults to this agent session's own thread." }))) });
|
|
7613
|
-
/** Parameters for reading or targeting a single task by id. */
|
|
7614
|
-
const TaskGetInput = Schema$1.Struct({ taskId: TaskIdField });
|
|
7615
|
-
const TaskListFilter = Schema$1.Struct({
|
|
7616
|
-
projectId: Schema$1.optional(ProjectIdField),
|
|
7617
|
-
repositoryKey: Schema$1.optional(RepositoryKeyField),
|
|
7618
|
-
threadId: Schema$1.optional(ThreadIdField),
|
|
7619
|
-
status: Schema$1.optional(StatusField),
|
|
7620
|
-
assignee: Schema$1.optional(AssigneeField)
|
|
7621
|
-
});
|
|
7622
|
-
Schema$1.Union([Schema$1.Struct({
|
|
7623
|
-
type: Schema$1.Literal("upserted"),
|
|
7624
|
-
task: Task
|
|
7625
|
-
}), Schema$1.Struct({
|
|
7626
|
-
type: Schema$1.Literal("deleted"),
|
|
7627
|
-
taskId: TaskId
|
|
7628
|
-
})]);
|
|
7629
|
-
/**
|
|
7630
|
-
* What a board subscription carries: the current board, then every change.
|
|
7631
|
-
*
|
|
7632
|
-
* The snapshot rides on the stream rather than being fetched separately
|
|
7633
|
-
* because a list call followed by a subscribe has a gap — anything committed
|
|
7634
|
-
* between the two is lost, and the board then shows stale rows until something
|
|
7635
|
-
* else happens to touch them. The server takes the snapshot *after* it has
|
|
7636
|
-
* subscribed, so the worst case is an event the subscriber already has, which
|
|
7637
|
-
* is idempotent, rather than one it never sees.
|
|
7638
|
-
*/
|
|
7639
|
-
const TaskStreamEvent = Schema$1.Union([
|
|
7640
|
-
Schema$1.Struct({
|
|
7641
|
-
type: Schema$1.Literal("snapshot"),
|
|
7642
|
-
tasks: Schema$1.Array(Task)
|
|
7643
|
-
}),
|
|
7644
|
-
Schema$1.Struct({
|
|
7645
|
-
type: Schema$1.Literal("upserted"),
|
|
7646
|
-
task: Task
|
|
7647
|
-
}),
|
|
7648
|
-
Schema$1.Struct({
|
|
7649
|
-
type: Schema$1.Literal("deleted"),
|
|
7650
|
-
taskId: TaskId
|
|
7651
|
-
})
|
|
7652
|
-
]);
|
|
7653
|
-
Schema$1.Struct({
|
|
7654
|
-
patch: TaskUpdateInput,
|
|
7655
|
-
/** The status the caller believes the task is in. */
|
|
7656
|
-
expectedStatus: TaskStatus
|
|
7657
|
-
});
|
|
7658
|
-
Schema$1.Union([
|
|
7659
|
-
Schema$1.Struct({
|
|
7660
|
-
outcome: Schema$1.Literal("updated"),
|
|
7661
|
-
task: Task
|
|
7662
|
-
}),
|
|
7663
|
-
Schema$1.Struct({ outcome: Schema$1.Literal("missing") }),
|
|
7664
|
-
Schema$1.Struct({
|
|
7665
|
-
outcome: Schema$1.Literal("conflict"),
|
|
7666
|
-
current: Task
|
|
7667
|
-
})
|
|
7668
|
-
]);
|
|
7669
|
-
/**
|
|
7670
|
-
* Start an agent on a task.
|
|
7671
|
-
*
|
|
7672
|
-
* The input is only the task, deliberately. Everything else the thread needs —
|
|
7673
|
-
* which project, which model, what prompt — is resolved server-side from the
|
|
7674
|
-
* task's own project, because the caller that matters next is not a person
|
|
7675
|
-
* clicking a button but Phase 12's orchestrator, and a client that supplies the
|
|
7676
|
-
* model is a client that can start a task on a provider the user does not use.
|
|
7677
|
-
*
|
|
7678
|
-
* Worktrees are absent for a harder reason: `prepareWorktree` needs a base
|
|
7679
|
-
* branch, a task carries none, and nothing resolves one server-side. A thread
|
|
7680
|
-
* started from a task therefore runs in the project's own checkout.
|
|
7681
|
-
*/
|
|
7682
|
-
const TaskStartThreadInput = Schema$1.Struct({ taskId: TaskIdField });
|
|
7683
|
-
/**
|
|
7684
|
-
* The started thread and the task as it now stands.
|
|
7685
|
-
*
|
|
7686
|
-
* Both are returned because both changed: the caller navigates to the thread,
|
|
7687
|
-
* and the board would otherwise show the old status until the change event
|
|
7688
|
-
* arrived.
|
|
7689
|
-
*/
|
|
7690
|
-
const TaskStartThreadResult = Schema$1.Struct({
|
|
7691
|
-
task: Task,
|
|
7692
|
-
threadId: ThreadId
|
|
7693
|
-
});
|
|
7694
|
-
/**
|
|
7695
|
-
* A task cannot be started, and the reason is a property of the task or the
|
|
7696
|
-
* server rather than a failure.
|
|
7697
|
-
*
|
|
7698
|
-
* Each reason is a different thing for the user to fix, which is why this is a
|
|
7699
|
-
* union and not one message. `no-model` is the one worth explaining: a task
|
|
7700
|
-
* carries no model selection, so the thread runs on the project's default. The
|
|
7701
|
-
* server-side fallback that exists for first-run bootstrap is deliberately not
|
|
7702
|
-
* used here — it hardcodes a provider, so reaching for it would silently start
|
|
7703
|
-
* someone who works in Claude on Codex. Failing is the honest answer.
|
|
7704
|
-
*/
|
|
7705
|
-
const TaskNotStartableReason = Schema$1.Literals([
|
|
7706
|
-
"no-project",
|
|
7707
|
-
"project-missing",
|
|
7708
|
-
"no-model"
|
|
7709
|
-
]);
|
|
7710
|
-
var TaskNotStartableError = class extends Schema$1.TaggedErrorClass()("TaskNotStartableError", {
|
|
7711
|
-
taskId: TaskId,
|
|
7712
|
-
reason: TaskNotStartableReason
|
|
7713
|
-
}) {
|
|
7714
|
-
get message() {
|
|
7715
|
-
switch (this.reason) {
|
|
7716
|
-
case "no-project": return "This task has no project, so there is nowhere to run it. Set a project on the task first.";
|
|
7717
|
-
case "project-missing": return "This task's project is not registered on this server. Point it at a project that is.";
|
|
7718
|
-
case "no-model": return "This task's project has no default model. Choose one in the project's settings, then start the task again.";
|
|
7719
|
-
}
|
|
7720
|
-
}
|
|
7721
|
-
};
|
|
7722
|
-
/** Result shape for the board's list RPC. */
|
|
7723
|
-
const TaskListResult$1 = Schema$1.Struct({ tasks: Schema$1.Array(Task) });
|
|
7724
|
-
/** Result shape for every RPC that resolves to a single task. */
|
|
7725
|
-
const TaskResult = Schema$1.Struct({ task: Task });
|
|
7726
|
-
/** `get` distinguishes "no such task" from an error, so the task is nullable. */
|
|
7727
|
-
const TaskGetResult = Schema$1.Struct({ task: Schema$1.NullOr(Task) });
|
|
7728
|
-
var TaskToolUnavailableError = class extends Schema$1.TaggedErrorClass()("TaskToolUnavailableError", {
|
|
7729
|
-
capability: Schema$1.Literal("tasks"),
|
|
7730
|
-
environmentId: EnvironmentId,
|
|
7731
|
-
threadId: ThreadId,
|
|
7732
|
-
providerSessionId: TrimmedNonEmptyString,
|
|
7733
|
-
providerInstanceId: ProviderInstanceId
|
|
7734
|
-
}) {
|
|
7735
|
-
get message() {
|
|
7736
|
-
return `MCP credential does not grant the ${this.capability} capability.`;
|
|
7737
|
-
}
|
|
7738
|
-
};
|
|
7739
|
-
var TaskNotFoundError = class extends Schema$1.TaggedErrorClass()("TaskNotFoundError", { taskId: TaskId }) {
|
|
7740
|
-
get message() {
|
|
7741
|
-
return `No task exists with id ${this.taskId}.`;
|
|
7742
|
-
}
|
|
7743
|
-
};
|
|
7744
|
-
/**
|
|
7745
|
-
* A task tool hit the store and the store failed. The underlying persistence
|
|
7746
|
-
* error is deliberately not surfaced to the agent — it carries SQL detail that
|
|
7747
|
-
* is noise in a tool result — but it is logged server-side.
|
|
7748
|
-
*/
|
|
7749
|
-
var TaskStoreError = class extends Schema$1.TaggedErrorClass()("TaskStoreError", { operation: TrimmedNonEmptyString }) {
|
|
7750
|
-
get message() {
|
|
7751
|
-
return `The task store failed during ${this.operation}.`;
|
|
7752
|
-
}
|
|
7753
|
-
};
|
|
7754
|
-
/**
|
|
7755
|
-
* No task is linked to the calling agent's thread.
|
|
7756
|
-
*/
|
|
7757
|
-
var TaskNotLinkedError = class extends Schema$1.TaggedErrorClass()("TaskNotLinkedError", { threadId: ThreadId }) {
|
|
7758
|
-
get message() {
|
|
7759
|
-
return `No task is linked to thread ${this.threadId}.`;
|
|
7760
|
-
}
|
|
7761
|
-
};
|
|
7762
|
-
const TaskToolError = Schema$1.Union([
|
|
7763
|
-
TaskToolUnavailableError,
|
|
7764
|
-
TaskNotFoundError,
|
|
7765
|
-
TaskNotLinkedError,
|
|
7766
|
-
TaskStoreError
|
|
7767
|
-
]);
|
|
7768
|
-
//#endregion
|
|
7769
7778
|
//#region ../../packages/contracts/src/p4ProjectFile.ts
|
|
7770
7779
|
/** File name of the checked-in P4 project file, resolved at the workspace root. */
|
|
7771
7780
|
const P4_PROJECT_FILE_NAME = "p4.json";
|
|
@@ -7790,7 +7799,7 @@ const P4ProjectFile = Schema$1.Struct({
|
|
|
7790
7799
|
$schema: Schema$1.optionalKey(Schema$1.String.annotate({ description: `URL of the JSON Schema for this file, typically "${P4_PROJECT_FILE_SCHEMA_URL}".` })),
|
|
7791
7800
|
iconPath: Schema$1.optionalKey(trimmedNonEmpty({ description: "Workspace-relative path to the project icon (e.g. \"assets/logo.svg\"). Checked before P4Code's built-in icon locations." }, P4_PROJECT_FILE_PATH_MAX_LENGTH)),
|
|
7792
7801
|
scripts: Schema$1.optionalKey(Schema$1.Array(P4ProjectFileScript).annotate({ description: "Project scripts shared with everyone who opens this repository in P4Code." }).check(Schema$1.isMaxLength(P4_PROJECT_FILE_MAX_SCRIPTS))),
|
|
7793
|
-
taskPrefix: Schema$1.optionalKey(trimmedNonEmpty({ description: "
|
|
7802
|
+
taskPrefix: Schema$1.optionalKey(trimmedNonEmpty({ description: "Checked-in prefix for this project's readable task ids on the task board (e.g. \"P4\" numbers tasks P4-1, P4-2, ...). Letters and digits, starting with a letter, at most 10 characters; compared case-insensitively. A project-specific Settings override takes precedence." }).check(Schema$1.isPattern(TASK_READABLE_ID_PREFIX_PATTERN)))
|
|
7794
7803
|
}).annotate({
|
|
7795
7804
|
title: "P4 project file",
|
|
7796
7805
|
description: "Checked-in project configuration for P4Code (p4.json at the repository root). See https://p4.codes for documentation."
|
|
@@ -37552,22 +37561,36 @@ const resolveTaskRepositoryKey = Effect.fn("taskBoard.resolveRepositoryKey")(fun
|
|
|
37552
37561
|
if (Option.isNone(project)) return null;
|
|
37553
37562
|
return (yield* (yield* RepositoryIdentityResolver).resolve(project.value.workspaceRoot))?.canonicalKey ?? null;
|
|
37554
37563
|
});
|
|
37564
|
+
/** Resolve configured prefix precedence without filesystem or store effects. */
|
|
37565
|
+
function selectTaskReadableIdPrefix(input) {
|
|
37566
|
+
return (input.projectOverride ?? input.projectFilePrefix ?? input.defaultPrefix).toUpperCase();
|
|
37567
|
+
}
|
|
37555
37568
|
/**
|
|
37556
37569
|
* Resolve the readable-id prefix for a task's project.
|
|
37557
37570
|
*
|
|
37558
|
-
*
|
|
37559
|
-
*
|
|
37560
|
-
*
|
|
37561
|
-
* project, a removed project, or a `p4.json` without the field all fall back to
|
|
37562
|
-
* the default `T` - filing the task always beats refusing it over a label.
|
|
37571
|
+
* A server setting may override each project. Without one, the checked-in
|
|
37572
|
+
* `p4.json` value remains portable across clones. The configured default is
|
|
37573
|
+
* used when neither exists, including tasks with no project.
|
|
37563
37574
|
*/
|
|
37564
37575
|
const resolveTaskReadableIdPrefix = Effect.fn("taskBoard.resolveReadableIdPrefix")(function* (projectId) {
|
|
37565
|
-
|
|
37576
|
+
const current = yield* (yield* ServerSettingsService).getSettings.pipe(Effect.orElseSucceed(() => ({
|
|
37577
|
+
defaultTaskPrefix: "T",
|
|
37578
|
+
projectTaskPrefixes: {}
|
|
37579
|
+
})));
|
|
37580
|
+
if (projectId == null) return selectTaskReadableIdPrefix({ defaultPrefix: current.defaultTaskPrefix });
|
|
37581
|
+
const override = current.projectTaskPrefixes[projectId];
|
|
37582
|
+
if (override !== void 0) return selectTaskReadableIdPrefix({
|
|
37583
|
+
defaultPrefix: current.defaultTaskPrefix,
|
|
37584
|
+
projectOverride: override
|
|
37585
|
+
});
|
|
37566
37586
|
const project = yield* (yield* ProjectionProjectRepository).getById({ projectId }).pipe(Effect.catchCause(() => Effect.succeed(Option.none())));
|
|
37567
|
-
if (Option.isNone(project)) return
|
|
37587
|
+
if (Option.isNone(project)) return selectTaskReadableIdPrefix({ defaultPrefix: current.defaultTaskPrefix });
|
|
37568
37588
|
const file = yield* (yield* P4ProjectFileLoader).load(project.value.workspaceRoot);
|
|
37569
|
-
if (Option.isNone(file) || file.value.taskPrefix === void 0) return
|
|
37570
|
-
return
|
|
37589
|
+
if (Option.isNone(file) || file.value.taskPrefix === void 0) return selectTaskReadableIdPrefix({ defaultPrefix: current.defaultTaskPrefix });
|
|
37590
|
+
return selectTaskReadableIdPrefix({
|
|
37591
|
+
defaultPrefix: current.defaultTaskPrefix,
|
|
37592
|
+
projectFilePrefix: file.value.taskPrefix
|
|
37593
|
+
});
|
|
37571
37594
|
});
|
|
37572
37595
|
/** Project settings + live snapshots into the two lookups a start needs. */
|
|
37573
37596
|
const buildTaskStartProviderDefaults = (input) => ({
|
|
@@ -88308,14 +88331,22 @@ const writeDependencies = [
|
|
|
88308
88331
|
RepositoryIdentityResolver
|
|
88309
88332
|
];
|
|
88310
88333
|
/**
|
|
88311
|
-
* `task_create` also mints the task id
|
|
88312
|
-
* prefix from
|
|
88334
|
+
* `task_create` also mints the task id, routes to the requested board, and
|
|
88335
|
+
* resolves the readable-id prefix from settings or `p4.json`.
|
|
88313
88336
|
*/
|
|
88314
88337
|
const createDependencies = [
|
|
88315
88338
|
...writeDependencies,
|
|
88316
88339
|
Crypto.Crypto,
|
|
88317
|
-
P4ProjectFileLoader
|
|
88340
|
+
P4ProjectFileLoader,
|
|
88341
|
+
ServerSettingsService,
|
|
88342
|
+
TaskRepositoryRegistry,
|
|
88343
|
+
ProjectionThreadRepository
|
|
88318
88344
|
];
|
|
88345
|
+
const TaskCreateToolInput = Schema$1.Struct({
|
|
88346
|
+
...TaskCreateInput.fields,
|
|
88347
|
+
...TaskSourceSelector.fields,
|
|
88348
|
+
linkCurrentThread: Schema$1.optional(Schema$1.Boolean.annotate({ description: "Link the new task or ticket to this conversation. Use this when the user asks to capture the current chat as work." }))
|
|
88349
|
+
});
|
|
88319
88350
|
const TaskListResult = Schema$1.Struct({ tasks: Schema$1.Array(Task) });
|
|
88320
88351
|
const TaskListTool = Tool.make("task_list", {
|
|
88321
88352
|
description: "List tasks on the board, most recently updated first. Every filter is optional and they combine with AND; omit all of them to list every task. Use this to find work rather than guessing task ids.",
|
|
@@ -88339,8 +88370,8 @@ const TaskCurrentTool = Tool.make("task_current", {
|
|
|
88339
88370
|
dependencies
|
|
88340
88371
|
}).annotate(Tool.Title, "Get the current task").annotate(Tool.Readonly, true).annotate(Tool.Destructive, false).annotate(Tool.Idempotent, true);
|
|
88341
88372
|
const TaskCreateTool = Tool.make("task_create", {
|
|
88342
|
-
description: "File a new task on the board. Defaults to the 'backlog' status
|
|
88343
|
-
parameters:
|
|
88373
|
+
description: "File a new task on the p4code board, or a Linear ticket with source 'linear'. Defaults to the 'backlog' status. Set linkCurrentThread to true when the user asks to capture this conversation as a task or ticket; the new row then appears in this chat header. Set status to 'todo' only when the work is genuinely queued next.",
|
|
88374
|
+
parameters: TaskCreateToolInput,
|
|
88344
88375
|
success: Task,
|
|
88345
88376
|
failure: TaskToolError,
|
|
88346
88377
|
dependencies: createDependencies
|
|
@@ -88449,16 +88480,25 @@ const TaskToolkitHandlersLive = TaskToolkit.toLayer({
|
|
|
88449
88480
|
return current;
|
|
88450
88481
|
}),
|
|
88451
88482
|
task_create: (input) => Effect.gen(function* () {
|
|
88452
|
-
yield* requireTaskCapability();
|
|
88453
|
-
const
|
|
88483
|
+
const invocation = yield* requireTaskCapability();
|
|
88484
|
+
const taskRepositories = yield* TaskRepositoryRegistry;
|
|
88485
|
+
const threads = yield* ProjectionThreadRepository;
|
|
88486
|
+
const { source, linkCurrentThread, ...createInput } = input;
|
|
88487
|
+
const tasks = taskRepositories.forSource(source);
|
|
88488
|
+
const currentProjectId = linkCurrentThread === true && createInput.projectId === void 0 ? Option.getOrUndefined(yield* threads.getById({ threadId: invocation.threadId }).pipe(Effect.catch(toTaskStoreError("task_create:current_thread"))))?.projectId : void 0;
|
|
88454
88489
|
const crypto = yield* Crypto.Crypto;
|
|
88455
88490
|
const taskId = TaskId.make(yield* crypto.randomUUIDv4.pipe(Effect.orDie));
|
|
88456
88491
|
const timestamp = yield* nowIso$1;
|
|
88457
|
-
const
|
|
88458
|
-
|
|
88459
|
-
|
|
88460
|
-
|
|
88461
|
-
|
|
88492
|
+
const linkedInput = linkCurrentThread === true ? {
|
|
88493
|
+
...createInput,
|
|
88494
|
+
...currentProjectId === void 0 ? {} : { projectId: currentProjectId },
|
|
88495
|
+
threadId: invocation.threadId
|
|
88496
|
+
} : createInput;
|
|
88497
|
+
const repositoryKey = yield* resolveTaskRepositoryKey(linkedInput.projectId);
|
|
88498
|
+
const readableIdPrefix = yield* resolveTaskReadableIdPrefix(linkedInput.projectId);
|
|
88499
|
+
const parentTaskId = yield* resolveParentTaskId(linkedInput.parentTaskId, "task_create");
|
|
88500
|
+
const row = buildTaskRow(parentTaskId === void 0 || parentTaskId === null ? linkedInput : {
|
|
88501
|
+
...linkedInput,
|
|
88462
88502
|
parentTaskId
|
|
88463
88503
|
}, taskId, timestamp, repositoryKey);
|
|
88464
88504
|
return yield* tasks.create(row, readableIdPrefix).pipe(Effect.catch(toTaskStoreError("task_create")));
|